皆さん こんにちは。
今回はラズピコのマイコン RP2040 の目玉機能である PIO についてお話します。
ネット上に「ラズピコPIO」の記事はゴロゴロしていますがデータシートと手持ちの本を参考にしながら私なりにPIOについてまとめてみることにしました。
PIOとは
PIOとは prgrammable input / output の略で、端子の部分とマイコン中核部の仲介役をプログラマブルに行うことができるものです。
例えば SPI , I2C , UART などのペリフェラルの数が不足している場合に、PIOでプログラムを組むことで不足分を補うことができます。
データシートには以下のものがつくれると書いてあります。
• 8080 and 6800 parallel bus
• I2C
• 3-pin I2S
• SDIO
• SPI, DSPI, QSPI
• UART
• DPI or VGA (via resistor DAC)
PIOはプログラマブルです。RP2040には2つのPIOがあり、それぞれ4つのステートマシンを持っています。
GPIOの操作やデータ転送を行う逐次プログラムを独立に実行することができます。
PIOステートマシンは汎用プロセッサとは異なり、正確なタイミング、固定機能ハードウェアとの密接な統合に重点を置きIO用に高度に特化されています。
IOに特化した部分をアセンブラで書いて、システム全体をC言語でとりまとめるといったイメージになります。
(言語にPythonを使う環境もあるのですが、こちらのサイトではC/C++を扱っています)
ブロック図
RP2040にはPIOが2つあります。
PIOひとつ分のブロック図を以下にしめします。
PIOのハードウェア
PIOひとつは以下の要素で構成されています。
・4つのステートマシン(以降はSMと略します)
・8つのFIFO
・命令用メモリーは32命令分を4つのSM共用で使う
・I/Oマッピング
・IREQ(割り込み要求)
・DREQ(DMA要求)
ステートマシン(SM)
SMはCPUに相当する部分で命令用メモリーに書かれたプログラムを順次実行します。
ブロック図は以下の通りです。
以下の要素から構成されています。
・Out Shift : 出力シフトレジスタ (OSR)
・In Shift : 入力シフトレジスタ (ISR)
・Scratch X : スクラッチレジスタ X
・Scratch Y : スクラッチレジスタ Y
・PC : プログラムカウンタ
・Clock Div : クロック分周器
・Control Logic : コントロールロジック
FIFO
FIFOは First In – First Out の略で、先入れ先出しのメモリーです。
ひとつのFIFOは4つの32ビットデータを格納することができます。
システム側から書き込まれたデータはFIFOに保持され、PIOのPULL命令で OSRに取り込まれます。
システム側に読み込まれるデータはPIOのPUSH命令でISRからFIFOに保持されシステムに読み出されます。
命令メモリー
ひとつのPIOに対して4つのSMが命令を読み出す共通のメモリーで、32命令を格納することができます。
1命令は16ビット長なので、全部で64バイトの命令用メモリーがあります。
I/Oマッピング
I/OマッピングはPIOの命令と端子(IOピン)の対応づけを行います。
マッピング設定 | 内容 |
---|---|
out pins | どの端子に対して出力するのかを設定する |
in pins | どの端子から入力するのかを設定する |
side-set pins | サイドセット・ピンをどの出力端子にするのかを設定する |
割り込み要求
ひとつのPIOからはCPUに対する割り込みを2つ出力することができます。
(IRQ0 , IRQ1)
PIOのプログラム
プログラムにはアセンブラを使います。
アセンブラで書いたコードは xxx.pio として保存します。
C/C++言語システムからはこれを xxx.pio.h というヘッダーファイルとして取り込みます。
アセンブラ命令
アセンブラの命令は1命令が16ビット固定長になります。
命令は以下の9つあります。
・IN
・OUT
・PUSH
・PULL
・MOV
・JMP
・SET
・IRQ
・WAIT
命令セット
PIO命令は16ビット長で、各命令は以下のようなエンコーディングになっています。
遅延とサイドセット・ピン
16ビットの命令のうち、遅延とサイドセット・ピンはビット12-8の5ビットに割り当てられています。
サイドセット・ピンを使わなければ最大で31サイクル(5ビット)を遅延させることができます。
例えば2つの端子をサイドセット・ピンに割り当てると最大で7サイクル(3ビット)までしか遅延できなくなります。
遅延とサイドセット・ピンを組み合わせて5ビットを使うことになります。
遅延の使い方
具体的な命令を書いた方がわかりやすいですね。
set pins, 1 [2]
これはあるピンの状態を 1 (High-Level) にした後、2サイクル遅延させる命令です。
命令を実行した後[]でくくったサイクル数分だけ遅延(ウェイト)します。
上で説明した通り、[]内で指定できる数値は最大で31です。
サイドセット・ピンの使い方
pull side 1
set y,7 side 0
pullでFIFOからデータを受け取って、サイドセット指定しているピンの状態を 1 (High-Level)にし、
setでスクラッチレジスタyに7をセットし、サイドセット指定しているピンの状態を 0 (Low-Level)にします。
ピン操作をするための補助コマンドと言ったイメージでしょうか。
コマンドの実行と一緒にピンの状態を設定することができます。
in命令
構文:
in 入力元 , シフト量
in命令はISRをシフト量分だけシフトし、入力元の値をISRの空いた領域に書きます。
入力元(シフト元)はIO端子だけに限りません。
シフトによってISRから吐き出された値をISRに取り込むこともできます。
入力元にNULLを指定すると0が取り込まれます。
シフト方向(右シフト、左シフト)の設定は sm_config_set_in_shift()関数で行います。
右シフト: MSBからLSBに向かってシフト
左シフト: LSBからMSBに向かってシフト
例:
in pins, 1
ISRを1ビットシフトし、あらかじめ指定されているピンから1ビット分の値を取り込みます。
以下に入力元に指定できるものを示します。
入力元 | 内容 |
---|---|
pins | あらかじめ設定しておいたIO端子 |
X | スクラッチレジスタX |
Y | スクラッチレジスタY |
OSR | 出力シフトレジスタ |
null | 値 0 |
ISR | 入力シフトレジスタX |
in命令のイメージは以下の通りです。
push命令
構文:
push
push命令はISRからRX FIFOに32ビットデータを書き込み、ISRとISRのシフトカウンタをクリアします。
ただしRX FIFOに空きがない場合には待機(ブロック)します。
push block :
blockビットはデフォルトで1なので、pushと同じ動作になります。
push nonblock :
FIFOに空きがあればpushし、満杯の場合には何もせず次の命令を実行します。
この時ISRの値はクリアされます。
push iffull :
入力シフト数の合計がしきい値(SHIFTCTRL_PUSH_THRESH)に達したら、ISRからRX FIFOに32ビットデータを書き込み、ISRのシフトカウンタをクリアします。
ただしRX FIFOに空きがなければ待機(ブロック)します。
Auto push に設定しておくことで、この命令を自動で行ってくれます。
しきい値及び Auto pushの設定は sm_config_set_in_shift()関数で行います。
push iffull nonblock :
入力シフト数の合計がしきい値に達したら、ISRからRX FIFOに32ビットデータを書き込み、ISRのシフトカウンタをクリアします。
ただしRX FIFOに空きがなければ何もせず次の命令を実行します。
この時ISRの値はクリアされます。
例:
push
push命令はISRからRX FIFOに32ビットデータを書き込み、ISRとISRのシフトカウンタをクリアします。
ただしRX FIFOに空きがない場合には待機(ブロック)します。
Auto push を true にしておけば、push命令は単なる push だけで事足りるように思いますが、いかがでしょうか。
使ってみないと何ともわからないですね。
push命令のイメージは以下の通りです。
out命令
構文:
out 出力先 , シフト量
out命令はOSRをシフト量分だけシフトし、あふれた値を出力先に書きます。
出力先(シフト先)はIO端子だけに限りません。
シフト方向(右シフト、左シフト)は sm_config_set_out_shift()関数で設定する。
右シフト: MSBからLSBに向かってシフト
左シフト: LSBからMSBに向かってシフト
例:
out pins, 1
OSRを1ビットシフトし、あふれた値をIO端子に出力します。
以下に出力先に指定できるものを示します。
出力先 | 内容 |
---|---|
pins | あらかじめ設定しておいたIO端子 |
pindir | あらかじめ設定しておいたIO端子の方向 |
X | スクラッチレジスタX |
Y | スクラッチレジスタY |
pc | プログラムカウンタ |
exec | 次に実行する命令レジスタ |
null | 値 0 |
ISR | 入力シフトレジスタX |
out命令のイメージは以下の通りです。
pull命令
構文:
pull
pull命令はTX FIFOからOSRに32ビットデータを読み込み込みOSRのシフトカウンタをクリアします。
ただしTX FIFOが空の場合には待機(ブロック)します。
pull block :
blockビットはデフォルトで1なので、pullと同じ動作になります。
pull nonblock :
pull命令はTX FIFOからOSRに32ビットデータを読み込み込みOSRのシフトカウンタをクリアします。
ただしTX FIFOが空の場合には何もせず次の命令を実行し、OSRにはレジスタXの値が入ります。
pull ifempty :
出力シフト数の合計がしきい値に達したら、TX FIFOからORSに32ビットデータを書き込み、OSRのシフトカウンタをクリアします。
ただしTX FIFOが空の場合には何もせず次の命令を実行します。
Auto pull に設定しておくことで、この命令を自動で行ってくれます。
しきい値及び Auto pullは sm_config_set_in_shift()関数で設定します。
pull ifempty nonblock :
出力シフト数の合計がしきい値に達したら、TX FIFOからORSに32ビットデータを書き込み、OSRのシフトカウンタをクリアします。
ただしTX FIFOが空の場合には何もせず次の命令を実行し、OSRにはレジスタXの値が入ります。
例:
pull noblock
pull命令はTX FIFOからOSRに32ビットデータを読み込み込みOSRのシフトカウンタをクリアします。
ただしTX FIFOが空の場合には何もせず次の命令を実行し、OSRにはレジスタXの値が入ります。
pull命令のイメージは以下の通りです。
mov
構文:
mov 転送先 , オペレーション 転送元
mov命令はPIOのSM内でデータをコピーします。
オペレーションが含まれる場合もあります。
以下に転送先と転送元に指定できるものを示します。
転送先 | 転送元 |
---|---|
pins | pins |
X | X |
Y | Y |
pc | status |
ISR | ISR |
OSR | OSR |
実行 | null (0) |
以下にオペレーションに指定できるものを示します。
~ または ! | ビットを反転する |
---|---|
:: | ビットを逆順にする |
(注)auto pullを使う場合には mov で OSR を使わないようにしてください。
auto pullの判定は全てのサイクルで行われるため、OSRに対してmovした場合、pullしたデータを上書きするタイミングがあるからです。
例:
mov x, -y
XにYを反転した値を代入します。
jmp
構文:
jmp 条件 ジャンプ先
pcの値を特定のアドレスに設定します。
ジャンプ先は絶対アドレスになります。
条件 | 説明 |
---|---|
なし | 無条件ジャンプ |
!x | xが0の場合にジャンプ |
x– | xが0でない場合にジャンプしxを減算する |
!y | yが0の場合にジャンプ |
y– | yが0でない場合にジャンプしxを減算する |
x!=y | xとyが等しくない場合にジャンプ |
pin | SMX_EXECCTRLレジスタのJMP_PINで指定したGPIOがHの時にジャンプする |
!osre | osrが空でない場合にジャンプ |
例:
jmp !osre loop
OSRの値がしきい値に達していたらラベルloopにジャンプします。
set
構文:
set 出力先 , 5ビットの値
set命令は5ビットの値を設定します。
以下に出力先に指定できるものを示します。
出力先 | GPIO端子番号 |
---|---|
pindirs | ピンの方向 |
X | スクラッチレジスタX |
Y | スクラッチレジスタY |
例:
set x,7
Xに7を代入します。
irq
構文:
irq
irq命令は割り込みフラグのセット・クリアを行います。
ひとつのPIOは0~7番の割り込みフラグを持っています。
これらは各SMで共有しています。
0~3番はシステムレベルの割り込みとして出力されます。
4~7番はPIO内のSMからしか見えません。
waitと組み合わせて他のSMとタイミングを同期することができます。
割り込みフラグはwaitによってもクリアされます。
命令 | 効果 |
---|---|
irq 0 | 割り込みフラグ0をセットする |
irq wait 3 | 割り込みフラグ3をセットし3がクリアされるまで待機する |
irq clear 7 | 割り込みフラグ7をクリアする |
同じプログラムでも4つのSMで異なる割り込みフラグを立てたい場合があります。
命令の最後に _rel を付加すると、それぞれのSMで異なる割り込みフラグの操作ができます。
割り込みフラグ番号の計算方法は以下の通りです。
(num & 4) + ((SM番号 + num) & 3)
命令 | 効果 |
---|---|
irq 0 _rel | SM X(0-3)からX番の割り込みフラグをセットする |
irq clear 4 _rel | SM X(0-3)からX+4番の割り込みフラグをクリアする |
irq wait 1 _rel | SM X(0-3)から(X+1mod 4)番の割り込みフラグをセットしクリアされるまで待機する |
wait
構文:
wait
GPIO, PIN, 割り込みフラグのいずれかからソースを選び
設定した状態に変化するまで待機(ブロック)します。
GPIOとPINはGPIOの番号で指定するかIN命令のIOマッピングの
番号で指定するかの違いです。
PINを使うと同じプログラムで複数のSMに対してSM毎に異なる
GPIOを対象として処理できます。
割り込みフラグのセットをソースに指定した場合、セットされた時に
SMによってフラグがクリアされます。
命令の最後にrelを付加することで割り込みフラグ番号をirq命令と同じ計算方法で相対的に扱うことができます。
命令 | 効果 |
---|---|
wait 0 gpio 10 | GPIO10が”L”になるまで待機する |
wait 1 gpio 5 | GPIO5が”H”になるまで待機する |
wait 0 pin 0 | IN_BASEで指定したGPIOが”L”になるまで待機する |
wait 1 pin 2 | IN_BASE+2で指定したGPIOが”H”になるまで待機する |
wait 0 irq 0 | 割り込みフラグ0がクリアされるまで待機する |
wait 1 irq 7 | 割り込みフラグ7がセットされるまで待機しフラグ7をクリアする |
wait 1 irq 4 rel | 割り込みフラグ4+xがセットされるまで待機しフラグ4+xをクリアする |
nop
構文:
nop
PIOにnop(no operation 何もしない)命令はありません。
ただ利便性のためにnopと記述すると mov y,y に変換します。
スクラッチレジスタYにYを代入するため結果的に何も起こりません。
以上 駆け足でアセンブラ命令について説明しました。
いかがでしたか?
概略程度は理解できたでしょうか?
堅苦しい説明はこのくらいにして次回から実際にコードを書いてみることにします。
お疲れさまでした。