Skip to main content
LCE
Principal III
September 23, 2026
Solved

STM32G431: ADC1 channel sequence confusion

Heyho,

I’m confused - or the STM32G431KBU (in QFN32) is…

I’m using ADC 1 with 2 external inputs, PA0, PA1, and the internal “REFI” and temperature channel “TEMP”.

All running with continuous and circular DMA in the background, no interrupts. If I need an ADC value, I simply grab it from the ADC DMA buffer.

This is basically working, except for the channel sequence. Which I set manually, but have at least triple-checked the SQR1 / SQR2 values with the RM...

 

If I set up the channel sequence in ADC1->SQR1 in the order: PA0, PA1, REFI, TEMP, then I find in the DMA buffer instead of this order: 0, PA0, REFI, TEMP
→ so PA0 was “shifted”, PA1 is nowhere to be found (it is not 0, checked the GPIO via scope).

 

If I add and insert a dummy channel “VDMY” as the first in the sequence, so that the sequence should result in VDMY, PA0, PA1, REFI, TEMP, I find this order in the DMA buffer:
PA1, VDMY, PA0, REFI, TEMP

At least I get all the channels now, the measurement data is correct.

 

So, what am I messing up in the ADC & DMA setup?

Something in the init sequence concerning DMA / ADC ?

 

u16Adc1DmaBuf[80]

00 1313 0000 1972 1504 0936
05 1313 0000 1972 1504 0936
10 1313 0000 1972 1504 0936
15 1313 0000 1972 1504 0936
20 1313 0000 1972 1503 0936
25 1313 0000 1972 1504 0936
30 1313 0000 1972 1504 0936
35 1313 0000 1972 1504 0936
40 1313 0000 1972 1503 0936
45 1313 0000 1972 1504 0936
50 1313 0000 1972 1504 0936
55 1313 0000 1972 1504 0936
60 1313 0000 1972 1504 0936
65 1313 0000 1972 1504 0936
70 1313 0000 1972 1504 0936
75 1313 0000 1972 1504 0936
*** end
VDMY PA0 PA1 REFI TEMP <- that' what it should be, but it is:
PA1 VDMY PA0 REFI TEMP

The ADC registers:

ADC1->
ISR 0000000B
IER 00000000
CR 10000005
CFGR 80003003
CFGR2 000000D5
SMPR1 36DB6DB6
SMPR2 06DB6DB6
SQR1 12040084
SQR2 00000010
SQR3 00000000
SQR4 00000000
DR 00000000
CALFACT 0000003A
OFR1 00000000
OFR2 00000000
OFR3 00000000
OFR4 00000000
DIFSEL 00000000

ADC12_COMMON->
CSR 0000000B
CCR 00D00000
CDR 00000000

DMA1_Channel3->
CCR 000015A9
CNDTR 27
CMAR 20000A24
CPAR 50000040
&ADC1->DR 50000040

 

And here’s the ADC & DMA init and start:

/* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
/* ADC1 init function
*/
uint8_t Adc1Init(void)
{
uint32_t u32StartTick = 0;

/* ++++++++++++++++++++++++++++++++++++++++++++++++++++ */
/* ADC1 clock enable */
if( (RCC->AHB2ENR & RCC_AHB2ENR_ADC12EN) != RCC_AHB2ENR_ADC12EN )
{
__IO uint32_t u32TmpReg;

RCC->AHB2ENR |= RCC_AHB2ENR_ADC12EN;
u32TmpReg = ( RCC->AHB2ENR & RCC_AHB2ENR_ADC12EN );
(void)u32TmpReg;
}

/* ++++++++++++++++++++++++++++++++++++++++++++++++++++ */
/* register reset */
ADC1->CR = 0;
ADC1->CFGR = 0;
ADC1->CFGR2 = 0;
ADC1->SMPR1 = 0;
ADC1->SMPR2 = 0;

/* sequence set later */
ADC1->SQR1 = 0;
ADC1->SQR2 = 0;
ADC1->SQR3 = 0;
ADC1->SQR4 = 0;

/* no offsets */
ADC1->OFR1 = 0;
ADC1->OFR2 = 0;
ADC1->OFR3 = 0;
ADC1->OFR4 = 0;
/* single-ended */
ADC1->DIFSEL = 0;

/* no interrupts */
ADC1->IER = 0;
ADC1->ISR = 0x000007FF;

ADC12_COMMON->CCR = 0;

/* ++++++++++++++++++++++++++++++++++++++++++++++++++++ */
/* GPIO & DMA init */
Adc1GpioDmaInit();

/* ++++++++++++++++++++++++++++++++++++++++++++++++++++ */
/* register settings */

/* CFGR
* JQ disable,
* continuous & overrun mode,
* DMA set later: DMA circular, DMA on
*/
ADC1->CFGR = ( ADC_CFGR_JQDIS |
ADC_CFGR_CONT | ADC_CFGR_OVRMOD );

/* CFGR2
* oversampling:
* enable, x64, shift 6
*/
ADC1->CFGR2 = ( ADC_CFGR2_ROVSE |
(ADC_CFGR2_OVSR_2 | ADC_CFGR2_OVSR_0) |
(ADC_CFGR2_OVSS_2 | ADC_CFGR2_OVSS_1) );

/* ++++++++++++++++++++++++++++++++++++++++++++ */
/* sampling CYCLE time, same for all channels */
#if( 0 )
ADC1->SMPR1 = 0x00000036;
ADC1->SMPR2 = 0x06180000;
#elif( 1 )
ADC1->SMPR1 = ( (ADC_SAMPLTIME_CYCLES_ALL << 0) | (ADC_SAMPLTIME_CYCLES_ALL << 3) | (ADC_SAMPLTIME_CYCLES_ALL << 6) |
(ADC_SAMPLTIME_CYCLES_ALL << 9) | (ADC_SAMPLTIME_CYCLES_ALL << 12) | (ADC_SAMPLTIME_CYCLES_ALL << 15) |
(ADC_SAMPLTIME_CYCLES_ALL << 18) | (ADC_SAMPLTIME_CYCLES_ALL << 21) | (ADC_SAMPLTIME_CYCLES_ALL << 24) |
(ADC_SAMPLTIME_CYCLES_ALL << 27) );
ADC1->SMPR2 = ( (ADC_SAMPLTIME_CYCLES_ALL << 0) | (ADC_SAMPLTIME_CYCLES_ALL << 3) | (ADC_SAMPLTIME_CYCLES_ALL << 6) |
(ADC_SAMPLTIME_CYCLES_ALL << 9) | (ADC_SAMPLTIME_CYCLES_ALL << 12) | (ADC_SAMPLTIME_CYCLES_ALL << 15) |
(ADC_SAMPLTIME_CYCLES_ALL << 18) | (ADC_SAMPLTIME_CYCLES_ALL << 21) | (ADC_SAMPLTIME_CYCLES_ALL << 24) );
#else
ADC1->SMPR1 = ( (ADC_SAMPLTIME_CYCLES_ALL << 0) | (ADC_SAMPLTIME_CYCLES_ALL << 3) );
ADC1->SMPR2 = ( (ADC_SAMPLTIME_CYCLES_ALL << 18) | (ADC_SAMPLTIME_CYCLES_ALL << 24) );
#endif

/* ++++++++++++++++++++++++++++++++++++++++++++ */
/* channel SEQUENCER configuration */
#if( 0 )
ADC1->SQR1 = 0x10481003;
#else
ADC1->SQR1 = 0;
ADC1->SQR2 = 0;
Adc1ConfigChannelSeq(ADC_CHANNEL_EXT_2, 1);
Adc1ConfigChannelSeq(ADC_CHANNEL_EXT_0, 2);
Adc1ConfigChannelSeq(ADC_CHANNEL_EXT_1, 3);
Adc1ConfigChannelSeq(ADC_CHANNEL_INT_18, 4);
Adc1ConfigChannelSeq(ADC_CHANNEL_INT_16, 5);
/* sequence length */
ADC1->SQR1 |= (uint32_t)(ADC_CHANNELS_NUM - 1);
#endif

/* ++++++++++++++++++++++++++++++++++++++++++++ */
/* wake up temperature sensor / enable "internal path" to ADC */
ADC12_COMMON->CCR |= ADC_CCR_VSENSESEL;
/* enable VREFINT "internal path" to ADC */
ADC12_COMMON->CCR |= ADC_CCR_VREFEN;
/* set prescaler */
ADC12_COMMON->CCR |= ADC_CCR_PRESC_2;

/* ++++++++++++++++++++++++++++++++++++++++++++ */
/* voltage regulator enable */
ADC1->CR |= ADC_CR_ADVREGEN;
u32StartTick = HAL_GetTick();
while( (HAL_GetTick() - u32StartTick) < (uint32_t)ADC_TIMEOUT_MS_REGEN );

/* ++++++++++++++++++++++++++++++++++++++++++++ */
/* calibration */
ADC1->CR &= ~ADC_CR_ADEN;
ADC1->CR |= ADC_CR_ADCAL;
u32StartTick = HAL_GetTick();
while( ((HAL_GetTick() - u32StartTick) < (uint32_t)ADC_TIMEOUT_MS_CAL) &&
((ADC1->CR & ADC_CR_ADCAL) == ADC_CR_ADCAL) );

if( (ADC1->CR & ADC_CR_ADCAL) == ADC_CR_ADCAL )
{
#if DEBUG_ADC_ERR
uart_printf(SZC_TEXT_ERR "ADC CAL\n\r");
uart_printf("CR %08lX\n\r", ADC1->CR);
uart_printf(SZC_TEXT_ERR "ADC not started\n\r");
#endif /* DEBUG_ADC_ERR */

return HAL_ERROR;
}

/* ++++++++++++++++++++++++++++++++++++++++++++ */
/* DMA start */
Adc1DmaStart();

/* ADC enable */
ADC1->CR |= ADC_CR_ADEN;

/* wait for ADC RDY */
u32StartTick = HAL_GetTick();
while( ((HAL_GetTick() - u32StartTick) < (uint32_t)ADC_TIMEOUT_MS_RDY) &&
((ADC1->ISR & ADC_ISR_ADRDY) == 0) );

if( (ADC1->ISR & ADC_ISR_ADRDY) == 0 )
{
#if DEBUG_ADC_ERR
uart_printf(SZC_TEXT_ERR "ADC ADRDY\n\r");
uart_printf("ISR %08lX\n\r", ADC1->ISR);
uart_printf(SZC_TEXT_ERR "ADC not started\n\r");
#endif /* DEBUG_ADC_ERR */

return HAL_ERROR;
}

/* ++++++++++++++++++++++++++++++++++++++++++++ */
/* start ADC */
ADC1->CR |= ADC_CR_ADSTART;

return HAL_OK;
}


/* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
/* ADC1
* clock, GPIO & DMA init
*/
void Adc1GpioDmaInit(void)
{
/* ++++++++++++++++++++++++++++++++++++++++++++++++++++ */
/* ADC1 GPIO Configuration
* PA0 ------> "ADM_IN0" in schematic
* -> VPWR = ~ 5V, divider 47k / 22k
* PA1 ------> "ADM_IN1" in schematic
* -> VRECT = ~ 6V..12V, divider 47k / 10k
*/
GPIO_InitTypeDef GPIO_InitStruct = { 0 };

/* clock enabled in gpio.c */

GPIO_InitStruct.Pin = ADC_IN0_Pin | ADC_IN1_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(ADC_IN_GPIO_Port, &GPIO_InitStruct);

/* ++++++++++++++++++++++++++++++++++++++++++++++++++++ */
/* DMA init */
DMA1_Channel3->CCR = 0;
DMA1_Channel3->CPAR = (uint32_t)&ADC1->DR;
DMA1_Channel3->CMAR = (uint32_t)u16Adc1DmaBuf;
DMA1_Channel3->CNDTR = 0; /* later = (uint32_t)ADC_DMA_BUF_SIZE; */

/* DMA MUX
* ATTENTION: MUX channel = DMA channel - 1
*/
DMAMUX1_Channel2->CCR = DMA_REQUEST_ADC1;

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

/* CCR */
DMA1_Channel3->CCR = ( DMA_CCR_PL_0 |
DMA_CCR_MSIZE_0 | DMA_CCR_PSIZE_0 |
DMA_CCR_MINC |
DMA_CCR_CIRC |
DMA_CCR_TEIE );
}


/* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
/* ADC1 channel sequence setting
* channel & rank
*/
uint8_t Adc1ConfigChannelSeq(uint32_t u32Channel, uint32_t u32Rank)
{
if( (ADC1->CR & ADC_CR_ADSTART) != 0 ) return HAL_BUSY;

/* set bit shift */
uint32_t u32Shift = (uint32_t)(u32Rank * ADC_RANK_BASE_1);
/* set SQRx register */
volatile uint32_t *pu32Reg = &ADC1->SQR1;

/* check rank */
if( u32Rank > 4 )
{
pu32Reg = &ADC1->SQR2;
u32Rank -= 4;
u32Shift = (uint32_t)((u32Rank - 1) * ADC_RANK_BASE_1);
}

pu32Reg += (uint32_t)(((u32Shift & ADC_REG_SQRX_REGOFFSET) >> ADC_SQRX_REGOFFSET_Pos) << 2UL);

/* clear rank */
*pu32Reg &= ~(ADC_RANK_CHNL_MASK << (u32Shift & ADC_RANK_CHNL_MASK));
/* set channel to rank */
*pu32Reg |= (u32Channel << (u32Shift & ADC_RANK_CHNL_MASK));

return HAL_OK;
}


/* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
/* ADC DMA start or re-start
*/
void Adc1DmaStart(void)
{
/* stop if still running */
if( u8Adc1DmaActive != 0 ) Adc1DmaStop();

DMA1_Channel3->CCR &= ~DMA_CCR_EN;

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

DMA1_Channel3->CPAR = (uint32_t)&ADC1->DR;
DMA1_Channel3->CMAR = (uint32_t)u16Adc1DmaBuf;
DMA1_Channel3->CNDTR = (uint32_t)ADC_DMA_BUF_SIZE;

/* clear ADC (DMA) flags ? */
ADC1->ISR = (uint32_t)0x7FE;

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

/* peripheral DMA enable */
ADC1->CFGR |= ADC_CFGR_DMACFG;
ADC1->CFGR |= ADC_CFGR_DMAEN;

u8Adc1DmaActive = 1;
}


 

Thanks in advance!

 

 

waclawek.jan
waclawek.janBest answer
Super User
September 24, 2026

Thanks for coming back with the solution.

It kinda *is* documented, both in DS and RM; but I agree that it *is* confusing.

JW

16 replies

Associate II
September 23, 2026

If I was doing this on a lower level than HAL I would be concerned that ADC1 and ADC2, Temp and Vref are all independent (as they are on STM32G431.

Then I would set up independent DMA channels to move them to 4 different ring buffers with the same buffer size and sampling interval for all.

It would then be a case of having a ring buffer pointer to all four rings that read them at the same point in time and moved onto the next sample. 

Reading your code HAL etc is sort of doing this but it needs to either have four ring buffers or know the interleaving order (and keep same sampling interval) and use one interleaved buffer.  Then there is the matter of reading that interleaved buffer and exactly where to read it.

Its often hard to sync where data is going into the ring and where data is coming out of the ring and your results suggest the possibility that the input pointer is close to or on top of the output pointer so that say three channels are new data and one is old data.

I would suggest a) that the length of the ring buffer is an integer multiple of the number of channels (eg for four channels 0x04 the buffer is say 0x04 x 0x1000 = 0x4000) that will help greatly and b) that output pointer lags behind input pointer so that the pointers cannot overlap.

Then there is the matter of how the DMA sequences reading the four sensors into the ring.

There is a lot to be said for having a ring buffer per sensor if software will allow.

What I have also done in the past is use the ring buffer for moving average filtering and that works well if the ring is same length as the filter so input sequentially and output = sum of all values in the ring divided by ring length.

Hope that helps give an idea to solve, it would take me too long to read all the code and get up to speed on it. Cheers

 

 

LCE
LCEAuthor
Principal III
September 24, 2026

​@rich.g.williams : thanks for checking, but completely wrong assumptions on your side: these are not 4 ADCs which can have their own DMA channel.

These are are 2 “pin-inputs” (PA0, PA1), and the internal signals (voltage reference and temperature sensor) have their own channels connected to ADC1.

I have done this before, simply setting up the ADC in this way and letting it run continuously with DMA in the background, but on a H7 (with 9 channels on ADC3), and on a L03x & L07x. With these STM32 I don’t have this channel shift.

TDK
September 23, 2026

I don’t see any issues in the code presented.

 

I suspect the issue is somewhere in your assumptions. Probably:

  • Pins are not connected in the manner you think.
  • Code running is not what you think, or additional code is running that you are missing.

Possibly there is a bug in the relevant code not shown. For example, configuration of CR is not shown anywhere. It’s happening clearly, but where and what else is that code doing.

 

Showing the actual output of the program could also help. I see “ADC CAL\n\r” in the code but not in the results shown. Similar for other log statements. Showing the code that outputs results could also help.

 

Calibration should probably be done before configuration. Not sure if it matters.

