ボタンをきっかけで使うとき!(Arduino詳解)

詳しく解説!!Arduino-その7です。前回の記事はこちらです。

hirocom777.hatenadiary.org

ボタンをきっかけで使うとき

 前回はボタンモジュールによる入力を試してみました。ボタンモジュールなどの電気的なスイッチによる入力は主に二つに分かれます。一つはボタンの状態を常に検出してるパターン。押している時だけ音が鳴るとかあかりが点くとかいうやつです。もう一つは押した瞬間を検出しているバターン。きっかけを見るパターンです。パソコンのキーなんかはこちらに近いかな(押しっぱなしを検出する機能もありますが)

ボタンの状態を見るパターンでは、スケッチ上で見るタイミングを考えて使えば大体問題ないと思います。でも、押した瞬間を検出する場合はちょっと注意が必要なんです。

ボタンモジュールをはじめ一般的な電気スイッチは、中で2つ(もしくはそれ以上の)金属片が離れた形で配置されていて、ボタンを押すと金属片同士が接触して電気が流れる仕組みになっています。間にバネなどが入っていてボタンを離すと金属片同士が離れます。この接触したり離れたりなんですが、実際にはきれいに切り替わることはないのです。接触し始めや、離れ始めの時は電気の流れ方が不安定になります。

このような状態だと、短い間に切り替えが複数回発生することになるため検出すべき『きっかけ』を誤検出してしまいます。俗にいう『チャタリング』ですね。ボタンを押すきっかけを検出する場合は、この『チャタリング』に対する対策をとる必要があります。

f:id:HiroCom777:20200322182506j:plain

チャタリング対策

 チャタリングの対策ですが、ハードウェアによるものとソフトウェアによるものの2つがあります。で、もちろんこのブログでは部品の追加や改造が必要なハードウェアによる対策ではなくて、ソフトウェアによる対策を考えますよ!!前回作ったボタンを押す毎にLEDのON/OFFを切り替えるスケッチを改良していきます。

 チャタリングは一定の時間が経過すれば収まります。つまり、最初に立ち上がりを検出してから一定の時間HIGHを継続して検出した時点でHIGHを確定できればチャタリングを回避できるわけです。で、以下のようなスケッチを考えてみました。

int buttonState = 0;
unsigned long lastTime = 0;

void setup() {
  pinMode(3, OUTPUT);
  pinMode(2, INPUT);
}
void loop() {
  if (digitalRead(2) == LOW) {
    buttonState = 0;
    lastTime = 0;
  } else { //digitalRead(2) == HIGH
    if (lastTime == 0) {
      lastTime = millis();
    } else { //lastTime != 0
      if (buttonState == 0 && (millis() - lastTime) > 50) {
        buttonState = 1;
        digitalWrite(3, !digitalRead(3));
      }
    }
  }
}
setup-その前に

 今回もsetupの前に記述があります。intは説明済ですが、その下のunsigned longっていうのは別の変数型を意味します。unsigned longは符号なしの整数を意味します。つまりマイナスの値は取り扱えないという意味です。その分プラス側の大きな値を扱えるんです。何故こんな変数型を使うのかは後ほど説明します。

今回はここに書いてある2つの変数buttonState とlastTime の動きがカギになります。

スケッチの動き

 実際のスケッチの動きですが、setupは前回と同じです。次のloopですが、まずボタンモジュールが繋がっているピンの入力をdigitalReadで読み込んでbuttonStateにセットするのは前回と同じですが、その後に変数lastTimeも0にセットしています。 次のelseのブロック(つまり、digitalReadの読込み結果がHIGHの時)では、lastTimeが0の場合lastTimeに何かを入力しています。millis()って何なんでしょ?

・millis()
 millis()はArduinoで用意されている関数です。Arduinoで現在のスケッチが開始されてから経過した時間をミリ秒単位で返します。スケッチとは別に時間を測ってくれているのです。この時に返してくる値の型がunsigned longなんです。経過時間を返すのでプラスのみ(マイナスは不要)そして、出来るだけ大きな値を扱いたい。なのでunsigned longなんですね。ちなみにunsigned longが扱える値は0 ~4,294,967,295 (2^32 - 1)まで。millis()もこの値を超えると0に戻ってしまいます。単位がミリ秒なので約50日です。ここだけ注意しましょう。

つまり、lastTimeには、最初にdigitalReadがHIGHになった時のスケッチが起動してからの経過時間をミリ秒で入力しています。

さて、続いて点灯を切り替える部分ですがif文の内容が少し変わっていますね。digitalReadでボタンの状態を読み込んでいた部分が以下のようになっています。

 (millis() - lastTime) > 50 && lastTime != 0

lastTimeには最初にdigitalReadがHIGHになった時の時間が記録されています。それから一度でもdigitalReadがLOWになるとlastTimeは0に設定しなおされてしまいます。0に設定されていないということがdigitalReadはその間HIGHを継続(つまり、安定している)ということです。millis()で再度経過時間を確認して、lastTime との時間差を確認すればどれだけの時間入力が安定しているかがわかります(ただしlastTime = 0を除く)。今回は50ミリ秒と設定しました。この値はハードウェアによって多少変わりますが、一般的にはこの値で十分だと思います。

つまり、50ミリ秒間HIGHが安定したら点灯を切り替えるように動きます。実際に書き込んで確認してみてください。

実際には

 ネット上で公開されているスケッチを見てみると、以前説明したdelayコマンド使って処理している例を見かけます。それでも目的を達成できれば問題ないのですがdelayは一定の時間何もしない命令なのでその間CPUは眠っているのも同然なんです。delayを多用すると、行く行く大きなスケッチになった場合、処理が追い付かなくなります。millis()はスケッチ本体の動作とは関係なく動いているので積極的に使いましょう!!

もう少しシンプルに

 しかしながら、ちょっとスケッチが複雑になっちゃいましたね。次回は、もう少しシンブルに記述する方法を考えてみようと思います。
hirocom777.hatenadiary.org


Arduino UNO入門の連載記事はコチラからどうぞ!!