A low-battery warning
A 9 V battery on the terminal, a TK01 XL LED on a second pin, and a sketch that lights it below 7.5 V. The one idea worth having is hysteresis: the light comes on below 7.5 V and goes out only above 7.7 V, so a reading that wobbles across the line does not make it flicker.
Two blocks, two pins
The voltmeter's SIG goes to the analog pin, as before. The TK01's SIGNAL goes to a second, digital pin: D9 on an Uno, GPIO 4 on an ESP32, GPIO 5 on an ESP32-S3, GP15 on a Pico. Both blocks' GND pins go to your board's GND. The battery goes on the terminal, minus on the left screw.
The battery here is only being watched. Your board runs from USB, so the only current the battery supplies is the block's own 0.24 mA.
Why the obvious sketch flickers
The obvious sketch is one line: LED on if the reading is under 7.5 V, off if not. It works until the battery gets near 7.5 V, and then it flickers. A battery drifts down over hours, and each reading wobbles a few hundredths of a volt either way. For as long as the battery sits near the line, some readings land under it and some over.
Hysteresis: two lines instead of one
The fix is two thresholds. The warning turns on below 7.5 V, and turns off only above 7.7 V. Between the two, it stays as it was. No wobble is 0.2 V wide, so once the battery has crossed 7.5 V the light stays on, and it goes out only when somebody fits a battery that really is fuller.
The name for this is hysteresis, and most thermostats use it. In the sketch
it is the warning variable, remembered from one pass of loop() to the next,
and the two if tests that each look at it before changing it. The averaging
from the last article helps too: sixteen readings wobble a quarter as much, so
0.2 V is a wide margin.
The threshold itself is a choice. An alkaline 9 V battery starts a little
above 9 V, and many devices give up on one somewhere around 7 V, so 7.5 V warns
with some life left. To flash the warning rather than light it steadily,
without a delay that stops the readings, see blinking without
stopping.
The code
The calibrated sketch plus a TK01 on LED_PIN. warning turns on below LOW_V and off only above LOW_V + HYSTERESIS; between the two it keeps whatever it was.
/*
Voltmeter - low-battery warning 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: left screw to the battery's minus, right screw to its plus.
TK01 XL LED, counted from its own square pad:
GND -> GND
SIGNAL -> D9 on an Uno, GPIO 4 on an ESP32, GPIO 5 on an
ESP32-S3, GP15 on a Pico
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;
// The pin the TK01 is on. Uno: 9. ESP32: 4. ESP32-S3: 5. Pico: 15.
const int LED_PIN = 9;
// 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; // the terminal is 5 x SIG
const float CAL = 1.000; // multimeter / sketch
const int SAMPLES = 16;
const float LOW_V = 7.5; // warn below this
const float HYSTERESIS = 0.2; // and stop only above LOW_V + this
bool warning = false;
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);
pinMode(LED_PIN, OUTPUT);
}
void loop() {
float battery = sigVolts() * SCALE * CAL;
if (!warning && battery < LOW_V) {
warning = true;
} else if (warning && battery > LOW_V + HYSTERESIS) {
warning = false; // only a real recovery, such as a new battery
}
digitalWrite(LED_PIN, warning ? HIGH : LOW);
Serial.print("battery ");
Serial.print(battery, 2);
Serial.println(warning ? " V LOW" : " V");
delay(1000);
}Set CAL from the previous article first, or the threshold is only as good as the uncalibrated reading. LED_PIN is the TK01 book's pin on each board, except on an ESP32-S3, where GPIO 4 is already SIG and the LED moves to GPIO 5.
The same warning in MicroPython: sixteen readings averaged, a TK01 on LED_PIN, lit below LOW_V and put out only above LOW_V + HYSTERESIS.
"""
Voltmeter - low-battery warning, 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: left screw to the battery's minus, right screw to its plus.
TK01 XL LED, counted from its own square pad:
GND -> GND
SIGNAL -> GPIO 4 on an ESP32, GPIO 5 on an ESP32-S3, GP15 on a Pico
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
# The pin the TK01 is on. ESP32: 4. ESP32-S3: 5. Pico: 15.
LED_PIN = 4
SCALE = 5.0 # the terminal is 5 x SIG
CAL = 1.000 # multimeter / sketch
SAMPLES = 16
LOW_V = 7.5 # warn below this
HYSTERESIS = 0.2 # and stop only above LOW_V + this
adc = ADC(Pin(SIG_PIN))
led = Pin(LED_PIN, Pin.OUT)
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 battery_volts():
sig = sum(read_once() for _ in range(SAMPLES)) / SAMPLES
return sig * SCALE * CAL
warning = False
while True:
battery = battery_volts()
if not warning and battery < LOW_V:
warning = True
elif warning and battery > LOW_V + HYSTERESIS:
warning = False # only a real recovery, a new battery
led.value(1 if warning else 0)
print("battery {:.2f} V{}".format(battery, " LOW" if warning else ""))
time.sleep_ms(1000)There is no Uno here: an Uno cannot run MicroPython. On an ESP32-S3 the LED is on GPIO 5, because GPIO 4 is the analog pin. Set CAL first, as in the previous article.
When it does not work
It is a choice, not a property of the battery. A fresh alkaline 9 V battery starts a little above 9 V, and many devices give up on one somewhere around 7 V. 7.5 V warns with some life left. For a 12 V lead-acid battery, somewhere around 11.8 V at rest is a common warning point.
The wobble is bigger than the 0.2 V hysteresis. Check the terminal screws are tight and the header's GND is wired, then raise HYSTERESIS or SAMPLES. A battery under a changing load also sags and recovers, which looks exactly like a wobble.
It needs its own pin. SIG_PIN reads the voltmeter; LED_PIN drives the TK01. On an ESP32-S3 the analog pin is GPIO 4, so the LED moves to GPIO 5. Both blocks' GND pins go to your board's GND.
The block draws about 0.24 mA from the battery it measures, all the time. If the battery also powers your board, add the board's own current: tens of milliamps for an Uno or an ESP32, which will flatten a 9 V battery in hours, not months.
Zero, stuck at the top, or wandering: where to look first.
When the number is wrong →Edit this page — content/books/voltmeter/a-low-battery-warning.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.