概要
タイマー割り込みを使おうとしたら、案外一筋縄ではいかなかったのでメモします。
難しいことは一点に限ります
なんかrebootがかかっている!
マルチタスクの処理等の影響かもしれませんが、非常に難しいです。
非常に参考になるサイト
timer関数自体の中身
サンプル1
タイマー割り込みではGiveするのみで、別に回してるタスクで監視する
CPU1で回します。CPU0はdelay(1)を入れないとWDTが起動してrebootします。
hw_timer_t * timer = NULL;
volatile SemaphoreHandle_t timerSemaphore;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
volatile uint32_t isrCounter = 0;
volatile boolean flag = false;
volatile boolean flag_change = false;
void IRAM_ATTR onTimer() {
portENTER_CRITICAL_ISR(&timerMux);
isrCounter++;
portEXIT_CRITICAL_ISR(&timerMux);
xSemaphoreGiveFromISR(timerSemaphore, NULL);
}
void timer_task(void *pvParameters) {
while (1) {
if (xSemaphoreTake(timerSemaphore, 0) == pdTRUE) {
uint32_t isrCount = 0;
portENTER_CRITICAL(&timerMux);
isrCount = isrCounter;
portEXIT_CRITICAL(&timerMux);
digitalWrite(13, !digitalRead(13));
flag_change = true;
//Serial.println(isrCount);
}
if (isrCounter % 300 == 0 and flag_change == true) {
flag_change = false;
//timerEnd(timer);
timerWrite(timer, 0); //タイマー初期化
timerStop(timer);
if (flag == true) {
flag = false;
timerAlarmWrite(timer, 100, true); //repeat
//timerAttachInterrupt(timer,&onTimer,true);
} else {
flag = true;
timerAlarmWrite(timer, 50, true); //repeat
//timerAttachInterrupt(timer,&onTimer,true);
}
//timerAlarmEnable(timer);
timerRestart(timer);
}
}
}
void setup() {
Serial.begin(115200);
delay(50); // Serial Init Wait
xTaskCreateUniversal(
timer_task,
"task1",
8192,
NULL,
1,
NULL,
APP_CPU_NUM
);
timerSemaphore = xSemaphoreCreateBinary();
timer = timerBegin(0, 80, true);
timerAttachInterrupt(timer, &onTimer, true);
timerAlarmWrite(timer, 50, true);
timerAlarmEnable(timer);
pinMode(13,OUTPUT);
}
void loop() {
delay(1);
}
#結果
タスク内でpin13を出力を反転させているので、pin13をロジアナで計測した結果を示す。
タスク内で300回ごとに周期を100usと50usの切り替えをしている。切り替え時の様子
50usもまぁまぁ正確です。
切り替え時の間隔は5~10usほど伸びているように見えました。
周期を10usの場合を見ると、かなりきついイメージです。
タスクをもう一つ増やしたところ
こんな感じで、ちょうど1msタスクが回らない部分が発生しました。
1ms以下に正確に動作してほしい場合esp32は弱いですね。
Timer_ISR内の処理量
どれくらいの処理を入れたらrebootが起こるのか試してみます。
GPIO.out_w1ts
GPIO.out_w1tc
を繰り返してみます。
結果
300セットまでくると、HIGH,LOWの繰り返しだけで処理時間がかかる
さらに増やすと、次の割り込みまでに処理が終わらないだろう
800セットまで増やしたところ、間隔が設定の50usから83usに変化した。
これは割り込み中に次の割り込みに飛ばずに、割り込み処理が終わってから、次の処理をしているということだ。
hw_timer_t * timer = NULL;
volatile SemaphoreHandle_t timerSemaphore;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
volatile uint32_t isrCounter = 0;
volatile boolean flag = false;
volatile boolean flag_change = false;
void IRAM_ATTR onTimer(){
portENTER_CRITICAL_ISR(&timerMux);
isrCounter++;
portEXIT_CRITICAL_ISR(&timerMux);
xSemaphoreGiveFromISR(timerSemaphore,NULL);
for(int i = 0; i<100; i++){
GPIO.out_w1tc = 1 << 13; //13pin LOW
GPIO.out_w1ts = 1 << 13; //13pin HIGH
}
}
void setup() {
pinMode(13,OUTPUT);
Serial.begin(115200);
delay(50);
timerSemaphore = xSemaphoreCreateBinary();
timer = timerBegin(0,80,true);
timerAttachInterrupt(timer,&onTimer,true);
timerAlarmWrite(timer,50,true);
timerAlarmEnable(timer);
uint32_t fre= getApbFrequency();
Serial.println("hello");
Serial.println(fre);
}
void loop() {
if(xSemaphoreTake(timerSemaphore,0) == pdTRUE){
uint32_t isrCount = 0;
portENTER_CRITICAL(&timerMux);
isrCount = isrCounter;
portEXIT_CRITICAL(&timerMux);
//digitalWrite(13,!digitalRead(13));
flag_change = true;
//Serial.println(isrCount);
}
}
外部割込み+タイマー割り込みでタイマー割り込みの処理時間が長い場合
rebootするかと思ったが、問題なかった
引っ掛かったこと
setup内で
xTaskCreateUniversalでタスクを作った後に、delayを入れるとrebootした
原因
setup内でセマフォの定義をしていた
かつ、定義をdelayの後においていた、
つまり
setup{
task定義
delay
セマフォ定義
}
task{
セマフォ使用
}
としていた。task内でセマフォの状態を参照していたため
delayを入れると、セマフォの定義が終わる前にtaskが実行され、未定義のセマフォが参照されrebootしていた
対策
delayをtask定義の前に入れるか、削除で解決
タスク定義はできるだけ最後にした方がいいのかな。
マルチタスクの弊害が出てしまった。
今回でだいぶesp32のことを知れた。
おもしれぇやつだな、おめぇ
追記:プログラム解説
setup内
void setup(){
xTaskCreateUniversal(
timer_task,
"task1",
8192,
NULL,
1,
NULL,
APP_CPU_NUM
);
timerSemaphore = xSemaphoreCreateBinary();
timer = timerBegin(0, 80, true);
timerAttachInterrupt(timer, &onTimer, true);
timerAlarmWrite(timer, 50, true);
timerAlarmEnable(timer);
}
- セマフォ定義(並列実行プログラム定義)
- タイマー定義
timerBegin(0, 80, true); // timer0使用(0~3まで)、分周比80
timerAlarmWrite(timer, 50, true); //割り込みが発生するカウント数、true:繰り返し実行
この場合は、
割り込み処理内
void IRAM_ATTR onTimer() {
portENTER_CRITICAL_ISR(&timerMux); //割込み禁止
isrCounter++;
// 自由な処理記述:割り込み処理なので内容は軽く
portEXIT_CRITICAL_ISR(&timerMux); //割り込み処理許可
xSemaphoreGiveFromISR(timerSemaphore, NULL); //セマフォの何かをpdTRUEに変更
}
並列処理内部、セマフォ内部
void timer_task(void *pvParameters) {
while (1) {
if (xSemaphoreTake(timerSemaphore, 0) == pdTRUE) {
portENTER_CRITICAL(&timerMux);
isrCount = isrCounter;
// 自由な処理記述
portEXIT_CRITICAL(&timerMux);
}
}
}
Lチカテスト
#include <Arduino.h>
#include <Ticker.h>
//38kHz 搬送波用タイマー
hw_timer_t * timer = NULL;
volatile SemaphoreHandle_t timerSemaphore;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
volatile uint32_t isrCounter = 0;
volatile boolean flag = false;
volatile boolean flag_change = false;
//IRIS OHYAMAリモコン用タイマー
Ticker IR_IRIS;
void timer_task(void *pvParameters) {
while (1) {
if (xSemaphoreTake(timerSemaphore, 0) == pdTRUE) {
uint32_t isrCount = 0;
portENTER_CRITICAL(&timerMux);
isrCount = isrCounter;
digitalWrite(13, !digitalRead(13));
portEXIT_CRITICAL(&timerMux);
}
}
}
void IRAM_ATTR onTimer() {
portENTER_CRITICAL_ISR(&timerMux);
isrCounter++;
portEXIT_CRITICAL_ISR(&timerMux);
xSemaphoreGiveFromISR(timerSemaphore, NULL);
}
void setup() {
Serial.begin(115200);
xTaskCreateUniversal(
timer_task,
"task1",
8192,
NULL,
1,
NULL,
APP_CPU_NUM
);
timerSemaphore = xSemaphoreCreateBinary();
timer = timerBegin(0, 80, true);
timerAttachInterrupt(timer, &onTimer, true);
timerAlarmWrite(timer, 100, true);
timerAlarmEnable(timer);
//IR_IRIS.attach();
pinMode(13,OUTPUT);
}
void loop() {
// put your main code here, to run repeatedly:
}
なぜか、割り込み処理が起きていないところがある。
割り込みが早すぎて、セマフォの処理が追いついていないということが原因である。
改良
セマフォ内でdigitalWriteしていたが、割り込み処理内に入れてみる
正しく動いた