Arduino よもやま話-19(ピンチェンジ割り込みの回路とスケッチ)
◆まえがき
ESP32を使ったミニオシロ作りを少しずつやっているのですが、その作業の中でタクトスイッチを使った割り込みで、チャッタリングの影響が(ほとんど)出ない回路とソフトを作ることが出来ました。ということで、自分へのメモを兼ねて紹介します。なお、ボードは Arduino UNO と ESP32 を対象としています。
・UNOでタクトスイッチを読み込み

よくある使い方ですが、ちゃんとやろうとすると奥が深いです。
◆チャッタリング現象
スイッチの検出で問題になるのが接点のチャッタリングです

上の写真はタクトスイッチを操作した時に発生するチャッタリングの波形です。こんなにばたばたしていたのでは、プログラムに何度も割り込みが入ってしまって、おかしなことが起きます。
◆回路
スイッチの状態を検出する場合、抵抗でプルアップしてGNDに落とすやり方が定石です。しかし、今回はスイッチがONになった瞬間の検出だけ出来れば良いので、コンデンサでGNDに落とす方法でやってみました。回路図としては以下のようになります。
・Arduino UNO の回路図

C1で入力ピンを瞬間的にGNDに落とす回路になっています。C1と並列に入っている抵抗(R1)はコンデンサの電荷を放電させるためのものです。これが無いと1回ボタンを押すとC1が充電されてしまって、2回目以降の検出が出来なくなります。
右側の図はCPUの内部まで表現した回路で、内部のプルアップ抵抗(Rp 20-50kΩ)で入力ピンは+5Vに引っ張り上げられています。なお、UNOではピン割り込みに使えるピンはD2とD3だけなので、この例ではD2を入力ピンに使っています。
・ESP32の場合

ESP32はピン割り込みに使えるピンは多いのですが、この図ではD26を使っています。なお、データーシートによるとESP32のプルアップ抵抗(Rp)は45kΩ(TYP) となっています。
◆チャッタリング対策の原理
ポイントとなるのはC1とR1の値で、今回はC1=0.1μF、R1=470kΩにしたのですが、その理由は以下の通りです。
・C1の値の決定
C1の値を変えて試してみると、330pFまで減らしてもピンチェンジ割り込みが正常に掛かりました。だったら少し余裕を見て1000pFにしようかと思ったのですが、それでは放電抵抗のR1の値が大きくなりすぎてしまいます。そんなことで0.1μFにしました。詳しくは次の項で説明しますが、R1の値は470kΩにしたので、放電の時定数(T2)は47msになります。
・動作波形
話はもう少し複雑なので波形で説明します。下の図の上側がポートの入力波形、下側はコンデンサ(C1)の電圧波形です。

