2025-06-19 3:55 AM
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:
The SAI A has the following configurations:
And then, I created a GPDMA channel for SAI1 A:
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.