ESP32[5] タイマー割込
ある程度複雑なプログラムを書くとなると、どうしても割込という機能が欲しくなってきます。割込とはハード的なトリガを受けて、今動いているプログラムを一旦やめて、それぞれの割込みに設定されたプログラムを実行することです。OSが載っている場合は、OSも割込みで動いているので、OSの配下の約束で割込を行うことになります。OSがない場合、割り込みベクタに登録したり、少しCPUのハードウエアの知識が必要です。今回は、ESP32の話なので、OSは関係ありません。そのうえ、ESP32の開発環境は割り込みベクタなどのハードを意識する必要がないので普通にCのプログラムを書く感覚で割り込みが実現できます。
割込みは大抵は二種類の割込が欲しくなります。一つは時間を刻むための時間用の割込、もう一つはGPIOが変化したときの割込です。マイコンでプログラムを書く場合は、永久ループの中での処理が殆どなので、そのループの中でGPIOのステータスを確認すること(ポーリング方式)で、割込無しにプログラムを書くことは可能なので、本当に割込が必要かをよく検討することをお勧めします。また、プログラム構造のテクニックになりますが、ループを離れてしまう処理、いわゆるサブルーチン化をしてしまう場合、GPIOのステータスをチェックする間隔が長くなるように作ると、そのチェックまではステータスが反映されません。したがって、ポーリング方式を取る場合はポーリングする間隔をよく考えないと思うような動きをしません。その点、割込はそのようなことを考えなくても済みます。しかし、割込は他のプログラムを止めてしまうので、割込処理には時間がかかるコードを書くのではなく、ステータスの変化状態を確認する程度で抜けるようにしておかないと、ランタイムエラーで悩まされることになります。
割込のうんちくはこのくらいにしておいて実際のESP32の割込の書き方を書いてみたいと思います。まずは、タイマー割込みです。ESP32には4つのタイマーがあります。私はあまり、多くのタイマー割込を使用しません。msec タイマーを作っておきその呼び出し関数内で必要なタイマーを複数カウントするとする方法をよく取ります。
まずは、タイマーをグローバルで初期化します。
hw_timer_t * timer = NULL; //timer 初期化
int msec; //msec カウンター用グローバル変数
タイマー割込み呼ばれる関数を作ります。この関数には、IRAM_ATTRをつけて置くことだそうです。
void IRAM_ATTR msecond() { msec++; if (msec < 0) msec = 0; // Over flow }
タイマーの設定とタイマーイネーブルを setup()の中に書きます。
// msec timer start
timer = timerBegin(0, 80, true); //timer=1us
timerAttachInterrupt(timer, &msecond, true);
timerAlarmWrite(timer, 1000, true); // 1ms
timerAlarmEnable(timer);
timer = timerBegin(0-3,クロック数, true/false);
true: カウントアップ
false: カウントダウン
普通は true で良いと思います。
timerAttachInterrupt(timerのポインタ, 呼び出す関数のポインタ, true/false);
true: エッジカウント
false: レベルカウント
普通は true で良いと思います。
timerAlarmWrite(timerのポインター, 割り込みを発生されるためのカウント数, true/false);
true: 繰り返し割込み
false: 1ショット
普通は true で良いと思います 。
したがって、上記の例題は、1usecタイマーを作ってカウントアップしていき、1000カウントで割り込みを発生されるので、つまり、1msec 毎に割込みが発生します。
私はloop()の中で色々なカウンタにこれを使います。例えば、1秒経過で何かをしたい場合は、カウントを始める位置に、
xx_timer = 1000;
割込み関数の中に、 xx_timer–;
loop()の中に、
if(xx_timer <=0) {
do_something();
xx_timer = 1000;
}
と書いておけば実質1秒カウンターが作れます。使い方が良いのかどうかわかりませんが、これならタイマーが足りなくなるということはありません。