esp32のtimer割り込みと外部割込みの個人的メモ

# 概要
タイマー割り込みを使おうとしたら、案外一筋縄ではいかなかったのでメモします。
難しいことは一点に限ります

**なんかrebootがかかっている！**

マルチタスクの処理等の影響かもしれませんが、非常に難しいです。

# 非常に参考になるサイト
https://lang-ship.com/blog/work/esp32-timer/

https://lang-ship.com/blog/work/esp32-freertos-l03-multitask/

timer関数自体の中身

https://github.com/espressif/arduino-esp32/blob/af35773d65a8f328dba8090dff66ba43abe0e7be/cores/esp32/esp32-hal-timer.c#L112-L117

https://lang-ship.com/blog/work/esp32-freertos-l04-interrupt/

# サンプル1
タイマー割り込みではGiveするのみで、別に回してるタスクで監視する
CPU1で回します。CPU0はdelay(1)を入れないとWDTが起動してrebootします。


```C

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をロジアナで計測した結果を示す。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405484/f666bf98-d58f-21cc-645f-ea73f6801fbe.png)

タスク内で300回ごとに周期を100usと50usの切り替えをしている。切り替え時の様子

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405484/f0206948-53d0-3a02-859a-5f351a13f0b0.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405484/de68af77-7ddc-a338-e1cd-05ebedaa168f.png)

50usもまぁまぁ正確です。
切り替え時の間隔は5~10usほど伸びているように見えました。

周期を10usの場合を見ると、かなりきついイメージです。

タスクをもう一つ増やしたところ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405484/7076b052-c6d5-e6cd-55bb-10d2788a297f.png)

こんな感じで、ちょうど1msタスクが回らない部分が発生しました。

1ms以下に正確に動作してほしい場合esp32は弱いですね。

# Timer_ISR内の処理量
どれくらいの処理を入れたらrebootが起こるのか試してみます。

GPIO.out_w1ts
GPIO.out_w1tc

を繰り返してみます。

## 結果
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405484/3540c728-de97-c2eb-bbde-802f5667795b.png)
100セットぐらいでは特に問題なし。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405484/056e16db-9b1c-a1f1-f731-6ea25bbac858.png)
300セットまでくると、HIGH,LOWの繰り返しだけで処理時間がかかる
さらに増やすと、次の割り込みまでに処理が終わらないだろう

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405484/dd78bb89-ad61-fdab-598d-1436a6d2e33f.png)
800セットまで増やしたところ、間隔が設定の50usから83usに変化した。
これは割り込み中に次の割り込みに飛ばずに、割り込み処理が終わってから、次の処理をしているということだ。

```C

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内
```C: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);
}
```
* セマフォ定義（並列実行プログラム定義）
* タイマー定義

```C
timerBegin(0, 80, true);  // timer0使用(0~3まで)、分周比80
timerAlarmWrite(timer, 50, true);  //割り込みが発生するカウント数、true:繰り返し実行
```
この場合は、

https://garretlab.web.fc2.com/arduino/esp32/reference/cores/timer/timerAlarmWrite.html

## 割り込み処理内

```C
void IRAM_ATTR onTimer() {
  portENTER_CRITICAL_ISR(&timerMux);  //割込み禁止
  isrCounter++;
  // 自由な処理記述:割り込み処理なので内容は軽く
  portEXIT_CRITICAL_ISR(&timerMux);   //割り込み処理許可
  xSemaphoreGiveFromISR(timerSemaphore, NULL);   //セマフォの何かをpdTRUEに変更
}
```

## 並列処理内部、セマフォ内部

```C
void timer_task(void *pvParameters) {
  while (1) {
    if (xSemaphoreTake(timerSemaphore, 0) == pdTRUE) {
      portENTER_CRITICAL(&timerMux);
      isrCount = isrCounter;
      // 自由な処理記述
      portEXIT_CRITICAL(&timerMux);
    }
  }
}
```

## Lチカテスト

```C
#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:

}
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405484/f694fb28-a8a4-e6d5-47d6-0d96a6ddfa7a.png)

小さいパルスの幅は100us(10kHz)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405484/b7bad64d-163a-894e-e6ba-e0b6a3d76526.png)

なぜか、割り込み処理が起きていないところがある。
割り込みが早すぎて、セマフォの処理が追いついていないということが原因である。

## 改良
セマフォ内でdigitalWriteしていたが、割り込み処理内に入れてみる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/405484/8aea26a5-c126-a9d8-63ba-0c039cf9b2ee.png)

正しく動いた