PASのブログ

PASの中の人が書く雑記ブログです。

アナマル⑤ スイッチを使おう③

さて前回は、スイッチのオルタネート動作にチャレンジしました。
でもうまく行ったり行かなかったりしていますね。。。

まずはここの原因を探りつつ、オルタネート動作を完成させていきます。

なぜうまく動かないのか。

プログラムはループしている。

一部抜粋して載せます。

void loop() {
  if(!digitalRead(SW1))//もしSW1がHIGHではないなら
    {
      state = 1 - state;
    }

  if(state == 1)
  {
   digitalWrite(LED_BUILTIN, HIGH);   // LEDへの信号をHIGH(点灯)
  }
  else
  digitalWrite(LED_BUILTIN, LOW);    // LEDへの信号をLOW(消灯)
  }

原因はvoid loop()です。

プログラムの流れはこうです。

スイッチが押されていたらstateの値をひっくり返す

stateが1なら点灯させる

stateが0なら消灯させる

スイッチが押されていたらstateの値をひっくり返す

ポイントはこのプログラムは
「スイッチが押されていたら」と書かれていることです。 つまり、押しっぱなしの状態のときには、ONとOFFが目にも止まらない速さで繰り返されているのです。

では、「スイッチを押したら」と書くにはどうすればいいでしょうか?
色々考えられそうですが、例えば

  1. 一度押したら離すまでstateを書き換えない。
  2. 前回のstateと今回のstateの値を比べて、違っていたら押された瞬間だと判断する。

僕の場合は大抵1番でやるのですが、2番で書かれている記事が多いようですので2番にチャレンジしてみます。

前回の状態を保存してチャレンジ

//SW2
//stateを使って状態保存
//前回の状態を保存

const int SW1 = A4;
int state = 0; //ONOFFの状態を保存
int val = 0; //スイッチの状態を保存
int old_val = 0; //前回の状態を保存

void setup() {
  pinMode(LED_BUILTIN, OUTPUT); //LEDはとりあえず基板上のものを設定。
  pinMode(SW1,INPUT_PULLUP);//A4をデジタルポート入力でプルアップ。
  
}

void loop() {
  val = digitalRead(SW1); //スイッチの状態をvalに入れる
    
  if((val == 1) && (old_val == 0))  
    {
      state = 1 - state;
    }

  old_val = val;  // 処理が済んだので前回の状態にvalをコピーする

  if(state == 1)
  {
   digitalWrite(LED_BUILTIN, HIGH);   // LEDへの信号をHIGH(点灯)
  }
  else
  {
  digitalWrite(LED_BUILTIN, LOW);    // LEDへの信号をLOW(消灯)
  }

}

これを書き込んで動作させてみましょう。
完璧ではないですが先程よりかなり安定してきましたね。

では解説します。

流れとしては、前回と同じく、

stateをひっくり返す

stateが1なら点灯、0なら消灯させる

となっており変わりません。
変わったのは、まず、ifの条件文です。

前回はif(!digitalRead(SW1))
今回はif((val == 1) && (old_val == 0))
ですね。

&&というのは、「かつ」という意味です。条件式のアンドは&&と書きます。

なのでif((val == 1) && (old_val == 0))の意味としては「valが1で、かつ、old_valが0ならば」ということです。
押しっぱなしにすると、old_val = val; // 処理が済んだので前回の状態にvalをコピーするという部分で、valもold_valも1が書き込まれますので、state = 1 - state;という、LEDの点灯消灯を切り替えされる部分が呼び出されません。

では何故、それでもまだ安定しないのでしょうか?
それは、スイッチのチャタリングという動作が関係してきます。

チャタリングとはなにか

プッシュスイッチにしても、トグルスイッチにしても、ほとんどのスイッチは内部にスプリングなり板バネの構造があります。 切り替えた際のショックや、バネの跳ね返りで、非常に短い間隔で、波形がばらつき、ONとOFFを繰り返します。

f:id:pas_fx:20200604200533p:plain
チャタリングの様子 出典:wikipedia

そうしますと、その瞬間はスイッチを連射しているのと同じことになりますので、前項で試した、valを使って状態推移を保存するやり方でも、すり抜けることがあります。

チャタリングに対策する方法はこんな方法などがあります。

ハード的に
○抵抗とコンデンサで波形をなまらせる方法。
○さらにコンパレータで波形を鮮明にする。
○シュミットトリガなどを使い波形を整える(立ち上がりを鮮明にする)
ソフト的に ○チャタリングが落ち着くまで待って、一定時間ONだったらONと判断する。
○何度もチェックして一定回数ONを数えたらONと判断する。 ○一回目のONを検出した瞬間、状態を監視するのをやめる。

など様々な方法があります。 今回は、最も(プログラムを書く上で)単純な、
「ソフト的に一回目のONを検出した瞬間、状態を監視するのをやめる。」
方法を使います。

※うちのブランドで販売しているスイッチャーはこれらを組み合わせて二重三重に対策することで信頼度を高めていたりします。

完成

//SW2
//stateを使って状態保存
//前回の状態を保存
//ソフト的に一回目のONを検出した瞬間、状態を監視するのをやめる。

const int SW1 = A4;
int state = 0; //ONOFFの状態を保存
int val = 0; //スイッチの状態を保存
int old_val = 0; //前回の状態を保存

void setup() {
  pinMode(LED_BUILTIN, OUTPUT); //LEDはとりあえず基板上のものを設定。
  pinMode(SW1,INPUT_PULLUP);//A4をデジタルポート入力でプルアップ。
  
}

void loop() {
  val = digitalRead(SW1); //スイッチの状態をvalに入れる
    
  if((val == 1) && (old_val == 0))  
    {
      state = 1 - state; // stateをひっくり返す。
      delay(10);// 10ms待つ。
    }

  old_val = val;  // 処理が済んだので前回の状態にvalをコピーする。

  if(state == 1)
  {
   digitalWrite(LED_BUILTIN, HIGH);   // LEDへの信号をHIGH(点灯)
  }
  else
  {
  digitalWrite(LED_BUILTIN, LOW);    // LEDへの信号をLOW(消灯)
  }

}

delay(10)は、10ms(ミリセカンド、10/1000秒)待て、という命令です。

完璧にオルタネイト動作が実現しました。

次回は、この段階までの知識で4chにする方法を解説します。