NuttX for Raspberry Pi PicoでPico Audio Packを使う

Raspberry Pi Pico専用のオーディオモジュール [Pico Audio Pack](https://shop.pimoroni.com/products/pico-audio-pack) を [NuttX](http://nuttx.apache.org/) [for Raspberry Pi Pico](https://qiita.com/yunkya2/items/55f1222e8b2b903dd7ff) から使えるようにします。

最近は国内でもいろんな所でPico関連パーツを扱うようになってきましたね。

https://akizukidenshi.com/catalog/g/gM-16237/

https://www.sengoku.co.jp/mod/sgk_cart/detail.php?code=EEHD-5SKG

https://www.marutsu.co.jp/pc/i/40755064/

https://raspberry-pi.ksyic.com/main/index/pdp.id/681/pdp.open/681


# Raspberry Pi Picoのオーディオ出力

[Pico Audio Pack](https://shop.pimoroni.com/products/pico-audio-pack) や [Pico VGA Demo Base](https://shop.pimoroni.com/products/pimoroni-pico-vga-demo-base) には、オーディオチップとしてTIの[PCM5100A DAC](https://cdn.shopify.com/s/files/1/0174/1800/files/pcm5100a_617130f1-79f1-45ac-96bc-a3752b4afa59.pdf?v=1611151321)が搭載されています。このチップは特に複雑な制御は必要なく、ステレオのPCMデータをI2Sで送り込むだけで非常に簡単に音を出すことができます。
一方で、音量や音質の調整、PCM以外のコーデックといった機能は一切持たないので、そういった機能はすべてソフトウェアで実現する必要があります。

RP2040はI2Sインターフェースを持っていませんが、PIO (Programmable I/O) 機能を用いることでソフトウェア的にI2Sでのオーディオデータ転送を行うことができます。GitHub上のRaspberry Pi Pico SDKの追加ライブラリである[Pico extras](https://github.com/raspberrypi/pico-extras)には、PIOでI2Sを実装してオーディオ出力を行う機能が用意されています。

https://github.com/raspberrypi/pico-extras/tree/master/src/rp2_common/pico_audio_i2s

I2Sについては、Wikipediaの以下の記述が参考になります(日本語ページは説明が簡略すぎるので、英語版を参照した方が良いです)。

https://en.wikipedia.org/wiki/I%C2%B2S

# NuttXのオーディオサブシステム

NuttXのオーディオサブシステムの構造はあまりまとまった資料がないようなのですが、全体の構成としてはおおむね下図のようになるようです(各レイヤの名前は適当に付けたもので、正式名称ではありません)。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/642667/72cd645a-f920-b034-6e3c-954553f2d471.png)

アプリケーションは `/dev/audio/` 以下に作られるデバイスノードに対するioctlでオーディオの制御を行いますが、それをNuttXカーネル内の以下のレイヤで処理します。

- /dev/audioドライバ (`nuttx/audio/audio.c`)
    - アプリケーションに対して直接デバイスノードの機能を提供します。
    - 下位層のドライバは、初期化時に `audio_register()` APIでこのレイヤに対して登録を行うことで、アプリケーションにその機能を提供することができるようになります。
- オーディオデコーダ (`nuttx/audio/*_decode.c`)
    - 特定のコーデックで符号化されたオーディオデータを復号し、下位のオーディオデバイスにデータを渡す機能を提供します。 
    - 現在は、PCMデータを処理する`pcm_decode.c`のみが実装されています。主にwavファイルの先頭にあるRIFFヘッダを処理して、ファイルで指定されたサンプリングレートやビット幅等のオーディオパラメータを下位のデバイスに設定します。
- オーディオデバイスドライバ (`nuttx/drivers/audio/*.c`)
    - オーディオ出力を行う音源チップを制御します。
    - 音源チップをハードウェアレジスタで制御できる場合はここで直接行いますが、チップがI2CやSPI等のインターフェースで接続されている場合や、オーディオデータの転送にI2Sを使う場合には、さらに下位にあるバスドライバを使用します。
    - 音源チップによってはオーディオコーデックをハードウェアで持つものがあるので、上位のオーディオデコーダを通らず、直接/dev/audioドライバに登録する場合もあるようです。
- バスドライバ
    - I2S, I2C, SPIなどのバス機能を各SoCごとに固有の実装で実現します。

今回のNuttX for Raspberry Pi Picoへの実装では、これらを以下のように使用します。

- /dev/audioドライバ ([nuttx/audio/audio.c](https://github.com/apache/incubator-nuttx/blob/master/audio/audio.c))
    - NuttXに用意されているドライバをそのまま使用します。
- オーディオデコーダ ([nuttx/audio/pcm_decode.c](https://github.com/apache/incubator-nuttx/blob/master/audio/pcm_decode.c))
    - 今回サポートするのはPCMデータ(wavファイル)のみです。`pcm_decode.c`をそのまま使用します。
- オーディオデバイスドライバ ([nuttx/drivers/audio/audio_i2s.c](https://github.com/apache/incubator-nuttx/blob/master/drivers/audio/audio_i2s.c))
    - 前述したとおり、Pico Audio PackのPCM5100A DACはI2Sでのオーディオデータ出力機能のみを持つので、I2Sのみを使用するオーディオデバイスドライバである `audio_i2s.c` を使用します。
- バスドライバ ([nuttx/arch/arm/src/rp2040/rp2040_i2s.c](https://github.com/apache/incubator-nuttx/blob/master/arch/arm/src/rp2040/rp2040_i2s.c))
    - 今回、新規に実装した部分です。RP2040 PIOでI2Sインターフェースを実装し、その機能を `audio_i2s.c` に提供します。
    - PIOのプログラムは、`rp2040_i2s.c` から呼び出される [rp2040_i2s_pio.c](https://github.com/apache/incubator-nuttx/blob/master/arch/arm/src/rp2040/rp2040_i2s_pio.c) が持っています。8bit/16bit PCM、モノラル/ステレオといったオーディオフォーマットごとに異なるPIOプログラムを持っていて、上位層からのフォーマット指定に基づいてPIOのコードを差し替えます。
    - RP2040 PIOの制御のために、[rp2040_pio.h](https://github.com/apache/incubator-nuttx/blob/master/arch/arm/src/rp2040/rp2040_pio.h)、[rp2040_pio.c](https://github.com/apache/incubator-nuttx/blob/master/arch/arm/src/rp2040/rp2040_pio.c) が制御用APIを提供します。Pico SDKの [hardware_pio API](https://raspberrypi.github.io/pico-sdk-doxygen/group__hardware__pio.html) とほぼ同等のAPIを用意しています。

これら各レイヤの初期化は、OS起動時に呼ばれる [nuttx/boards/arm/rp2040/common/src/rp2040_i2sdev.c](https://github.com/apache/incubator-nuttx/blob/master/boards/arm/rp2040/common/src/rp2040_i2sdev.c) で行っています。
最下位のレイヤであるバスドライバから順次、初期化して上位レイヤにそれを登録して、を繰り返しているのが分かると思います。

```c:rp2040_i2sdev.c
int board_i2sdev_initialize(int port)
{
  FAR struct audio_lowerhalf_s *audio_i2s;
  FAR struct audio_lowerhalf_s *pcm;
  FAR struct i2s_dev_s *i2s;
  char devname[12];
  int ret;

  ainfo("Initializing I2S\n");

  i2s = rp2040_i2sbus_initialize(port);

#ifdef CONFIG_AUDIO_I2SCHAR
  i2schar_register(i2s, 0);
#endif

  audio_i2s = audio_i2s_initialize(i2s, true);

  if (!audio_i2s)
    {
      auderr("ERROR: Failed to initialize I2S\n");
      return -ENODEV;
    }

  pcm = pcm_decode_initialize(audio_i2s);

  if (!pcm)
    {
      auderr("ERROR: Failed create the PCM decoder\n");
      return  -ENODEV;
    }

  snprintf(devname, 12, "pcm%d", port);

  ret = audio_register(devname, pcm);

  if (ret < 0)
    {
      auderr("ERROR: Failed to register /dev/%s device: %d\n", devname, ret);
    }

  return 0;
}
```

# 接続

実装したオーディオドライバを使うためのPicoの接続について説明します。
[Pico Display Pack](https://qiita.com/yunkya2/items/2c73efe937e1c83caf2a)の時と同様に、Audio Pack単体だとUARTが繋げない他、再生したいオーディオデータを置くストレージもないため、今回もブレッドボード上でPicoとAudio Packの各ピンを繋ぐことにします。

以下のピン同士を接続します。

|端子名|ピン番号|
|----:|-------:|
|I2S_DATA|12|
|GND|13|
|I2S_BCK|14|
|I2S_LRCK|15|
|3V3|36|

更に、以下の記事で行ったように、microSDカードスロットの各端子を以下のように繋ぎます。

https://qiita.com/yunkya2/items/3eefddb73f1fa0709cb3

|microSDカードスロット|Pico端子|Pico ピン番号|
|:----------:|:-----------:|:-----------------------:|
|DAT2|(接続しない)|-|
|CD/DAT3|GP17 (SPI0 CSn)|22|
|CMD|GP19 (SPI0 TX)|25|
|VDD|3V3|36|
|CLK|GP18 (SPI0 SCK)|24|
|VSS|GND|3, 38 などのGNDピンのどれか|
|DAT0|GP16 (SPI0 RX)|21|
|DAT1|(接続しない)|-|
|カード検出スイッチB|(接続しない)|-|
|カード検出スイッチA|(接続しない)|-|

以下のような接続になりました。

https://twitter.com/yunkya2/status/1381630997662294017

# ビルドと起動

NuttXのコンフィグレーション `raspberrypi-pico:audiopack` を指定してビルドします。

```
$ git clone https://github.com/apache/incubator-nuttx.git nuttx
$ git clone https://github.com/apache/incubator-nuttx-apps.git apps
$ cd nuttx
$ ./tools/configure.sh raspberrypi-pico:audiopack
$ make
```

起動すると、NuttX appsに用意されている音楽プレイヤーである `nxplayer` が利用可能になります。
以下のように、microSDカードに置いておいた wav ファイルを再生できます。再生中の一時停止、再開や再生停止に対応しています。

```
NuttShell (NSH) NuttX-10.0.1
nsh> ls /dev/audio
/dev/audio:
 pcm0
nsh> nxplayer
NxPlayer version 1.05
h for commands, q to exit

nxplayer> play 01.wav
nxplayer> stop
nxplayer> play 02.wav
nxplayer> pause
nxplayer> resume
nxplayer> stop
nxplayer>
```