cancel
Showing results for 
Search instead for 
Did you mean: 

VREFINT - sporadic false(?) readings

SKled.1
Senior II

Using a STM32F091 board, I set up the ADC to:

  • sample at the longest selectable sampling time (239.5 cycles)
  • clock source is the internal 14 MHz RC osc
  • resolution 12 bit
  • sequence of all available channels incl. vrefint

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:

  • all channels' values but one stay in their +/- deviation of a few percent from the measured rail's nominal value
  • only the VREFINT value has a lot stronger deviations
    • it is on point most of the time
    • but every few seconds I get a value that's about 26% lower than expected
    • when this wrong value comes, it stays pretty close to an ADC value of 2042 or so vs. the factory-calibrated one of 1536, the converted-back Vdda would be 2.465V instead of 3.3V
    • I checked my conversion code: it stays correct for all legal ADC values, no number overflow or such things.
    • I don't see these events on the oscilloscope, i.e. I don't see a real Vdda glitch (of that magnitude anyway, there is some switching noise in the +/-100mV range)
  • as all other channels do not show a simultaneous glitch in their value curves over time, even though they should, as they all should be affected by a glitching VDDA = VREF, I therefore conclude this must be an artifact of some sort

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

1 ACCEPTED SOLUTION

Accepted Solutions
SKled.1
Senior II

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? 😉

View solution in original post

6 REPLIES 6

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

SKled.1
Senior II

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 😉

SKled.1
Senior II

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? 😉

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.

SKled.1
Senior II

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)

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