Skip to main content
Visitor II
August 21, 2025
Question

STM32N6 Nucleo audio input using SAI with GPDMA

  • August 21, 2025
  • 2 replies
  • 872 views

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. 
    This topic has been closed for replies.

    2 replies

    Technical Moderator
    August 27, 2025

    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

    jweberAuthor
    Visitor II
    September 1, 2025

    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!

    jweberAuthor
    Visitor II
    September 8, 2025

    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!

    Graduate
    November 15, 2025

    Hey, I am also facing a similar problem. How did you end up solving it?
    SAI works, but the frequency of FSYNC and BCK is way too low.

    jweberAuthor
    Visitor II
    November 17, 2025

    Hello! Here is my SAI configuration :

     h_sai_B1.Instance = SAI1_Block_B;
     h_sai_B1.Init.AudioMode = SAI_MODEMASTER_RX;
     h_sai_B1.Init.Synchro = SAI_ASYNCHRONOUS;
     h_sai_B1.Init.OutputDrive = SAI_OUTPUTDRIVE_DISABLE;
     h_sai_B1.Init.NoDivider = SAI_MASTERDIVIDER_DISABLE;
     h_sai_B1.Init.FIFOThreshold = SAI_FIFOTHRESHOLD_EMPTY;
     h_sai_B1.Init.SynchroExt = SAI_SYNCEXT_DISABLE;
     h_sai_B1.Init.MckOutput = SAI_MCK_OUTPUT_DISABLE;
     h_sai_B1.Init.MonoStereoMode = SAI_STEREOMODE;
     h_sai_B1.Init.CompandingMode = SAI_NOCOMPANDING;
     h_sai_B1.Init.AudioFrequency = SAI_AUDIO_FREQUENCY_16K;
     h_sai_B1.Init.Mckdiv = 2;
     h_sai_B1.Init.DataSize = SAI_DATASIZE_24;
     h_sai_B1.FrameInit.FrameLength = 64;
     h_sai_B1.FrameInit.ActiveFrameLength = 32;
     h_sai_B1.FrameInit.FSDefinition = SAI_FS_CHANNEL_IDENTIFICATION;
     h_sai_B1.FrameInit.FSPolarity = SAI_FS_ACTIVE_LOW;
     h_sai_B1.FrameInit.FSOffset = SAI_FS_BEFOREFIRSTBIT;
     h_sai_B1.SlotInit.FirstBitOffset = 1;
     h_sai_B1.SlotInit.SlotSize = SAI_SLOTSIZE_32B;
     h_sai_B1.SlotInit.SlotNumber = 2;
     h_sai_B1.SlotInit.SlotActive = SAI_SLOTACTIVE_0 | SAI_SLOTACTIVE_1;

    Pay attention to the PLL you want to use and the frequency, it has to be close to 1.052 MHz for BCLK. 

    Regards.