やりたいこと
ボタンを押したときにそれを検出して処理をしたい。ループの中とかで検出処理をごにょごにょやるのはつらいのでボタン検知の割り込み処理を試す。LOWからHIGHになったとき(立ち上がりエッジ:RISING)や逆にHIGHからLOW(立ち下がりエッジ:FALLING)になった時を検出するやつ。
概要
void ICACHE_RAM_ATTR shopYobidashiCallback() {
Serial.println("working");
}
void setup() {
pinMode(INPUT_PIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(INPUT_PIN), shopYobidashiCallback, FALLING);
}
割り込みが効くGPIOでattachInterruptを呼び出し。GPIOがどれかはちゃんと確認していない。(データシート参照)とりあえず、12は効いて1-15が効くと思う。
呼び出し元
attachInterrupt(割り込み番号,コールバック関数名,検出エッジ)の引数。
割り込み番号はdigitalPinToInterrupt(ピン番号)で取得できる。
コールバック関数名はそのまま関数名
検出エッジはLOW/CHANGING/RISING/FALLINGが選べる模様。LOWはLOWの時常に。実際に試したのはFALLINGのみ。(参照 ESPでなくArduinoのドキュメントなので少し違うかもしれない)
コールバック
ドキュメントはこちら
コールバックには関数をRAM上に配置する指定のIRAM_ATTRまたはICACHE_RAM_ATTR(多分こっちはDupricated)がいる。
つけ忘れると下みたいなメッセージが出て再起動するはず。
ISR not in IRAM!
User exception (panic/abort/assert)
--------------- CUT HERE FOR EXCEPTION DECODER ---------------
Abort called
>>>stack>>>
ctx: cont
RAMにない=Flashにあるとそっちの書き込みとかぶってだめよという事らしい。
その他制限として
delay()
oryield()
を読んだり内部で使うものを使用できない- 1ms以上かかる処理をすると不安定orクラッシュ引き起こす。よって、時間かかる処理は他でして極力そのためのフラグ操作など推奨。フラグはvolatileつける(参照)
- ヒープ操作は不具合起こしえる。malloc最小限にしてreallocとfreeは使うなと。stringやvectorも要注意。詳細はドキュメント。
- C++のnewやdeleteも
あとは、ドキュメントに書いてないけどコールバックの宣言は使う前に。
チャタリング対策
立ち上がり、立ち上がりを検出したいが変化した時に押しているのか押していないのか中間の状態を経由するのでその際に何度もコールバック関数が呼ばれてしまう。(参照)
今ブレッドボードでやっているけどイメージは下。
0件だよ
0
working
working
working
working
working
working
working
working
略
呼ばれたときの処理で出力される”working”が一回であってほしいが何度も出力される。
参照先に色々な対策があるが、ソフトウェアで対策できれば楽。今回の使用用途としては押されたときにあるURLをたたければよい。また、即時に何度も呼ばれなくてもよい。なので、エッジ検出後一定期間その状態が続いていれば処理を実行するとすればよい。
- ボタン押す
- 最初の立ち上がり/立下りを検出後最初数100msはチャタリングしているかもしれないので読み飛ばす
- その後、100ms間隔で数回状態が継続していたら押されていると判断する
みたいな感じで今回は大丈夫。
ソフトウェア的にやらないなら積分回路組んだり、ラッチ回路組んだり、いいスイッチ使ったりとあるが要件厳しくなければソフトで切り抜けた方が大体良い気がする。(というかそういう要件に収めたい)
で、ループからボタン検知と状況確認の並行処理しようとしたらESP8266はスレッドなかった。並行処理するためにFreeRTOSいれる。FreeRTOSを使おうと思ったら「ESP8266などいまさら開発する暇ないんじゃ!」とのお言葉。ESP32-C3なら使えるらしい。
ループ切り替えながらやる。2つの処理があるけど、時間はざっくりでよい。
下のイメージになった。
コールバック(shopYobidashiCallback)はフラグの設定のみ
メインのループ(Arduinoのloop関数)は並行処理するループ(yoyakuCheckLoop,yobidashiCheckLoop)をそれぞれ呼び出しのみ。
yobidashiCheckInterval 経過していなかったらスキップする処理を入れつつチェックをいれる。yobidashiCheckLoopが走っている間はもう一個の処理(yoyakuCheckLoop)は順番回ってこない。
volatile bool pushed = false;
unsigned long lastTimeYoyakuCheck;
unsigned long lastTimeYobidashiCheck;
void IRAM_ATTR shopYobidashiCallback() {
pushed = true;
}
const unsigned long yobidashiCheckInterval = 100;
void yobidashiCheckLoop() {
// チェック間隔以内なら
if ((millis() - lastTimeYobidashiCheck) < yobidashiCheckInterval) {
return;
}
lastTimeYobidashiCheck = millis();
// ボタン押されていなかったら戻る
if (!pushed) {
return;
}
// チャタリング安定待ち(待ち時間は大体)
delay(200);
// LOWが100ms間隔で3回続くか
for (int count = 0; count < 3; count++) {
if (digitalRead(INPUT_PIN)) {
pushed = false;
return;
}
delay(100);
}
// 実処理
Serial.printf("read:%d\n", digitalRead(INPUT_PIN));
pushed = false;
}
void loop() {
yoyakuCheckLoop();
yobidashiCheckLoop();
}