🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Arduino

Arduinoは、AVRマイコン、単純なI/O(入出力)ポートを備えた基板、C言語を元としたArduinoのプログラム言語と、それを実装した統合開発環境から構成されたシステムです。

Q&A

解決済

3回答

3287閲覧

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

marblechocolate

総合スコア5

Arduino

Arduinoは、AVRマイコン、単純なI/O(入出力)ポートを備えた基板、C言語を元としたArduinoのプログラム言語と、それを実装した統合開発環境から構成されたシステムです。

0グッド

0クリップ

投稿2019/10/21 16:41

前提・実現したいこと

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

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

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

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

ソースコード

Arduino

1#include <Servo.h> 2#include <LiquidCrystal.h> 3 4Servo servo1; //Servoオブジェクトの宣言 5Servo servo2; //Servoオブジェクトの宣言 6LiquidCrystal lcd(12, 11, 5, 4, 3, 2); //lcd(RS,ENABLE,D4,D5,D6,D7)を宣言 7unsigned long time; 8 9void setup() { 10 Serial.begin(9600) ; // 9600bpsでシリアル通信のポートを開く 11 lcd.begin(16, 2); //(桁数,行数)を指定 12 pinMode(7,INPUT); 13 pinMode(10,OUTPUT); 14 servo1.attach(13); //servo変数をピンに割り当てる、ここでは13番ピン 15 servo2.attach(8); //servo変数をピンに割り当てる、ここでは8番ピン 16 servo1.write(90); //角度を指定、ここでは90度 17 servo2.write(90); //角度を指定、ここでは90度 18 19} 20 21void loop() { 22 time=millis(); 23 24 int val=0; 25 26 for(;;){ 27 if(digitalRead(7)==HIGH){ 28 val=val+1; 29 if(val>2) 30 val=0; 31 delay(20); 32 while(digitalRead(2)==LOW) {} 33 } 34 switch(val){ 35 case 0: 36 servo1.write(105); 37 lcd.clear(); 38 lcd.print("close"); 39 lcd.setCursor(0,1); 40 lcd.print("mode serect"); 41 delay(100); 42 digitalWrite(10,HIGH); 43 break; 44 case 1: 45 servo1.write(105); 46 lcd.clear(); 47 lcd.print("mode 1"); 48 if(time>5000){ 49 lcd.clear(); 50 lcd.print("open"); 51 servo1.write(50); 52 delay(1000); 53 break; 54 } 55 else{} 56 case 2: 57 servo1.write(105); 58 lcd.clear(); 59 lcd.print("mode 2"); 60 if(time>10000){ 61 lcd.clear(); 62 lcd.print("open"); 63 servo1.write(50); 64 delay(1000); 65 break; 66 } 67 else{} 68 default:{} 69 } 70 } 71 lcd.clear(); 72 lcd.print("end"); 73 delay(5000); 74 servo2.write(0); 75 delay(5000); 76} 77

試したこと

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

補足

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

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

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でしかありません。なんなら、

Arduino

1void setup(){ 2 Serial.begin(9600); 3} 4void loop(){ 5 unsigned long time=millis(); 6 while(1){ 7 Serial.println(time); 8 } 9}

とかいうプログラムで確かめて見ればいいです。このプログラムでは表示される値はず~っと変わりません。
ボタンが押されてからの経過時間を知りたいのなら、ボタンが押されたときに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ラベル)に飛ぶ、というそれだけの動作をします。それ以上のなにかはありません。なので、

Arduino

1 switch(val){ 2 case 0: 3 Serial.println("#0"); 4 case 1: 5 Serial.println("#1"); 6 case 2: 7 Serial.println("#2"); 8 default: 9 Serial.println("#d"); 10 }

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

Text

1#0 2#1 3#2 4#d

valが2なら

Text

1#2 2#d

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

Arduino