①は待機状態でスイッチはOFFです。
②スイッチON
スイッチがONになると、ポートの電圧は瞬間的にGNレベルになります。この時、ソフトにピンチェンジ割り込み(FALLING)が発生します。
②スイッチONの後
RpとC1の時定数(T1 4.5ms)で決まるカーブで電圧が上昇して行きます。電圧の上昇つまりRAISINGなので、ピンチェンジ割り込みのFALLINGには引っかからないはずなのですが、実際には電圧上昇速度が遅いためにノイズの影響を受けてピンチェンジ割り込みが入ってしまう、つまり誤検出することがありました。
ATmega328Pのポートの入力はシュミットトリガなのでノイズには強いはずなのですが、これくらいゆっくりした電圧の変化に対してはあまり効果は無いようです。電圧上昇速度をうんと速くすれば誤検出しなくなるはずですが、後で説明するチャッタリング対策のためにはC1を小さくすることは出来ません。
③チャッタでスイッチOFF
ここからがスイッチを離す場合の挙動です。
スイッチを離す時にチャッタが発生し、瞬間的にスイッチがOFFになったとしても、ポートの入力電圧に変化はありません(厳密にはRpとR1で分圧されなくなるので僅かに電圧は上昇)。しかし、コンデンサ(C1)の電圧は時定数T2で放電して行きます。このまま接点OFFが続けば何も起こらずに済むのですが、、
④再度ON
チャッタリングなので再度接点がONになってしまうことがあります。こうなると、ポートの入力電圧は一旦その時点のコンデンサの電圧(Vx)まで下がった後で再度時定数T1で上昇することになります。
この時、もしVxが入力のロジックのLowレベル電圧より下がっていれば、ピンチェンジ割り込みが発生してしまって誤動作になります。
・対策
②で電圧が上昇する時の誤動作は、正規の割り込みの直後に発生するので、これはソフトで対策することにしました。説明は省略しましたが、②のスイッチONの時にもチャッタリングが発生しますがこれも同じソフトで対策(無視)可能です。
④のタイミングで発生する誤動作は、T2の時定数を大きくすることで回路的に対策することにしました。具体的には、T2の時間を長くすることで、チャッタの発生時間程度ではVxの電圧があまり下がらないようにしました。ちなみに、チャッタの時間が5ms程度なのに対して、T2は47msとしています。
◆プログラム
チャッタ対策効果の確認プログラムを以下に示します。
プログラムの動作としては、スイッチを押すたびにボード上のLEDがトグルで点滅させ、同時にスイッチが押された時刻(millsの値)をシリアルに表示します。
割込み処理ルーチンの irq_Sw() の中でチャッタ対策を行っています。
34行目はノイズで割り込みが入った場合の処置で、FALLING割り込みなので本当にLOWレベルになっているか確認しています。(この行は無いよりマシ程度の効果かも知れません)
35行目で最後に有効と見なした割り込みからの経過時間をチェックしていて、50ms以内の割込みだったらチャッタリングと見なして無視するようにしています。
割込みの都度 irq を true にしてメインルーチン側で表示などの処理を行っています。これ以外にも動作確認用にいろいろな変数を使っていますが、これらは動作確認用なので削除可能です。
テストの様子(ESP32)

横に実験に使ったセラコンが転がっています。記事では簡単に出来たように書いていますが、実際にはいろいろな値を試しています。なお、このブレッドボードは標準品を加工して作った「ガニ股ブレッドボード」です。
◆まとめ
対策のための追加部品は抵抗とコンデンサが各1個必要になりました。実はプログラムはもっと複雑になっても構わないから外付け部品を出来るだけ少なくしたかったのですが、これくらいが限界でしょう。
冒頭に書きましたが、この記事はボタンが押されたことの検出を目的としています。つまり、ボタンが押され続けている、あるいは、ボタンが離された場合の検出は別の話になります。ただ、マイコンの操作はスイッチをちょんちょんと押して設定していくような使い方が多いので、この記事の対策だけで使える場合が多いと思います。
あと、タイマー割り込みでスイッチの状態を周期的にチェックする方法を使えばこの記事に書いたようなことは考えないでも良くなります。でも、今取り組んでいるオシロスコープでタイマー割り込みを使うと、波形キャプチャーの周期が乱れる恐れがあります。これは測定器としては致命的な問題になりかねません。
ということで、ピンチェンジ割り込みで処理したいと考えています。そのあたりの話は以前の記事の中にちょっと出て来ます。なお、Arduino の環境で動かしている限り、millis()などの関数のためにタイマー割り込みが使われるのは仕方ないものとあきらめています。もしこれが問題になる場合はシステム割り込みを止めて対策することが必要になると思います。
ESP32を使ったミニオシロ作りを少しずつやっているのですが、その作業の中でタクトスイッチを使った割り込みで、チャッタリングの影響が(ほとんど)出ない回路とソフトを作ることが出来ました。ということで、自分へのメモを兼ねて紹介します。なお、ボードは Arduino UNO と ESP32 を対象としています。
・UNOでタクトスイッチを読み込み

よくある使い方ですが、ちゃんとやろうとすると奥が深いです。
◆チャッタリング現象
スイッチの検出で問題になるのが接点のチャッタリングです

上の写真はタクトスイッチを操作した時に発生するチャッタリングの波形です。こんなにばたばたしていたのでは、プログラムに何度も割り込みが入ってしまって、おかしなことが起きます。
◆回路
スイッチの状態を検出する場合、抵抗でプルアップしてGNDに落とすやり方が定石です。しかし、今回はスイッチがONになった瞬間の検出だけ出来れば良いので、コンデンサでGNDに落とす方法でやってみました。回路図としては以下のようになります。
・Arduino UNO の回路図

