2020-10-13 07:26 AM
Using a STM32F091 board, I set up the ADC to:
At startup, there is a slight deviation: I set up the ADC to only sample VERFINT, take a lot of samples and average them; from that, calculate Vdda based on the formula in the reference manual, and store that value in a higher precision format, to calculate all the other voltages with during normal operation of the firmware.
Then I set the channel sequence to all channels, as said above.
The VREFINT channel is also captured as part of the sequence to, regularly, roughly monitor whether the 3V3 supply voltage is still OK.
During operation, when the ADC sequence is being continuously triggered by software, I make the following observations:
If nobody objects to this logic, the question now would be: What / where is this artifact?
It shouldn't be too low sampling time, it is already the longest possible.
I have read the part of the ADC chapter in the reference again, also the errata sheet, and found nothing I might have overlooked that pertains to this.
Edit:
Ok, I forgot one thing: For the regular voltages' channels , I had (software) 8x averaging, and this Vrefint monitoring that I added recently, uses the raw last ADC value for that channel to convert it back to Vdda.
Disabling the averaging for the regular voltages now changed the picture a bit, but it's not less weird: Now *some* of the channels also have a "last error timestamp" (milsec resolution from systick) that is aligned with the last Vdda error (i.e. out of +/-10% bounds).
While other channels, after letting this run for 15 minutes, still only have timestamps from a few 100msec after boot, where it's expected to be stabilizing.
So: I do now see this on some channels besides Vrefint/Vdda, but not on others, and nothing special on the oscilloscope. All channels get captured in the same loop by the same code, the only special treatment is that the Vrefint values was taken from a buffer with last samples instead of with averaged ones. And which channels those are that get affected seems to stay the same.
I.e. before disabling averaging, the also affected channel's errors had been "averaged away" before. Using that as solution and ignoring the cause seems not a good idea.
Interesting seems the pattern of the affected channel numbers:
0 no, 1 no, 2 yes, 3 yes, 4 no, 5 no, 6 yes (not tracking beyond that in the test, but seems like 2x no, 2x yes, 2x no, ...)
Solved! Go to Solution.
2020-10-15 09:33 AM
The funny behavior disappears when I stop my status LED switching in the 1 kHz systick ISR. Thought at first might have to do with pin switching producing noise. But no single line of code commented out seemed to be the culprit, different combinations of (de)activated code could produce the same results.
So I got the idea it was about timing. Although pin toggling shows that my systick runs at expected 1 kHz, changing its timing (what's done inside it, i.e. when after the call it returns) changed the behavior.
So it finally dawned on me that the ADC conversion sequence might get interrupted by the only higher priority interrupt - the systick - at very inopportune times.
And yes, when the error was present, the ADC ISR OVR bit is set.
Since it seems to make sense to me that the systick has the highest priority (after all, it's my time base, and also, does critical monitoring of a pin that needs immediate reaction on change, next to the funny LED toggling),
I guess:
using DMA for this makes the most sense? Didn't think I'd need it for this application, but looks like I do.
@Community member how about a new entry for your gotcha list, *if* this is a worthy one? ;)
2020-10-13 03:19 PM
Can you try this on a "known good" board such as Nucleo or Disco?
Can you post a minimal but complete compilable example exhibiting the problem, so that others could try to reproduce it? (This sort of relates to the first question - the simplest thing would be to reproduce it on a 'F091 Nucleo)
JW
2020-10-14 02:53 AM
Hrm, I have a nucleo... let's see whether the used ADC pins are on some special stuff on the board like LEDs or button or something...
I will try to do that, also cutting down the program to the essentials, may take a while.
--
On an unrelated note - I saw your post in another thread linking to your "stm32 gotchas" - very nice list! There were some things on it I was not aware of, e.g. how heavy the price for interrupts is. This basically already helped me to not waste time doing it wrong first for a future project ;)
2020-10-15 09:33 AM
The funny behavior disappears when I stop my status LED switching in the 1 kHz systick ISR. Thought at first might have to do with pin switching producing noise. But no single line of code commented out seemed to be the culprit, different combinations of (de)activated code could produce the same results.
So I got the idea it was about timing. Although pin toggling shows that my systick runs at expected 1 kHz, changing its timing (what's done inside it, i.e. when after the call it returns) changed the behavior.
So it finally dawned on me that the ADC conversion sequence might get interrupted by the only higher priority interrupt - the systick - at very inopportune times.
And yes, when the error was present, the ADC ISR OVR bit is set.
Since it seems to make sense to me that the systick has the highest priority (after all, it's my time base, and also, does critical monitoring of a pin that needs immediate reaction on change, next to the funny LED toggling),
I guess:
using DMA for this makes the most sense? Didn't think I'd need it for this application, but looks like I do.
@Community member how about a new entry for your gotcha list, *if* this is a worthy one? ;)
2020-10-15 02:39 PM
Okay, so it's lengthy ADC ISR, combined with nested lengthy, resulting in ADC overrun thus falling out of sync, and considering the neighbouring channel's data, do I understand it correctly?
In certain way, DMA helps in that it may reduce the ISR entry-exit code, but it's not a panacea either.
Your sampling rate is cca 14MHz/250==55ksps, and If you run the 'F0 at its maximum 48MHz it's around 850 cycles per ADC conversion. That's somewhat more than I'd expect for two sane ISRs, so there's something rotten. Mty guess is you are using Cube and zero optimization, don't you. So there's room for improvement there, too.
> critical monitoring of a pin that needs immediate reaction on change
Isn't that candidate for EXTI or timer-trigger interrupt? Not that it helps with the ADC situation, just saying.
> how about a new entry for your gotcha list
Yes, a good candidate, falling into the broader "too long ISR results in surprising behaviour" group. I chalk it up, but the list is already quite long (years, if I will be able to keep the present pace).
JW
PS. Thanks for coming back with the solution. Please select your post as Best so that thread is marked as solved.
2020-10-16 01:56 AM
The thing is that the ISRs aren't that long, although I don't know what you'd consider long. There are 2 function calls, one of them just checks the pin* and the other does a single digit number of mul, add, shift.
I am not "using cube" in the sense of the HAL. I set every single thing to LL, and once you do that, some big clunky superstructure you'd otherwise have disappears.
There is one extra function call that's unnecessary because, yeah, I kept their ..._it.c file with all the ISRs even though I don't like that structure at all - I wanted to keep the generated stuff in order as long as I'm not sure I won't change anything anymore.
Anyway, the LL are basically just thin, more readably named inline wrappers around CMSIS, shouldn't make a difference, for things I've seen that are potentially frequently called anyway.
And right, optimization is off.
I guess I could make a global define that I can switch on / off, and use that to then always have optimization on specifically for my ISRs.
* (which I don't use an EXTI for because an EXTI triggers on edges - there was some situation where it may observe a steady state but still need to react - I forgot the details, but since I need the systick anyway and 1 kHz is good for that task, it seemed natural to put it in there)
2022-11-16 10:35 AM
Why is it, that when I come across some surprising behaviour in STM32, searching often returns something I've been involved in... :)
On STM32F303RBT6 rev.Y clocked at 70MHz, I set up ADC1 using this code:
ADC12_COMMON->CCR = 0
OR (0b11 SHL ADC_CCR_CKMODE_Pos) // 11: HCLK/4 (Synchronous clock mode) // this together with the maximum sampling cca 600 ADC clocks leads to cca 30ksps
OR (0b00000 SHL ADC_CCR_DUAL_Pos) // no dual-mode ADC here
OR (1 * ADC_CCR_VREFEN) // enable vrefint channel
OR (1 * ADC_CCR_TSEN) // temperature sensor enable
OR (0 * ADC_CCR_VBATEN) // VBAT meas channel
;
ADC1->CR = 0
OR (0b00 SHL ADC_CR_ADVREGEN_Pos) // intermediate state
;
ADC1->CR = 0
OR (0b01 SHL ADC_CR_ADVREGEN_Pos) // 01: ADC Voltage regulator enabled.
;
then calib etc., and then
ADC1_DMAChannel->CNDTR = ADC1VAL_SIZE;
ADC1_DMAChannel->CPAR = (uint32_t)&ADC1->DR;
ADC1_DMAChannel->CMAR = (uint32_t)&adc1val[0];
ADC1_DMAChannel->CCR = 0
OR (0 * DMA_CCR_MEM2MEM) // memory-to-memory mode
OR (0 * DMA_CCR_PL_0) // priority level, 0 = Low to 3 = Very High
OR (DMA_SxCR_xSIZE_HALFWORD * DMA_CCR_MSIZE_0) // memory-side size
OR (DMA_SxCR_xSIZE_HALFWORD * DMA_CCR_PSIZE_0) // peripheral-side size
OR (1 * DMA_CCR_MINC) // memory-side increment
OR (0 * DMA_CCR_PINC) // peripheral-side increment
OR (1 * DMA_CCR_CIRC) // circular
OR (DMA_SxCR_DIR_P2M * DMA_CCR_DIR) // direction
OR (0 * DMA_CCR_TEIE) // transfer error interrupt enable
OR (0 * DMA_CCR_HTIE) // half-transfer interrupt enable
OR (0 * DMA_CCR_TCIE) // transfer-complete interrupt enable
OR (1 * DMA_CCR_EN) // enable
;
ADC1->SMPR1 = 0
OR (0b111 SHL ADC_SMPR1_SMP1_Pos) // 111: 601.5 ADC clock cycles
;
ADC1->SMPR2 = 0
OR (0b111 SHL ADC_SMPR2_SMP16_Pos) // 111: 601.5 ADC clock cycles
OR (0b111 SHL ADC_SMPR2_SMP18_Pos) // 111: 601.5 ADC clock cycles
;
ADC1->SQR1 = 0
OR ((3 - 1) SHL ADC_SQR1_L_Pos) // nr of conversions
OR (1 SHL ADC_SQR1_SQ1_Pos)
OR (16 SHL ADC_SQR1_SQ2_Pos)
OR (18 SHL ADC_SQR1_SQ3_Pos)
;
ADC1->CFGR = 0
OR ADC_CFGR_DMAEN // enable DMA trigger
OR ADC_CFGR_DMACFG // this is needed for circular DMA
OR ADC_CFGR_CONT // continuous mode
// there are many more interesting option in this register, which we will leave as 0 now
;
ADC1->CR = 0 // due to the "locking" mechanisms of both ADVREGEN and ADEN we might omit these fields
OR (0b01 SHL ADC_CR_ADVREGEN_Pos) // 01: ADC Voltage regulator enabled.
OR ADC_CR_ADEN
OR ADC_CR_ADSTART // software-trigger start
;
i.e. it samples 3 channels, 3rd one is VREFINT, and uses DMA to store them into RAM circularly. I then in debugger stop execution and have a look at registers which to me look OK:
(gdb) p /x *DMA1_Channel1
$22105 = {
CCR = 0x5a1,
CNDTR = 0x1,
CPAR = 0x50000040,
CMAR = 0x200031c4
}
(gdb) p /x &adc1val
$22106 = 0x200031c4
(gdb) p /x *ADC1
$22107 = {
ISR = 0xb,
IER = 0x0,
CR = 0x10000005,
CFGR = 0x2003,
RESERVED0 = 0x0,
SMPR1 = 0x38,
SMPR2 = 0x71c0000,
RESERVED1 = 0x0,
TR1 = 0xfff0000,
TR2 = 0xff0000,
TR3 = 0xff0000,
RESERVED2 = 0x0,
SQR1 = 0x490042,
SQR2 = 0x0,
SQR3 = 0x0,
SQR4 = 0x0,
DR = 0x626,
RESERVED3 = 0x0,
RESERVED4 = 0x0,
JSQR = 0x0,
RESERVED5 = {[0x0] = 0x0, [0x1] = 0x0, [0x2] = 0x0, [0x3] = 0x0},
OFR1 = 0x0,
OFR2 = 0x0,
OFR3 = 0x0,
OFR4 = 0x0,
RESERVED6 = {[0x0] = 0x0, [0x1] = 0x0, [0x2] = 0x0, [0x3] = 0x0},
JDR1 = 0x0,
JDR2 = 0x0,
JDR3 = 0x0,
JDR4 = 0x0,
RESERVED7 = {[0x0] = 0x0, [0x1] = 0x0, [0x2] = 0x0, [0x3] = 0x0},
AWD2CR = 0x0,
AWD3CR = 0x0,
RESERVED8 = 0x0,
RESERVED9 = 0x0,
DIFSEL = 0x0,
CALFACT = 0x3c
}
Then dump the measured array:
$22137 = {[0x0] = 0x627, [0x1] = 0x68e, [0x2] = 0x5ff}
(gdb)
$22138 = {[0x0] = 0x626, [0x1] = 0x68f, [0x2] = 0x5fe}
(gdb)
$22139 = {[0x0] = 0x626, [0x1] = 0x68f, [0x2] = 0x5fe}
(gdb)
$22140 = {[0x0] = 0x628, [0x1] = 0x68f, [0x2] = 0x5ff}
(gdb)
$22141 = {[0x0] = 0x625, [0x1] = 0x68f, [0x2] = 0x5fe}
(gdb)
$22142 = {[0x0] = 0x627, [0x1] = 0x68f, [0x2] = 0x5ff}
(gdb)
$22143 = {[0x0] = 0x626, [0x1] = 0x690, [0x2] = 0x5fe}
(gdb)
$22144 = {[0x0] = 0x626, [0x1] = 0x690, [0x2] = 0x6ff}
(gdb)
$22145 = {[0x0] = 0x626, [0x1] = 0x690, [0x2] = 0x5ff}
(gdb)
$22146 = {[0x0] = 0x626, [0x1] = 0x68f, [0x2] = 0x500}
(gdb)
$22147 = {[0x0] = 0x627, [0x1] = 0x68f, [0x2] = 0x5fe}
(gdb)
$22148 = {[0x0] = 0x627, [0x1] = 0x68f, [0x2] = 0x5ff}
(gdb)
$22149 = {[0x0] = 0x627, [0x1] = 0x68f, [0x2] = 0x5ff}
(gdb)
$22150 = {[0x0] = 0x644, [0x1] = 0x691, [0x2] = 0x5ff}
(gdb)
$22151 = {[0x0] = 0x642, [0x1] = 0x691, [0x2] = 0x5ff}
(gdb)
$22152 = {[0x0] = 0x643, [0x1] = 0x690, [0x2] = 0x501}
(gdb)
$22153 = {[0x0] = 0x642, [0x1] = 0x690, [0x2] = 0x5ff}
(gdb)
$22154 = {[0x0] = 0x643, [0x1] = 0x693, [0x2] = 0x5ff}
(gdb)
$22155 = {[0x0] = 0x644, [0x1] = 0x693, [0x2] = 0x5ff}
(gdb)
$22156 = {[0x0] = 0x643, [0x1] = 0x68f, [0x2] = 0x6ff}
(gdb)
$22157 = {[0x0] = 0x644, [0x1] = 0x693, [0x2] = 0x501}
(gdb)
$22158 = {[0x0] = 0x644, [0x1] = 0x691, [0x2] = 0x5fe}
(gdb)
$22159 = {[0x0] = 0x642, [0x1] = 0x691, [0x2] = 0x5fc}
(gdb)
$22160 = {[0x0] = 0x643, [0x1] = 0x691, [0x2] = 0x5ff}
(gdb)
$22161 = {[0x0] = 0x643, [0x1] = 0x691, [0x2] = 0x5ff}
(gdb)
$22162 = {[0x0] = 0x642, [0x1] = 0x691, [0x2] = 0x5fe}
(gdb)
$22163 = {[0x0] = 0x643, [0x1] = 0x691, [0x2] = 0x6ff}
(gdb)
$22164 = {[0x0] = 0x644, [0x1] = 0x690, [0x2] = 0x5fe}
(gdb)
$22165 = {[0x0] = 0x643, [0x1] = 0x690, [0x2] = 0x5fe}
(gdb)
$22166 = {[0x0] = 0x643, [0x1] = 0x691, [0x2] = 0x5ff}
(gdb)
$22167 = {[0x0] = 0x643, [0x1] = 0x691, [0x2] = 0x5ff}
(gdb)
$22168 = {[0x0] = 0x645, [0x1] = 0x691, [0x2] = 0x5ff}
(gdb)
$22169 = {[0x0] = 0x644, [0x1] = 0x691, [0x2] = 0x5fe}
(gdb)
$22170 = {[0x0] = 0x644, [0x1] = 0x691, [0x2] = 0x5ff}
(gdb)
$22171 = {[0x0] = 0x643, [0x1] = 0x692, [0x2] = 0x500}
(gdb)
$22172 = {[0x0] = 0x643, [0x1] = 0x690, [0x2] = 0x5ff}
(gdb)
$22173 = {[0x0] = 0x643, [0x1] = 0x691, [0x2] = 0x500}
(gdb)
$22174 = {[0x0] = 0x646, [0x1] = 0x690, [0x2] = 0x5fe}
(gdb)
$22175 = {[0x0] = 0x644, [0x1] = 0x690, [0x2] = 0x5fe}
(gdb)
$22176 = {[0x0] = 0x644, [0x1] = 0x690, [0x2] = 0x5fd}
(gdb)
$22177 = {[0x0] = 0x643, [0x1] = 0x68f, [0x2] = 0x5fe}
(gdb)
$22178 = {[0x0] = 0x643, [0x1] = 0x68f, [0x2] = 0x5fe}
(gdb)
$22179 = {[0x0] = 0x643, [0x1] = 0x691, [0x2] = 0x5fe}
(gdb)
$22180 = {[0x0] = 0x644, [0x1] = 0x690, [0x2] = 0x5fe}
Note how the VREFINT value (the last in array) jumps ocassionally +0x100 or -0x100 (around 0x5ff is the expected value). Note that the other two channels don't jump.
There's no ADC overrun (see ISR), and I've stopped all other DMAs, too. If in SQR1
I swap channels 1 and 2, i.e. VREFINT now gets into the middle of the array, it stops jumping, and the third value (which is the internal temperature sensor) does not jump either.
This is not a particularly "quiet" board and VDDA is connected straight with VDD to a vanilla 3.3V LDO, but the exact +-0x100 jumps are suspicious, aren't they.
JW
PS.
@Amel NASRI , I wonder if this is worth for ST to have a look... :)