Skip to main content
LCE
Principal II
September 24, 2026
Question

STM32G431: SAI as master receiver + DMA, channel swap after restart

  • September 24, 2026
  • 0 replies
  • 6 views

Heyho,

next G4 problem…

G431 with an audio ADC (TI’s PCM1822) connected to SAI1_Block_A, SAI is in master receiver mode, meaning it provides the I2S clocks for the external ADC.

DMA is used, circular, 32-bit.

Signals looking good at all audio sampling frequencies, up to 192 kHz.

Problem:

always at 1st start, and every now and then after a SAI + DMA stop and restart, somehow the 2 ADC channels are swapped.

I checked all I2S signals with a scope, and data is always where it’s supposed to be.

I can check the DMA buffer, it’s definitely the SAI.

BAD buffer / channels swapped, 
signal connected to RIGHT ( = R ) channel only,
so LEFT should be 0

L R L R L R L R
000: +0.31 -0.00 +0.37 +0.00 +0.38 -0.00 +0.36 -0.00
008: +0.30 -0.00 +0.20 -0.00 +0.09 -0.00 -0.04 -0.00


GOOD buffer: L = 0, R = signal
L R L R L R L R
000: +0.00 +0.26 +0.00 +0.16 +0.00 +0.04 -0.00 -0.09
008: +0.00 -0.20 -0.00 -0.30 -0.00 -0.36 +0.00 -0.38

our analyzer:

 

Here are the DMA + SAI start and stop functions:

/* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
/* SAI_1_A: I2S -> RX = ADC <-> DMA start */
uint8_t SaiAdcDmaStart(void)
{
if( (1 == u8AdcDmaActive) ||
((SAI_ADC_12->CR1 & SAI_xCR1_SAIEN) != 0) )
{
#if DEBUG_SAI_ERR
uart_printf(SZC_TEXT_ERR "SaiAdcDmaStart() active\n\r");
#endif /* DEBUG_SAI_ERR */

return HAL_ERROR;
}

/* reset DMA RX complete flags */
u8AdcDmaRxHalfCplt = 0;
u8AdcDmaRxCplt = 0;

/* DMA buffer pointer reset */
u16AdcDmaBufPtr = 0;

/* DMA error counter reset */
u32SaiIrqErrCnt = 0;

/* clear DMA flags */
DMA1->IFCR = DMA_IFCR_CGIF1;

DMA1_Channel1->CPAR = (uint32_t)&(SAI_ADC_12->DR);
DMA1_Channel1->CMAR = (uint32_t)&i32AdcInBuf[0];
DMA1_Channel1->CNDTR = (uint32_t)AUDIO_DMA_BUF_SIZE;

/* DMA enable */
DMA1_Channel1->CCR |= DMA_CCR_EN;

/* peripheral DMA enable */
SAI_ADC_12->CR1 |= SAI_xCR1_DMAEN;

/* SAI 1 A enable */
SAI_ADC_12->CR1 |= SAI_xCR1_SAIEN;

u8AdcDmaActive = 1;

return HAL_OK;
}


/* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
/* SAI_1_A: I2S -> RX = ADC <-> DMA stop */
uint8_t SaiAdcDmaStop(void)
{
uint8_t u8RetVal = 0;
uint32_t u32TickStart = HAL_GetTick();

/* disable */
SAI_ADC_12->CR1 &= ~SAI_xCR1_DMAEN;
SAI_ADC_12->CR1 &= ~SAI_xCR1_SAIEN;

/* wait */
while( (SAI_ADC_12->CR1 & SAI_xCR1_SAIEN) != 0 )
{
if( (HAL_GetTick() - u32TickStart) > (uint32_t)SAI_TO_DISABLE_MS ) break;
}
/* check */
if( (SAI_ADC_12->CR1 & SAI_xCR1_SAIEN) != 0 )
{
#if DEBUG_SAI_ERR
uart_printf(SZC_TEXT_ERR "SaiAdcDmaStop() SAIEN != 0\n\r");
#endif /* DEBUG_SAI_ERR */

u8RetVal = HAL_ERROR;
}

DMA1_Channel1->CCR &= ~DMA_CCR_EN;

u8AdcDmaActive = 0;

/* clear DMA flags */
DMA1->IFCR = DMA_IFCR_CGIF1;

return u8RetVal;
}

And here’s the SAI and DMA init:

