cancel
Showing results for 
Search instead for 
Did you mean: 

Playing WAV file using I2S protocol over SAI with DMA (L432KC)

kj.obara
Associate III

Hi, I'm trying to L432KC to play WAV file using Adafruit MAX98357 I2S Class-D Mono Amp, SAI+DMA and I2S protocol.

The code is running as one the tasks of FreeRTOS, but getting rid of RTOS doesn't help.

WAV file is read from SD card, and I've already been able to play it using built-in DAC, but I wanted to use an external amp for better quality.

My code gets stuck, when I'm expecting a transmit completed callback, which never happens.

while (!loadNextPart) condition is always true and I'm looping endlessly.

I would be expecting HAL_SAI_TxCpltCallback to change it to true at some point.

512-byte buffers I use seem to be loaded correctly as I used the same logic to play the WAV files successfully from built-in DAC.

Partial code is below. Would anyone have any idea what I'm missing here?

(The code is slightly messy - sorry - I've been changing it for the last week)

void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
 
  /** Configure LSE Drive Capability
  */
  HAL_PWR_EnableBkUpAccess();
  __HAL_RCC_LSEDRIVE_CONFIG(RCC_LSEDRIVE_LOW);
  /** Initializes the CPU, AHB and APB busses clocks
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSI|RCC_OSCILLATORTYPE_LSE
                              |RCC_OSCILLATORTYPE_MSI;
  RCC_OscInitStruct.LSEState = RCC_LSE_ON;
  RCC_OscInitStruct.LSIState = RCC_LSI_ON;
  RCC_OscInitStruct.MSIState = RCC_MSI_ON;
  RCC_OscInitStruct.MSICalibrationValue = 0;
  RCC_OscInitStruct.MSIClockRange = RCC_MSIRANGE_11;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
  RCC_OscInitStruct.PLL.PLLM = 1;
  RCC_OscInitStruct.PLL.PLLN = 8;
  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_Handler();
  }
  /** Initializes the CPU, AHB and APB busses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_MSI;
  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_2) != HAL_OK)
  {
    Error_Handler();
  }
  PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_USART1|RCC_PERIPHCLK_USART2
                              |RCC_PERIPHCLK_SAI1|RCC_PERIPHCLK_I2C3;
  PeriphClkInit.Usart1ClockSelection = RCC_USART1CLKSOURCE_PCLK2;
  PeriphClkInit.Usart2ClockSelection = RCC_USART2CLKSOURCE_PCLK1;
  PeriphClkInit.I2c3ClockSelection = RCC_I2C3CLKSOURCE_PCLK1;
  PeriphClkInit.Sai1ClockSelection = RCC_SAI1CLKSOURCE_PLLSAI1;
 
  if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure the main internal regulator output voltage
  */
  if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1) != HAL_OK)
  {
    Error_Handler();
  }
  /** Enable MSI Auto calibration
  */
  HAL_RCCEx_EnableMSIPLLMode();
}
 
void MX_DMA2_SAI_Init(void)
{
    LL_AHB1_GRP1_EnableClock(LL_AHB1_GRP1_PERIPH_DMA2);
 
    hdma2.Instance = DMA2_Channel1;
    hdma2.Init.Direction = DMA_MEMORY_TO_PERIPH;
    hdma2.Init.Mode = DMA_NORMAL;
    hdma2.Init.MemInc = DMA_MINC_ENABLE;
    hdma2.Init.PeriphInc = DMA_PINC_DISABLE;
    hdma2.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
    hdma2.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma2.Init.Request = DMA_REQUEST_1;
    hdma2.Init.Priority = DMA_PRIORITY_VERY_HIGH;
 
    hsai_BlockA1.hdmatx = &hdma2;
    hdma2.Parent = &hsai_BlockA1;
 
    HAL_DMA_DeInit(&hdma2);
    if (HAL_OK != HAL_DMA_Init(&hdma2)) Error_Handler();
    HAL_NVIC_SetPriority(DMA2_Channel1_IRQn, 5, 0);
    HAL_NVIC_EnableIRQ(DMA2_Channel1_IRQn);
}
 
void HAL_SAI_MspInit(SAI_HandleTypeDef* hsai)
{
  GPIO_InitTypeDef GPIO_InitStruct;
/* SAI1 */
    if(hsai->Instance==SAI1_Block_A)
    {
        /* SAI1 clock enable */
        if (SAI1_client == 0)
        {
            __HAL_RCC_SAI1_CLK_ENABLE();
            __HAL_RCC_DMA2_CLK_ENABLE();
            /* Peripheral interrupt init*/
            HAL_NVIC_SetPriority(SAI1_IRQn, 5, 0);
            HAL_NVIC_EnableIRQ(SAI1_IRQn);
        }
        SAI1_client++;
 
        /**SAI1_A_Block_A GPIO Configuration
        PA8     ------> SAI1_SCK_A
        PA9     ------> SAI1_FS_A
        PA10     ------> SAI1_SD_A
        */
        GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10;
        GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
        GPIO_InitStruct.Pull = GPIO_PULLUP;
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
        GPIO_InitStruct.Alternate = GPIO_AF13_SAI1;
        HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
    }
}
 
//I created this function as I wasn't sure after reading the HAL documentation if I need to register the callback
//pointing to this function or if the HAL_SAI_TxCpltCallback would replace the weak pointer defined in HAL.
void HAL_SAI_TxComplete(SAI_HandleTypeDef *hsai)
{
  loadNextPart = true;
}
 
void HAL_SAI_TxCpltCallback(SAI_HandleTypeDef *hsai)
{
  loadNextPart = true;
}
 
