cancel
Showing results for 
Search instead for 
Did you mean: 

STM32U599 SAI (PDM Mono) GPDMA got only 0's

Hi there,

I tried to add support for a PDM microphone, but so far I could only get 0's from DMA. Somehow, the polling method (HAL_SAI_Receive) did return non-zero values. Here is how I set up the SAI and DMA:

I did so by modifying our project's existing STM32CubeMX file.

The PDM microphone was connected to the MCU via:

  • PA10 (SAI1_D1)
  • PB8 (SAI1_CK1)

The SAI A has the following configurations:

  • Mode: PDM using MicroPhones pair (1-2) and CK1 as clock
  • Clock Enable: Pdm Clock1 Enable
  • Protocol: Free
  • Audio Mode: Master Receive
  • Frame Length: 16 bits
  • Data Size: 8 bits
  • Slot Size: DataSize
  • Output Mode: Mono
  • Companding Mode: No companding mode
  • First Bit: MSB First
  • Frame Synchro Active Level Length: 1
  • Frame Syncrho Definition: Start Frame
  • Frame Synchro Polarity: Active Low
  • Frame Synchro Offset: 0
  • First Bit Offset: 0
  • Number of Slots (only Even Values): 2
  • Slot Active: Use Slot 0 and 1 only
  • Master Clock Divider: Enabled
  • Audio Frequency: 16 KHz
  • Clock Strobing: Rising Edge
  • Fifo Threshold: Empty
  • Output Drive: Disabled

 

And then, I created a GPDMA channel for SAI1 A: 

  • Circular Mode: Enabled
  • Circular Port: Port0
  • Request: SAI1_A
  • DMA Handle in IP Structure: hdmarx
  • Block HW request protocol: Single/Burst Level
  • Priority: Very high
  • Transaction Mode: Normal
  • Direction: Peripheral to Memory
  • Node Type: GPDMA Linear Addressing
  • Source Address Increment After Transfer: Disabled
  • Data Width: Byte
  • Burst Length: 1
  • Allocated Port for Transfer: Port0
  • Destination Address Increment After Transfer: Enabled
  • Data Width: Byte
  • Burst Length: 1
  • Allocated Port for Transfer: Port1
  • Data Handling Configuration: Disabled
  • Trigger Configuration: Disabled
  • Execution Mode of the Linked List: Circular
  • Linked List Execution Mode: The list if fully executed
  • Transfer Event Generation: The TC (and the HT) even is generated at the (respectfully half) end of each block

 

The clock tree is configured such that PLL2P (supplying to SAI1) has a rate of 2.048 MHz, which is within my MEMS PDM microphone's f_clk range. After code generation, the following code are added to my source code:

