Select Your Language

免責事項

  • 本サイトの情報の利用、内容、サービスによって、利用者にいかなる損害、被害が生じても、著者は一切の責任を負いません。ユーザーご自身の責任においてご利用いただきますようお願いいたします。

    本サイトで頒布している基板およびキットは、技術者、またはそれに準ずる電気的知識をお持ちの電子工作ファンの方のためのものです。一般のオーディオファンの方のためのものではありません。
    また、頒布基板およびキットは、いかなる条件でも動作を保証するものではございませんので、あらかじめご了承ください。

    電子工作では、火傷、感電、火災などの可能性があります。十分に注意をして作業して下さい。

    営利目的のご使用は認めておりません。 記事の転載や、基板・キットの商用利用の方は、ご連絡ください。学生やサークルの学習目的でまとめてご購入する場合は特別価格でご提供させていただきます。
無料ブログはココログ

スポンサー

« RaspberryPi Pico USB-DDC化? I2S出力調査 | トップページ | Volumio/MoodAudioに秋月電子のI2CタイプOLEDを接続して曲名などを表示するPython3版 »

2022年3月21日 (月)

RaspberryPi Pico USB-DDC化 I2SとMSBファースト後詰め出力対応

RaspberryPi Picoには、PIOというステートマシンを使ったIOポート制御機能があり、マイコン側から見るとハードウェアとして機能するものが搭載されています。PIOは2個。その中にステートマシンが4個づつ入っています。

公式のusb_sound_cardというソフトでもI2S出力にこのPIOを使用しています。

Pico_05

そこで、PIOを改造して、I2SとMSBファースト後詰め(右詰め=Right-Justified)とを一緒に出してしまおうというのが今回の記事の内容です。ソースと、コンパイル済みデータも載せますので興味のある方は読んでみてください。

 

 

3線シリアルのフォーマットの再確認

PCM1795というDAC-ICのデータシートからの抜粋です。

I2srjust_00

MSBファースト後詰(Right-Justified)めは、このようにLRCKの切替りタイミングにLSBが合うようになっています。BCKが32fsの場合、16bitデータで埋まり空きデータがありません。

I2srjust_02

    < 16bitデータ、BCK=32fsの場合 >

LRCKの直後がMSBになるところがポイントです。

 

これに対してI2Sフォーマットは以下のようになっています。

I2srjust_01

LRCKの極性が逆という部分はおいておいて、LRCKのあと1bitおいてからMSBが来るのがポイントです。

つまり、16bit固定という前提条件であれば、I2SとMSBファースト後詰め(右詰め=Right-Justified)とでLRCKの位置をズラすだけで同居できます。

I2srjust_12

図にするとこうなります。

 

それを実現したのが、一番上のオシロ波形です。I2S用のLRCK(ワードクロック)とMSBファースト後詰めのLRCKが別ポートに出力されています。

 

 

 

 

ソース改変

audio_i2s.pioというファイルがpioを制御するソースコードです。以下のようにしました。

 

.program audio_i2s
.side_set 3
                                    ; /---- LRCK(MSB fast)
                                    ; |/--- WCLK(i2s)
                                    ; ||/-- BCLK
bitloop1:                        ; |||
    out pins, 1         side 0b010
    jmp x-- bitloop1 side 0b011
    out pins, 1         side 0b000
public entry_point:
    set x, 14            side 0b001

bitloop0:
    out pins, 1         side 0b100
    jmp x-- bitloop0 side 0b101
    out pins, 1         side 0b110
;public entry_point:    元のエントリポイントの位置

    set x, 14            side 0b111

% c-sdk {

static inline void audio_i2s_program_init(PIO pio, uint sm, uint offset, uint data_pin, uint clock_pin_base) {
    pio_sm_config sm_config = audio_i2s_program_get_default_config(offset);

    sm_config_set_out_pins(&sm_config, data_pin, 1);
    sm_config_set_sideset_pins(&sm_config, clock_pin_base);
    sm_config_set_out_shift(&sm_config, false, true, 32);

    pio_sm_init(pio, sm, offset, &sm_config);

    uint pin_mask = (1u << data_pin) | (7u << clock_pin_base);
    pio_sm_set_pindirs_with_mask(pio, sm, pin_mask, pin_mask);
    pio_sm_set_pins(pio, sm, 0); // clear pins

    pio_sm_exec(pio, sm, pio_encode_jmp(offset + audio_i2s_offset_entry_point));
}

%}

 

という感じで、pioソースを弄りました。side_setを2から3へと増やして別タイミングのLRCKを追加しているだけです。entry_pointの位置を動かしているのは後述します。最初は元の位置で検証しました。

 

