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

ESP32 ( ESP-WROOM-32 )

ESP32 のマルチタスク(デュアルコア)で GPIO を制御した場合の挙動

先の節では、シリアルプロッタやシリアルモニタに出力した場合、あたかも3つのタスクが同時に行われているかのように見えました。

では、実際に ESP32 ( ESP-WROOM-32 )のGPIO を制御したらどうなるのでしょうか?

単純な digitalWrite 関数を使って見てみたいと思います。

以下の簡単な普通のシングルコア用の通常のスケッチを入力してみてください。

【ソースコード】 (※無保証 ※PCの場合、ダブルクリックすればコード全体を選択できます)

void setup() {
  pinMode(4, OUTPUT);
}

void loop() {
  digitalWrite(4, LOW);
  digitalWrite(4, HIGH);
}

digitalWrite 関数を使って、GPIO を HIGH ⇔ LOW で切り替える簡単なプログラムです。
これをオシロスコープで電圧波形を見てみるとこうなります。
使っているオシロは 100MHz のストレージオシロスコープです。

兼ねてから、digitalWrite 関数は速度が遅いということは分かっていましたが、これはちょっと時間がかかり過ぎですね。
1MHzしか速度が出ていません。
こういう仕様だと思われますので、仕方ありません。
digitalWrite で高速パルスを出して制御するようなことはあまり無いと思いますので良いのですが、これは頭に入れておいた方が良いですね。

(2019/11/27追記)
最近では、digitalWriteを使うよりも、LEDCライブラリを使った方が遙かに高速で安定したパルスを出力できることが判りました。
以下の記事を参照してください。
Arduino – ESP32 の PWM ( LEDC )で 40MHzまでの安定した高周波パルスを思い通りに出せたぞ

 

では、今度はマルチタスクの以下のスケッチを入力してみてください。

【ソースコード】 (※無保証 ※PCの場合、ダブルクリックすればコード全体を選択できます)

TaskHandle_t th[2];

void Task1(void *pvParameters) {
  pinMode(4, OUTPUT);

  while(1) {
    digitalWrite(4, LOW);
    digitalWrite(4, HIGH);
  }
}

void Task2(void *pvParameters) {
  pinMode(5, OUTPUT);
  
  while(1) {
    digitalWrite(5, LOW);
    digitalWrite(5, HIGH);
  }
}

void setup() {
  Serial.begin(115200);
  Serial.println();

  pinMode(18, OUTPUT);

  xTaskCreatePinnedToCore(Task1,"Task1", 4096, NULL, 3, &th[0], 0); //Task1実行
  xTaskCreatePinnedToCore(Task2,"Task2", 4096, NULL, 5, &th[1], 1); //Task2実行
}

void loop() {
  digitalWrite(18, LOW);
  digitalWrite(18, HIGH);
}

これは、前節のシリアルプロッタに出力したマルチタスクのCPUコア割り当てと同じにしてみました。

Task 1 は Core 0
Task 2 は Core 1
メイン loop は Core 1

です。
そこで、これをコンパイルする前に、Debug設定を以下のようにしてみて下さい。

そして、コンパイルして、シリアルモニターを起動してみてください。
すると、こんな感じのメッセージが表示されると思います。

これは、つまり、Task1 や Task2 の while ループで ウォッチドッグタイマが介入したというメッセージです

(2019/11/27追記)
コメント投稿欄のteruさんから指摘いただきました。
この記事を書いた当初はウォッチドッグタイマを良く知らずに想像で書いていました。
コメント欄に書いてある通り、この場合はウォッチドッグタイマが定期的にリセットできずにウォッチドッグタイマが介入して強制的にリセットがかかったようです。
失礼いたしました。
m(_ _)m

 

ウォッチドッグタイマが動作する余地を与えるためには、delay(1); を入れてやるか、main.cpp のようにmicros() 関数を入れてやる必要があります
(※Arduino core ESP32 v1.0.4では、micros()関数はただ単にmicro second単位で動作休止するだけの関数になっていました。2019/12/18)
また、vTaskDelay(1) でもOKです。
ウォッチドッグタイマはマイコンを監視するプログラムなので、動作しないとプログラムが落ちますから、無視するわけにいきません。

