freeRTOSのタスク関連を使ううえで、APIなどちょっと長くて面倒だなと思ったのでC++でラッピングクラスを作ってみた。
また、実装時にいろいろ間違ったことなどをTips的に載せてみる。
タスクラッピングクラス
簡単な使い方については、examplesフォルダのものを参照してもらうとして、今回生成したのは、以下の2つのテンプレート。
- TaskControl
- TaskControlV
前者が仮想関数がない場合に使うもので、後者が仮想関数を用意するクラスで使うもの。
クラスメソッドのtask内にタスク内で処理する内容を記述し、startProcessを呼び出すことでタスクが生成、管理対象となる。
ほかクラスメソッドとしてsuspend/resumeとnotify関連も入れてあるので、簡単なものならこれで対応できると思う。
notify関連の簡単な動作例は、sample2側に入れてある。
タスクを使用するときの気づき
freeRTOSのタスクで実装していた時に、よく考えればわかりそうなのだが、気が付かづに実装していろいろエラーを出してしまったことをちょっと羅列してみる。
優先順位
値が小さいほうが優先順位が高いと思った。
そのため、タスクが起動されないという問題が発生してしまった。
(これは後述)
実際は値が大きいほど優先順位は高くなる。
ESP32のマニュアル側にはその詳細が記載されていなかったため、過去の経験からそう思い込んでしまっていた。
いくつかのサイトをめぐっても、同じような間違いをしているのが結構いるようだった。
設定可能な値は0以上となっていて、app_mainは(最小値+1)つまり1の優先順位になっている。
app_mainの位置づけ
エントリー用のタスクとして登録起動されたもの。
そのため、returnしても問題はない。
returnした後、app_mainのタスクは破棄されるようだ。
これに関連しての問題として、app_main内にオート変数としてTaskControlの派生オブジェクトを生成し、タスクを生成する実験を行っていたのだが、タスク起動後タスクが変な動きをしてしまっていた。
原因はapp_mainはそのままreturnしてしまっていたため、オブジェクトも破棄されてしまっていたため。当然といえば当然なのだが、気が付くまで時間を使ってしまった。
タスクを生成する場合、そのタスクに関する変数などは、グローバルなどに置いておくのが良いだろう。またタスクを生成してそちらでマイコンの制御を行うのであれば、app_mainはリターンしておくのが良いのだろう。
一部サイトでは、app_mainの最後で永久ループを入れたほうが良いような書き方をしていたのがあるが、何もしないタスクを残すのはリソースの無駄でしかないと思う。
タスクの起動
優先順位と関連する話題なのだが、実装したタスクが起動しないといった状況に陥ったときがあった。
extern "C"
void app_main()
{
test2.startProcess("test2", 4096, 5, 1);
test3.startProcess("test3", 4096, 5, 0);
test1.startProcess("test1", 4096, 5, 0);
}
メインから上のような形で3つのタスクを起動したのだが、test1のタスクのみ起動しなかった。
test1~test3のタスクはfor文の永久ループで常にRunning状態になるようなもの。
これは、app_mainのプライオリティが1のためtest3のタスクが最優先で実行し続けてしまいtest1のタスク起動まで処理がされなかったため。
test3が動作したのは、test2がコア1での動作だったのでコア0のapp_mainはまだ動けたから。
一応、メインと同じプライオリティにしたところtest1まで動作させることはできた。
この問題に関しても、タスク関連のソースをちらちら見ている限り、プライオリティは高めに設定しているのが多く、そのほとんどがタスク起動直後vTaskDelayを呼び出している。
これは、タスクをブロッキング状態にさせ、app_mainの動作をさせているのかな?と思ったりもした。
コメント