“DS3231 RTCを使う”の記事では、DS3231のアラームによるトリガー出力信号をロジアナで測定した。今回はこの信号でArduinoに割込みを掛けてみる。
attachInterrupt関数
Arduinoの組み込み関数attachInterrupt()を使うと、ポートへの入力信号で割込みをかけることが出来る。本家サイトのリファレンスはこちら。
要約するとこんな感じ。
- 割込みに使えるピンがボードによって決まっている(UNOの場合はD2,D3)
- ピン番号と「割込み番号」の対応付けにdigitalPinToInterrupt()関数を使う
- 「割込みハンドラ」は引数なしの戻り値void型の関数で定義する
- 割込みの原因となる「トリガ信号の極性」はLOW,CHANGE,RISING,FALLINGから選ぶ
- attachInterrupt()関数には「割込み番号」「割込みハンドラ」と「トリガ信号の極性」を与える
ハードウエアの配線
ArduinoとDS3231は、以下の表のように結線。
Arduino | DS3231 |
---|---|
A4 | SDA |
A5 | SCL |
D2 | SQW(INT) |
スケッチの変更
“DS3231 RTCを使う”の記事で使ったサンプルスケッチを少し変更して、以下のようにする。アラームのトリガ検出をI2Cでチップの内部レジスタを読み出すDS3231_get_a2()関数で行っていたのを、割込み経由でアラーム検出するよう変更した。
以下、サンプルスケッチから変更した関数のみ掲載。
ds3231AlermInt.ino
#define INTERRUPT_PIN 2
static int alermTriggered = 0;
// 割り込みハンドラ
void alermISR()
{
alermTriggered = 1;
}
void setup()
{
Serial.begin(115200);
Wire.begin();
DS3231_init(DS3231_CONTROL_INTCN);
DS3231_clear_a2f();
set_next_alarm();
// 割込のセットアップ
attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), alermISR, FALLING);
}
void loop()
{
char buff[BUFF_MAX];
unsigned long now = millis();
struct ts t;
// once a while show what is going on
if ((now - prev > interval) && (Serial.available() <= 0)) {
DS3231_get(&t);
// display current time
snprintf(buff, BUFF_MAX, "%d.%02d.%02d %02d:%02d:%02d", t.year,
t.mon, t.mday, t.hour, t.min, t.sec);
Serial.println(buff);
if (alermTriggered) { // ← 割込み経由のチェックに変更
Serial.println(" -> alarm2 has been triggered");
set_next_alarm();
// clear a2 alarm flag and let INT go into hi-z
DS3231_clear_a2f();
alermTriggered = 0;
}
prev = now;
}
}
実行結果
各信号線にロジアナを接続し、上記スケッチを実行した結果は以下。
プログラムの構造上、割込みハンドラで変数alermTriggeredにフラグ設定が無い限り、loop()関数内でset_next_alarm()関数が呼ばれて次のアラーム設定がされない。つまり、割込みが連続して掛かっているという事はプログラムが期待通りに動作している証拠だ。
割込み処理内で複雑な処理はNG
“DS3231 RTCを使う”の記事の時も実は疑問に思った事だが、割込み出力SQW(INT)がLowの期間が数秒間と長い(上記のキャプチャで表示されているロジアナの記録期間全体はなんと約2分!)。これは、loop()関数の中で5秒に1回割込みチェックを行いその中で割込みクリア関数DS3231_clear_a2f()を呼ぶ事に起因する(今回プログラムをちゃんと読んでやっと気づいた…汗)。そこで、割込みハンドラの中でDS3231_clear_a2f()関数を呼んだら一瞬で割込みクリア出来るのではと変更したところ…なんとプログラムが正常に動かなくなってしまった。
サンプルプログラムだけぼーっと眺めていると忘れてしまうのだが、I2CデバイスであるDS3231の割込みクリア関数DS3231_clear_a2f()の中では当然I2C通信でDS3231のレジスタをクリアに行くわけで、これはマイコン本体内のメモリマップドレジスタの変更みたく一瞬で終わる処理ではない。そのような複雑な処理を割込みハンドラ内で記述するのはご法度であり、不適切だったというわけだ。
コメント