Slide 1

Slide 1 text

RP2040のPIOを使う話 Toshifumi NISHINAGA (CV: ついなちゃん) @tnishinaga 2023-12-02 KernelVM Hokuriku 6 1

Slide 2

Slide 2 text

⾃⼰紹介 • tnishinaga(CVついなちゃん) • kernelvm online 4でJTAGでarmプ ロセッサをデバッグする発表した ⼈ • https://speakerdeck.com/tnishinaga /kernelvm-online4 2

Slide 3

Slide 3 text

発表の経緯 • Raspberry Pi Pico(RP2040)のPIOはいろんなプロトコルを⾼速 に喋れるらしい • 例 • DVI: https://github.com/Wren6991/PicoDVI • USB: https://github.com/sekigon-gonnoc/Pico-PIO-USB • ロジックアナライザ: https://github.com/dotcypress/ula • どんなプロトコルでも⾃由なピン割当で作れる夢のペリフェラ ルだと思っていた • *実際に触ってみると⾊々制約があった • 今更だけど勉強して使えるようになったので、得た知⾒を共有 したい 3

Slide 4

Slide 4 text

参考資料 • RP2040 Datasheet • build-date: 2023-06-14 • build-version: a6fe703-clean • https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf • PIOに関する解説記事 • PIO学習の初期に様々な先⼈の解説記事を読み、理解の助けとさせて いただきました。みなさま素晴らしい記事をありがとうございました • 覚えているサイトのみ記載 • https://hanya-orz.hatenablog.com/entry/2021/07/28/191910 • https://blog.boochow.com/article/rpi-pico-spec-3.html • https://qiita.com/yunkya2/items/8fce43c0dbb97be97682 4

Slide 5

Slide 5 text

⽬次 • PIOとは • Rust+PIOプログラミング 5

Slide 6

Slide 6 text

PIOとは 6

Slide 7

Slide 7 text

PIO(Programable I/O) • RP2040の特徴的なペリフェラル • Picoが流⾏ったの⼀因(と思っている) • ペリフェラルとして2基搭載されている • 特徴 • 任意のIOペリフェラルをプログラムして⾃ 作できる • IO制御が⾼速 • PIOの動作クロックはCPUと同じ • 命令もIO制御も1 clock実⾏ • 理論上最⼤62.5MHzでLチカ可能 (コアクロック125MHzの場合) • 普通のGPIO制御と⽐べて超⾼速 • ↑これらはCPLDやFPGAの領域だった 7 画像はRP2040のデータシート Figure 38より引⽤

Slide 8

Slide 8 text

PIOの中⾝ • シンプルなRISCプロセッサ*を4基搭載 • *: StateMachine(SM) と呼ぶ • 32命令分のプログラムメモリを全SMで共有 • 全9命令の独⾃命令セット • 全命令は1cycle = 1 clock(=core_clock / divisor)で動く • SMから基本1cycleでGPIOを制御可能 • GPIOのFunctionでPIOに割り当てると利⽤可 • ⼊⼒安定のため2 FF同期回路がついているので ⼊⼒には2cycle必要(3.5.6.3. Input Synchronisers ) • FIFOでCPUとデータをやり取りする • IRQ割り込みも出せる • やったこと無いので割愛 8 画像はRP2040のデータシート Figure 38より引⽤

Slide 9

Slide 9 text

SMのレジスタ • 汎⽤レジスタ(X,Y) • 32bit汎⽤レジスタ • 主にループカウンタとして使う • ISR(InShift)/OSR(OutShift) • 主に外部と値をやり取りするため の32bitレジスタ • push/pull命令でFIFOとデータをやり 取り • out/in命令でGPIOの⼊出⼒をやり取 り 9 画像はRP2040のデータシート Figure 39より引⽤

Slide 10

Slide 10 text

その他⼊出⼒レジスタ • PINS • GPIOのHigh/Lowを設定したり、⼊⼒ 状態を⾒るための32bitレジスタ • 各ビットが各GPIOピンに対応する • 予め設定したbaseピンが0bit⽬に対応 • n-bit⽬は(base + n) % 32ピンに対応 • PINDIRS • GPIOの⼊出⼒を設定する32bitレジス タ • 0が出⼒。1が⼊⼒。 • ビットとGPIOの対応はPINSと同様 10 0 1 0 1 ... 0 1 0 1 PINS 0 1 0 1 ... 0 1 0 1 GPIO 29 28 27 26 ... 1 0 31 30 base=30の場合

