Arduino – ESP32 のマルチタスク ( Dual Core ) を試す

記事公開日:2017年7月19日
最終修正日:2017年10月7日

スポンサーリンク

こんばんは。

今回は、とにかくスゴイです。
ようやく、趣味の電子工作でこんな時代がやってきました!!!

ESP32 ( ESP-WROOM-32 ) のマルチタスク ( Dual Core ) です。
ただし、Arduino core for ESP32 についてです。

ESP32 は CPU がデュアルコアになっているので、1つのCPUコアの動作中に別のCPUを同時に動かすことができる優れものです。
これを使ってしまうと、他のマイコンには移れないくらいの素晴らしさを感じてしまいます。

何度も言いますが、とにかくスゴイです!!!

電子工作でデュアルコアを自在に操作できるなんて、一昔前までは思っても見ませんでした。

前回の記事でも述べましたが、Arduino core for ESP32 ではマルチタスクを使う前提で Arduino 関数が組まれています。
FreeRTOS というリアルタイム OS で Arduino core プログラムが作られていたというわけです。

FreeRTOS についてはまだ勉強中で良く分かりませんが、Arduino core for ESP32 上ではその関数を直接呼ぶことができます。
ちょっと特殊なところもありますが、あるポイントを押さえておくと、それほど難解なこともなく使えます。

では、私なりに Arduino – ESP32 のマルチタスクを検証してみたので説明してみたいと思います。

なお、今回の記事を書くにあたって、以下のサイトもたいへん参考にさせていただきましたので、合わせてご参照ください。
KERIさんとgarretlabさんの記事は頻繁に参考にさせていただいております。
改めて感謝いたします。m(_ _)m

ESP32でデュアルコアを使おう!

マルチタスク・デュアルコアの実験(ESP-WROOM-32)

準備するもの

ESP-WROOM-32 ( ESP32 )開発ボード

スイッチサイエンスさんの以下のボードは、パーツの保護機能が充実していてお薦めです。

ESPr® Developer 32

このボードをレビューした以下の記事もご参照ください。

ESPr Developer 32 ( スイッチサイエンス製 ) を使ってみました

ESP32-DevKitC でも特に問題無く使えます。

Arduino IDE 等の設定

Arduino IDE は 1.8.3 を推奨します。

Arduino IDE で ESP32 開発ができるようにするための方法は以下の記事を参照してください。

Arduino core for the ESP32 のインストール方法

Arduino core for ESP32 でのマルチタスクの組み方

では、Arduino core for ESP32 のスケッチ上でFree RTOS ライブラリを使う方法を説明します。

メインloopとは別の、もう1つのタスクプログラムを組む方法は以下のような感じになります。

void タスク名(void *pvParameters){
  while(1){
    任意のプログラム
  }
}

void setup(){
  xTaskCreatePinnedToCore(
           タスク名,
           "タスク名",
           スタックメモリサイズ,
           NULL,
           タスク優先順位,
           タスクハンドルポインタ,
           Core ID
  );
}

void loop(){

}

これで、setup関数でメインloop とは別のタスクが生成され、メインloop と同時に 新タスクの while ループが実行されるわけです。

他のサイトで、xTaskCreate という関数でマルチタスクプログラムを組んでいるところがありますが、Arduino core for ESP32 の場合は、xTaskCreatePinnedToCore でないとダメです。

このことについては、以前、GitHub のESP-IDF のページで述べられていました。
(現在はなぜか削除されてしまいました)

要するに、xTaskCreate は下位互換性のためのもので、1番目のコアのみ動作する関数です。
他のCPUコアを使うためには、xTaskCreatePinnedToCore を使うこと、というような記載がされています。
(英訳が間違えていたらスイマセン)

タスク名は好きな名前にすることができます。

スタックメモリサイズは、あまり大きくし過ぎないようにした方が賢明です。
因みに次の節で述べますが、メインループのデフォルトでは 4096 byte と、意外と小さいです。

タスク優先順位は 1~25 の範囲です。
大きい方が優先順位が高まります。
シングルコアで同時に実行するプログラムがある場合、優先順位が高い方が優先され、もう一方のプログラムは実行されないようです。

最大値が 25 という根拠は、FreeRTOSConfig.h ファイルに configMAX_PRIORITIESという定数で、それらしき定義があったためです。
このファイルは以下のフォルダにあります。(Windows10の場合)

C:\Users\User-Name\Documents\Arduino\hardware\espressif\esp32\tools\sdk\include\freertos\freertos

タスクハンドルポインタは、TaskHandle_t型の引数を定義して、そのアドレスを指定すれば良いです。
これについては、後述します。

CoreID は CPU のコア番号です。
ESP32 ( ESP-WROOM-32 )の場合は、デュアルコアなので、0 か 1 のどちらかになります。

そのタスク中のコア番号( Core ID ) を取得するためには以下の関数を使えば取得できます。

xPortGetCoreID()

また、そのタスクの優先順位を取得するためには、以下の関数を使います。

uxTaskPriorityGet( タスクハンドルポインタ )

Arduino core for ESP32 の メイン loop 関数のマルチタスク構成について

では、上記を踏まえて、Arduino core for ESP32 のメイン loop 関数の構成をテキストエディタで見てみましょう。

Windows10 の場合は以下のフォルダの main.cpp というファイルです。

C:\Users\User-Name\Documents\Arduino\hardware\espressif\esp32\cores\esp32

