Skip to main content
Graduate II
October 27, 2025
Solved

L073: ADC & DMA, strange buffer "shift" in CIRCULAR mode

  • October 27, 2025
  • 11 replies
  • 472 views

Heyho,

strange things happening that I'd like to understand for a STM32L073 (Nucleo for now).

Setup on a L073 @ 32MHz:

- ADC is clocked by PCLK/4
- ADC prescaler = 2 -> divide by 4 -> 2 MHz
- 12 bit right aligned
- ref V = VDDA = 3.3V
- hardware oversampling + shift is used (great feature!)
- 7 ADC channels sequenced
- DMA, with buffer for 8 samples per channel -> u16AdcDmaBuf[56]

 

In non-circular mode (CIRC neither set in DMA- nor in AD-registers), after each DMA's transfer complete interrupt, DMA and ADC are restarted, everything is as it should be, and as it is set in the sequencing registers.

Here's what the DMA buffer looks like, and all this makes sense:
buffer[0] = 2031 is the DAC output set to 2047,
buffer[1] & buffer[2] = 4095 are connected to VDDA -> 4095 ok,
buffer[5] is internal VREF = 1.2V -> perfect,
buffer[6] is internal temperature sensor -> ~24°C -> perfect

1) ADC & DMA: not circular

DmaBuf
[00] 2031 4095 4095 1926 3083 1524 0604
[07] 2031 4095 4095 1926 3083 1524 0604
[14] 2031 4095 4095 1927 3081 1525 0604
[21] 2031 4095 4095 1925 3081 1525 0605
[28] 2031 4095 4095 1926 3081 1525 0605
[35] 2031 4095 4095 1926 3081 1525 0605
[42] 2031 4095 4095 1926 3081 1525 0605
[49] 2031 4095 4095 1926 3081 1526 0605
 DAC2 PWR5 PWR3 SNSA SNS5 REFI TMPI

registers:
ADC1->
ISR 0000000B
IER 00000000
CR 10000005
 REGEN
 START
 EN
CFGR1 = 00003001
 CONT
 OVRMOD
 right aligned
 DMAEN

DMA1_Channel1->
CCR 0000358B
 EN
 TCIE
 TEIE
 MINC
 MSIZE = 1 = 16b
 PSIZE = 1 = 16b

 

Now (all before ADC / DMA start) I turn on circular mode in ADC1->CFGR1 |= DMACFG, 
and in DMA1_Channel1->CCR |= CIRC.
And now I still get some nice AD values, but the buffer has shifted by 1, compare to above:

2) ADC & DMA: CIRCular mode

DmaBuf
[00] 0602 2031 4095 4095 1926 3085 1525
[07] 0602 2031 4095 4095 1926 3084 1525
[14] 0602 2031 4095 4095 1925 3083 1525
[21] 0602 2031 4095 4095 1927 3084 1525
[28] 0602 2031 4095 4095 1926 3084 1525
[35] 0602 2031 4095 4095 1926 3084 1525
[42] 0602 2031 4095 4095 1926 3083 1525
[49] 0602 2031 4095 4095 1926 3084 1525
 DAC2 PWR5 PWR3 SNSA SNS5 REFI TMPI

registers:
ADC1->
ISR 0000000B
IER 00000000
CR 10000005
 REGEN
 START
 EN
CFGR1 = 00002003
 CONT
 right
 12b
 DMACFG = CIRC
 DMAEN

DMA1_Channel1->
CCR 000035AB
 EN
 TCIE
 TEIE
 CIRCular
 MINC
 MSIZE = 1 = 16b
 PSIZE = 1 = 16b

 

Note: register dumps are from running application

What am I missing? Been checking all registers, DS, RM, feels like I have overseen one bit or so...

 

