モーター制御などの組み込みシステムでは、一定の制御周期でプログラムを実行するタイマ割り込みが必要不可欠ですよね。
今回の記事では、M5Stackでタイマ割り込みを実装する方法について解説します。
なお、排他制御など難しい処理は扱わず、分かりやすいよう最低限の実装に留めました。
それでは始めていきましょう!
この記事はこんな人におすすめ!
- 一定の制御周期でプログラムを実行したい人
- タイマ割り込みについて知りたい人
- M5Stackの知識を深めたい人

概要
タイマ割り込みは、一定の周期でプログラムを実行したいときに用います。
今回のゴールは、図のようにシリアル通信で定期的に情報を送信するプログラムの作成です。

タイマ割り込みの仕組み
マイコンは動作クロック周波数で高速に動作します。
すぐに処理されてしまう簡単なプログラムを1秒間隔で動作させたいとき、どのようにしますか?
メインループ内に1秒待つ処理を加えるのも1つの手です。
でも、タイマ割り込み機能を使えば、待ち時間を作る必要はないです。
タイマ割り込みプログラムをいきなり見ても何をどうしたいのか、さっぱり分からないと思います。プログラムを理解するにはタイマ割り込みの知識が必要不可欠です。
そこでタイマ割り込みについて簡単に説明します。
タイマ割り込みは簡単です。
マイコンが持つタイマ機能が時間をカウントします。
所定の時間になったらメインの処理を一時中断して別の処理をしてねと、タイマ機能からCPUへ割り込み要求を出させます。別の処理が実行し終わったら、中断していたメインの処理に戻します。
割り込みサービス・ルーチン
割り込みサービス・ルーチンは割り込み関数のことです。
タイマ機能が割り込みを要求したときに実行する処理を記述します。
割り込みサービスルーチンを実行する際は、メイン処理ループを一時中断。中断したメイン処理ループのアドレスは忘れないようスタックに記憶しておきます。
割り込みサービス・ルーチンが終了したら、スタックから中断したメイン処理ループのアドレスを取り出し、メイン処理ループを再開します。

割り込みベクタ・テーブル
タイマ割り込みが発生したときに実行する割り込みサービス・ルーチンを対応付けた表を割り込みベクタ・テーブルといいます。
割り込みベクタ・テーブルには、割り込みサービス・ルーチンの先頭アドレスが書かれます。

M5Stackのタイマ機能
タイマ割り込みプログラムを書く前に、M5Stackのタイマ機能を調べておきましょう。
タイマ数
M5StackのマイコンはESP32が使用されています。ESP32のタイマ数は「4」です。プログラムでは0~3を指定します。
ペリフェラル周波数
M5Stackのペリフェラル周波数は80MHzです。
タイマのカウント周波数は、分周器というペリフェラル周波数を整数分の1に落とす装置で作り出します。タイマカウントしやすいように分周比を80とし、1カウントを80M / 80 = 1MHzにするのが一般的です。
タイマ割り込みプログラム
ここまでタイマ割り込みの仕組み、M5Stackのタイマ機能について説明してきました。これらを理解していれば、タイマ割り込みサンプルプログラムの意味がきっと分かると思います。
▼は、0.1s毎にシリアル通信でプログラム経過時間を送信するサンプルプログラムです。
#include<M5Stack.h>
hw_timer_t * timer = NULL;
volatile uint32_t counter = 0;
volatile uint32_t current_time = 0;
void onTimer(){
counter++;
current_time = millis();
Serial.printf("No. %u, %u ms\n", counter, current_time);
}
void setup() {
M5.begin();
Serial.begin(115200);
// タイマ作成
timer = timerBegin(0, 80, true);
// タイマ割り込みサービス・ルーチン onTimer を登録
timerAttachInterrupt(timer, &onTimer, true);
// 割り込みタイミング(ms)の設定
timerAlarmWrite(timer, 100000, true);
// タイマ有効化
timerAlarmEnable(timer);
}
void loop() {
}
プログラムは、まず割り込みサービス・ルーチン onTimerを定義します。そして、タイマ作成→タイマ割り込みサービス・ルーチンの登録→割り込みタイミングの設定→タイマ有効化といった流れです。
このプログラムを実行し、シリアル通信をモニタすると、0.1毎にタイマ割り込みが生じていることが確認できます。

シリアル通信のモニタ方法は▼の記事で解説しています。

ここからは各処理について解説していきます。
割り込みサービス・ルーチンの定義
タイマ割り込み時に実行する割り込みサービス・ルーチンを定義します。
void onTimer(){
/* (省略) */
}
タイマ作成
タイマを作成するにはtimerBegin関数を呼び出します。
timer = timerBegin(0, 80, true);
第1引数は使用するタイマ番号です。0~3の値を設定しましょう。今回は0を設定しました。
第2引数は分周比です。M5Stackのペリフェラル周波数は80MHzでしたね。分周比80に設定し、1カウントを1MHzとしました。
タイマ割り込みサービス・ルーチンの登録
割り込みサービス・ルーチン「onTimer」をタイマ割り込みに登録するため、timerAttachInterrupt関数を呼び出します。
timerAttachInterrupt(timer, &onTimer, true);
第1引数には、作成したタイマを指定します。第2引数は登録する割り込みサービス・ルーチンを指定します。第3引数はtrueを指定しましょう。割り込みはレベルトリガーになります。
割り込みタイミングの設定
何秒毎に割り込みが発生するか設定するため、timerAlarmWrite関数を呼び出します。
timerAlarmWrite(timer, 100000, true);
第1引数には、作成したタイマを指定します。第2引数は、割り込み発生のカウント数です。今回は0.1秒毎に割り込み発生したいので、1MHz × 0.1s = 100000回 としています。第3引数は、割り込みは周期的か1回か指定します。 trueだと周期的に割り込みが発生します。
タイマ有効化
ここまででタイマ割り込みの設定が整いました。timerAlarmEnable関数を呼び出し、タイマ割り込みを有効化しましょう。
timerAlarmEnable(timer);
引数は作成したタイマを指定します。
まとめ
今回の記事では、M5Stackでタイマ割り込みを実装する方法について解説しました。
解説したことは
- タイマ割り込みの仕組み
- M5Stackのタイマ機能
- タイマ割り込みプログラム
タイマ割り込みには、割り込み中に別の割り込みを禁止する排他制御がありますが、今回は分かりやすい最低限の実装に留めました。
これから、M5Stackでタイマ割り込みを実装する方のお役に立てれば幸いです。
最後までお読み頂きありがとうございました。

