FreeRTOSの導入とTips 


久しぶりのブログはFreeRTOSの話をしようと思います. 俺も頭を並列に分割したいよな.

RTOSとは 


RTOSとは Real Time OS(リアルタイムOS) の略称で組み込みデバイスなどで使われるOSです.
リアルタイムOSの特徴は時間的制約を管理するのに特化しているという点です.
組み込みデバイスでプログラミングしてると出てくる問題としてリアルタイム性の確保というものがあります.
例えば, 「センサーデータを何秒ごとに取りたい. センサーデータの取得に比べると優先順位は落ちるけど定期的にLEDをピカピカさせたい. それとは別に常時ブザーを鳴らしたい.」 といった処理をさせたいと思います.
これを実際に書こうとすると

  1. タイマーで割り込みする
  2. フラグを更新する
  3. main loopで楽しく分割して処理する

といった処理を書くことになります. するとこういった質問が寄せられるのは想像に難しくないでしょう.

Q: これC言語で書くの, しんどいってレベルじゃなくない..?
A: はい.

純粋にフラグの優先度を管理するのが難しいし, 割り振られた作業間のデータのやり取りの扱いを考えないといけないし, デバッグをするときも大変になるのは目に見えてます. そこでRTOSの出番です.

これを導入することでそれぞれの作業をタスクとして切り分けつつ, タスクの優先度や時間的制約の解決, タスク間でのデータのやり取りをサポートすることができます. 嬉しいね.

FreeRTOSとは 


FreeRTOSはマイクロコントローラー向けにオープンソースで開発されているRTOSです. 上に書いた時間的制約や優先度の管理だけでなく,

  • TCP/IP通信をサポートした FreeRTOS+TCP
  • 軽量なコマンドライン環境を持った FreeRTOS+CLI
  • openやreadなどのIOペリフェラルを兼ね備えた FreeRTOS+IO
  • UDP通信をサポートした FreeRTOS+UDP

などの機能もサポートしています.

ドキュメントとか 


