Battery charge, idle and sleep current
A battery product is judged on the current it draws when nothing is happening. Relay 0 of the AT1032S sits in series with the battery lead and measures current in both directions down to one microamp: charge flowing into the cell, and everything the board takes back out of it. The other dry contacts connect the charger and switch a test load, so a single script walks the product through charging, idle, sleep and load, and reports what it drew in each state.
One meter in the battery lead. The dry contacts put the board into each state in turn.
Wiring​
| AT1032S | Device under test |
|---|---|
RELAY 0 | In series with the battery positive lead |
RELAY 1 | Charger supply to the board's charge input |
RELAY 2 | Test load across the board's output |
DA10 | Sleep request line to the MCU |
Current read through relay 0 is positive when it flows into the battery and negative when the board is drawing from it, so charging and discharging are told apart by sign alone.
The test sequence​
- NodeJS
- Python
import { AT1000 } from '@ikalogic/at1000';
// ---- The product, and what each state should draw -------------------------
const RELAY_BATTERY = 0; // in series with the battery lead: the meter
const RELAY_CHARGER = 1; // connects the charger to the board
const RELAY_LOAD = 2; // switches a test load across the output
const SLEEP_IO = 10; // asks the firmware to go to sleep
const LOGIC_V = 3.3;
// Windows on the signed current, in amps. Positive flows into the battery.
const CHARGING = { low: 0.080, high: 0.250, settle_ms: 3000 };
const IDLE = { low: -0.020, high: -0.002, settle_ms: 1500 };
const SLEEP = { low: -0.000200, high: -0.000020, settle_ms: 4000 };
const LOAD = { low: -0.400, high: -0.050, settle_ms: 1500 };
// ---------------------------------------------------------------------------
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
// Microamps below a milliamp, milliamps above it.
const fmt = (a) => Math.abs(a) < 0.001
? `${(a * 1e6).toFixed(1)} uA`
: `${(a * 1000).toFixed(1)} mA`;
const devices = await AT1000.findDevices();
const tester = await AT1000.open(devices[0]);
await tester.reset();
const battery = tester.power.relay_0();
const charger = tester.relays.relay(RELAY_CHARGER);
const load = tester.relays.relay(RELAY_LOAD);
const sleepLine = tester.gpio.digital(SLEEP_IO);
await sleepLine.configure_output({ voh: LOGIC_V, vol: 0, vih: 2.0, vil: 0.8, value: false });
await tester.relays.open(); // charger and load off
await tester.relays.relay(RELAY_BATTERY).close(); // battery in circuit, measured
// Put the board into one state, let it settle, and measure what it draws.
async function measure(name, setup, window) {
await setup();
await sleep(window.settle_ms);
const amps = await battery.read_current();
const ok = amps >= window.low && amps <= window.high;
console.log(
`${name.padEnd(9)} ${fmt(amps).padStart(11)} ` +
`expected ${fmt(window.low)} to ${fmt(window.high)} ${ok ? 'ok' : 'FAIL'}`);
return ok;
}
const results = [];
results.push(await measure('charging', async () => {
await charger.close(); // charger on
}, CHARGING));
results.push(await measure('idle', async () => {
await charger.open(); // running from the battery
}, IDLE));
results.push(await measure('sleep', async () => {
await sleepLine.write(true); // ask the firmware to sleep
}, SLEEP));
results.push(await measure('load', async () => {
await sleepLine.write(false); // wake it back up
await load.close(); // and put a load on it
}, LOAD));
await tester.relays.open();
const failed = results.filter((ok) => !ok).length;
console.log('');
console.log(failed === 0
? `All ${results.length} states drew what they should.`
: `${failed} of ${results.length} states were out of range.`);
await tester.reset();
from ikalogic_at1000 import AT1000
import time
# ---- The product, and what each state should draw ---------------------------
RELAY_BATTERY = 0 # in series with the battery lead: the meter
RELAY_CHARGER = 1 # connects the charger to the board
RELAY_LOAD = 2 # switches a test load across the output
SLEEP_IO = 10 # asks the firmware to go to sleep
LOGIC_V = 3.3
# Windows on the signed current, in amps. Positive flows into the battery.
CHARGING = {"low": 0.080, "high": 0.250, "settle_s": 3.0}
IDLE = {"low": -0.020, "high": -0.002, "settle_s": 1.5}
SLEEP = {"low": -0.000200, "high": -0.000020, "settle_s": 4.0}
LOAD = {"low": -0.400, "high": -0.050, "settle_s": 1.5}
# -----------------------------------------------------------------------------
def fmt(amps):
"""Microamps below a milliamp, milliamps above it."""
if abs(amps) < 0.001:
return f"{amps * 1e6:.1f} uA"
return f"{amps * 1000:.1f} mA"
devices = AT1000.find_devices()
tester = AT1000.open(devices[0])
tester.reset()
battery = tester.power.relay_0()
charger = tester.relays.relay(RELAY_CHARGER)
load = tester.relays.relay(RELAY_LOAD)
sleep_line = tester.gpio.digital(SLEEP_IO)
sleep_line.configure_output(voh=LOGIC_V, vol=0, vih=2.0, vil=0.8, value=False)
tester.relays.open() # charger and load off
tester.relays.relay(RELAY_BATTERY).close() # battery in circuit, measured
def measure(name, setup, window):
"""Put the board into one state, let it settle, and measure what it draws."""
setup()
time.sleep(window["settle_s"])
amps = battery.read_current()
ok = window["low"] <= amps <= window["high"]
print(f"{name:<9} {fmt(amps):>11} "
f"expected {fmt(window['low'])} to {fmt(window['high'])} "
f"{'ok' if ok else 'FAIL'}")
return ok
def start_charging():
charger.close() # charger on
def run_idle():
charger.open() # running from the battery
def go_to_sleep():
sleep_line.write(True) # ask the firmware to sleep
def apply_load():
sleep_line.write(False) # wake it back up
load.close() # and put a load on it
results = [
measure("charging", start_charging, CHARGING),
measure("idle", run_idle, IDLE),
measure("sleep", go_to_sleep, SLEEP),
measure("load", apply_load, LOAD),
]
tester.relays.open()
failed = [ok for ok in results if not ok]
print("")
if not failed:
print(f"All {len(results)} states drew what they should.")
else:
print(f"{len(failed)} of {len(results)} states were out of range.")
tester.reset()
tester.close()
Adapting it to your product​
- The four windows are the whole specification. Run the script once on a known-good unit, read the printed currents, and set the limits around them.
settle_msmatters most for sleep: give the firmware long enough to actually get there, or you will measure the tail of the wake state instead.- Add a state by writing one more window and one more setup step. Radio transmit bursts, a display on, or a motor running all follow the same shape.
- Relay 2 switches whatever load represents real use. Swap it for the product's own output connector if the load lives on the board already.