arduino/esp32でGPIOの立ち上がりと立ち下がりを別の割り込み処理にしたい場合対処法を共有します。
ちょっとハマりましたので、同じ悩みに当たった方の解決になれば幸いです。
↓回路のイメージ。 リードスイッチのON / OFFでDFPlayer miniから音が流れるって想定です。
(赤外線センサは挿してあるだけなので、お気になさらず。)
1ハマり目 同じPinにRISING/FALLINGを充てることはできない。
こういう指定をしても、片方しか受け付けてくれません。
void setup() {
pinMode(ISRPin, INPUT);
attachInterrupt(ISRPin, ISR, RISING);
attachInterrupt(ISRPin, ISR, FALLING);
}
2ハマり目 CHANGEの割り込み要因がわからない。
こういう指定をすると、RISINGで割り込みに入ったのか、FALLINGで割り込みに入ったのかわからない。
void setup() {
pinMode(ISRPin, INPUT);
attachInterrupt(ISRPin, ISR, CHANGE);
}
なお、割り込み関数 : ISR()の中でdigitalRead(ISRPin)すると、当然チャタリング(※)の影響で誤動作します。 もちろん割り込み関数内でwait掛けて値が安定するのを待つのは、他の割り込みに入れなくなって誤動作の元となるので、するべきではないです。
※チャタリング スイッチが開閉する際、スパッとはOFF>ON, ON>OFFとはならず、数msはON/OFFを繰り返す現象 今回のリードスイッチだとOFF>ONした時にこんな感じになります↓
その他の注意 割り込み関数内で挙動が正しくない機能がある。
以下は割り込み関数内での利用に注意が必要です。
- millis() はカウントアップされない。
- delay() は機能しない。
- micros() は1~2ms後にカウントがおかしくなる。
- delayMicroseconds() なら、割り込み関数内でも正しく動く。
- 割り込み関数とメインプログラム間の値の受け渡しをするならvolatile宣言必須。
で、解決策は・・・。
やり方は色々あると思いますが、こうしてしまいました。
お気づきかと思いますが、 スイッチから来る線を分岐してRISING用とFALLING用でそれぞれ1ピンずつ使ってしまうのです。
寝る前に気づいて、えーっと思いましたが、やってみたら問題なし。
エレガントさには欠けますが、 8PinPICと違っていっぱいIO余ってますしね。
まぁいっか。という感じです。
コードはこんな風になります。
#include "Arduino.h"
#define ISR_IGNORE_MS (50)
void ISR_R();
void ISR_F();
int ISR_R_Pin = 4;
int ISR_F_Pin = 16;
volatile unsigned long rapTime;
void setup() {
pinMode(ISR_R_Pin, INPUT);
pinMode(ISR_F_Pin, INPUT);
attachInterrupt(ISR_R_Pin, ISR_R, RISING);
attachInterrupt(ISR_F_Pin, ISR_F, FALLING);
}
void loop() {
//略
}
void ISR_R() {
if ( ( millis() - rapTime ) > ISR_IGNORE_MS ) {
rapTime = millis();
funcR();//立ち上がりの割り込み発生でやりたい仕事
}
}
void ISR_F() {
if ( ( millis() - rapTime ) > ISR_IGNORE_MS ) {
rapTime = millis();
funcF();//立ち下がりの割り込み発生でやりたい仕事
}
}
チャタリング対策については、 コード中の if ( ( millis() – rapTime ) > ISR_IGNORE_MS ) でチャタリングが発生していそうな時間中の立ち上がり/立ち下がりを無視して割り込み関数抜けるようにしています。
という訳で、半年後あたりに同じ問題で悩んでGoogle先生に質問する自分宛のメモでした。