人が侵入したらアクションを起こす その2 割り込み処理

 今年はインテル創業50年に当たります。当初は半導体メモリの会社でした。1974年に発表された8ビット・マイコン8080Aにも、ハードウェア割り込み端子INTとINTEがありました。Arduino UNOのマイコンはATMega328です。CPU本体以外にタイマやUARTなども内蔵しているので、たくさんの割り込み要因があり、割り込みテーブルが用意されているので、素早く対応ができます。

 ATmega328の外部に出ている割り込み専用ピンは2番のINT0と3番のINT1ですが、外部I/Oピンは、三つのグループに分けて割り込みベクタが用意されていて、それぞれのピンに割り込み許可を出す出さないが設定できます。したがって、いずれのピン変化も割り込み用に設定できます。

 Arduino UNOでは、2番ピン(int.0)と3番ピン(int.1)が利用できます。 アセンブラ・レベルで割り込みが発生すると、自動的にレジスタはスタックに退避させられますが、Arduinoのスケッチは高級言語なので、細かいことは考えずに割り込みを利用できます。

用意されている外部割り込み関連の関数

◆割り込みを設定 attachInterrupt(割り込み番号, 呼び出す関数名, トリガ条件)

  割り込み番号;2番ピン(0)もしくは3番ピン(1)
  呼び出す関数名;任意な名称
  トリガ条件;LOW、CAHNGE、RISING、FALLING、HIGH

 UNOでは割り込み番号を0もしくは1と記述していましたが、Due、Zero、MKR1000および101ボードの場合、割り込み番号=ピン番号です。そのようなあいまいさをなくすために、digitalPinToInterrupt(interruptPin)の記述が推奨されています。トリガ条件のHIGHはDue、Zero、MKR1000ボードのみで追加されました。

◆割り込みを停止 detachInterrupt(割り込み番号) 

スケッチ

 ポーリングのときに2番ピンにセンサの出力をつないでいるので、接続はそのままにします。立ち上がりRISING時を検出するため560kΩでプルダウンし、信号がないとき常時LOWにしておきます。割り込みが発生したときに呼び出す関数内では、時間のかかる処理を書かないことが原則です。たとえば、Serial.print()とかは時間がかかるので記述しませんが、動作確認時には原則を無視して実験してもかまいません。理由は、割り込みがいつかかるかわからないし、複数の割り込みが間髪入れずに起こるかもしれないからです。

 ここでは二つの外部割り込みしか使わないように見えますが、実際は、シリアル通信、サーボ・モータ、パルス検出などはライブラリの内部で割り込みが使われていると思われます。

 今回はそのようなタイマ関連の関数を利用していないので、特別な注意は不要です。次のスケッチでは、割り込み関数blink()では、グローバル変数flagを反転するだけの処理を行っています。blink()のなかで、

flag= !flag;
digitalWrite(LEDpin, flag);


と書いても動作は同じです。そうするとloop()では何もすることがないので、サンプルとしては不適切です。

const int LEDpin = 13;
const byte interruptPin = 2;
volatile int flag= LOW;

void setup() {
pinMode(LEDpin, OUTPUT);
pinMode(interruptPin,INPUT);
attachInterrupt(digitalPinToInterrupt(interruptPin), blink, RISING);
}

void loop() {
digitalWrite(LEDpin, flag);
}

void blink() {
flag= !flag;
}

 volatileはコンパイラへの指示です。コンパイラは優秀なので、メモリを消費しないように=メモリ領域を少なくするように、そしてプログラムが速く動くように機械語を生成します。その結果、loop()の中で変化しない変数はレジスタに割り当てたほうが高速になると判断されると、flagが消えてしまうことがあるのをメモリに残してほしいという指示です。
 このサンプルは、割り込みを使う意味がないです。loop()はメインの処理を継続してやり続ける内容になっていて、初めて割り込みが生きます。

 次のスケッチでは、メインのプログラムは、'.'を出力しています。温度を測って表示するというようなスケッチに置き換えてもかまいません。

 割り込みがかかるとflagがHIGHになるので、findと表示したのち改行し、LEDを1秒間点灯します。

const int LEDpin = 13;
const byte interruptPin = 2;
volatile int flag= LOW;

void setup() {
Serial.begin(9600);
pinMode(LEDpin, OUTPUT);
pinMode(interruptPin, INPUT);
attachInterrupt(digitalPinToInterrupt(interruptPin), blink, RISING);
}

void loop() {
Serial.print(".");
if (flag==HIGH) {
Serial.println(" find ");
digitalWrite(LEDpin, HIGH);
delay(1000);
flag= LOW;
digitalWrite(LEDpin, LOW);
}
delay(10);
}

void blink() {
flag= HIGH;
}

 実行中の様子です。560kΩで2番ピンをプルダウンしているので、テストをする目的で2番ピンを5Vへつないだ瞬間、割り込みがかかります。チャタリングがあるので、delay(1000);を取り去って実行すると複数回findが表示されます。センサのパナソニックのPaPIRsの出力をつなぐとチャタリングはなく、人を検知したときだけ、findの表示が出ます。人の動きは遅いので、findの表示は複数回続きます。

 このスケッチの割り込み関数はフラグを操作しているだけです。メインのスケッチでは1秒間LEDを点灯している間も割り込みがかかっているかもしれませんが、無視されています。

 割り込み関数内でグローバル変数をカウントアップするような記述を追加すれば、メインのプログラムで何が実行されていようと、人を検知した回数をメインのスケッチから読み出せます。

 センサを窓際に設置したとき、窓の外を猫が通るかもしれません。人が侵入したときの差をその変数の値で判断して、侵入を確実だと確信できるようなスケッチにすることも可能だと思います。

(※1)プログラム・リストは、表示の関係でTabキーが無視されるので、スペースに代えてあります。また、リスト中を2回クリックすると全選択になるので、CTRL-Cでコピーし、テキスト・エディタにCTRL-Vで貼り付けて利用してください。エディタに持っていくと、スペースやリターン・コードなどが化けていることがあるので、一度消して、Arduino IDEのテキスト・エディタで修正してください。

(※2)ATMega328には、INT0/1の外部割り込み以外に、PCINT0/1/2のピン変化割り込みがあり、UNO以外では利用できるモデルがあります。これ以外に、タイマが3種類内蔵されているので、比較やオーバフロー時に割り込みを発生させられるので、サーボ・モータのライブラリやソフト・シリアル・ライブラリで利用しています。USART、SPI、A-Dコンバータなども、変換/転送終了時などに割り込みを発生できます。Arduinoがそれらをすべて利用しているわけではありません。

(※3)割り込み関係では、割り込みを禁止するnoInterrupts();と有効にするInterrupts();関数があります。通常は割り込みは有効の状態です。どうしても、自分のスケッチのなかでほんの一瞬、ほかの割り込みがかかると正確に処理ができないというときにnoInterrupts();を実行します。そうすると、すべての割り込みを禁止してしまうので、シリアル通信をしていたりすると、バッファ・オーバランとかが起こってエラーになることもあります。自分のスケッチでは割り込みを禁止しているクリチカルな部分は短い数ステップで終了させ、すぐにInterrupts();で割り込みを有効にします。

前へ

人が侵入したらアクションを起こす その1 ポーリングを試す

次へ

グラフィック・ディスプレイで表現力を上げる その1 ライブラリの導入