2020-04-17 12:31 AM
Hello,
I am trying to implement I2S protocol for my project with STM32L433RBT6. I should play short (up to 5 seconds) .wav stereo audio samples that are with 44.1 kHz sampling rate and 16 bit audio sample. The I2S line goes into the CS4344 DAC. Thus, my I2S theoretical configuration should look like this:
SCLK - Fs * audio sample in bits * num of channels -> 44100*16*2 = 1,4 MHz
LRCK - Fs -> 44,1 kHz
MCLK - Fs * 256 (according to STM32L4 reference manual) -> 11.3 MHz
SD - slot size is 16 bit, whole audio frame 32 bit, number of slots 2 -> because .wav file audio data is 32 bits per sample i.e. 16 bit for each channel
My RCC configuration is:
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 1;
RCC_OscInitStruct.PLL.PLLN = 20;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV7;
RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
Error_FatalHandler();
}
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK) {
Error_FatalHandler();
}
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART2 | RCC_PERIPHCLK_SAI1 | RCC_PERIPHCLK_ADC;
PeriphClkInit.Usart2ClockSelection = RCC_USART2CLKSOURCE_PCLK1;
PeriphClkInit.Sai1ClockSelection = RCC_SAI1CLKSOURCE_PLLSAI1;
PeriphClkInit.AdcClockSelection = RCC_ADCCLKSOURCE_PLLSAI1;
PeriphClkInit.PLLSAI1.PLLSAI1Source = RCC_PLLSOURCE_HSE;
PeriphClkInit.PLLSAI1.PLLSAI1M = 1;
PeriphClkInit.PLLSAI1.PLLSAI1N = 10;
PeriphClkInit.PLLSAI1.PLLSAI1P = RCC_PLLP_DIV7;
PeriphClkInit.PLLSAI1.PLLSAI1Q = RCC_PLLQ_DIV2;
PeriphClkInit.PLLSAI1.PLLSAI1R = RCC_PLLR_DIV8;
PeriphClkInit.PLLSAI1.PLLSAI1ClockOut = RCC_PLLSAI1_SAI1CLK | RCC_PLLSAI1_ADC1CLK;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK) {
Error_FatalHandler();
Thus, from this configuration I get 1.45% error in LRCLK -> MCLK/256 -> 11.428571MHz/256 = 44631.7Hz. I could reach different error values with differect MCLK/LRCLK ratios that are specified in CS4344 datasheet. Although, I am not sure why STM32L4 ratio is hardcoded 256 ("The audio frame length can be configured to up to 256 bit clock cycles...").
The data from .wav files is read through SPI protocol with FATFS. The SPI is configured as follows:
SPI_MainStruct.Instance = SPI1;
SPI_MainStruct.Init.Mode = SPI_MODE_MASTER;
SPI_MainStruct.Init.Direction = SPI_DIRECTION_2LINES;
SPI_MainStruct.Init.DataSize = SPI_DATASIZE_8BIT;
SPI_MainStruct.Init.CLKPolarity = SPI_POLARITY_LOW;
SPI_MainStruct.Init.CLKPhase = SPI_PHASE_1EDGE;
SPI_MainStruct.Init.NSS = SPI_NSS_SOFT;
SPI_MainStruct.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
SPI_MainStruct.Init.FirstBit = SPI_FIRSTBIT_MSB;
SPI_MainStruct.Init.TIMode = SPI_TIMODE_DISABLE;
SPI_MainStruct.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
SPI_MainStruct.Init.CRCPolynomial = 7;
SPI_MainStruct.Init.CRCLength = SPI_CRC_LENGTH_DATASIZE;
SPI_MainStruct.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;
if (HAL_SPI_Init(&SPI_MainStruct) != HAL_OK) {
Error_FatalHandler();
}
I should have used SDIO instead but hardware is already made so I have what I have.
My whole application uses RTX Kernel with multiple threads. The main thread is the audio and it has highest priority. When conditions are met sound should be played in the output. I use DMA to transfer the I2S data with HAL_SAI_Transmit_DMA function. SAI and DMA confiuration is as follows:
GPIO_InitStruct.Pin = GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF13_SAI1;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_8;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF13_SAI1;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
hdma_sai1_a.Instance = DMA2_Channel1;
hdma_sai1_a.Init.Request = DMA_REQUEST_1;
hdma_sai1_a.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_sai1_a.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_sai1_a.Init.MemInc = DMA_MINC_ENABLE;
hdma_sai1_a.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
hdma_sai1_a.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
hdma_sai1_a.Init.Mode = DMA_NORMAL;
hdma_sai1_a.Init.Priority = DMA_PRIORITY_HIGH;
if (HAL_DMA_Init(&hdma_sai1_a) != HAL_OK){
Error_FatalHandler();
hsai_BlockA1.Instance = SAI1_Block_A;
hsai_BlockA1.Init.AudioMode = SAI_MODEMASTER_TX;
hsai_BlockA1.Init.Synchro = SAI_ASYNCHRONOUS;
hsai_BlockA1.Init.OutputDrive = SAI_OUTPUTDRIVE_DISABLE;
hsai_BlockA1.Init.NoDivider = SAI_MASTERDIVIDER_ENABLE;
hsai_BlockA1.Init.FIFOThreshold = SAI_FIFOTHRESHOLD_EMPTY;
hsai_BlockA1.Init.AudioFrequency = SAI_AUDIO_FREQUENCY_44K;
hsai_BlockA1.Init.SynchroExt = SAI_SYNCEXT_OUTBLOCKA_ENABLE;
hsai_BlockA1.Init.MonoStereoMode = SAI_STEREOMODE;
hsai_BlockA1.Init.CompandingMode = SAI_NOCOMPANDING;
hsai_BlockA1.Init.TriState = SAI_OUTPUT_NOTRELEASED;
if (HAL_SAI_InitProtocol(&hsai_BlockA1, SAI_I2S_STANDARD, SAI_PROTOCOL_DATASIZE_16BIT, 2) != HAL_OK) {
Error_FatalHandler();
}
I can guarantee that ALL needed clocks are up and running. To read and dump data to I2S output I use the TX half complete and TX complete callbacks:
void HAL_SAI_TxHalfCpltCallback(SAI_HandleTypeDef *hsai) {
if (hsai->Instance == SAI1_Block_A) {
Audio_Communication.read_size = eAudio_ReadHalf;
}
}
void HAL_SAI_TxCpltCallback(SAI_HandleTypeDef *hsai) {
if (hsai->Instance == SAI1_Block_A) {
Audio_Communication.read_size = eAudio_ReadFull;
}
}
<...>
if (WAV_FileData.num_of_samples > 0) {
do {
if (Audio_Communication.read_size == eAudio_ReadHalf) {
AUDIO_ReadDataBuffer(Audio_Communication.dma_buffer_ch1_1, pfile);
if (Audio_Communication.read_size != eAudio_ReadFull) {
Audio_Communication.read_size = eAudio_None;
}
}
if (Audio_Communication.read_size == eAudio_ReadFull) {
AUDIO_ReadDataBuffer(Audio_Communication.dma_buffer_ch1_1, pfile);
if (HAL_SAI_Transmit_DMA(SAI_MainStructGet(), Audio_Communication.dma_buffer_ch1_1, Audio_Communication.dma_buffer_ch1_1_size) != HAL_OK) {
Error_FatalHandler();
}
Audio_Communication.read_size = eAudio_None;
Audio_Communication.dma_buffer_ch1_1_size = 0;
}
} while (WAV_FileData.num_of_samples > 0);
AUDIO_ResetAudioStruct();
trace("[AUDIO] -> Sending signal to LED task!\r\n");
<...>
In the AUDIO_ReadDataBuffer I use f_read function. I call the data transmit function once I have all the buffer full. I cannot transmit half the buffer when half TX callback and then the other half when TX callback - because MCU cannot read data from .wav files that fast and I would be transfering the same buffer again. It looks like I2S is working faster then the SPI. The main problem of all this:
In the audio output I get only noise, some badass strange noise.
My main concerns are:
I used a debugger to check if reading and transmitting is ok -> everything looks great. Data is being read and transmitted. Just not fast enough I think, right?
What could be the problem? How should I deal with it? Is the MCU capable of handling IS2 with 44,1 kHz sampling rate?
2020-04-17 05:54 PM
If SPI runs at 40MHz or even 20MHz, it should comfortably fill I2S with a 1.4MHz bitclock, unless you do something silly - although both Cube/HAL and RTOS may be a way to disaster, and you also appear to use other stuff we don't know about.
But first thing first, divide et impera. Use a fixed buffer, fill it with something simple - e.g. a sawtooth - and in a minimal program (no OS), transmit it to I2S repeatedly. You should hear/see on oscilloscope the sawtooth. Then, gradually add the OS, the SPI/SDCard stuff, and other stuff you may want be running.
JW