cancel
Showing results for 
Search instead for 
Did you mean: 

Audio streaming clock sync

ajgboomer
Associate III
Posted on February 13, 2013 at 17:00

I am interested in techniques for synchronizing the STMF4 I2S clock with an incoming audio stream on the USB. The clock on the source and the clock on the STM32 board will always be slightly out of sync causing either its DMA to the codec to be ahead or behind the stream. Dropping / stuffing samples or re-sampling the stream are not preferred solutions.

Thanks,

Tony

#stm32f4-i2s #streaming
33 REPLIES 33
ajgboomer
Associate III
Posted on February 15, 2013 at 19:26

Hello LowPower,

That is an interesting point on WinXp support. I would prefer that the Client (STM32) adapt to the Host, but if adaption is handled on the host end for Win7 on up it is sufficient. I will have to check IOS too.

Regards,

AjG

ajgboomer
Associate III
Posted on February 15, 2013 at 19:29

Update: A better link for USB Audio support is in USB.ORG's approved class specification documents section : http://www.usb.org/developers/devclass_docs#approved

FYI,

AjG

tsuneo
Senior
Posted on February 16, 2013 at 12:10

> I think that the host must support the feedback and unfortunately windows XP doesn't support it

Here is an interesting post by a MS engineer, which answered to Async support on Windows.

http://www.freelists.org/post/wdmaudiodev/USB-Audio-synch-mechanism

Date: Tue, 13 May 2003 19:11:09 -0700

 

