cancel
Showing results for 
Search instead for 
Did you mean: 

ADC init bug with optimization >= O1 (STM32L4)

Nils Zottmann
Associate II

ADC channel is erroneously initialized as differential if optimization is O1 or higher (O2, O3).

  • STM32CubeMX 4.26.1
  • FW_L4_V1.12.0
  • Create blank project for NUCLEO-L476RG
  • PC5 in ADC1 IN14 Single-ended, Rank 1 640.5 Cycles, everything else default
  • Project for SW4STM32

ADC Init looks fine:

  sConfig.Channel = ADC_CHANNEL_14;
  sConfig.Rank = ADC_REGULAR_RANK_1;
  sConfig.SamplingTime = ADC_SAMPLETIME_640CYCLES_5;
  sConfig.SingleDiff = ADC_SINGLE_ENDED;
  sConfig.OffsetNumber = ADC_OFFSET_NONE;
  sConfig.Offset = 0;

Simple readout:

while (1)
  {
	HAL_ADC_Start(&hadc1);
	HAL_ADC_PollForConversion(&hadc1, 1000);
	printf("%lu\n", HAL_ADC_GetValue(&hadc1));
	HAL_Delay(500);
  }

Everything ok.

Now if I change optimization to O1 or higher, I get wrong values. After a long debugging session, I found DIFSEL register != 0 which set the channel to differential. Setting DIFSEL to 0 in debugger solves the problem.

The code in stm32l4xx_hal_adc.c :2694 where this wrong value in DIFSEL is set (found with debugger, register value changes after this call):

LL_ADC_SetChannelSingleDiff(hadc->Instance, sConfig->Channel, sConfig->SingleDiff);

I don't see a problem here. Code and variable values are the same with both optimizations, but with O0 there is no change to DIFSEL, with O1 DIFSEL gets changed.

What is the reason for this bug? I suspect something in the HAL library isn't marked volatile so the compiler "optimizes" something it shouldn't. Perhaps a look at the disassembly could help here, but I'm no expert here.

Bugs like these are really hard to find as the symptom are just wrong ADC values.

5 REPLIES 5

"Optimization breaks my code" almost invariably points to hitting an undefined behaviour, i.e. violating the C language constraints in the source.

If I deciphered the stuff going into the LL_ADC_SetChannelSingleDiff() macro correctly, it ends up using the MODIF_REG() macro with the last "parameter" being something like

(Channel & ADC_SINGLEDIFF_CHANNEL_MASK) & (ADC_DIFSEL_DIFSEL << (SingleDiff & ADC_SINGLEDIFF_CHANNEL_SHIFT_MASK))

and the right-side of the bitwise AND reducing to

0x7FFFF << (0x7F & 0x20)

This hits C99, 6.5.7 Bitwise shift operators, #3:

If the value of the right operand is negative or is

greater than or equal to the width of the promoted left operand, the behavior is undefined.

JW

Nils Zottmann
Associate II

Thank very much for the reply, now I understand what the real problem is. Shifting by (0x7F & 0x20) means shifting by 0x20 = 32, this violates "... geater than or equal to the width" of the 32 bit value.

Even the comment above the section says

/* Bits of channels in single or differential mode are set only for         */
/* differential mode (for single mode, mask of bits allowed to be set is    */
/* shifted out of range of bits of channels in single or differential mode. */

So perhaps this was done intentionally ignoring that it leads to undefinded behavior.

@ST: please fix this.

Nils Zottmann
Associate II

Possible Workarounds:

  • Changing HAL is not an option as it breaks with every regeneration or update
  • Setting optimization to O0 or Og works, but as the behaviour is undefined, this is more luck than a fix and could break again any time. Unfortunately, CubeMX sets optimization with every regeneration to O3 again and I don't know why, this is not the case with other projects. In my project.ioc there is a line ProjectManager.CompilerOptimize=3. Other projects with Og as a default have a 6 here. I'm not sure how reliable this option is and I didn't find a way to change it in the gui. I found this statement from ST: "At the moment it is not possible to change the default optimization level in STM32CubeMX. I will put this option as a proposal for future releases." (https://st-microelectronics.jiveon.com/thread/50971-cubemx-problem#comment-202646). So you can't change it, but why is the default different for different projects?
  • Best solution for me at the moment: after each undefined set of DIFSEL, set DIFSEL to the correct value:

After each call of

HAL_ADC_ConfigChannel()

call

hadc1.Instance->DIFSEL = 0;

Adjust 0 to correct value if using differential mode.

This is no solution, just a workaround until the bug gets fixed.

Amel NASRI
ST Employee

Hello,

Thanks @Nils Zottmann​  for reporting this issue and @Community member​  for your help to analyze it and identify the root cause.

This is reported internally.

Please note also that your comments about default optimization level in CubeMX generated code are shared with our STM32CubeMX team.

-Amel

To give better visibility on the answered topics, please click on Accept as Solution on the reply which solved your issue or answered your question.

Danny Chow
Associate II

I encountered the same problem here.

At this moment, I need to manually set the DIFSEL register to zero before starting the ADC.