audio_i2s.c というソースでもside_setを3に増やす対応が必要でした。

const audio_format_t *audio_i2s_setup(const audio_format_t *intended_audio_format,
                                               const audio_i2s_config_t *config) {
    uint func = GPIO_FUNC_PIOx;
    gpio_set_function(config->data_pin, func);
    gpio_set_function(config->clock_pin_base, func);
    gpio_set_function(config->clock_pin_base + 1, func);
    gpio_set_function(config->clock_pin_base + 2, func);

ソースコードの途中の抜粋です。 赤い行を追加しています。 IOの初期設定です。

これで無事にI2S信号にプラスして別タイミングのLRCKが出力されるようになりました。

 

 

 

アナログ出力波形の確認

ラズパイ公式のソースコードに不具合が残ったままでした。音が出たのでOKにしたのでしょうね。 LRのデータがズレています。

I2srjust_03

1kHz方形波。 黄色がLch。水色がRch。明らかにLchが遅れています。

I2srjust_04

X-Yリサージュ波形。位相が揃っていると斜め一直線になるハズですが・・・

 

webで検索したところ、I2SでLRタイミングずれに気が付いている人もいらっしゃいました。

Raspberry Pi PicoのPIOプログラミング(I2Sインターフェースの実装)

DMAでHalfWordスワップを起こすんだとか。。 よくある32bitマイコンではDMA転送時にバイトスワップとかハーフワードスワップさせるオプションがあったりするのですが、picoの公式資料には何も書いていません。32bitマイコンでDMA転送するときHalfWordスワップが標準とは考えにくいです。16bitバスで転送しているならありえますが。。。

 

バイトスワップさせる命令 channel_config_set_bswap()

というのがあったので、試したらデータが壊れました(笑) やりたいのはHalfWordのswapですからね。当然の結果。

※32bitマイコンなので、Word=32bit。HalfWord=16bit。

 

 

LR位相ズレの原因

一般のPCMデータはLchが先頭で送られることになっています。そのすぐ後ろのRchのデータと合わせて1サンプリング分のデータです。

以下の図は左側がデータの先頭です。1段目は通常のPCMデータの配列。

I2srjust_11

2段目のようにデータのLRスワップが発生すると、LRが逆になって再生されます。この時点では、ただ左右逆なだけです。

 

ああ、左右逆だぁっと単純にLRCK信号を反転させると、3段目のようにRchデータから先に出力され、1サンプリングのペアがズレてしまいLR位相ズレを引き起こします。

 Lの信号はLchから。

 Rの信号はRchから出てる。    よしよし、OK!

と判断を誤ったのがラズパイ公式のソースコードです。惜しい。

 

pioソースを見ても、エントリポイントがデータの最初の部分なのですが、ちょうどI2SのLRCKがHになった所に設定されていました。I2SのLRCK=HはRchを意味します。

Lchから始まるI2Sデータを正常に送るのであればLRCKがLになった所にエントリポイントを置かなくれはいけません。

 

 

 

LR位相ズレ対策

ソースコード上、どこでLRが入れ替わっているのか探したのですが、全く見つかりません。ホントにDMA転送時にHalfWordスワップが起きているのかもしれませんね。

 

数時間、悩んだあげく、

usb_sound_card.c でusbからパケットを受信している部分でLRを入れ換えることにしました。

static void _as_audio_packet(struct usb_endpoint *ep) 関数内の

for (int i = 0; i < audio_buffer->sample_count * 2; i++) {
    out[i] = (int16_t) ((in[i] * vol_mul) >> 15u);
}

という部分を

for (int i = 0; i < audio_buffer->sample_count * 2; i++) {
    out[i+1] = (int16_t) ((in[i] * vol_mul) >> 15u);
    i++;
    out[i-1] = (int16_t) ((in[i] * vol_mul) >> 15u);
}

という感じに16bitデータを入れこにします。 ここ、ボリュームを演算している部分です。

それと、pioのentry_pointの位置を移動してLchからデータが出力されるように変更しました。

 

 

 

I2srjust_05

位相ズレがなくなりました。

I2srjust_06

リサージュもばっちり一直線です。

良かった良かった。

 

 

 

 

R2R-DACの動作確認

というわけで、本題のMSBファースト後詰めR2R-DACでの動作です。

I2srjust_07

とりあえず実験なのでポストフィルタなしで素の出力を観測してみます。

 

I2srjust_08

さすがのNOS-DAC。

 

I2srjust_09

このガタガタ具合はまさにNOS-DACですね。

 

I2srjust_10

インパルスも完ぺきなまでに尖ってます。

R2R-DACというかマルチビットDACの特徴はデルタシグマ方式と違って、その瞬間、瞬間のデータをデコードすることで、前回のデータを一切引きずらない点にあると思います。

 

ただし、オシロで観測していると、5V電源の揺れをそのまま出力に出してしまうようですので、DAC周りの電源をしっかり設計してあげる必要があると感じました。

 

 

 

バイナリデータ&ソースコード

最後にXIAO RP2040のデータを置いておきます。

ダウンロード - usb_sound_card_kai.uf2

BOOTスイッチを押しながらUSBを接続するとフォルダが見えるので、そこに放り込んでください。そうすると勝手にリブートされてUSB-DACとして認識します。

 

改造したソースコードはこちら。

ダウンロード - usb_sound_card.zip

展開して、もとファイルを上書きしてからコンパイルしてください。入っているフォルダはそれぞれ別になってますのでお気を付けください。

 

 

 

ピンアサイン

上記バイナリをXIAO RP2040で使用したときのピンアサインは以下のようになっています。 

Pico_10

 

使ったことないけど、Tiny2040でも同様に出力されると思います。

Pico_11

 

もちろん、すべてのIOがアサインされている公式のpico基板でも出力されます。GPIO 26~29です。

 

picoプログラムのコンパイル

ubuntuにてコンパイルする方法を備忘録的に軽く書いておきます。

・VirtualBOXにubuntuをインストール
  この時点では20.04LTS日本語Remixを使いました。

・pico-SDKなどのインストール
  公式サイトの手順をみてインストール。

2.1 Get the SDK and example

$ cd
$ mkdir pico
$ cd pico
$ git clone https://github.com/raspberrypi/pico-sdk.git --branch master
$ cd pico-sdk
$ git submodule update --init
$ cd ..
$ git clone https://github.com/raspberrypi/pico-extras.git --branch master
$ git clone https://github.com/raspberrypi/pico-playground.git --branch master

2.2. Install the Toolchain

$ sudo apt update
$ sudo apt install cmake gcc-arm-none-eabi libnewlib-arm-none-eabi build-essential

2.3. Updating the SDK

$ cd 
$ cd pico/pico-sdk
$ git pull
$ git submodule update

3.1. Building cmake

$ cd
$ cd pico/pico-playground
$ mkdir build
$ cd build
$ export PICO_SDK_PATH=../../pico-sdk
$ cmake ..

 3.2. Build

$ cd
$ cd pico/pico-playground/build/apps/usb_sound_card
$ make clean       (一度目なら省略)
$ make

これで usb_sound_card.uf2 がコンパイルできます。

・pico実行ファイルを実機に転送

picoのBOOTスイッチを押しながらRESETすると見えるフォルダに.uf2ファイルを入れるとpicoは再起動します。

 

 

今後の展開

クロック設定している箇所とUSBのディスクリプタ設定をどうにかすれば、16bitのままでしたらpioそのままで96kHzや192kHzまで対応可能な可能性が高いです。この価格のマイコン基板でも結構遊べるUSB-DDCになりそうな気配を感じますね。

 

もし興味がありましたら、皆さんも遊んでみてはいかがでしょうか?

私よりも先にハイレゾ出力できるようになった方がいらっしゃったら、ぜひ、ソース公開などお願いします。

 

単なるDDC基板、もしくは秋月R2R-DACを乗せたUSB-DAC基板を作ってみるのも楽しそうです。

 

 

 

« RaspberryPi Pico USB-DDC化? I2S出力調査 | トップページ | Volumio/MoodAudioに秋月電子のI2CタイプOLEDを接続して曲名などを表示するPython3版 »

PCオーディオ」カテゴリの記事

Raspberry Pi pico」カテゴリの記事

コメント

さすが、たかじんさん。現象の確認から対策までが速い。👍

プログラミングからは足を洗った(つもり?)ので、RaspberryPI系オーディオに手を出すことはありませんな。😄

三毛にゃんジェロさん

picoは遊びですが、秋月のR2R-DACを鳴らしたいというのが主な目的です。。。

なにせマルチビットDACですからね。データシートを見ると15bit相当の分解能が出ていそうな雰囲気です。
ざっと見た感じではグリッジノイズも皆無なので、使いやすそうです。

コメントを書く

(ウェブ上には掲載しません)

« RaspberryPi Pico USB-DDC化? I2S出力調査 | トップページ | Volumio/MoodAudioに秋月電子のI2CタイプOLEDを接続して曲名などを表示するPython3版 »

サイト内検索(new)

2025年3月
            1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31