2020年9月3日木曜日

STM32でDCC/DDCを作る方向で (56) 設計資料 feedback

告知です.
コミケ99にて当社のDDC/DACを頒布いたします.
  日付   2021年12月31日(金) 東地区 テ-40b  東5ホール
  サークル名    bangflat
コミケにお越しの際はお立ち寄りいただけますとありがたいです.
商品紹介ページを作りました.
ーーーー

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);
}

それぞれの関数定義です.wr_ptr、rd_ptrを採取しています.
void TransferComplete_CallBack_HS(void) {
USBD_AUDIO_HandleTypeDef   *haudio;
haudio = (USBD_AUDIO_HandleTypeDef *) PDEV->pClassData;
wr_ptr = haudio->wr_ptr;
rd_ptr = 0;
wrptr_valid = 1;
}

void HalfTransfer_CallBack_HS(void) {
USBD_AUDIO_HandleTypeDef   *haudio;
haudio = (USBD_AUDIO_HandleTypeDef *) PDEV->pClassData;
wr_ptr = haudio->wr_ptr;
rd_ptr = AUDIO_TOTAL_BUF_SIZE/2;
wrptr_valid = 1;
}


ビデオ鑑賞

最後に、write ptrと再生ptrの安定度をビデオで確認します.
リングバッファの0通過時にGPIOをトグルするモニタ信号です.write ptrと再生ptrと音声を表示しています.再生ptrでトリガしています.write ptrがピクピクと動いていますがlockが外れるほどではないです.(条件は384kHz 32bit リングバッファ30720bytes)

かしこ

0 件のコメント:

コメントを投稿