質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.80%

Arduinoでタクトスイッチが押された回数でサーボの動作を変えたい

解決済

回答 3

投稿

  • 評価
  • クリップ 0
  • VIEW 1,513

前提・実現したいこと

自動餌やり機を作ろうとしています。
まず、タクトスイッチが押された回数を数え、switch case文を使って条件分けをし、時間をカウントして何種類かのモードでサーボを動作させたいです。
例)一回押された→5時間後にサーボが動く。二回押された→10時間後にサーボが動く。
実験では5秒後、10秒後でやってます。
また、LCDにも今の状態やモードを表示させたいです。

※ガチのArduino初心者なので長く見苦しいコードすみません。

発生している問題・エラーメッセージ

タクトスイッチを押しても変化が無い。

ソースコード

#include <Servo.h>
#include <LiquidCrystal.h>

Servo servo1;  //Servoオブジェクトの宣言
Servo servo2;  //Servoオブジェクトの宣言
LiquidCrystal lcd(12, 11, 5, 4, 3, 2); //lcd(RS,ENABLE,D4,D5,D6,D7)を宣言
unsigned long time;

void setup() {
  Serial.begin(9600) ;   // 9600bpsでシリアル通信のポートを開く
  lcd.begin(16, 2);       //(桁数,行数)を指定
  pinMode(7,INPUT);
  pinMode(10,OUTPUT);
  servo1.attach(13); //servo変数をピンに割り当てる、ここでは13番ピン
  servo2.attach(8);  //servo変数をピンに割り当てる、ここでは8番ピン
  servo1.write(90);  //角度を指定、ここでは90度
  servo2.write(90);  //角度を指定、ここでは90度

}

void loop() {
  time=millis();

  int val=0;

  for(;;){
    if(digitalRead(7)==HIGH){
      val=val+1;
      if(val>2)
        val=0;
        delay(20);
        while(digitalRead(2)==LOW) {}
    }
  switch(val){
    case 0:
      servo1.write(105);
      lcd.clear();
      lcd.print("close");
      lcd.setCursor(0,1);
      lcd.print("mode serect");
      delay(100);
      digitalWrite(10,HIGH);
      break;
    case 1:
      servo1.write(105);
      lcd.clear();
      lcd.print("mode 1");
      if(time>5000){
      lcd.clear();
      lcd.print("open");
      servo1.write(50);
      delay(1000);
      break;
      }
      else{}
    case 2:
      servo1.write(105);
      lcd.clear();
      lcd.print("mode 2");
      if(time>10000){
      lcd.clear();
      lcd.print("open");
      servo1.write(50);
      delay(1000);
      break;
      }
      else{}
    default:{}
  }
  }
  lcd.clear();
  lcd.print("end");
  delay(5000);
  servo2.write(0);
  delay(5000);
}

試したこと

for文を消してみて、int val=1;にしてみたが、case1で時間がカウントされず、一瞬でswitch文を抜け出してしまう。
そもそもcase文も違うのか?

補足

もうお手上げです。
サーボの角度は今は適当です。
サーボ2の動作で物理的に電源スイッチを切れるような機構にして強制的にArduinoの電源を切る予定です。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

checkベストアンサー

0

