今から始める電子工作 ⑥ ディジタル入力 その2 チャタリング対策

 前回、ディジタル入力の事例で、スイッチ1個をつなぎました。スイッチ1個だと、チャタリングがあってもさほどほかに影響は出ない場合が多いですが、複数個を扱うとき、チャタリング対策をしないと誤動作の原因になります。

チャタリングを観測する

 前回作成した回路です。

 スケッチです。内蔵のプルアップ抵抗ありにしています。

// constants won't change. They're used here to set pin numbers:
const int buttonPin = 2;  // the number of the pushbutton pin
const int ledPin = 13;    // the number of the LED pin

// variables will change:
int buttonState = 0;  // variable for reading the pushbutton status

void setup() {
  // initialize the LED pin as an output:
  pinMode(ledPin, OUTPUT);
  // initialize the pushbutton pin as an input:
  pinMode(buttonPin, INPUT_PULLUP);
}

void loop() {
  // read the state of the pushbutton value:
  buttonState = digitalRead(buttonPin);

  // check if the pushbutton is pressed. If it is, the buttonState is HIGH:
  if (buttonState == HIGH) {
    // turn LED on:
    digitalWrite(ledPin, HIGH);
  } else {
    // turn LED off:
    digitalWrite(ledPin, LOW);
  }
}

 オシロスコープのプローブを2番ピンとGNDにあてて波形を観測します。頻度は少ないですが、ときどきチャタリングが生じています。

チャタリングをハードウェアで対策する

 スイッチの入力波形に比べてチャタリングの波形は鋭い形をしています。言葉を変えて表現するなら、高い周波成分になります。高い周波数を除去するには、ローパス・フィルタ、またの名をハイカット・フィルタを挿入するとよいです。

 ローパス・フィルタは積分回路とも呼ばれます。下の図は、典型的はRCによる積分回路で、入力に方形波を入れると、出力波形は図のようになまった感じになります。

 最初のスケッチの回路にコンデンサを追加して書き直すと次のようになります(47kは内蔵のプルアップ抵抗と仮定)。似てないですね。コンデンサには充電されていて、スイッチを押すと放電されるという動作になりそうです。

 オシロスコープで、実際の波形を観測します。立ち上がりのところが少しなまって(丸みを帯びている)いる感じです。立ち下がりはスパッときれいです。波形によっては、立ち上がりのところにちょっともやっとしたノイズっぽいのが出ることがあります。コンデンサによってチャタリングが抑制されているのかどうかわかりません。

 回路自体、積分回路になっていません。

 チャタリング対策の積分回路でよく見るのは下記の回路です。

  • R1がスイッチSW1のプルアップ抵抗
  • R2とC1で積分回路
  • 内蔵のプルアップ抵抗は廃止

 実際に配線して波形を観測します。なを、スケッチはプルアップなしに変更しています。プルアップを生かすと、2ピンは0Vまで電圧が落ちません。

 コンデンサをつけないときの波形です。チャタリングが発生しています。その頻度は数ms程度です。

 コンデンサをつけて積分回路にします。波形の立ち上がり、立ち下がりともになまっています。ほぼ15msぐらいがその変化している時間です。

 思いっきり高速で押しボタンを押したときの波形です。50ms間隔より早く押すことはできなかったです。

 したがって、チャタリングが数msで起こっても、15ms程度のなまった波形になることでチャタリングを除去できていて?、人の押す速度を考慮しても、問題ない時間(15ms)といえるでしょう。

 スイッチによっては、チャタリングが10ms程度長く生じることもあるので、コンデンサの容量を少し増やすというような試行錯誤が必要かもしれません。

 少し疑問があります。プルアップ抵抗はもともと47kΩぐらいを使っていました。それが、1kΩと小さくなったので、スイッチを押したときに約50倍の電流が流れてしまっているのではないかと。

 R1を1kから47kに変更して観測した波形では、立ち上がりの時間が余計にかかっているように見えます。

