Skip to content

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.

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.


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.

GovernorWhenWhy
schedutilproduction defaultFrequency 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.
ondemandfallback if schedutil regresses on the pinned kernelOlder but well-understood. Slightly higher idle draw, marginally more responsive transitions on some kernels.
performancebring-up tuning onlyPin to max frequency to remove governor latency from any measurement. Never enabled in production — burns ~30 mA continuously for no end-user benefit.
powersavenot usedPins to min frequency. Wake latency too high for the keypress→frame budget below.

Set in stage-kn86-base of system-image-build.md:

/etc/systemd/system/kn86-cpufreq.service
[Unit]
Description=KN-86 CPU governor
After=multi-user.target
[Service]
Type=oneshot
ExecStart=/bin/sh -c 'echo schedutil > /sys/devices/system/cpu/cpufreq/policy0/scaling_governor'
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target

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 # max
arm_freq_min=600 # idle floor; lower than this hurts wake latency
over_voltage=0 # no over-voltage

The 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).

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.

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:

  1. Dim after the primary display has been blanked for ~30 s. nOSh sends OLED_FILL with a low-grayscale level (the SSD1322 supports 16 levels — see ADR-0015 + the caps bit in coprocessor-protocol.md §4.3 VERSION_RESPONSE). The status strip becomes legible-but-quiet; CIPHER scrollback rows go fully dark.
  2. 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).

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:

  1. nOSh detects idle (same last_input_ts clock that drives the primary blank).
  2. nOSh issues PSG_RESET (0x21) to silence audio synthesis. The Pico’s PSG generator throttles to “no samples to produce.”
  3. nOSh issues OLED_CLEAR (0x33) once the OLED dim+blank cascade above has completed.
  4. 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.

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.

StageBudgetNotes
USB HID event ingress (keyboard controller → Pi evdev)~5 msQMK debounce + USB hub + kernel evdev.
nOSh wake-from-idle (read evdev, set last_input_ts, decide to wake)~5 msCheap 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 msGovernor latency.
DPMS-on + Elecrow backlight ramp + first SDL render~300 msBacklight 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 msRuns in parallel with the primary wake; not on the critical path for the 500 ms target.
Total~360 msComfortably 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.

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).

SubsystemBudgeted typicalBudgeted peakMeasured typicalMeasured peakSource
Pi Zero 2 W (CPU + Wi-Fi off + idle Linux)~120–180 mA~250 mATBDTBDCLAUDE.md Battery row baseline
Pi Pico 2 (active: PSG + OLED)~25–55 mA~80 mATBDTBDADR-0017 §1
Pi Pico 2 (dormant)<5 mA<8 mATBDTBDADR-0017 §1
Elecrow 7” IPS (lit)~150 mA~200 mATBDTBDDatasheet extrapolation
Elecrow 7” IPS (DPMS-off)~10 mA~15 mATBDTBDBacklight off; controller idle
SSD1322 OLED (full-bright, full-field)~30 mA~40 mATBDTBDADR-0015 §1
SSD1322 OLED (dim status only)~5 mA~10 mATBDTBDEstimate; see ADR-0015 KU#3
SSD1322 OLED (blanked)<1 mA<2 mATBDTBDEstimate
MAX98357A (audio playing)~200 mA peak~300 mATBDTBDSpeaker current; mostly amp output to load
MAX98357A (silent)~2 mA~3 mATBDTBDIdle
System (typical, lit + audio + Pico active)~145–235 mA~370 mATBDTBDSum of typical bands; ADR-0017
System (idle, blanked + dormant)~50–80 mA~100 mATBDTBDPi 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.