"If you feel a post has answered your question, please click ""Accept as Solution""."
LCE
LCEAuthor
Principal III
September 24, 2026

​@TDK thanks for checking.

 

> Pins are not connected in the manner you think.

Checked that with scope and datasheet (which I suppose is correct), hardware seems to be correct.

 

> Code running is not what you think, or additional code is running that you are missing.

I’m working alone on this project, so I think I have all under control, there should not be anything else influencing the ADC code.

 

ADC1->CR is first nulled, then set up later as shown in the code above.
In CR only ADCAL, ADVREGEN, ADEN, and ADSTART are set, all “by the book” with some timeout and bit checking if needed.

 

But I will check the sequence once more, esp. concerning setup and CAL.

Ozone
Principal
September 24, 2026

> Checked that with scope and datasheet (which I suppose is correct), hardware seems to be correct.

I use to stop the code in the debugger after the ADC config is written, read out the config registers, and cross-check it with the reference manual.

I don’t know the G4xx in detail, but in many derivates, the internal channels get some “special treatment”.
I would check the reference manual here in detail.

LCE
LCEAuthor
Principal III
September 24, 2026

​@TDK I forgot: the UART output is only used in case of errors:

 

	/* ++++++++++++++++++++++++++++++++++++++++++++ */
/* calibration */
ADC1->CR &= ~ADC_CR_ADEN;
ADC1->CR |= ADC_CR_ADCAL;
u32StartTick = HAL_GetTick();
while( ((HAL_GetTick() - u32StartTick) < (uint32_t)ADC_TIMEOUT_MS_CAL) &&
((ADC1->CR & ADC_CR_ADCAL) == ADC_CR_ADCAL) );

if( (ADC1->CR & ADC_CR_ADCAL) == ADC_CR_ADCAL )
{
#if DEBUG_ADC_ERR
uart_printf(SZC_TEXT_ERR "ADC CAL\n\r");
uart_printf("CR %08lX\n\r", ADC1->CR);
uart_printf(SZC_TEXT_ERR "ADC not started\n\r");
#endif /* DEBUG_ADC_ERR */

return HAL_ERROR;
}

“SZC_TEXT_ERR” = “#E ” which is my “UART error tag”

So only if calibration fails, I get this output.

I also check all registers via UART, all looking good. It’s quite tedious checking the SQRn registers…

 

PS: I just moved calibration directly after peripheral clock activation and “register reset”, so all ADC registers = 0, then voltage regulator enable (with wait as prescribed).

No matter where I put the setup steps, it either doesn’t work at all (this I can confirm with the RM), or the channels are still mixed up.

waclawek.jan
Super User
September 24, 2026

I wouldn’t enable DMA until after the calibration, just before starting the regular ADC conversions; and I’d make sure the ADC status register is explicitly cleared just before enabling DMA. I’d also stop and check registers in debugger at that very point, too.

But I can’t explain the observed behaviour from the description either. Don’t have a ‘G4 either and it does not make much sense to try to reproduce on other hardware as ADCs tend to have small but significant differences across STM32 families.

JW

LCE
LCEAuthor
Principal III
September 24, 2026

> I wouldn’t enable DMA until after the calibration, just before starting the regular ADC conversions

That’s what I do.

I prepare the DMA relatively early in ADC init, but it gets enabled only before setting ADC_CR_ADSTART.

 

> I’d also stop and check registers in debugger at that very point, too.

Register settings look good, at all times, and same in debugger and UART output.

 

>  it does not make much sense to try to reproduce on other hardware 

Absolutely! Very similar code is running on H7 and L0. Similar, but not the same.
All STM32 ADCs are not built equal! ;-)

LCE
LCEAuthor
Principal III
September 24, 2026

Got it!

And as almost always, it was a simple thing:

I checked the HAL definitions (using like a 100 other definitions, incl. LL_) and found that when using “PA0” as external ADC input, the internal channel number is not 0, but 1. And accordingly PA1 → channel 2.

I cannot find any such hint in the DS or RM, this should be documented more clearly somewhere in the RM.

 

Thanks to all for having a look at it!

waclawek.jan
waclawek.janBest answer
Super User
September 24, 2026

Thanks for coming back with the solution.

It kinda *is* documented, both in DS and RM; but I agree that it *is* confusing.

JW

Ozone
Principal
September 24, 2026

Admittedly, the DSs and RMs are not for the faint-hearted.

However, I had seen much worse.