🖱️

Arduinoで早押しボタンを作る 【その3 割り込み】

2022/10/11に公開

はじめに

目標

  • 入力用のピンを増やす
    経緯についてはこちらのまとめの項を参照

方法

D2, D3ピン以外のデジタルピンが割り込み入力として使用できない状況を打開する方法はいくつか考えられます

  1. ポーリングで実現する
    デジタルピン自体は豊富にあるので、これを能動的にチェックしにいくポーリングであれば3個以上の入力を監視することが可能であると考えられる
    →できればやりたくない。そもそもめんどくさいから割り込みを選んでるところがあるのでタイマーやポーリングの実装はしたくない
  2. 別のボードを購入する
    公式のリファレンスによればMega系など他のボードでは3個以上のピンに割り込みの設定をできることになっている[1]
    →新たにボードを買うのはめんどくさい。箱に入れる構想もあるのであまり大きいものに変更もしたくない
  3. D2, D3ピン以外のデジタルピンに割り込みを設定する方法を探す
    →きっとあるだろう。ほかが面倒なので探すしかない

というわけで3を採用します

回路図

動作の確認としてはたとえばD5, D6ピンでその2の時と同じことが実現できればよいのでこんな感じで良いでしょう

配線図

プログラム

実際には紆余曲折いろいろと試した結果こうなっているのですが
ひとまず完成版のソースコードをご覧ください

ソースコード

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

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

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

こちらのコードを実行するとD5, D6ピンで割り込み動作が、実現できます
前回との差分、解説などは長くなるので補足に回したいと思います

結果

入力ボタンが押されるとLEDが点灯、リセットボタンが押されるとLEDが消灯することが確認できました
動作の見た目は前回のものと同じなので動画はなしです

まとめ

なんとかD2, D3以外のピンでも割り込みを発生させることができました
追加でボードを購入しなければいけない危機を回避しました
これでボタンを増やすことができるようになったので今度こそほぼ完成ですね

次回はArduinoから少し離れて、早押しボタンの筐体を作りたいと思います

補足

D2, D3以外のデジタルピンで割り込みを実現する方法

前回との差分

まずは前回のソースコードと見比べてみましょう

  • 初期化の部分
-  // 入力ボタンのピンとリセットボタンのピンをそれぞれ設定します
-  attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), buttonPushed, FALLING);
-  attachInterrupt(digitalPinToInterrupt(RESET_PIN), resetPushed, FALLING);

+  // D5, D6ピンの割り込みを有効にします
+  PCICR |= B00000100;
+  PCMSK2 |= B01100000;
  • 割り込み発生時に呼ばれる部分
-  // 入力ボタンが押された時に呼ばれる関数
-  void buttonPushed(){
-    // 入力ボタンが押されたことを記憶しておきます
-    isButtonPushed = 1;
-  }
-  // リセットボタンが押された時に呼ばれる関数
-  void resetPushed(){
-    // リセットボタンが押されたことを記憶しておきます
-    isButtonPushed = 2;
-  }

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

どちらも全然違いますね、順に説明していきます
(loopの中身も異なりますがこれは本質ではないので説明は省略します)

参考にさせていただいた記事

こちらの記事が大変参考になりました
https://garchiving.com/external-interrupts-with-arduino/
今回必要な内容だけ抜粋すると

  • 各ピンはPCIE0, PCIE1, PCIE2の3つのグループに分けられている
    • PCIE0には(D8~D13)、PCIE1には(A0~A5)、PCIE2には(D0~D7)が含まれる
  • それぞれに該当するPCMSKのbitを1にすることで割り込みを有効にできる
  • この時PCICRでPCIE0~2のグループごとの割り込みも有効にする必要がある
  • 割り込み時に呼ばれる関数はグループごとに異なりそれぞれPCIE0 → ISR(PCINT0_vect), PCIE1 → ISR(PCINT1_vect), PCIE2 → ISR(PCINT2_vect)である

レジスタによる割り込みの有効化

レジスタとはプロセッサー内部の記憶装置で、Arduinoのようなマイコンボードでは特定の場所の数値を変更することで設定を変更することができます

たとえばD5の割り込みを有効にしたい場合、

bit 7 6 5 4 3 2 1 0
Arduinoピン D7 D6 D5 D4 D3 D2 D1 D0
PCMSK2 - - 1 - - - - -

としたうえで、

bit 7 6 5 4 3 2 1 0
PCICR - - - - - 1 - -

と設定する必要があります
これをコードで表すと

PCICR  |= B00000100;
PCMSK2 |= B00100000;

となりますOR(|=)を取っているのは設定したいbit以外を変更しないようにするためです

割り込み発生時に呼ばれる関数

前項の設定を行うと以下の関数が呼ばれることとなります

ISR(PCINT2_vect)
{
  //ここに処理を記述
}

attachInterruptで設定する場合は割り込み時に呼ばれる関数(例:buttonPushed)をピン毎に設定することができました。しかし、レジスタで有効化した場合はD0~D7のどのピンが割り込みを発生させてもISR(PCINT2_vect)が呼ばれてしまいます
またattachInterruptで設定する場合は、LOW, CHANGE, RISING, FALLING, HIGHを指定することでピンの電圧が低いとき、変わったとき、上がったときなど割り込みが発生する条件も指定することができましたが、こちらもすべての条件でISR(PCINT2_vect)が呼ばれてしまいます(CHANGEと同じ)

attachInterrupt場合と同じことを実現しようとするとどのピンが割り込みを発生させたのか判断したうえで、前の電圧状態を覚えておく必要があったりして結構めんどくさいです。本来はISR(PCINT2_vect)の中でこのような処理をして、loopの中には本当にイベントが発生したときの処理だけを書くのが健全?だと思いますが、今回は2つのボタンが押されたかどうかという単純なイベントしか判断する必要がないので、ISR(PCINT2_vect)の中ではいずれかのボタンが押されたことだけを通知して、判定などもloopの中に書いてしまいました

参考

脚注
  1. Arduino Reference attachInterrupt() ↩︎

Discussion