Slide 11

Slide 11 text

baseピンの設定 • PINSやPINDIRのbaseピンは以下の命令(後述)ごとに設定できる • in • out • set • side-set • jmp(⼊⼒1ピンのみ) • ⾃由に(不連続に独⽴して)ピン割り当てできるのは上記5ピンだけ • 実⽤的にはin/out/side-setの3つまで • 1つの命令で2本以上制御の必要があるシリアル通信は、ピン割り当てに制限が出る • PIOのSM*_PINCTRLレジスタで設定する • これはPIOの外(CPU)から設定が必要 11

Slide 12

Slide 12 text

PIOの命令セット • 命令は以下の9種類のみ • IO関係 • in • out • set • FIFO⼊出⼒ • push • pull • 値操作 • mov • 条件分岐 • jmp • その他(説明省略) • wait • irq • 各命令にDelayとside setをつけられる 12 画像はRP2040のデータシート Table 365より引⽤

Slide 13

Slide 13 text

IO関係 • in • ソースからISRにn-bitシフトして⼊⼒す る命令 • 主にPINSを指定してGPIOのピン状態を 得るために使う • out • OSRの値をデスティネーションにn-bitシ フトして出⼒する命令 • 主にPINSを指定してGPIOにH/Lを出⼒す るために使う • set • 最⼤5bitまでの即値を各レジスタにセッ トする命令 13 ISR OSR PINS PINS n-bit shift n-bit shift 即値 PINS in out set

Slide 14

Slide 14 text

Delayとside-set • Delay • 命令実⾏後n-cycle待つ機能 • 信号のHi/Lowをクロック周波数分固定するのに使う • 最⼤で31cycle(5bit)待てる • side-setに割り当てるピン数によって減少 • side-setとフィールドが共通なため • side-setに1bit割り当てただけで最⼤7cycleしか待てなくなるらしい • https://blueeyes.sakura.ne.jp/2021/02/05/3745/ • side-set • 命令実⾏時と同時にピンの状態をsetできる機能 • 主にクロック信号をセットするのに使う 14

Slide 15

Slide 15 text

in・out・set・side-setの使い分け • in・out • コンパイル時に決まらない信号の⼊出⼒(データ通信)に使う • set • あらかじめ状態が決まっている信号の出⼒に使える • SPI通信のCSピンなど • side-set • 主にin・outとリンクした信号の出⼒に使う • クロック信号など 15

Slide 16

Slide 16 text

FIFO⼊出⼒(push/pull) • pull • FIFOから32bit値をOSRに取り込む • 以下の条件をつけられる • IfEmpty: OSRのビットカウント(シフト済みビット数)が スレッショルドを超えた場合のみpullする • Block: • FIFOがempryの場合、stallする • push • ISRから32bit値をFIFOに書き込む • 以下の条件をつけられる • IfFull: ISRののビットカウント(シフト済みビット数)が スレッショルドを超えた場合のみpushする • Block • FIFOがfullの場合、stallする 16 FIFO OSR ISR push pull TX FIFO RX FIFO

Slide 17

Slide 17 text

Tips: auto push/pull • OSR/ISRのビットカウントが thresholdを超えた際に⾃動で push/pullする機能 • PIOペリフェラルの設定で有効化できる • RP2040のデータシート(※)には以 下のメリットがあると書かれている • コードが短くシンプルになる • (例では)2倍速くなる • 挙動の把握が難しいので、個⼈的に は初⼿から使うのは⾮推奨 • 送受信ビット数 % threshold != 0 の場合 、余りの部分の扱いが難しい • 動作確認後の⾼速化時に検討すると良さ そう 17 ※: 3.5.4. Autopush and Autopull , pp.333 RP2040のデータシート pp.333- 334から引⽤ Autopullなし Autopullあり

Slide 18

Slide 18 text

値操作(mov) • 各レジスタ間で値をmovできる • mov時に以下の操作もできる • invert: ビット反転(0を0xFFFFFFFFにする) • bit-reverse: ビットを逆転させる(0x3を0xC0000000にする) • 即値は扱えない • 即値を扱いたい場合はset命令を使う 18

Slide 19

Slide 19 text

