🚀

Arduinoで早押しボタンを作る 【その6 プログラム設計】

2022/10/17に公開

はじめに

目標

  • はじめに押した人しかボタンが付かないようにする

その2やその3でほぼ完成ですね!って言ってたけど実はあれは嘘です
たとえば前回のコードにコピペでボタンとLEDを増やしてみるとこんな感じになると思います

int BUTTON_PIN_0 = 3;
int BUTTON_PIN_1 = 5;
int RESET_PIN = 2;
int LED_PIN_0 = 4;
int LED_PIN_1 = 6;
int ISR_Called = 0;
  
void setup() {
  // put your setup code here, to run once:
  // 結果を表示する用のLEDのピンを設定します
  pinMode(LED_PIN_0, OUTPUT);
  pinMode(LED_PIN_1, OUTPUT);
  
  // D2,3,5ピンの割り込みを有効にします
  PCICR |= B00000100;
  PCMSK2 |= B00101100;
}

void loop() {
  // put your main code here, to run repeatedly:
  if(ISR_Called > 0){
    if(digitalRead(BUTTON_PIN_0) == LOW){
      // 入力ボタン0が押されているのでLEDを点灯します
      digitalWrite(LED_PIN_0, HIGH);      
    }else if(digitalRead(BUTTON_PIN_1) == LOW){
      // 入力ボタン1が押されているのでLEDを点灯します
      digitalWrite(LED_PIN_1, HIGH);      
    }else if(digitalRead(RESET_PIN) == LOW){
      // リセットボタンが押されているのでLEDを点灯します
      digitalWrite(LED_PIN_0, LOW);
      digitalWrite(LED_PIN_1, LOW);
    }
    ISR_Called = 0;
  }
}

// D5, D6いずれかに割り込み(CHANGE)が発生したときに呼ばれる関数
ISR(PCINT2_vect)
{
  //ここに処理を記述
  ISR_Called = 1;
}

コードを読める人なら想像がつくと思いますが、ボタンを押すと両方のLEDが付きます
そしてボタンを増やすたびにこの追加作業をやるのかと思うとめんどうです

これをいい感じにするのが今回の目標です

方法

回路図


配線図

プログラム

設計

オブジェクト指向という設計思想の状態遷移という考え方でプログラムを書いていきたいと思います

状態遷移図(ステートマシン図)

簡単に説明すると、
待機状態、解答状態という2つのモードがあり
待機状態では早押しボタンの入力が有効になっており
解答状態ではリセットボタンの入力のみが有効になっている
それぞれのボタンが押された時に待機状態↔解答状態が入れ替わる
というような動作です

ソースコード

これを実現するために書いたコードがこれです

int RESET_PIN = 2;
int BUTTON_PIN_0 = 3;
int BUTTON_PIN_1 = 5;
int LED_PIN_0 = 4;
int LED_PIN_1 = 6;
int ButtonPins[] = {BUTTON_PIN_0, BUTTON_PIN_1};
int LedPins[] = {LED_PIN_0, LED_PIN_1};
int IOpinCnt = -1;
int PushedButtonIdx = -1;
typedef enum event
{
  evBUTTON_PUSHED = 0,
  evRESET_PUSHED,

  evNONE = -1
} Event;
Event ev = evNONE;
typedef enum state
{
  stSTANDBY = 0,
  stANSWER,

  stNONE = -1
} State;
State st = stNONE;

void setup()
{
  // put your setup code here, to run once:
  // デバッグ用出力の有効化
  Serial.begin(115200);
  Serial.println("setup start");

  // 何度も使うので、ボタンとLEDの個数を定数に取り出しておく
  if(sizeof(ButtonPins) / sizeof(int) != sizeof(LedPins) / sizeof(int)){
    Serial.println("pin count error");
    return;
  }
  IOpinCnt = sizeof(ButtonPins) / sizeof(int);

  // LEDのピンを出力に設定する
  for (size_t i = 0; i < IOpinCnt; i++)
  {
    pinMode(LedPins[i], OUTPUT);
  }

  // D2,3,5ピンの割り込みを有効にする
  PCICR |= B00000100;
  PCMSK2 |= B00101100;

  st = stSTANDBY;
}

void loop()
{
  switch (st)
  {
  case stSTANDBY:
    switch (ev)
    {
    case evBUTTON_PUSHED:    
      ev = evNONE;
      // 何番が押されたかチェックする
      for (size_t i = 0; i < IOpinCnt; i++)
      {
        if (digitalRead(ButtonPins[i]) == LOW)
        {
          PushedButtonIdx = i;
          break;
        }
      }

      if (PushedButtonIdx != -1)
      {
        // 対応するLEDを点灯する
        digitalWrite(LedPins[PushedButtonIdx], HIGH);
        // 解答中状態に遷移する
        st = stANSWER;
      }
      PushedButtonIdx = -1;
      break;
    default:
      break;
    }
    break;
  case stANSWER:
    switch (ev)
    {
    case evBUTTON_PUSHED:
      ev = evNONE;

      // 押されたのがリセットボタンかチェックする
      if (digitalRead(RESET_PIN) == LOW)
      {
        ev = evRESET_PUSHED;
        break;
      }
      break;
    case evRESET_PUSHED:
      ev = evNONE;
      // LEDを消灯する
      for (size_t i = 0; i < IOpinCnt; i++)
      {
        digitalWrite(LedPins[i], LOW);
      }
      // ボタン待機状態に戻る
      st = stSTANDBY;
      break;

    default:
      break;
    }
    break;
  default:
    break;
  }
}

// PCINT2のいずれかのピンに割り込み(CHANGE)が発生したときに呼ばれる関数
ISR(PCINT2_vect)
{
  ev = evBUTTON_PUSHED;
}

結果

はじめに押した人しかボタンが付かないようにできました

まとめ

これまでとは大幅にコードを変更してボタンの排他制御を実現しました
変更容易性も少しは高くなったと思うので、今後はより楽に機能を追加できるのではないでしょうか?

てなわけで次回はなにか機能を追加してみましょう

Discussion