Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

概要

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 = B00000011val = 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とかを高速化させればもうちょっと短くなるかと。
ここまで短いコードなら、タイマー関数に突っ込んでも気にならない気はする。

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした