今回も Arduino のフレームワークで RTOS を使います。
今回はコア0とコア1のタイマー割り込みの実験です。
この記事は JTAG でデバッグすることを前提にして書いています。
環境構築については こちら をご覧になってください。
投稿時の開発環境を記しておきます。
PC:
Windows10 OS
開発ボード :
ESP32-DevKitCーVE
(Soc : ESP32-D0WD-V3)
デバッガー(H/W):
FT2232D
デバッガー (S/W) :
Visual Studio Code + PlatformIO + Arduino Framework
FreeRTOS
こちら が本家ですが、ESP32用としては このへん を見るのが良さそうです。
プロジェクトをつくる
VSCodeで、もし使っていたプロジェクトを開いていたら、File – Close Folder して閉じておきます。
その後にVSCodeからPlatformIOをOpenします。
以下の内容でプロジェクトを新規に作成します。
Name : ESP32A-RTOS-timerInt
Board : Espressif ESP32 Dev Module
Framework : Arduino Framework
Name : ESP32A の “A” は Framework (Arduino Framework)の頭文字を示しています。
(後から見てわかるように、Arduinoを使うことを明示しています)

platformio.ini に以下の4行を追加して、 Ctrl + s で保存しておきます。
COM[8]の8の部分はデバイスマネージャーのポート(COMとLPT)で Silicon Labs CP210x から始まるCOMの番号を記述します。
upload_port はプログラムをアップロードする時に使うCOMポートを指定します。
monitor_port は printf()出力をモニターするCOMポートを指定します。
debug_tool = minimodule
upload_port = COM[8]
monitor_port = COM[8]
monitor_speed = 115200
今回使うタイマー割り込みに関連するAPI
(注)RTOSのタイマーではありません
機能:タイマーを作成します
第1引数:タイマーの番号
第2引数:プリスケーラー(分周比)を設定します
第3引数:trueでカウントアップします(falseでカウンドダウン)
戻り値:作成されたタイマー構造体へのポインタが返る。つくられない場合にはNULLが返る
※上のリンクでは第3引数が resolution になっていますが誤りです。
機能:タイマーに割り込み処理を関連づけます
第1引数:タイマー構造体へのポインタ
第2引数:割り込みをトリガーするエッジを選択します(2022.8月現在 trueのみ有効)
戻り値:なし
機能:タイマーのアラーム値と自動リロードを設定します
第1引数:タイマー構造体へのポインタ
第2引数:イベントを生成するアラーム値
第3引数:自動リロードするか否か
戻り値:なし
機能:タイマーのアラームイベントを有効にします
第1引数:タイマー構造体へのポインタ
戻り値:なし
コーディングする
main.cppを以下のようにコーディングします。
#include <Arduino.h>
#define STACK_DEPTH 4096 // スタック深さ
#define PRIORITY2 2 // 優先順位
#define DIVIDER80 80 // プリスケーラー 80MHz/80 = 1MHz (1clock)
#define IO25 25
#define IO26 26
TaskHandle_t taskHandle[2];
hw_timer_t *timer0 = NULL;
hw_timer_t *timer1 = NULL;
// コア0の割り込みハンドラ
void IRAM_ATTR onTimer0() {
digitalWrite(IO25, HIGH);
// あくまでも実験用です、通常は時間待ちしないでください
delayMicroseconds(1000);
digitalWrite(IO25, LOW);
}
// コア1の割り込みハンドラ
void IRAM_ATTR onTimer1() {
digitalWrite(IO26, HIGH);
// あくまでも実験用です、通常は時間待ちしないでください
delayMicroseconds(1000);
digitalWrite(IO26, LOW);
}
// コア0で動くタスク
void Task0a(void *args) {
timer0 = timerBegin(0, DIVIDER80, true); // 1クロックを1MHz(1μsec)に設定する
// タイマー割り込み関数を登録する
timerAttachInterrupt(timer0, &onTimer0, true);
timerAlarmWrite(timer0, 10000, true); // 10msec周期でタイマー割り込み
timerAlarmEnable(timer0); // 有効にする
while (true) {
printf("Task0a\r\n");
vTaskDelay(1000);
}
}
// コア1で動くタスク
void Task1a(void *args) {
timer1 = timerBegin(1, DIVIDER80, true); // 1クロックを1MHz(1μsec)に設定する
// タイマー割り込み関数を登録する
timerAttachInterrupt(timer1, &onTimer1, true);
timerAlarmWrite(timer1, 10000, true); // 10msec周期でタイマー割り込み
timerAlarmEnable(timer1); // 有効にする
while (true) {
printf("Task1a\r\n");
vTaskDelay(1000);
}
}
void setup() {
pinMode(IO25, OUTPUT);
pinMode(IO26, OUTPUT);
digitalWrite(IO25, LOW);
digitalWrite(IO26, LOW);
// コア0で動くタスクをつくる
xTaskCreatePinnedToCore(Task0a, "Task0a", STACK_DEPTH, NULL, PRIORITY2,
&taskHandle[0], PRO_CPU_NUM);
// コア1で動くタスクをつくる
xTaskCreatePinnedToCore(Task1a, "Task1a", STACK_DEPTH, NULL, PRIORITY2,
&taskHandle[1], APP_CPU_NUM);
}
void loop() { vTaskDelay(100); }
ビルドして実行する
ビルドしてエラーがないことを確認して、メニューから Run – Start Debugging (またはF5キー)すると少ししてから
loopTaskWDTEnabled = false; の行で停止します。
もう一度 F5キーを押すとプログラムを実行します。
プログラムの概要
setup()自体はコア1上で動いています。
ここでコア0, コア1 それぞれで動くように2つのタスクをつくります。
コア0のタスクでタイマー割り込みハンドラを生成することにより、それはコア0で動作するようになります。
コア1のタスクでタイマー割り込みハンドラを生成することにより、それはコア1で動作するようになります。
10msec周期でタイマー割り込みを動作させてみました。
それぞれの挙動がわかるように異なるGPIO出力をパタパタさせています。
デュアルコアの割り込み確認
コア0, 1 それぞれのタイマー割り込みハンドラ内でそれぞれのGPIO出力をパタパタさせて、それをモニターしてみました。
それぞれの割り込みで GPIO25 と 26 を ハイレベルにしています。
GPIO25 と 26 のハイレベルが重なっていれば割り込みもデュアルコアで動いていると言えますね。
上が GPIO25 下が GPIO26 です。


私のイメージでは同期はとれないものだと思っていました。
同期しているように見えます。そういう仕様なのでしょうか。。
ついでにsetup()でコア0, コア1のタスクを作成する順番をかえてみました。
コア1の方が先に動いているようですね。

いずれにせよ割り込みもデュアルコアで動いていることが確認できました。
コア0は主に無線用として使われるようなのでシステム全体を見ながらバランス良く使ってあげましょう (^_^)
いかがでしたか?
皆さまもデュアルコアの割り込みを実感できましたか。