The ''USB Audio and Windows'' whitepaper (available from

 

http://www.microsoft.com/whdc/hwdev/tech/audio/USBaud.mspx[1]) says:

 

 

''Starting with Windows 98, Usbaudio.sys supported the adaptive and

 

synchronous endpoints, but it did not implement the asynchronous endpoint

 

correctly. Full support for asynchronous endpoints in Usbaudio.sys is

 

plannedfor Windows Longhorn.''

 

 

However, I have implemented async. isoch. synch. mechanism on two different

 

devices - and it has worked perfectly - starting from Windows 98 SE.

 

 

What's not correct about the implementation?

 

 

Thanks.

 

Devendra.

 

 

 

Date: Wed, 14 May 2003 09:37:31 -0700

 

Hello Devendra,

 

 

The error with async endpoints has to do with the timeliness of the

 

updating of the sample rate based on the data from the feedback

 

endpoint. Technically it will work with all OS's back to Win98, but only

 

recently have I fixed the timing.

 

 

Thanks,

 

DJ Sisolak

 

Microsoft Corp.

Tsuneo

tsuneo
Senior
Posted on February 16, 2013 at 13:20

As of synchronization methods, the USB audio spec refers to this chapter of the original USB2.0 spec.

5.12.4.1 Synchronization Type

The outline of Async Sink is here.

                           |

                  Host <---|---> Device

                           |

                                                             Counter

               +---------- synch IN EP <--- shift to    <--- gated by SOF

               |                           10.14 format         ^

               |                                                |

               |                                           Master clock (256fs)

            Feedback                                            |

               |                                                |

               |                                             Divider (/256)

               V                                                |

Audio         rate                                              V

Source ===(conversion)===> isoc OUT EP ===> buffer ==========> DAC ===> Speaker

Above isoc OUT EP provides the audio stream data,

Synch (feedback) IN EP gives the frequency ratio of the sampling clock / SOF timing.

Using this ratio, host tunes the number of samples per packet, so that it matches to the sampling clock on the device.

On the device side, the master clock is fed to a counter on a STM32F4, as its external clock source.

Gated by SOF trigger, the counter value is captured at SOF timing. The difference of two contiguous captures gives the ''ratio'' of the sampling clock and SOF. The firmware puts this ratio to the feedback EP, after shifting it into 10.14 (10.10) fixed-point format.

The principle is so simple.

Of course, the devil is in the details 😉

Tsuneo
ajgboomer
Associate III
Posted on February 26, 2013 at 23:35

The original post was too long to process during our migration. Please click on the provided URL to read the original post. https://st--c.eu10.content.force.com/sfc/dist/version/download/?oid=00Db0000000YtG6&ids=0680X000006I6cL&d=%2Fa%2F0X0000000brs%2FBTwbBjqNDXzhou0ZS5JcmE1_QCMCj0GB.mHz7kblIvw&asPdf=false
jjackbauer7
Associate III
Posted on March 16, 2013 at 00:34

Hi ajg,

While waiting for tsuneo to come back (I hope) let me ask you politely to explain how TIM2 is configured to capture the desynchro in your application?

Thanks in advance.

jjackbauer7
Associate III
Posted on March 22, 2013 at 22:52

Hi all,

The desynchro (delay) must be measured between main APBx clock or I2S clock?

In STM32 I2S has a dedicated PLL that is why APBx clock is different from I2S clock.

If the delay is to be measured is with I2S clock which signal must be considred:

WS: word select

or MCLK: Master clock

or Bit clock

romanetz4
Associate II
Posted on November 06, 2015 at 11:52

Hi, all. Let me continue this discussion, as I haven't found any working example for USB Audio on STM32 with explicit feedback.

First of all, we need to measure SOF to MCLK/256 rate. This is done with TIM2.CCR1 channel. This code [CODE]

//timer2 is clocked via the MCLK frequency and captures its counter value by SOF event

void TMR2_Config(void)

{

GPIO_InitTypeDef GPIO_InitStructure;

// TIM2_CH1_ETR pin (PA.15) configuration

GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;

GPIO_Init(GPIOA, &GPIO_InitStructure);

GPIO_PinAFConfig(GPIOA, GPIO_PinSource15, GPIO_AF_TIM2);

TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;

TIM_ICInitTypeDef TIM_ICInitStructure;

TIM_OCInitTypeDef TIM_OCInitStructure;

/* Enable the TIM2 clock */

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);

TIM_Cmd(TIM2, DISABLE);

/* Time base configuration */

  TIM_TimeBaseStructure.TIM_Period            = 0xffffffff;

  TIM_TimeBaseStructure.TIM_Prescaler         = 0;

  TIM_TimeBaseStructure.TIM_ClockDivision     = 0;

  TIM_TimeBaseStructure.TIM_CounterMode       = TIM_CounterMode_Up;

  TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;

    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);

    //clock TIM2 via ETR pin

    TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0);

    /* TIM2 input trigger selection */

    /* At leading edge of SOF signal we need to capture TIM2 value into CCR1 register and reset TIM2 counter itself.

In the SOF interrupt handler capture flag is reset and value from TIM2 CCR1 register is accumulated to compute feedback value. As according to the standard, explicit feedback value contains ratio of sample rate to SOF rate and should be sent in 10.14 format, it is accumulated during 2^FEEDBACK_RATE periods. To compute feedback value it should be shifted left by 6 digits (due to the MCLK to sample rate 256 times ratio). */

    /* Enable capture*/

    TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;

    TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;

    TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_TRC;

    TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;

    TIM_ICInitStructure.TIM_ICFilter = 0;

    TIM_ICInit(TIM2, &TIM_ICInitStructure);

    //program TMR2 to USB SOF capture 

    TIM_RemapConfig(TIM2,TIM2_USBFS_SOF);

    TIM_SelectInputTrigger(TIM2, TIM_TS_ITR1);

    TIM_SelectSlaveMode(TIM2,TIM_SlaveMode_Reset);

    TIM_Cmd(TIM2, ENABLE);

}

[/CODE]
romanetz4
Associate II
Posted on November 07, 2015 at 07:17

At the SOF handler, we need to place feedback calculating routine. Maybe like this:

[CODE]

static uint8_t  usbd_audio_SOF (void *pdev)

