Calibrating and averaging
Two fixes for two different errors. A multimeter reading divided by the sketch's reading gives one factor, CAL, that removes the steady offset from the resistors and the reference. Averaging sixteen readings removes a quarter of the wander. Neither does the other's job.
Two kinds of wrong
A reading can be wrong in two ways, and they need different fixes.
It can be off: 9.14 V every time for a battery the multimeter calls 9.00 V. That is the resistors' tolerance, the reference voltage your board measures against, and the ADC's own gain, all added up. It is the same from one reading to the next.
It can wander: 9.02, 8.98, 9.05, 8.99. That is noise in the ADC and on the wires, and it is different every time.
Calibrating: one number
Run the first sketch with CAL at 1.000 and a battery on the terminal.
Measure the same battery with a multimeter, probes on the two screws. Then:
CAL = what the meter says / what the sketch saysA meter at 9.00 V and a sketch at 9.14 V gives CAL = 0.9847. Put that in the
sketch and every reading is multiplied by it. The offset is gone whatever
caused it, because every cause was a multiplication too.
The block alone should need a factor within about 2 % of 1. Going by their part numbers the resistors are ±1 % parts, which puts the ratio within about 1.6 % of a fifth; your board's reference adds its own share. A factor much further from 1 is a wiring or settings mistake, not a calibration.
Averaging: sixteen readings
sigVolts() now reads SAMPLES times and returns the mean. Random wander
shrinks by the square root of the number of readings, so sixteen leaves a
quarter of it. The cost is time, and sixteen readings take a few milliseconds.
Averaging does nothing to the offset. Sixteen readings that are all 1.5 % high average to 1.5 % high. Calibrate, and average, and the reading is both steady and right.
The code
The first sketch with two changes: sigVolts() averages SAMPLES readings, and every reading is multiplied by CAL. Leave CAL at 1.000, measure the battery with a multimeter, then set CAL to the meter's reading divided by the sketch's.
/*
Voltmeter - calibrated and averaged TK09 / /p/tk09
Wiring. Count from the square pad on the TinkerBlock board, terminal
at the top, header at the bottom:
GND -> GND
NC -> nothing (both NC pins are unconnected on the board)
SIG -> A0 on an Uno, GPIO 34 on an ESP32, GPIO 4 on an
ESP32-S3, GP26 on a Raspberry Pi Pico
Terminal, same way up: left screw GND, right screw +. DC only, at
most 25 V on an Uno, about 15.5 V on an ESP32 or ESP32-S3, 16.5 V on
a Pico. Never mains.
Arduino IDE
Tools > Board your board, e.g. ESP32S3 Dev Module
Tools > Port the one that appears when you plug in
Tools > USB CDC On Boot Enabled (ESP32-S3 only)
No library needed.
*/
// The pin SIG is wired to. Uno: A0. ESP32: 34. ESP32-S3: 4. Pico: 26.
const int SIG_PIN = A0;
// Uno: 5.0 and 1023. Pico: 3.3 and 1023. The ESP32s do not use these.
const float VREF = 5.0;
const float ADC_MAX = 1023.0;
const float SCALE = 5.0; // 30 k over 7.5 k: the terminal is 5 x SIG
const float CAL = 1.000; // multimeter / sketch, once you have both
const int SAMPLES = 16; // averaging 16 leaves a quarter of the wander
float sigVolts() {
float sum = 0;
for (int i = 0; i < SAMPLES; i++) {
#if defined(ARDUINO_ARCH_ESP32)
sum += analogReadMilliVolts(SIG_PIN) / 1000.0;
#else
sum += analogRead(SIG_PIN) * VREF / ADC_MAX;
#endif
}
return sum / SAMPLES;
}
void setup() {
Serial.begin(115200);
}
void loop() {
float input = sigVolts() * SCALE * CAL;
Serial.print("input ");
Serial.print(input, 2);
Serial.println(" V");
delay(500);
}CAL belongs to one block on one board with one supply. Move the block to another board, or the Uno to another USB port, and measure it again. Everything else is as in the first sketch: SIG_PIN, and VREF and ADC_MAX for the Uno and the Pico.
The same in MicroPython: sixteen readings averaged, then multiplied by five and by CAL. Set CAL the same way, the meter's reading divided by the sketch's.
"""
Voltmeter - calibrated and averaged, MicroPython TK09 / /p/tk09
Wiring. Count from the square pad on the TinkerBlock board, terminal
at the top, header at the bottom:
GND -> GND
NC -> nothing (both NC pins are unconnected on the board)
SIG -> GPIO 34 on an ESP32, GPIO 4 on an ESP32-S3,
GP26 on a Raspberry Pi Pico
Terminal, same way up: left screw GND, right screw +. DC only, at
most about 15.5 V on an ESP32 or ESP32-S3, 16.5 V on a Pico. Never
mains.
Thonny
Run > Configure interpreter MicroPython (ESP32) or
MicroPython (Raspberry Pi Pico)
Save it to the board as main.py to run it on every power-up.
Nothing to install: machine, sys and time are built in.
"""
from machine import ADC, Pin
import sys
import time
# The GPIO number SIG is wired to. ESP32: 34. ESP32-S3: 4. Pico: 26.
SIG_PIN = 34
SCALE = 5.0 # 30 k over 7.5 k: the terminal is 5 x SIG
CAL = 1.000 # multimeter / sketch, once you have both
SAMPLES = 16 # averaging 16 leaves a quarter of the wander
adc = ADC(Pin(SIG_PIN))
if sys.platform == "rp2":
def read_once():
return adc.read_u16() * 3.3 / 65535
else:
adc.atten(ADC.ATTN_11DB) # full range; the default is ~1 V
def read_once():
return adc.read_uv() / 1_000_000 # calibrated by the firmware
def sig_volts():
return sum(read_once() for _ in range(SAMPLES)) / SAMPLES
while True:
print("input {:.2f} V".format(sig_volts() * SCALE * CAL))
time.sleep_ms(500)There is no Uno here: an Uno cannot run MicroPython. On an ESP32, read_uv needs a recent MicroPython release. CAL belongs to one block on one board, so measure again after moving it.
When it does not work
An ordinary hand-held meter is usually good to well under a percent on its DC volts range, which is better than the block uncalibrated. Its own accuracy is printed in its manual. Calibrating makes the sketch agree with the meter, so it can never be more accurate than the meter.
It helps. The divider's error is the same at every voltage, but an ESP32's ADC error is not, so calibrate near the middle of the range you care about: at 9 V for a 9 V battery, at 12 V for a 12 V one.
Averaging n readings shrinks random wander by the square root of n, so sixteen is a quarter of it. Sixty-four would be an eighth, and takes four times as long. Sixteen analogRead calls take a few milliseconds at most on any of these boards, which is nothing next to a half-second loop.
No. The block itself is good to within about 2 %. A factor that far from 1 means something else is wrong: VREF or ADC_MAX set for a different board, a source with resistance of its own, or the meter's probes on different points from the terminal's screws. Find it before calibrating it away.
A 9 V battery, a TK01, and a warning that does not flicker.
A low-battery warning →Edit this page — content/books/voltmeter/calibrating-and-averaging.mdx
Questions about this product
See what other owners have asked, and read their solutions.
Voltmeter
Loading discussions…
Discuss this article
Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.