なによりもまず。
    if(digitalRead(7)==HIGH){
としていますが、スイッチの回路はどうなっているでしょう。
初心者にありがちな致命的ミスとして、電源(5V)からスイッチを通してPINに繋いでいるだけ、という配線をしがちで、そうするとこのプログラムになるのですが。(プログラムがこうだから必ず間違っている、というわけでもないです)
スイッチがOFFのときにマイコンのピンはどこにも繋がっていない(電位が固定されない)状態になるわけで、それはやってはいけないことです。
ハードウェアが間違っていると、ソフトがどんなに頑張ってもまともに動かせないことも多いです。


ソフト面でとてもとても大きな勘違いがあるのではないかと思うのが、
  time=millis();
です。
この後で
      if(time>5000){
とか
      if(time>10000){
という使い方をしていますが、time=millis();というのはその後timeを参照するとその時点でのmillis()が取得できるということではありません
あくまで、プログラムを順次実行していって、time=millis();という文に到達したときにmillis()という関数を実行して得られた結果の値がtimeに入るだけです。timeが例えば10という値になったら、新たにtime=millis()等で値が代入されるまではず~っと10でしかありません。なんなら、

void setup(){
  Serial.begin(9600);
}
void loop(){
  unsigned long time=millis();
  while(1){
    Serial.println(time);
  }
}


とかいうプログラムで確かめて見ればいいです。このプログラムでは表示される値はず~っと変わりません。
ボタンが押されてからの経過時間を知りたいのなら、ボタンが押されたときにtime=millis();として値を取得し、millis()-timeを計算することで経過時間を得ることになるでしょう。


すでに指摘がある部分、
        while(digitalRead(2)==LOW) {}
ここで2pinをどうこうする、というのはあり得ませんね。7pinの間違い、また、スイッチが「押されている間」ここにとどまっていたいということでしょうから、digitalRead()がHIGHかLOWしか返さないのでどちらも同じことですが、
        while(digitalRead(7) == HIGH); //慣習的に{}ではなく;
あるいは
        while(digitalRead(7) != LOW);
でしょうね。


あとよくある間違いというか。
switch文というのは、switchのカッコの中の値を調べて、それと同じ値のcaseラベル(一致するものがなければdefaultラベル)に飛ぶ、というそれだけの動作をします。それ以上のなにかはありません。なので、

  switch(val){
    case 0:
      Serial.println("#0");
    case 1:
      Serial.println("#1");
    case 2:
      Serial.println("#2");
    default:
      Serial.println("#d");
  }


というのは、valが0の場合

#0
#1
#2
#d


valが2なら

#2
#d


という出力をします。あくまで、caseラベルに飛ぶというだけで次のラベルで止まるとかそういう仕組みはありませんから。
もし、valが0の時に以降の出力をしたくないのなら(そしてそれぞれvalの値だけを出力したいなら)

  switch(val){
    case 0:
      Serial.println("#0");
      break; //switch文を終了する
    case 1:
      Serial.println("#1");
      break;
    case 2:
      Serial.println("#2");
      break;
    default:
      Serial.println("#d");
      break;
  }


のようにbreakを入れてやらなければいけません。


これぐらい修正すれば動かないかなぁ...(確認していません)


追記。
for(;;)を抜ける手段が用意されていなくて「電源を切る」ところに行き着かないので...
ちょっと格好はよくないですけれど、餌をやり終わったら電源OFF、もcaseの中でやっちゃいましょうか。

    case 1:
      servo1.write(105);
      lcd.clear();
      lcd.print("mode 1");
      if(time>5000){
        lcd.clear();
        lcd.print("open");
        servo1.write(50);
        delay(5000);
        lcd.clear();
        lcd.print("end");//このendはほとんど表示される間もなく電源が落ちるかも
        servo2.write(0);//このサーボ動作で電源OFF
        while(1); //念の為、無限ループで動作を止める
      break;
    case 2:
      //同様の変更


そうすると、ついでに(?)for(;;)そのものが不要になったりしますけれど。

int val = 0; //valはloop()を出ても保存される必要がある

void loop() {
  if (digitalRead(7) != LOW) {//LOWと比較するのは私の趣味と思って下さい。一応理由はありますが。
    time = millis();
    val = val + 1;
    if (val > 2)
      val = 0;
    delay(20);
    while (digitalRead(7) != LOW) {}
  }
  switch (val) {
      //略
  }
}


とか。


とりあえず解決になったところで、好き勝手に書いてみたのでご参考まで。コンパイルは通ってるけど動作確認はしてません。

#include <Servo.h>
#include <LiquidCrystal.h>

//変わりそうな値はプログラム中に値を書き込まず、一旦変数を介する
//HARDWARE SETTINGS
const int PIN_BUTTON    = 7;
const int PIN_servoFeed = 13;
const int PIN_servoPwr  = 8;

//ANGLE DEFINITIONS
const int ANG_CLOSE   = 105;//餌箱閉じる角度
const int ANG_FEEDING = 50; //餌箱開く
const int ANG_PON     = 90; //電源ON
const int ANG_POFF    = 0;  //電源OFF

//TIME DEFINITIONS
const int TM_MODE1 = 5000;//餌箱動作時間1
const int TM_MODE2 = 10000;//同2
const int TM_FEED  = 5000; //給餌時間
const int TM_WAIT   = 1000; //汎用待ち時間
const int TM_POFF  =  500; //電源OFF待ち時間

//自明な、プログラムそのままのことはコメントに書かない

//DEVICE INSTANCE
Servo servoFeed; //給餌用サーボ
Servo servoPwr;  //終了時電源スイッチ操作してOFFするサーボ
LiquidCrystal lcd(12, 11, 5, 4, 3, 2); //lcd(RS,ENABLE,D4,D5,D6,D7)を宣言

//valiables
unsigned long time;   //時間測定用時刻保持
int val = 0;          //待ち時間モード保持
bool feeding = false; //給餌開始かどうか
String modeStr;       //モード表示の文字列
int feederAngle;      //餌箱サーボの角度
int pwrAngle;         //電源制御の角度

void setup() {
  Serial.begin(9600) ; // 9600bpsでシリアル通信のポートを開く
  lcd.begin(16, 2); //(桁数,行数)を指定
  pinMode(PIN_BUTTON, INPUT);
  pinMode(10, OUTPUT); //10PIN?
  servoFeed.attach(PIN_servoFeed);
  servoPwr.attach(PIN_servoPwr);
  feederAngle = ANG_CLOSE;
  servoFeed.write(feederAngle);
  pwrAngle = ANG_PON;
  servoPwr.write(pwrAngle);
}

void lcdPrint(String line1, String line2 = "") {
  lcd.clear();
  lcd.print(line1);
  lcd.setCursor(0, 1);
  lcd.print(line2);
}

void loop() {
  if (!feeding) {
    feederAngle = ANG_CLOSE;
    unsigned long tm;
    if (digitalRead(PIN_BUTTON) == HIGH) {
      //ボタンが押されたら
      time = millis(); //時計リセット
      //モード変更
      val = val + 1;
      if (val > 2)
        val = 0;
      //モードの設定
      switch (val) {
        case 0:
          modeStr = "mode select";
          digitalWrite(10, HIGH);//???
          break;
        case 1:
          modeStr = "mode 1";
          tm = TM_MODE1;
          break;
        case 2:
          modeStr = "mode 2";
          tm = TM_MODE2;
          break;
        default:
          break;
      }
      lcdPrint("close", modeStr);
      delay(20);
      //ボタンが離されるのを待つ
      while (digitalRead(PIN_BUTTON) == HIGH) {}
    }
    if ( val != 0 && (millis() - time > tm) ) {
      //時間が来た
      feeding = true;//給餌に遷移
      lcdPrint("open", modeStr);
      time = millis();
    }
  } else {
    //給餌開始
    feederAngle = ANG_FEEDING;
    if (millis() - time > TM_FEED) {
      feederAngle = ANG_CLOSE;
      lcdPrint("end");
      if (millis() - time > TM_FEED + TM_POFF) {
        pwrAngle = ANG_POFF;
      }
    }
  }
  //サーボ制御はここで一括
  servoFeed.write(feederAngle);
  servoPwr.write(pwrAngle);
}

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/10/22 23:10

    あ、あとfor(;;)から出られない問題があるか...単純に無くすと問題が起こるし、さてどうするかな。

    キャンセル

  • 2019/10/24 23:53

    御回答ありがとうございます!
    タクトスイッチの回路はプルダウン回路にしているので問題無いです。

    >> time=millis();というのはその後timeを参照するとその時点でのmillis()が取得できるということではありません。
    millis()関数、どうやら大きな勘違いをしていました。プログラム例で理解できました。ありがとうございました。

    >> for(;;)を抜ける手段が用意されていなくて「電源を切る」ところに行き着かないので...
    ちょっと格好はよくないですけれど、餌をやり終わったら電源OFF、もcaseの中でやっちゃいましょうか。
    サーボ1の動作後はもう動かす予定はないので確かにcaseの中でやっちゃったほうが良いですね。そうすればfor文が省けますし。笑 

    アドバイスを参考にしてプログラム書き直してみたら自分が思ってたような動作になりました。ありがとうございました!

    キャンセル

0

ツッコミどころはいろいろありますが、、

  if(digitalRead(7)==HIGH){
val=val+1;

コレだと、SWをおしてるあいだ、ずっとvalが加算され続けられます
こういうことをする場合は、OFFからONになった瞬間を判断するようにしないといけません

#って、これだけを修正してもまだダメだけど

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

0

完全な回答ではないですが、パッと気になったところを、、
・digitalRead(7)==HIGHを検出したあと、digitalRead(2)==LOW になるまで待っていますが、これは正しいですか?
・現状だとfor(;;)から抜けられないように見えます。そのためtimeの値も変化しません。

[ 解決案 ]
・for(;;)を無くす。
・int val=0; をsetupで行う。
・餌をやったかどうかのフラグを用意する。
・餌をやった時だけ、最後のlcd.clear〜を実行する

これだけではダメかもしれませんが、少し正解に近づくかと思います。
またインデントを整えるとプログラムの流れが見やすくなるので、是非参考にしてください。

[ 追記 ]

if (digitalRead(7)==HIGH) {
    //....
    while(digitalRead(7)==LOW) {}
}


(2が7の間違いとして) これだと while(digitalRead(7)==LOW)の条件は最初の判別でおそらくfalseになるので、待たずに次に行ってしまいますね。。while(digitalRead(7)!=LOW)が正しいかと。
thkanaさん補足ありがとうございます!

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/10/22 10:39

    インデントは、Arduino IDEであれば[CTRL]+Tキー一発で自動整形してくれますので是非。

    キャンセル

  • 2019/10/24 23:51

    御回答ありがとうございます。

    >>digitalRead(7)==HIGHを検出したあと、digitalRead(2)==LOW になるまで待っていますが、これは正しいですか?
    これは完全に私のミスで、2ピンではなく7ピンの間違いでした。すみません。

    >>while(digitalRead(7)==LOW)の条件は最初の判別でおそらくfalseになるので、待たずに次に行ってしまいますね。。while(digitalRead(7)!=LOW)が正しいかと。
    なるほど、確かに最初の判別でfalseになりそうですね。そこまで考えていませんでした。。

    自動整形の機能も初めて知りましたm(__)mありがとうございます。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.80%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る