Arduinoでタクトスイッチのチャタリング対策

#概要
Arduinoで電子工作をしていていろいろ悩むことの一つにタクトスイッチのチャタリング対策。delay()関数を使えば簡単にできてしまうけども、delay()は使いたくない。そんなときのチャタリング対策覚え書き。ライブラリ使えよという気持ちもなきにしもあらず。

#調べたら出てくるやつ
チャタリング対策は結局スイッチのon,　offがうまく働いていないときで、よくあるのは複数の変数を用意して、何度か判定をかける方法。とか、タイマー割り込みを使う方法。
タイマー割り込みを高々スイッチ処理に使うのももったいないので、タイマー割り込みは出来る限り使いたくないなぁとも思ったり。複数の変数を使う場合は配列などを使って順次格納していき、全てがHighになったら押したと認識させる。例えば3回読み直す方法はこんな感じ。

```
for(int i; i>3; i++){
   val[i] = digitalRead(PIN);
//  delay(10);
}

if((val[0] & val[1] & val[2]) == 1){
   state = 1 - state;
}
```

みたいな。
別に配列使わなくてもbyte型に詰め込んだらいいのでは?と思ったので、byte型に順次情報を詰め込んでチャタリング対策する方法を模索。以下のように。

#チャタリング対策
##コード1
###コード

```
//変数の宣言
byte val; //格納するバイト型を用意
byte state; //スイッチの状態

//   スイッチが押されたら古い情報を残してスイッチの情報を取得する
if(digitalRead(PIN) == 1){
   while((digitalRead(PIN) == 1)&&(val != B011111111)&&(val !=B11111111)){
      val = val<<1;　//上位ビットに古い情報を格納
      val += digitalRead(PIN);　//下位ビットに新しい情報を格納
   }
}else{
   val = 0;　//押されてなければ初期化
}

//チャタリング対策後のスイッチ判定
switch(val){
   case B01111111: //押されたと判断
      state = 1 - state;
      val = B111111111;　//最上位ビットに判定済みであることを格納
      break;
//   case B11111111: //押され続けたと判断      
//      break;
   default: //押されていないと判断
      break;
}
```

だいたいこんな感じ。

###変数の宣言
```
byte val; //格納するバイト型を用意
byte state; //スイッチの状態
```

変数の設定。int型でもいけるけど、少ないメモリを活用するならbyte型でいいのかな。
チャタリング判定をvalで行い、結果をstateに反映させる。

###チャタリング処理
```
if(digitalRead(PIN) == 1){
   while((digitalRead(PIN) == 1)&&(val != B01111111)&&(val !=B11111111)){
      val = val<<1;　//上位ビットに古い情報を格納
      val += digitalRead(PIN);　//下位ビットに新しい情報を格納
   }
}else{
   val = 0;　//押されてなければ初期化
}
```

while条件はボタンが押されつづけたかどうか。B11111111については後述。
初期条件は```val = B00000000```で、スイッチが押されると条件が真になり、```B00000001```となる。そのまま押し続ければ```val = B00000011```、```val = B00000111```と増えていき、チャタリングが起こらなければ```val = B01111111```で抜け出す。
途中でチャタリングが起きれば、```val = B00011110```などと最下位に0が入った状態で抜け出す。

###スイッチ判定と後処理
```
switch(val){
   case B01111111: //押されたと判断
      state = 1;
      val = B111111111;　//最上位ビットに判定済みであることを格納
      break;
//   case B11111111: //押され続けたと判断      
//      break;
   default: //押されていないと判断
      break;
}
```
無事にチャタリングなく```val = B01111111```にたどり着いたら、押されたと判断して```state = 1```になり一件落着。後処理として```val = B11111111``` (```B01111111```でない) とさせることにより、```val = B11111111```の間は、2サイクル目以降、while文の条件に入らないので、スイッチを離すまで値は変わらず初期化もされない。またswitch文の条件で分けることで、多重判定されることない。

###まとめ1 と 改良点
多分これでチャタリング対策ができてる気はする。多分。
while文を7サイクルしたあとにチャタリングしたら誤作動が起きるとか、そんなことは知らない。その場合は2バイトとかにすれば15サイクルまで対応できる。
一回の処理で判定することを諦めて、たとえばwhile文を使わずにif文だけにした場合、whileからのbreakに悩む必要がないのでもう少し簡単にかける。ただし、loop()のサイクルを少なくとも7回行わなければいけないので、loop()が長いとボタンの効きが鈍くなる気はする。その場合はB01111111で抜け出す代わりに、もっと下位のビットで抜け出すようにすればよいかも。

##コード2
###コード

```
//変数の宣言
byte val; //格納するバイト型を用意
byte state; //スイッチの状態

//   スイッチが押されたら古い情報を残してスイッチの情報を取得する
if((digitalRead(PIN) == 1){
      val = val<<1;　//上位ビットに古い情報を格納
      val += digitalRead(PIN);　//下位ビットに新しい情報を格納
   }
}else{
   val = 0;　//押されてなければ初期化
}

//チャタリング対策後のスイッチ判定
if(val = B01111111){
   state = 1 - state;
}
```

###まとめ2
while文を使って1サイクルに収めるということを諦めると、ものすごくすっきりしたコードになる。whileからのbreakを設定する必要がないから。
```B01111111```になりstateを切り替える。その後押しっぱなしにしている間は```B11111111```になるので、stateを変えることはない。ついでにスイッチを離した瞬間はすぐに0になる。離した時のチャタリングも考えるべきかもしれないけど、離したあとに連続して1を7回拾うことは無いと思う。
綺麗にできたので、関数として閉じて、複数個のスイッチに対応できるようにする。

##コード3
###コード

```
#define MAXPIN 13

int checkbutton(int i){
   static byte val[MAXPIN];
   static byte state[MAXPIN];

   if(digitalRead(PIN[i]) == 1){
      val[i] = val[i]<<1;
      val[i] += digitalRead(PIN[i]);
   }else{
      val[i] = 0;
   }

   if(val[i] == B01111111){
      state[i] = 1 - state[i];
   }

   return state[i];
}
```

関数で閉じたことで、関数を呼び出すたびに初期化されないようにstaticを使って変数を定義する必要があり、staticを使ったことにより、複数ピンに対応させるのに配列を使う必要がでる。なんというか、無駄が多い気もするけども。
たーだ関数で閉じたおかげでloop()内がすっきりした。

```
void loop(){
   for(int i=0; i<MAXPIN; i++){
      button[i] = checkbutton(i);
   }
}
```

loop()内で書くのはこれだけ。

###まとめ
digitalReadとかを高速化させればもうちょっと短くなるかと。 
ここまで短いコードなら、タイマー関数に突っ込んでも気にならない気はする。

チャタリングの起こりにくいスイッチ買うのが一番早い気もする。
書いてる途中だけど、ひと段落したので一旦公開。