LoginSignup
5
9

More than 3 years have passed since last update.

Raspberry Piで、DACチップ無しでI2Sを出力する

Last updated at Posted at 2020-05-09

またニッチ

Raspberry Piは、GPIOに外付けDACを接続して高音質化という楽しみ方がある。この場合、DACのドライバーとセットで使用することになる。
もしDAC無しでI2S信号を得たい場合、もしくはDACの制御は別に行う場合、合うドライバーが無い。合わないドライバーを使うと、DACの制御に失敗してエラーになったりしないだろうか。
外付けDAC無しで、Raspberry PiをI2S信号発生器として使いつつ、BCLKなどのパラメーターを操作したい場合、どうすれば…
試したことを記載したが、専門外なので、間違っていたらすいません。
以下、Raspberry Pi Foundation-provided kernelの4.9.118で確認。

ALSAドライバーの構造

本家で、CPU, dai, codecなどの構成要素を知る。
こちらは、本家より図が詳細で、構造体の説明もある。
Raspberry Piでの実装は、こちらが詳しい。

作戦

上記の構造解説の理解に基づき…

  • CPU(SoC) DAIは、Raspberry Pi用のもの(sound/soc/bcm/bcm2835-i2s.c)をそのまま使う
  • DAI linkは、Raspberry Piのカーネルの外付けDAC用の中で、単純なものを流用する
  • CODEC DAIは、Raspberry Piのカーネルの外付けDAC用の中で、I2Cなどによるcodecの制御を伴わないものを流用する

DAI link詳細

sound/soc/bcm/の中を漁ると、rpi-simple-soundcard.cという気になるファイル名のファイルがある。シンプルですか、そして比較的新しい。
こちらによると、同じcodecが複数のHATで使われて重複コードになったりしたので、整理したようだ。
これを改造しよう。

CODEC DAI詳細

sound/soc/codecs/以下を眺めると、I2Cなどでcodecの制御を伴わないものは色々あるようだ。
この中に、bt-sco.cというものがある。実は今回、受け側はBluetoothチップを想定しているので、都合が良さそうだ。Kconfigの説明でもDummy BT SCO codec driverとあり、codecが無くても動きそうな予感。
これをそのまま使ってみよう。

Device Tree

Raspbrerry Pi用外付けDACは、使う時に/boot/config.txtdtoverlay=でdtboファイルを追記するものが多い。dtboは、Device Tree Blob Overlayのこと…だと思う。
Device Tree Overlayはこちらに概要がある。
rpi-simple-soundcard.cの中で、bt-scoを使うものは無いので、この組み合わせをDevice Tree Overlayに登録するのが流儀か。

Device Tree詳細

現状Raspberry Piでは、Pi 4でも、ビルド時のKERNELに設定する値はv7lで、arm64ではなくarmで動かしている模様
arch/arm/boot/dts/overlays/hifiberry-dac-overlay.dtsなどを見ると、CODEC DAIとDAI LINKの組み合わせが書いてあるように見える。
これを真似しよう。

実装

Device Tree

arch/arm/boot/dts/overlays/rpi-null-dac-overlay.dts
// Definitions for RPi Null DAC
/dts-v1/;
/plugin/;

/ {
        compatible = "brcm,bcm2835";

        fragment@0 {
                target = <&i2s>;
                __overlay__ {
                        status = "okay";
                };
        };

        fragment@1 {
                target-path = "/";
                __overlay__ {
                        bt-sco {
                                #sound-dai-cells = <0>;
                                compatible = "linux,bt-sco";
                                status = "okay";
                        };
                };
        };

        fragment@2 {
                target = <&sound>;
                __overlay__ {
                        compatible = "rpi,rpi-null-dac";
                        i2s-controller = <&i2s>;
                        status = "okay";
                };
        };
};

CODEC DAIとDAI LINKの名前を、compatibleで指定。