(2019/11/27追記)
whileループ内でウォッチドッグタイマによる強制リセットさせないためには、3秒以内に最低1回 delay(1) を置くことです。
その他、ウォッチドッグタイマを停止させる関数がありますが、あまりお勧めできません。

 

※ESP8266 で活躍した yield() は、ESP32 のマルチタスクでは効きませんので要注意です。
ですが、今回はオシロで波形を見るだけなので、あえて無視します。

オシロ波形はこんな感じになります。

メインloop の digitalWrite 関数は全く効いておらず、パルスが出力されていませんね。
やはり、Task2 と メインloop が Core 1 でダブっているために、優先順位の低い メインloop はタスクが実行されていないようです。

また、Task 1 および Task 2 は while ループにより、3.3 MHz まで速度アップしました。
ということは、main.cpp ファイルにあるように、メインloop 関数には、micros() というオーバーフローを監視する関数が入っており、速度が遅くなっている原因のようです。

そして、Task1 と Task2 の波形は同時ではなく、少しズレていますね。
立ち上がりや立下りが一致するところはありませんので、おそらくGPIO レジスタ操作はマルチタスクとはいえ、同時に実行できないと思われます。

では、
Task1 は Core 0
Task2 は Core 0
メインloop は Core 1
の場合はこうなります。

今度は、Task 1 が実行されていません。
そして、メインloop 関数のHIGH-LOW の速度は、micros() というオーバーフロー監視関数が入っているので遅くなっています。

メインloop 以外のタスクでも、Core が同じ番号ならば、優先順位の高い方だけしか実行されませんね。

では、次はすべて同じ Core で、
Task1 → Core 1
Task2 → Core 1
メインloop → Core 1
としてみたらどうなるのでしょうか?

予想に反して、Task1 だけが実行され、Task2 は常時 HIGHレベルになっています。
なぜかタスク優先順位が効いていません。

普通、同じ Core ならば、タスク優先順位の高い、Task2 が実行されなければおかしいのですが・・・。
これは良く分かりません・・・。

実は、Twitter のつきしまみなつさんや、コメント投稿欄の yitabashi さんから情報を頂いたのですが、whileループにvTaskDelayがあれば、そのTaskが休止している間、他の優先順位の高いTaskが実行されるとのことです。
つまり、同じ Core 上の場合、メインloop の前に まず Task1 が実行され、vTaskDelay が無いので、次の Task2 が実行できる隙間が全くありません。当然、その後に実行されるメインloop が実行される余地もありません。
よって、Task1 だけが実行されたということです。
vTaskDelay(1)でも入れると、同じタスク優先順位ならば、交互にタスクが実行されるとのことです。
ということは、前節のサイン波シリアルプロッタの波形も、同じCore上のタスクはvTaskDelay の間に別のタスクが実行されているから、あたかも3つのタスクが動いているように見えたと言えそうです。
みなさん、実験してみてください。

いずれにせよ、これでマルチタスクの Core ID は別にするべき、ということがハッキリわかりました。

メインloop とマルチタスクを組む場合、タスクは 0 にすべきで、結局、GPIOを使う場合のタスクは2つまでということで考えておけばよいと思います。

(2019/11/27追記)
この記事を書いた当初は良く解らず書いていましたが、FreeRTOSの特性上、同じCPUでもマルチタスクを組むことができます。
失礼いたしました。

 

余談

実は、この後、レジスタを直接叩いてマルチタスクを実行してみました。
シングルタスクでは 10MHz くらいは出たのですが、マルチタスクにすると 2.5MHz まで落ちてしまいました。
それは、同時にHIGH⇔LOW 切り替えしているわけではなく、時間差でHIGH⇔LOW となっているために、2.5MHz まで速度が落ちたと思われます。

