STM32 ADC readings bouncing ±50 LSB — CubeMX default sampling time the culprit?
Asked by cube_mx_curses ·
Running into a wall with STM32G4 ADC. 12-bit ADC reading a 0–3.3 V analog signal from a simple potentiometer — nothing exotic, just wiper to the ADC pin, 3.3 V and GND to the ends. The readings should be rock stable but they're bouncing around by 40–60 LSB even when I'm not touching anything.
I let CubeMX configure the ADC — 12-bit resolution, right-aligned, continuous conversion, software trigger. The only thing I explicitly set was the channel, everything else is whatever CubeMX defaulted to. VDDA is connected to my 3.3 V rail (same net as everything else).
Here's a quick print of what I'm seeing in the UART output:
ADC: 2041 2047 2039 2086 2041 2033 2078 2046 2091 2038
That's sampling a dead-stable DC input. Expected ±1–2 LSB of quantisation noise, not ±50. I tried adding a 100 nF cap between the ADC pin and GND and it didn't make a visible difference.
What am I actually dealing with here? Is this normal for the internal ADC or have I misconfigured something?
3 Replies
Four separate issues at work here, and CubeMX has quietly set you up for the worst one. Let me go through them.
1. CubeMX default sampling time is almost certainly too short
CubeMX defaults to the minimum ADC sampling time — on the STM32G4 that's 2.5 ADC clock cycles. The ADC samples by charging an internal capacitor (typically 4–8 pF) through the combined impedance of your source plus the ADC's own input resistance. If sampling time is too short, the capacitor doesn't fully charge before conversion starts, and you get a reading that's partly from the current input and partly from the previous conversion. That shows up as exactly the kind of code-to-code variation you're describing.
The formula for minimum required sampling time:
t_min = (R_source + R_ADC) × ln(2^N+1) × C_ADC
For a 12-bit ADC with C_ADC ≈ 4 pF and a 10 kΩ potentiometer source impedance, you need roughly 560 ns of sampling time. At 42.5 MHz ADC clock that's about 24 ADC cycles minimum — not 2.5.
In CubeMX, increase the "Sampling Time" for your channel. I'd start with 47.5 or 92.5 cycles for a 10 kΩ source — that gives you solid margin. Your throughput drops, but unless you're doing audio-rate sampling you won't notice.
2. VDDA is not independently decoupled
The STM32G4 has a VDDA pin that should be decoupled separately from the main VDD — a 1 µF bulk ceramic plus a 10 nF ceramic right on the pin. If your VDDA is just connected to the same 3.3 V rail without its own local decoupling, every digital switching event on VDD (GPIO toggles, UART transmission, any peripheral activity) couples directly into the ADC supply and creates reference noise.
Check your schematic and PCB: is there a 100 nF (ideally 10 nF + 1 µF) ceramic on VDDA, placed within 5 mm of the pin? If not, that's the second fix.
3. Missing RC filter at the ADC input
The cap you added from the ADC pin to GND helps, but without a series resistor before it, it doesn't form a low-pass filter — it just changes the effective source impedance that the ADC sees. The correct anti-aliasing arrangement is: source → R (e.g. 1 kΩ) → ADC pin, with the capacitor from the ADC pin to GND (10–100 nF depending on your bandwidth requirement).
This RC filter blocks high-frequency noise picked up on the ADC input trace before it reaches the sample-and-hold. The signal conditioning basics page covers this in more detail. The cutoff: f_c = 1 / (2π × 1kΩ × 10nF) ≈ 16 kHz — usually fine for a slowly changing potentiometer input.
4. Use hardware oversampling for a clean result
The STM32G4 ADC has built-in hardware oversampling — you can configure it to average 16, 64, or 256 samples and right-shift the result, giving you effective noise reduction without firmware loops. In CubeMX, under ADC settings, look for "Oversampling" and set it to 16× with a 4-bit right shift (keeps 12-bit effective output). That alone typically drops noise from ±50 LSB to ±2 LSB even before fixing the sampling time.
Fix the sampling time and VDDA decoupling first — oversampling on top of a fundamentally bad setup just averages bad readings. The ADC explainer page has more on how oversampling and ENOB work if you want the background on why this helps.
Alice has covered the firmware and supply angles well. I'll add the layout dimension because it's relevant here even for a simple setup.
When VDDA shares a ground pour with the digital logic section, return currents from GPIO switching and oscillator activity run under the ADC input traces on their way back to the power source. Even a small ground impedance (a few milliohms at DC, considerably more at the frequencies involved) creates a millivolt-level fluctuation in the local ground reference at the ADC input — which reads as ADC code noise.
On a 2-layer board, this is hard to fully fix, but a few things help:
- Place the VDDA decoupling capacitors on the same side of the board as the VDDA pin, ground via directly beside each cap, routing the via to the nearest ground fill without crossing a high-current return path.
- Keep ADC input traces away from clock lines (HSE oscillator traces, SysTick-related toggling GPIO) and especially away from switching regulator traces if you have one on-board.
- If you have a ground plane, avoid any gap or slot under the ADC input traces — that's where the return current argument matters most.
The 10 kΩ potentiometer wiper impedance is also doing you no favours. The higher the source impedance, the more susceptible the ADC input is to noise pickup on the input trace — which is exactly why Alice's RC filter at 1 kΩ series helps: it sets the ADC input impedance at the noise frequencies, not the source impedance.
The voltage divider page touches on source impedance implications if you're generating a reference voltage with a resistor divider rather than a pot — same principle applies to ADC loading.
Worth spelling out the sampling time calculation more concretely for anyone doing this properly.
For STM32G4, the ADC reference manual (RM0440, section 21.4.13) gives the minimum sampling time requirement:
t_SMPL(min) ≥ (R_source + R_AIN) × C_s × ln(2^(N+1))
Where for the STM32G4:
- R_AIN (internal ADC input resistance): 6 kΩ typical from the datasheet
- C_s (internal sampling capacitor): 4.4 pF typical
- N = 12 (for 12-bit resolution)
- ln(2^13) ≈ 9.01
With a 10 kΩ potentiometer source:
t_SMPL(min) ≥ (10000 + 6000) × 4.4e-12 × 9.01 ≈ 634 ns
At 42.5 MHz ADC clock (period ≈ 23.5 ns), that's 27 cycles minimum. The STM32G4 sampling time options in ADC_SMPR are: 2.5, 6.5, 12.5, 24.5, 47.5, 92.5, 247.5, 640.5 cycles. The 47.5 cycle option (1.12 µs at 42.5 MHz) gives you 1.8× margin, which is what I'd pick as the default for anything above a few kΩ source impedance.
You set this in ADC_SMPR1 or ADC_SMPR2 depending on the channel number (channels 0–9 in SMPR1, 10–18 in SMPR2), SMP[2:0] field. CubeMX writes this register but defaults to 0b000 (2.5 cycles) without warning you it might be inadequate. If you're reading the register directly to verify, expect 0b011 for 24.5 cycles or 0b100 for 47.5 cycles.
Reducing the source impedance is the cleaner long-term fix — buffering the signal with a unity-gain op-amp before the ADC input brings R_source to milliohms and lets you run minimum sampling time without penalty.
Related Discussions
Op-amp output oscillating at ~200 kHz — only happens when I add the anti-aliasing cap
Building a signal conditioning board for a resistive sensor — non-inverting amplifier stage at gain of 6 (51 kΩ / 10 kΩ feedback), running o
NTC thermistor temperature reading jumping ±4°C — ADC noise or something in the circuit?
I've got an NTC thermistor (10 kΩ at 25°C, standard B = 3950) in a voltage divider with a 10 kΩ fixed resistor, top rail 3.3 V, thermistor t