C1で入力ピンを瞬間的にGNDに落とす回路になっています。C1と並列に入っている抵抗(R1)はコンデンサの電荷を放電させるためのものです。これが無いと1回ボタンを押すとC1が充電されてしまって、2回目以降の検出が出来なくなります。
右側の図はCPUの内部まで表現した回路で、内部のプルアップ抵抗(Rp 20-50kΩ)で入力ピンは+5Vに引っ張り上げられています。なお、UNOではピン割り込みに使えるピンはD2とD3だけなので、この例ではD2を入力ピンに使っています。
・ESP32の場合

ESP32はピン割り込みに使えるピンは多いのですが、この図ではD26を使っています。なお、データーシートによるとESP32のプルアップ抵抗(Rp)は45kΩ(TYP) となっています。
◆チャッタリング対策の原理
ポイントとなるのはC1とR1の値で、今回はC1=0.1μF、R1=470kΩにしたのですが、その理由は以下の通りです。
・C1の値の決定
C1の値を変えて試してみると、330pFまで減らしてもピンチェンジ割り込みが正常に掛かりました。だったら少し余裕を見て1000pFにしようかと思ったのですが、それでは放電抵抗のR1の値が大きくなりすぎてしまいます。そんなことで0.1μFにしました。詳しくは次の項で説明しますが、R1の値は470kΩにしたので、放電の時定数(T2)は47msになります。
・動作波形
話はもう少し複雑なので波形で説明します。下の図の上側がポートの入力波形、下側はコンデンサ(C1)の電圧波形です。

