3.1. マイクロコントローラ

OpenMV Cam は マイクロコントローラ(MCU)上で動作します。MCU とは、CPU、作業用メモリ(RAM)、プログラム格納領域(フラッシュ)、そして一連の ペリフェラル(外界とやり取りするためのハードウェアブロック)を単一のチップに統合したものです。

ペリフェラルこそが興味深い部分です。それぞれが 1 つの仕事に特化したシリコンの一片です。ピンを high または low に駆動する、アナログ電圧を測定する、シリアルバス上にバイトを送り出すといった具合です。CPU は レジスタ(ハードウェアが監視・更新する固定のメモリアドレス)を介して各ペリフェラルを設定し、読み取ります。

MicroPython はこれらのレジスタを machine モジュール内のクラスでラップしています。machine.Pin(...)汎用入出力(GPIO)ピンを制御するオブジェクトを返します。これはチップが high(約 3.3 V)または low(約 0 V)に保持できる、あるいは外部から何かに駆動されたときにそのどちらかの状態として読み取れる、1 本のワイヤです。machine.ADC(...) はアナログ-デジタル変換器を公開し、ピン上の電圧を測定して数値として報告します。machine.UART(...)universal asynchronous receiver/transmitter(UART)を動作させます。これは TX(送信)と RX(受信)の 2 本のワイヤを介して、バイトを 1 ビットずつ送受信するペリフェラルです。その他のクラスが残りのペリフェラルをカバーします。スクリプトは Python オブジェクトを読み書きし、MicroPython は各アクセスを対応するレジスタの読み書きに変換し、それらが物理的なワイヤ上のビットを動かします。

ラベル付きのブロックを含む単一のチップの外形図。 CPU、RAM、フラッシュ、そして内部バスで接続された 一列のペリフェラルブロック(GPIO、ADC、Timer/PWM、 UART/SPI/I2C、CAN)が描かれ、各ペリフェラルから 物理的なピンのラベルへ向かってチップの外へ矢印が伸びている。

MCU は CPU、メモリ、ペリフェラルを単一のチップにまとめています。各ペリフェラルは machine モジュール内のクラスによって Python に公開されます。

3.1.1. メインループ

ほとんどすべてのマイクロコントローラのプログラムは同じ形を共有しています。スクリプトの先頭での一度きりのセットアップ(モジュールのインポート、ピンの設定、バスのオープン)と、その後の末尾にある無限の while True: ループです。ループ内では、プログラムが入力を読み取り、判断を下し、出力を更新するという動作を何度も繰り返します。このループ こそ がプログラムであり、スクリプトが終了すると、デバイスは何もしなくなります。

# setup, runs once
from machine import Pin
led = Pin("P0", Pin.OUT)

# main loop, runs forever
while True:
    led.value(1)
    # ... do work ...
    led.value(0)
    # ... do other work ...

この形、すなわち一度セットアップしてから永久にループするというのが メインループ パターンです。これ以降の内容はすべて、その中に何を入れるかについてです。

3.1.2. リアルタイム制御

デスクトッププログラムは他の多くのプログラムと並行して動作します。オペレーティングシステムはその作業を 1 つ以上の スレッド(ミリ秒単位で切り替えられる独立した実行の流れ)に割り当ててスケジューリングします。あるスレッドが I/O(ディスク、ネットワーク、ユーザーのマウス操作)を待っている間、OS は CPU を別のスレッドに渡します。プログラムはほとんどがイベント駆動です。入力が届くとウィンドウマネージャがあなたのコードを呼び出し、ソケットにバイトが届くと HTTP ライブラリがあなたのコードを再開させます。何かより大きなものがあなたを呼び出しているのです。

マイクロコントローラのプログラムはその逆です。デフォルトではオペレーティングシステムもスケジューラも他のスレッドも存在しません。今示したメインループが 唯一の ループです。ペリフェラルは割り込みを発生させたりステータスフラグを公開したりし、ループはそれらをポーリングするか、割り込みを直接処理します。もしループが time.sleep_ms(1000) で停止すれば、デバイスはその 1 秒間何もせず、その隙間を埋める他のスレッドは存在しません。

ここから 2 つの帰結が導かれ、それらはあらゆる場所に当てはまります。

  • 時間は現実である。 タイトなループ内でピンを 2 回読むのはマイクロ秒単位ですが、10 ミリ秒スリープするということは、その 10 ミリ秒の間ほかに何も起こらないということです。ノンブロッキングタイミング パターンがその対処法です。

  • ハードウェアは現実である。 machine.Pin.value1 に設定すると、物理的なワイヤにおよそ 3.3 V が印加され、0 に設定するとそこにおよそ 0 V が印加されます。回路の他の部分はその電圧を即座に感知します。ピンが誤って駆動された場合に破損しうる部品も含めてです。