cancel
Showing results for 
Search instead for 
Did you mean: 

STM32N6 Nucleo audio input using SAI with GPDMA

jweber
Associate II

Hello everyone,

I'm trying to configure the SAI and GPDMA peripherals on an STM32 to acquire audio from an I2S microphone. My goal is to capture a 32-bit, 2-channel audio stream at a 16 kHz sample rate, with the STM32 acting as the clock master. I've followed the steps below, but I'm running into two main issues:

  • While the HAL_SAI_RxCpltCallback and HAL_SAI_RxHalfCpltCallback functions are being called, the acquisition buffer remains unchanged (full of zeros).
  • I'm seeing a 16 kHz signal on all three I2S lines (SCK, FS, and SD), which is incorrect. The clock (SCK) should be much higher.

Here's a summary of the steps I've taken:

  • PLL4 Configuration: I've configured PLL4 to generate a 1.024 MHz clock (16 kHz * 64 = 1.024 MHz). This clock feeds into the IC7 peripheral.
  • GPDMA1 Configuration: I've enabled the clock and interrupts for GPDMA1 Channel 0 and added a linked-list initialization.
  • SAI1 Block B1 Configuration: I've set up the SAI handle for I2S standard, 32-bit data, 16 kHz audio frequency, Master RX Asynchronous mode, and 2 channels.
  • SAI MSP Configuration (HAL_SAI_MspInit):
    • I've configured the SAI clock source to use PLL4 via IC7.
    • The necessary GPIOs (SCK, SD, FS) have been initialized.
    • I've enabled NVIC for SAI1_B.
    • I've configured the DMA node for the SAI1_B peripheral, adding it to the queue in circular mode.
    • I've linked the DMA queue to the DMA channel and associated the SAI handle with the DMA.
  • Interrupt Handlers: I've overwritten the global interrupt handlers for GPDMA1_Channel0 and SAI1_B.
  • Callbacks: I've implemented the HAL_SAI_RxCpltCallback and HAL_SAI_RxHalfCpltCallback functions.
  • DMA Attributes: I've set the security and attribute configurations for GPDMA Channel 0.
  • Data Reception: I'm calling HAL_SAI_Receive_DMA to start the acquisition.

I've attached part of my code snippets below.

/**
  * @brief GPDMA1 Initialization Function
  * @PAram None
  * @retval None
  */
