プログラマブル IO¶
RP2040 は I2C、SPI、UART など標準的な通信プロトコルをハードウェアでサポートしています。ハードウェアでサポートされていないプロトコルや、カスタム化した I/O 動作が必要な場合は、プログラマブル IO (Programmable Input Output: PIO)が活躍します。また、MicroPython のアプリケーションの中には、データを送信するためにピンを高速でオン/オフするビットバンギングと呼ばれる技術を使用しているものがあります。これは、プロセッサが他のロジックを実行するよりもビットバンギングに集中するため、処理全体が遅くなる可能性があります。しかし、PIO は、CPU がメインの作業を実行している間に、ビットバンギングをバックグラウンドで行えます。
RP2040 には、2つの Cortex-M0+ プロセッシングコアに加えて、2つの PIO ブロックがあり、それぞれが4つの独立したステートマシンを持っています。これらのステートマシンは、先入れ先出し (FIFO) バッファを使って、他のエンティティとの間でデータを転送できます。これにより、ステートマシンとメインプロセッサは独立して動作しながら、データを同期させることができます。各 FIFO には4ワード(各32ビット)があり、これを DMA にリンクすることで大容量のデータを転送できます。
すべての PIO 命令は共通のパターンにしたがいます:
<instruction> .side(<side_set_value>) [<delay_value>]
サイドセット .side(...)
と遅延 [...]
の部分はいずれもオプションで、指定するとその命令で複数の処理を行えます。これにより、PIO のプログラムは小さく、効率的になります。
タスクを処理する命令には、以下の9つのものがあります:
jmp()
は、コードの違う場所に制御を移しますwait()
は、特定のアクションが発生するまで一時停止しますin_()
は、ビット列をソース(スクラッチレジスタまたはピンのセット)から入力シフトレジスタにシフトしますout()
は、ビット列を出力シフトレジスタからデスティネーションにシフトしますpush()
は、RX FIFO にデータを送信しますpull()
は、TX FIFO からデータを受信しますmov()
は、データをソースからデスティネーションに移動しますirq()
は、IRQ フラグを設定またはクリアしますset()
は、リテラル値をデスティネーションに書き込みます
命令修飾子は次のものがあります:
.side()
は、命令の開始時にサイドセットピンを設定します[]
は、命令の実行後に特定のサイクル数だけ遅延します
ディレクティブもあります:
wrap_target()
は、プログラムの実行をどこから続行するかを指定しますwrap()
は、プログラムの制御フローをラップする命令を指定しますlabel()
は、jmp()
命令で使うラベルを設定しますword()
は、プログラムの命令として機能する生の16ビット値を出力します
サンプルコード¶
PIO とステートマシンの使い方を簡単に理解するには、ソースに含まれるサンプルルコード pio_1hz.py
を参照してください。以下に引用します。
# LED を点滅し、1Hz の IRQ を起こす PIO の使用例。
import time
from machine import Pin
import rp2
@rp2.asm_pio(set_init=rp2.PIO.OUT_LOW)
def blink_1hz():
# サイクル: 1 + 1 + 6 + 32 * (30 + 1) = 1000
irq(rel(0))
set(pins, 1)
set(x, 31) [5]
label("delay_high")
nop() [29]
jmp(x_dec, "delay_high")
# サイクル: 1 + 7 + 32 * (30 + 1) = 1000
set(pins, 0)
set(x, 31) [6]
label("delay_low")
nop() [29]
jmp(x_dec, "delay_low")
# blink_1hz プログラムを持ち、出力に Pin(25) を使うステートマシンの作成。
sm = rp2.StateMachine(0, blink_1hz, freq=2000, set_base=Pin(25))
# ミリ秒単位のタイムスタンプをプリントする IRQ ハンドラの設定。
sm.irq(lambda p: print(time.ticks_ms()))
# ステートマシンを始動。
sm.active(1)
これは、2000Hz で blink_1hz
プログラムを実行し、ピン 25 に接続する、クラス rp2.StateMachine
インスタンスを作成します。 blink_1hz
プログラムは PIO を使って、このピンに接続されたLEDを 1Hz で点滅させ、LED がオンになると IRQ を起こします。このIRQ はミリ秒単位のタイムスタンプを出力する lambda
関数を呼び出します。
blink_1hz プログラムは PIO のアセンブラルーチンです。これは、出力として構成され、ローから始まる単一のピンに接続します。命令は次のとおりです:
irq(rel(0))
は、ステートマシンに関連付けられた IRQ を起こします。- LED は
set(pins, 1)
命令により点灯します - 値 31 をレジスタ X に入れ、その後に
[5]
で指定したように、5サイクルだけ遅延します。 nop() [29]
命令は 30 サイクルだけ遅延します。jmp(x_dec, "delay_high")
により、レジスタ X が 0 になるまでdelay_high
ラベルとの間をループします。この命令が実行された後、X は 1 だけ減ります。X は値31から始まるのでので、このジャンプは31回発生します。したがってnop() [29]
が合計32回実行されます(32回のループのそれぞれでjmp
による1命令のサイクルもかかります)。set(pins, 0)
はピン 25 をローに設定することにより、LED をオフにします。- 次に
nop() [29]
とjmp(...)
の32回のループを実行します。 wrap_target()
とwrap()
が指定されていないため、デフォルトが使用され、プログラムの実行は最下から最上に折り返されます。このラッピングに実行サイクルはかかりません。
ルーチン全体は、正確にステートマシンの 2000 サイクルかかります。ステートマシンの周波数を 2000Hz に設定すると LED が 1Hz で点滅します。