ポエムです

nRF5で使えるリアルタイムオペレティングシステム(RTOS)を見てみました。RTOSとか、全然知識ないです。なので、ポエムです。

SenStick http://senstick.com を開発していて、イベント駆動の開発をC言語で自分で関数を組み合わせて作るのが辛くなってきたので、今時の開発ライブラリあるいはRTOSを使えば先人の苦労の結晶の恩恵で、楽ができるのかなと思ってRTOSを探してみました。

今時のRTOSは、IPv6なTCP/IPスタックやHTTPSやCoAPなどのプロトコルを提供しているから、RTOSをベースにしておけば、将来的にIP系の技術を使うときに安心だとも思います。こういう、今使わないけど将来使うかもしれないっていうのは、将来もずっと使うことはない定番ですから、多分意味ないですけど。

nRF52対応なRTOS

オープンソースなnRF5対応RTOSをぐぐってみると:

があります。

BLEスタックを独自で持っているか、持たないか

nRF5対応RTOSは、BLEスタックを独自で持っているか、Nordicのソフトデバイスを使っているかの2つに分類できます。

nRF5のソフトデバイスは、ユーザのファームウェアの処理にかかわらず、BLEの通信を扱うために、BLEの通信処理が必ず最優先で実行される作りになっていて、またユーザのアプリケーションが暴走してもBLEのの通信スタックには影響しないよう、割り込みやメモリ領域のアクセスレベルを設定します。

RTOSは、タスクのプリエンプション(実行の横取り)など、割り込みを利用するものは、特権モードが使えること前提のコードもあります。そういった仕組みだと、特権モードはソフトデバイスが持っているので、RTOSをそこに入れることができません。

やるとすれば、BLEスタック自体を独自開発してソフトデバイスの役割を自前で持つか、特権モードを使わないで作るかになります。特権モードがないと、メモリアクセス保護は使えませんが、ソフトウェア割り込みはソフトウェアデバイスがユーザアプリに開放していますから、プリエンプションの実装などはそれらを使えば実装も可能でしょう。

nRF5はBLEのパケットをマイコンで処理するため、マイクロ秒単位の応答速度が必要です。そして、1チップでBLEの通信とユーザのアプリケーションが実行される構成で、スタックがBLEの認証を取るためには、ユーザのコードがどれほど馬鹿なことをしても、BLEの通信に影響しない作りが求められます。また通信スタックが使っているメモリ領域を外部から変更して破壊されないためには、メモリ領域の保護が必要です。

Nordicのソフトデバイスは、nRF5が採用しているCortex-Mの実行モードを利用して実装しています。Nordicのソフトデバイスは、ハンドラモードで実行され、ユーザのファームウェアはスレッドモードで実行されます。ハンドラモードは全ての特権モードへのフルアクセスができ、メモリアクセス権限も設定できます。スレッドモードで実行されるユーザのファームウェアは、許されていない割り込みへのアクセスや、ソフトデバイスが使うメモリ領域へのアクセスなどができないようになっています。 Cortex-Mの動作及び実行モード http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0203hj/Cihhfga3.html

BLEスタックを独自に持つのが、zephyr と mynewt 。それ以外はBLEスタックにソフトデバイスを使います。ですから前者であれば割り込みやメモリ保護など特権モードを活用した実装ができます。後者では特権モードは使えず、スレッドモードの範囲で実装したものになります。

zephyr

インテルが主導してLinuxファウンデーションの活動を通じてコミュニティで作られているRTOSです。ライセンスは、Apache 2.0 license。

ターゲットはIntel Edisonなどがあるので、x86やIntel Curieの中にある独自32ビットプロセッサARC、それにnRF52やTI社のWiFiモジュールCC3200があります。

カーネルの割り込みや特権モードの機能は、それぞれのプロセッサの種類ごとarchごとに、アセンブラコードとC言語のソースファイルがあります。Cortex-MのソースコードはWindRiverの名義になっていますが、WindRiverはIntelに買収されているので、その資産がここにあるのでしょう。