arch/arm/boot/dts/overlays/Makefile
@@ -137,6 +137,7 @@ dtbo-$(CONFIG_ARCH_BCM2835) += \
        rpi-dac.dtbo \
        rpi-display.dtbo \
        rpi-ft5406.dtbo \
+       rpi-null-dac.dtbo \
        rpi-poe.dtbo \
        rpi-proto.dtbo \
        rpi-sense.dtbo \

DAI link

sound/soc/bcm/rpi-simple-soundcard.c
@@ -227,6 +227,28 @@ static struct snd_rpi_simple_drvdata drvdata_merus_amp = {
        .fixed_bclk_ratio = 64,
 };

+static struct snd_soc_dai_link snd_rpi_null_dac_dai[] = {
+       {
+               .name           = "RPi Null DAC",
+               .stream_name    = "RPi Null DAC",
+               .codec_dai_name = "bt-sco-pcm-wb",
+               .codec_name     = "bt-sco",
+               .dai_fmt        = SND_SOC_DAIFMT_I2S |
+                                       SND_SOC_DAIFMT_NB_NF |
+                                       SND_SOC_DAIFMT_GATED |
+                                       SND_SOC_DAIFMT_CBS_CFS,
+               // .dpcm_playback = 1,
+               // .dpcm_capture = 1,
+       },
+};
+
+static struct snd_rpi_simple_drvdata drvdata_rpi_null_dac = {
+       .card_name = "snd_rpi_null_dac",
+       .dai       = snd_rpi_null_dac_dai,
+       .fixed_bclk_ratio = 128,
+};
+
+
 static const struct of_device_id snd_rpi_simple_of_match[] = {
        { .compatible = "adi,adau1977-adc",
                .data = (void *) &drvdata_adau1977 },
@@ -241,6 +263,8 @@ static const struct of_device_id snd_rpi_simple_of_match[] = {
        { .compatible = "rpi,rpi-dac", &drvdata_rpi_dac},
        { .compatible = "merus,merus-amp",
                .data = (void *) &drvdata_merus_amp },
+       { .compatible = "rpi,rpi-null-dac",
+               .data = (void *) &drvdata_rpi_null_dac },
        {},
 };

DTOのcompatibleで指定した名前をsnd_rpi_simple_of_match[]に追記。
そこから、drvdata_rpi_null_dacsnd_rpi_null_dac_daiと情報をリンクさせる構造か。

snd_rpi_simple_drvdata構造体

.fixed_bclk_ratioを設定すると、snd_rpi_simple_init()にて、snd_soc_dai_set_bclk_ratio()でBCLKを設定してくれるようだ。ratioなので、単位はFS?
試しに未設定状態でBCLKを測定したら500KHzで、これを2MHzにしたかったので、2048/16=128にしてみたら、2MHzになったっぽい。デフォルトは32FSということ? 参考

snd_soc_dai_link構造体

CODEC DAIの指定と、どちらがクロックのマスターになるか、などの設定ができる。

CODEC DAIの指定は、.codec_dai_nameに名前を書く。
他の例では、snd_soc_dai_driver構造体の.nameと一致させているようなので、sound/soc/codecs/bt-sco.cにあるbt-sco-pcm-wbにしてみる。wbはWide Bandで、16KHzも行けそう。

クロックの設定は、include/sound/soc-dai.hによると、This is wrt the codec, the inverse is true for the interface i.e. if the codec is clk and FRM master then the interface is clk and frame slave. とのことで、codecから見ての設定値らしい。UARTのTX/RXもそうだが、どちらから見て、が重要。
SND_SOC_DAIFMT_CBS_CFSとしておけば、codecから見てSlave、CPU(SoC)から見てMaster、になるはず。

以下蛇足。

SND_SOC_DAIFMT_GATEDは、多分不要。最初DTOの記述が間違っていてBCLKが出ない時に試行錯誤で入れた名残り。DAI bit clocks can be be gated (disabled) when the DAI is not sending or receiving PCM data in a frame. This can be used to save power.とのこと。
Bluetoothの場合、I2SのBCLKは、SCO通信中のみ出ることが多い気がするので、このままでいいか。

.dpcm_playback.dpcm_captureも試行錯誤の名残りで、同じbt-scoを使ったsound/soc/mediatek/mt2701/mt2701-cs42448.cに記述があったため、入れてみた。
結果的には、この設定が無くても、入出力共に動作。bt-sco.c.playback.captureがあるからか。

CODEC DAI

arch/arm/configs/bcm2711_defconfig
@@ -1506,3 +1506,4 @@ CONFIG_FUNCTION_PROFILER=y
 CONFIG_KGDB=y
 CONFIG_KGDB_KDB=y
 CONFIG_KDB_KEYBOARD=y
+CONFIG_SND_SOC_BT_SCO=m

bt-scoをそのまま使いたいが、Raspberry Piでは無効になっているので、arch/arm/configs/bcm2711_defconfig(Raspberry Pi 4の場合)で有効化。

ビルド

ビルドは、本家手順そのまま。今回はクロスでビルド。
この手順は、

  • make modules_installによる.koのインストール
  • cp arch/arm/boot/dts/overlays/*.dtb* mnt/fat32/overlays/によるDTOのインストール

を含んでいる。素晴らしい。

コンテンツ

適当にフリー素材のwavを持ってきて、Audacityで、CODEC DAIであるbt-sco-pcm-wbに合わせて、モノラル/16kHz/16bitに変換。
VLCのメディア情報で、再生状態にして上記を確認。codecはPCM S16 LEとある。aplayの振る舞いまでは確認していないが、bt-sco-pcm-wbのSNDRV_PCM_FMTBIT_S16_LEと一致していれば、問題になることはないだろう。

実行

config.txt

実行前に、/boot/config.txt[all]の下に、dtoverlay=rpi-null-dacでDTOを追記。

dmesg

[    3.041860] snd-rpi-simple soc:sound: bt-sco-pcm-wb <-> fe203000.i2s mapping ok

aplay -L

default:CARD=sndrpinulldac
    snd_rpi_null_dac, RPi Null DAC bt-sco-pcm-wb-0
    Default Audio Device

aplay

aplay -D default:sndrpinulldac mono-16KHz-16bit.wav

起動時にデバイスの優先順位?が変わることがあるようなので、-Dでデバイスを指定。

…と、ここで問題発生。再生失敗。

aplay: main:828: audio open error: Invalid argument

dmesgを見ると、

[   24.194217] ASoC: bt-sco-pcm-wb <-> fe203000.i2s No matching channels

チャンネルがおかしいらしい。
コンテンツはAudacityモノラル化しており、bt-sco.cでは、channels_minchannels_max1になっているので、問題なさそうだが…。
試しにchannels_max2にしてみる。

sound/soc/codecs/bt-sco.c
@@ -48,14 +48,14 @@ static struct snd_soc_dai_driver bt_sco_dai[] = {
                .playback = {
                        .stream_name = "Playback",
                        .channels_min = 1,
-                       .channels_max = 1,
+                       .channels_max = 2,
                        .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
                        .formats = SNDRV_PCM_FMTBIT_S16_LE,
                },
                .capture = {
                         .stream_name = "Capture",
                        .channels_min = 1,
-                       .channels_max = 1,
+                       .channels_max = 2,
                        .rates = SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_16000,
                        .formats = SNDRV_PCM_FMTBIT_S16_LE,
                },

結果

i2s.png

わーい!

おまけ

GPIO20(IN)とGPIO21(OUT)を直結して、再生中に

arecord -D default:sndrpinulldac rec.wav

で録音したら、録音もできました。

今後

  • ふつーに48KHzステレオとかハイレゾとか
  • モノラル片側1ch出力
  • ユーザー空間からの動的設定変更
  • てきとーなDACを指定すればふつーに動いたんじゃ疑惑
5
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
9