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. RP2040のPIOを使う話
    Toshifumi NISHINAGA (CV: ついなちゃん)
    @tnishinaga
    2023-12-02 KernelVM Hokuriku 6
    1

    View full-size slide

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

    • https://speakerdeck.com/tnishinaga
    /kernelvm-online4
    2

    View full-size slide

  3. 発表の経緯
    • 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

    View full-size slide

  4. 参考資料
    • 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

    View full-size slide

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

    View full-size slide

  6. 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より引⽤

    View full-size slide

  7. 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より引⽤

    View full-size slide

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

    9
    画像はRP2040のデータシート
    Figure 39より引⽤

    View full-size slide

  9. その他⼊出⼒レジスタ
    • 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の場合

    View full-size slide

  10. 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

    View full-size slide

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

    View full-size slide

  12. 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

    View full-size slide

  13. 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

    View full-size slide

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

    View full-size slide

  15. 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

    View full-size slide

  16. 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あり

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  24. ピン割当は⾃由にできるの?
    • ⭕⼊⼒・出⼒・クロックを各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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  28. pio-rs
    • Rustで作られたPIOアセンブラ
    • ビルド時にアセンブルする
    • 動的にアセンブルも可能らしい
    • RustでPIOのプログラムを書け
    る(右図下)
    • コード補完のお陰で設定できるレ
    ジスタが⾒えて便利なのでおすす

    • PIOアセンブリコードでも書ける(
    右図上)
    29
    PIOアセンブリ⽅式
    Rust形式
    右図はpio-rs (https://github.com/rp-rs/pio-rs)の
    READMEより引⽤

    View full-size slide

  29. 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ピン等を
    初期化する

    View full-size slide

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

    View full-size slide

  31. 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の値が出⼒される図

    View full-size slide

  32. 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
    より引⽤

    View full-size slide

  33. 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
    よりコードを引⽤

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  37. JTAGとは
    • (本資料の⽂脈では)プロセッサデバッグに使う通信規格の⼀

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

    View full-size slide

  38. 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コマンド⽤の別プログラムで制御
    する)

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  41. コードを作る
    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にミスがあります

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide