cancel
Showing results for 
Search instead for 
Did you mean: 

STM32L476 SAI frame synchronization is incorrect swapped left right if HAL_SAI_Transmit_DMA is called 4 us after rising edge of WS

Posted on October 20, 2016 at 11:45

Hi All,

I have an STM32L476RE connected to an audio codec. The audio codec is the master.SAI1_Block_Ais configured asSAI_MODESLAVE_TX,SAI_ASYNCHRONOUS,SAI_I2S_STANDARD,SAI_PROTOCOL_DATASIZE_16BIT,SAI_STEREOMODE with 2 channels, left and right. My project mostly works. That is, I can play and record audio. However, under certain conditions the frame synchronization gets swapped around so that the left channel data gets played over the right channel instead. As far as I can tell this only happens on the first time that audio is played after a power up. To investigate the cause I created a test case where the STM32L4 powers on, initializes the audio, starts playing a sine tone on the left channel and zero on the right channel, delays for some time and then resets itself withNVIC_SystemReset() so that the test repeats and I can listen to the audio and see how often the problem occurs by playing the left channel tone on the right channel. The frame synchronization was wrong about 1/10 to 1/50 of the tests. Anyway, after trying various things like making sure the FIFO was empty etc I eventually decided to monitor the level of the LRCLK (Left Right CLocK) or Frame Select (FS) or Word Select (WS) or whatever you want to call it, the I2S signal that specifies if the channel is Left (0) or Right (1). I did this because I have also used devices like the STM32F407 for audio, and know that the I2S errata specifies that the I2S should only be enabled when the WS is high.

// Synchronise the call of HAL_SAI_Transmit_DMA to the FS:
while

(hal_gpio_pin_read(&FS_INPUT) == 1) {}
while

(hal_gpio_pin_read(&FS_INPUT) == 0) {}
uint32_t k = 0;
for

(k = 0; k != 40; k ++) {}
hal_gpio_pin_on(&LED6);
HAL_SAI_Transmit_DMA(&hsai_BlockA1, (uint8_t*)(&dac_buf[0]), AUDIO_BUFFER_SIZE);
hal_gpio_pin_off(&LED6);

This means that the call of

HAL_SAI_Transmit_DMA

is synchronised to the FS. This seems to fix the frame synchronization, that is, I have not seen an incorrect synchronization yet.

Using the signal on LED6 I found that the frame synchronization is swapped when

HAL_SAI_Transmit_DMA

is called approximately 4 us after the rising edge of FS. If I adjust the delay in the for loop to achieve this 4 us, then the frame synchronization is mostly wrong: it is wrong in about 8/10 of the tests (compare to above). Is this a known problem? The STM32L476 has no errata on the SAI? To me this looks like it could be a hardware problem, I could reduce my project to a minimal version and post the code if you think this could be a software problem. The reference manual RM0351 says: In slave mode, the audio frame starts when the audio block is enabled and when a start of frame is detected. In Slave TX mode, no underrun event is possible on the first frame after the audio block is enabled, because the mandatory operating sequence in this case is: 1. Write into the SAI_xDR (by software or by DMA). 2. Wait until the FIFO threshold (FLH) flag is different from 000b (FIFO empty). 3. Enable the audio block in slave transmitter mode.

I have tried this sequence by pulling__HAL_SAI_ENABLE out of theHAL_SAI_Transmit_DMA function and first waiting for the fifo threshold to be not empty, but it waits for ever as I suppose the SAI does not send DMA requests before it is enabled, so I don't understand how the above sequence is supposed to work

This actually does work, the problem was that I was waiting for the FIFO to be full but FTH was configured for half full.
2 REPLIES 2
Posted on October 20, 2016 at 12:29

Ok filling the fifo also solves the left / right synchronization:

