Skip to content

HDS 9.0.5 support - #147

Open
Sofronio wants to merge 2 commits into
mainfrom
hds-9.0.5-support
Open

HDS 9.0.5 support#147
Sofronio wants to merge 2 commits into
mainfrom
hds-9.0.5-support

Conversation

@Sofronio

@Sofronio Sofronio commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

PR: HDS 9.0.5 support — BQ27427 battery fuel gauge

Summary

HDS 9.0.5 hardware adds a TI BQ27427YZFR single-cell Li-ion fuel gauge
(fixed I2C address 0x55, Impedance Track algorithm) compared to 8.3.1.
This PR adds detection, initialization, battery info, and battery
protection for the new board while keeping full compatibility with 8.3.1
and earlier hardware.

Design principles

  • Probe at boot, enable on demand: at startup the firmware probes I2C
    address 0x55 (with retries for cold-start timing) and verifies
    DEVICE_TYPE == 0x0427. If the gauge is absent, gauge-only features stay
    disabled and the 8.3.1 behavior is unchanged.
  • 9.0.5 board variant: new V9_0_5 config block (GPIO6 repurposed from
    battery ADC to CHRG_CTRL charge enable, GPOUT on GPIO14, no BATTERY_PIN)
    with a dedicated esp32s3-905 PlatformIO env. On 9.0.5 the battery
    voltage and percent come straight from the gauge (batteryPercent()
    helper); 8.3.1 keeps the legacy ADC/ADS1115 path.
  • ADS1115 support retained: ADS_init() is gated on !b_hasFuelGauge;
    boards that later drop the ADS1115 fall back to the existing internal-ADC
    path.
  • No changes to existing menu framework logic: new menu items are
    registered with the existing mechanism. mainMenuSize() centralizes the
    effective main-menu size so returning from a submenu cannot resurrect
    hidden items (review fix).

Changes

File Change
include/config.h New V9_0_5 block (CHRG_CTRL=6, GPOUT=14, no BATTERY_PIN)
include/fuel_gauge.h Driver API (pin macros overridable)
src/fuel_gauge.cpp Detection with retry, Chem 1202 enforcement, CC_GAIN sign-bit fix, read API, charging via TP4056 CHRG pin, low-SOC notify, design-capacity query/set, deep-sleep hook
include/fuel_gauge_menu.h Bat. Info menu: full dual-column page with gauge, limited voltage/CRG/USB page without
include/menu.h Register menuBatInfo + menuBatteryProtect; mainMenuSize(); compactMainMenu() hides Battery Protect on gauge-less boards
include/parameter.h New globals b_hasFuelGauge, b_batteryProtect
include/power.h batteryPercent(); BATTERY_PIN paths guarded for 9.0.5
include/storage.h New KEY_BAT_PROTECT
include/usbcomm.h New bc command (query/set design capacity)
include/decent_protocol.h, include/websocket.h Battery percent via batteryPercent()
src/hds.ino Call fuelGaugeBegin() + compactMainMenu(); BATTERY_PIN guarded paths; ADS_init() gated
platformio.ini Gauge library dep; CPU 80 MHz config; esp32s3-905 env
tools/bq27427_probe/ I2C diagnostics probe project
.gitignore, lib/README.md Build artifacts, library source notes

Library dependency