条件分岐(jmp) • 以下の条件でラベルまで⾶べる • Always • X != 0, Y != 0, X != Y • X-- != 0, Y-- != 0 • PIN • 予め設定したGPIOピンの状態に応じて分岐 • 使ったこと無いので詳細不明 • !OSRE • OSRが空でないなら分岐 19

Slide 20

Slide 20 text

jmp命令の使い⽅ • ループを作る(X--,Y--) • C⾔語の do { do_somothing } while ( X-- ) ループが作れる • post-decrementなことに気をつけないとループ回数がずれるので注意 • 例: X=1の場合、do_somethingは2回実⾏される • 評価してからデクリメントのため • 条件分岐(その他) • C⾔語の if ( !condition ) { do_something } 的な使い⽅をする • もちろんgoto⽂としても使える 20

Slide 21

Slide 21 text

その他の機能 • label • 任意の場所にラベルを付けられる • jmp命令のアドレス指定に使う • wrap • プログラムのouter loopを作るため専⽤ のラベル • Arduinoのloop()関数を作るためのラベルと いう認識 • PIOアセンブラが.wrapに到達すると .wrap_targetに勝⼿に⾶ばしてくれる 21 PIOのプログラム .wrap .wrap_target

Slide 22

Slide 22 text

PIO Assembler(pioasm) • アセンブリ⾔語のソースコード(PIO source file)⽤のアセンブ ラらしい • アセンブリ後のバイナリをC⾔語のヘッダ形式で出してくれる らしい • Raspberry Pi Pico SDKに⼊っているらしい 22

Slide 23

Slide 23 text

PIOに関する疑問 • 結局どんなことに向いてて、どんなことに向いてないの? • ピン割当は⾃由にできるの? 23

Slide 24

Slide 24 text

PIOに向いていること・向いてないこと • 向いていること • 受け取ったデータを単純に⼊出⼒すること • 向いていないこと • 複雑な状態遷移や計算が必要なこと • プログラムメモリが少なく、計算命令もなく、条件分岐も強⼒でないため • 複雑な制御はCPU、データの⼊出⼒はPIOで役割分担が必要 24

Slide 25

Slide 25 text

ピン割当は⾃由にできるの? • ⭕⼊⼒・出⼒・クロックを各1ピンずつま でなら可 • 各ピンのbaseは独⽴しているので、ピン割当 は⾃由 • 🔺それ以上は厳しい • ⾼速で効率的な通信をするにはOUT/IN pin baseから連続したGPIOピンを使う必要がある (=ピン割当に制約がでる) • out命令はOSRの値を⼀定数SHIFTしかできないた め • ピン割当制約をなくすためには、1cycle分送るため に32bitの疎な値を⽤意する必要があり、⾮効率 25 4bitデータ出⼒を効率よく制御する イメージ図 GPIOは連番必須 0 1 0 1 ... 0 1 0 1 OSR 0 1 0 1 ... 0 1 0 1 3 2 1 0 ... 3 2 1 0 out 8度⽬ out 1度⽬ GPIO

Slide 26

Slide 26 text

PIOのまとめ • 単純なIOペリフェラルを⾃作できるペリフェラル • RP2040に2基搭載 • 中⾝はシンプルなRISCプロセッサ(SM)が4つ⼊っている • 9命令の独⾃命令セット • プログラムは最⼤32命令(全SMで共有) • 最⼤動作クロックはCPUのコアクロック • IO含む全命令を1 cycle実⾏可 • 理論上 (CPUのコアクロック / 2) Hzの信号を出⼒可能 26

Slide 27

Slide 27 text

Rust+PIOプログラミング Rust+PIOでプログラムの作り⽅や例をご紹介 27

Slide 28

Slide 28 text

Rust + PIOプログラムの作り⽅ • PIOのプログラムを作る • pio-rsを使う • PIOのセットアップを⾏う • PIOをCPUのプログラムから使う 28

Slide 29

Slide 29 text

