15#include "driver/gpio.h"
16#include "freertos/FreeRTOS.h"
17#include "freertos/task.h"
19static const char*
TAG =
"BQ25895";
95 bool start()
override;
98 const char*
getName()
const override {
return "power"; }
124 bool readReg(uint8_t reg, uint8_t* value)
const;
125 bool writeReg(uint8_t reg, uint8_t value);
126 bool updateRegBits(uint8_t reg, uint8_t mask, uint8_t value,
const char* label);
129 void readChargerStatus();
130 bool setChargeCurrentMa(uint16_t currentMa);
143 bool fastChargeEnabled_ =
false;
144 uint32_t lastWdtKickMs_ = 0;
147 bool powerButtonHeld_ =
false;
148 uint32_t powerButtonHoldStartMs_ = 0;
154 mutable uint16_t cachedBatteryMv_ = 0;
156 mutable bool cachedUsbConnected_ =
false;
157 mutable bool cachedBatteryPresent_ =
false;
161 bool prevUsbConnected_ =
false;
162 bool prevBatteryPresent_ =
false;
171bool BQ25895Power::readReg(uint8_t reg, uint8_t* value)
const {
172 if (!device_ || !value)
return false;
173 return bus_->
readReg(device_, reg, value, 1) == ESP_OK;
182bool BQ25895Power::writeReg(uint8_t reg, uint8_t value) {
183 if (!device_)
return false;
184 return bus_->
writeReg(device_, reg, &value, 1) == ESP_OK;
195bool BQ25895Power::updateRegBits(uint8_t reg, uint8_t mask, uint8_t value,
const char* label) {
197 if (!readReg(reg, ¤t)) {
198 LOG_E(
TAG,
"Read failed: %s", label);
202 uint8_t newVal = (current & ~mask) | (value & mask);
203 if (newVal == current) {
204 LOG_D(
TAG,
"%s already set (0x%02X)", label, current);
208 if (!writeReg(reg, newVal)) {
209 LOG_E(
TAG,
"Write failed: %s", label);
213 LOG_D(
TAG,
"%s: 0x%02X -> 0x%02X", label, current, newVal);
227 LOG_I(
TAG,
"Initializing BQ25895 power management");
232 LOG_E(
TAG,
"I2C bus not initialized");
238 if (bus_->addDevice(
BQ25895_ADDR, &device_) != ESP_OK) {
239 LOG_E(
TAG,
"Failed to add BQ25895 to I2C0");
247 LOG_E(
TAG,
"Failed to read vendor register");
251 uint8_t partNumber = (vendor >> 3) & 0x07;
252 if (partNumber != 7) {
253 LOG_E(
TAG,
"BQ25895 not detected (got part=%d)", partNumber);
257 LOG_I(
TAG,
"BQ25895 detected (REG14=0x%02X)", vendor);
269 if (!updateRegBits(
BQ_REG_CHG_CTRL, 0x0E, (uint8_t)(sysMinCode << 1),
"SYS_MIN=3.3V")) {
290 gpio_config_t io_conf = {};
292 io_conf.mode = GPIO_MODE_INPUT;
293 io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
294 io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
295 io_conf.intr_type = GPIO_INTR_NEGEDGE;
296 gpio_config(&io_conf);
299 esp_err_t err = gpio_install_isr_service(0);
300 if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
301 LOG_E(
TAG,
"Failed to install ISR service: %s", esp_err_to_name(err));
309 gpio_config_t btn_conf = {};
311 btn_conf.mode = GPIO_MODE_INPUT;
312 btn_conf.pull_up_en = GPIO_PULLUP_ENABLE;
313 btn_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
314 btn_conf.intr_type = GPIO_INTR_DISABLE;
315 gpio_config(&btn_conf);
350void BQ25895Power::readChargerStatus() {
351 uint8_t reg0b = 0, reg0c = 0;
352 uint8_t chrgStat = 0;
355 chrgStat = (reg0b >> 3) & 0x03;
356 bool pgStat = (reg0b >> 2) & 0x01;
359 bool vsysMinRegulation = (reg0b & 0x01) != 0;
362 cachedUsbConnected_ = pgStat;
381 cachedBatteryPresent_ =
true;
382 }
else if (cachedUsbConnected_) {
386 cachedBatteryPresent_ = !vsysMinRegulation;
389 cachedBatteryPresent_ = !vsysMinRegulation &&
399 bool statusChanged = (cachedChargeStatus_ != prevChargeStatus_) ||
400 (cachedUsbConnected_ != prevUsbConnected_) ||
401 (cachedBatteryPresent_ != prevBatteryPresent_);
404 const char* chrgText[] = {
"Not charging",
"Pre-charge",
"Fast charging",
"Charge done"};
405 uint16_t chargeMa = (ichgr & 0x7F) * 50;
406 if (!cachedBatteryPresent_ && cachedUsbConnected_) {
407 LOG_I(
TAG,
"USB connected, no battery (VBAT=%dmV, ICHG=%dmA)", vbat, chargeMa);
409 LOG_I(
TAG,
"Status: %s, USB=%d, VBAT=%dmV, ICHG=%dmA",
410 chrgText[
static_cast<int>(cachedChargeStatus_)],
411 cachedUsbConnected_, vbat, chargeMa);
414 prevChargeStatus_ = cachedChargeStatus_;
415 prevUsbConnected_ = cachedUsbConnected_;
416 prevBatteryPresent_ = cachedBatteryPresent_;
422 if (reg0c == 0x80 && chrgStat == 3 && cachedBatteryPresent_) {
424 }
else if (reg0c == 0x80) {
426 LOG_D(
TAG,
"WDT expired (post-sleep), kicking");
441bool BQ25895Power::setChargeCurrentMa(uint16_t currentMa) {
455 LOG_I(
TAG,
"Charge current set to %dmA (code=%d)", currentChargeMa_, code);
468 vTaskDelay(pdMS_TO_TICKS(10));
477 uint8_t code = batv & 0x7F;
478 cachedBatteryMv_ = 2304 + (code * 20);
479 return cachedBatteryMv_;
488 if (mv == 0)
return 0;
504 return cachedUsbConnected_;
520 return cachedChargeStatus_;
544 return cachedBatteryPresent_;
558 LOG_I(
TAG,
"Charging %s", enabled ?
"enabled" :
"disabled");
566 if (preShipModeCb_) {
580 LOG_E(
TAG,
"Failed to set BATFET_DIS");
584 LOG_I(
TAG,
"Entered shipping mode");
590void BQ25895Power::kickWatchdog() {
604 uint32_t nowMs = (uint32_t)(esp_timer_get_time() / 1000);
605 if ((nowMs - lastWdtKickMs_) >= 30000) {
606 lastWdtKickMs_ = nowMs;
612 if (!powerButtonHeld_) {
613 powerButtonHeld_ =
true;
614 powerButtonHoldStartMs_ = nowMs;
616 powerButtonHeld_ =
false;
617 LOG_W(
TAG,
"Power button long-press, entering ship mode");
621 powerButtonHeld_ =
false;
658 gpio_set_intr_type(
CHG_IRQ_PIN, GPIO_INTR_NEGEDGE);
CDC Log: logging over TinyUSB CDC and UART.
#define LOG_W(tag, fmt,...)
#define LOG_D(tag, fmt,...)
#define LOG_I(tag, fmt,...)
#define LOG_E(tag, fmt,...)
PowerSource getPowerSource() const override
Returns current active power source.
ChargeStatus getChargeStatus() const override
Returns cached charge-state machine value.
void stop() override
Stops power manager service state.
uint8_t getBatteryPercent() const override
Estimates battery percentage from measured voltage.
bool isBatteryPresent() const override
Returns whether a battery is considered present.
void setPreShipModeCallback(PreShipModeCallback cb) override
bool init() override
Initializes charger hardware and applies safe boot defaults.
void refresh() override
Forces a synchronous re-read of charger status registers.
bool isBatteryLow() const override
Indicates low-battery threshold state.
void update() override
Periodic power-manager update handling watchdog and IRQ-driven refresh.
uint16_t getBatteryVoltage() const override
Returns measured battery voltage in millivolts.
bool isUsbConnected() const override
Returns cached USB power presence.
const char * getName() const override
bool isBatteryCritical() const override
Indicates critical-battery threshold state.
void setChargingEnabled(bool enabled) override
Enables or effectively disables charging current.
void enterShipMode() override
Requests battery ship mode via BATFET disconnect.
bool start() override
Starts power manager service state.
void recoverFromSleep() override
Restores charger IRQ handling and refreshes status after wakeup.
void prepareForSleep() override
Disables the charger IRQ before light sleep.
core::ServiceState getState() const override
virtual esp_err_t readReg(I2cDeviceHandle dev, uint8_t reg, uint8_t *data, size_t len)=0
virtual esp_err_t writeReg(I2cDeviceHandle dev, uint8_t reg, const uint8_t *data, size_t len)=0
void(*)() PreShipModeCallback
static constexpr uint8_t BQ_REG_VBUS
static constexpr uint8_t BQ_REG_ADC_CTRL
static constexpr uint32_t kPowerButtonLongPressMs
Hold duration on the power/flash button that triggers ship mode.
static BQ25895Power g_powerManager
Singleton power manager instance.
II2cBus * getI2cBus0()
Returns singleton instance of I2C bus 0.
static constexpr uint16_t CHARGE_CURRENT_MIN
static constexpr uint8_t BQ_REG_SYSV
static constexpr uint16_t BATTERY_MIN_MV
Battery voltage thresholds for state estimation. LiPo battery characteristics (3.7V nominal,...
static constexpr uint8_t BQ_REG_FAULT
static constexpr uint8_t BQ_REG_SYS_STATUS
static constexpr uint16_t BATTERY_FULL_MV
static constexpr uint16_t BATTERY_USB_PASSTHRU_MIN_MV
static constexpr uint8_t BQ_ICHG_STEP_MA
Charge current and safety threshold constants.
static constexpr uint8_t BQ_REG_TS
static constexpr uint8_t BQ_REG_CHG_CTRL
static constexpr uint8_t BQ_REG_MISC
IPowerManager * getPowerManagerInstance()
Returns the singleton power manager instance.
static constexpr uint16_t BATTERY_MAX_MV
static constexpr uint16_t CHARGE_CURRENT_FAST
static constexpr uint16_t CHARGE_CURRENT_MAX
static constexpr uint8_t BQ_REG_INPUT_CTRL
BQ25895 register map constants.
static constexpr uint8_t BQ_REG_FAST_CHG
static constexpr uint16_t BATTERY_EMPTY_MV
static void charger_isr(void *arg)
GPIO interrupt handler for the charger IRQ pin.
static volatile bool charger_irq_pending
Charger IRQ flag set by ISR and consumed in update().
static constexpr uint16_t CHARGE_CURRENT_SLOW
static constexpr uint8_t BQ_REG_TIMER
static constexpr uint16_t BQ_SYS_MIN_MV
static constexpr uint8_t BQ_REG_ICHG
static constexpr uint8_t BQ_REG_VINDPM
static constexpr uint8_t BQ_REG_VENDOR
static constexpr uint16_t BATTERY_USB_PASSTHRU_MAX_MV
static constexpr uint8_t BQ_REG_BATV