Raspberry Pi Zeroでmpd_gui改を動かす手順
拙作のVolumio2特化対応したmpd_gui改をRaspberry Pi Zeroで動かす手順の紹介です。
blue-7さんの公開されているLCDにmpd/volumioの曲名情報を表示するアプリmpd_gui(mpd or volumio対応)をカスタムしたものになります。
現時点ではWaveShare 1.3" 240x240 LCD HATとRaspi DAP Base(じんそんさんよりリリース予定)に対応しています。WaveShare 1.3" 240x240 LCD HATはI2S信号ラインとキーの配線がぶつかってるた2か所カット・ジャンパーの必要があります。
mpd_gui改の変更点とか
- Volumio2とSocket.ioで通信
- CPU負荷を多少低減
- メニューシステムの追加
- キュー、プレイリスト、ミュージックライブラリの操作を追加
- Alsa Mixer制御を追加(デジフィルなどDACの設定変更用)
- LCD Sleep機能の追加(一定時間無操作でSPI通信をOFF)
現時点での制約事項など
- volumio2のみ対応です。(将来的にはmpdにも対応はしたい)
- GPIOは5ボタンまで対応
- dtoverlayの変更はできません
- volumio-2.513-2018-12-07-pi.imgで動作確認しました
WaveShare液晶の改造
- KEY1とJoystick DownがI2S信号とぶつかってるので配線カットして下記のポートに繋ぎ変えてください
- KEY1 P21 -> P17
- Joystick Down P19 -> P22
build手順
準備(prepare)
sudo apt update sudo apt install build-essential cmake libboost-system-dev libboost-date-time-dev libboost-random-dev libssl-dev raspi-config libcv-dev libopencv-dev opencv-doc fonts-takao-gothic libtag1-dev libcurl4-openssl-dev git clone --recurse-submodules https://github.com/socketio/socket.io-client-cpp.git cd socket.io-client-cpp cmake ./ make clean make cd .. git clone https://github.com/tkztkztkz/NanoPi-NEO.git cd NanoPi-NEO/ git checkout SUPPORT_VOLUMIO
ビルド例 WaveShare IPS 1.3 240x240 KEYボタンを3ボタンとして使用
make -f makefile.rpiz DISPTYPE="-DDISPLAY_13IPS240240_RPI -DGPIO_BUTTON_POLARITY_INVERT -DGPIO_BUTTON_PREV=17 -DGPIO_BUTTON_NEXT=20 -DGPIO_BUTTON_PLAY=16" TARGET=mpd_gui
ビルド例 WaveShare IPS 1.3 240x240 ジョイスティックを5ボタンとして使用
make -f makefile.rpiz DISPTYPE="-DDISPLAY_13IPS240240_RPI -DGPIO_BUTTON_POLARITY_INVERT -DGPIO_5BUTTON -DGPIO_BUTTON_PREV=5 -DGPIO_BUTTON_NEXT=26 -DGPIO_BUTTON_PLAY=13 -DGPIO_BUTTON_UP=6 -DGPIO_BUTTON_DOWN=22" TARGET=mpd_gui
ビルド例 Raspi DAP Base
make -f makefile.rpiz DISPTYPE="-DDISPLAY_RASPI_DAP_BASE -DGPIO_BUTTON_POLARITY_INVERT -DGPIO_5BUTTON -DGPIO_BUTTON_PREV=24 -DGPIO_BUTTON_NEXT=26 -DGPIO_BUTTON_PLAY=5 -DGPIO_BUTTON_UP=27 -DGPIO_BUTTON_DOWN=22" TARGET=mpd_gui
設定変更 SPIの有効化
- raspi-config
Interfacing Option -> P4 SPI -> Yes
起動手順
mpd_guiを/usr/local/binにコピーして実行権限を付けたうえで、下記のコマンドを/etc/rc.localあたりに放り込んでください。
ビルド例 WaveShare IPS 1.3 240x240 ジョイスティックを5ボタンとして使用
gpio -g mode 5 up gpio -g mode 26 up gpio -g mode 13 up gpio -g mode 6 up gpio -g mode 22 up /usr/local/bin/mpd_gui &
WaveShare IPS 1.3 240x240 KEYボタンを3ボタンとして使用
gpio -g mode 17 up gpio -g mode 20 up gpio -g mode 16 up /usr/local/bin/mpd_gui &
Raspi DAP Base
gpio -g mode 24 up gpio -g mode 26 up gpio -g mode 5 up gpio -g mode 27 up gpio -g mode 22 up /usr/local/bin/mpd_gui &
操作方法
- PREV/PLAY/NEXTは、前曲・再生開始停止・次曲
- UP/DOWNで音量を変更
- PREV/NEXT長押しで音量を連続的に変更
- PLAY長押しでメニューに入ります。長押しでメニューから抜けます
Si5351A Linuxドライバの解析メモ
ドライバの実装仕様
- Linux KernelのCommon Clk Frameworkに則り実装
- Si5351シリーズに対応。最大8ch出力できる。
- 今回はSi5351A 10-MSOP品を制御対象としているの3chでの使い方に絞ってます
- 8ch出力時のclkout6/7のDivider制約にも対応していそう
- dtsでクロックツリーを記述できる(各clkoutのクロックソースなど)
- clocks にリファレンスクロックを指定。clocksは別途fixed-clockで定義しておき参照する。
- silabs,pll-sourceはPLLのソースを指定
- 0でXTAL
- 1でClkin(Variant_Cのみ)
- silabs,multisynth-sourceはMultiSynthのソースを指定
- 0でPLLA
- 1でPLLB or VCXO(Variant_Cのみ)
- silabs,clock-sourceはclkoutにどのMultiSynthをソースにするか指定。
- 0で自動的にclkoutのソースをMultiSynthに結び付ける。clockout_Nに対してMultiSynth_Nを割あてる。
- 1でclkout1-3のソースをMultiSynth_0に結び付け、clkout5-7のソースをMultiSynth_4に結び付ける。clkout0,4には無効
- 2でclkoutのソースをxtalに結び付ける
- 3でclkoutのソースをclkinに直結。Variant_Cのみ有効
- silabs,pll-master 指定したclkoutにPLLの再設定を許可する
- この指定のあるclkoutに周波数設定を行ったとき、PLL設定周波数を算出・設定する。
- silabs,drive-strength Drive Strength指定可(2,4,6,8mA)
- silabs,disable-state Disable State指定可(LOW/HIGH/Hi-Z/Never)
- clock-frequency clkoutの周波数初期値を設定
dts設定方法
- dtsの例
/ { clocks { /* 25MHz reference crystal */ ref25: oscillator { compatible = "fixed-clock"; #clock-cells = <0>; clock-frequency = <25000000>; }; }; }; &i2c0 { status = "okay"; si5351: clock-generator { compatible = "silabs,si5351a-msop"; reg = <0x60>; #address-cells = <1>; #size-cells = <0>; #clock-cells = <1>; /* connect xtal input to 25MHz reference */ clocks = <&ref25>; clock-names = "xtal"; /* connect xtal input as source of pll0 and pll1 */ silabs,pll-source = <0 0>, <1 0>; clkout0 { reg = <0>; silabs,drive-strength = <8>; silabs,multisynth-source = <0>; silabs,clock-source = <0>; silabs,pll-master; silabs,disable-state = <2>; clock-frequency = <22579200>; }; clkout1 { reg = <1>; silabs,drive-strength = <8>; silabs,multisynth-source = <0>; silabs,clock-source = <0>; silabs,disable-state = <2>; clock-frequency = <2822400>; }; clkout2 { reg = <2>; silabs,drive-strength = <8>; silabs,multisynth-source = <0>; silabs,clock-source = <0>; silabs,disable-state = <2>; clock-frequency = <44100>; }; }; };
使い方
- pll-masterを付加したclkをclk_set_rateすると、PLLまで設定が走る
- pll-masterが無いclkはclk_set_rateすると、そのときのPLL rateからMultiSynthとR Divで誤差の小さくなるクロックを作る
- このdtsの場合clkout0 -> clkout1/2の順で設定する必要がある
- clkout0にmclkを。clkout1にbclk,clkout2にlrclkを割り当てた。
- clock-cells = <1>なので下記のように参照する。2番目の値がclkoutのreg値を指す
clocks = <&si5351 0>, <&si5351 1>, <&si5351 2>;
備考
- 最初のクロック設定で正しく設定しているのにクロックが出ない場合
- 初回のPLL設定シーケンスに問題がありそう
- device-treeに何でも良いので初期クロック(clock-frequency)を書いておくことで回避可能です
- NEO2用Audio Kernelでは下記2点の修正を加えています
- xtal_cl設定機能
- device treeより水晶の容量負荷を変更できるようにしています
- driver init時にクロック出力をPowerDownに設定
- driver init時にクロック出力状態の場合、不安定になるケースが見られたので修正しています。
- xtal_cl設定機能
Si5351Aを使って簡易I2Sマスタを作ってみる
外部クロックがらみでいろいろ調べていたところ、秋月電子通商でSilicon Labsの3CHクロックジェネレータSi5351Aモジュールを見つけたので、これでI2Sマスタを作ってみた。
クロック出力をちょうど3系統持っているので、MCLK/BCLK/LRCLKの3種を生成してみる。
ポイント
- Si5351Aモジュール搭載の25MHzクロックから計算上の誤差やAccumulated Jitterなしで44.1kHz/48kHz系のクロックを生成できる。
- 通常SoCやDAI/DAC内部のDividerで生成するLRCLKまで低Jitterのクロック出力で賄えるのが特徴かな?
- LRCLKで出力タイミングを作っているDACには効果があるかもしれない
- SoC内蔵のDividerよりSi5351AのDividerのほうがJitterが少ないと仮定してですが
- 内蔵PLLのAccumulated Jitterがそもそも多いRaspberry Piにも良さそう。SoCが出力できないMCLKも生成できる。
- RPiの出力はAccumulated Jitterと呼ばれる計算上の辻褄あわせで、時々1周期の長さが可変するJitterが存在する。
- RPiのAccumulated JitterはMASHフィルタを無効化することで抑制できるらしい(未テスト)
- 8Pin DIPモジュール形状で500円とお手ごろ価格。電源とI2Cを繋ぐだけ。
- 自作基板を作るならSi5351A 110円くらい+水晶 100円くらいかな?
- 使用上の注意点としては、必ずVDDOを先かVDDと同時に立ち上げること。VDDOを供給せずVDDを入れると壊れることもあるようです。
- チップが非常に弱いので逆接や電源シーケンスを守らないと簡単に壊れます
実装方法
ハードウェア
結線について
- POWER
- VCORE/VOUTに3.3Vを供給する
- GNDを繋ぐ
- I2C
- SCL/SDAをSBCに接続する。NanoPi NEO2の場合にはPU抵抗を忘れずに。
- CLKOUT
- CLK0をMCLK/CLK1をBCLK/CLK2をLRCLKに接続する。
- 実験の様子。上のほうに居る8pin DIP基板が秋月のSi5351Aモジュール。
対応ハードウェア
blue-7さんが頒布しているハードウェアを使うと簡単に動作させることができます。
トップの写真はI2S AUDIO HATに秋月のSi5351Aモジュールを搭載したものです。
ソフトウェア
- Si5351Aドライバはdrivers/clk/clk-si5351.cに居るのでこれを利用
- DAI LINKドライバでSi5351Aのクロックを設定して音を出すようにした
- DAI_FMTはCBM_CFMに設定する(BCLK/LRCLKを外部から供給する)
- 適切にMCLK/BCLK/LRCLKを算出し、Si5351Aドライバに設定(clk_set_rate)
- 適切にfsを算出し、CPU DAIドライバに設定(snd_soc_dai_set_bclk_ratio)
考察とかメモとか
- 音質はIntPLLより向上。
- 水晶発振器をOE切り替えて鳴らすのとどっこいかちょい劣るくらい?
- Si5351Aの発振子の種類で結構音が変わるっぽい
- 秋月のモジュールに載ってる25MHzは割と元気な音がする
- BCLK_RATIO(MCLK/BCLK比)が正しく出力されるので、AK449X以外のMCLKが必要なDACでも問題なく動作可能
- PCM1794(I2S/RJ24)やFN1242A(I2S/RJ24)あたりでも再生できることを確認済み
実験用のカーネルなど
- ソースはgithubにコミット済み github.com
- クロック制御の実体は下記ソース
sound/soc/codecs/i2s_mfdl.c
- Si5351Aの指定は下記dtsを参照のこと
arch/arm64/boot/dts/allwinner/sun50i-h5-nanopi-neo2-i2s_3clk.dts
- 近いうちに対応済みVolumio2イメージを公開予定です
- ラズパイ向けのドライバセットも出せるかも
Si5351Aのスペックをメモ
- Si5351Aは25MHzの水晶振動子を基に2.5kHzから200MHzまでのクロックを生成できる。
- 150MHzを超えるクロックはMultisynth Dividerの設定値に制約が生じるがオーディオ用途では関係なさそう
- 500kHzを下回るクロックはMultiSynth Dividerで生成できないが、Output StageのR Divと併用することで2.5kHzまで下げられる
- PLLは2系統で3ch出力
- 各chにMultisynth DividerとR Divが居て、クロックソースをPLLA or PLLBから選ぶ形式
- オーディオ用途だと基本的にすべてが整数比で割り切れるのでPLLAだけ使えればよい
- ジッタ関連
- Phase Jitterは3.5psだそうな
- Webにしか記述は無い。おそらくTypical
- Period Jitter max 155ps / typ 70ps
- 水晶発振器には及ばないがそこそこ良さそう。
- 京セラのKCシリーズではPhase Jitter max 1.0ps/Period Jitter max 50ps
- Phase Jitterは3.5psだそうな
簡易外部クロック化について 補足
必要なスペック
DAI(I2Sコントローラ)側
- 高いBCLK入力に対応可能であること
- NEO2(H5)では98.304MHzでも動いてた。ただし、12.288MHzより高い周波数はスペック範囲外となる。
- RPi Zero(BCM2835)では24.576MHzまで確認。それ以上は未確認。
- CBM_CFS動作が可能であること
- Codec Bitclk Master/Codec FrameSync Slaveを意味するALSA用語
- SBCのI2Sコントローラが、入力したBCLKからLRCLKを生成・出力できること
- NEO2(H5)/RPi Zero(BCM2835)ともに可能
- 高いBCLK RATIO(BCLK/LRCLK比)を設定可能であること
- 22.5792MHz/44.1kHz再生時には512fs必要
- 45.1584MHz/44.1kHz再生時には1024fs必要
- NEO2(H5)では2048fsまで指定可
- RPi Zero(BCM2835)では1024fsまで指定可
DAC側
- 高いBCLK RATIO(BCLK/LRCLK比)を入力可能であること
- 高い再生周波数を再生できること
- 384kHz/32bit再生可能だと大丈夫そう
- これは44.1kHz再生時でも352.8kHz相当のデータ転送スピードとなるため
- BCLK/MCLK比、LRCLK/BCLK比に制約が無いこと
- AK4495のデータシートを読むと、MCLK/LRCLKについては位相の制約などがあるが、BCLKについての制約は書かれていない
- BCLKをDAC内デジタル信号処理のクロックに使っているとダメな可能性が高い
ソフトウェア
CPU DAI(I2Sドライバ) (改造)
- CBM_CFS動作が可能であること
- NEO2(H5)は機能追加が必要
- RPi Zero(BCM2835)は対応済み
- set_bclk_ratioに対応していること
- NEO2(H5)は機能追加が必要
- RPi Zero(BCM2835)は対応済み
- ハイレートなfslenの計算ができること
- NEO2(H5)は機能追加が必要
- RPi Zero(BCM2835)は対応済み
- CBM_CFS動作が可能であること
DAI LINKドライバ (新規作成)
- 外部クロックの制御(OE制御やSi514などの可変クロック制御)ができること
- BCLK RATIO(fs)を算出の上CPU DAI(I2Sドライバ)に設定すること
その他のDACでの動作
- ES9023(SabreberryDAC ZERO)では512fsで音が出ることを確認
- PCM5102Aでは64fs(384kHz)のみ音が出た。
- 残念ながら512fs動作はできず。
- 128fsから上は無音となる。
- TDA1543/TDA1387T(I2S)では動作せず。TDA1545A(RJ)では512fs動作した。
- 前者はI2Sのため、BCLKのカウンタが溢れたのではないかと推察
- I2SはLRCLKのエッジから1clkディレイして取り込み開始とか、左詰なので量子化bit数分取り込んだらシリパラ止めるかデータラッチしておく必要があるなど制御が複雑
- 後者はRJのためカウンタは不要で、シリパラにジャブジャブデータを突っ込んで、LRCLKのエッジでデータラッチすれば良いからだと推察
実装周りのメモ
- NEO2(H5)とRPi Zero(BCM2835)でFrameLengthの指定方法が違う
- NEO2(H5)では片chのFrameLengthをPeriodとして指定するだけでOK
- I2S(1bit shift)やRJのデータ出力開始位置は自動算出してくれる
- 指定可能なのは8/12/16/20/24/28/32bit
- 言い換えるとデータ出力開始位置を柔軟に指定できない
- 古いDAC ICの18bitなどには対応できない(裏技で対応することはできそう?という情報有り。未確認)
- RPi Zero(BCM2835)では、Lch/Rchのデータ開始位置をbit単位で指定する必要がある
- 例 I2S 64fs の場合 Lch tx_ch1_pos = 1, Rch tx_ch2_pos = 33
- 例 LJ24 64fsの場合 Lch tx_ch1_pos = 0, Rch tx_ch2_pos = 32
- 例 RJ24 64fsの場合 Lch tx_ch1_pos = 8, Rch tx_ch2_pos = 40
- I2S/LJ/RJでそれぞれ計算が必要で面倒だが、データ出力開始位置を柔軟に指定できる
- RJで15bit出力するなど特殊なslotwidthを実現可能。古いマルチビットDAC ICを使用するときにはとても良さそう。
- NEO2(H5)では片chのFrameLengthをPeriodとして指定するだけでOK
AK449Xの簡易外部クロック化について
旭化成のDAC AK449Xシリーズにて、複雑なクロック生成回路ではなく単純な発振器を用いて外部クロック化する手法の紹介。
前回(BCLK/LRCLK出力しかないRaspberry Piで旭化成のDAC IC(AK449xシリーズ)を鳴らす実験 - _tkz_ memo])は外部回路追加無しでしたが今回はちょっとだけ外部回路を追加して、もっと高品質なクロックを供給できないかな?というお話です。
NanoPi NEO2とRaspberry Pi Zeroで動作確認済み
なお、データシート的にはダメとは書いてないように見えるが、イレギュラーなクロック入力になるので、動作保証はできません。
実現方法1 水晶発振器2個用意しOEピンで切り替え
- MCLK=BCLKとし、BCLKをMCLK相当まで上げる
- サンプリング周波数に合わせて22.5792MHz/24.576MHzのどちらかを切り替えMCLK=BCLKとする
- 発振器のOEピンはGPIOで制御
- LRCLKはSBC側I2SブロックでBCLK入力を基に生成する(CBM_CFS動作)
- BCLK RATIO(BCLK/LRCLK比)は下記のような設定になる
- 22.5792MHz/44.1kHz再生時 512fs
- 22.5792MHz/88.2kHz再生時 256fs
- 22.5792MHz/176.4kHz再生時 128fs
- 24.576MHz/48kHz再生時 512fs
- 24.576MHz/96kHz再生時 256fs
- 24.576MHz/192kHz再生時 128fs
- 24.576MHz/384kHz再生時 64fs
実装イメージ
- 使用した水晶発振器はKyocera KC7050
- GPIOでOEを制御
- BCLKに22.5792MHz or 24.576MHzを入力
実現方法2 可変クロックジェネレータを制御
- MCLK=BCLKとし、BCLKをMCLK相当まで上げる
- 可変周波数出力のできるSi514を使用する
- LRCLKはSBC側I2SブロックでBCLK入力を基に生成する(CBM_CFS動作)
実装イメージ
- クロックジェネレータはSilicon Labs Si514
- I2CでSi514を制御
- BCLKには任意のクロックを入力可能だが、デフォルトでは22.5792MHz or 24.576MHzを入力
- BCLK RATIO(BCLK/LRCLK比)は水晶発振器使用の場合と同じ
- 8pin DIPソケットは次回以降に説明する別のお話なので、今のところ無視してください。
メモと考察
- IntPLLに比べると音質は向上
- 比較的ハードウェア実装は簡単
- ソフトウェアは結構面倒だが、DAI LINKドライバ化したので、OEピンはdevice-treeで指定できる
実験用のカーネルなど
- ソースはgithubにコミット済み github.com
- クロック制御の実体は下記ソース
sound/soc/codecs/i2s_mfdl.c
- OE制御ピンの指定、Si514の指定は下記dtsを参照のこと
arch/arm64/boot/dts/allwinner/sun50i-h5-nanopi-neo2-i2s_1clk.dts
ALSA SoCドライバメモ No.004 外部クロック関連
外部クロックを使う場合
- 外部クロックを使うときのクロック制御はDAI LINKに組み込むか、CODECに組み込むか。どちらでも良さそう。
- PCM5122のGPIOとDividerを利用しているHiFiBerry DAC+PROではdai link (sound/soc/bcm/hifiberry-dacpro.c)でやっている
- PCM5122内蔵GPIOの制御はdai linkから子のcodecハンドラを辿ってi2cハンドラを取得し、i2c writeするという離れ業をやっている。あまり真似したくない方法。
- クロックの向きの話
- CBM_CFM/CBM_CFS時のfs指定
- DAI LINK or CODEC to CPU DAI
- snd_soc_dai_set_tdm_slotかsnd_soc_dai_set_bclk_ratio経由で外部から指定できる。
- DAI LINK or CODEC to CPU DAI
- Dai link/codecからCPU DAIに設定可能なCallbackはこの辺。実装はされていないものもある。
- うまく使えば、dai linkからMCLKを指定して、内蔵PLLに設定させるとかもできそう
int (*set_sysclk)(struct snd_soc_dai *dai, int clk_id, unsigned int freq, int dir); int (*set_pll)(struct snd_soc_dai *dai, int pll_id, int source, unsigned int freq_in, unsigned int freq_out); int (*set_clkdiv)(struct snd_soc_dai *dai, int div_id, int div); int (*set_bclk_ratio)(struct snd_soc_dai *dai, unsigned int ratio);
ALSA SoCドライバメモ No.003 callbackシーケンス
ALSA SoCドライバ開発でいちいちgrepするのが面倒な情報をメモしておく
Callbackシーケンスのメモ 制御でよく使いそうなhw_paramsとshutdownのみ
- ポイントは開始時はdai_link->codecs->cpudai。終了時はcpudai->codecs->dai_linkとなること
- codecsが複数ある場合は、hw_params/shutdown両方とも昇順のみ。shutdown時に降順にはしてくれないので注意が必要!
- cpu daiが最後になるので、外部クロック制御はdai_link/codecどちらでやっても大丈夫そう。
- dai_link/codecでfsやclkを決定し、cpu daiに設定
- cpu daiは設定されたfs/clkを元にPLLやDIVを決定し設定
- と、いうことができる。
hw_params call sequence in soc_pcm_hw_params
- dai_link rtd->dai_link->ops->hw_params
- codec for (i = 0; i < rtd->num_codecs; i++) { soc_dai_hw_params }
- cpu dai ret = soc_dai_hw_params(substream, params, cpu_dai);
- platform platform->driver->ops->hw_params