STM32でDCC/DDCを作ろう! INDEXページへ
情報が間違っていても責任とらないし賠償とかしないです.
連載48回でリングバッファについて触れました.STM32の中に設けたリングバッファは、audio data packetをチビチビと時計回りに積んでゆきます.またSPI DMAは一定のペースで時計回りに読んでゆきます.積みをwrite ptr、SPIを再生ptrと下図に描いてあります.
再生ptrの回転速度は水晶精度で一定です.しかしwrite ptrの回転速度はフラフラと変動します.hostがaudio dataを送るレートがフラフラしたり、平均的に遅かったり速かったりするためです.
そこでレート調節機構が必要になります.それがfeedbackです.
USB規格書から読み取れるfeedback仕様は連載26回に書きました.
STM32でDCCを作る方向で (26) feedback完全理解
でもこの仕様通りに実装しても正しく稼働しないと思うんです.仕様を変更してうまくいったのが連載34回でした.
STM32でDCC/DDCを作る方向で (34) feedbackの世界の片隅で
実装したfeedback仕様とは、write ptrが再生ptrよりも半周期先行するようにfeedbackする です.
事情説明は34回を読んでもらうとして、以下ではcodeの実装状況を説明します.
descriptor
feedback EPをdescriptorに記述する必要があります.注意点は、audio data EPとfeedback EPは同じ番号じゃないとダメらしいです.ここではEP1の逆向きなのでEP81になっています.
//-------- Endpoint Descriptor -----------------
// bLength : 0x07 (7 bytes)
// bDescriptorType : 0x05 (Endpoint Descriptor)
// bEndpointAddress : 0x81 (Direction=IN EndpointID=1)
// bmAttributes : 0x11 (TransferType=Isochronous SyncType=None EndpointType=Feedback)
// wMaxPacketSize : 0x0004
// Bits 15..13 : 0x00 (reserved, must be zero)
// Bits 12..11 : 0x00 (0 additional transactions per microframe -> allows 1..1024 bytes per packet)
// Bits 10..0 : 0x04 (4 bytes per packet)
// bInterval : 0x01 (1 ms)
0x07, 0x05, AUDIO_FBK_EP, 0x11, LOBYTE(0x0004),HIBYTE(0x0004), AUDIO_FBK_INTERVAL,
末尾にInterval=1mSecと書かれています.この意味は、1mSec毎にfeedbackを送るよと言ってます.sample codeは4mSecだったのですが、応答速度を速めるため1mSecに変えました.
1mSecはTIM3の1mSec割り込みで
1mSecはTIM3に作ってもらいます.そしてTIM3割り込みroutine内でfeedbackを送信しています.
void TIM3_IRQHandler(void) {
if(wrptr_valid==1) {
if ( rd_ptr == 0 ) wr_adv = wr_ptr - AUDIO_TOTAL_BUF_SIZE/2;
else if( wr_ptr <= AUDIO_TOTAL_BUF_SIZE/2 ) wr_adv = wr_ptr;
else wr_adv = wr_ptr - AUDIO_TOTAL_BUF_SIZE;
wrptr_valid=0;
}
uint32_t fb;
fb = fbc - wr_adv/3;
buf_feedback[0] = fb; // fractional
buf_feedback[1] = fb>>8; // fractional
buf_feedback[2] = fb>>16; // integer
buf_feedback[3] = fb>>24; // integer
USBD_LL_Transmit(PDEV, 0x81, buf_feedback, 4);
HAL_TIM_IRQHandler(&htim3);
}
前半でごちゃごちゃと計算して得られたwr_advは、write ptrが再生ptrよりも半周期先行していればゼロになります.offsetしていれば正負の数字になります.
fb = fbc - wr_adv/3 はfeedback値を決定します.fbcはfeedbackの標準値です.別の場所で定義されています.-wr_advでfeedback値を補正します.3で割っているのはloopゲイン調整のため.
fbcはこのように定義されています.fbcはサンプリング周波数により変わります.数字の意味は上でリンクした連載26回を読むと解ると思います.
#define AUDIO_Fs44_FB 0x00058333 // 0x00058333
#define AUDIO_Fs48_FB 0x00060000 // 0x00060000
#define AUDIO_Fs88_FB 0x00058333<<1 // 0x000b0666
#define AUDIO_Fs96_FB 0x00060000<<1 // 0x000c0000
#define AUDIO_Fs176_FB 0x00058333<<2 // 0x00160ccc
#define AUDIO_Fs192_FB 0x00060000<<2 // 0x00180000
#define AUDIO_Fs352_FB 0x00058333<<3 // 0x002c1999
#define AUDIO_Fs348_FB 0x00060000<<3 // 0x00300000
最後にUSBD_LL_Transmit()をcallすることによりEP81からfeedback値がhostへ伝達されます.
write ptrと再生ptrを採取するには
再生ptrを知るには割り込みを利用するしかありません.なぜなら、再生はSPI DMAが勝手にやっているので、再生ptrを知る術がないんです.その代わりにSPI DMAは、バッファを半分消化した時点で割り込みをかけてくれます.全部消化したときにも割り込みをかけてくれます.
半分割り込みと全部割り込みのcallback関数をSPIの初期化時に登録します.TransferComplete_CallBack_HS、HalfTransfer_CallBack_HSがそれです.
static void MX_SPI1_Init(void) {
HAL_SPI_RegisterCallback(&hspi1, HAL_SPI_TX_COMPLETE_CB_ID, (pSPI_CallbackTypeDef)TransferComplete_CallBack_HS);
HAL_SPI_RegisterCallback(&hspi1, HAL_SPI_TX_HALF_COMPLETE_CB_ID, (pSPI_CallbackTypeDef)HalfTransfer_CallBack_HS);
}
最後に、write ptrと再生ptrの安定度をビデオで確認します.
リングバッファの0通過時にGPIOをトグルするモニタ信号です.write ptrと再生ptrと音声を表示しています.再生ptrでトリガしています.write ptrがピクピクと動いていますがlockが外れるほどではないです.(条件は384kHz 32bit リングバッファ30720bytes)
かしこ
0 件のコメント:
コメントを投稿