チャタリングをソフトウェアで対策する

 スイッチが押されたら2ピンはLOWになりますが、チャタリングがあればHIGHに戻り、すぐにLOWになります。1回だけのときもあれば、複数回繰り返されることがあります。その時間は、スイッチによって異なり、1msから数msの間繰り返されることもあります。

 このチャタリングをソフトウェアでスキップします。実現方法はいくつもあると思いますが、今回は次の方法をとります。

  • 割り込みで2ピンがLOWになったことを検出
  • 10ms待つ
  • その後、割り込みがかかっていなければチャタリングの期間はスキップできた

 スイッチが押されたことを割り込みで知り、割り込み処理関数push()内ではbuttonStateFlag を HIGHにするだけです。

 loop()の中では、スイッチが押されたらLEDを1ms光らせます。これは、別の処理でもかまいません。光らせた後、10ms待ちます。

 スイッチは押されるとLOWになります。チャタリングが起こっても10ms後LOWのまま(buttonStateFlag がHIGH)であれば、スイッチは押されたままであることがわかります。なので、LEDは点灯させません。

 この後、人がスイッチを押さない時間が50~100ms以上あります。なので、10msの間LEDを点灯させないというのは問題になりません。

const int buttonPin = 2;  // the number of the pushbutton pin
const int ledPin = 13;    // the number of the LED pin

volatile int buttonStateFlag = HIGH; // variable for reading the pushbutton status

void push() {
    buttonStateFlag = LOW;
}

void setup() {
    Serial.begin(9600);
    Serial.println("start");
    // initialize the LED pin as an output:
    pinMode(ledPin, OUTPUT);
    // initialize the pushbutton pin as an input:
    pinMode(buttonPin, INPUT_PULLUP); 
    attachInterrupt(digitalPinToInterrupt(buttonPin), push, FALLING);
}
 
void loop(){
    Serial.print("."); 
    
    digitalWrite(ledPin, LOW);
    if (buttonStateFlag == LOW) {
        // turn LED on:
        digitalWrite(ledPin, HIGH);
        delay(1);
        digitalWrite(ledPin, LOW);
    }
    buttonStateFlag = HIGH;
    delay(10);
}

 10ms待つ間、メインの仕事ができません。上記のスケッチで10ms待つのをやめると、チャタリングが起こったら、割り込みがかかります。それをタイマを使って1msごとに記録して、起こったら'1'、おこらなかったら'0'とし、8~16回記録をし、'0'が3回続いたらチャタリングが収まったと解釈するというプログラムも実現できるかもしれません。

 ソフトウェアの対応はスケッチの見通しが悪くなるので採用したくないです。

割り込みの設定

 上記のスケッチで、

attachInterrupt(digitalPinToInterrupt(buttonPin), push, FALLING);

を使って、割り込みの条件などを設定しました。設定しておくと、割り込み条件が満たされると、今何をしていても、勝手に指定の割り込み処理関数へ飛び、戻ってきます。

 三つの引数があります。

  • 割り込み番号
  • 割り込みが起こったときに呼び出す関数名
  • 割り込みのかかる条件

 UNO3ではD2とD3の二つが割り込み用のピンでした。割り込み番号は、2ピンD2がint0、3ピンD3がint1です。なので、最初の引数は0もしくは1を指定します。しかし、ほかのAruinoのボードではすべてのディジタル・ポートが割り込みに使えるなどの拡張が行われました。その結果、割り込み番号とピン番号の対応が複数できて?しまったので、利用者は混乱します。

  digitalPinToInterrupt(ピン番号)

という記述方法で、この複雑さから解放されることになりました。

 で、UNO4は、図の中に’INT’と書かれたピンは存在しませんが、ドキュメント(https://docs.arduino.cc/resources/datasheets/ABX00087-datasheet.pdf)には、

  • D3  Digital  GPIO 3 (PWM~) / Interrupt Pin
  • D2  Digital  GPIO 2 / Interrupt Pin

と書かれています。しかし、割り込み番号は不明です。なので、digitalPinToInterrupt(ピン番号)という表記しか使えません。

 次の引数、「割り込みが起こったときに呼び出す関数名」は、わかりやすいのにしましょう、my_callbackとかわけのわからないのは避けましょう。

 三番目の引数は、ピンの状態がどのように変化したかという条件を指定します。

  • LOW  ピンがLOW
  • CHANGE  ピンの状態が変化
  • RISING  ピンの状態がLOWからHIGHに変わったとき
  • FALLING  ピンの状態がHIGHからLOWに変わったとき

 これ以外に、特定のArduinoボードでHIGH がサポートされていますが、UNO4は不明です。

  https://docs.arduino.cc/language-reference/en/functions/external-interrupts/attachInterrupt/

 このドキュメントによれば、HIGHはサポートされていないと思われます。

前へ

今から始める電子工作 ⑤ ディジタル入力 その1 スイッチ1個の基本

次へ

今から始める電子工作 ⑦ アナログ入力 その1 analogReadResolution(14)とanalogReference(AR_INTERNAL)