/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
/* SAI 1 A
* ADC IN
* init
*/
void Sai1AdcInit(void)
{
Sai1AdcIoDmaInit();

/* reset registers */
SAI_ADC_12->CR1 = 0;
SAI_ADC_12->CR2 = 0;
SAI_ADC_12->FRCR = 0;
SAI_ADC_12->SLOTR = 0;

/* CR1: most important settings here
* HAL init: 088B02E1 with DMA & SAI enables
* DMA and SAI enable are set when started
* MCKEN = 0 -> no MCLK used
* OSR = 0 -> x256
* MCKDIV = 8 -> depends on clock
* NODIV = 1
* SYNCEN = 00 -> async mode
* CKSTR = 1 -> I2S: sample on SCK rising
* DS = 111 -> data size 32 bit
* PRTCFG = 00 -> "free" protocol -> I2S
* MODE = 01 -> master receiver
*/
SAI_ADC_12->CR1 = ( SAI_xCR1_MCKDIV_3 |
SAI_xCR1_NODIV |
SAI_xCR1_CKSTR |
SAI_xCR1_DS |
SAI_xCR1_MODE_0 );

/* FRCR: frame configuration -> FS = LRCK
* HAL init: 00031F3F
* FSOFF = 1 -> 1 bit offset for I2S #### MUST for PCM1822 ####
* FSPOL = 1 -> FS is active high (rising edge)
* FSDEF = 1 -> FS signal is a start of frame signal + channel side identification
* FSALL[6:0] = 32 -1 -> Frame synchronization active level length
* FRL[7:0] = 64 -1 -> Frame length
*/
SAI_ADC_12->FRCR = ( SAI_xFRCR_FSPOL | SAI_xFRCR_FSDEF |
SAI_xFRCR_FSOFF |
((32 - 1) << SAI_xFRCR_FSALL_Pos) |
((64 - 1) << SAI_xFRCR_FRL_Pos) );

/* SLOTR: slot configuration
* HAL init: FFFF0180
* SLOTEN[15:0] = 3 -> slot enable 1 & 2
* NBSLOT[3:0] = 1 +1 -> number of slots in an audio frame
* SLOTSZ[1:0] = 10 -> 32b slot size
* FBOFF[4:0] = 0 -> first bit offset
*/
SAI_ADC_12->SLOTR = ( ((3) << SAI_xSLOTR_SLOTEN_Pos) |
((1) << SAI_xSLOTR_NBSLOT_Pos) |
SAI_xSLOTR_SLOTSZ_1 );
}


/* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
/* SAI
* GPIO & DMA init
*/
void Sai1AdcIoDmaInit(void)
{
/* ++++++++++++++++++++++++++++++++++++++++++++++++++++ */
if( (RCC->APB2ENR & RCC_APB2ENR_SAI1EN) != RCC_APB2ENR_SAI1EN )
{
__IO uint32_t u32TmpReg;

RCC->APB2ENR |= RCC_APB2ENR_SAI1EN;
u32TmpReg = ( RCC->APB2ENR & RCC_APB2ENR_SAI1EN );
(void)u32TmpReg;
}

GPIO_InitTypeDef GPIO_InitStruct = { 0 };

/* ++++++++++++++++++++++++++++++++++++++++++++++++++++ */
/** SAI1_A_Block_A GPIO Configuration
PA8 ------> SAI1_SCK_A
PA9 ------> SAI1_FS_A
PA10 ------> SAI1_SD_A
MCLK not used
*/
GPIO_InitStruct.Pin = SAI_1_SCLK_Pin | SAI_1_LRCK_Pin | SAI_1_A_DATA_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF14_SAI1;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);


/* ++++++++++++++++++++++++++++++++++++++++++++++++++++ */
/* DMA init */

DMA1_Channel1->CCR = 0;
DMA1_Channel1->CPAR = (uint32_t)&(SAI_ADC_12->DR);
DMA1_Channel1->CMAR = (uint32_t)i32AdcInBuf;
DMA1_Channel1->CNDTR = 0; /* later = (uint32_t)AUDIO_DMA_BUF_SIZE; */

/* CCR
* PL priority level: = 11 = very high
* MSIZE memory size: = 10 = 32b
* PSIZE periph size: = 10 = 32b
* MINC memory increment = 1
* CIRC circular mode = 1
* DIR read from periph = 0
* interrupt enables:
* TEIE transfer error = 1
* HTIE half transfer = 1
* TCIE transfer complete = 1
*/
DMA1_Channel1->CCR = ( DMA_CCR_PL_1 | DMA_CCR_PL_0 |
DMA_CCR_MSIZE_1 | DMA_CCR_PSIZE_1 |
DMA_CCR_MINC |
DMA_CCR_CIRC |
DMA_CCR_TEIE | DMA_CCR_HTIE | DMA_CCR_TCIE );

/* DMA MUX
* ATTENTION: MUX channel = DMA channel - 1
*/
DMAMUX1_Channel0->CCR = DMA_REQUEST_SAI1_A;

/* clear DMA flags */
DMA1->IFCR = DMA_IFCR_CGIF1;
}

Anybody any ideas?

Thanks in advance!