Thanks in advance!

    This topic has been closed for replies.
    Best answer by gbm

    In the code above you are setting ADC DMAEN and DMACFG bits BEFORE calibration. This might be the problem. Do it AFTER calibration is completed.

    11 replies

    Super User
    October 27, 2025

    How does OVRMOD get its value changed between the two? Feels like that may explain it.

    LCEAuthor
    Graduate II
    October 27, 2025

    Ah, thanks for the hint, that was my latest test: same behavior in both cases if OVRMOD is set or not.

    LCEAuthor
    Graduate II
    October 27, 2025

    The biggest difference might be the access to the DMA target registers, which happens for both versions after DMA's transfer complete interrupt, so in circular mode when the "buffer shift" occurs, some values from the DMA buffer are copied while ADC & DMA are still active.

    In non-circular mode, the DMA buffer is only accessed when the ADC has stopped.

    On the other hand, accessing the DMA buffer for UART debug printf also happens when DMA & ADC are active in non-circular mode.

     

    Super User
    October 27, 2025

    > The biggest difference might be the access to the DMA target registers, which happens for both versions after DMA's transfer complete interrupt

    Are you changing the DMA stream's CMARx register while DMA is active (in circular mode)? That's not allowed.

    Or are you only reading the memory buffer and not accessing the target register? That's of course fine to do while DMA is active.

    LCEAuthor
    Graduate II
    October 27, 2025

    Thanks for your input!

    Are you changing the DMA stream's CMARx register while DMA is active (in circular mode)?

    No

    Or are you only reading the memory buffer and not accessing the target register?

    Yes, only accessing the buffer.

    I wondered if that might be a problem, but I cannot imagine how that could "shift" the buffer.

     

    It does NOT matter how the DMA is set up and what is done in transfer complete ISR:

    - DMA in non-circular mode, with a) disable b) new setting of counter and memory target register c) re-enable d) ADC re-start 

    - DMA in circular mode: only re-start ADC

    So only the ADC's circular bit ADC_CFGR1_DMACFG is the "problem-trigger".

    LCEAuthor
    Graduate II
    October 27, 2025

    It's such a simple MCU compared to the H7, so I've checked and played with every ADC & DMA bit.

    As soon as ADC_CFGR1_DMACFG is set, this buffer shift occurs.

    Maybe someone from ST has an idea about the L0 ADC & DMA? 

    @STea @Peter BENSCH ?

    Graduate
    October 27, 2025

    Check if calibration is completed before setting the DMA operation in ADC.

    Super User
    October 27, 2025

    Without seeing the code, it's hard to know exactly.

    I suspect the ADC has a previous transaction already in memory and so when DMA is enabled, it shifts that one out first. Reading the ADC data register before enabling DMA should clear this, but if ADC is converting continuously, it'll just get set again.

    DMA should be enabled before ADC is started.

     

    I imagine if you created a new project with DMA in circular mode from the start, with multiple channels, the problem will disappear.

    LCEAuthor
    Graduate II
    October 27, 2025

    @gbm  I do that.

    ADC register prep
    ...
    	/* ++++++++++++++++++++++++++++++++++++++++++ */
    	/* enable ADC internal voltage regulator */
    	ADC1->CR = ADC_CR_ADVREGEN;
    
    	/* delay for ADC internal voltage regulator stabilization,
    	 * 	compute number of CPU cycles to wait for, from delay in us
    	 * 		Note: Variable divided by 2 to compensate partially CPU processing
    	 * 				cycles (depends on compilation optimization).
    	 * 		Note: If system core clock frequency is below 200kHz, wait time
    	 * 				is only a few CPU processing cycles.
    	 */
    	uint32_t u32WaitVregCnt = ((LL_ADC_DELAY_INTERNAL_REGUL_STAB_US * (SystemCoreClock / (100000 * 2))) / 10);
    	while( u32WaitVregCnt != 0 ) u32WaitVregCnt--;
    
    	/* ADC calibration
    	 *	- before ADC enable
    	 *	- before DMA enable
    	 */
    	ADC1->CR |= ADC_CR_ADCAL;
    
    	uint8_t u8AdcCalErr = 0;
    	uint32_t u32TickStart = LL_GetTick();
    
    	while( (ADC1->CR & ADC_CR_ADCAL) != 0 )
    	{
    		/* timeout check */
    		if( (LL_GetTick() - u32TickStart) > ADC_TIMEOUT_CAL_MS )
    		{
    			u8AdcCalErr = 1;
    			break;
    		}
    	}
    
    	if( 0 == u8AdcCalErr )
    	{
    		/* reset EOCAL bit */
    		ADC1->ISR |= ADC_ISR_EOCAL;
    		u8ErrorPwr &= ~ERROR_PWR_CALAD;
    	}
    	else
    	{
    		#if DEBUG_ADC
    			uart_printf("#E ADC_Init() CAL, DMA off\n\r");
    		#endif 	/* DEBUG_ADC */
    
    		u8ErrorPwr |= ERROR_PWR_CALAD;
    
    		return;
    	}
    
    /* +++++++++++++++++++++++++++++++++++++++++++++++++++ */
    /* DMA settings and start */
    
    	DMA1_Channel1->CCR = 0;
    
    ...

      

    LCEAuthor
    Graduate II
    October 27, 2025

    ADC is started last:


    ...
    
    /* +++++++++++++++++++++++++++++++++++++++++++++++++++ */
    /* start prep & start */
    
    	/* DMA1_Channel1_IRQn interrupt configuration */
    	NVIC_SetPriority(DMA1_Channel1_IRQn, NVIC_IRQ_PRIO_DMA_ADC);
    	NVIC_EnableIRQ(DMA1_Channel1_IRQn);
    
    	/* enable DMA */
    	DMA1_Channel1->CCR |= DMA_CCR_EN;
    
    	/* enable VREFINT & temperature sensor */
    	ADC1_COMMON->CCR |= ( ADC_CCR_TSEN | ADC_CCR_VREFEN );
    
    	/* ADC enable */
    	ADC1->CR |= ADC_CR_ADEN;
    	/* ADC START */
    	ADC1->CR |= ADC_CR_ADSTART;
    }
    
    ​

     

    LCEAuthor
    Graduate II
    October 27, 2025

    offline for today, thanks so far !

    LCEAuthor
    Graduate II
    October 28, 2025

    To make sure it's not the DMA memory buffer access, I added the transfer half complete interrupt (HT).

    At HT buffer[0] is copied, at TC (transfer complete) buffer[last] is copied.

    No change, still buffer "shift".