ST7789とのSPI通信プログラムを実装していて、初期化関数の中でコマンド送信後delay()でちょっとだけ時間調整をしている部分があった。
この部分もESP-IDF側のAPIに切り替えるため、vTaskDelay()関数を使ったのだが、ロジックアナライザで見たら全然ディレイがかかっていなかった。
そこで、これ関連について調べてみた。
タイミング調整のための関数
タイミングを調整するための関数として利用できそうなものは、次の2つの系統があるようだ。
- ets_delay_us
- vTaskDelay
これを使って以下のようなサンプルコードを作成してみた。
内容は、2番ピンの変化でets_delay_usとvTaskDelayの実質経過時間を見るというもの。
#include <driver/gpio.h>
#include <esp32/rom/ets_sys.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
extern "C" void app_main()
{
//初期化
gpio_config_t io_conf = {
1ULL << GPIO_NUM_2,
GPIO_MODE_OUTPUT,
GPIO_PULLUP_DISABLE,
GPIO_PULLDOWN_DISABLE,
GPIO_INTR_DISABLE
};
gpio_config(&io_conf);
gpio_set_level(GPIO_NUM_2, 1);
ets_delay_us(2500);
gpio_set_level(GPIO_NUM_2, 0);
vTaskDelay(200/portTICK_PERIOD_MS);
gpio_set_level(GPIO_NUM_2, 1);
}
結果としては以下の様になった。(小数点以下2桁目で四捨五入)
- ets_delay_us
2.5ms - vTaskDelay
193.2ms
ets_delay_usはほぼ設定どおりだがvTaskDelayはだいぶ誤差がある感じ。
またvTaskDelayで10以下を設定した場合、ほとんどディレイがされない結果となった。
vTaskDelay(vTaskDelayUntil?)
上を見る限り、時間を守るためにはベストな選択肢ではないらしい。
vTaskDelayUntilを使った方がいいみたいなことが書かれいていた。
そこで、vTaskDelayUntilで置き換えてみた。
auto xLastWakeTime = xTaskGetTickCount();
gpio_set_level(GPIO_NUM_2, 0);
vTaskDelayUntil( &xLastWakeTime, 200 / portTICK_PERIOD_MS);
gpio_set_level(GPIO_NUM_2, 1);
コードとしては上のような感じ。
これで計測してみたところ、200.0217msとほぼ設定どおりの時間になった。
FreeRTOSのタスクを使用して、正確な間隔で処理するにはvTaskDelayUntilが良いということなのだろうか。
ただそうするとvTaskDelayの必要性というのがよくわからない。
freertosのサイトでも違いについての質問の対しての説明があったのだが、説明になっていない説明だったし。
あと、こちらの関数を使う場合、ディレイ時間はTick単位になる。
ESP32-IDFのFreeRTOSの初期設定では10msが1 Tickになるため、10ms単位での時間指定となる。
この時間は設定で最低1msまでの調整が可能だった。
1msに設定しなおした場合vTaskDelayでも結構いい線行っていた。
vTaskDelayの時間誤差はたぶん1Tickの下の範囲なのだろう。
ets_delay_us
ESP32のサイトで検索しても掲載はなかったのだが、ググるとこの関数が良く出てくる。
ヘッダーはファイルはesp32/rom/ets_sys.hで、その中の説明では以下の様になっている。
CPU do while loop for some time.
In FreeRTOS task, please call FreeRTOS apis.
設定されいている時間通りディレイがかかっていて、while loopと書かれているので、Arduinoの
delayMicrosecondsの様にループ処理で時間調整をしているものと思われる。
そのため、複数タスクを実行して、他のタスクにスイッチさせたい場合にはこの関数は使うなということなのだろう。
ただ、μ秒単位かつ正確な時間調整ができるので、細かい時間調整をするには、こちらを使うべきなのだろう。
電源管理との関係
次に、電源管理との関連を見てみた。
これは、唐突かもしれないが、某掲示板でets_delay_usの遅延時間が想定したものと違うという情報を目にし、CPU周波数により時間が異なるという情報を得たため。
前出の実装とはちょっと異なりTick単位を1msにしてもので以下のような構成にして見た。
- ets_delay_usで2500μ秒
- vTaskDelayで200m秒
- vTaskDelayUntilで200m秒
- vTaskDelayで5m秒
- vTaskDelayUntilで5m秒
これを電源管理あり、無しで実行したところ以下の様になった。(小数点2桁目四捨五入)
電源管理 | 1 | 2 | 3 | 4 | 5 |
なし | 2.5ms | 199.5ms | 200ms | 5ms | 5ms |
あり | 2.5ms | 185.8ms | 192.2ms | 4.8ms | 4.9ms |
電源管理ありの場合、CPUの周波数が変化するためか実時間がかなり変化するようだった。
上記に関しては、esp_pm_lock_createでESP_PM_CPU_FREQ_MAX用のものを作成し、それを使ったロック期間は正しい時間になった。なので、精密な時間経過を利用したい場合にはロックをするしかないかもしれない。
そうでないのであれば、時間についてはある程度妥協し、電源管理を有効にするのだろう。
最後に
ここまでで調べた内容については私の環境での話かもしれないが、それなりシビアに時間管理をする場合には、きちんと調べて設計、実装する必要があるようだ。
コメント