これについてはまだ実験中で、正確なことは言えず、あくまで予想ですが、レジスタを直接叩くような GPIO の HIGH⇔LOW 切り替えのマルチタスクは基本的に不可能と思われ、実際の速度はシングルタスクと変わらないものと思われます。

これについては、改めて分かり次第ご報告したいと思います。

(2019/11/27追記)
最近では、digitalWriteを使うよりも、LEDCライブラリを使った方が遙かに高速で安定したパルスを出力できることが判りました。
以下の記事を参照してください。
Arduino – ESP32 の PWM ( LEDC )で 40MHzまでの安定した高周波パルスを思い通りに出せたぞ

 

まとめ

以上、ザッと Arduino core for ESP32 マルチタスクの注意点をまとめると、

●Arduino-ESP32 のマルチタスクでは、xTaskCreatePinnedToCore を使うこと。
●Arduino core for ESP32は、予め FreeRTOS ライブラリがインクルードされていて、メインloop は Core 1 で、タスク優先度が1 、スタックサイズはデフォルトで 4096 byte 8192 byte となっている。
●マルチタスク中の時間カウントは、Tick という単位で、遅延させるためには vTaskDelay を使う。delay関数は vTaskDelay で作らている。
●メインloop の速度は、オーバーフロー監視関数っぽいものが入っているため、 while ループより遅い。whileループを使う場合はdelay(1)を入れないと、ウォッチドッグが監視できなくなるので注意
●シリアルモニタやシリアルプロッタでは、3タスク以上可能なように見えるが、GPIO 出力は2つ分のタスクしか実行できない。つまり、CPU Core 数のみということ。
●同じ Core 上で別々のタスクを動かす場合、vTaskDelay の間に他の優先順位が高いタスクが実行される。
同じCore で同じタスク優先順位ならば、vTaskDelayの間にそれぞれ交互にタスクが実行される

と、こんな感じでしょうか。
私なりの視点で Arduino – ESP32 のマルチタスクを検証してみましたが、まだ FreeRTOS については取組み始めたばかりなので、間違えていたらコメント等でご連絡いただけると助かります。

でも、これでマルチタスクが少し理解できたように思います。

今、電光掲示板で4つの記事をスクロールさせながら、別の Core で Web GET して記事を更新するということができています。
Web から GET リクエストして記事を取得していても、文字スクロールが停止しないので、改めて ESP32 のポテンシャルの凄さを実感しています。

次回こそはこれを紹介していこうと思います。

ではまた・・・。・・