main.cpp

#include "freertos/FreeRTOS.h"
#include "freertos/task.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 loopTask(void *pvParameters)
{
    setup();
    for(;;) {
        micros(); //update overflow
        loop();
    }
}

extern "C" void app_main()
{
    initArduino();
    xTaskCreatePinnedToCore(loopTask, "loopTask", 4096, NULL, 1, NULL, ARDUINO_RUNNING_CORE);
}

#endif

これを見ても分かる通り、FreeRTOS ライブラリがインクルードされていて、

xTaskCreatePinnedToCore

という FreeRTOS の関数でタスクが生成されて、loopTask という関数内で無限ループが構成されています。

7行目の CONFIG_FREERTOS_UNICORE はどこで定義されているか突き止められなかったのですが、値は常時 false となっています。
これは、Serial.print などでシリアルモニターに出力すると false になっていることがわかります。

ということは、UNICORE ではないということになって、デュアルコア対応ではないのかな? と疑問に思ってしまいますね。
これは、Twitter で つきしまみなつさんから教えてもらったのですが、CONFIG_FREERTOS_UNICORE の設定は ESP-IDF で設定できるそうで、Arduino-ESP32 では、IDF で設定したものを使っているようです。
( ESP-IDF についてはこちらの記事を参照してください )

そして、FreeRTOSConfig.h というファイルにこんな記述がありました。

/* ESP31 and ESP32 are dualcore processors. */
#ifndef CONFIG_FREERTOS_UNICORE
#define portNUM_PROCESSORS 2
#else 
#define portNUM_PROCESSORS 1
#endif

この、portNUM_PROCESSORS という定数をシリアルモニタに表示させると、2と表示されました。
これから恐らく、Arduino-ESP32 では常時デュアルコアで動いていると言えそうです。

また、スタックサイズはデフォルトで 4096 byteとなっています。
ちょっと小さいですよね。

今まで、HALT エラーになっていた場合は、このスタックサイズを超えてメモリーを使っていた可能性があります。
これを大きくすれば解消される場合がありますが、大きすぎると他のプログラムが動かなくなってしまうので、要注意です。

前回の記事で述べましたが、SSL の Webページから記事を取得する場合はこのスタックサイズが小さすぎるので変える必要があります。

タスク優先度は1となっていて、一番低い値です。
新たにタスクを生成する場合には1以上にすれば優先されます。

タスクハンドルは NULL となっています。
これについては良く分かりませんが、メインループは NULL と覚えておけばよいと思います。

CoreID は
CONFIG_FREERTOS_UNICORE = false
なので、
ARDUINO_RUNNING_CORE = 1
となります。
とすると、別のタスクをデュアルコアで動かしたい場合は 0 とすれば良いということになります。

ただ、まだよく調べていないのですが、ESP32 の場合は Core 0 で WiFi や Bluetooth などの制御用のプログラムが動いている可能性があります。
そう考えると、Core 0 に大量のユーザープログラムを入れてしまうと、うまく動作しない場合があります。
これは要注意したいところですね。

スポンサーリンク


mgo-tec電子工作 関連コンテンツ ( 広告含む )

投稿者:

mgo-tec

Arduino , ESP32 ( ESP-WROOM-32 ) , ESP8266 ( ESP-WROOM-02 )等を使って、主にスマホと連携した電子工作やプログラミング記事を書いてます。ライブラリも作ったりしてます。趣味、独学でやってますので、動作保証はしません。 電子回路やプログラミングの専門家ではありません。 畑違いの仕事をしてます。 でも、少しだけ電気の知識が必要な仕事なので、電気工事士や工事担任者等の資格は持ってます。

「Arduino – ESP32 のマルチタスク ( Dual Core ) を試す」への5件のフィードバック

    1. けりさん

      コメントいただき、感謝いたします。
      あ、そうなんですね。
      delay() はあまり深くチェックしてなかったので、助かります。
      早速記事を修正したいと思います。
      そういえば、当方でも delay() はあまりエラーが出なかったので、不思議に思っておりました。
      けりさんの記事を参考にさせていただきながら、情報もいただき、感謝感謝です。
      ありがとうございました。
      m(_ _)m

    2. 今、確認しました。
      確かに、esp32-hal-misc.c に、delay() は vTaskDelay で作られていました。
      改めて、情報ありがとうございました。
      m(_ _)m

  1. 検証していないのですが、「予想に反して、Task1 だけが実行され、Task2 は常時 HIGHレベルになっています。なぜかタスク優先順位が効いていません。」の部分は、Task1がTask2より先に実行され、Task1中の無限ループのままOS側に制御を戻していないからだと思われます。
    while(1){}中にvTaskDelay(Delay1);を入れて、一度OS側に処理を明け渡してみてはどうでしょうか。

    1. yitabashi さん

      記事をご覧いただきありがとうございます。

      おっしゃる通りでした。
      Twitter でも つきしまみなつさんが検証されていて、vTaskDelay の間に優先度の低い他の Task が実行されているとの報告がありました。
      まだまだ FreeRTOS は勉強不足なのですが、 vTaskDelay の使い方は何となく分かって来ました。
      記事も修正しようと思います。
      情報ありがとうございました。
      m(_ _)m

コメントを残す

メールアドレスが公開されることはありません。

*画像の文字を入力してください。(スパム防止の為)