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
* 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.DataSize = SAI_DATASIZE_16;
hsai_BlockA1.Init.FirstBit = SAI_FIRSTBIT_MSB;
hsai_BlockA1.Init.ClockStrobing = SAI_CLOCKSTROBING_RISINGEDGE;
// hsai_BlockA1.Init.ClockStrobing = SAI_CLOCKSTROBING_FALLINGEDGE;
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.FIFOThreshold = SAI_FIFOTHRESHOLD_1QF;
// hsai_BlockA1.Init.FIFOThreshold = SAI_FIFOTHRESHOLD_FULL;
hsai_BlockA1.Init.AudioFrequency = SAI_AUDIO_FREQUENCY_16K;
// hsai_BlockA1.Init.AudioFrequency = SAI_AUDIO_FREQUENCY_16K * 8;
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.FrameLength = 16;
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
* 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.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
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.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
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.SrcDataWidth = DMA_SRC_DATAWIDTH_HALFWORD;
// NodeConfig.Init.DestDataWidth = DMA_SRC_DATAWIDTH_HALFWORD;
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;
// NodeConfig.DataHandlingConfig.DataAlignment = DMA_DATA_PACK;
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.LinkAllocatedPort = DMA_LINK_ALLOCATED_PORT1;
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")));
// uint8_t m_buffer[MMB109Microphone::BUFFER_SIZE] __attribute__((aligned(32), section(".sram_d2")));
// uint8_t m_buffer[MMB109Microphone::BUFFER_SIZE] __attribute__((aligned(32), section(".ram_d2")));
void start_recording()
{
SCB_EnableDCache();
SCB_CleanDCache_by_Addr((void *)m_buffer, BUFFER_SIZE);
// SCB_DisableDCache();
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. I have tried many different configurations (those I commented out above) and still got zeros. I have wasted a whole week and counting on this issue and would appreciate any help.
