●Arduinoでスイッチを使う

Arduinoに押しボタンスイッチを取り付けてみます。
基本的に入力可能なデジタルピンならばどこにでも接続できます。

一番若い番号であるデジタルピン2番に接続する事にしました。

デジタル回路に接続するには、プルダウンやプルアップをする必要があります。
ピンがフリーの時にプラスの電圧を掛けるのがプルアップ、マイナスの電圧を掛けるのがプルダウンと言います。
もし、プルアップもプルダウンもしないと、値が不定になり誤動作する可能性があります。


▼プルダウン

プルダウンでは、GNDと対象とするピンを1KΩ〜10KΩの抵抗で接続してピンに常にマイナスの電圧がかかるようにします。
それに対してスイッチはプラス極に接続します。
スイッチが押されるとピンはプラスの電圧がかかり、スイッチが離されるとマイナスの電圧がかかるためデジタル回路が認識できるのです。
スイッチが押された状態ではプラス極とマイナス極を抵抗がつないでしまい電力が消費されますが、抵抗値が大きいため些細な電流しか流れません。


プルダウンしている所、GNDと2番デジタルピンが抵抗で接続されています。
void setup () {
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(2, INPUT);
}

void loop () {
  digitalWrite(LED_BUILTIN, digitalRead(2));
}

基板上のLEDのピン番号はLED_BUILTINの定数になっています(ボードにより違うため)、このプログラムではスイッチが押されるとLEDが点灯します。


▼プルアップ

Arduinoには内蔵プルアップ回路があり、プルアップの場合にはプラス極との間に抵抗を接続しなくても、プルアップできるようになっています。
ただし、内蔵プルダウン回路は存在しません。
プルアップを使用した場合には、対象デジタルピンがプラスの電圧を帯びているため、スイッチはGNDとの間に接続します。
スイッチを押すことによりピンにマイナスの電圧がかかり、デジタル信号として認識されます。


内蔵プルアップを使うため、スイッチ以外の部品が必要ありません。
void setup () {
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(2, INPUT_PULLUP);
}

void loop () {
  digitalWrite(LED_BUILTIN, !digitalRead(2));
}

基板上のLEDのピン番号はLED_BUILTINの定数になっています(ボードにより違うため)、このプログラムではスイッチが押されると基板上のLEDが点灯します。
内蔵プルアップを有効にしているため"pinMode"の設定で、"INPUT_PULLUP"が指定されています。

プルダウンに対してプルアップはピンにかかる電圧が逆になるため、プログラムでデータを取得する際にONとOFFが逆転させる必要があります。
そのため、"digitalRead"の戻り値に対して"!"を付ける事によりONとOFFを逆転させています。


■チャタリング対策

押しボタンなどの各種接点が接触するスイッチでは、ONになった瞬間やOFFになった瞬間に接点がバウンドしたり切れが良く接続されないために、
瞬間的にON・OFFが繰り返されます。
人間の目ではほんの一瞬ですが、マイコンの高速なスピードでは非常に長い間ON・OFFが繰り返されている事になるためプログラムが誤動作します。
void setup () {
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(2, INPUT_PULLUP);
}

int i,j;

void loop () {
  j=digitalRead(2); 
  if(!j){
    //スイッチが変化するまで待つ
    while(digitalRead(2)==j);
    i=!i;
    digitalWrite(LED_BUILTIN,i);
  }
}

このプログラムではボタンを押すとLEDが点灯、もう一度押すと消灯になるようになっていますが、通常のスイッチではチャタリングでうまく動作しないはずです。
チャタリング防止には対象ピンを複数回にわたり時間を空けて読んで値が確定しているのか確認する必要があります。
void setup () {
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(2, INPUT_PULLUP);
}

int i,j;

void loop () {
  if(chatteringCut_pullupPin(2)){
    i=!i;
    digitalWrite(LED_BUILTIN,i);
  }
}

unsigned int chatteringCut_pullupPin(int pinNo){
  unsigned int i=0;
  while(!digitalRead(pinNo)){
    i++;
    delay(1);
  }
  //1111111111000000 でアンドを取り下位ビットをマスクする
  //つまりループが63を超えるまで0しか返さない
  return i&(~(unsigned int)0x3F);
}

chatteringCut_pullupPin関数では、時間を空けてデジタルピンを64回読んでONを確定させて、その後はOFFになるまでカウントアップし長押しを検出します。
このプログラムでは正しく動作します。
ただ、ボタンを押している間はプログラムがループに突入して全体が止まるためもっと別のアプローチが必要になるかもしれません。
チャタリング対策のプログラミング方法は色々あると思いますのでご参考までに。


■押したらONになるプログラム

実際には押した瞬間にONになってほしいですので、押したらON、もう一度押したらOFFになるプログラムを作成しました。
void setup () {
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(2, INPUT_PULLUP);
}

int i;
bool sw1; //スイッチの状態を保存するフラグ

void loop () {
  if (chatteringCut_pullupPin(2, &sw1)) {
    i = !i;
    digitalWrite(LED_BUILTIN, i);
  }
}

#define NUM_OF_READS 64  //読みだし回数

unsigned int chatteringCut_pullupPin(int pinNo, bool*vari) {
  unsigned int _on = 0, _off = 0;
  while (1) {
    if (digitalRead(pinNo)) {
      //OFFの場合
      _off++;
      _on = 0;
    } else {
      //ONの場合
      _off = 0;
      _on++;
    }
    if (_on > NUM_OF_READS || _off > NUM_OF_READS) break;
    delay(1);
  }
  if (_on > NUM_OF_READS) {
    //ONで終了した場合
    if (*vari) {
      return 0;
    } else {
      *vari = true;
      return 1;
    }
  } else {
    //OFFで終了した場合
//    if (!*vari) {
//      return 0;
//    } else {
      *vari = false;
      return 0;
//    }
  }
}
このプログラムならある程度実用的に使えるのではないかと思います。
しかし、押した瞬間に64ms以上ループして停止します。
そこで改良したのが次です。
void setup () {
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(2, INPUT_PULLUP);
}

//スイッチ1の状態を保存する変数
unsigned char sw1, sw1_on_cont, sw1_off_cont;

void loop () {
  //遅延をさせずに10ms毎に実行するためミリ秒を比較する
  static unsigned long m_sec_old;
  if (m_sec_old + 10 < millis()) {
    m_sec_old = millis();
    chatteringCut_pullupPin(2, &sw1, &sw1_on_cont, &sw1_off_cont);
  }

  //スイッチ1の変数が変化しONになったらON/OFFする
  static char sw1_old;
  if (sw1!=sw1_old) {
    if(sw1) digitalWrite(LED_BUILTIN,!digitalRead(LED_BUILTIN));
    sw1_old = sw1;
  }
}

//チャタリングを除くため繰り返し読む回数
#define NUM_OF_READS 5

bool chatteringCut_pullupPin(unsigned int pinNo, unsigned char*sw_flag, unsigned char*on_counter, unsigned char*off_counter) {
  if (digitalRead(pinNo)) {
    //OFFの場合
    *on_counter = 0;
    if (*off_counter < NUM_OF_READS) (*off_counter)++;
  } else {
    //ONの場合
    if (*on_counter < NUM_OF_READS) (*on_counter)++;
    *off_counter = 0;
  }
  if (*on_counter >= NUM_OF_READS) *sw_flag = true;
  else if (*off_counter >= NUM_OF_READS) *sw_flag = false;
  return *sw_flag;
}
赤色部分にてミリ秒の時間を見ながら処理するかを決めています。
delay()を使わないため、プログラムの一時停止は無いです。


▲トップページ > マイコンなど