2017年, AWSはFreeRTOSの買収を発表しました. AWSのバックアップを受けた影響なのか, FreeRTOSのドキュメントは非常に整備されて(私見)たくさんの知見や情報が公式から日本語で出るようになりました. FreeRTOSの細かい仕様について僕がここで語るよりかはドキュメントを読んだほうが早いと思うので, ぜひ公式HPで確認してみてください.
(https://aws.amazon.com/jp/freertos/)

導入 


FreeRTOS公式HPにダウンロードリンクがあるので, そこからダウンロードして解凍してください. (https://www.freertos.org/a00104.html)

そしてこのページを参考にプロジェクト側からインクルードするフォルダーを探します.
(https://www.freertos.org/a00017.html)

1. Core File 

まずはFree RTOSのコアになるファイルたちです. 場所はFreeRTOS/Sourceにあります.

2. Portable File 

次にPortable Fileについてです. これは各プロセッサーやコンパイラのための設定が書かれたファイルになっていて,
FreeRTOS/Source/Portable/[compiler]/[architecture] にあります.
自分の使用するマイコンに合わせたものを使用してください. 例えばSTM32F4だと
FreeRTOS/Source/portable/GCC/ARM_CM4F になります.

3. Memory Manage 

最後はメモリーのマネジメントについてのファイルです. 場所はFreeRTOS/Source/portable/MemMangにあります.
これはタスクのメモリ(ヒープの扱い方)について設定されていて, heap_1.c, heap_2.c, heap_3.c, heap_4.c, heap_5.cから選択することになります. 基本的にはheap_4.cを使っておけば問題ないと思います.

4. FreeRTOSConfig.hの設定 

最後にFreeRTOSのコンフィグファイルです. これは自前で用意しなければいけません. 重要なものとしては

  • CPUの動作周波数を設定する configCPU_CLOCK_HZ
  • RTOSの動作周波数(Tick)を設定する configTICK_RATE_HZ

があります. これ以外にも色々と設定しないといけない(めんどくさいね)ので, サンプルとして公式が上げてるものなどを参考にしてみてください.(https://www.freertos.org/a00110.html)

最後にこれらのファイルをファイルにぶち込んでパスを通し, プロジェクトにインポートするとRTOSが動きます. チュートリアルはネットに色々あると思うのでそちらを参照してください.

私がSTM32F413向けに設定したFreeRTOSの設定ファイルはGitHubに置いてあるので困ったら見てみてください.
(https://github.com/dangorogoro/Dangoromouse3rei/tree/master/FreeRTOS)

FreeRTOSのTips 


これを書きたかったから書いたみたいなところがある.
最近, 色々RTOSの知見を得たのでそれをここでシェアしておきます.

RTOSをC++で扱う. 

FreeRTOSはC言語で書かれているのでC++で利用する際に問題点が発生します.
というのも, C++のメンバ関数はクラスの実体に紐付いてるので, RTOSのタスクにそのままを渡すことができません.
そのため, C++でメンバ関数をタスクに割り当てるには

1
2
3
4
//In class
xTaskCreate(
[](void *this_pointer) {static_cast<decltype(*this)>(this_pointer)->task();}
,....);

みたいなコードを書く必要があります.
詳しくはこちらをご覧ください.

参考文献 

皆も歌おう.

ESP32のpthreadの実装が賢い 



esp32-idfではpthread内の実装がFreeRTOSをラップしたものなのですごい. これはstd::threadとかがマイコンで動作するということです.
ただ, std::threadでは

  • タスクの優先順位が決められない
  • タスクのメモリー使用量の管理ができない.

という問題があるので真にリアルタイムが求められる環境ではstd::threadの使用はもう少し考えたほうが良さそうです.

タスクの優先度は数値が高いものが優先される 


皆知ってた?俺は間違えた.
混乱の原因は間違いなくSTM32の割り込み優先度でしょう.
Cortex-Mシリーズのアーキテクチャにおいて数値的に低い割り込みの処理は数値的に大きいものよりも優先して処理されます. つまり, 割り込み優先度が2のサブルーチンと5のサブルーチンがあったときに

  • (数値的優先度) 2 < 5
  • (論理的優先度) 2 > 5

となって割り込み優先度が2のサブルーチンが優先的に実行されます. 当然,

Q: 直感に反してない?
A: 分かるよ.

となるわけです. 一方, FreeRTOSのタスクの優先度は数値的に大きいものが論理的優先度が高くなります.
これは, FreeRTOSのドキュメントでも指摘されていて(This is the most counterintuitive aspect of ARM Cortex-Mとか言われてる), Coretex-MシリーズのアーキテクチャでFreeRTOSを使用する際に混乱の元になります. 注意しましょう.

補足ですが, 割り込み関数とFreeRTOSを一緒に動作させると複雑さは更に加速します.
FromISRで終わるFreeRTOS関数は割り込みのサブルーチンから操作できる関数でQueueにデータを入れたりできます.
これらの関数は割り込みセーフですがFreeRTOSConfig.hファイルで定義されているconfigMAX_SYSCALL_INTERRRUPT_PRIORITYマクロで指定された優先度以上の論理的優先度を持つ割り込みからは呼び出すことができません.
つまり, Cortex-Mシリーズのマイコンで割り込みサブルーチンからFromISRなどの関数を呼び出したいときは数値的優先度をconfigMAX_SYSCALL_INTERRRUPT_PRIORITYより大きくし, 論理的優先度を下げるという操作が必要になります. ウッソだろ.

Cortex-Mの割り込みのデフォルトの数値的優先度は0でこれは最高の論理的優先度になります. そのため, 何もせずに割り込みセーフなRTOS APIを割り込みのサブルーチンから呼ぶともれなく死んでしまいます.

また, 割り込みサブルーチンの優先度は0-15の4bitだったり, 0-7の3bitだったりします. 一方でFreeRTOSの優先度は0-255になっているため, これらの間で変換を考える必要があります.
Cortex-Mのpriority registerは8ビット中上位n bitが使われます. 図で表すとこんな感じです. (FreeRTOS.orgより)
Images

そして優先度の変換の計算はこの8bitを使って行います. 具体的にはこれらの画像を見てください.
Images
Images

例えば, configMAX_SYSCALL_INTERRRUPT_PRIORITYが191のとき, FreeRTOS APIを割り込みサブルーチンから安全に呼ぶには,

  • priority bitが4 bitの環境では優先度を12以上
  • priority bitが3 bitの環境では優先度を5以上

にしないといけません. 難しいね. 詳細はこちらでご確認ください.
(https://www.freertos.org/RTOS-Cortex-M3-M4.html)

CubeMXの生成するFreeRTOSがすごい 


CubeMXではミドルウェアを選択してFreeRTOS(正しくはFreeRTOSをCMSISの規格でラップしたCMSIS-RTOS)のコードを生成することができます.
僕もこれでRTOSのコードを生成して人生経験イケイケ六年目をしたかった. しかし, 現実というのは厳しかった. 思い描いてた空想が現実になって現れる現象をよく「現実に肩を叩かれる」というふうに表現することがあるが, これはそういう騒ぎではない. 現実は常に我々の先を行くのだ.
具体的にはCubeMXの生成するコードにバグがあってRTOSが適切に動かないという問題があります.(お前ウッソやろ)

詳細についてはこちらの記事を参照してください.
(http://www.nadler.com/embedded/newlibAndFreeRTOS.html)

端的に説明すると

  • RTOSでは複数のタスク(スレッド)からmallocやfreeなどのシステムコールが呼ばれても大丈夫なようにスレッドセーフなシステムコールを提供する必要がある.
  • CubeMXの生成するコードはスレッドセーフじゃなかった(終わり)

そのため, CubeMXで生成するRTOSを使うとヒープの確保とかで死にます. 上記サイトではこれを回避するためのheap_useNewlib.cというファイルを作っています. これを使って

-Xlinker --wrap=malloc -Xlinker --wrap=_malloc_r

というコンパイラオプションを追加してあげる事でmallocをラップして対応しています.
実際に私がマイクロマウスで動かしているものもheap_useNewlib.cを使っていて動作を確認しています.
(https://github.com/dangorogoro/Dangoromouse3rei/tree/master/FreeRTOS)

最後に苦しんでいるオタクのツイートをシェアしておきます.

FreeRTOSの使用例 


僕はマイクロマウスというロボット競技に参加していてそのロボットにFreeRTOSを載せています. 具体的にはタスクを

  • 迷路探索を行うタスク
  • 軌道追従を行うタスク
  • インターフェースを制御するタスク
  • センサーの情報を取得するタスク

みたいな感じでタスクを分けてます.
FreeRTOSがないと一つのループの中でこれらの処理を全てやらないといけないわけですが, FreeRTOSを導入することでそれぞれの作業を切り分けて実行できるのがいいポイントです.

あと, 簡単にバックグランドでの処理(例えば, 迷路探索タスクの優先度を下げて処理を行うことで, 他のタスクが動作していない時間に事前計算を回せる)みたいなことが簡単にできるのが嬉しいですね.

おわりに 


いかがでしたでしょうか.
RTOSを導入することでリアルタイム性が求められるシステムにおいて, タスクの時間的制約や優先度などの問題をRTOS側で解決することができます.
使う際には色々知識が必要になりますが(並列コンピューティングに相当するのかな?), 使えるようになるとかなり便利だと思うので, ぜひ使ってみてください.