🚪

RasPi Pico PIOの入出力設定に関する混乱

2024/09/06に公開

Raspberry Pi Pico / Pico 2に使われているSoCマイコンRP2040/RP2350にはPIO(Programmable IO)という機能があり、独自のステートマシンを組み込んで高速な信号生成や通信を行うことができます。

ところがこの機能の仕様には不鮮明が点があります。SDKのAPIもその仕様に基づいているため、一部のAPIの利用に混乱が生じています。

結論

PIOの入出力を設定する際、sm_config_set_xxx_pins() をはじめとするステートマシン設定APIとpio_sm_set_consecutive_pindirs()をはじめとする入出力方向設定APIの両方を呼ばなければPIOの入出力は正しく機能しません。

例えば、 sm_config_set_in_pins()とpio_sm_set_consecutive_pindirs()の双方を呼んでIOを設定する必要があります。

PIOのIO設定機能(ハードウェア)について

RP2040やRP2050のデータシートを読むと、PIOx:SMy_PINCTRLレジスタによってPIOが使用するGPIOを指定できることになっています。ここでyは0から3までの番号で、PIOの中のステートマシンの番号を示します。

PIOx:SMy_PINCTRLによる入出力マッピング

RP2000シリーズのSoCマイコンは、GPIOを32本持っています。PIOのステートマシンが利用できるのはこのうち連続するピンだけであり、ステートマシンごとにGPIOのベースビット番号を指定して、最大何本を利用するか指定します。なお、この本数はPIOのIN, SET, OUT命令で異なります。また、使用するピンについては各命令間でオーバーラップも可能です。

この、「オーバーラップが可能」と言う時点で(?)となるわけですが、ひとまず置いておきます。

例として挙げるとPIOのOUT命令は次の二つのフィールドで利用するGPIOピンを指定します。

  • PIOx:SMy_PINCTRL.OUT_BASE
  • PIOx:SMy_PINCTRL.OUT_COUNT

OUT_BASEはPIOのOUT命令が利用するGPIOピンのうち、一番小さな番号のものを指定します。OUT_COUNTには連続して何本を利用するかを指定します。

こうしてPIOx:SMy_PINCTRLレジスタで入力ピンと出力ピンを指定すればめでたくPIOでIOを使えるようになると考えるのはごく自然なことです。が、そうは問屋が卸しません。

データシートには明記していませんが、どうやらこれらのフィールドはPIO命令のオペランドとGPIOピンの間のマッピングを決めるものでしかなく、ピン自体の入出力制御には無関係のようです。

SET命令による入出力方向制御

PIOのSET命令には

SET PINDIRS, VALUE

なるフォーマットがあるのですが、これが何を表すのかはデータシートのインストラクションセットの項(RP2040データシート3.4節)には書いていません。データシートのGPIOマッピング(同3.5.6節)にふんわりと書いていることから類推すると、このPINDIRSこそがデータシートのどこにも表れないPIO方向制御レジスタの名前のようです。実はPINDIRSがレジスタだとはデータシートには書いていないのですが、そう解釈しないと辻褄が合いません。

さて、SET命令は

  • PIOx:SMy_PINCTRL.SET_BASE
  • PIOx:SMy_PINCTRL.SET_COUNT

で表される最大5ビットのフィールドに対して、VALUEで指定する即値を書き込みます。VALUEの各ビットがSET_BASEから始まるGPIOピンの5bitに対応しています。この値がどう働くかも明記されていませんが、ビットが1なら対応するピンが出力に0ならピンが入力になるようです。

と言うことで、どうやらRP2000シリーズのSoCマイコンはCPUコアからPIOの入出力を決めることはできず、PIOのプログラムから直接切り替えるしかないようです。

SDKのIO設定機能(API)について

PIOの命令から入出力を切り替えられることはわかりましたが、実はPIOのプログラムだけで32本のGPIOピンを制御することはできません。

なぜなら以下のレジスタとフィールドはPIOの命令からは変更できないからです。

  • PIOx:SMy_PINCTRL.SET_BASE
  • PIOx:SMy_PINCTRL.SET_COUNT

この猛烈に面倒な問題を解決するために pio_sm_set_consecutive_pindirs() APIが用意された、というのが実情なようです。この関数の中ではCPUコアからPIOx:SMy_PINCTRL.SET_BASEフィールドを書き換えながら、PIOのSET命令で5bitずつ設定を繰り返すことで32bit全体の方向を制御しています。

DIRSレジスタをCPUコアから可視にしてしまえば簡単なのですが、なぜそうしないのでしょう。

まとめ

RasPi Picoに使われているRP2000シリーズでPIOを使う場合、入出力は以下のようにして設定する必要があります。

まず、pio_sm_set_consecutive_pindirs()かその類似APIを使ってPIOに接続されたGPIOの物理的な入出力方向を設定します。

  • pio_sm_set_consecutive_pindirs()
  • pio_sm_set_pindirs_with_mask()

次にsm_config_set_xxx_pins()や類似のAPIを使ってPIOステートマシンの命令とGPIOのマッピングを設定します。

  • sm_config_set_in_pins()
  • sm_config_set_in_pin_base()
  • sm_config_set_out_pins()
  • sm_config_set_out_pin_base()
  • sm_config_set_out_pin_count()
  • sm_config_set_set_pins()
  • sm_config_set_set_pin_base()
  • sm_config_set_set_pin_count()
  • sm_config_set_sideset_pins()
  • sm_config_set_sideset_pin_base()

pio_sm_set_consecutive_pindirs()の注意点

このAPIは内部でステートマシンを1つ使用します。そのため、実行中のステートマシンがある場合にはそれ以外のステートマシンを使うよう気を付けなければなりません。

使用していないステートマシンを選ぶにはpio_claim_unused_sm() APIを使うとよいでしょう。

雑感

PIOの章に書いてあることからは、そこはかとなくハードウェア設計者の気配を感じます。例えばPINDIRSはラッチの名前のように思えますし、GPIO MUXの説明がCPUプラグラマ視点を喪失しています。ハードウェア設計仕様をそのままSoCデータシートにコピペしたと考えれば、PIO設定のわかりにくさも納得できます。

Discussion