Power Idle
OS-side knobs that hit the post-coprocessor battery target: CPU governor, frequency caps, screen blanking on both displays, the coprocessor dormancy signal, the keypress-to-frame wake budget, and the per-subsystem draw table for validation. Read this if you are tuning idle behaviour, debugging “why isn’t the device sleeping,” or measuring real draw against the budget.
boot-and-systemd.md— service graph the idle hooks plug into.coprocessor-firmware.md— Pico-side dormant-mode implementation.../hardware/build-specification.md§3 Power topology — the hardware draw envelope this doc must satisfy.../../adr/ADR-0017-realtime-io-coprocessor.md— coprocessor power model + post-coprocessor runtime band.../../software/runtime/orchestration.md— runtime hooks that fire idle / wake events.
Battery capacity, baseline runtime, and the ~13–17 h post-coprocessor band are canonical in CLAUDE.md’s Battery row. This doc is about how the OS hits that band; it does not restate the canonical values.
CPU governor
Section titled “CPU governor”The Pi Zero 2 W’s cpufreq subsystem ships with several governors; we standardize on schedutil for production and switch to performance during specific bring-up tuning passes.
| Governor | When | Why |
|---|---|---|
schedutil | production default | Frequency tracks the kernel scheduler’s utilisation signal. Lower idle draw than ondemand, faster wake than powersave, and it’s the modern default on recent Pi OS kernels. |
ondemand | fallback if schedutil regresses on the pinned kernel | Older but well-understood. Slightly higher idle draw, marginally more responsive transitions on some kernels. |
performance | bring-up tuning only | Pin to max frequency to remove governor latency from any measurement. Never enabled in production — burns ~30 mA continuously for no end-user benefit. |
powersave | not used | Pins to min frequency. Wake latency too high for the keypress→frame budget below. |
Set in stage-kn86-base of system-image-build.md:
[Unit]Description=KN-86 CPU governorAfter=multi-user.target
[Service]Type=oneshotExecStart=/bin/sh -c 'echo schedutil > /sys/devices/system/cpu/cpufreq/policy0/scaling_governor'RemainAfterExit=yes
[Install]WantedBy=multi-user.targetFrequency caps
Section titled “Frequency caps”Pi Zero 2 W’s stock arm_freq ceiling is 1000 MHz. We hold to that ceiling — overclocking is off the table for the Pelican-mounted thermal envelope (no active cooling, foam-lined interior).
# /boot/config.txt additions ([device-tree-overlays.md](/device/os/device-tree-overlays/))arm_freq=1000 # maxarm_freq_min=600 # idle floor; lower than this hurts wake latencyover_voltage=0 # no over-voltageThe arm_freq_min=600 is a tuning knob — bring-up may reveal that 400 MHz minimum saves more idle current at acceptable wake latency. TBD pending bring-up — see ADR-0017 KU#1 (the joint power-measurement task).
Screen-blank timing
Section titled “Screen-blank timing”Primary display (Elecrow 7” IPS)
Section titled “Primary display (Elecrow 7” IPS)”After N seconds of input idle the primary display blanks via DPMS-off. The HDMI signal stays driving (the Elecrow’s controller does not power-cycle), but the backlight and panel pixel power drop to ~0 — saving the ~150 mA the panel draws when lit.
N is currently TBD pending bring-up. The candidate values are 60 s (aggressive — battery-favoring), 180 s (balanced), or 300 s (operator-favoring). The right number is whichever doesn’t surprise the operator mid-session and recovers below the wake-budget cited below. Bench measurement at Stage 1c of the build-spec assembly plan picks the value.
The blank is implemented by nOSh, not by the Linux console blanker. nOSh tracks last_input_ts, and on (now - last_input_ts) > KN86_IDLE_BLANK_SECONDS issues a KMSDRM DPMS-off through SDL2; on the next input event it re-enables DPMS and re-renders. The Linux console blanker is disabled (consoleblank=0 on cmdline.txt) to avoid double-blanking.
CIPHER-LINE OLED (auxiliary)
Section titled “CIPHER-LINE OLED (auxiliary)”The CIPHER-LINE OLED follows a separate, longer policy because it carries the operator-visible status strip (battery, timer, mode) which is useful to glance at without waking the primary display. Two-stage:
- Dim after the primary display has been blanked for ~30 s. nOSh sends
OLED_FILLwith a low-grayscale level (the SSD1322 supports 16 levels — see ADR-0015 + thecapsbit in coprocessor-protocol.md §4.3VERSION_RESPONSE). The status strip becomes legible-but-quiet; CIPHER scrollback rows go fully dark. - Blank after a further ~5 minutes of no input. Full
OLED_CLEAR. The OLED’s per-pixel current drops to <1 mA in this state.
Wake on keypress restores both displays — primary first, OLED follows by ~50 ms (the OLED restore is one OLED_SET_ROW for each of the 4 rows over UART, ~1 KB total at 100 KB/s = ~10 ms over the wire plus Pico-side render time).
Coprocessor dormancy
Section titled “Coprocessor dormancy”The Pico 2 has its own idle states (coprocessor-firmware.md). The Pi signals “go dormant” via a UART command and the Pico drops from ~25–55 mA active to <5 mA in dormant mode (per ADR-0017 §1).
The signal is currently a Pi → Pico OLED+PSG quiesce sequence rather than a dedicated DORMANT frame type:
- nOSh detects idle (same
last_input_tsclock that drives the primary blank). - nOSh issues
PSG_RESET(0x21) to silence audio synthesis. The Pico’s PSG generator throttles to “no samples to produce.” - nOSh issues
OLED_CLEAR(0x33) once the OLED dim+blank cascade above has completed. - nOSh continues sending heartbeat HELLO frames every 5 s (coprocessor-protocol.md §5.2). The Pico’s main loop sees no audio or OLED work pending and reduces its core clock; the Pico SDK’s
sleep_ms()between heartbeats puts the chip into a sleep state that hits the <5 mA target.
A dedicated DORMANT frame type that lets the Pi tell the Pico “you can stop heartbeating until I poke you” is a future-version protocol candidate; for v0.2 the heartbeat-and-quiesce path is sufficient.
On wake (any keypress), nOSh resumes audio/OLED commands; the Pico’s first command-receive ISR snaps the chip back to active mode.
Wake-from-idle latency budget
Section titled “Wake-from-idle latency budget”Target: <500 ms from keypress to first frame on the primary display. This is the operator-perceived wake time — the moment a key press registers to the moment something appears on screen.
| Stage | Budget | Notes |
|---|---|---|
| USB HID event ingress (keyboard controller → Pi evdev) | ~5 ms | QMK debounce + USB hub + kernel evdev. |
nOSh wake-from-idle (read evdev, set last_input_ts, decide to wake) | ~5 ms | Cheap if the SDL event loop is alive; longer if the kernel had to wake the CPU from a deep idle. |
CPU clock ramp (schedutil from 600 MHz min → 1000 MHz) | ~50 ms | Governor latency. |
| DPMS-on + Elecrow backlight ramp + first SDL render | ~300 ms | Backlight ramp is the dominant cost; the panel itself wakes faster than the LED driver settles. |
OLED restore (4 OLED_SET_ROW frames over UART) | ~50 ms | Runs in parallel with the primary wake; not on the critical path for the 500 ms target. |
| Total | ~360 ms | Comfortably inside the 500 ms budget. |
If bench measurement reveals the Elecrow backlight ramp is the bottleneck (likely), the dim-instead-of-blank fallback for the primary display is the lever — keep the panel at 5% backlight on idle, drop wake to <100 ms, accept ~10 mA of “always on” cost. TBD pending bring-up.
Measured-vs-budgeted draw
Section titled “Measured-vs-budgeted draw”This is the validation table. Measured values are populated at Stage 1c of ../hardware/build-specification.md §4 Assembly Plan. All “Measured” cells are TBD pending bring-up — see ADR-0017 KU#1 (joint power measurement) and ADR-0015 KU#3 (OLED actual draw).
| Subsystem | Budgeted typical | Budgeted peak | Measured typical | Measured peak | Source |
|---|---|---|---|---|---|
| Pi Zero 2 W (CPU + Wi-Fi off + idle Linux) | ~120–180 mA | ~250 mA | TBD | TBD | CLAUDE.md Battery row baseline |
| Pi Pico 2 (active: PSG + OLED) | ~25–55 mA | ~80 mA | TBD | TBD | ADR-0017 §1 |
| Pi Pico 2 (dormant) | <5 mA | <8 mA | TBD | TBD | ADR-0017 §1 |
| Elecrow 7” IPS (lit) | ~150 mA | ~200 mA | TBD | TBD | Datasheet extrapolation |
| Elecrow 7” IPS (DPMS-off) | ~10 mA | ~15 mA | TBD | TBD | Backlight off; controller idle |
| SSD1322 OLED (full-bright, full-field) | ~30 mA | ~40 mA | TBD | TBD | ADR-0015 §1 |
| SSD1322 OLED (dim status only) | ~5 mA | ~10 mA | TBD | TBD | Estimate; see ADR-0015 KU#3 |
| SSD1322 OLED (blanked) | <1 mA | <2 mA | TBD | TBD | Estimate |
| MAX98357A (audio playing) | ~200 mA peak | ~300 mA | TBD | TBD | Speaker current; mostly amp output to load |
| MAX98357A (silent) | ~2 mA | ~3 mA | TBD | TBD | Idle |
| System (typical, lit + audio + Pico active) | ~145–235 mA | ~370 mA | TBD | TBD | Sum of typical bands; ADR-0017 |
| System (idle, blanked + dormant) | ~50–80 mA | ~100 mA | TBD | TBD | Pi idle + Pico dormant + display blanked |
The runtime-band shift from the pre-coprocessor ~20 h baseline to the post-coprocessor ~13–17 h band (CLAUDE.md Battery row) assumes the System (typical) band above. If measurement at Stage 1c shows mid-band materially above ~200 mA, escalate to Josh per ADR-0017 §“Trade-off Analysis” — the mitigation paths (larger battery, Pico underclock when audio is silent, more aggressive dormancy gating) get re-evaluated.