FreeRTOS理解その14（デッドロックとWatchdogTimer）

# デッドロックをWatchdogTimerで解消
[その13](https://qiita.com/infinite1oop/items/051bc17b3cc5091a2ab3)のデッドロックのサンプルコードに、[ここで](https://qiita.com/infinite1oop/items/7f94919d1e0d4cf7d18d)紹介したWatchdogTimerを追加し、デッドロックを解消する（リセットする）。

## 環境
Arduino IDE環境下で、ESP32搭載のM5Stackを利用。

## ソースコード
### 定義
```c++
#include <M5Stack.h>
#include "esp_system.h"

#define TIMEOUT 3000
#define L_BTN 39
#define R_BTN 37

uint32_t      l_btn_cnt;
uint32_t      r_btn_cnt;
xTaskHandle   xTaskL, xTaskR;
SemaphoreHandle_t xSemSerial, xSemLCD;

hw_timer_t *timer = NULL;
```

- Watchdogが動作するまでの時間（3000ms）
- M5StackボタンのGPIO番号
- ボタン押下時のカウンタ（押下数）
- タスクハンドラおよびセマフォ
- WatchdogTimer用変数


### 割り込みハンドラ
```c++
void IRAM_ATTR resetModule() {
  ets_printf("Reboot\n");
  esp_restart();
}

void IRAM_ATTR l_btn_intr() {
  l_btn_cnt++;
}

void IRAM_ATTR r_btn_intr() {
  r_btn_cnt++;
}
```

- resetModule(): WatchdogTimer満了時にリセット（esp_restart()）
- l_btn_intr()/r_btn_intr(): 押下カウンタのインクリメント

### TaskLとTaskR
```c++
void TaskL(void *arg) {
  uint32_t old_cnt = l_btn_cnt;
  char buf[16];
  while (1) {
    if (l_btn_cnt != old_cnt) {
      sprintf(buf, "TaskL %d", l_btn_cnt); // \n
      if (xSemaphoreTake(xSemSerial, portMAX_DELAY) == pdTRUE) {
        Serial.println(buf);
        if (xSemaphoreTake(xSemLCD, portMAX_DELAY) == pdTRUE) {
          M5.Lcd.fillRect(20, 20, 200, 40, BLACK);
          M5.Lcd.drawString(buf, 20, 20);
          xSemaphoreGive(xSemLCD);          
        }
        xSemaphoreGive(xSemSerial);
      }
      old_cnt = l_btn_cnt;
    }
    vTaskDelay(1);
    timerWrite(timer, 0); // It works here.
  }
}

void TaskR(void *arg) {
  uint32_t old_cnt = r_btn_cnt;
  char buf[16];
  while (1) {
    if (r_btn_cnt != old_cnt) {
      sprintf(buf, "TaskR %d", r_btn_cnt); // \n
      if (xSemaphoreTake(xSemLCD, portMAX_DELAY) == pdTRUE) {
        M5.Lcd.fillRect(20, 60, 200, 40, BLACK);
        M5.Lcd.drawString(buf, 20, 60);
        if (xSemaphoreTake(xSemSerial, portMAX_DELAY) == pdTRUE) {
          Serial.println(buf);
          xSemaphoreGive(xSemSerial);        
        }
        xSemaphoreGive(xSemLCD);
      }
      old_cnt = r_btn_cnt;
    }
    vTaskDelay(1);
    timerWrite(timer, 0); // It works here.
  }
```

- [その13](https://qiita.com/infinite1oop/items/051bc17b3cc5091a2ab3)のサンプルコード１参照
- timerWrite()のみ追加、ここでWatchdogTimerスタート


### setup()
```c++
void setup() {
  Serial.begin(115200);
  M5.begin();
  M5.Lcd.setTextColor(WHITE);
  M5.Lcd.setTextSize(4);  
  pinMode(L_BTN, INPUT);
  pinMode(R_BTN, INPUT);
  attachInterrupt(digitalPinToInterrupt(L_BTN), l_btn_intr, FALLING);
  attachInterrupt(digitalPinToInterrupt(R_BTN), r_btn_intr, FALLING);
  l_btn_cnt = 0;
  r_btn_cnt = 0;
  Serial.println("Task Start");
  xTaskCreate(&TaskL, "TaskL", 1024, NULL, 1, &xTaskL);
  xTaskCreate(&TaskR, "TaskR", 1024, NULL, 1, &xTaskR);
  xSemSerial = xSemaphoreCreateMutex();
  xSemaphoreGive(xSemSerial);  
  xSemLCD = xSemaphoreCreateMutex();
  xSemaphoreGive(xSemLCD);

  timer = timerBegin(0, 80, true);                  //timer 0, div 80
  timerAttachInterrupt(timer, &resetModule, true);  //attach callback
  timerAlarmWrite(timer, TIMEOUT * 1000, false);    //set time in us
  timerAlarmEnable(timer);                          //enable interrupt
}
```

- [その13](https://qiita.com/infinite1oop/items/051bc17b3cc5091a2ab3)のsetup()にESP32のWatchdogTimer（[こちら](https://qiita.com/infinite1oop/items/7f94919d1e0d4cf7d18d)）参照の初期設定を追加

### loopタスク（loop()）
```c++
//Even when DeadLock happens, this task runs.
void loop() {
  static uint16_t i = 0;
  Serial.printf("Loop %d\n", i++);
  vTaskDelay(1000);
}
```

- １秒ごとにカウンタ表示（シリアル経由でPCへ）

## 実験
[その13](https://qiita.com/infinite1oop/items/051bc17b3cc5091a2ab3)で記載したとおり、左右両方のボタンを押し続けると、TaskLおよびTaskRがデッドロックするタイミングがある。デッドロックしても、loopタスクは動作し続ける（下図の「Loop 12」などの表記）。

![M5StackDeadLockWatchDogのコピー.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/634816/75d9fa07-10a7-eba9-12ec-0cff1a51f49e.png)

デッドロックしてから約3秒後にリセットされているのがわかる（赤枠部分）。