①は待機状態でスイッチはOFFです。
②スイッチON
スイッチがONになると、ポートの電圧は瞬間的にGNレベルになります。この時、ソフトにピンチェンジ割り込み(FALLING)が発生します。
②スイッチONの後
RpとC1の時定数(T1 4.5ms)で決まるカーブで電圧が上昇して行きます。電圧の上昇つまりRAISINGなので、ピンチェンジ割り込みのFALLINGには引っかからないはずなのですが、実際には電圧上昇速度が遅いためにノイズの影響を受けてピンチェンジ割り込みが入ってしまう、つまり誤検出することがありました。
ATmega328Pのポートの入力はシュミットトリガなのでノイズには強いはずなのですが、これくらいゆっくりした電圧の変化に対してはあまり効果は無いようです。電圧上昇速度をうんと速くすれば誤検出しなくなるはずですが、後で説明するチャッタリング対策のためにはC1を小さくすることは出来ません。
③チャッタでスイッチOFF
ここからがスイッチを離す場合の挙動です。
スイッチを離す時にチャッタが発生し、瞬間的にスイッチがOFFになったとしても、ポートの入力電圧に変化はありません(厳密にはRpとR1で分圧されなくなるので僅かに電圧は上昇)。しかし、コンデンサ(C1)の電圧は時定数T2で放電して行きます。このまま接点OFFが続けば何も起こらずに済むのですが、、
④再度ON
チャッタリングなので再度接点がONになってしまうことがあります。こうなると、ポートの入力電圧は一旦その時点のコンデンサの電圧(Vx)まで下がった後で再度時定数T1で上昇することになります。
この時、もしVxが入力のロジックのLowレベル電圧より下がっていれば、ピンチェンジ割り込みが発生してしまって誤動作になります。
・対策
②で電圧が上昇する時の誤動作は、正規の割り込みの直後に発生するので、これはソフトで対策することにしました。説明は省略しましたが、②のスイッチONの時にもチャッタリングが発生しますがこれも同じソフトで対策(無視)可能です。
④のタイミングで発生する誤動作は、T2の時定数を大きくすることで回路的に対策することにしました。具体的には、T2の時間を長くすることで、チャッタの発生時間程度ではVxの電圧があまり下がらないようにしました。ちなみに、チャッタの時間が5ms程度なのに対して、T2は47msとしています。
◆プログラム
チャッタ対策効果の確認プログラムを以下に示します。
// ピンチェンジ割り込みのチャッタ対策効果確認(ESP32, Arduino UNO)このプログラムはArduino UNO で動く状態になっていますが、コメントアウトを変更すればESP32でも動きます。(5, 6, 18行のコメントアウトを解除し、7, 8, 19行をコメントアウト)
// SwPinに接続したボタンスイッチの操作でLEDとシリアルに出力
// 2020/09/27 ラジオペンチ
//#define SwPin 26 // swPin (ESP32)
//#define LedPin 2 // ESP32 DevKit 搭載LED
#define SwPin 2 // swPin (Arduino UNO int0)
#define LedPin 13 // Arduino UNO 搭載LED
volatile boolean swPushed = false; // swPushed flag
volatile boolean irq = false; // 割込み有りフラグ
volatile unsigned long tXXX = 0; // ボタン操作時刻
void setup() {
Serial.begin(115200);
pinMode(LedPin, OUTPUT);
pinMode(SwPin, INPUT_PULLUP); // 操作ボタン(Hold)
// attachInterrupt(SwPin, irq_Sw, FALLING); // ピンチェンジ割り込み(ESP32)
attachInterrupt(0, irq_Sw, FALLING); // ピンチェンジ割り込み(Arduino UNO)
}
void loop() {
while (irq == false) { // ボタン操作待ち(ボタン押し待ち)
}
irq = false; // 割込みフラグクリア
Serial.println(tXXX); // 時刻(millisの値)を出力
digitalWrite(LedPin, swPushed); // 動作確認LEDを点灯
}
void irq_Sw() {
static unsigned long tLast = 0; // 最後のボタン操作時刻(millsの値)
unsigned long tNow;
tNow = millis(); // 現在時刻を記録
if (digitalRead(SwPin) == LOW) { // 本当にLOWになっていて、←念のため
if ((tNow - tLast) > 50) { // 前回の操作から50ms以上たっていたら、
irq = true; // 割込み(ボタン押し)フラグON
swPushed = ! swPushed; // 状態フラグをトグル
tLast = tNow; // 次回の判定用に時刻を保存
tXXX = tLast; // 動作確認用に時刻をコピー
}
}
}
プログラムの動作としては、スイッチを押すたびにボード上のLEDがトグルで点滅させ、同時にスイッチが押された時刻(millsの値)をシリアルに表示します。
割込み処理ルーチンの irq_Sw() の中でチャッタ対策を行っています。
34行目はノイズで割り込みが入った場合の処置で、FALLING割り込みなので本当にLOWレベルになっているか確認しています。(この行は無いよりマシ程度の効果かも知れません)
35行目で最後に有効と見なした割り込みからの経過時間をチェックしていて、50ms以内の割込みだったらチャッタリングと見なして無視するようにしています。
割込みの都度 irq を true にしてメインルーチン側で表示などの処理を行っています。これ以外にも動作確認用にいろいろな変数を使っていますが、これらは動作確認用なので削除可能です。
テストの様子(ESP32)

横に実験に使ったセラコンが転がっています。記事では簡単に出来たように書いていますが、実際にはいろいろな値を試しています。なお、このブレッドボードは標準品を加工して作った「ガニ股ブレッドボード」です。
◆まとめ
対策のための追加部品は抵抗とコンデンサが各1個必要になりました。実はプログラムはもっと複雑になっても構わないから外付け部品を出来るだけ少なくしたかったのですが、これくらいが限界でしょう。
冒頭に書きましたが、この記事はボタンが押されたことの検出を目的としています。つまり、ボタンが押され続けている、あるいは、ボタンが離された場合の検出は別の話になります。ただ、マイコンの操作はスイッチをちょんちょんと押して設定していくような使い方が多いので、この記事の対策だけで使える場合が多いと思います。
あと、タイマー割り込みでスイッチの状態を周期的にチェックする方法を使えばこの記事に書いたようなことは考えないでも良くなります。でも、今取り組んでいるオシロスコープでタイマー割り込みを使うと、波形キャプチャーの周期が乱れる恐れがあります。これは測定器としては致命的な問題になりかねません。
ということで、ピンチェンジ割り込みで処理したいと考えています。そのあたりの話は以前の記事の中にちょっと出て来ます。なお、Arduino の環境で動かしている限り、millis()などの関数のためにタイマー割り込みが使われるのは仕方ないものとあきらめています。もしこれが問題になる場合はシステム割り込みを止めて対策することが必要になると思います。