ESP32でマルチコアを試す12行

![SS 192.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565043/a41cd967-f549-8677-1efa-2e99bf69518d.png) とにかく今すぐESP32のマルチコアを試したい

#やること

ESP32DevKitCとArduinoIDEを使って、ESP32のマルチコア処理を試します。
マルチスレッドとマルチコアの違いについては[**こちら**](
https://qiita.com/Ninagawa_Izumi/items/0e01762eaf916569605d)にまとめました。
![SS 1378.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565043/788d9604-850e-bf59-613a-9cd08d44b1ee.png)

今回はイメージとしては図の右上になり、コアを２つ使いますが、プログラムで使うスレッドは１つずつという感じになります。

#参考URL
[mgo-tec電子工作 Arduino – ESP32 のマルチタスク ( Dual Core ) を試す](https://www.mgo-tec.com/blog-entry-arduino-esp32-multi-task-dual-core-01.html)
が大変参考になりました。ありがとうございました。

#準備
- Arduino IDEの入ったPC
- Arduino IDEでESP32が使えるようにしてある
- ESP32DevKitC

# 最小限のスケッチ
なるべく最小限の構成でマルチコアを試してみます。

```cpp:multi_core_test.ino
TaskHandle_t thp[1];//マルチスレッドのタスクハンドル格納用
long a = 0;

void setup() {
  Serial.begin(115200);
  xTaskCreatePinnedToCore(Core0a, "Core0a", 4096, NULL, 3, &thp[0], 0); 
}

void loop() {
  Serial.println(a);//メインで実行するプログラム
  delay(100);
}

void Core0a(void *args) {
  while (1) {
    delay(1);//サブで実行するプログラムを書く
    a++;
  }
}
```

##### 実行結果
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565043/d48fa084-9d7b-b843-5b7f-0cd3061fdc3f.png" width = 500>
ArduinoIDEのシリアルモニタ（115200bps)を開くと、100ごとに数字がカウントアップ表示されてきます。

# 説明その１
ESP32にはCPUコアを2つ持っており、内部的にはFreeRTOSという分散処理が得意なOSが働いています。
CPUコアにはCore0とCore1があり、Arduino IDEで書いたスケッチの**メインループはCore1が担当**しています。
今回はCore0aという名前で、メインではない方のCPUコアでスレッドを１つ立て、サブとして働くプロセスを実行しています。

変数aに対し、Core0aのループはdelay(1)のペースで加算を行なっています。
一方、Core1で動くメインループは、delay(100)のペースで変数aの内容をシリアルモニタに表示しています。
つまり、Core1が１回実行される間に、Core0aは100回実行されます。結果として、表示される内容は100番とばしになります。

# 説明その２
マルチスレッド用のコマンドをスクリプトに解説を加える形で説明します。

```cpp:multi_core_test.ino

//マルチスレッドのタスクハンドル格納用です。
//タスクハンドルはアドレスで渡す必要があるため、ここで配列として設定しています。
TaskHandle_t thp[1];

//カウントを加算し、シリアルモニタで表示するための変数を設定しています。
//ここで宣言した変数は、コアを跨いでアクセスできる変数になります。
long a = 0;

//セットアップ関数です。スレッドの設定はここで行います。
void setup() {

  //シリアルモニタ表示用の宣言です。
  Serial.begin(115200);

  //ここでスレッドを立てることを宣言しています。
  xTaskCreatePinnedToCore(Core0a, "Core0a", 4096, NULL, 3, &thp[0], 0); 
  //xTaskCreatePinnedToCore()がスレッドの宣言です。
  //内容は([タスク名], "[タスク名]", [スタックメモリサイズ(4096or8192)],
  //      NULL, [タスク優先順位](1-24,大きいほど優先順位が高い)],
  //      [宣言したタスクハンドルのポインタ(&thp[0])], [Core ID(0 or 1)]); 
}

void loop() {//メインCPU(Core1)で実行するプログラム
  Serial.println(a);//変数aの中身をシリアル出力
  delay(100);//100/1000秒待つ
}

void Core0a(void *args) {//サブCPU(Core0)で実行するプログラム
  while (1) {//ここで無限ループを作っておく
    //サブで実行するプログラムを書く
    delay(1);//1/1000秒待つ
    a++;//aに１を加算する
  }
}
```

同じ要領で2つのコアそれぞれに対し、２つめ３つめのサブプロセスを追加してくことができます。
今回はマルチコアを試しましたが、１つのコアに複数のスレッドを立てればマルチスレッドとなります。
マルチコアとマルチスレッドは同時に設定できますので、冒頭の図の右下のような形でプログラムを走らせることもできます。
ただし、複数の独立したプロセスを同時に走らせると、同期やデータのやりとりのタイミングでいくつかの調整が必要になってきます。
FreeRTOSにはその制御を簡単にするための関数も用意されており、ArduinoIDEからも使うことができるようです。

# 実験

Core0,Core1それぞれに3つずつスレッドを立てます。
メインのloopも加え、合計7つのスレッドが動くか試します。


```cpp:multi_core_7threads.ino

TaskHandle_t thp[6];//マルチスレッドのタスクハンドル格納用
long a ,b, c = 0;

void setup() {
  Serial.begin(115200);
  xTaskCreatePinnedToCore(Core0a, "Core0a", 4096, NULL, 7, &thp[0], 0);
  xTaskCreatePinnedToCore(Core0b, "Core0b", 4096, NULL, 8, &thp[1], 0);
  xTaskCreatePinnedToCore(Core0c, "Core0c", 4096, NULL, 9, &thp[2], 0);
  xTaskCreatePinnedToCore(Core1a, "Core1a", 4096, NULL, 6, &thp[3], 1);
  xTaskCreatePinnedToCore(Core1b, "Core1b", 4096, NULL, 5, &thp[4], 1);
  xTaskCreatePinnedToCore(Core1c, "Core1c", 4096, NULL, 4, &thp[5], 1);
}

void loop() {//メインループ
  Serial.print("main"); Serial.println(a+b+c); delay(100);
}

void Core1a(void *args) {//スレッド ①
  while (1) {Serial.print("a:"); Serial.println(a); delay(100);}
}

void Core1b(void *args) {//スレッド ②
  while (1) { Serial.print("b:"); Serial.println(b); delay(100);}
}

void Core1c(void *args) {//スレッド ③
  while (1) { Serial.print("c:"); Serial.println(c); delay(100);}
}

void Core0a(void *args) {//スレッド ④
  while (1) { delay(1); a++;}
}

void Core0b(void *args) {//スレッド ⑤
  while (1) { delay(2); b++;}
}

void Core0c(void *args) {//スレッド ⑥
  while (1) { delay(3); c++;}
}
```
##### 実行結果
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565043/744d799d-edf2-f80d-62ac-c58719e78465.png" width = 500>

７つのスレッドが無事に動きました。
スレッド間の実行優先順位として、**メインのvoid loop() {}は最下位**に位置している模様です。

# ウォッチドッグとディレイ
メインループ以外にあるdelay(1)は今回のプログラムでは必須です。
システム側で無限ループに陥っているスレッドを監視しており、そう判定すると自動的にシステムをリセットするウォッチドッグという仕組みがあります。
それを防ぐことができるのがdelay(1)のようです。
（デフォルトではメインループにはウォッチドッグが適用されません。）
また、delay中に他の処理を行うことで並行処理を実現させているらしく、そういう意味でも各スレッドにdelayが入っていると円滑です。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/565043/bb9ab711-ef51-18d8-fdc2-d71eb90bff84.png" width =200>


# 次回

次はプロセス間の数値の受け渡し方法や同期のとりかたなどについてまとめたいと思います。

次回記事：

https://qiita.com/Ninagawa_Izumi/items/f75400b579ef6fbcbdf4

次次回記事：

https://qiita.com/Ninagawa_Izumi/items/cc9b960535ec87b7c2e3

前回記事：

https://qiita.com/Ninagawa_Izumi/items/0e01762eaf916569605d