1 switch(val){ 2 case 0: 3 Serial.println("#0"); 4 break; //switch文を終了する 5 case 1: 6 Serial.println("#1"); 7 break; 8 case 2: 9 Serial.println("#2"); 10 break; 11 default: 12 Serial.println("#d"); 13 break; 14 }

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


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


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

Arduino

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

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

Arduino

1int val = 0; //valはloop()を出ても保存される必要がある 2 3void loop() { 4 if (digitalRead(7) != LOW) {//LOWと比較するのは私の趣味と思って下さい。一応理由はありますが。 5 time = millis(); 6 val = val + 1; 7 if (val > 2) 8 val = 0; 9 delay(20); 10 while (digitalRead(7) != LOW) {} 11 } 12 switch (val) { 13 //略 14 } 15}

とか。


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

Arduino

1#include <Servo.h> 2#include <LiquidCrystal.h> 3 4//変わりそうな値はプログラム中に値を書き込まず、一旦変数を介する 5//HARDWARE SETTINGS 6const int PIN_BUTTON = 7; 7const int PIN_servoFeed = 13; 8const int PIN_servoPwr = 8; 9 10//ANGLE DEFINITIONS 11const int ANG_CLOSE = 105;//餌箱閉じる角度 12const int ANG_FEEDING = 50; //餌箱開く 13const int ANG_PON = 90; //電源ON 14const int ANG_POFF = 0; //電源OFF 15 16//TIME DEFINITIONS 17const int TM_MODE1 = 5000;//餌箱動作時間1 18const int TM_MODE2 = 10000;//同2 19const int TM_FEED = 5000; //給餌時間 20const int TM_WAIT = 1000; //汎用待ち時間 21const int TM_POFF = 500; //電源OFF待ち時間 22 23//自明な、プログラムそのままのことはコメントに書かない 24 25//DEVICE INSTANCE 26Servo servoFeed; //給餌用サーボ 27Servo servoPwr; //終了時電源スイッチ操作してOFFするサーボ 28LiquidCrystal lcd(12, 11, 5, 4, 3, 2); //lcd(RS,ENABLE,D4,D5,D6,D7)を宣言 29 30//valiables 31unsigned long time; //時間測定用時刻保持 32int val = 0; //待ち時間モード保持 33bool feeding = false; //給餌開始かどうか 34String modeStr; //モード表示の文字列 35int feederAngle; //餌箱サーボの角度 36int pwrAngle; //電源制御の角度 37 38void setup() { 39 Serial.begin(9600) ; // 9600bpsでシリアル通信のポートを開く 40 lcd.begin(16, 2); //(桁数,行数)を指定 41 pinMode(PIN_BUTTON, INPUT); 42 pinMode(10, OUTPUT); //10PIN? 43 servoFeed.attach(PIN_servoFeed); 44 servoPwr.attach(PIN_servoPwr); 45 feederAngle = ANG_CLOSE; 46 servoFeed.write(feederAngle); 47 pwrAngle = ANG_PON; 48 servoPwr.write(pwrAngle); 49} 50 51void lcdPrint(String line1, String line2 = "") { 52 lcd.clear(); 53 lcd.print(line1); 54 lcd.setCursor(0, 1); 55 lcd.print(line2); 56} 57 58void loop() { 59 if (!feeding) { 60 feederAngle = ANG_CLOSE; 61 unsigned long tm; 62 if (digitalRead(PIN_BUTTON) == HIGH) { 63 //ボタンが押されたら 64 time = millis(); //時計リセット 65 //モード変更 66 val = val + 1; 67 if (val > 2) 68 val = 0; 69 //モードの設定 70 switch (val) { 71 case 0: 72 modeStr = "mode select"; 73 digitalWrite(10, HIGH);//??? 74 break; 75 case 1: 76 modeStr = "mode 1"; 77 tm = TM_MODE1; 78 break; 79 case 2: 80 modeStr = "mode 2"; 81 tm = TM_MODE2; 82 break; 83 default: 84 break; 85 } 86 lcdPrint("close", modeStr); 87 delay(20); 88 //ボタンが離されるのを待つ 89 while (digitalRead(PIN_BUTTON) == HIGH) {} 90 } 91 if ( val != 0 && (millis() - time > tm) ) { 92 //時間が来た 93 feeding = true;//給餌に遷移 94 lcdPrint("open", modeStr); 95 time = millis(); 96 } 97 } else { 98 //給餌開始 99 feederAngle = ANG_FEEDING; 100 if (millis() - time > TM_FEED) { 101 feederAngle = ANG_CLOSE; 102 lcdPrint("end"); 103 if (millis() - time > TM_FEED + TM_POFF) { 104 pwrAngle = ANG_POFF; 105 } 106 } 107 } 108 //サーボ制御はここで一括 109 servoFeed.write(feederAngle); 110 servoPwr.write(pwrAngle); 111}

投稿2019/10/22 14:06

編集2019/10/25 13:59
thkana

総合スコア7703

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

thkana

2019/10/22 14:10

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

2019/10/24 14:53

御回答ありがとうございます! タクトスイッチの回路はプルダウン回路にしているので問題無いです。 >> time=millis();というのはその後timeを参照するとその時点でのmillis()が取得できるということではありません。 millis()関数、どうやら大きな勘違いをしていました。プログラム例で理解できました。ありがとうございました。 >> for(;;)を抜ける手段が用意されていなくて「電源を切る」ところに行き着かないので... ちょっと格好はよくないですけれど、餌をやり終わったら電源OFF、もcaseの中でやっちゃいましょうか。 サーボ1の動作後はもう動かす予定はないので確かにcaseの中でやっちゃったほうが良いですね。そうすればfor文が省けますし。笑  アドバイスを参考にしてプログラム書き直してみたら自分が思ってたような動作になりました。ありがとうございました!
guest

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 00:27

編集2019/10/22 06:23
Kapustin

総合スコア1186

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

thkana

2019/10/22 01:39

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

2019/10/24 14:51

御回答ありがとうございます。 >>digitalRead(7)==HIGHを検出したあと、digitalRead(2)==LOW になるまで待っていますが、これは正しいですか? これは完全に私のミスで、2ピンではなく7ピンの間違いでした。すみません。 >>while(digitalRead(7)==LOW)の条件は最初の判別でおそらくfalseになるので、待たずに次に行ってしまいますね。。while(digitalRead(7)!=LOW)が正しいかと。 なるほど、確かに最初の判別でfalseになりそうですね。そこまで考えていませんでした。。 自動整形の機能も初めて知りましたm(__)mありがとうございます。
guest

0

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

if(digitalRead(7)==HIGH){

val=val+1;

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

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

投稿2019/10/21 23:32

y_waiwai

総合スコア88038

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問