Skip to main content
Visitor II
March 12, 2024
Question

ADC init hangs after code optimization

  • March 12, 2024
  • 2 replies
  • 1319 views

Controller: STM32G431VBT6
Libraries: CMSIS
Compiler: arm-none-eabi-gcc version 13.2.1
Code Optimization: OPT = -Os

After code optimization MCU hangs, depending on code constellation. It hangs in ADC initialization.

 

...
system_clock_init();
timer_init();
ADC_init();
USART_init();
...

 

 

void clock_init(void)
{
 // configure wait states (2.7 V .. 3.6 V @ 170 MHz)
 FLASH->ACR = (FLASH->ACR & ~FLASH_ACR_LATENCY) | FLASH_ACR_LATENCY_4WS;

 // enable and reset instruction cache
 FLASH->ACR |= FLASH_ACR_ICEN | FLASH_ACR_ICRST;

 // debug software enable
 FLASH->ACR |= FLASH_ACR_DBG_SWEN;

 // start crystal oscillator (HSE)
 RCC->CR |= RCC_CR_HSEON; // switch on crystal oscillator
 while((RCC->CR & RCC_CR_HSERDY) == 0);

 RCC->PLLCFGR = (8 << RCC_PLLCFGR_PLLPDIV_Pos); // P main PLLP division factor
 RCC->PLLCFGR |= RCC_PLLCFGR_PLLPEN; // R enable PLL P
 RCC->PLLCFGR |= ((2 - 2) << RCC_PLLCFGR_PLLR_Pos); // R main PLL division factor for PLL R clock (system cloc
 RCC->PLLCFGR |= RCC_PLLCFGR_PLLREN; // R enable PLL R
 RCC->PLLCFGR |= (85 << RCC_PLLCFGR_PLLN_Pos); // N main PLL multiplication factor for VCO
 RCC->PLLCFGR |= ((2 - 1) << RCC_PLLCFGR_PLLM_Pos); // M division factor for the main PLL input clock
 RCC->PLLCFGR |= (0b11 << RCC_PLLCFGR_PLLSRC_Pos); // HSE clock selected as PLL clock entry

 RCC->CR |= RCC_CR_PLLON; // switch on PLL
 while((RCC->CR & RCC_CR_PLLRDY) == 0);

 // select PLL as system clock
 RCC->CFGR = RCC_CFGR_SW_1 | RCC_CFGR_SW_0;
}
void ADC_init(void)
{
 // DMAMUX (see chapter 13 in manual)
 RCC->AHB1ENR |= RCC_AHB1ENR_DMAMUX1EN; // enable DMAMUX1 clock
 DMAMUX1_Channel4->CCR = 5; // DMA1_Channel5 - ADC1
 DMAMUX1_Channel5->CCR = 36; // DMA1_Channel6 - ADC2

 // DMA
 RCC->AHB1ENR |= RCC_AHB1ENR_DMA1EN; // enable DMA1 clock

 // DMA ADC1
 DMA1_Channel5->CCR = (0b01 << DMA_CCR_MSIZE_Pos); // memory data size 16 bit
 DMA1_Channel5->CCR |= (0b01 << DMA_CCR_PSIZE_Pos); // peripheral data size 16 bit
 DMA1_Channel5->CCR |= DMA_CCR_MINC; // memory address pointer is incremented after each data transfer
 DMA1_Channel5->CCR |= DMA_CCR_CIRC; // enable circular mode
 DMA1_Channel5->CCR |= (0b00 << DMA_CCR_DIR_Pos); // direction is peripheral to memory
 DMA1_Channel5->CNDTR = ADC1_NUM; // number of data items to transfer
 DMA1_Channel5->CPAR = (uint32_t) &ADC1->DR; // periphal address
 DMA1_Channel5->CMAR = (uint32_t) DMA_ADC1_buffer; // memory address
 NVIC_EnableIRQ(DMA1_Channel5_IRQn); // enable interrupt
 DMA1_Channel5->CCR |= DMA_CCR_EN; // enable DMA1_Channel5

 // DMA ADC2
 DMA1_Channel6->CCR = (0b01 << DMA_CCR_MSIZE_Pos); // memory data size 16 bit
 DMA1_Channel6->CCR |= (0b01 << DMA_CCR_PSIZE_Pos); // peripheral data size 16 bit
 DMA1_Channel6->CCR |= DMA_CCR_MINC; // memory address pointer is incremented after each data transfer
 DMA1_Channel6->CCR |= DMA_CCR_CIRC; // enable circular mode
 DMA1_Channel6->CCR |= (0b00 << DMA_CCR_DIR_Pos); // direction is peripheral to memory
 DMA1_Channel6->CNDTR = ADC2_NUM; // number of data items to transfer
 DMA1_Channel6->CPAR = (uint32_t) &ADC2->DR; // periphal address
 DMA1_Channel6->CMAR = (uint32_t) DMA_ADC2_buffer; // memory address
 NVIC_EnableIRQ(DMA1_Channel6_IRQn); // enable interrupt
 DMA1_Channel6->CCR |= DMA_CCR_EN; // enable DMA1_Channel6

 // ADC
 RCC->AHB2ENR |= RCC_AHB2ENR_ADC12EN; // enable ADC clock
 ADC12_COMMON->CCR |= (0b0000 << ADC_CCR_PRESC_Pos); // prescaler divided by 1
 ADC12_COMMON->CCR |= ADC_CCR_VSENSESEL; // enable temperature sensor channel
 ADC12_COMMON->CCR |= (0b11 << ADC_CCR_CKMODE_Pos); // select adc_hclk / 4

 // ADC1
 ADC1->CR &= ~ADC_CR_ADEN; // disable ADC
 ADC1->CR &= ~ADC_CR_DEEPPWD; // exit Deep-power-down mode
 ADC1->CR |= ADC_CR_ADVREGEN; // enable ADC voltage generator

 // ADC voltage regulator startup time tADCVREG_STUP
 DELAY_US(DELAY_IDX_MAIN, 25);

 ADC1->CFGR |= ADC_CFGR_CONT; // continuous mode on regular channels
 ADC1->CFGR |= ADC_CFGR_DMACFG; // DMA circular mode
 ADC1->CFGR |= ADC_CFGR_DMAEN; // enable DMA

 ADC1->SMPR1 |= (0b100 << ADC_SMPR1_SMP1_Pos); // sampling time for ADC channel 1 = 47.5 cycles
 ADC1->SMPR2 |= (0b100 << ADC_SMPR2_SMP11_Pos); // sampling time for ADC channel 11 = 47.5 cycles
 ADC1->SMPR2 |= (0b001 << ADC_SMPR2_SMP13_Pos); // sampling time for ADC channel 13 = 6.5 cycles
 ADC1->SMPR2 |= (0b100 << ADC_SMPR2_SMP16_Pos); // sampling time for ADC channel 16 = 47.5 cycles

 ADC1->SQR1 = ((ADC1_NUM - 1) << ADC_SQR1_L_Pos); // regular channel sequence length
 ADC1->SQR1 |= (16 << ADC_SQR1_SQ1_Pos); // 1st conversion is ADC channel 16, ADC1_TEMP_IN
 ADC1->SQR1 |= (1 << ADC_SQR1_SQ2_Pos); // 2nd conversion is ADC channel 1, ADC1_TEMP_PA
 ADC1->SQR1 |= (11 << ADC_SQR1_SQ3_Pos); // 3rd conversion is ADC channel 11, ADC1_V_SUPPLY
 ADC1->SQR1 |= (13 << ADC_SQR1_SQ4_Pos); // 6th conversion is ADC channel 13, ADC1_OPAMP1_SHUNT_W
 ADC1->JSQR = ((1 - 1) << ADC_JSQR_JL_Pos); // injected channel sequence length

 // calibration for single-ended inputs mode
 ADC1->CR &= ~ADC_CR_ADCALDIF; // single-ended inputs mode
 ADC1->CR |= ADC_CR_ADCAL; // start calibration (will be reset after calibration)
 while (ADC1->CR & ADC_CR_ADCAL) {}

 // calibration for differential inputs mode
 ADC1->CR |= ADC_CR_ADCALDIF; // differential inputs mode
 ADC1->CR |= ADC_CR_ADCAL; // start calibration (will be reset after calibration)
 while (ADC1->CR & ADC_CR_ADCAL) {}

 ADC1->ISR |= ADC_ISR_ADRDY; // clear ADC_ISR_ADRDY
 ADC1->CR |= ADC_CR_ADEN; // enable ADC
 while ((ADC1->ISR & ADC_ISR_ADRDY) == 0) {}

 // stabilization time tSTAB
 DELAY_US(DELAY_IDX_MAIN, 10);

 for (uint8_t i = 0; i < ADC1_NUM; i++)
 {
 s_ADC1[i].sum = 0;
 s_ADC1[i].raw = &DMA_ADC1_buffer[i];
 s_ADC1[i].offset = 0;
 }

 ADC1->IER |= ADC_IER_JEOCIE; // enable end of injected conversion interrupt

 // ADC2
 ADC2->CR &= ~ADC_CR_ADEN; // disable ADC
 ADC2->CR &= ~ADC_CR_DEEPPWD; // exit Deep-power-down mode
 ADC2->CR |= ADC_CR_ADVREGEN; // enable ADC voltage generator

 // ADC voltage regulator startup time tADCVREG_STUP
 DELAY_US(DELAY_IDX_MAIN, 25);

 ADC2->CFGR |= ADC_CFGR_CONT; // continuous mode on regular channels
 ADC2->CFGR |= ADC_CFGR_DMACFG; // DMA circular mode
 ADC2->CFGR |= ADC_CFGR_DMAEN; // enable DMA

 ADC2->SMPR1 |= (0b100 << ADC_SMPR1_SMP8_Pos); // sampling time for ADC channel 8 = 47.5 cycles
 ADC2->SMPR1 |= (0b100 << ADC_SMPR1_SMP9_Pos); // sampling time for ADC channel 9 = 47.5 cycles
 ADC2->SMPR2 |= (0b001 << ADC_SMPR2_SMP16_Pos); // sampling time for ADC channel 16 = 6.5 cycles
 ADC2->SMPR2 |= (0b001 << ADC_SMPR2_SMP18_Pos); // sampling time for ADC channel 18 = 6.5 cycles

 ADC2->SQR1 = ((ADC2_NUM - 1) << ADC_SQR1_L_Pos); // regular channel sequence length
 ADC2->SQR1 |= (8 << ADC_SQR1_SQ1_Pos); // 1st conversion is ADC channel 8, ADC2_IR_FRONT
 ADC2->SQR1 |= (9 << ADC_SQR1_SQ2_Pos); // 2nd conversion is ADC channel 9, ADC2_IR_LEFT
 ADC2->SQR1 |= (16 << ADC_SQR1_SQ3_Pos); // 1st conversion is ADC channel 16, ADC2_OPAMP2_SHUNT_V
 ADC2->SQR1 |= (18 << ADC_SQR1_SQ4_Pos); // 2nd conversion is ADC channel 18, ADC2_OPAMP3_SHUNT_U
 ADC2->JSQR = ((1 - 1) << ADC_JSQR_JL_Pos); // injected channel sequence length

 // calibration for single-ended inputs mode
 ADC2->CR &= ~ADC_CR_ADCALDIF; // single-ended inputs mode
 ADC2->CR |= ADC_CR_ADCAL; // start calibration (will be reset after calibration)
 while (ADC2->CR & ADC_CR_ADCAL) {}

 // calibration for differential inputs mode
 ADC2->CR |= ADC_CR_ADCALDIF; // differential inputs mode
 ADC2->CR |= ADC_CR_ADCAL; // start calibration (will be reset after calibration)
 while (ADC2->CR & ADC_CR_ADCAL) {}

 ADC2->ISR |= ADC_ISR_ADRDY; // clear ADC_ISR_ADRDY
 ADC2->CR |= ADC_CR_ADEN; // enable ADC
 while ((ADC2->ISR & ADC_ISR_ADRDY) == 0) {}

 // stabilization time tSTAB
 DELAY_US(DELAY_IDX_MAIN, 10);

 for (uint8_t i = 0; i < ADC2_NUM; i++)
 {
 s_ADC2[i].sum = 0;
 s_ADC2[i].raw = &DMA_ADC2_buffer[i];
 s_ADC2[i].offset = 0;
 }

 ADC2->IER |= ADC_IER_JEOCIE; // enable end of injected conversion interrupt

 s_ADC1[ADC1_TEMP_INT].filter = 0;
 s_ADC1[ADC1_V_SUPPLY].filter = 2;
 s_ADC1[ADC1_TEMP_PA].filter = 1;
 s_ADC2[ADC2_IR_FRONT].filter = 5;
 s_ADC2[ADC2_IR_LEFT].filter = 3;
 s_ADC1[ADC1_OPAMP1_SHUNT_W].filter = 2;
 s_ADC2[ADC2_OPAMP2_SHUNT_V].filter = 2;
 s_ADC2[ADC2_OPAMP3_SHUNT_U].filter = 2;

 NVIC_EnableIRQ(ADC1_2_IRQn);

 ADC1->CR |= ADC_CR_ADSTART; // start conversion
 ADC2->CR |= ADC_CR_ADSTART; // start conversion
}

 

