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


Raspberry Pi Pico専用のオーディオモジュール Pico Audio PackNuttX for Raspberry Pi Pico から使えるようにします。

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

Raspberry Pi Picoのオーディオ出力

Pico Audio PackPico VGA Demo Base には、オーディオチップとしてTIのPCM5100A DACが搭載されています。このチップは特に複雑な制御は必要なく、ステレオのPCMデータをI2Sで送り込むだけで非常に簡単に音を出すことができます。
一方で、音量や音質の調整、PCM以外のコーデックといった機能は一切持たないので、そういった機能はすべてソフトウェアで実現する必要があります。

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

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

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

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

アプリケーションは /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)
    • NuttXに用意されているドライバをそのまま使用します。
  • オーディオデコーダ (nuttx/audio/pcm_decode.c)
    • 今回サポートするのはPCMデータ(wavファイル)のみです。pcm_decode.cをそのまま使用します。
  • オーディオデバイスドライバ (nuttx/drivers/audio/audio_i2s.c)
    • 前述したとおり、Pico Audio PackのPCM5100A DACはI2Sでのオーディオデータ出力機能のみを持つので、I2Sのみを使用するオーディオデバイスドライバである audio_i2s.c を使用します。
  • バスドライバ (nuttx/arch/arm/src/rp2040/rp2040_i2s.c)
    • 今回、新規に実装した部分です。RP2040 PIOでI2Sインターフェースを実装し、その機能を audio_i2s.c に提供します。
    • PIOのプログラムは、rp2040_i2s.c から呼び出される rp2040_i2s_pio.c が持っています。8bit/16bit PCM、モノラル/ステレオといったオーディオフォーマットごとに異なるPIOプログラムを持っていて、上位層からのフォーマット指定に基づいてPIOのコードを差し替えます。
    • RP2040 PIOの制御のために、rp2040_pio.hrp2040_pio.c が制御用APIを提供します。Pico SDKの hardware_pio API とほぼ同等のAPIを用意しています。

これら各レイヤの初期化は、OS起動時に呼ばれる nuttx/boards/arm/rp2040/common/src/rp2040_i2sdev.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の時と同様に、Audio Pack単体だとUARTが繋げない他、再生したいオーディオデータを置くストレージもないため、今回もブレッドボード上でPicoとAudio Packの各ピンを繋ぐことにします。

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

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

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

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 (接続しない) -

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

ビルドと起動

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>