cancel
Showing results for 
Search instead for 
Did you mean: 

ADC init hangs after code optimization

Marc1
Associate III

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?

4 REPLIES 4
Pavel A.
Evangelist III

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.

 

> 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.

Pavel A.
Evangelist III

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.

 

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