Upgrade to Pro — share decks privately, control downloads, hide ads and more …

RP2040のPIOを使う話/KernelVM Hokuriku 6

RP2040のPIOを使う話/KernelVM Hokuriku 6

Toshifumi NISHINAGA

December 02, 2023
Tweet

More Decks by Toshifumi NISHINAGA

Other Decks in Programming

Transcript

  1. 発表の経緯 • 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
  2. 参考資料 • 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
  3. 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より引⽤
  4. 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より引⽤
  5. SMのレジスタ • 汎⽤レジスタ(X,Y) • 32bit汎⽤レジスタ • 主にループカウンタとして使う • ISR(InShift)/OSR(OutShift) •

    主に外部と値をやり取りするため の32bitレジスタ • push/pull命令でFIFOとデータをやり 取り • out/in命令でGPIOの⼊出⼒をやり取 り 9 画像はRP2040のデータシート Figure 39より引⽤
  6. その他⼊出⼒レジスタ • 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の場合
  7. 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
  8. PIOの命令セット • 命令は以下の9種類のみ • IO関係 • in • out •

    set • FIFO⼊出⼒ • push • pull • 値操作 • mov • 条件分岐 • jmp • その他(説明省略) • wait • irq • 各命令にDelayとside setをつけられる 12 画像はRP2040のデータシート Table 365より引⽤
  9. 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
  10. 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
  11. 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
  12. 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あり
  13. 値操作(mov) • 各レジスタ間で値をmovできる • mov時に以下の操作もできる • invert: ビット反転(0を0xFFFFFFFFにする) • bit-reverse:

    ビットを逆転させる(0x3を0xC0000000にする) • 即値は扱えない • 即値を扱いたい場合はset命令を使う 18
  14. 条件分岐(jmp) • 以下の条件でラベルまで⾶べる • Always • X != 0, Y

    != 0, X != Y • X-- != 0, Y-- != 0 • PIN • 予め設定したGPIOピンの状態に応じて分岐 • 使ったこと無いので詳細不明 • !OSRE • OSRが空でないなら分岐 19
  15. jmp命令の使い⽅ • ループを作る(X--,Y--) • C⾔語の do { do_somothing } while

    ( X-- ) ループが作れる • post-decrementなことに気をつけないとループ回数がずれるので注意 • 例: X=1の場合、do_somethingは2回実⾏される • 評価してからデクリメントのため • 条件分岐(その他) • C⾔語の if ( !condition ) { do_something } 的な使い⽅をする • もちろんgoto⽂としても使える 20
  16. その他の機能 • label • 任意の場所にラベルを付けられる • jmp命令のアドレス指定に使う • wrap •

    プログラムのouter loopを作るため専⽤ のラベル • Arduinoのloop()関数を作るためのラベルと いう認識 • PIOアセンブラが.wrapに到達すると .wrap_targetに勝⼿に⾶ばしてくれる 21 PIOのプログラム .wrap .wrap_target
  17. PIOに向いていること・向いてないこと • 向いていること • 受け取ったデータを単純に⼊出⼒すること • 向いていないこと • 複雑な状態遷移や計算が必要なこと •

    プログラムメモリが少なく、計算命令もなく、条件分岐も強⼒でないため • 複雑な制御はCPU、データの⼊出⼒はPIOで役割分担が必要 24
  18. ピン割当は⾃由にできるの? • ⭕⼊⼒・出⼒・クロックを各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
  19. PIOのまとめ • 単純なIOペリフェラルを⾃作できるペリフェラル • RP2040に2基搭載 • 中⾝はシンプルなRISCプロセッサ(SM)が4つ⼊っている • 9命令の独⾃命令セット •

    プログラムは最⼤32命令(全SMで共有) • 最⼤動作クロックはCPUのコアクロック • IO含む全命令を1 cycle実⾏可 • 理論上 (CPUのコアクロック / 2) Hzの信号を出⼒可能 26
  20. pio-rs • Rustで作られたPIOアセンブラ • ビルド時にアセンブルする • 動的にアセンブルも可能らしい • RustでPIOのプログラムを書け る(右図下)

    • コード補完のお陰で設定できるレ ジスタが⾒えて便利なのでおすす め • PIOアセンブリコードでも書ける( 右図上) 29 PIOアセンブリ⽅式 Rust形式 右図はpio-rs (https://github.com/rp-rs/pio-rs)の READMEより引⽤
  21. 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ピン等を 初期化する
  22. 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の値が出⼒される図
  23. 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 より引⽤
  24. 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 よりコードを引⽤
  25. 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コマンド⽤の別プログラムで制御 する)
  26. JTAG_Sequenceコマンド • 単純にJTAGのシーケンス処理を⾏うだけのコマンド • JTAGのステートとか最終ビットのTMS処理など考えなくて良い • 以下の情報が送られてくる • 送受信データのバイト数 •

    送受信データのビット数 • 送受信中のTMSの値 • 受信処理を⾏うかのフラグ • 送信するデータ • 以下の応答を返す • 受信したデータ • 受信処理を⾏うかのフラグが⽴っている場合 40
  27. ⼊出⼒を設計する • PIOの気持ちになって、動き やすいフォーマットを考える • コードを書きながら適宜修正 • PIOに送信するもの • 送受信データのビット数

    • TMSの値 • 送信データ • PIOから受け取るもの • 受信データ 41 bit count TMS 31bit 1bit send data 32 * X bit PIO received data 32 * X bit
  28. コードを作る 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にミスがあります