FRESULT SAI_OpenWaveFile(pWaveFile_t wavFile)
{
    FRESULT fresult = FR_OK;
 
    fresult = f_open(wavFile->FilePointer, wavFile->FilePath, FA_READ);
    if(fresult == FR_OK)
    {
        fresult = f_read(wavFile->FilePointer, (void*)wavFile, headerLength, &readHeaderLength);
        if (fresult == FR_OK && headerLength == readHeaderLength)
        {
            if ((wavFile->WaveFileID != WAVE_RIFF_ID) ||
            (wavFile->WaveFileFormat != WAVE_WAVE_ID) ||
            (wavFile->FormatChunkID != WAVE_FORMAT_ID) ||
            (wavFile->DataChunkID != WAVE_DATA_ID))
            {
                fresult = FR_INVALID_OBJECT;
            }
            else
            {
                hsai_BlockA1.Init.AudioFrequency = wavFile->SampleRate;
 
                HAL_SAI_MspInit(&hsai_BlockA1);
                MX_DMA2_SAI_Init();
                MX_SAI1_Init();
                HAL_SAI_RegisterCallback(&hsai_BlockA1, HAL_SAI_TX_COMPLETE_CB_ID, HAL_SAI_TxComplete);
            }
        }
    }
 
    return fresult;
}
 
FRESULT SAI_PlayWaveFile(pWaveFile_t wavFile, bool fromBeginning)
{
    FRESULT fresult = FR_OK;
    wavFile->CurrentLocation = &(wavFile->FilePointer->fptr);
    bool nextRead = false;
 
    if(fromBeginning)
    {
        *(wavFile->CurrentLocation) = headerLength;
    }
 
    if (wavFile->NumChannels == 1 && wavFile->BitsPerSample == 8)
    {
        SAI1_Block_A->CR1 |= SAI_DATASIZE_8 << SAI_xCR1_DS_Pos;
        SAI1_Block_A->CR1 |= SAI_MONOMODE << SAI_xCR1_MONO_Pos;
        MODIFY_REG(DMA2_Channel1->CCR, DMA_CCR_MSIZE_Msk, 0);
        MODIFY_REG(DMA2_Channel1->CCR, DMA_CCR_PSIZE_Msk, 0);
    }
    else if (wavFile->NumChannels == 1 && wavFile->BitsPerSample == 16)
    {
        SAI1_Block_A->CR1 |= SAI_DATASIZE_16 << SAI_xCR1_DS_Pos;
        SAI1_Block_A->CR1 |= SAI_MONOMODE << SAI_xCR1_MONO_Pos;
        MODIFY_REG(DMA2_Channel1->CCR, DMA_CCR_MSIZE_Msk, DMA_CCR_MSIZE_0);
        MODIFY_REG(DMA2_Channel1->CCR, DMA_CCR_PSIZE_Msk, DMA_CCR_PSIZE_0);
    }
    else if(wavFile->NumChannels == 2 && wavFile->BitsPerSample == 8)
    {
        SAI1_Block_A->CR1 |= SAI_DATASIZE_8 << SAI_xCR1_DS_Pos;
        SAI1_Block_A->CR1 |= SAI_STEREOMODE << SAI_xCR1_MONO_Pos;
        MODIFY_REG(DMA2_Channel1->CCR, DMA_CCR_MSIZE_Msk, 0);
        MODIFY_REG(DMA2_Channel1->CCR, DMA_CCR_PSIZE_Msk, 0);
    }
    else if(wavFile->NumChannels == 2 && wavFile->BitsPerSample == 16)
    {
        SAI1_Block_A->CR1 |= SAI_DATASIZE_16 << SAI_xCR1_DS_Pos;
        SAI1_Block_A->CR1 |= SAI_STEREOMODE << SAI_xCR1_MONO_Pos;
        MODIFY_REG(DMA2_Channel1->CCR, DMA_CCR_MSIZE_Msk, DMA_CCR_MSIZE_0);
        MODIFY_REG(DMA2_Channel1->CCR, DMA_CCR_PSIZE_Msk, DMA_CCR_PSIZE_0);
    }
    else
        return FR_INVALID_OBJECT;
 
    currentBuffer = (uintptr_t)(currentBuffer == (uintptr_t)wavBuffer1 ? wavBuffer2 : wavBuffer1);
 
    while ( *(wavFile->CurrentLocation) + BUFFER_SIZE < wavFile->DataChunkSize && fresult == FR_OK)
    {
        fresult = f_read(wavFile->FilePointer, (void*)currentBuffer, BUFFER_SIZE, &readHeaderLength);
 
        if (nextRead)
        {
            currentBuffer = (uintptr_t)(currentBuffer == (uintptr_t)wavBuffer1 ? wavBuffer2 : wavBuffer1);
 
            if (fresult == FR_OK)
            {
                __HAL_DMA_ENABLE(hsai_BlockA1.hdmatx);
                if (HAL_OK != HAL_SAI_Transmit_DMA(&hsai_BlockA1, (uint8_t*)currentBuffer, sizeof(wavBuffer1)))
                {
                    HAL_SAI_DMAStop(&hsai_BlockA1);
                }
            }
            else { return FR_INT_ERR; } //just returning an error
 
            *(wavFile->CurrentLocation) += BUFFER_SIZE;
            while (!loadNextPart) { taskYIELD(); } //!!! THIS LOOP NEVER ENDS
            loadNextPart = false;
        }
        else
        {
            currentBuffer = (uintptr_t)(currentBuffer == (uintptr_t)wavBuffer1 ? wavBuffer2 : wavBuffer1);
        }
 
        nextRead = true;
    }
 
    HAL_SAI_DMAStop(&hsai_BlockA1);
 
    return fresult;
}

0 REPLIES 0