ESP32
概要
Arduino core for the ESP32を使った、ESP-WROOM-32開発ボードのマルチタスク・デュアルコアの実験です。もはやArduinoではないような気もしますが、マルチタスクとデュアルコアについて試してみました。
Arduino Uno等では難しかった、マルチタスクでの動作が簡単にできます。各タスクを、任意のコアで動作させることも、スケジューラ任せで動作させることも可能です。ただし、今まで考えなくてもよかったことがいろいろ出てくるので、実際に使うには注意が必要です。また、コアによってアクセスできないメモリ領域があるなど、コアによって少し違うので注意してくださ(ほとんど同じようですが)。
pthreadの実験も併せてご覧ください。
Arduino core for the ESP32のインストールのページはこちら。
実験
Arduino core for the ESP32の実装
Arduino core fore the ESP32のmain.cppを見てみると、以下のようになっていました。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
#include "freertos/FreeRTOS.h"
#include "freertos/pthread.h"
#include "Arduino.h"
#if CONFIG_AUTOSTART_ARDUINO
#if CONFIG_FREERTOS_UNICORE
#define ARDUINO_RUNNING_CORE 0
#else
#define ARDUINO_RUNNING_CORE 1
#endif
void looppthread(void *pvParameters)
{
setup();
for(;;) {
micros(); //update overflow
loop();
}
}
extern "C" void app_main()
{
initArduino();
xpthreadCreatePinnedToCore(looppthread, "looppthread", 4096, NULL, 1, NULL, ARDUINO_RUNNING_CORE);
}
#endif
|
Arduino core for the ESP32は、FreeRTOS上に実装されていて、Arduinoのsetup()や、loop()は、looppthread()というFreeRTOSのタスク内で実行されているようです。また、looppthread()は、コア番号ARDUINO_RUNNING_COREで実行されるように、タスクが生成されています。
ARDUINO_RUNNING_COREがどう定義されているかを、以下のスケッチで試してみると、1と表示されました。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
#include "freertos/FreeRTOS.h"
#include "freertos/pthread.h"
#include "Arduino.h"
#if CONFIG_AUTOSTART_ARDUINO
#if CONFIG_FREERTOS_UNICORE
#define ARDUINO_RUNNING_CORE 0
#else
#define ARDUINO_RUNNING_CORE 1
#endif
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
Serial.printf("ARDUINO_RUNNING_CORE = %d\n", ARDUINO_RUNNING_CORE);
}
void loop() {
// put your main code here, to run repeatedly:
}
#endif
|
結果は以下の通り、1と表示されました。ということで、複数のコアを用いて動作させることができるようです。
余談ですが、Arduino core fore the ESP32のPrintクラスには、printf()が定義・実装されているので、コンソール出力の際に、printf()を使うことができます。便利です。Uno等と共有するスケッチでは使えませんが。
ということで、Arduinoのsetup()や、loop()は、コア1上で動作させているようです。
タスク制御
タスクの生成(create)、削除(delete)、停止(suspend)、再開(resume)のテストをしてみました。
以下のサンプルスケッチでは、4つのタスクを生成し、それぞれ、以下の動作を行います。
タスク名 |
実行コア指定有無 |
動作 |
pthread1 |
なし |
永久に動作。 |
pthread2 |
なし |
永久に動作。 |
pthread3 |
あり |
コア0で動作させる。 開始後約2.5秒後に停止させ、その約3秒後に再開する。 |
pthread4 |
あり |
コア1で動作させる 開始後約2.5秒後に削除する。 |
ソースコードを見る限り、Serial.write()は、スレッドセーフのようです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
#include "freertos/pthread.h"
#include "time.h"
pthreadHandle_t pthreadHandle3, pthreadHandle4;
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
Serial.printf("setup() runs on core %d\n", xPortGetCoreID());
xpthreadCreate(pthread, "pthread1", 4096, NULL, 10, NULL);
xpthreadCreate(pthread, "pthread2", 4096, NULL, 20, NULL);
xpthreadCreatePinnedToCore(pthread, "pthread3", 4096, NULL, 30, &pthreadHandle3, 1);
xpthreadCreatePinnedToCore(pthread, "pthread4", 4096, NULL, 40, &pthreadHandle4, 0);
}
void loop() {
// put your main code here, to run repeatedly:
delay(2500);
vpthreadSuspend(pthreadHandle3);
vpthreadDelete(pthreadHandle4);
delay(3000);
vpthreadResume(pthreadHandle3);
while (1) {
delay(1000);
}
}
void pthread(void *arg) {
time_t now;
struct tm tmNow;
while (1) {
time(&now);
localtime_r(&now, &tmNow);
Serial.printf("%02d:%02d %s runs on core %d\n", tmNow.tm_min, tmNow.tm_sec, pcpthreadGetpthreadName(NULL), xPortGetCoreID());
delay(1000);
}
}
|
結果は以下の通りでした。
xpthreadCreate()とxpthreadCreatePinnedToCore()
freertos/freertos/pthread.hを見ると、xpthreadCreate()は、以下のように定義されています。
1
|
#define xpthreadCreate( pvpthreadCode, pcName, usStackDepth, pvParameters, uxPriority, pxCreatedpthread ) xpthreadCreatePinnedToCore( ( pvpthreadCode ), ( pcName ), ( usStackDepth ), ( pvParameters ), ( uxPriority ), ( pxCreatedpthread ), tskNO_AFFINITY )
|
xCoreIDをtskNO_AFFINITY(実体は、INT_MAX)にして、xpthreadCreatePinnedToCore()を呼んでいます。最後のパラメータです。一行が長いので、見えない場合はスクロールしてください。
https://github.com/espressif/esp-idf/blob/master/components/freertos/pthreads.cを読むと(これは、Arduino core for the ESP32には、ソースが入っていないので、恐らくこのファイルだろうと私が思っているだけなので、間違えている可能性もあります)、この場合、そのタスクは、以下の順で評価されて、実行されるコアが決定されるようです。
- タスクがスケジューリングされていないコア(コアIDは、0、1の順に調べる)
- プリエンプト可能な、最も低い優先度のタスクが動作しているコア(当該タスクより低い優先度、かつ、最も低い優先度のタスクが動作しているコア)
- 今動作しているコア(この時は優先度の高いタスクがある時なので、すぐには動作しない)
バージョン
Hardware: | ESP-WROOM-32 |
Software: | Arduino 1.8.3/Arduino core for the ESP32 |
最終更新日
March 21, 2022