{     uint8_t res;

static uint16_t n;

  /* Check if there are available data in stream buffer.

    In this function, a single variable (PlayFlag) is used to avoid software delays.

    The play operation must be executed as soon as possible after the SOF detection. */

if (usbd_audio_AltSet==1)

{

if (PlayFlag)

  {

/* Start playing received packet */

    res=AUDIO_OUT_fops.AudioCmd((uint8_t*)(IsocOutRdPtr),  /* Samples buffer pointer */

                            AUDIO_OUT_PACKET,          /* Number of samples in Bytes */

                            AUDIO_CMD_PLAY);           /* Command to be processed */

    /* Increment the Buffer pointer or roll it back when all buffers all full */  

IsocOutRdPtr += AUDIO_OUT_PACKET; //48 samples*4bytes/sample=192 bytes

    if (IsocOutRdPtr >= (IsocOutBuff + (AUDIO_OUT_PACKET * OUT_PACKET_NUM)))

    {/* Roll back to the start of buffer */

      IsocOutRdPtr = IsocOutBuff;

    }

  };

accum+=(TIM2->CCR1);

SOF_num++;

if ((!flag))

{

if (SOF_RATE>6)

{feedback_data+=accum>>(SOF_RATE-6);}

else

{feedback_data+=accum<<(6-SOF_RATE);};

feedback_data>>=1;

DCD_EP_Tx (pdev, AUDIO_IN_EP, (uint8_t *) &feedback_data, 3);

accum=0;

flag=1;

SOF_num=0;

};

  }

return USBD_OK;

}

static uint8_t  usbd_audio_DataIn (void *pdev, uint8_t epnum)

{

if (epnum == (AUDIO_IN_EP&0x7f))

{

flag=0;

SOF_num=0;

}

return USBD_OK;

}

uint8_t tmpbuf[AUDIO_OUT_PACKET+16] __attribute__ ((aligned(4)));

static volatile uint16_t rest;

static volatile uint16_t max_length;

static uint8_t  usbd_audio_DataOut (void *pdev, uint8_t epnum)

{     

 uint16_t curr_length;

 uint8_t flag;

 uint16_t curr_pos,rest;

 static uint16_t tmp;

if (epnum == AUDIO_OUT_EP)

  {

 curr_length=USBD_GetRxCount (pdev,epnum);

  DCD_EP_PrepareRx(pdev,

                    AUDIO_OUT_EP,

                    (uint8_t*)tmpbuf,

                    curr_length);

 curr_pos=(IsocOutWrPtr-IsocOutBuff);

 rest=(AUDIO_OUT_PACKET * OUT_PACKET_NUM)-curr_pos;

 if (curr_length<AUDIO_OUT_PACKET) {STM_EVAL_LEDToggle(LED3);};

 if (curr_length>AUDIO_OUT_PACKET) {STM_EVAL_LEDToggle(LED5);};

 if (rest<curr_length)

 { 

 if (rest>0)

 {memcpy((uint8_t*)IsocOutWrPtr,tmpbuf,rest);

 IsocOutWrPtr = IsocOutBuff;};

 if ((curr_length-rest)>0)

 {memcpy((uint8_t*)IsocOutWrPtr,tmpbuf+rest,curr_length-rest);

 IsocOutWrPtr+=curr_length-rest;};

 }

 else

 {

 if (curr_length>0)

 {memcpy((uint8_t*)IsocOutWrPtr,tmpbuf,curr_length);

 // Increment the Buffer pointer

 IsocOutWrPtr += curr_length;};

   }

 //roll it back when all buffers are full

 if (IsocOutWrPtr >= (IsocOutBuff + (AUDIO_OUT_PACKET * OUT_PACKET_NUM)))

 IsocOutWrPtr = IsocOutBuff;

    /* Toggle the frame index */

    ((USB_OTG_CORE_HANDLE*)pdev)->dev.out_ep[epnum].even_odd_frame =

      (((USB_OTG_CORE_HANDLE*)pdev)->dev.out_ep[epnum].even_odd_frame)? 0:1;

    /* Prepare Out endpoint to receive next audio packet */

   /* DCD_EP_PrepareRx(pdev,

                     AUDIO_OUT_EP,

                     (uint8_t*)tmpbuf,

                     196);

*/

    /* Trigger the start of streaming only when half buffer is full */

    if ((PlayFlag == 0) && (IsocOutWrPtr >= (IsocOutBuff + ((AUDIO_OUT_PACKET * OUT_PACKET_NUM) / 2))))

    {

      /* Enable start of Streaming */

      PlayFlag = 1;

      //DCD_EP_Tx(pdev,AUDIO_IN_EP,(uint8_t*)&feedback_data,3);

    }

  }

  return USBD_OK;

}

[/CODE]

romanetz4
Associate II
Posted on November 07, 2015 at 07:29

Changing the MCLK frequency, you can see that host changes real sample rate of incoming data and LEDs flash more or less frequently.

The only issue I have now is to determine ODD/EVEN PID of data IN token for async feedback EP.