Arduino – ESP32 のスケッチを使って、スイッチの挙動をシリアルモニターで確認
では、Arduino core for the ESP32 を使って、シリアルモニターでスイッチの挙動を確認してみるプログラムを組んでみます。
チャタリング対策は一切していません。
以下のスケッチを入力してみてください。
【ソースコード】 (※無保証 ※PCの場合、ダブルクリックすればコード全体を選択できます)
const uint8_t buttonA_GPIO = 39; uint32_t buttonA_count = 0; void setup() { Serial.begin(115200); pinMode(buttonA_GPIO, INPUT); //GPIO #39 は内部プルアップ無し Serial.println(digitalRead(buttonA_GPIO)); } void loop() { if(digitalRead(buttonA_GPIO) == 0){ Serial.printf("Button A pressed ! count = %d\r\n", buttonA_count); buttonA_count++; }else{ buttonA_count = 0; } }
これは、シンプルにGPIO #39 の電圧値を読み取るスケッチです。
millis関数は使っていません。
6行目では、Arduino で定番の GPIO の INPUT 設定です。
ただし、注意して欲しいのは、先ほども述べたように、ESP32 の GPIO #34~#39 は内部プルアップ回路無しです。
ですから、ここでは INPUT 設定のみでOKです。
7行目や、11行目では、GPIO の電圧値を読み取っていますが、この場合、HIGHレベルかLOWレベルかだけしか判別しません。
つまり、HIGHレベルの場合は1を返し、LOWレベルの場合は0を返します。
11-16行目でGPIO #39 が LOWレベルになったらシリアルモニターに文字を表示させ、buttonA_countをカウントアップしていきます。
HIGHレベルになったらカウントをゼロにします。
これをメインループ関数で常時繰り返し実行させているので、ボタンの電圧を常に監視していることになります。
その結果、ボタンが押されて、目立ったチャタリングが発生すれば、その時点でカウントがゼロからスタートするので明白に判別できると思います。
では、これをコンパイル書き込み実行させ、シリアルモニターを115200 bps で起動してみてください。
そして、スイッチを押してみて下さい。
まず、ESP32-DevKitC の外付けタクトスイッチの結果はこうなりました。
シリアルモニターの右下端の「出力をクリア」をクリックしながら、タクトスイッチを何回か押しても、滅多にカウントが途中でゼロに戻ることはありませんでした。
先ほど述べたように、私の指のON-OFF 最速は約 80 ms 程度なので、その間、38回ループしていることになります。
ということは、プログラムの1ループは約 2.1ms程度になってしまいます。
いくら素早く押しても、殆どカウントがゼロに戻ることはありませんでした。
また、比較的ゆっくり押すと、カウントは100以上になります。
プログラムが間違えたかと思い、試しに2回連続で押してみると、ちゃんとカウントはゼロに戻るので、プログラムに問題はないと思います。
では、同じように M5Stack に書き込んで、ボタンAを押してみた結果はこうなりました。
外付けタクトスイッチと大差無い結果です。
以上から推測すると、恐らくチャタリングが発生している時間が 1ms 以内と短いため、ESP32 の Analog 入力では検知できていないと言えると思います。
Arduino core for the ESP32 のメインloop関数ではウォッチドッグタイマが作動するようになっていて、速度はあまり速くないのです。
逆にそれがチャタリング対策になっていて都合が良いです。
(メインloop関数についてはこちらの記事参照)
それと、M5Stack の場合はツェナーダイオードがあることによって、更にチャタリングが発生しにくくなっているのではないかと思います。
でも、しばらく ON-OFF を繰り返していると、たまにゼロにカウントが戻る時があります。
結果的に、チャタリング対策した方が良いかもしれませんが、Arduino core for the ESP32 でプログラミングする場合、あまり神経質にならなくても良いと言えるかも知れません。
スイッチの感度をプログラミングで調整してみる
では、今度はスイッチの感度をArduino プログラムで調整してみようと思います。
個人的に作ったプログラムを段階的に紹介していきます。
最後のプログラムは我ながら良くできたと思うものが出来ましたが、それに至るまでの過程が自分にとって大事だと思いました。
まずは、瞬時ボタン ( momentary button )についてです。
なお、基本的に素人ですので、無駄が多いかもしれませんし、誤っているかもしれません。
もし何かありましたらコメント投稿等でご連絡いただけると助かります。
では、だいたいチャタリング発生の時間などを把握できたので、まず、第1段階として、スイッチ1個のモーメンタリーボタン(瞬時押下)をプログラミングしてみます。
まず、先ほど見たオシロスコープのチャタリング波形を考慮しながら、下図の様に考えてみました。
先ほどのプログラムでは、カウンター計測をしていましたが、一般的にチャタリング対策はマイコンの時間計測で行った方がいろいろと有利です。
スイッチが押されて、接点が接触すると、GPIO #39 は LOW レベルになります。
それから、LOWレベルの時間を計測しますが、チャタリングで HIGH レベルになると時間をリセットするようにします。
10 ms 程度 LOW レベル検知をキープしていれば、スイッチONと判定すれば良いと思います。
私の場合、どんなに素早く押しても 80 ms なので、スイッチ ON 判定はもっと幅を取って、50ms としても良いかもしれません。
スイッチONと判定した後は、GPIO #39 が HIGHレベルなるのを待って、HIGH レベルになったら、時間計測をリセットします。
その時、チャタリングを検知しても再度リセットされるので、ここは特に問題にならないと思います。
では、以上を考慮して、まずは安易なプログラムをザッと組んでみました。
(これは、イマイチなもので、後でもっと良いプログラムにしています)
【ソースコード】 (※無保証 ※PCの場合、ダブルクリックすればコード全体を選択できます)
const uint8_t buttonA_GPIO = 39; uint32_t start_time = 0; boolean button_on = false; boolean ON_start = false; //****************************************** void setup() { Serial.begin(115200); pinMode(buttonA_GPIO, INPUT); //GPIO #39 は内部プルアップ無し Serial.println(digitalRead(buttonA_GPIO)); } //****************************************** void loop() { My_ButtonA(); } //****************************************** void My_ButtonA(){ switch( digitalRead(buttonA_GPIO) ){ case LOW: // LOW = 0 if( !button_on ){ button_on = true; ON_start = true; start_time = millis(); //時間リセット }else{ if( ON_start == true ){ if( Time_Mesure(start_time) > 30 ){ Serial.println("Button A Momentary Pressed Determined!"); ON_start = false; } } } break; case HIGH: // HIGH = 1 if( button_on ){ button_on = false; start_time = millis(); //時間リセット } break; default: break; } } //****************************************** uint32_t Time_Mesure( uint32_t st_time ){ return millis() - st_time; }
19行目のArduino IDE 定番の digitalRead 関数は、ADコンバーター Analog Inputo の電圧値が HIGH か LOW だけを判別して返します。
HIGH の場合は 1 を、LOW の場合は 0 を返しますが、Arduino – ESP32上で、”HIGH” と “LOW” という名前が定義されているので、20行や34行のように、それをそのまま使った方が見た目に分かりやすいです。
スイッチが押されて、GPIO #39 が LOW レベルになってから、24行目で Arduino IDE でお馴染みのmillis関数で時間計測をリセットします。
millis 関数はマイコンが起動してから自動的にミリセコンド単位でカウントアップして、uint32_t 型( unsigned long )の整数を返します。
時間計測にはmicros 関数もありますが、あまり細かいとチャタリング対策に影響が出てしまうので、millis関数で良いと思います。
27行目で時間計測してから 30 ms 以上になったら、ボタンが押されたと判定しています。
チャタリングは 1ms 以下なので、十分すぎる時間です。
もっと小さくしても良いのですが、先に述べたように、ボタンを押す速さは 80ms 以上なので、これでも反応は十分速いです。
34-38行で、ボタンがリリースされて、HIGH レベルになったら、時間計測をリセットしています。
では、これを Arduino IDE でコンパイル実行してみてください。
シリアルモニターを 115200 bps で起動して、M5Stack のAボタンを素早く押してみて下さい。
以下のように表示されると思います。
出来るだけ素早く連打しても、ちゃんと反応してくれると思います。
誤作動も殆ど無いと思います。
これで、27行目の値をいろいろ変化させて実験してみて下さい。
例えば、500 にすると、反応しなくなり、ちょっと長押しすると反応するようになります。
これが要するにボタンの感度になると思います。
自分の最速が 80 ms だと把握していれば、好きなように感度調整できそうですね。
では、次のページでは、ボタンの長押し設定などをプログラミングしてみます。
コメント
結構前の記事なのでもう解決済みかもしれませんが・・・。
ボタンの処理は遅延を最小限にするのに割り込み(ISR)を使われたほうが良いかと思います。
あと、どんなスイッチを使ってもほぼチャタリングは発生するので、バイナリーセマフォかミューテックスを使うのが常套手段かなと思います。
Lang-Shipさんが詳しくまとめられているので、参考になると思います。
https://lang-ship.com/blog/work/esp32-freertos-l06-semaphore-mutex/
PeakAliveさん
記事をご覧いただき、ありがとうございます。
チャタリングには割り込みとかバイナリセマフォとかミューテックスつかうのが常套手段というのは知らなかったです。
当時はFreeRTOSは全く無知でした。
しばらくプログラミングから離れておりますが、久々に勉強しなおそうかと思いました。
Lang-Shipさんは以前、大変お世話になったので、参考にさせて頂こうと思います。
情報ありがとうございました!!!
m(_ _)m