ドキュメントもしっかりしていますし、ソースコードを読んでもきれいなコードだと思います。BLEのスタック部分は、無線通信のハードウェアに直接関わる部分はNordicの中の人が参加してソースコードを書いているようです。L2CAPよりも上のレイヤはインテル名義になっています。nRF52以外でBLEを使えるように、HCIでシリアル通信でコントローラを接続しても使えるようになっています。スタックのQIDは取得しているっぽい?

プロジェクトを見る限り、RTOSを作りました、それ以上でもそれ以下でもないです。Linuxで同じことができるけれども、Linuxが動くメモリも豊富なリッチなものでも、メモリが64kBのものでも、1つのOSのAPIで開発を統合できる、そんな感じに見えます。

Intelは、Intel EdisonやCurieなどを出してIoTの時代に向けた自社半導体製品の販売に力を入れています。IoT系は、Linuxを走らせられないメモリの小さな数多くのデバイスと、それらを統合してイン−ネット側との橋渡しをするLinuxも走らせられるほどリッチなハブデバイスの2つに別れるでしょう。その2つの開発基盤として、これまで買収したRTOSのリソースを公開、また発展させていくコミュニティとして位置づけているのかしら?

でもnRF51のI2Cがサポートされていないっぽい? 移植してBLE通信ができましただけしか確認していないのじゃないかな。

本当にRTOSでしかないので、Intelが組込みやっぱり辞めましたになると、プロジェクトが止まるのかなと思えます。

mynewt

ApacheのインキュベーションにあるRTOSです。nRF51/nRF52/RISC-Vなどをサポートしています。

NimBLEという独自のBLEスタックを持っていて、Nordicのソフトウェアデバイスよりも2倍同時接続数が多いなど、その特性の高さをアピールしています。BLEの規格を決めている中心メンバーのNordicが作っている半導体を使って、そのBLEスタックであるソフトウェアデバイスに対して、何も真正面から喧嘩売るようなアピールしなくてもと思うのですが、そうらしい。

米国の runtime.io という会社が主体になって開発しているようです。デバッグどうするのかな?と思ったら、社内ではEclipseを使ってデバッグしているそうですが、その環境構築手順はまだ解説を書いていない、そのうち書くかもと、メーリングリストで2017年1月にやり取りされていました。めちゃくちゃうちわっぽい。

このプロジェクトの立ち上げのきっかけが、ネットで制御できるIoTな街灯をATMELのマイコンで開発したら、その制御ハードウェアの種類がいくつもできたり、ファームウェアのバージョン管理の手間が爆発したりと、管理面で散々なことになったことらしいです。

ソースコードを見ると、徹底してモジュールにして管理していて、しかもモジュールごとにGitのレポジトリを参照させている。テストコードがけっこう多い。そしてファームウェアの更新にブートローダがあり、署名したファームウェアを受取り更新する。デバイスの管理ツールもある。ネットに繋がったデバイスを管理するところからスタートしたのだろうなと思わせるコードです。

Cortex-Mのスレッド/ハンドラモードは使っていないっぽい。32-bitマイコンで動きますというのは、優先権限がない場合でも移植できるよという意味なのかも。Segger SystemView対応が入っている。開発環境はDockerで提供されている。実機のUSBデバッグは、gdbとかのツールがDockerで動くからVirtualBoxの拡張パックでUSBポートを追加して、仮想化したのがUSBにアクセスできるようにして、という手順。ビルド環境はすぐ手に入るけど、デバッグ環境がめんどくさい。

バイナリのビルドで、ブートローダ、カーネル、それとアプリケーションをそれぞれ分割してバイナリにして配置できる。ブートローダがカーネルもアプリケーションも更新できる仕組み。今時のネットの環境だと、ファーム更新は必ず入れておきたいから、こういうのが根っこで提供されているのは、よいと感じる。自分で作ったら、もしも何かあっても文鎮化しないかのテストって、めちゃくちゃしんどいだろうし。自分のアプリケーションは自分しか使わないから自分が開発する他ないけど、ブートローダみたいなものは、誰かが1回開発したら全員使うものは同じものなのだから、根っこで提供されていればいい。

ARMはソフトバンクグループになりましたが、このプロジェクトはRISC−Vをサポートしているので、そういう面での勢力にもなれるのかしら。

contiki-os

スイス?の大学のセンサネットワークの開発で20年近く使われているみたいです。とにかくメモリを使わない、書き間違いをしにくい平易なコードが書ける工夫がされている感じのコードだなと思います。シンプルですが、見ていると、いい感じです。

特徴的なのは、プロトスレッドという仕組みです。通常のスレッドは実行中のレジスタとメソッド呼び出しのスタックを保持するメモリ領域を確保します。これだとスレッド(あるいは処理単位、タスク)の数だけスタック割り当て(たいてい200バイト程度?)が必要になります。

ContikiOSのプロトスレッドは、そのスレッドの実行場所がどこかを記録する1ワード(実体はポインタです)を保持するだけで、スタックなどは確保しません。すべての実行は1つのスタックで行われます。プロトスレッドは、それぞれのタスクが実行した丁度いい頃合いにリターンして、CPU処理時間を気を利かせて渡し合う、協調的実行をスレッドとしてコードで表現できる仕組みとみると、スッキリします。

組込みでのC言語のソースコードには、データ処理の流れと、時間軸での処理の流れの2つが入ります。例えば、センサーからデータを取得してフラッシュに保存する処理を考えます。処理の流れは、まずI2Cバスに接続したセンサーからデータを取得して、次にSPI接続されたフラッシュに記録する処理です。実際のコードは、これに時間的な処理、例えばI2Cバスからデータを読み出す、プロセッサを待ち状態にするにはもったいない程度に長い時間を別のコンカレントに実行されている処理に渡すとか、が入ります。

データの処理の流れと時間の流れの処理は別の処理ですが、1つのソースコードに入れるために、例えば支時間のかかる処理の終了通知を受け取るために、コールバック関数を使い、そのために処理関数を分割して、さらに関数ポインタを扱う、なんていうややこしいことになります。

プロトスレッドは、そういった時間の流れの処理の記述を、データ処理の流れと統一してシンプルにコードで表現できる、間違いをしにくいコードを書ける、よい仕組みだと思います。

CoAPクライアントのサンプルをnRF52ターゲットにビルドしてみると、text 37kバイト、data 552バイト、bss 15,700バイトでした。nRF52は64kバイトのメモリがあり、ソフトウェアデバイスに13kバイト取られても61kバイトが使えます。通常のスレッドの実装でも十分に足りるメモリ量がありますから、bss16kバイトというのは、特徴的ですが意味はありません。

しかし、NordicのIoTキットはver.0.9でnRF51のサポートを切りました。nRF51はメモリが16kBのものと32kBのものがあります。ソフトウェアデバイスに13kB取られたら、使えるメモリは、それぞれ3kBと19kBです。マウスなどに使うなら16kB版でも足ります。しかしIP層に使うには、32kB版でも19kBでは通常は実装できません。そういう場面で、ContikiOSなら実装できるのは、特徴かもしれません。

ContikiOSのソースコードを見ていると、ファミコンに使われているZ80互換のプロセッサ向けに、通常の意味での、コンテキストスイッチとスタックを実装したスレッドが実装されています。きっと研究を始めた頃は、普通のスレッドで開発していたけれども、研究で超低消費電力にするにはメモリ量が小さいものしか使えないでしょうし、それを打破するためにプロトスレッドを編み出したのかなと、勝手に思いました。

一般的なRTOSとは言えないけれども、プロトスレッドの仕組みは、実行順序を管理するライブラリとして組み込むことも可能です。非常にシンプルで、変わり種だけれども、よくできているなと思います。

FreeRTOS

古くからオープンであるRTOSらしい。GPLライセンスと商用ライセンスのダブルライセンス。今の最新版のver9は全面的にソースコードを書き直したらしい。メソッド名や変数名に、その値型がわかる命名規則を使っているので、ちょっと微妙な名前に見えるけれども、スマートなコード補完機能がないエディタを使っている環境でも安心なのだろうと思う。

FreeRTOSはtickレスモードに対応した。nRF51は、クロックカウンタであるsystick(Cortex-M0+ではsystickはオプション)がないので、このモードがでて初めてnRF51で使えるようになった。nRF52にはsystickがもちろんある。

FreeRTOSをnRF5で使う必要性は、IP層が欲しい時くらい。NordicのSDKにはメールボックスやセマフォそしてスケジューラがライブラリである。だからFreeRTOSをわざわざ使わなくても、協調的な処理でアプリケーションは作れる。ただIP層が欲しい時は、それらがRTOSをベースに作られているから、FreeRTOSを使うことになるだろう。

移植性が得られる(かもしれない)のは、あるかもしれない。他社のBLE半導体で、FreeRTOSベースのSDKを提供しているところへ移植するときに、BLEのAPIは異なるから書き換えが必要だけれども、自分のアプリケーション部分はそのまま使えるだろう。

mbed os

mbedプラットホームはすごいと思うけれども、mbed osは、何をやりたいのかわからない中途半端なものに思える。KeilについてくるARMのRTXというRTOSにC++ラッパを追加して提供している。ソースコードまであるのは、とてもよい。mbed cliが提供されていて、ローカル開発環境が手軽に構築できる。ただし、ローカルではツールチェーンはgccになる。

mbedプラットホームは、オンラインのIDEに純正ARMコンパイラをセットにして提供していて、アカウントを作ってサンプルコードをフォークすれば、できたHEXファイルをUSBストレージに見えるマイコンボードに転送するだけで、コード実行ができる。ユーザのコードはレポジトリで管理されるので、一度mbedプラットホームを使えば、開発環境構築の手間どころが必要性がなくなり、かつコードの開発履歴が自動的に保存される現代的なソフトウェア開発環境に、自動的にとりこまれるのが素晴らしいと思う。

一方で、mbed osは、mbedプラットホームにOSとネット側の監理サーバを加えて、OS側は、超低消費電力に必須なイベント駆動、ネットワークでのデバイス管理の仕組みを備えているものらしい。その意味でmynewtに似ているけれども、その監理のネットワーク・プロトコルはLWM2M、またIoT向け通信プロトコルとして、CoAP, HTTP, MQTT などのサポートが並ぶ。

LWM2Mの仕様は公開されているので見に行くと、めちゃくちゃたくさんの文章があって、私には把握できない。サーバ側のコードは公開していないみたい。

SPIドライバのソースコードをみると、メンバ関数がif defで定義されていた。イベント駆動で動かすマイコンでは、ペリフェラルは指定したメモリ領域にデータを溜め込んでから割り込みをかけてくる程度にはスマートに動くので、非同期処理をよく使う。でも初期化処理の時は、非同期を入れるとコールバック関数+状態変数で管理するバグりやすい手間なコードを書くはめになるから、ブロック呼び出しの関数を使ったりする。

SPIに限らず、ペリフェラルはノンブロッキング(非同期)とブロッキング(同期)を混じって使ったり、あるいはノンブロッキングはハードウェアとしてサポートがなかったりと、いろいろあるから、if defを使いたい気持ちはわかるけれども、C言語ではなくてC++なのだから、アブストラクトクラスで基本関数を定義して、ブロキングなSPIとノンブロッキングなSPIそれぞれ継承してクラス化する方法があったのではとも思う。

とりあえず、C++で if def が使われていて、その理由がよくわからないので、これはかかわらないでおこう、そう思った。あとは、ネットワーク対応というわりに、ファームウェア更新に大事なブートローダの機能が見当たらない。カーネルはカーネルでしかないから、ブートローダは別のコードになるはずに思えるけど、何処か別のところにあるのかもしれないけど。ブートローダを使うためには、カーネルとユーザアプリケーションのバイナリは個別アドレスで配置しないと更新できないと思うけど(カーネルとファームウェアを1つのバイナリにして更新するのもありだけど、その場合はブートローダがカーネルを使えないからネットワーク実装全てもたせることになって、バイナリサイズが大きくなるだろうし)、そういうアドレス分割でのファームウェア生成もあるのかよくわからず。探せばあるのかしら。

あとは、mbedのBLEのライブラリの評判が、まず悪い。APIがよく変わる、動作がおかしいみたいな。NordicのBLEのAPIのラッパーだと思うから、そんな変なものにはならないと思うだけど、まず口々に使えないというから、あまりよくないのだろう(伝聞)。

mbed osには、いろいろ、関わらないでおこうと思った。

まとめ

そんな感じ。

Comments

2017-03-07