マルチタスク
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には、ソースが入っていないので、恐らくこのファイルだろうと私が思っているだけなので、間違えている可能性もあります)、この場合、そのタスクは、以下の順で評価されて、実行されるコアが決定されるようです。

  1. タスクがスケジューリングされていないコア(コアIDは、0、1の順に調べる)
  2. プリエンプト可能な、最も低い優先度のタスクが動作しているコア(当該タスクより低い優先度、かつ、最も低い優先度のタスクが動作しているコア)
  3. 今動作しているコア(この時は優先度の高いタスクがある時なので、すぐには動作しない)

バージョン

Hardware:ESP-WROOM-32
Software:Arduino 1.8.3/Arduino core for the ESP32

最終更新日

March 21, 2022

inserted by FC2 system