もっと楽しむために
組込みRustの入門を終えましたが、旅はまだ始まったばかりです。この先にもっと楽しい世界が広がっています!
注: この本に貢献していただけるならうれしいです! 以下の項目や、ほかにも組込みに関するトピックについて、サンプルコードや課題を追加する作業をお手伝い頂ける方はぜひ手を貸してください。
貢献するにあたってガイドが必要な場合は、イシューを立ててください。ご自分で情報を追加できるようなら、プルリクエストを送ってくださっても結構です。
組込みソフトウェアができること
以下は、組込みソフトウェアを書くにあたっての設計にかかわるトピックです。多くの問題は、いろいろなやり方で解決できます。ここではいくつかのやり方を紹介し、それぞれの向き不向きについて解説します。
マルチタスク
私たちが作ったプログラムはすべてシングルタスクでした。もしもOSもスレッドもない環境でマルチタスクを実現するとしたら、どうするでしょうか。よく使われる方法がふたつあります。プリエンプティブ・マルチタスクと協調的マルチタスクです。
プリエンプティブ・マルチタスクでは、現在実行中のタスクは他のタスクにいつでもプリエンプトされる(強制的に差し替えられる)可能性があります。もともとのタスクは一時停止され、プロセッサは別のタスクを実行します。そしてあとになってから最初のタスクを再開します。マイクロコントローラは割り込みのかたちでプリエンプションをハードウェアサポートしています。
協調的マルチタスクでは、実行中のタスクは中断点に到達するまで実行を続けます。そしてその中断点に到達すると、タスクを一時停止し、別のタスクを実行します。そしてあとになって最初のタスクを再開します。ふたつのマルチタスクの大きな違いは、協調的マルチタスクは実行をいつでも強制的に差し替えるのではなく、あらかじめ知られた中断点において実行制御を譲るということです。
スリープ
この本のすべてのプログラムは、新しい処理が必要かどうかペリフェラルをポーリングしていました。ですが、なにもする必要がないときだってあります。そんなときにはマイクロコントローラは「スリープ」すべきです。
プロセッサがスリープすると、命令の実行が止まり、電力を節約できます。マイクロコントローラはできるかぎりスリープさせるべきです。ですが、どうやっていつ起きればよいと判断するのでしょうか? 「割り込み」(詳細は下をご覧ください)でマイクロコントローラを起こすことができますが、他にも方法があります。また、プロセッサを「スリープ」させる命令はwfi
とwfe
となります。
マイクロコントローラができること
nRF52やnRF51などのマイクロコントローラにはできることがたくさんあります。そのなかでも、さまざまな問題を解決するためによく使われる機能があります。
以下では、そういった機能を組込み開発でいかに効果的に使うかを解説します。
ダイレクトメモリアクセス (DMA)
このペリフェラルは非同期の memcpy
と言っていいでしょう。もしもmicro:bit v2をお使いでしたら、すでにDMAを使ったことになります。なぜならHALがUARTEとTWIMペリフェラルにこの機能を利用しているからです。DMAペリフェラルは、まとまったデータの転送に使われます。RAMからRAMへも、UARTEのようなペリフェラルからRAMへも、RAMからペリフェラルへも使えます。たとえば256バイトをUARTEからバッファに読み込むとしましょう。DMA転送をスケジュールすれば、バックグラウンドで処理を行うことができます。処理の終了はレジスタをポーリングすることで確認し、転送作業中はその終了を待たずに他のタスクを実行できるのです。これがどうやって実装されているのか興味があれば、UART の章で見たserial_setup
モジュールをのぞいてみてください。それでも満足できなければ、nrf52-hal
のコードをどうぞ。
割り込み
マイクロコントローラが実世界とやり取りするとき、なにかイベントが起きたときに即座に反応しなくてはならないことがよくあります。
マイクロコントローラには割り込み機能があります。つまりなにかイベントが起きたときに、実行中の処理を中断し、そのイベントに反応するのです。たとえば、ボタンが押されると即座にモーターを止めたいとか、タイマのカウントダウンが終わったらセンサに測定させたいとかいったときに便利です。
割り込みはとても便利なものですが、適切に扱うのは簡単ではありません。イベントにすばやく反応はしたいのですが、他の処理も続けたいところです。
Rustでは、デスクトップアプリケーションにおけるスレッドの概念に似せて割り込みを設計しています。それはつまり、割り込み処理を実行するコードとメインアプリケーションとでデータを共有するとき、Send
とSync
についてよく考えなくてはならないということも意味します。
パルス幅変調 (PWM)
簡単に言うと、PWMとは「オンの時間」と「オフの時間」を一定の割合(デューティー比)で保ちながら、周期的にオン、オフを繰り返すことです。十分高い周波数でこれをLEDに使うと、調光することもできます。低いデューティー比、たとえばオンの時間10%、オフの時間90%ではLEDはとても暗く光ります。それに対し、オンの時間90%、オフの時間10%といった高いデューティー比ではLEDはずっと明るく(ほとんど全灯に思えるほどに)なります。
一般的に、PWMはどれだけの電力を電子機器に与えるかを制御するのに使われます。適切な駆動回路を挟めば、PWMを利用してマイクロコントローラでモーターの操作もできます。どれだけの電力を供給するかを調整することで、モーターのトルクとスピードを制御できるのです。さらに角度センサを加えれば、モーターを自動制御するシステムを作ることもできます。
PWMはすでにembedded-hal
Pwm
トレイトによって抽象化されています。その実装はnrf52-hal
をご覧ください。
デジタル入力
この本では、LEDを制御するためにマイクロコントローラのピンをデジタル出力として扱ってきました。ですが、ピンはデジタル入力として設定することもできます。デジタル入力ピンは、スイッチ(オン/オフ)やボタン(押された/押されていない)の二値状態を読むことができます。
デジタル入力もまたembedded-hal
InputPin
トレイトで抽象化されています。もちろんnrf52-hal
がそれを実装しています。
(ネタバレ スイッチやボタンの二値状態を読むのは案外簡単ではありません。;-) )
アナログデジタルコンバータ (ADC)
世の中にはたくさんのデジタルセンサがあり、I2CやSPIプロトコルを使ってセンサからデータを読み取ります。ですが、アナログセンサもあります! これらのセンサは検知するものの大きさに比例した電圧を出力します。
ADCペリフェラルは、たとえば1.25
ボルトといった「アナログな」電圧レベルを、プロセッサが計算に使える[0, 65535]
に収まる「デジタルな」数値に置き換えます。
ここでもまた、embedded-hal
adc
モジュールとnrf52-hal
が抽象化と実装をしてくれています。
デジタルアナログコンバータ (DAC)
お気づきかもしれませんが、DACはADCの反対です。デジタルな値をレジスタに書き込むことで、[0, 3.3V]
(3.3V
電源と想定)の範囲に収まる電圧を「アナログ」ピンから出力することができます。このアナログピンを適切な回路に接続し、レジスタに一定の高い周波数で適当な値を書き込めば、音を発生させることも音楽を奏でることだってできます!
リアルタイムクロック (RTC)
このペリフェラルは、「人にわかりやすいかたちで」時間を追ってくれます。「ティック」を秒、分、時、日、月、年といった単位に変換します。うるう年や夏時間にも対応できます!
その他の通信プロトコル
- SPI:
embedded-hal
spi
モジュールによって抽象化、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のリポジトリでイシューを立ててください。また、Rust Embedded matrix channelに参加してもらってもいいです。上記のモジュールの開発者のほとんどとやり取りできます。
組込みシステム一般についてのトピック
以下は、micro:bitやその上に載っているハードウェアについてではなく、組込みシステム開発における便利なテクニックを紹介します。
ジャイロスコープ
パンチングマシンの課題では、加速度計を使って三軸における加速度の変化を計測しました。しかし、モーションセンサは他にもあります。ジャイロスコープはそのひとつで、「回転」を三次元で計測することができます。
この機能は、たとえばロボットの転倒を防ぎたいときなどにはとても便利です。さらに、ジャイロスコープからのデータと加速度計からのデータとを合わせて、センサフュージョンと呼ばれるテクニックを使うこともできます。(詳細は下を参照してください)
サーボモーターとステッピングモーター
たとえばリモコンカーを進めたりバックさせたりと、モーターはただ一方向に回ればいいというときもありますが、ときにはモーターを決まった角度だけ正確に動かしたいこともあります。
より正確な操作が可能なサーボモーターとステッピングモーターというものがあります。マイクロコントローラを使い、決まった方向に決まった角度だけ動かしたり、あるポジションで止めることができます。これを利用して、たとえば時計の針を動かすとかいったことができます。
センサフュージョン
micro:bitはふたつのモーションセンサを搭載しています。加速度計と磁力計です。それぞれは(固有)加速度と、(地球の)磁場を計測しています。ですが、これらの大きさを「まとめて」もっと有用なデータとすることができます。つまり、ボードの向きについて「信頼性の高い」計測をすることができます。ここでの「信頼性を高める」とは、ひとつのセンサでは防ぐことのできないエラーを減らすという意味です。
このように異なるセンサからのデータを集めてより信頼性の高いデータを取得することを、センサフュージョンと呼びます。
さて、次はなにに取り組みましょうか? いくつか提案です。
microbit
ボードサポートクレートについてくるサンプルコードを試してみるといいかもしれません。コードはすべてお持ちのmicro:bitで動作します。
- Rust Embedded matrix channelに参加するのもいいかもしれません。組込みソフトウェアに貢献している開発者たちが集まっています。
microbit
のBSP、nrf52-hal
、embedded-hal
などを書いた人たちです。
- 組込みRustで今すぐ使えるものの一覧をお探しなら、Awesome Rust Embeddedリストがおすすめです。
- Real-Time Interrupt-driven Concurrencyを試してみてもいいでしょう。非常に効率的なプリエンプティブ・マルチタスク・フレームワークです。タスクの優先順位づけとデッドロックのない実行をサポートしてくれます。
embedded-hal
による抽象化をさらに深く学ぶのもいいかもしれません。また、それを利用してプラットフォームに依存しないドライバをあなた自身で書いてみるのもいいでしょう。
- Rustを別の開発ボードで走らせてもいいでしょう。一番簡単な始め方は、
cortex-m-quickstart
というプロジェクトテンプレートを使うことです。
- このモーションセンサのデモを試すこともできます。実装の詳細とソースコードはこのブログ記事にあります。
- Rustの型システムがいかにI/O設定におけるバグを防ぐかについて、このブログ記事が解説してくれています。
- japaric氏のブログは組込みRustについて幅広いトピックを扱っています。
- Weekly driver initiativeに参加するのもいいでしょう。
embedded-hal
トレイトを利用して、多くのプラットフォーム(ARM Cortex-M, AVR, MSP430, RISCVなど)で動作する汎用性の高いドライバを開発してみませんか。