edrean/BQ27427 Battery Fuel Gauge Arduino Library @ 1.0.4 (MIT, resolved
automatically from the PlatformIO registry; upstream:
https://github.com/edreanernst/BQ27427_Arduino_Library)

Features

1. Boot detection and initialization

  1. Probe I2C address 0x55 with retries; no ACK -> treat as 8.3.1.
  2. Read CONTROL + DEVICE_TYPE (0x0001), verify 0x0427.
  3. Chemistry check: if CHEM_ID != 1202 run the TRM switch flow with long
    CFGUPMODE timeouts and sealed-state restore (the library's setChemID
    uses a 50 ms timeout and its chemID()/CHEM_B comparison never matches
    the hex-nibble encoding the chip returns). Hard-coded for the 4.2 V cell.
  4. CC_GAIN sign-bit fix: early batches ship a negative coulomb-counter gain
    which inverts current/power readings; the value lives in RAM and resets
    on POR, so it is re-checked and fixed on every boot.

2. Bat. Info menu

  • With gauge (9.0.5): one dual-column page — voltage, chip temp, current,
    power, capacity, FCC, health, battery level, charging, USB; plus a note
    page. 500 ms live refresh; ENTER cycles, NEXT steps back (exit on first
    page).
  • Without gauge (8.3.1): simplified page — voltage, battery level, charging,
    USB (no current/power/notes).
  • Charging state reads the TP4056 CHRG pin (GPIO10, low = charging)
    instead of gauge current.

3. Battery Protect (9.0.5, off by default, persisted in NVS)

Menu on/off. When enabled, SOC >= 80 % pulls CHRG_CTRL low to cut off the
charger; charging resumes at 75 % (hysteresis). The TP4056 charge-enable
input is driven by the ESP32 GPIO6 on 9.0.5.

4. Low-SOC notify

Serial notification (once per transition) when SOC drops below 10 %.

5. Design capacity command (bc)

USB serial command: bc prints the current design capacity, bc <mAh>
writes it (validated 300-2000 mAh). No write at boot — the user decides.

Testing

Both boards tested on hardware, 2026-08-14.

HDS 9.0.5 (with BQ27427)

Item Result
Device identification DEVICE_TYPE 0x0427, FW 0x0202
Chemistry 1202 (4.2 V) enforced, persists across reboots
CC_GAIN negative sign bit detected and fixed at boot (0xB4 -> 0x34)
Battery readings 4086 mV / 99 % / 1237 mAh / SOH 94 % / 27.1 C
Charging (CHRG pin) low = charging, verified
Bat. Info menu dual-column page, cycling, 500 ms refresh
Battery Protect menu on/off, CHRG_CTRL gating (cutoff at 80 %)
bc command query + write verified (600 <-> 800 mAh)
Weighing normal operation, no regression

HDS 8.3.1 (no BQ27427)

Item Result
Boot normal
Gauge probe skipped cleanly after retries
Bat. Info menu simplified page (voltage/level/CRG/USB), no Battery Protect
Weighing normal operation, no regression

Out of scope / follow-ups

  • Deep-sleep hook wiring into the actual power.h sleep path (API only
    in this PR); GPOUT (GPIO14) available for SOC_INT wake-up
  • BLE command for design capacity (USB bc only for now)
  • Menu registration single-point refactor (separate task)
  • Long-term observation of FCC/SOH learning convergence (~1-2 full cycles)
  • Cycle-count feature: not supported by BQ27427 (no CycleCount command);
    host-side counting or SOH can be used instead

References

  • TI datasheet SLUSEBSA / TRM SLUUCD5 (see docs/9.0.5/, Chinese translation included)
  • Key protocol details (Control() read method, chem-switch flow, SOH at 0x20,
    CC_GAIN sign-bit issue, STOP-terminated writes) documented in
    docs/9.0.5/BQ27427_中文手册.md and BQ27427_Notes_EN.md

HDS 9.0.5 adds a TI BQ27427 battery fuel gauge at I2C 0x55. Probe the
address and DEVICE_TYPE at boot; when absent (8.3.1 and earlier) all
gauge features stay disabled and the menu looks unchanged. When present,
enforce Chem ID 1202 (4.2 V cell), expose a paged Bat. Info menu with
voltage/temperature/current/power/capacity/health/SOC/charging/USB, and
provide a deep-sleep hook. ADS1115 battery path is kept for older boards.

Co-Authored-By: Claude <noreply@anthropic.com>

@ODevStudio ODevStudio left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Findings for now

request changes on PR #147. I found three substantive issues.

[P2] compactMainMenu() is undone after returning from any submenu. On gauge-less hardware, compactMainMenu() only decrements currentMenuSize; it does not change the compile-time size of mainMenu. When a submenu’s Back item is selected, selectMenu() restores currentMenuSize = getMenuSize(mainMenu), which includes menuBatInfo again. In the default build, Bat. Info is the last array element, so an 8.3.1 board will start with it hidden, but after entering and leaving e.g. Calibration it becomes visible as a dead menu entry. The array and initial size confirm this layout.
Fix: centralize the main-menu size calculation so returning to mainMenu uses the compacted size, or remove visibility dynamically instead of mutating the array/size once at boot.
[P2] The chemistry-change path bypasses the pinned library’s safety logic and can leave the gauge unsealed. enforceChem1202() manually unseals a sealed device, sleeps a fixed 1 second, sends CHEM_B, soft-resets, and merely prints the resulting ID. It never restores the prior sealed state and does not verify CFGUPDATE transitions or propagate failure. The exact pinned 1.0.4 library already implements setChemID: it records whether the chip was sealed, polls CFGUPMODE, checks the transition, and explicitly seals the IC again when appropriate.
Fix: preferably use bq.setChemID(CHEM_B) after the raw 0x1202 check, or reproduce its timeout/error/reseal behavior. A failed chemistry transition should also make initialization fail rather than silently enabling potentially inaccurate SOC/SOH data.
Fix: gate ADS_init() with !b_hasFuelGauge and make updateBattery() prefer fuelGaugeVoltageV() when present, or revise the PR description if retaining ADS1115 for the main battery path is intentional.

The PR is otherwise structurally straightforward, and the pinned library does verify DEVICE_TYPE == 0x0427 inside BQ27427::begin(), so the apparent lack of an explicit device-type check in fuel_gauge.cpp is not a defect.

- Add V9_0_5 board variant (CHRG_CTRL=6, GPOUT=14, no BATTERY_PIN) and
  esp32s3-905 env; battery voltage/percent come from the gauge on 9.0.5
  while 8.3.1 keeps the legacy ADC/ADS1115 path (batteryPercent helper).
- Bat. Info menu now works on both boards: full dual-column page with the
  gauge, limited voltage/CRG/USB page without it. Charging state reads the
  TP4056 CHRG pin (GPIO10) instead of gauge current.
- Battery Protect menu (off by default, NVS): charge cutoff at 80% SOC via
  CHRG_CTRL, resume at 75%.
- Boot detection retries to cover cold-start timing; CC_GAIN sign-bit fix
  (early batch bug) re-applied on every boot; chemistry switch uses the TRM
  flow with long timeouts and restores the sealed state (library setChemID
  has a 50 ms CFGUPMODE timeout and chemID()/CHEM_B never compares equal).
- "bc" USB command queries/sets design capacity (no write at boot).
- Review fixes: centralized mainMenuSize() so returning from a submenu
  cannot resurrect hidden items; ADS_init() gated on !b_hasFuelGauge.

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants