あなたがまだ探求できること
まだほんの表面をなぞったにすぎません!あなたが探求できることは、まだたくさん 残っています。
注記: これを読んでいて、以下の項目やその他の関連する組み込みトピックについて、 Discovery book に例や演習を追加するのを手伝いたいと思ってくださるなら、 ぜひ力を貸してください!
本書への貢献方法について支援やメンタリングが必要な場合は、open an issue を 作成してください。あるいは、情報を追加する Pull Request を送ってください!
組み込みソフトウェアに関するトピック
これらのトピックでは、組み込みソフトウェアを書くための戦略を扱います。多くの 問題はさまざまな方法で解決できますが、これらの節ではいくつかの 戦略と、それらを使うことに意味がある場合(または意味がない場合)について説明します。
マルチタスク
これまでのプログラムのほとんどは単一のタスクを実行していました。OS がなく、 したがってスレッドもないシステムで、どうすればマルチタスクを実現できるでしょうか。 マルチタスクには主に 2 つのアプローチがあります。プリエンプティブマルチタスクと 協調的マルチタスクです。
プリエンプティブマルチタスクでは、現在実行中のタスクは、任意の時点で 別のタスクによって プリエンプト(割り込まれ)される可能性があります。プリエンプションが 発生すると、最初のタスクは中断され、代わりにプロセッサは 2 番目のタスクを実行します。 ある時点で最初のタスクは再開されます。マイクロコントローラは、割り込み という形で プリエンプションをハードウェア的にサポートしています。第 11 章でスネークゲームを作ったときに、 私たちは割り込みに触れました。
協調的マルチタスクでは、実行中のタスクは 中断点 に到達するまで実行されます。 プロセッサがその中断点に到達すると、現在のタスクの実行を停止し、 代わりに別のタスクを実行します。ある時点で最初のタスクは再開されます。これら 2 つのマルチタスクのアプローチの主な違いは、協調的マルチタスクでは、実行中の任意の 時点で強制的にプリエンプトされるのではなく、既知の 中断点で実行制御を 譲る ことです。
スリープ
これまでのプログラムはすべて、何かする必要があるかどうかを確かめるために、 周辺機器を継続的にポーリングしてきました。しかし、ときには何もすることがありません! そのようなとき、マイクロコントローラは「スリープ」すべきです。
プロセッサがスリープすると、命令の実行を停止するため、電力を節約できます。
電力を節約するのはほとんど常に良い考えなので、マイクロコントローラはできるだけ
多くの時間をスリープ状態で過ごすべきです。しかし、何らかの動作を行うためにいつ
起きなければならないかを、どうやって知るのでしょうか。「割り込み」(それが正確に何かは
下を参照してください)は、マイクロコントローラを起こすイベントの 1 つですが、ほかにも
あり、wfi と wfe はプロセッサを「スリープ」させる命令です。
マイクロコントローラの機能に関連するトピック
マイクロコントローラ(nRF52/nRF51 のようなもの)には多くの機能があります。しかし、 多くのマイクロコントローラは、さまざまな種類の問題を解決するために使える、共通した 機能を備えています。
これらのトピックでは、そのような機能のいくつかと、それらを効果的に 組み込み開発で活用する方法について説明します。
ダイレクトメモリアクセス (DMA).
この周辺機器は、非同期の memcpy の一種です。micro:bit v2 を使っているなら、
実はすでにこれを使っています。HAL が UARTE と TWIM 周辺機器でこれを
代わりに行ってくれているからです。DMA 周辺機器は、データの一括
転送を行うために使えます。RAM から RAM へ、UARTE のような周辺機器から RAM へ、
あるいは RAM から周辺機器へ、といった転送です。たとえば「UARTE からこのバッファに
256 バイト読み込む」といった DMA 転送をスケジュールし、それをバックグラウンドで
走らせたまま、完了したかどうかをレジスタをポーリングして確認できます。そうすれば
転送の進行中に別の作業を行えます。これがどのように実装されているかをもっと知るには、
UART 章の serial_setup モジュールを確認できます。それでもまだ足りなければ、
nrf52-hal のコードを読み込んでみることさえできます。
割り込み
現実世界とやり取りするためには、何らかのイベントが発生したときに、 マイクロコントローラが 即座に 応答する必要があることがよくあります。
マイクロコントローラには割り込みを受ける機能があり、つまり特定のイベントが 発生すると、その時点で行っている処理をいったん止めて、代わりにその イベントに応答します。これは、ボタンが押されたときにモーターを止めたい場合や、 タイマーのカウントダウンが終了したときにセンサーを測定したい場合に、非常に役立ちます。
これらの割り込みは非常に便利ですが、正しく扱うのは少し難しいこともあります。 私たちはイベントに素早く応答できるようにしたい一方で、 ほかの処理も継続できるようにしておきたいのです。
Rust では、割り込みをデスクトップ Rust プログラムにおけるスレッドの概念に近いものとして
モデル化します。これは、メインアプリケーションと、割り込みイベントの処理
の一部として実行されるコードのあいだでデータを共有するときに、
Rust の Send と Sync の概念についても考えなければならないことを意味します。
パルス幅変調 (PWM)
ひとことで言えば、PWM とは、何かをオンにしてから周期的にオフにすることを、 「オンの時間」と「オフの時間」のあいだのある比率(「デューティサイクル」)を保ちながら 繰り返すことです。十分に高い周波数で LED に対して使うと、これを使って LED を暗くできます。たとえば、オンの時間が 10%、オフの時間が 90% のような低い デューティサイクルでは LED はかなり暗くなりますが、オンの時間が 90%、オフの時間が 10% のような高いデューティサイクルでは、LED ははるかに明るくなります(ほとんど フルパワーで駆動されているかのようです)。
一般に、PWM は、ある電気機器にどれだけの 電力 を与えるかを制御するために使えます。 マイクロコントローラと電動モーターの間に適切な(電力用の)電子回路があれば、 PWM を使ってモーターに与える電力の大きさを制御できるため、そのトルクや速度を 制御するのに使えます。さらに、角度位置センサーを追加すれば、さまざまな負荷において モーターの位置を制御できる閉ループコントローラを作れます。
PWM はすでに embedded-hal Pwm trait の中で抽象化されており、nrf52-hal に
もこの実装があります。
デジタル入力
私たちは LED を駆動するために、マイクロコントローラのピンをデジタル出力として使ってきました。 スネークゲームを作るときには、これらのピンをデジタル入力として 設定する方法も少し見ました。デジタル入力として使うと、これらのピンは スイッチ(オン/オフ)やボタン(押されている/押されていない)の二値の状態を読み取れます。
デジタル入力もまた embedded-hal InputPin trait の中で抽象化されており、
もちろん nrf52-hal にはその実装があります。
(ネタバレ スイッチ / ボタンの二値状態を読むのは、見た目ほど 簡単ではありません ;-) )
アナログ-デジタル変換器 (ADC)
世の中には多くのデジタルセンサーがあります。I2C や SPI のようなプロトコルを使って
それらを読み取れます。しかし、アナログセンサーも存在します!これらのセンサーは、測定している量に
比例した電圧レベルを出力するだけです。
ADC ペリフェラルは、その「アナログ」の電圧レベル、たとえば 1.25
ボルトを、たとえば [0, 65535] の範囲にある「デジタル」の数値へと変換でき、プロセッサはそれを
計算に利用できます。
ここでも、embedded-hal adc module と nrf52-hal がこの用途に対応しています。
デジタル-アナログ変換器 (DAC)
ご想像のとおり、DAC は ADC のちょうど反対です。ある
レジスタにデジタル値を書き込むことで、ある「アナログ」ピンに [0, 3.3V] の範囲の
電圧を出力できます(3.3V の電源を仮定)。このアナログピンが
適切な電子回路に接続され、正しい値が一定の高速なレート(周波数)でレジスタに
書き込まれると、音や
さらには音楽まで生成できます!
リアルタイムクロック (RTC)
このペリフェラルは、「人間向けの形式」で時刻を追跡するために使用できます。秒、分、 時、日、月、年です。このペリフェラルは、 「tick」をこれらの人にとって扱いやすい時間単位へ変換してくれます。さらに、うるう年や 夏時間まで処理してくれます!
その他の通信プロトコル
- SPI。
embedded-halspimodule で抽象化されており、nrf52-halによって実装されています - I2S。現在は
embedded-halでは抽象化されていませんが、nrf52-halによって実装されています - Ethernet。
smoltcpという小さな TCP/IP スタックが存在し、一部の チップ向けには実装されていますが、micro:bit に載っているものには Ethernet ペリフェラルがありません - USB。これについてはいくつか実験的な取り組みがあり、たとえば
usb-deviceクレートがあります - Bluetooth。
rubbleという未完成の BLE スタックが存在し、nrf チップをサポートしています。 - SMBUS。現時点では、
embedded-halで抽象化もされておらず、nrf52-halによる実装もありません。 - CAN。現時点では、
embedded-halで抽象化もされておらず、nrf52-halによる実装もありません - IrDA。現時点では、
embedded-halで抽象化もされておらず、nrf52-halによる実装もありません
アプリケーションごとに使用する通信プロトコルは異なります。ユーザー向けの アプリケーションには通常 USB コネクタがあります。USB は PC やスマートフォンで広く普及した プロトコルだからです。一方、車の内部では多くの CAN 「バス」が見つかります。デジタルセンサーの中には SPI を使うものもあり、I2C を使うものもあれば、SMBUS を使うものもあります。
もし embedded-hal における抽象化や、
一般的なペリフェラル実装の開発に興味があるなら、遠慮なく HAL
リポジトリで issue を立ててください。あるいは Rust Embedded matrix channel に参加して、
上記のものを作った人たちの大半と連絡を取ることもできます。
一般的な組み込み関連トピック
これらのトピックでは、私たちのデバイスや、その上のハードウェアに 固有ではない項目を扱います。その代わりに、組み込み システムで使用できる有用な技法について説明します。
ジャイロスコープ
Punch-o-meter の演習の一環として、私たちは加速度計を使って 3 次元での加速度の変化を測定しました。しかし、ジャイロスコープのような他のモーション センサーもあり、これを使うと 3 次元での「回転」の変化を測定できます。
これは、たとえば転倒を避けたいロボットのような特定のシステムを構築しようとするときに 非常に有用です。さらに、ジャイロスコープのようなセンサーからのデータは Sensor Fusion と呼ばれる技法を使って加速度計のデータと組み合わせることもできます (詳細は後述します)。
サーボモーターとステッピングモーター
一部のモーターは主に一方向または反対方向に回転させるためだけに使われます。 たとえば、ラジコンカーを前進または後退させる場合です。しかし、モーターがどのように回転するかを より正確に測定したいこともあります。
私たちのマイクロコントローラは、サーボモーターやステッピングモーターを駆動する ために使用でき、これによりモーターが何回転するかをより正確に制御でき、あるいは モーターを特定の位置に置くことさえできます。たとえば、時計の針を特定の方向へ 動かしたい場合です。
センサーフュージョン
micro:bit には 2 つのモーションセンサー、加速度計と磁力計が搭載されています。 それぞれ単独では、(固有)加速度と(地球の)磁場を測定します。 しかし、これらの物理量を「融合」することで、より有用なもの、すなわち「ロバスト」な測定、 つまりボードの姿勢の測定を得られます。ここでロバストとは、 単一のセンサーで可能な場合よりも測定誤差が小さいことを意味します。
異なるソースからより信頼性の高いデータを導き出すこの考え方は センサーフュージョンとして知られています。
では、次はどこへ進みましょうか。いくつかの選択肢があります:
microbitボードサポートクレートにあるサンプルを見てみることができます。これらのサンプルはすべて 手元の micro:bit ボードで動作します。
- Rust Embedded matrix channel に参加することもできます。組み込みソフトウェアに貢献している人や取り組んでいる人の多くが
そこに集まっています。たとえば、
microbitBSP、nrf52-hal、embedded-halなどを書いた人たちもいます。
- 今の Rust Embedded で利用できるものの全体像を知りたいなら、Awesome Rust Embedded リストを見てみてください
- Real-Time Interrupt-driven Concurrency を見てみることもできます。非常に効率的なプリエンプティブ・マルチタスキングフレームワークで、 タスクの優先順位付けとデッドロックのない実行をサポートしています。
embedded-halプロジェクトのさらに多くの抽象化を見てみて、それを基に独自の プラットフォーム非依存ドライバを書いてみるのもよいでしょう。
- 別の開発ボードで Rust を動かしてみることもできます。始めるための最も簡単な方法は、
cortex-m-quickstartCargo プロジェクトテンプレートを使うことです。
- このモーションセンサーデモ を試してみることもできます。実装の詳細と ソースコードは このブログ記事 で確認できます。
- Rust の型システムがどのように I/O 設定のバグを防げるかを説明している このブログ記事 を見てみることもできます。
- Rust による組み込み開発に関するさまざまなトピックについては、japaric’s blog を見てみることもできます。
- Weekly driver initiative に参加して、
embedded-haltraits の上に構築され、あらゆる種類のプラットフォーム(ARM Cortex-M, AVR, MSP430, RISCV, など)で動作する汎用ドライバを書くのを手伝うこともできます。