どもです。
FPGAのみならずマイコンでもオーディオ信号処理やってみたいなと思い、Raspberry Pi Picoを触り始めています。
で、オーディオ信号のやり取りにご多分に漏れずI2Sを使うわけですが、Raspberry Piのマイコン(RP2040/RP2350)にはI2Sがペリフェラルとして存在せず、開発者がProgrammable IO(PIO)を使用してペリフェラルを構築する必要があります。
ということで、Raspberry Pi Pico (RP2040)を使ってPIOで遊んでみました。
Programmable IOとは
直訳すれば「プログラム可能なIO」となりますが、実態はRP2040/RP2350のCPUコアとは分離されたステートマシンとFIFOなどから構成されるIO制御専用のハードウェアです。

一つのPIOブロックあたりに4つのステートマシン、4ペアの送受信用FIFO、命令メモリと割り込みから構成されます。

ステートマシンは2つのスクラッチレジスタ、入力用/出力用シフトレジスタ、プログラムカウンタ、分周器、命令制御ロジックで構成されます。ふむふむ。
CPUを介さずにステートマシンにメモリ(レジスタ)とI/Oのやり取りを任せるというのは、DMAに似たサムシングを感じますね。
PIOのプログラム
で、ここからPIOならではの難しさというか奥深さに触れていくことになるのですが、なんとPIO自体のプログラムは最大32命令のアセンブリ言語(C/C++で開発する場合)になります。
最大32命令で各種インターフェイスを実装する必要があるということです。
ただし、ここに繰り返し実行される命令はカウントされないので、この「繰り返し」を使って上手に命令をまとめる必要がありそうです。
とりあえずLチカさせてみる
なんかよくわからん部分も多いのでとりあえずサンプルコードをいじってPIOでLチカしてみます。
今回はVScode上にRaspberry Pi Pico開発プラグインが構築されている前提で進めます。
「New C/C++ Project」を選択し、

今回はプロジェクト名を「pio_led」としました。
Features一覧から「PIO interface」を選択します。
stdio supportはRaspberry Pi Pico単体でも見ることができるように「Console over USB」にしておきます。

なんと、LEDを点滅させるサンプルプログラムが記述された状態でコード一式が出力されます。初心者には大変助かります。

とりあえず実行してみます。
とりあえずPIOでLチカ(デフォでサンプルプログラムが出力されたのでそのまま動かしただけ) pic.twitter.com/lVnqZJhfJx
— AUDIY (@AUDIY14) July 27, 2025
まぁ問題なく動きますね。
アセンブリ言語の本体を見てみましょう。

以下サイトの内容と照らし合わせながら読んでいくと次のようになります。
- TX FIFO上の32bitデータを出力シフトレジスタに格納
- スクラッチレジスタYに出力シフトレジスタ上の32bitのデータを格納
- スクラッチレジスタXにスクラッチレジスタYの値を代入
- 設定したGPIO(今回はLEDにつながるGPIO25)の論理を1に設定
- (ここからlp1)スクラッチレジスタXの値を1減らし、スクラッチレジスタの値が0でなければlp1冒頭に移動
- スクラッチレジスタXの値が0になったら再びスクラッチレジスタXにスクラッチレジスタYの値を代入
- 設定したGPIO(今回はLEDにつながるGPIO25)の論理を1に設定(lp1ここまで)
- (ここからlp2)スクラッチレジスタXの値を1減らし、スクラッチレジスタの値が0でなければlp2冒頭に移動
- スクラッチレジスタXの値が0になったら3番に戻る
フローチャートにするとこんな感じではないでしょうか(noとYesが逆です)。

これを1命令あたり最速1クロックサイクルで実行するというから非常に高速です。もちろん、ステートマシン内部の分周器や各命令に遅延命令を付加することで実行速度を調整できます。
Cプログラムを覗く
さて、アセンブリ言語でプログラムされたPIOはC言語からどのように制御されているか見てみます。
Cプログラムの中でblink_pin_forever関数が呼び出されています。

関数の定義を追っていきます。pio_sm_set_enabled()関数はC/C++ SDK上で定義されているので何も考えないこととし、blink_program_init()関数の定義を追ってみると・・・

blink.pio.hというヘッダファイル内に定義があります。

これですが、定義そのものは自分で書く必要がありそうです。
実はアセンブリ言語の下部に同じ定義が記述されています。おそらくはこれをもとにヘッダファイルが生成されるのだと思います。

このあたりは1からやってみないことにはわからないですね
いちから書いてみる
「見様見真似でいちから書いてみます」ということで書いてみようとしましたが、VScodeのRaspberry Pi Pico開発環境に慣れていないこともあり、非常に時間がかかりそうなので今回はここまでとします。
まずはPIOを使って任意周波数の矩形波を出力できるようにしたいですね。