feat(power): add battery monitoring and status overlays

This commit is contained in:
chenjintao
2026-08-18 11:53:14 +08:00
parent 609fe978f1
commit 8f8254b397
31 changed files with 1655 additions and 22 deletions
@@ -0,0 +1,43 @@
# ADR 0005: Battery Management Ownership
## Status
Accepted
## Context
The current board uses GPIO9 to measure a protected 600 mAh single-cell Li-ion
battery through a 100 kOhm / 100 kOhm divider. GPIO10 receives the active-low
TP4057 charge indication. Battery state is affected by ADC calibration, audio
and stepper load transients, SOC estimation, threshold hysteresis, and runtime
policy, so it cannot have duplicate state in several layers.
## Decision
- `platform` owns ADC1 setup, calibration, trimmed-mean sampling, divider
conversion, TP4057 GPIO sampling, and internal audio/stepper load-quality
tracking.
- `domain` owns the single battery status snapshot, voltage filtering, SOC
curve interpolation, implausible-jump confirmation, charge/discharge
direction constraints, and low/critical hysteresis.
- `control_plane` owns the periodic monitoring worker and reactions to semantic
state transitions.
- `domain` maps its battery snapshot to semantic LCD visibility, while
`platform` owns transparent PNG composition and panel serialization.
- The first release logs low and critical transitions but intentionally does
not stop actuators, enter sleep, or cut power. Typed shutdown preparation
remains a reserved, unsupported hook until hardware wake and shutdown policy
are decided.
- `app_composition` keeps using the aggregate control-plane runtime lifecycle;
it does not acquire battery samples directly.
## Consequences
- GPIO9 must never be configured as the legacy power-hold output on this board.
- High-load samples remain available for diagnostics but cannot update SOC or
low-battery state. Normal processing resumes after the configured cooldown.
- GPIO10 cannot distinguish a full battery from an unplugged charger because
both produce a high level; the public state therefore reports only charging
versus not charging.
- The voltage-to-SOC curve is an initial approximation and requires bench
calibration against measured battery voltage and a controlled discharge run.
+67
View File
@@ -0,0 +1,67 @@
# Battery Management
## Hardware Profile
- Cell: protected single-cell Li-ion, 600 mAh, 3.7 V nominal, 4.2 V full.
- Supported measurement range: 3.3 V to 4.2 V for SOC estimation.
- GPIO9: ADC1 battery sense through R17=100 kOhm (VBAT to ADC) and R18=100
kOhm (ADC to ground). `VBAT = VADC * 2`.
- GPIO10: TP4057 active-low charge output with an input pull-up. A high level
means either charge complete or charger absent.
- Legacy power hold: disabled (`CONFIG_TQ_POWER_HOLD_GPIO=-1`).
## Processing Pipeline
Each hardware sample consists of 16 calibrated ADC readings. The platform
driver sorts them, discards the lowest four and highest four, averages the
remaining eight, and applies the divider ratio. Domain processing then applies:
1. A 2.5 V to 4.5 V plausibility check.
2. Three-sample confirmation for voltage jumps greater than 300 mV.
3. An exponential filter with 80% previous and 20% new voltage.
4. Piecewise-linear interpolation over the Li-ion discharge curve.
5. A two-percent maximum SOC change per accepted sample.
6. Charge/discharge direction constraints.
7. Three-sample confirmation and hysteresis for low and critical states.
The initial state thresholds are 20%/25% for low entry/recovery and 5%/10% for
critical entry/recovery.
Board calibration can be adjusted without source changes using
`CONFIG_TQ_BATTERY_CALIBRATION_GAIN_PPM` and
`CONFIG_TQ_BATTERY_CALIBRATION_OFFSET_MV` after comparing the log against a
multimeter.
## High-Load Handling
Audio playback events and synchronous stepper rotations update one
platform-owned load tracker. While a load is active and for five seconds after
its terminal event, samples are marked suppressed. The monitor continues to
poll once per second for diagnostics and charge state, but suppressed voltage
does not update filtered voltage, SOC, or low-battery state.
Playback generations prevent a stale terminal event from ending suppression
for a newer playback. Stepper cleanup executes after every synchronous return,
including errors and timeouts.
## LCD Status Overlays
The direct LCD renderer composites the transparent `charging.png` asset in the
top-right corner whenever GPIO10 reports active-low charging. It composites
`lowbat.png` at the bottom center after the domain level enters low or critical
state. Both overlays may be visible together, and QR-code presentations omit
them to preserve scan reliability. Overlay state changes do not trigger any
shutdown, sleep, prompt, or actuator policy.
## Deferred Policy
Critical status currently produces a state-transition log only. The reserved
shutdown hook returns `ESP_ERR_NOT_SUPPORTED`; it does not stop audio or the
stepper, enter deep sleep, or release board power. Implement wake sources and
the product shutdown sequence before enabling any automatic critical action.
## Bench Calibration
After flashing, compare the `cp_battery` voltage log with a multimeter at no
load near 4.2 V, 3.9 V, 3.6 V, and 3.3 V. Record audio and stepper voltage sag,
then run a controlled discharge to tune the SOC curve and threshold timing.
+3 -3
View File
@@ -23,7 +23,7 @@ dark. This is accepted for this plan.
## Startup Order
```text
power hold
power-hold configuration (disabled on the current battery-sensing board)
-> audio init
-> play boot sound with timeout
-> audio deinit or idle release
@@ -42,7 +42,7 @@ power hold
| Order | Stage | Wait policy | Failure policy | Notes |
| --- | --- | --- | --- | --- |
| 1 | Power hold GPIO | Wait | Continue only if board power remains stable | Configure the hold GPIO and assert the active level before any heavier work. |
| 1 | Power-hold configuration | Wait | Continue only if board power remains stable | The current board sets the hold GPIO to `-1`; legacy boards may configure and assert a dedicated hold output. GPIO9 is reserved for battery ADC. |
| 2 | Audio init | Wait with bounded timeout | Log and skip boot sound | Initializes the codec path needed for playback: I2C, I2S, codec, PA control, codec buffers, and decoder resources. |
| 3 | Boot sound playback | Wait for completion or timeout | Stop playback and continue boot | The sound must not block startup indefinitely. Use a short product-defined timeout. |
| 4 | Audio release | Wait for synchronous release | Continue if release reports a recoverable error | If Wi-Fi/TLS starts soon after, deinit or release idle audio resources to reduce DMA/internal heap pressure. |
@@ -53,7 +53,7 @@ power hold
| 9 | Turntable calibration | Product decision; default wait with timeout | Continue with degraded direction accuracy on timeout/failure | This is the mechanical SW1 homing/calibration process, not the stepper electrical init. |
| 10 | Storage prepare | Wait | Stop features that need persistent state | Initializes NVS before Wi-Fi credentials, identity, auth, and fortune state are read or written. |
| 11 | Wi-Fi bootstrap | Wait until STA connected or AP config mode starts | Enter config AP or keep retry policy active | Must not overlap with boot sound playback or heavy audio initialization. |
| 12 | Runtime workers | Start and continue | Stop only failed dependent feature | Starts PTT, slot switch, Wi-Fi state monitor, model command worker, LCD runtime worker, and WebSocket wait worker as configured. |
| 12 | Runtime workers | Start and continue | Stop only failed dependent feature | Starts battery monitoring, PTT, slot switch, Wi-Fi state monitor, model command worker, LCD runtime worker, and WebSocket wait worker as configured. |
| 13 | Network services | Async after network readiness | Retry using existing policy | Includes multimodal WebSocket, LAN HTTP config server, SNTP, HTTPS fortune refresh, and auth flows. |
## Clarifications
+4 -2
View File
@@ -15,7 +15,8 @@ the ESP32-S3 MCU perspective.
| 6 | `CONFIG_TQ_SLOT_SWITCH_GPIO` | Input | Slot / key input | Rocker microswitch / original SW2 function | Uses the unconnected `TP_SCL` contact; active-low with internal pull-up |
| 7 | `CONFIG_TQ_SCREEN_BACKLIGHT_GPIO` | Output | LCD | LCD adapter backlight control | Active-high by default |
| 8 | `CONFIG_TQ_SCREEN_SPI_CLK_GPIO` | Output | LCD | ST7789P3 SPI clock / SCK | SPI2 host, 20 MHz default |
| 9 | `CONFIG_TQ_POWER_HOLD_GPIO` | Output | Power | Board power hold | Active-high by default |
| 9 | `CONFIG_TQ_BATTERY_ADC_GPIO` | Analog input | Battery | Battery voltage through 100 kOhm / 100 kOhm divider | ADC1 input; do not configure as a power-hold output |
| 10 | `CONFIG_TQ_BATTERY_CHARGE_GPIO` | Input | Battery charger | TP4057 charge status | Active-low, internal pull-up enabled |
| 11 | `CONFIG_MULTIMODAL_AUDIO_PA_GPIO` | Output | Audio amplifier | PA enable | Managed by codec GPIO interface |
| 12 | `CONFIG_TQ_CALIBRATION_SWITCH_GPIO` | Input | Turntable calibration | Button 1 / SW1 direction calibration | Active-low, external and internal pull-up |
| 13 | `CONFIG_MULTIMODAL_PTT_BUTTON_GPIO` | Input | Keys / multimodal | Button 2 / K2 push-to-talk | Active-low, external and internal pull-up |
@@ -46,6 +47,7 @@ is installed.
| `CONFIG_TQ_SLOT_SWITCH_PULLDOWN` | unset | SW2 uses pull-up only. |
| `CONFIG_TQ_CALIBRATION_SWITCH_PULLDOWN` | unset | Calibration switch uses pull-up only. |
| `CONFIG_MULTIMODAL_PTT_BUTTON_PULLDOWN` | unset | Button 2 uses pull-up only. |
| `CONFIG_TQ_POWER_HOLD_GPIO` | `-1` | Legacy board power-hold output is disabled because GPIO9 is the battery ADC input. |
## Boot Hardware Test Profile
@@ -90,7 +92,6 @@ them to an active project feature.
| --- | --- | --- |
| GPIO0 / K1 | Switch to GND, commonly boot/download related | Not consumed by application code |
| GPIO3 / `touch` | TTP223 touch output | Present on the board but not consumed by the current input policy |
| GPIO10 / `Charge` | Charger status | No current firmware consumer found |
| GPIO14 | Unassigned | Not used |
| GPIO35 | Unassigned | Not used |
| GPIO36 | Unassigned | Not used |
@@ -107,6 +108,7 @@ them to an active project feature.
- `docs/gpio-map.md`
- `components/platform/src/lcd_direct.c`
- `components/platform/src/platform_stepper_driver.c`
- `components/platform/src/platform_battery_driver.c`
- `components/platform/src/multimodal/audio_pipeline/audio_pipeline_codec_impl.c`
- `components/control_plane/src/runtime/control_plane_key_input.c`
- `components/platform/src/platform_console_driver.c`
+6 -5
View File
@@ -1,8 +1,9 @@
# ESP32-S3 GPIO Map
This document defines the reference GPIO assignments for the ESP32-S3 board
from `SCH_招财猫0.3&商界0.2_2026-07-24.pdf`. Signal direction is described from
the MCU's perspective.
This document defines the active firmware GPIO assignments for the current
battery-sensing ESP32-S3 board revision. Signal direction is described from the
MCU's perspective. Older 2026-07-24 drawings assigned GPIO9 to a legacy power
hold net; that assignment is not valid for the current board.
## GPIO Table
@@ -24,8 +25,8 @@ the MCU's perspective.
| 14 | GPIO20 | ESP_D_P | Bidirectional | USB D+ | Type-C |
| 15 | GPIO3 | touch | Input | TTP223 touch-button output | Present on the board but not consumed by the current input policy |
| 16 | GPIO46 | UNASSIGNED | N/A | No connection assigned | Reserved; no signal assigned |
| 17 | GPIO9 | Power | Output | Board power hold | Active high |
| 18 | GPIO10 | Charge | Input | Charger status | Not consumed by current firmware |
| 17 | GPIO9 | Power_t | Analog input | Battery voltage sense | R17=100 kOhm from VBAT and R18=100 kOhm to GND; ADC voltage is one half of VBAT |
| 18 | GPIO10 | Charge | Input | TP4057 charger status | Active-low while charging; high means full or external power absent |
| 19 | GPIO11 | PA_CTRL | Output | Audio power-amplifier enable | Amplifier control |
| 20 | GPIO12 | SW1 | Input | Button 1 / turntable calibration switch | External 10 kOhm pull-up; active low |
| 21 | GPIO13 | SW2 | Input | Button 2 / K2 push-to-talk input | External 10 kOhm pull-up; active low |