while
((hsai_BlockA1.Instance->SR & SAI_xSR_FLVL) != SAI_FIFOSTATUS_FULL)
{
hsai_BlockA1.Instance->DR = 0;
}
hal_gpio_pin_on(&LED6);
HAL_SAI_Transmit_DMA(&hsai_BlockA1, (uint8_t*)(&dac_buf[0]), AUDIO_BUFFER_SIZE);
hal_gpio_pin_off(&LED_PLAY);

I don't really want dummy samples though, it would be preferable if the DMA filled FIFO with samples before enabling the SAI, which the reference manual seems to indicate is possible
Posted on October 20, 2016 at 13:05

Ok so it turns out the problem was software related in that the sequence of RM0351 was not followed (wait for FIFO to not be empty before enabling the SAI).

May I suggest the following modification for

HAL_SAI_Transmit_DMA?

/**
* @brief Transmit an amount of data in non-blocking mode with DMA.
* @param hsai: pointer to a SAI_HandleTypeDef structure that contains
* the configuration information for SAI module.
* @param pData: Pointer to data buffer
* @param Size: Amount of data to be sent
* @retval HAL status
*/
HAL_StatusTypeDef HAL_SAI_Transmit_DMA(SAI_HandleTypeDef *hsai, uint8_t *pData, uint16_t Size)
{
if
((pData == NULL) || (Size == 0))
{
return
HAL_ERROR;
}
if
(hsai->State == HAL_SAI_STATE_READY)
{
/* Process Locked */
__HAL_LOCK(hsai);
hsai->pBuffPtr = pData;
hsai->XferSize = Size;
hsai->XferCount = Size;
hsai->ErrorCode = HAL_SAI_ERROR_NONE;
hsai->State = HAL_SAI_STATE_BUSY_TX;
/* Set the SAI Tx DMA Half transfer complete callback */
hsai->hdmatx->XferHalfCpltCallback = SAI_DMATxHalfCplt;
/* Set the SAI TxDMA transfer complete callback */
hsai->hdmatx->XferCpltCallback = SAI_DMATxCplt;
/* Set the DMA error callback */
hsai->hdmatx->XferErrorCallback = SAI_DMAError;
/* Set the DMA Tx abort callback */
hsai->hdmatx->XferAbortCallback = NULL;
/* Enable the Tx DMA Stream */
if
(HAL_DMA_Start_IT(hsai->hdmatx, (uint32_t)hsai->pBuffPtr, (uint32_t)&hsai->Instance->DR, hsai->XferSize) != HAL_OK)
{
__HAL_UNLOCK(hsai);
return
HAL_ERROR;
}
/* Enable the interrupts for error handling */
__HAL_SAI_ENABLE_IT(hsai, SAI_InterruptFlag(hsai, SAI_MODE_DMA));
// Following RM0351 Rev 4 pg 1329:
// 1) Write into the SAI_xDR (by software or by DMA). (We use DMA as this is the HAL_SAI_Transmit_DMA function)
/* Enable SAI Tx DMA Request */
hsai->Instance->CR1 |= SAI_xCR1_DMAEN;
// 2) Wait until the FIFO threshold (FLH) flag is different from 000b (FIFO empty).
// Don't wait for it to be e.g full as SAI might only generate DMA requests up until the FIFO is partially
// full according to the FTH FifoTHreshold bits
while
((hsai->Instance->SR & SAI_xSR_FLVL) == SAI_FIFOSTATUS_EMPTY){}
// 3) Enable the audio block in slave transmitter mode.
/* Check if the SAI is already enabled */
if
((hsai->Instance->CR1 & SAI_xCR1_SAIEN) == RESET)
{
/* Enable SAI peripheral */
__HAL_SAI_ENABLE(hsai);
}
/* Process Unlocked */
__HAL_UNLOCK(hsai);
return
HAL_OK;
}
else
{
return
HAL_BUSY;
}
}

I have moved

__HAL_SAI_ENABLE(hsai);

to after checking that some data has been transferred to the FIFO. A mistake I made before was waiting for the FIFO to be full, but this might never happen depending on the FTH bits, e.g the DMA might only be configured to fill the FIFO up to half full.