ラズピコ PIO ~解説編~

  • 2023.03.04
  • PIO
ラズピコ PIO ~解説編~

皆さん こんにちは。

今回はラズピコのマイコン 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を代入するため結果的に何も起こりません。

以上 駆け足でアセンブラ命令について説明しました。

いかがでしたか?
概略程度は理解できたでしょうか?

堅苦しい説明はこのくらいにして次回から実際にコードを書いてみることにします。

お疲れさまでした。

PIOカテゴリの最新記事