Associate II
June 16, 2026
Solved
STM32C071: ADC overrun when erasing flash
- June 16, 2026
- 2 replies
- 65 views
Hi,
I met a strange issue of ADC overrun during flash erase.
The ADC is configured with circular DMA+TIM3 triggered at 1khz:
static void ADC_Config(void) {
/* Enable clock */
RCC->AHBENR |= RCC_AHBENR_DMA1EN;
RCC->APBENR2 |= RCC_APBENR2_ADCEN;
RCC->APBENR1 |= RCC_APBENR1_TIM3EN;
/* ADC Init */
ADC1->CR |= ADC_CR_ADVREGEN;
uint32_t tick = SysTick->VAL;
/* Startup delay */
while ((tick - SysTick->VAL) < 960);
/* Calibration */
ADC1->CR |= ADC_CR_ADCAL;
while (ADC1->CR & ADC_CR_ADCAL);
/* TIM3 trigger, DMA enable */
ADC1->CFGR1 = ADC_CFGR1_EXTEN_0 | 3 << ADC_CFGR1_EXTSEL_Pos | ADC_CFGR1_DMACFG | ADC_CFGR1_DMAEN;
/* Use 14MHz ADC clock */
ADC1->CFGR2 = ADC_CFGR2_CKMODE_0;
/* Enable overrun interrupt */
ADC1->IER = ADC_IER_OVRIE;
/* Long sampling time */
ADC1->SMPR = ADC_SMPR_SMP1_0 | ADC_SMPR_SMP1_1 | ADC_SMPR_SMP1_2;
/* Vref and Tint enable */
ADC1->CHSELR = ADC_CHSELR_CHSEL9 | ADC_CHSELR_CHSEL10;
ADC->CCR = ADC_CCR_TSEN | ADC_CCR_VREFEN;
/* Enable ADC */
ADC1->ISR |= ADC_ISR_ADRDY;
ADC1->CR |= ADC_CR_ADEN;
while (!(ADC1->ISR & ADC_ISR_ADRDY));
/* DMA Init */
/* ADC1 request */
DMAMUX1_Channel0->CCR = 5;
DMA1_Channel1->CPAR = (uint32_t) &ADC1->DR;
DMA1_Channel1->CMAR = (uint32_t) AnBuf;
DMA1_Channel1->CNDTR = 2;
/* Size 16bits, memory increment, circular mode */
DMA1_Channel1->CCR = DMA_CCR_MSIZE_0 | DMA_CCR_PSIZE_0 | DMA_CCR_MINC | DMA_CCR_CIRC;
DMA1_Channel1->CCR |= DMA_CCR_EN;
/* TIM Init */
/* UEV as TRGO */
TIM3->CR2 = 2 << TIM_CR2_MMS_Pos;
TIM3->PSC = 12 - 1;
TIM3->ARR = 1000 - 1;
TIM3->CR1 |= TIM_CR1_CEN;
/* Enable ADC */
NVIC_SetPriority(ADC1_IRQn, 0);
NVIC_SetPriority(DMA1_Channel1_IRQn, 0);
NVIC_EnableIRQ(ADC1_IRQn);
NVIC_EnableIRQ(DMA1_Channel1_IRQn);
/* Start ADC conversion */
ADC1->CR |= ADC_CR_ADSTART;
}I’m aware that accessing flash during operation will block the CPU in a waiting state, so the flash erasing function is executed in RAM (even if it’t the case DMA should be unaffected as it’s a difference bus master ?):
__ramfunc int NVM_fErase(uint_fast8_t Page) {
if (Page >= NVM_PAGE_CNT) {
return -1;
}
__disable_irq();
FLASH->KEYR = 0x45670123;
FLASH->KEYR = 0xCDEF89AB;
__enable_irq();
while (FLASH->CR & FLASH_CR_LOCK);
/* Clear all error flags */
FLASH->SR = 0x0000FFFF;
while (FLASH->SR & FLASH_SR_BSY1) {
if (FLASH->SR & (FLASH_SR_PGSERR | FLASH_SR_SIZERR | FLASH_SR_PGAERR | FLASH_SR_WRPERR | FLASH_SR_PROGERR)) {
FLASH->CR |= FLASH_CR_LOCK;
return -1;
}
}
FLASH->CR |= FLASH_CR_PER;
MODIFY_REG(FLASH->CR, FLASH_CR_PNB, ((NVM_BASE_ADDR - 0x08000000) / NVM_PAGE_SIZE + Page) << FLASH_CR_PNB_Pos);
FLASH->CR |= FLASH_CR_STRT;
while (FLASH->SR & FLASH_SR_BSY1) {
if (FLASH->SR & (FLASH_SR_PGSERR | FLASH_SR_SIZERR | FLASH_SR_PGAERR | FLASH_SR_WRPERR | FLASH_SR_PROGERR)) {
FLASH->CR |= FLASH_CR_LOCK;
return -1;
}
}
FLASH->SR = FLASH_SR_EOP;
FLASH->CR &= ~FLASH_CR_PER;
FLASH->CR |= FLASH_CR_LOCK;
return 0;
}
Also to prevent instruction fetching of other interrupts:
- Vector table is copied to RAM
- IRQ handlers are in RAM
I also tried to catch unexpected flash access with MPU by disabling flash and ROM region access:
{ARM_MPU_RBAR(0, 0x08000000),
ARM_MPU_RASR(1UL, ARM_MPU_AP_NONE, 0UL, 1UL, 0UL, 0UL, 0x00UL, ARM_MPU_REGION_SIZE_128KB)},
{ARM_MPU_RBAR(1, 0x1FFF0000),
ARM_MPU_RASR(1UL, ARM_MPU_AP_NONE, 0UL, 1UL, 0UL, 1UL, 0x00UL, ARM_MPU_REGION_SIZE_32KB)},
I stripped down a minimal demo running on Nucleo-C071RB build with IAR 9.70.4, once you press the button you should enter ADC1_IRQHandler with OVR flag set.
