PlatformIOでESP32を開発する

loopTaskの罠


Arduino環境でESP32のスケッチをビルドすると、メイン関数としてこ のコードが実行されます。
やっていることは単純でloopTaskを起動して、loopTaskの先頭でsetup()を実行し、永久ループの中でloop()を実行しま す。
UNICOREのESP32では、2000ミリ秒ごとに50ミリ秒の待機が発生します。
デフォルトでは、Loop()の処理内容に関わらず、8192バイトのSTACKが消費されます。

こちらに ESP32のマルチコアに関する面白い記事が公開されています。
2つのコアを使ってハノイの塔を何回計算することができるかの実験ですが、loopTaskには罠が有ります。
以下のコードで計算してみます。
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "Arduino.h"

int cnt;

//ハノイの塔の関数。負荷をかけるためだけのもので本質ではない。
void hanoi(int n,char a,char b,char c)
{
  if(n>0) {
    hanoi(n-1,a,c,b);
    //Serial.printf("No. %d disk is moved from %c to %c.\n",n,a,b);
    hanoi(n-1,c,b,a);
  }
}

//ハノイの塔を実行する関数。負荷をかけるためだけのもので本質ではない。
void doHanoi(char* task, int n){
  int cnt=0;
  unsigned long time_s = micros();
  n=20;
  while(1){
    vTaskDelay(1);
    if(micros()-time_s < 10000000){
      hanoi(n,'a','b','c');
      cnt++;
    }else{
      Serial.printf("%d hanoi loops by %s on core %d done\n",cnt,task,xPortGetCoreID());
      Serial.printf("%d msec passed\n",micros()-time_s);
      break;
    }
  }
  while(1){
    vTaskDelay(1);
  }

}

void task0(void* param) {
  doHanoi("task0",20);
}

void task1(void* param) {
  doHanoi("task1",20);
}

void setup() {
  Serial.begin(115200);
  delay(100);
// コア0で関数task0をstackサイズ4096,優先順位1で起動
  xTaskCreatePinnedToCore(task0, "Task0", 4096, NULL, 1, NULL, 0);
// コア1で関数task1をstackサイズ4096,優先順位1で起動
  xTaskCreatePinnedToCore(task1, "Task1", 4096, NULL, 1, NULL, 1);
//  vTaskDelete( NULL );
}

void loop() {
// mail loopでのハノイの塔実施
//  doHanoi("main loop",20);
}

結果は以下のようになります。

59 hanoi loops by task0 on core 0 done
10089933 msec passed
30 hanoi loops by task1 on core 1 done
10232478 msec passed

一見すると、loop関数は何もしないように見えますが、loopTaskはCore#1で生きているので、
Core#1をtask1とloopTaskが奪い合うことに有ります。



次に以下のコードで計算してみます。
setup関数の最後で自分自身をDeleteします。
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "Arduino.h"

(中略)

void setup() {
  Serial.begin(115200);
  delay(100);
// コア0で関数task0をstackサイズ4096,優先順位1で起動
  xTaskCreatePinnedToCore(task0, "Task0", 4096, NULL, 1, NULL, 0);
// コア1で関数task1をstackサイズ4096,優先順位1で起動
  xTaskCreatePinnedToCore(task1, "Task1", 4096, NULL, 1, NULL, 1);
  vTaskDelete( NULL );
}

void loop() {
// mail loopでのハノイの塔実施
//  doHanoi("main loop",20);
}

setup関数内でtask0とtask1を起動した後で、自分自身をDeleteしてしまうので、loopTask内の赤字の部分は実行されな くなります。
void loopTask(void *pvParameters)
{
    setup();
    for(;;) {
#if CONFIG_FREERTOS_UNICORE
        yieldIfNecessary();
#endif
        if(loopTaskWDTEnabled){
            esp_task_wdt_reset();
        }
        loop();
        if (serialEventRun) serialEventRun();
    }

}

結果は以下の様になります。

59 hanoi loops by task0 on core 0 done
10089942 msec passed
59 hanoi loops by task1 on core 1 done
10090411 msec passed

setup関数の最後で自分自身を止めてしまうので、task1はCore#1を占有することができます。
2つ以上のタスクを起動する場合、loop関数は使わない方がいいです。

続く
....