In main.c, the following is added and called in main():

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

  /* USER CODE BEGIN SAI1_Init 0 */

  /* USER CODE END SAI1_Init 0 */

  /* USER CODE BEGIN SAI1_Init 1 */

  /* USER CODE END SAI1_Init 1 */
  hsai_BlockA1.Instance = SAI1_Block_A;
  hsai_BlockA1.Init.Protocol = SAI_FREE_PROTOCOL;
  hsai_BlockA1.Init.AudioMode = SAI_MODEMASTER_RX;
  hsai_BlockA1.Init.DataSize = SAI_DATASIZE_8;
  hsai_BlockA1.Init.FirstBit = SAI_FIRSTBIT_MSB;
  hsai_BlockA1.Init.ClockStrobing = SAI_CLOCKSTROBING_RISINGEDGE;
  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_16K;
  hsai_BlockA1.Init.MckOutput = SAI_MCK_OUTPUT_DISABLE;
  hsai_BlockA1.Init.MonoStereoMode = SAI_MONOMODE;
  hsai_BlockA1.Init.CompandingMode = SAI_NOCOMPANDING;
  hsai_BlockA1.Init.PdmInit.Activation = ENABLE;
  hsai_BlockA1.Init.PdmInit.MicPairsNbr = 1;
  hsai_BlockA1.Init.PdmInit.ClockEnable = SAI_PDM_CLOCK1_ENABLE;
  hsai_BlockA1.FrameInit.FrameLength = 8;
  hsai_BlockA1.FrameInit.ActiveFrameLength = 1;
  hsai_BlockA1.FrameInit.FSDefinition = SAI_FS_STARTFRAME;
  hsai_BlockA1.FrameInit.FSPolarity = SAI_FS_ACTIVE_LOW;
  hsai_BlockA1.FrameInit.FSOffset = SAI_FS_FIRSTBIT;
  hsai_BlockA1.SlotInit.FirstBitOffset = 0;
  hsai_BlockA1.SlotInit.SlotSize = SAI_SLOTSIZE_DATASIZE;
  hsai_BlockA1.SlotInit.SlotNumber = 2;
  hsai_BlockA1.SlotInit.SlotActive = 0x00000003;
  if (HAL_SAI_Init(&hsai_BlockA1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN SAI1_Init 2 */

  /* USER CODE END SAI1_Init 2 */

}

The GPDMA (Channel 3) was also initialized in main.c:

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

  /* USER CODE BEGIN GPDMA1_Init 0 */

  /* USER CODE END GPDMA1_Init 0 */

  /* Peripheral clock enable */
  __HAL_RCC_GPDMA1_CLK_ENABLE();

  /* GPDMA1 interrupt Init */
    HAL_NVIC_SetPriority(GPDMA1_Channel1_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(GPDMA1_Channel1_IRQn);
    HAL_NVIC_SetPriority(GPDMA1_Channel2_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(GPDMA1_Channel2_IRQn);
    HAL_NVIC_SetPriority(GPDMA1_Channel3_IRQn, 0, 0);
    HAL_NVIC_EnableIRQ(GPDMA1_Channel3_IRQn);

  /* USER CODE BEGIN GPDMA1_Init 1 */

  /* USER CODE END GPDMA1_Init 1 */
  /* USER CODE BEGIN GPDMA1_Init 2 */

  /* USER CODE END GPDMA1_Init 2 */

}

The following code is added to stm32u5xx_hal_msp.c:

extern DMA_NodeTypeDef Node_GPDMA1_Channel3;

extern DMA_QListTypeDef List_GPDMA1_Channel3;

extern DMA_HandleTypeDef handle_GPDMA1_Channel3;

static uint32_t SAI1_client =0;

void HAL_SAI_MspInit(SAI_HandleTypeDef* hsai)
{

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

  /** Initializes the peripherals clock
  */
    PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_SAI1;
    PeriphClkInit.Sai1ClockSelection = RCC_SAI1CLKSOURCE_PLL2;
    PeriphClkInit.PLL2.PLL2Source = RCC_PLLSOURCE_HSE;
    PeriphClkInit.PLL2.PLL2M = 1;
    PeriphClkInit.PLL2.PLL2N = 16;
    PeriphClkInit.PLL2.PLL2P = 125;
    PeriphClkInit.PLL2.PLL2Q = 2;
    PeriphClkInit.PLL2.PLL2R = 24;
    PeriphClkInit.PLL2.PLL2RGE = RCC_PLLVCIRANGE_1;
    PeriphClkInit.PLL2.PLL2FRACN = 0.0;
    PeriphClkInit.PLL2.PLL2ClockOut = RCC_PLL2_DIVP;
    if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
    {
      Error_Handler();
    }

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

    /**SAI1_A_Block_A GPIO Configuration
    PB8     ------> SAI1_CK1
    PA10     ------> SAI1_D1
    */
    GPIO_InitStruct.Pin = GPIO_PIN_8;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    GPIO_InitStruct.Alternate = GPIO_AF3_SAI1;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_10;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    GPIO_InitStruct.Alternate = GPIO_AF3_SAI1;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

      /* Peripheral DMA init*/

    NodeConfig.NodeType = DMA_GPDMA_LINEAR_NODE;
    NodeConfig.Init.Request = GPDMA1_REQUEST_SAI1_A;
    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_INCREMENTED;
    NodeConfig.Init.SrcDataWidth = DMA_SRC_DATAWIDTH_BYTE;
    NodeConfig.Init.DestDataWidth = DMA_SRC_DATAWIDTH_BYTE;
    NodeConfig.Init.SrcBurstLength = 1;
    NodeConfig.Init.DestBurstLength = 1;
    NodeConfig.Init.TransferAllocatedPort = DMA_SRC_ALLOCATED_PORT0|DMA_DEST_ALLOCATED_PORT1;
    NodeConfig.Init.TransferEventMode = DMA_TCEM_BLOCK_TRANSFER;
    NodeConfig.Init.Mode = DMA_NORMAL;
    NodeConfig.TriggerConfig.TriggerPolarity = DMA_TRIG_POLARITY_MASKED;
    NodeConfig.DataHandlingConfig.DataExchange = DMA_EXCHANGE_NONE;
    NodeConfig.DataHandlingConfig.DataAlignment = DMA_DATA_RIGHTALIGN_ZEROPADDED;
    if (HAL_DMAEx_List_BuildNode(&NodeConfig, &Node_GPDMA1_Channel3) != HAL_OK)
    {
      Error_Handler();
    }

    if (HAL_DMAEx_List_InsertNode(&List_GPDMA1_Channel3, NULL, &Node_GPDMA1_Channel3) != HAL_OK)
    {
      Error_Handler();
    }

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

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

    if (HAL_DMAEx_List_LinkQ(&handle_GPDMA1_Channel3, &List_GPDMA1_Channel3) != HAL_OK)
    {
      Error_Handler();
    }

    __HAL_LINKDMA(hsai, hdmarx, handle_GPDMA1_Channel3);

    if (HAL_DMA_ConfigChannelAttributes(&handle_GPDMA1_Channel3, DMA_CHANNEL_NPRIV) != HAL_OK)
    {
      Error_Handler();
    }

    }
}

As well as the following in stm32u5xx_it.c

/**
  * @brief This function handles GPDMA1 Channel 3 global interrupt.
  */
void GPDMA1_Channel3_IRQHandler(void)
{
  /* USER CODE BEGIN GPDMA1_Channel3_IRQn 0 */

  /* USER CODE END GPDMA1_Channel3_IRQn 0 */
  HAL_DMA_IRQHandler(&handle_GPDMA1_Channel3);
  /* USER CODE BEGIN GPDMA1_Channel3_IRQn 1 */

  /* USER CODE END GPDMA1_Channel3_IRQn 1 */
}



In my application code, I have added the following logic:

extern SAI_HandleTypeDef hsai_BlockA1;
uint8_t m_buffer[MMB109Microphone::BUFFER_SIZE] __attribute__((aligned(32), section(".sram_d3")));

void start_recording()
{
    SCB_EnableDCache();
	SCB_CleanDCache_by_Addr((void *)m_buffer, BUFFER_SIZE);
    HAL_StatusTypeDef result = HAL_SAI_Receive_DMA(&hsai_BlockA1, m_buffer, BUFFER_SIZE);
    Log::Warning("SAI DMA start: %d\n", result);

    Log::Warning("CR1 = 0x%08lX\n", SAI1_Block_A->CR1);
    Log::Warning("CR2 = 0x%08lX\n", SAI1_Block_A->CR2);
    Log::Warning("FRCR = 0x%08lX\n", SAI1_Block_A->FRCR);
    Log::Warning("SLOTR = 0x%08lX\n", SAI1_Block_A->SLOTR);
    Log::Warning("IMR = 0x%08lX\n", SAI1_Block_A->IMR);
    Log::Warning("SR = 0x%08lX\n", SAI1_Block_A->SR);
    Log::Warning("PDMCR = 0x%08lX\n", SAI1->PDMCR);
    Log::Warning("SAI1 Clock Source: %lu\n", __HAL_RCC_GET_SAI1_SOURCE());
    Log::Warning("PLL2P Frequency: %lu Hz\n", HAL_RCCEx_GetPeriphCLKFreq(RCC_PERIPHCLK_SAI1));
    Log::Warning("RCC->CR: 0x%08lX\n", RCC->CR);
    Log::Warning("RCC->CR: 0x%08lX\n", RCC->CR);               // Check PLL2ON/PLL2RDY bits
    Log::Warning("RCC->PLL2CFGR: 0x%08lX\n", RCC->PLL2CFGR);   // Main config
    Log::Warning("RCC->PLL2DIVR: 0x%08lX\n", RCC->PLL2DIVR);   // N, P, Q, R dividers
    Log::Warning("RCC->PLL2FRACR: 0x%08lX\n", RCC->PLL2FRACR); // Fractional setting

    bool pll2_on   = RCC->CR & RCC_CR_PLL2ON;
    bool pll2_rdy  = RCC->CR & RCC_CR_PLL2RDY;
    Log::Warning("PLL2ON: %lu, PLL2RDY: %lu\n", pll2_on ? 1UL : 0UL, pll2_rdy ? 1UL : 0UL);

    if (__HAL_SAI_GET_FLAG(&hsai_BlockA1, SAI_FLAG_OVRUDR)) {
        Log::Error("SAI Overrun/Underrun occurred");
    }
    if (__HAL_SAI_GET_FLAG(&hsai_BlockA1, SAI_FLAG_WCKCFG)) {
        Log::Error("Wrong clock configuration");
    }
    if (__HAL_SAI_GET_FLAG(&hsai_BlockA1, SAI_FLAG_AFSDET)) {
        Log::Error("Anticipated frame sync detection");
    }
    if (__HAL_SAI_GET_FLAG(&hsai_BlockA1, SAI_FLAG_LFSDET)) {
        Log::Error("Late frame sync detection");
    }

    HAL_SAI_StateTypeDef state = HAL_SAI_GetState(&hsai_BlockA1);
    uint32_t error = HAL_SAI_GetError(&hsai_BlockA1);
    Log::Warning("SAI state: %d, error: 0x%08lX", state, error);

    SAI_PdmInitTypeDef pdmInit = hsai_BlockA1.Init.PdmInit;
    Log::Warning("PDM Activation: %d, MicPairs: %lu, ClockEnable: 0x%08lX",
                 pdmInit.Activation,
                 pdmInit.MicPairsNbr,
                 pdmInit.ClockEnable);

    uint8_t test_buf[8];
    HAL_SAI_Receive(&hsai_BlockA1, test_buf, sizeof(test_buf), 100);
    Log::Info("Polled sample: %02X %02X %02X %02X %02X %02X %02X %02X",
    		test_buf[0], test_buf[1], test_buf[2], test_buf[3], test_buf[4], test_buf[5], test_buf[6], test_buf[7]);
}

The returned something like:

SAI DMA start: 0
CR1 = 0x00030281
CR2 = 0x00000004
FRCR = 0x0000000F
SLOTR = 0x00030100
IMR = 0x00000005
SR = 0x00010000
PDMCR = 0x00000101
SAI1 Clock Source: 0
PLL2P Frequency: 2048000 Hz
RCC->CR: 0x3F030005
RCC->CR: 0x3F030005
RCC->PLL2CFGR: 0x0001001F
RCC->PLL2DIVR: 0x1701F80F
RCC->PLL2FRACR: 0x00000000
PLL2ON: 1, PLL2RDY: 1
SAI state: 34, error: 0x00000000PDM Activation: 1, MicPairs: 1, ClockEnable: 0x00000100
Polled sample: B8 5D 07 20 00 10 00 00

...which should indicate that the system is working.


And I copy the PDM data to flash using:

static uint32_t m_logAddr = HRRAW_LOG_START;

static void ProcessPDMHalfBuffer(bool isFirstHalf)
{
    constexpr size_t halfSize = MMB109Microphone::BUFFER_SIZE / 2;
    uint8_t *activeBuf = m_buffer + (isFirstHalf ? 0 : halfSize);
    uintptr_t addr = (uintptr_t)activeBuf & ~31UL;
    uintptr_t end = ((uintptr_t)(activeBuf + halfSize) + 31UL) & ~31UL;
    SCB_InvalidateDCache_by_Addr((void *)addr, end - addr);

    activeBuf[1] = 0x35; // This magic is correctly written to flash

    // Write to flash (raw PDM)
    if (m_logAddr + halfSize <= HRRAW_LOG_START + HRRAW_LOG_SIZE)
    {
        if (isFirstHalf)
        {
            gSFlash.SectorErase(m_logAddr);
        }
        gSFlash.WriteData(m_logAddr, activeBuf, halfSize);
//        SCB_CleanDCache_by_Addr((void *)m_buffer, MMB109Microphone::BUFFER_SIZE);
        m_logAddr += halfSize;
    }
    else
    {
        Log::Info("Mic flash full.");
        MMB109Microphone::getInstance().Stop();
    }
}

extern "C" void HAL_SAI_RxHalfCpltCallback(SAI_HandleTypeDef *hsai)
{
	// This prints valid data, strange...
	uint8_t test_buf[8];
	HAL_SAI_Receive(&hsai_BlockA1, test_buf, sizeof(test_buf), 100);
	Log::Info("Polled sample: %02X %02X %02X %02X %02X %02X %02X %02X",
			test_buf[0], test_buf[1], test_buf[2], test_buf[3], test_buf[4], test_buf[5], test_buf[6], test_buf[7]);

    if (hsai->Instance == hsai_BlockA1.Instance)
    {
        // Log::Warning("First half buffer received, processing...\n");
        ProcessPDMHalfBuffer(true);
    }
}

extern "C" void HAL_SAI_RxCpltCallback(SAI_HandleTypeDef *hsai)
{
	// This prints valid data, strange...
	uint8_t test_buf[8];
		HAL_SAI_Receive(&hsai_BlockA1, test_buf, sizeof(test_buf), 100);
		Log::Info("Polled sample: %02X %02X %02X %02X %02X %02X %02X %02X",
				test_buf[0], test_buf[1], test_buf[2], test_buf[3], test_buf[4], test_buf[5], test_buf[6], test_buf[7]);

    if (hsai->Instance == hsai_BlockA1.Instance)
    {
        Log::Warning("Second half buffer received, processing...\n");
        ProcessPDMHalfBuffer(false);
    }
}

Sadly, although the callbacks were triggered successfully and periodically, only 0's (and the magic number 0x35) was copied to flash.

Thank you for your help in advance, and sorry for my broken English.

0 REPLIES 0