🐙

Arduinoで早押しボタン自作 【補足1 割り込みの精度】

2023/07/16に公開

はじめに

https://zenn.dev/articles/e914f50fdef502/
こちらの記事に「Arduinoの割り込みの精度はRPと比べてどうなんだい?(意訳)」
というコメントをいただきまして、
これについては私も気になっていたので検証してみました

目標

自作した早押しボタンがどの程度の入力の時間差まで正確に判定できるのかを確認する

方法

2台のArduinoを用いて、
片方からは任意の時間差でパルスを出力する
もう一方ではこれを回答者のボタン押下として割り込み検知して、
どちらが先に押下されたかを出力する

これを規定回数繰り返して、パルス出力の時間間隔に応じて
入力順が入れ替わる頻度がどのように変化するか検証する

回路図

2台のArduinoでパルスを出力するピンと受信するピンをつなぐだけなので省略

出力波形

出力波形(TIME_SPAN=100usのとき)

カーソル合わせるの忘れた

プログラム

ソースコード

パルス出力側
// 立下りを検知するようにしているので反転する
#define PRE_LOW HIGH
#define PRE_HIGH LOW
#define TIME_SPAN 50

void setup()
{
  // put your setup code here, to run once:  
  pinMode(13, OUTPUT);  // 波形確認用のトリガー
  pinMode(12, OUTPUT);
  pinMode(11, OUTPUT);

  // 初期状態として
  digitalWrite(12, PRE_LOW);
  digitalWrite(11, PRE_LOW);
  // ちょっと待って
  delay(500);

  // 規定回数だけ繰り返す
  for (size_t i = 0; i < 1000; i++)
  {
    digitalWrite(13, PRE_LOW);
    // 1PIN目をON
    digitalWrite(11, PRE_HIGH);
    // 検証のための時間だけ待って
    delayMicroseconds(TIME_SPAN);
    // 2PIN目をON
    digitalWrite(12, PRE_HIGH);

    // ちょっと待って
    delay(50);
    // リセットする
    digitalWrite(13, PRE_HIGH);
    delay(50);
    digitalWrite(11, PRE_LOW);
    digitalWrite(12, PRE_LOW);
    // 次のためにちょっと待つ
    delay(200);
  }
}

void loop()
{
  // put your main code here, to run repeatedly:
}
早押しボタン側
#define IOpinCnt 8
#define BUTTON_PIN_0 6
#define BUTTON_PIN_1 7
#define BUTTON_PIN_2 7
#define BUTTON_PIN_3 9
#define BUTTON_PIN_4 14
#define BUTTON_PIN_5 15
#define BUTTON_PIN_6 16
#define BUTTON_PIN_7 17
#define RESET_PIN 8

int ButtonPins[IOpinCnt] = {
    BUTTON_PIN_0, BUTTON_PIN_1, BUTTON_PIN_2, BUTTON_PIN_3,
    BUTTON_PIN_4, BUTTON_PIN_5, BUTTON_PIN_6, BUTTON_PIN_7};

typedef enum event
{
  evBUTTON_PUSHED = 0,
  evRESET_PUSHED,
  evCORRECT_PUSHED,
  evWRONG_PUSHED,
  evINIT_END,

  evNONE = -1
} Event;

typedef enum state
{
  stTURN_ON = 0,
  stSTANDBY,
  stANSWER,

  stNONE = -1
} State;

int PushedButtonIdx = -1;
Event ev = evNONE;
State st = stNONE;

void setup()
{
  // put your setup code here, to run once:

  // デバッグ用出力の有効化
  Serial.begin(115200);
  while (!Serial)
  {
  }
  Serial.println("setup start");

  st = stSTANDBY;

  // 必要なピンの割り込みを有効にする
  PCICR |= B00000111;  // [X X X X X PCMSK2 PCMSK1 PCMSK0]
  PCMSK0 |= B00000001; // PCINT0_vect[X X SCK MISO MOSI D10 D9 D8] // SCKはまともに動作しなさそう
  PCMSK1 |= B00001000; // PCINT1_vect[X RESET A5 A4 A3 A2 A1 A0]
  PCMSK2 |= B01000000; // PCINT2_vect[D7 D6 D5 D4 D3 D2 D1 D0]
}

void loop()
{
  // put your main code here, to run repeatedly:
  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;
          Serial.println(i);
          st = stANSWER;
          break;
        }
      }
      break;
    default:
      // do nothing
      break;
    }
  case stANSWER:
    switch (ev)
    {
    case evBUTTON_PUSHED:
      ev = evNONE;
      if (digitalRead(RESET_PIN) == LOW)
      {
        st = stSTANDBY;
      }
      break;
    default:
      // do nothing
      break;
    }
    break;
  default:
    // do nothing
    break;
  }
}

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

結果

Serial.println(i)のところでA3よりも先にD6が押されているとダメ
なので、出力が全部7だったらOKで0が出力されている場合はNGです
結果は

10ms 1ms 500us 100us 54us
試行回数 1000 1000 1000 1000 1000
総出力数 1006 1002 1001 1004 1004
0の出力回数 7 2 0 7 7

考察

今回の実験では54usまで検証を行いました
(出力側のArduinoでディレイなしで2つのピンを連続で操作したときの実測値が54usだった)
結果を見る限りでは54usまでほぼ正常に動作しているようです

しかし、そもそも時間の間隔にかかわらず約0.5%ほど正常に動作しないことも分かりました
1000回しかループしてないのに出力が1000を超えるのは何なんでしょうか?

Discussion