2018-08-01 04:41 AM
ADC channel is erroneously initialized as differential if optimization is O1 or higher (O2, O3).
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.
2018-08-01 06:24 AM
"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
2018-08-02 03:13 AM
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.
2018-08-02 04:20 AM
Possible Workarounds:
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.
2018-08-03 06:41 AM
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.
2018-09-21 09:22 AM
I encountered the same problem here.
At this moment, I need to manually set the DIFSEL register to zero before starting the ADC.