static void MX_GPDMA1_Init(void)
{

  /* Peripheral clock enable */
  __HAL_RCC_GPDMA1_CLK_ENABLE();

  /* GPDMA1 interrupt Init */
    HAL_NVIC_SetPriority(GPDMA1_Channel0_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(GPDMA1_Channel0_IRQn);
}

/**
  * @brief SAI1 Initialization Function
  * @PAram None
  * @retval None
  */
static void MX_SAI1_Init(void)
{

    /* DMA node configuration declaration */
  hsai_BlockB1.Instance = SAI1_Block_B;
  hsai_BlockB1.Init.AudioMode = SAI_MODEMASTER_RX;
  hsai_BlockB1.Init.Synchro = SAI_ASYNCHRONOUS;
  hsai_BlockB1.Init.OutputDrive = SAI_OUTPUTDRIVE_DISABLE;
  hsai_BlockB1.Init.NoDivider = SAI_MASTERDIVIDER_ENABLE;
  hsai_BlockB1.Init.FIFOThreshold = SAI_FIFOTHRESHOLD_EMPTY;
  hsai_BlockB1.Init.AudioFrequency = SAI_AUDIO_FREQUENCY_16K;
  hsai_BlockB1.Init.SynchroExt = SAI_SYNCEXT_DISABLE;
  hsai_BlockB1.Init.MckOutput = SAI_MCK_OUTPUT_ENABLE;
  hsai_BlockB1.Init.MonoStereoMode = SAI_STEREOMODE;
  hsai_BlockB1.Init.CompandingMode = SAI_NOCOMPANDING;
  if (HAL_SAI_InitProtocol(&hsai_BlockB1, SAI_I2S_STANDARD, SAI_PROTOCOL_DATASIZE_32BIT, 2) != HAL_OK)
  {
    Error_Handler();
  }
    
}


void HAL_SAI_MspInit(SAI_HandleTypeDef* hsai)
{

  GPIO_InitTypeDef GPIO_InitStruct;
  DMA_NodeConfTypeDef NodeConfig;
  RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};
/* SAI1 */
    if(hsai->Instance==SAI1_Block_B)
    {
      /* Peripheral clock enable */

  /** Initializes the peripherals clock
  */
    PeriphClkInitStruct.PeriphClockSelection = RCC_PERIPHCLK_SAI1;
    PeriphClkInitStruct.Sai1ClockSelection = RCC_SAI1CLKSOURCE_IC7;
    PeriphClkInitStruct.ICSelection[RCC_IC7].ClockSelection = RCC_ICCLKSOURCE_PLL4;
    PeriphClkInitStruct.ICSelection[RCC_IC7].ClockDivider = 250;
    if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInitStruct) != HAL_OK)
    {
      Error_Handler();
    }

      if (SAI1_client == 0)
      {
       __HAL_RCC_SAI1_CLK_ENABLE();
      }
    SAI1_client ++;

    __HAL_RCC_GPIOG_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();

    /**SAI1_B_Block_B GPIO Configuration
    PG1     ------> SAI1_SCK_B
    PA3     ------> SAI1_SD_B
    PG2     ------> SAI1_FS_B
    */
    GPIO_InitStruct.Pin = GPIO_PIN_1|GPIO_PIN_2;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF6_SAI1;
    HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_3;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF6_SAI1;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);


    HAL_NVIC_SetPriority(SAI1_B_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(SAI1_B_IRQn);

    /* Peripheral DMA init*/
    NodeConfig.NodeType = DMA_GPDMA_LINEAR_NODE;
    NodeConfig.Init.Request = GPDMA1_REQUEST_SAI1_B;
    NodeConfig.Init.BlkHWRequest = DMA_BREQ_SINGLE_BURST;
    NodeConfig.Init.Direction = DMA_PERIPH_TO_MEMORY;
    NodeConfig.Init.SrcInc = DMA_SINC_FIXED;
    NodeConfig.Init.DestInc = DMA_DINC_FIXED;
    NodeConfig.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_WORD;
    NodeConfig.Init.DestDataWidth = DMA_DEST_DATAWIDTH_WORD;
    NodeConfig.Init.SrcBurstLength = 1;
    NodeConfig.Init.DestBurstLength = 1;
    NodeConfig.Init.TransferAllocatedPort = DMA_SRC_ALLOCATED_PORT0|DMA_DEST_ALLOCATED_PORT0;
    NodeConfig.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
    NodeConfig.Init.Mode = DMA_NORMAL;
    NodeConfig.TriggerConfig.TriggerPolarity = DMA_TRIG_POLARITY_MASKED;
    NodeConfig.TriggerConfig.TriggerSelection = GPDMA1_TRIGGER_GPDMA1_CH0_TCF;
    NodeConfig.DataHandlingConfig.DataExchange = DMA_EXCHANGE_NONE;
    NodeConfig.DataHandlingConfig.DataAlignment = DMA_DATA_RIGHTALIGN_ZEROPADDED;
    NodeConfig.SrcSecure = DMA_CHANNEL_SRC_SEC;
    NodeConfig.DestSecure = DMA_CHANNEL_DEST_SEC;
    if (HAL_DMAEx_List_BuildNode(&NodeConfig, &Node_GPDMA1_Channel0) != HAL_OK)
    {
      Error_Handler();
    }

    if (HAL_DMAEx_List_InsertNode(&List_GPDMA1_Channel0, NULL, &Node_GPDMA1_Channel0) != HAL_OK)
    {
      Error_Handler();
    }

    if (HAL_DMAEx_List_SetCircularMode(&List_GPDMA1_Channel0) != HAL_OK)
    {
      Error_Handler();
    }

    handle_GPDMA1_Channel0.Instance = GPDMA1_Channel0;
    handle_GPDMA1_Channel0.InitLinkedList.Priority = DMA_LOW_PRIORITY_LOW_WEIGHT;
    handle_GPDMA1_Channel0.InitLinkedList.LinkStepMode = DMA_LSM_FULL_EXECUTION;
    handle_GPDMA1_Channel0.InitLinkedList.LinkAllocatedPort = DMA_LINK_ALLOCATED_PORT0;
    handle_GPDMA1_Channel0.InitLinkedList.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
    handle_GPDMA1_Channel0.InitLinkedList.LinkedListMode = DMA_LINKEDLIST_CIRCULAR;
    if (HAL_DMAEx_List_Init(&handle_GPDMA1_Channel0) != HAL_OK)
    {
      Error_Handler();
    }

    if (HAL_DMAEx_List_LinkQ(&handle_GPDMA1_Channel0, &List_GPDMA1_Channel0) != HAL_OK)
    {
      Error_Handler();
    }

    __HAL_LINKDMA(hsai, hdmarx, handle_GPDMA1_Channel0);
    }
}


