ラズパイPico USB-DDC Feedbackを機能するように実装(3)
こちらの続きになります。
DMAバッファの残量を見ることができるようになったので、あとはfeedbackすればOKです。
ところが、feedbackは少々クセがありました。 実際に動かしてみないと分らないものですね・・・
feedbackの間隔
アイソクロナス転送によりPCMデータは1msに1パケット(48±1サンプル)が送られてきます。 feedbackデータは1msでも返せますが、2msや4ms、8msと2の乗数で設定できます。
詳しくは電子うさぎさんが詳しく解説されています。 あちらはPIC32を使ってUSB2.0 ハイスピード通信ですので、SOFの1msではなくマクロとフレームの125us基準で書かれています。
ラズパイpicoではfeedbackするエンドポイント・ディスクリプタで定義します。
.ep2 = {
.bLength = sizeof(audio_device_config.ep2),
.bDescriptorType = 0x05,
.bEndpointAddress = AUDIO_IN_ENDPOINT,
.bmAttributes = 0x11, // Isochronous Transfer Type,
// Synchronization Type = No Synchronization,
// Usage Type = Feedback Endpoint
.wMaxPacketSize = 3,
.bInterval = 0x01, // 1..225 ms
.bRefresh = 3, // feedback 0:1ms 1:2ms 2:4ms 3:8ms
.bSyncAddr = 0,
},
.bInterval と .bRefresh です。前者は基本1を設定し、後者が2の乗数になります。
.bInterval x 2^(.bRefresh)= 1 x 2^3 = 8ms という具合
色々試してみたところ1ms間隔の方がバッファ量を安定化させやすいようです。
ですが、間隔を広げた方が音が伸びやかで聴き心地が良い事がわかりました。 実際に音を聴きいてチェックすることの重要性がわかりますね。 8msの次は16msですが、さすがに間隔が長すぎてオーバーフロー、アンダーフローが起きやすく安定させるのが難しい。ということで 8msで詰めていこうと思います。
正しくディスクリプタが設定できているのか確認するにはUSBView.exeを使います。
USBView は、Win2000の頃からWindows SDK に付録されているToolです。 20年くらい昔の話ですが、MSDNが会社に転がっていたのでWindows SDKのディスクもあって仕事で使っていました。そういえばCATCのUSBアナライザも棚に置いてありました。
USB2.0が出てきた頃、CATCに代わる物がなく非常に高価な機材だったハズですがサクッと購入して新規案件に備えるという部長判断は、、、感謝しかないですね。
OHCI, UHCI, EHCIでいちいち挙動が違っていて・・・
少し脱線してしまいました。
本線に戻りましょう。
feedback値の生成
まず、元ソースの以下の1行の意味を理解する必要があります。
uint feedback = (audio_state.freq << 14u) / 1000u;
audio_state.freq はサンプリング周波数です。 48kでは48000、44.1kは44100 です。
分りやすいですね。
<<14u は 14bit分左にシフトすることを意味します。 つまり16384 を掛けます。
/1000 は 1000で割っています。 これは1ms間の数値になるかと思われます。
48000 x 16384 ÷ 1000 = 786432
44100 x 16384 ÷ 1000 = 722534.4
なんのこっちゃ?
電子うさぎさん解説によると feedbackは16.16形式の32bit固定小数点だそうです。
hex表記にすると分りやすくなります。
48kHz時 : 0x000C0000
44.1kHz時 : 0x000B0666
赤い部分が整数部、青い部分が小数点以下となります。 分ったような分らないような・・・
USBの規格書もちらっと検索してみたけど、読み解くのに時間がかかりそうな気配です。
**逆から計算しましょう**
48kHz時で1ms間で消費するbit数は
48サンプル x 16bit x 2ch = 1536 bit です。
786432 ÷ 1536 = 512 となります。
という事で、feedback値は 消費するデータのbit数の512倍の数値を返せばよいってことらしいです。
(48サンプル x 16bit x 2ch) << 9 = 0x000C0000
と、ここまで上の元ソース1行から愚直に計算してきたのですが、
目標値から+側リミット、-側リミットを設けて増減分で計算した方が簡単そうです。
ざっくり書くと、
48サンプル中の1サンプル は 2.08 %
45サンプル中の1サンプル は 2.22 % です。
大きすぎて無視されると機能しなくなるので、1%あたりをめどにリミットをかけます。
int32_t fb_limit = (int32_t)(audio_state.freq >> 7);
正確には >>7 は128で割っているので1%ではなく約0.78%ですが、だいたいその辺にリミット値を設けたということです。
(*)掛算や割算より <<, >> というbit shift 演算の方が軽いため組み込みでは多用されます。
ソース改変
以下がフィードバックを返す部分の関数です。 青、赤の行が変更部分。
static void _as_sync_packet(struct usb_endpoint *ep) {
struct usb_buffer *buffer = usb_current_in_packet_buffer(ep);
buffer->data_len = 3;
// feedback =======================
int32_t fb_adj = 0;
int32_t fb_limit = (int32_t)(audio_state.freq >> 7); // limit 1%
fb_adj = (int32_t)(audio_state.freq*0.5 - (queue_level * 1000));
if(fb_adj != 0) fb_adj = fb_adj / 36 ; // K = 1/8 ?
if(fb_adj > fb_limit){ // Feedback adj limit H
fb_adj = fb_limit;
}else if(fb_adj < -fb_limit){ // Feedback adj limit L
fb_adj = -fb_limit;
}
// ==============================
// todo lie thru our teeth for now
//uint feedback = (audio_state.freq << 14u) / 1000u;
uint feedback = ((audio_state.freq + fb_adj) << 14u) / 1000u;
buffer->data[0] = feedback;
buffer->data[1] = feedback >> 8u;
buffer->data[2] = feedback >> 16u;
// keep on truckin'
usb_grow_transfer(ep->current_transfer, 1);
usb_packet_done(ep);
}
バッファ残量の目標値は1ms間のサンプル数の半分 *0.5 としました。 *0.7とか *0.8 にしても良いと思います。
赤文字の行は、フィードバックを8ms間隔に広げているので、8で割るのが適当かと思ったのですが、大きな増減値を返すとハンチングしてしまうことが分ったので増減値が小さくなるよう係数をかけて安定するようにしました。
最適な係数はまだ詰め切れていないかもしれませんが、再生開始から数秒間で目標値に収まることは確認できました。
モニター出力したときのキャプチャ画面。
DMAバッファ残量:フィードバック増減値 です。
24:0 は DMAバッファ残量が24サンプルで、フィードバックの増減値が0であることを示しています。
バッファ残量の目標値は 48kHz÷2÷1000 = 24 なのでバッチしあっています。 時々+27、-27という増減値をフィードバックして調整している様子がわかります。
同じく44.1kHz時のモニターです。
こちらは、44.1÷2=22.05と割り切れないためバッファ残量22でフィードバックの増減値に+1がでて正常です。
ふーぅ。 長い夏休みの宿題が終わったような気分です(笑)
バイナリデータ
現時点でのバイナリを置いておきます。
usb_sound_card28fix_feedback_mclk_led29.uf2
ほぼ音飛びしなくなったはずです。
実機確認
今回もこちらのZOUSANヘッドホンアンプを使用して音の確認をさせて頂きました。
テスト機を長らく貸し出して頂きまして、誠にありがとうございます。
単4のニッケル水素電池でテストを繰り返していましたが、12時間ではすまないくらい電池が持ちます。2台合計で30時間以上テストした中で充電は2回しかしませんでした。 相変わらず飽きのこない音で楽しくデバッグできました。
もし興味のある方は、こちらをご覧ください。
« 禁断のヘッドホンアンプ Single OPAMP版 出荷しました | トップページ | チップ測定ツール基板 »
「Raspberry Pi pico」カテゴリの記事
- RPiZeroRP2040 USB-DDC基板試作(2024.09.01)
- ラズパイPico USB-DDC Feedbackを機能するように実装(3)(2024.08.24)
- ラズパイPico USB-DDC Feedbackを機能するように実装(2)(2024.08.23)
- ラズパイPico USB-DDC Feedbackを機能するように実装(1)(2024.08.22)
- Raspberry Pi Pico 2 発表されました。(2024.08.09)
たかじんさん
> ふーぅ。 長い夏休みの宿題が終わったような気分です(笑)
お疲れ様でした。
私の方も、本業関連著書の執筆がやっと終わり、先々週に脱稿しました。
これで、電気工作に取りかかれるなぁと思っているのですが、講演の準備があったり、疲れがたまっていたりするのか、なかなか取りかかれずにおります。
現在、NNBA-1 を4枚制作途中になっています。
2枚は、VOL-12/01 のプリアンプ用で、残る2枚は、アナログレコードのイコライザアンプの Unbalance-Balance 変換に使います。
また、いろいろお世話になります。 よろしくお願いします。
投稿: n'Guin | 2024年9月 8日 (日) 16時23分