pio-rs • Rustで作られたPIOアセンブラ • ビルド時にアセンブルする • 動的にアセンブルも可能らしい • RustでPIOのプログラムを書け る(右図下) • コード補完のお陰で設定できるレ ジスタが⾒えて便利なのでおすす め • PIOアセンブリコードでも書ける( 右図上) 29 PIOアセンブリ⽅式 Rust形式 右図はpio-rs (https://github.com/rp-rs/pio-rs)の READMEより引⽤

Slide 30

Slide 30 text

pio-rsを使ったプログラムの作り⽅ 30 https://github.com/rp-rs/rp-hal/blob/main/rp2040- hal/examples/pio_blink.rs#L45-L63 よりコードを引⽤ 2. wrapループ⽤のラベ ルを定義し、アドレスと ラベルをbindで紐つける 1. pio::Asemblerを作る 以後、Assemblerに対し 命令を追加していく 4. wrapで囲まれたル ープの中にメインの 処理を書く 5. wrapループを有効に してアセンブルしてSM に読み込ませるプログラ ムを作る 3. ループ前にIOピン等を 初期化する

Slide 31

Slide 31 text

PIOのセットアップ • PIOで利⽤するGPIOのFunctionをPIOモードにする • PIOのSMを初期化して実⾏する • PIOプログラムをロードする • in・out・side-set等のbaseピンを指定する • clock divisorを設定する • push/pull_threshold等を設定する • PIOのSMを実⾏する 31

Slide 32

Slide 32 text

PIOで利⽤するGPIOのFunctionをPIOモー ドにする • PIOのPINSはGPIO0~31の⼊出⼒制 御可能 • 実際に⼊出⼒されるのはGPIOペリフェ ラルでFunctionをPIOにしたものだけ • Rustの型推論で特定GPIOの FunctionをPIOにできる • rp2040_hal::gpio::Pinのinto_function 関数を使う(右図下参照) 32 https://github.com/rp-rs/rp-hal/blob/main/rp2040- hal/examples/pio_blink.rs より引⽤ 0 1 0 1 ... 0 1 0 1 PINS X X X 1 ... 0 X 0 1 31 32 31 30 ... 3 2 1 0 X X X PIO ... PIO X PIO PIO GPIO Function GPIO 出⼒ FunctionをPIOにしたGPIOのみPINSの値が出⼒される図

Slide 33

Slide 33 text

PIOのSMを初期化して実⾏する • PIOプログラムをインストールする • rp2040_hal::pio::PIOのinstall関数を使う • https://docs.rs/rp2040- hal/latest/rp2040_hal/pio/struct.PIO.html#metho d.install • PIOBuilderを使ってSMをセットアップする • in・out・side-set等のbaseピンを指定する • set_*関数を使う • https://docs.rs/rp2040- hal/latest/rp2040_hal/pio/struct.PIOBuilder.ht ml#method.set_pins • clock divisorを設定する • clock_divisor_fixed_point関数を使ってPIOの動作ク ロック(コアクロック / (n + f / 256.0))のnとfを 設定する • (使う場合のみ)autopush/pullのthreshold等を 設定する • start関数でSMの実⾏を開始する 33 https://github.com/rp-rs/rp-hal/blob/main/rp2040- hal/examples/pio_blink.rs より引⽤

Slide 34

Slide 34 text

PIOをCPUのプログラムから使う • PIOのFIFOを読み書きしてデ ータをやり取りする • PIOBuilder::buildがFIFOの TX/RXを返すので、これを使 ってやり取りする • DMAでやり取りもできる • まだ試せていない • https://github.com/rp-rs/rp- hal/blob/main/rp2040- hal/examples/pio_dma.rs 34 https://github.com/ciniml/rust-dap/blob/main/rust-dap- rp2040/src/pio.rs#L458-L473 よりコードを引⽤

Slide 35

Slide 35 text

PIOプログラムの作例 • rust-dapに⼊れたJTAG制御⽤PIOプログラムを作例に、設計⼿ 順をご紹介 35

Slide 36

Slide 36 text

PIOプログラムの設計⼿順 • PIOプログラムは以下の⼿順で作るのがおすすめ • 仕様の把握 • ⼊出⼒の設計 • コーディング • デバッグ 36

Slide 37

Slide 37 text

rust-dapとは • CMSIS-DAP規格をRustで実装したデバッガ⽤ファームウェア • https://github.com/ciniml/rust-dap • RP2040のPIOを使って⾼速通信が可能 • ただし、2023/11/24時点ではSWDのみ実装 • JTAG実装を開発してPRを出している途中 • 発表までにはマージされてるはず 37

Slide 38

Slide 38 text

JTAGとは • (本資料の⽂脈では)プロセッサデバッグに使う通信規格の⼀ つ • JTAGインターフェース(アダプタ)が必要 • CMSIS-DAPはこのJTAG(とSWD)インターフェースを作るための規格 • 詳細は以下を参照 • JTAGでarmプロセッサをデバッグする話/KernelVM Online4 • https://speakerdeck.com/tnishinaga/kernelvm-online4 38

Slide 39

Slide 39 text

JTAG制御⽤PIOプログラム • CMSIS-DAPのJTAG Sequenceコマンド を実現するPIOプログラムを作る • https://arm- software.github.io/CMSIS_5/DAP/html/gr oup__DAP__JTAG__Sequence.html • 設計⽅針 • まず動くことを優先 • 速度はbitbangより速ければOK • in/out/set/side-setにそれぞれ2ピン以上 割り当てない(右図参照) • ピン割当に制約を作らないようにするため • ハードウェア制作時にピンを⾃由に決められる 39 PIO JTAG IN TDO OUT TDI Side-Set TCK SET TMS N/A nTRST N/A nSRST PIOとJTAGのピン割り当て図 CMSIS-DAPの仕様的にJTAG通信時には不要な ので n*RSTは本プログラムでは制御しない。 (SWJ_Pinsコマンド⽤の別プログラムで制御 する)

Slide 40

Slide 40 text

JTAG_Sequenceコマンド • 単純にJTAGのシーケンス処理を⾏うだけのコマンド • JTAGのステートとか最終ビットのTMS処理など考えなくて良い • 以下の情報が送られてくる • 送受信データのバイト数 • 送受信データのビット数 • 送受信中のTMSの値 • 受信処理を⾏うかのフラグ • 送信するデータ • 以下の応答を返す • 受信したデータ • 受信処理を⾏うかのフラグが⽴っている場合 40

Slide 41

Slide 41 text

⼊出⼒を設計する • PIOの気持ちになって、動き やすいフォーマットを考える • コードを書きながら適宜修正 • PIOに送信するもの • 送受信データのビット数 • TMSの値 • 送信データ • PIOから受け取るもの • 受信データ 41 bit count TMS 31bit 1bit send data 32 * X bit PIO received data 32 * X bit

Slide 42

Slide 42 text

コードを作る 1. FIFOから32bit取得し、bit countとTMS値 に分ける • pull して out命令で1bitと31bitに分けてレジスタ (Y,X)に⼊れる 2. TMS値をセットする • jmp命令で切り替えつつ、set命令を実⾏ • TMSが割り当てられているset命令は即値しか使えな いので、任意の値を⼊れるには条件分岐が必要 3. ループを作ってデータの送受信をする 1. OSRが空ならFIFOからpullする 2. TDIに1bit outして、side-setでTCKをLowにし てすこし待つ 3. TDOから1bit inして、side-setでTCKをHighに して少し待つ 4. ISRが満タンならFIFOにpushする 4. OSRが空でないなら、FIFOにpush 42 1 2 3 4 ※右図のプログラムはdelayにミスがあります

Slide 43

Slide 43 text

動作チェック • 動作クロックを遅くして ロジアナで波形を⾒る • digilentのロジアナ⽤ソフト WaveFormsが最近JTAGプ ロトコル対応したので便利 • Hi/Loのdelayが均等にな ってるかチェックする 43

Slide 44

Slide 44 text

デモ • openocdでspeedを1KHzに してテスト • クロック信号のdelayはい い感じにできてそう • 1周期1msなので1kHzで動 いていそう 44

Slide 45

Slide 45 text

デモ2 • openocdでspeedを1MHz にしてテスト • CPUからPIOへのデータ転 送が間に合ってない • 要改善 45

Slide 46

Slide 46 text

Rust+PIOプログラミングのまとめ • pio-rsを使えばRustでPIOのプログラムが作れる • PIOを利⽤するには初期設定が必要 • GPIOのFunction設定 • プログラムのロード、divisorの設定等 • PIOプログラムは以下の⼿順で作るのがおすすめ • 仕様の把握 • ⼊出⼒の設計 • コーディング • デバッグ 46

Slide 47

Slide 47 text

全体のまとめ • PIOを使えば単純なシリアル通信⽤ペリフェラルが作れる • (制約はあるものの)ピン割当も⾃由にできる • pio-rsを使えばRustでPIOプログラムが書ける 56