void main(void)
{
[...]
  MX_GPDMA1_Init();
  MX_SAI1_Init();
  SystemIsolation_Config();

    /* set up GPDMA configuration */
  /* set GPDMA1 channel 0 used by SAI1 */
  if (HAL_DMA_ConfigChannelAttributes(&handle_GPDMA1_Channel0,DMA_CHANNEL_SEC|DMA_CHANNEL_PRIV|DMA_CHANNEL_SRC_SEC|DMA_CHANNEL_DEST_SEC)!= HAL_OK )
  {
    Error_Handler();
  }
[...]

  if (HAL_OK != HAL_SAI_Receive_DMA(&hsai_BlockB1, (uint8_t *) acquisition_buffer, N_BLOCK_SIZE))
  {
      Error_Handler();
  }

  printf("SAI1_B DMA configuration done.\n");

}
Any help or suggestions would be greatly appreciated as I'm a bit stuck on what to check next.
Best regards. 
3 REPLIES 3
Saket_Om
ST Employee

Hello @jweber 

Why the DMA DestInc is set to fixed? 

NodeConfig.Init.DestInc = DMA_DINC_FIXED;

 Please refer to the example below: 

STM32CubeH5/Projects/STM32H573I-DK/Examples/SAI/SAI_AudioPlay at main · STMicroelectronics/STM32CubeH5 · GitHub

To give better visibility on the answered topics, please click on "Accept as Solution" on the reply which solved your issue or answered your question.
Saket_Om

Hello Saket_Om, 

Thank you for pointing out this mistake when I copy pasted example. It appears I try many configuration and was a bit lost with all the parameters to configure.

However, when I changed it to INCREMENTED, it started to work but not every time I run it (in FSBL).
I'm 
using the CMSIS RTOS middleware and I try to changed the interrupt priority of the GPDMA to be lower (higher value) than the configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY (which is 5 in my case).

Is there any parameter I should pay attention to be sure the DMA interruption can handle a semaphore unlock for a processing task? 

Thank you for your time!

Hello @Saket_Om ,

I tried working on the configuration using the link you provided as a reference. As I mentioned before, I managed to get it working, but it seems that the GPDMA and/or SAI configuration is still missing something. When I add a simple debug printf function to my project, the GPDMA is no longer initialized, and the clock doesn’t start.

I suspect this might be a side effect related to the length of my code, and that I’m missing some parameter to make the program deterministic. I’ve attached the .ld file to show how I separated the DMA memory from the other RAM space.

Here’s my DMA buffer declaration (as global):

__attribute__((section(".dma_nocache"), aligned(32))) 
uint32_t acquisition_buffer[N_DMA_BUFFER];
uint32_t audio_input_buffer[N_SAMPLES_IN_AUDIO_BUFFER];

Are there any other options I should consider since I’m using CMSIS-RTOS V2?
I don’t understand why the behavior of the GPDMA changes when I try to add a simple function such as display debug information using functions like printf AFTER initialization:

#ifdef DEBUG
  printf("--------  DEBUG SECTION ---------\r\n");
  SAI_debug_configuration();
  GPDMA_debug_configuration(handle_GPDMA1_Channel0, hsai_BlockB1);
  debug_clocks_configuration();
  printf("--------------------------------\r\n");
#endif

Thank you in advance!