前回取り上げたマルチタスクの続きです。ESP32のタスクの中でミリ秒単位やマイクロ秒単位でのdelayを行う方法について色々わかったので記録しておきます。
結論から言うと、実機で検証した結果「マルチタスク中ではdelay、delayMicroseconds、microsといったArduinoでの時間を扱う関数が無効になるので代替関数を使用しなくてはならない」というものです。
1:ESP32で動いているOS
前回のマルチタスクの記事の参考記事にあるように、ESP32で動いているOSは「freeRTOS」というOSらしいです。マルチタスクは、ArduinoではなくこのOSに対応する記述をすることで実現しています。
つまり、マルチタスク中ではfreeRTOSに即した記述をする必要があるのです。
(※上記の理由から、Arduinoのdelayなどの関数がマルチタスク内で無効になっていると考えられます)
2:マルチタスク中におけるdelayの書き方
では実際にdelayを行う方法です。以下のように「vTaskDelay」関数を使います。
void hoge() { Serial.println("hoge"); } void hello() { const portTickType yDelay = 1000 / portTICK_RATE_MS; // 1000ms while(1) { Serial.println("hello"); vTaskDelay(yDelay); // delay } } void hogeTask(void *pvParameters) { const portTickType xDelay = 3000 / portTICK_RATE_MS; // 3000ms while(1) { hoge(); vTaskDelay(xDelay); // delay } } void helloTask(void *pvParameters) { hello(); } void setup() { Serial.begin(115200); xTaskCreate(hogeTask,"hogeTask", 1024, NULL, 1, NULL); // hogeTaskタスク登録して実行 xTaskCreate(helloTask,"helloTask", 1024, NULL, 2, NULL); // helloTaskタスク登録して実行 } void loop() { }
上記のプログラムを書き込んでシリアルモニタを立ち上げると、1秒ごとにhello、3秒ごとにhogeが出力されます。
const portTickType yDelay = 3000 / portTICK_RATE_MS; // 3000ms const portTickType yDelay = 1000 / portTICK_RATE_MS; // 1000ms
vTaskDelay引数の計算で直接の数値ではなくportTICK_RATE_MSを用いているのは、freeRTOSはどうやらTICKという単位で処理をしているらしく、portTICK_RATE_MSという「1ミリ秒辺りのTICK」の基準を使って正確な値を出すためです。
(※ちゃんと調べてないので合ってるかの確証はありません)
■2017/06/27 追記・修正
修正前に「vTaskDelayでマイクロ秒単位でdelayができる」と書きましたが、実際に調べていくとそれは間違いで「vTaskDelayではマイクロ秒単位のdelayはできない」ことがわかりました。誤った記述をしてしまい、申し訳ありませんでした。
vTaskDelay((8 / portTICK_RATE_MS) / 1000); // 8μs delayのつもり
上記のような記述をしてもコンパイルは通りますが、vTaskDelay中の処理では「引数の小数点以下は切り捨て」扱いとなるため、引数に0が代入されたのと同じ結果になります。
よってvTaskDelayでマイクロ秒単位でのdelayはできません。
以上がマルチタスク中でdelayを使う方法になります。マルチタスク中にdelayMicrosecondsなどがあってもコンパイルが通るため、実機でこの現象に気づくまでにえらく時間がかかりました。またそれをどうにか解決する方法にたどり着くまでも苦労しました。
ネットで探してもイマイチ情報がないようなので、この記事が役に立てば幸いです。