コメント

  1. けり より:

    お疲れ様です!
    こちらのファイルにはdelay(), delayMicroseconds() が定義されています。
    https://github.com/espressif/arduino-esp32/blob/master/cores/esp32/esp32-hal-misc.c
    delayMicroseconds()はただ時間を潰して待つのであまり良くありませんが、delay()は問題なく使えそうです。

    • mgo-tec mgo-tec より:

      けりさん

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

    • mgo-tec mgo-tec より:

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

  2. yitabashi より:

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

    • mgo-tec mgo-tec より:

      yitabashi さん

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

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

      • 組み込みプログラマ より:

        興味があったので拝見させていただきました。

        通常であればmgo-tecさんの予想通りTask2のみが動き続けるはずですが、なぜかTask1だけが動作しています。
        yitabashiさんのコメントにあります「Task1がTask2より先に実行され」という事は、プリエンプションが有効なリアルタイムOSで両方ともReady状態であれば通常はありえません。
        そこで考えたのですが、Setup()がloop()と同じタスクから実行されていたとするとxTaskCreatePinnedToCore()でtask1が生成されたタイミングでCPU実行権がloop()タスクより優先度の高いtask1に移行して、Setup()の次の行のtask2のxTaskCreatePinnedToCore()が実行できなくてtask1のみが走り続けているのではないかと思いました。
        (繰り返しますが、プリエンプションの有効なリアルタイムOSでは、優先度が低いタスクが他の優先度の高いタスクを待たせて動作する事はあり得ません。vTaskDelayを入れると動作するのは、そのタイミングでSetup()のtask2のxTaskCreatePinnedToCore()が実行される為ではないかと想像します。)
        以上、想像ですがコメントさせていただきました。

        • mgo-tec mgo-tec より:

          組み込みプログラマさん

          私の様なアマチュアの記事をご覧いただき、誠にありがとうございます。
          この記事は随分昔に書いていて、自分の書いた文章を理解するのに時間がかかってしまいました。

          確かに、組み込みプログラマさんのおっしゃる通りだと思います。
          私も最近、なんとなく分かってきました。
          正にその通りで、setup関数とloop関数は同じcore 1 です。
          すると、xTaskCreatePinnedToCore を実行した時点でtask1がloopタスクよりも優先順位が高いので、task1が実行されてしまい、task2が実行される余地が無かったのだと思います。
          今考えると分かるのですが、この記事を書いた当初はサッパリわかりませんでした。
          現役プログラマの方からご意見いただけると、このブログの読者の方々の参考にもなりますし、とっても有り難いです。
          感謝感謝感謝でございます。
          m(_ _)m

  3. 通りかがり より:

    >相変わらず メインloop 関数の速度は遅いですね

    少し上で loop の外側で micros(); されてるせいで遅いと書かれておきながら、相変わらず・・・っていうネガティブ表現には違和感を感じます。
    速くなりようがないですから。

    micros(); が1を返すまで、つまりそこで強制的な1μsのウェイトが入ってるのと同じなので、相変わらずも何も、loop を抜けてから次に入るまで1μsかかるのが仕様だと思います。

    • mgo-tec mgo-tec より:

      通りがかりさん

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

      おっしゃる通りでした。
      今読み返すと、かなりネガティブ表現でした。
      この記事を書いた当初はもうちょっと速くできそうなものなのに、と思ってましたが、今思えばそういう仕様だということがよく分かって来ました。

      今では、Arduino-ESP32 によくぞ FreeRTOS を取り入れたものだと感心していて、開発チームの皆さんの凄さが身に染みて分かってきました。
      これは開発チームの方々にとっても失礼な表現でした。
      早速、修正します!!!

      開発チームの皆さま、申し訳ございませんでした。
      m(_ _)m

  4. teru より:

    古い記事に突っ込むのも恐縮ですが。

    「GPIO 出力は2つ分のタスクしか実行できない」という結論の意図が分かりかねます。
    while ループの中で、 vTaskDelay 等を呼び出すようにすれば、GPIO 出力のタスクを3つでも4つでも並列に実行可能なのではないでしょうか(もちろん少なくともdelay分、GPIO 出力の切り替わり速度は低下するでしょう)。
    逆に、記事中のようにビジーループのタスクを作ってしまうと、ほかの処理(例えば WiFiの処理や画面の処理)ができなくなってしまうので、あまり実用的ではないように思います。
    素人考えですが、継続して ON/OFFを高速で切り替える必要があるなら、PWM等を使用したほうがよいように思います。

    あと、ウォッチドッグタイマについての説明が不自然なように思います。
    ウォッチドッグタイマとは、基本的には「ウォッチドッグタイマが指定された間隔以内で定期的にリセットされなかった場合にシステムを再起動する」ものです。
    ウォッチドッグタイマが「動作しないとプログラムが落ちます」と書かれていますが、個人的には逆に「ウォッチドッグタイマが動作した」というと、「ウォッチドッグタイマがタイムアウトしてシステムが再起動した」ことを指すイメージがあるので違和感があります。
    また、「Task watchdog got triggered」のメッセージは、ウォッチドッグタイマの定期的なリセットをしなかった結果、ウォッチドッグタイマにより表示されているものなので、
    「ウォッチドッグタイマが介入できない」というより、むしろ「ウォッチドッグタイマが介入した」という方が自然なように思います。
    まとめると、「Task watchdog got triggered」のメッセージは、 while ループのために ウォッチドッグタイマがリセットされず、ウォッチドッグタイマが発動してしまったことにより出力されています(このウォッチドッグタイマはシステムの再起動をしないということですね)。ウォッチドッグタイマがリセットされるよう、while ループ中に vTaskDelay 等を入れる必要があります。

    • mgo-tec mgo-tec より:

      teruさん

      記事をご覧いただき、そしてとても詳しいご指摘、ありがとうございます。
      (別記事へ誤って投稿されたものは削除させていただきました。)

      この記事を書いた当初はFreeRTOSの仕組みもマルチタスクも良く分からずに、ただ「できた」というだけで書いたものです。
      それに、ウォッチドッグタイマも良く知らずに適当に書いてました。
      実は今も良く知りません。
      時たま出るウォッチドッグメッセージが邪魔で、最近はウォッチドッグを無効にしているくらいです。
      このブログでは結構頻繁に出る用語なのに、ろくに調べもしませんでした。
      実際、ウォッチドッグを動作させないようにプログラミングしていますので、その機能についてあまり調べる必要性に駆られていませんでした。
      自分的には、とりあえず作りたいものが完成すればいいや的な感じでした。

      何分、独学で見切り発車的な工作が多々ありますので、まだまだ誤って覚えていることが多いと思います。
      最近はプロ方々から手厳しいご指摘を受けることもしばしばありますので、記事中に「自分は素人で誤ってる可能性があります」と謳うようにしてます。
      覚えたばかりの専門用語も、できるだけ「知ったかぶり」しない様に最近は気を付けるようにしています。

      今回はご指摘を頂いたおかげで、また勉強になりました。
      この記事を見た方が間違えて覚えてしまっては困りますので、手が空いたら徐々に修正していこうと思います。
      いろいろありがとうございました。
      m(_ _)m

      • 組み込みプログラマ より:

        ESP32の情報を調べていたらまたmgo-tecさんのページにたどり着きましたので少しコメントを。
        まず、mgo-tecさんの発信する情報は皆さんに大変役立っているとおもいますので、我々の様な者のツッコミにめげずに続けていただければと思います。
        次に、情報を参照させて頂いているお礼に少しだけウォッチドッグタイマについて自分の知っている知識を書き込みます。
        (長いので不要であれば掲載せず無視してください。)
        ウォッチドッグタイマについてですが、この機能は元々プログラムがうまく動かない状態になってしまった場合にCPUにリセットをかける為に実装されています。
        通常、マルチタスクで実装する場合は、各タスクはセマフォ取得(xSemaphoreTake())やキュー受信(xQueueReceive())のAPIでイベントが発生するまで待ちます。そして、UARTやSPI/I2Cの受信割り込みやタイマのインターバル割り込み、GPIOの状態変化割り込み等が発生した際に先ほどのセマフォを開放(xSemaphoreGiveFromISR())したりキューにデータを入れたり(xQueueSendFromISR())して、担当のタスクを動かします。担当のタスクはセマフォ取得等から復帰してデータなどを処理し、再びセマフォ取得等を実施して次のイベントを待ちます。
        上記の通り「各タスクはイベントが発生した時にその処理を実施して再び待つという動作を繰り返す」という様に設計するので、まず(今回の記事の例はお試しだったのであえてツッコミませんでしたが)通常は今回の記事の様なタスクの使い方はしません。
        上記の通り、各タスクはイベントが発生すると動き出しますが、その際にバグ等でループから抜けなくなってしまった際などに、それを検知してCPUにリセットをかけるのがウォッチドッグタイマの役目です。
        ウォッチドッグタイマは通常は最低の優先度のタスクに実装し、タイマやDelay等で定期的に起きてウォッチドッグタイマのカウンタをクリアします。上記のバグ等で他の優先度の高いタスクが動きっぱなしになり、ウォッチドッグタスクが起動できない状態が続くとカウンタがオーバーフローしてリセットがかかるという仕組みになります。
        (マルチタスク設計では、今回の記事の様にCPU性能をフルに使い切って動き続ける様な設計をする事は無いので、必ずウォッチドッグのタイムアウトまでにウォッチドッグタスクが起動できる隙間が出来る様に各タスクの処理やウォッチドッグのタイムアウト時間を調整します。)
        趣味の電子工作ではあまり意識しませんが、製品として世の中に出ていくシステムは止まってしまっては困るのでこういった仕組みを実装します。(もちろん、通常はウォッチドッグありきの設計はせず、万が一動かなくなってしまった場合に自動で復旧させる為という意味で実装します。又、マルチタスク処理ではデッドロック等が発生してしまった際にはウォッチドッグは意味をなさないので、他の異常検出処理と併用して使用する事もあります。)
        以上、お役に立てれば幸いです。

        • mgo-tec mgo-tec より:

          組み込みプログラマ さん

          以前、コメントいただいた方と同じ「組み込みプログラマ」さんですね。
          再度コメントいただき、そして、とてもウォッチドッグタイマの詳細な解説頂き、ありがとうございます。

          最近の記事で、ウォッチドッグタイマ動作が無視できなくなってきたので、私もつい最近ようやく勉強し始めました。
          なるほど、そうやってプロの現場ではタスク管理をやっているんですね。
          ネット上ではある程度情報があったので、大体は理解したつもりだったんですが、プロの方から生の声を頂くと、とっても解りやすいです。
          私はまだFreeRTOSのセマフォやキューによるタスク管理は全く無知だったんですが、これからは避けて通れなくなりそうです。

          因みに、この記事も修正しました。
          実はArduino core ESP32のloop()関数等のCPU core1 は、デフォルトでウォッチドッグタイマ動作無効になっていました。
          Twitterで@tnkmasayukiさんから情報を頂きました。
          この記事を書いた当初は有効だったと思ったのですが、単なる勘違いだったか、修正されてしまったみたいです。
          あれれ??? って感じでした。

          そんなわけで、とっても有益な情報ありがとうございました。
          このコメントは他の読者の助けになること間違いなしです。
          自分もツッコミにめげずに、記事を書き続けていきたいと思います。
          今後とも何かお気づきの点がありましたら、コメント頂けると幸いです。
          m(_ _)m

  5. 組み込みプログラマ より:

    mgo-tecさん、お久しぶりです。組み込みプログラマです。

    今回はマルチタスク関連で私がハマった件について報告させてください。(どこかに書いておかないと忘れてしまいそうなので報告させて下さい。ただ、既知の内容でしたら掲載しないで頂いてもかまいません。)
    今回はHTTPサーバを構築する為に、TCP(80)のサーバソケットを開いてクライアントから接続される度に子スレッドを起こしてクライアントの処理をその子スレッドで実施させる構成(子スレッドは処理が終わったら終了する)をお試しで組んでみたのですが、10回程度接続があるとスレッドの生成がエラーになったりaccept()がエラーになり、最終的にはリセットが発生するという現象が発生しました。(ソケット操作はlwipのAPI(socket,bind、listen,acceptなど)を直接叩いています。)
    色々調べてみたのですが、最終的には「マイコン徹底入門」さんの「3.11. タスクの削除(自身の削除)」の記述にあった「タスクのためのメモリはvTaskDelete関数を実行した時点で解放されるのではなくその後Idleタスクが実行された時点で解放されます。」の記述が引っかかり、ArduinoのESP32のmain.cppを確認してみた所、loopタスク内で制御が握りっぱなしになっている事が分かりました。なので、loop()処理の中にvTaskDelay(100)をいれた所、症状が治まりました。多分ですが、Arduino的にはloopタスクが最低プライオリティなので走りっぱなしにしている様ですが、FreeRTOS内にはさらにプライオリティの低いIdleタスクがあり、そのタスクが動く隙間を作ってあげないと終了したタスクのリソースが開放されないのではないかと思われます。
    今回の様なタスクを使い捨てる構成だと発生してしまうと思いますので、もし同様の現象があったら疑ってみてください。
    以上、お邪魔しました。

    • mgo-tec mgo-tec より:

      組み込みプログラマさん

      再びコメントいただき、ありがとうございます。
      タスクの使い捨てについては、随分前にちょっとだけ試してみたことがあります。
      たしか、SSLサーバーをESP32で作った時、うまくいかなくてvTaskDelete()を使ってみたけど、うまく行かなかった覚えがあります。
      なるほど、vTaskDelayを置けば良いというのは良い情報をいただきました。
      更にIdleタスクがあるというのは、ありえそうですね。
      (私はFreeRTOSを全く辿れていないので、何も言えませんが。。。)
      自分の現在取組中のプログラミングではvTaskDeleteを試す機会が無いのですが、頭にインプットしておこうと思います。
      このコメントを見てくれた読者の方々には役立つと思います。
      有益な情報、ありがとうございました。
      m(_ _)m

      因みに、私は試していないのですが、Twitterでは、
      vTaskDelete(NULL);
      にすると良いという情報がありましたが、これではダメなんですかね?

  6. 組み込みプログラマ より:

    mgo-tecさん、こんにちは。組み込みプログラマです。

    > 更にIdleタスクがあるというのは、ありえそうですね。

    ちょっと調べてみたらprvIdleTask()というタスクがありました。これがIdleタスクの様で、終了タスクの削除処理以外にもモロモロの処理(vApplicationIdleHook()の呼び出しなど)をしている様なので、FreeRTOSの機能を使用するのであればloop()の中では特に支障が無い限りはdelayを入れたておいた方がよさそうです。

    > 因みに、私は試していないのですが、Twitterでは、
    > vTaskDelete(NULL);
    > にすると良いという情報がありましたが、これではダメなんですかね?

    私も実際にはタスクを終了する直前にvTaskDelete(NULL)を実行していましたがダメでした。(ちなみに、vTaskDeleteのパラメータは削除するタスクのハンドルで、NULLを指定するとこのAPIを実行したタスク自体が削除対象になります。)
    vTaskDelete(NULL)はあくまでタスクの削除を予約するだけの様なので、Idleタスクが動かないと実際の削除処理(スタックメモリやTCB等のタスクが使用していたリソースの開放や削除)が実行されないのでリソース不足が発生してしまうと思われます。

    そもそも、ArduinoのフレームワークやライブラリはESP32をマルチタスクでプログラミングする前提では考えられていない様なので、マルチタスクで実装する際にはどこまでできるか探りながらやる必要がある様ですね。
    今回もWebServerクラスで画像を載せたhtmlファイルを返した際に複数コネクションの接続要求が発生して(WebServerクラスでは複数コネクションは処理できず)ブラウザがダンマリになるので、コネクション毎に別タスクで処理する方法で実装できるかをお試しする為に実装していたらこんな現象に当たってしまいました。
    この後もSDカードへの複数タスクからの複数ファイルアクセスが可能かどうかなど、また探りながらお試しする必要がありそうです。
    (それでも、ESP-IDFで自分で開発環境を構築するよりはお手軽に開発できるのであまり文句も言えませんけど。)
    それでは失礼します。

    • mgo-tec mgo-tec より:

      組み込みプログラマさん

      お返事ありがとうございます。
      やっぱNULLでもダメでしたか。。。

      やっぱりメインloop内はなぜかウォッチドッグが無効になっていることも関係しているっぽいですし、不具合がある場合はできるだけdelay(1)以上を置いた方が良いということですね。

      私は現在、ブラウザに画像をストリーミング送信することを試していますが、core 0でウォッチドッグを有効にしてWiFi処理するようにしています。
      やっぱり、適度にdelayを入れないとブラウザがダンマリしてしまうので、難しかったです。
      loop内のcore 1ではWiFi処理しないようにしていましたが、適度にdelayを入れればloop内でもうまくできそうですね。
      私も時間がある時に良く調べてみたいと思います。
      貴重な情報ありがとうございました。
      m(_ _)m

タイトルとURLをコピーしました