After code optimization, depending on code constellations, the controller hangs during ADC initialization. Usually it hangs here:

 

while (ADC1->CR & ADC_CR_ADCAL) {}

 

It looks like ADC clock is missing. But in other firmware constellations ADC init works without problem.
E.g. ADC init hangs after adding a new sprintf instruction at a random position in code. Adding another instruction somewhere else in the code, the problem disappears.

Until now I haven't been able to isolate the problem. Sometimes it works and sometimes, after editing code, it hangs.

Any idea how to isolate the problem?

    This topic has been closed for replies.

    2 replies

    Super User
    March 12, 2024

    Usually it hangs here:

    Does it indeed spin inside that source line when you break in with the debugger?

    There are known issues in gcc at high levels of optimization, IIRC related to link time code generation (ltcg). If you indeed need very tight code, you may want to try Keil or IAR toolchains. Try also to selectively disable -Os on some source files or change linker optimization options.

     

    Marc1Author
    Visitor II
    March 12, 2024

    > Does it indeed spin inside that source line when you break in with the debugger?

    Yes it spins inside that source. If I comment out all while instructions in ADC init, the code starts, but ADC doesn't work. It looks like just ADC has no clock source.

     

    > Try also to selectively disable -Os on some source files or change linker optimization options.

    This could be a workaround without knowing if the problem appears again after doing further code changes.

    Super User
    March 12, 2024

    IIRC there's a known issue that writing enable bit in RCC->AHB... registers may require delay of few cycles, or barriers that prevent reordering of memory accesses, which can be affected by optimization. In the awesome HAL library these delays or barriers are often missing. If you can spot such occurrence of missing barrier or delay, please post a bug report.

     

    Marc1Author
    Visitor II
    March 12, 2024

    Could you tell more about that known issue and the corresponding workaround?
    What kind of barriers are you talking about? Is there any documentation?