mirror of
https://github.com/78/xiaozhi-esp32.git
synced 2026-01-14 01:07:30 +08:00
feat:添加绿荫搭子Bq27220电源管理代码
This commit is contained in:
parent
373783dc63
commit
f692480edc
BIN
docs/v1/verdure-assistant.jpg
Normal file
BIN
docs/v1/verdure-assistant.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 55 KiB |
@ -1,12 +1,23 @@
|
||||
# 绿荫搭子 (Verdure Assistant)
|
||||
<p align="center">
|
||||
<img width="80%" align="center" src="../../../docs/V1/verdure-assistant.jpg"alt="logo">
|
||||
</p>
|
||||
<h1 align="center">
|
||||
绿荫搭子 (Verdure Assistant)
|
||||
</h1>
|
||||
|
||||
## 简介
|
||||
|
||||
绿荫搭子是一款基于 ESP32-S3 的智能语音助手开发板,基于立创·实战派 ESP32-S3 开发板代码修改而来。
|
||||
绿荫搭子是一款基于 ESP32-S3 的智能语音助手开发板,基于立创·实战派 ESP32-S3 开发板修改而来。
|
||||
|
||||
主要是为实战派添加了电源管理模块,和重新设计了外壳,以及添加了电源管理的代码,后期可以进一步拓展功能。
|
||||
|
||||
## 项目地址
|
||||
|
||||
- 开源硬件平台: [https://oshwhub.com/greenshade/verdure-assistant](https://oshwhub.com/greenshade/verdure-assistant)
|
||||
-
|
||||
## B站复刻演示视频
|
||||
|
||||
- [https://www.bilibili.com/video/BV1x5vyBsEZj/](https://www.bilibili.com/video/BV1x5vyBsEZj/)
|
||||
|
||||
## 硬件特性
|
||||
|
||||
@ -16,6 +27,7 @@
|
||||
- **音频编解码**: ES8311 + ES7210
|
||||
- **摄像头**: GC0308 (可选)
|
||||
- **扩展芯片**: PCA9557 GPIO 扩展器
|
||||
- **电量计**: BQ27220 电池电量管理芯片
|
||||
|
||||
## 引脚配置
|
||||
|
||||
@ -48,6 +60,13 @@
|
||||
| BOOT 按钮 | GPIO0 |
|
||||
| LED | GPIO48 |
|
||||
|
||||
### BQ27220 电量计
|
||||
| 功能 | 配置 |
|
||||
|------|------|
|
||||
| I2C 地址 | 0x55 |
|
||||
| SDA | GPIO1 (与主 I2C 共用) |
|
||||
| SCL | GPIO2 (与主 I2C 共用) |
|
||||
|
||||
## 编译方法
|
||||
|
||||
### 方法一:使用 release.py 脚本(推荐)
|
||||
@ -84,12 +103,21 @@ python scripts/release.py verdure-assistant
|
||||
- ✅ 摄像头 (可选)
|
||||
- ✅ 设备端 AEC (回声消除)
|
||||
- ✅ Emote 动画表情
|
||||
- ✅ BQ27220 电池电量监测
|
||||
|
||||
## 特殊说明
|
||||
|
||||
- 本开发板使用 PCA9557 GPIO 扩展器控制功放使能和摄像头电源
|
||||
- 支持设备端 AEC (Acoustic Echo Cancellation)
|
||||
- 默认启用 GC0308 摄像头驱动
|
||||
- 集成 BQ27220 电量计,支持以下功能:
|
||||
- 电池电量百分比 (SOC)
|
||||
- 电池健康度 (SOH)
|
||||
- 电压、电流、温度监测
|
||||
- 剩余容量、满充容量
|
||||
- 充放电状态检测
|
||||
- 预估剩余使用/充电时间
|
||||
- 充放电循环次数
|
||||
|
||||
## 致谢
|
||||
|
||||
|
||||
349
main/boards/verdure-assistant/bq27220.cc
Normal file
349
main/boards/verdure-assistant/bq27220.cc
Normal file
@ -0,0 +1,349 @@
|
||||
#include "bq27220.h"
|
||||
#include <esp_log.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <cstring>
|
||||
|
||||
#define TAG "BQ27220"
|
||||
|
||||
Bq27220::Bq27220(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
|
||||
ESP_LOGI(TAG, "BQ27220 driver created at address 0x%02X", addr);
|
||||
}
|
||||
|
||||
uint16_t Bq27220::ReadReg16(uint8_t reg) {
|
||||
uint8_t buffer[2];
|
||||
ReadRegs(reg, buffer, 2);
|
||||
// BQ27220 uses little endian
|
||||
return buffer[0] | (buffer[1] << 8);
|
||||
}
|
||||
|
||||
uint16_t Bq27220::ControlCommand(uint16_t sub_cmd) {
|
||||
// Write control sub-command
|
||||
uint8_t cmd_buf[3];
|
||||
cmd_buf[0] = CMD_CONTROL;
|
||||
cmd_buf[1] = sub_cmd & 0xFF;
|
||||
cmd_buf[2] = (sub_cmd >> 8) & 0xFF;
|
||||
i2c_master_transmit(i2c_device_, cmd_buf, 3, 100);
|
||||
|
||||
// Wait for command to complete
|
||||
vTaskDelay(pdMS_TO_TICKS(15));
|
||||
|
||||
// Read response from MAC_DATA
|
||||
return ReadReg16(CMD_MAC_DATA);
|
||||
}
|
||||
|
||||
bool Bq27220::Init() {
|
||||
ESP_LOGI(TAG, "Initializing BQ27220...");
|
||||
|
||||
// Verify device ID
|
||||
uint16_t device_id = ControlCommand(CTRL_DEVICE_NUMBER);
|
||||
if (device_id != DEVICE_ID) {
|
||||
ESP_LOGE(TAG, "Invalid Device ID: 0x%04X (expected 0x%04X)", device_id, DEVICE_ID);
|
||||
return false;
|
||||
}
|
||||
ESP_LOGI(TAG, "Device ID verified: 0x%04X", device_id);
|
||||
|
||||
// Read firmware version
|
||||
uint16_t fw_version = GetFirmwareVersion();
|
||||
ESP_LOGI(TAG, "Firmware Version: 0x%04X", fw_version);
|
||||
|
||||
// Read hardware version
|
||||
uint16_t hw_version = GetHardwareVersion();
|
||||
ESP_LOGI(TAG, "Hardware Version: 0x%04X", hw_version);
|
||||
|
||||
// Read initial battery info
|
||||
ESP_LOGI(TAG, "Battery SOC: %d%%, Voltage: %dmV, Current: %dmA, Temp: %d°C",
|
||||
GetBatteryLevel(), GetVoltage(), GetCurrent(), GetTemperature());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int Bq27220::GetBatteryLevel() {
|
||||
uint16_t soc = ReadReg16(CMD_STATE_OF_CHARGE);
|
||||
// State of charge is in percentage (0-100)
|
||||
if (soc > 100) {
|
||||
soc = 100;
|
||||
}
|
||||
return soc;
|
||||
}
|
||||
|
||||
int Bq27220::GetVoltage() {
|
||||
// Voltage in mV
|
||||
return ReadReg16(CMD_VOLTAGE);
|
||||
}
|
||||
|
||||
int Bq27220::GetCurrent() {
|
||||
// Current in mA (signed)
|
||||
int16_t current = (int16_t)ReadReg16(CMD_CURRENT);
|
||||
return current;
|
||||
}
|
||||
|
||||
int Bq27220::GetTemperature() {
|
||||
// Temperature in 0.1°K, convert to Celsius
|
||||
uint16_t temp_k = ReadReg16(CMD_TEMPERATURE);
|
||||
// Convert from 0.1°K to °C: (temp_k / 10) - 273.15
|
||||
int temp_c = (temp_k / 10) - 273;
|
||||
return temp_c;
|
||||
}
|
||||
|
||||
int Bq27220::GetRemainingCapacity() {
|
||||
// Remaining capacity in mAh
|
||||
return ReadReg16(CMD_REMAINING_CAPACITY);
|
||||
}
|
||||
|
||||
int Bq27220::GetFullCapacity() {
|
||||
// Full charge capacity in mAh
|
||||
return ReadReg16(CMD_FULL_CHARGE_CAPACITY);
|
||||
}
|
||||
|
||||
int Bq27220::GetDesignCapacity() {
|
||||
// Design capacity in mAh
|
||||
return ReadReg16(CMD_DESIGN_CAPACITY);
|
||||
}
|
||||
|
||||
int Bq27220::GetStateOfHealth() {
|
||||
// State of health in percentage
|
||||
uint16_t soh = ReadReg16(CMD_STATE_OF_HEALTH);
|
||||
if (soh > 100) {
|
||||
soh = 100;
|
||||
}
|
||||
return soh;
|
||||
}
|
||||
|
||||
bool Bq27220::GetBatteryStatus(BatteryStatus* status) {
|
||||
if (!status) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t status_reg = ReadReg16(CMD_BATTERY_STATUS);
|
||||
// Copy the register value to the status structure
|
||||
*((uint16_t*)status) = status_reg;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint16_t Bq27220::GetFirmwareVersion() {
|
||||
return ControlCommand(CTRL_FW_VERSION);
|
||||
}
|
||||
|
||||
uint16_t Bq27220::GetHardwareVersion() {
|
||||
return ControlCommand(CTRL_HW_VERSION);
|
||||
}
|
||||
|
||||
int Bq27220::GetAveragePower() {
|
||||
// Average power in mW (signed)
|
||||
return (int16_t)ReadReg16(CMD_AVERAGE_POWER);
|
||||
}
|
||||
|
||||
int Bq27220::GetTimeToEmpty() {
|
||||
// Time to empty in minutes
|
||||
return ReadReg16(CMD_TIME_TO_EMPTY);
|
||||
}
|
||||
|
||||
int Bq27220::GetTimeToFull() {
|
||||
// Time to full in minutes
|
||||
return ReadReg16(CMD_TIME_TO_FULL);
|
||||
}
|
||||
|
||||
int Bq27220::GetCycleCount() {
|
||||
// Number of charge/discharge cycles
|
||||
return ReadReg16(CMD_CYCLE_COUNT);
|
||||
}
|
||||
|
||||
bool Bq27220::IsCharging() {
|
||||
int16_t current = GetCurrent();
|
||||
// Positive current means charging (with threshold to avoid noise)
|
||||
return current > 50; // 50mA threshold
|
||||
}
|
||||
|
||||
bool Bq27220::IsDischarging() {
|
||||
BatteryStatus status;
|
||||
if (GetBatteryStatus(&status)) {
|
||||
return status.dsg;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Bq27220::IsFullyCharged() {
|
||||
BatteryStatus status;
|
||||
if (GetBatteryStatus(&status)) {
|
||||
return status.fc;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Bq27220::ControlCommandNoRead(uint16_t sub_cmd) {
|
||||
uint8_t cmd_buf[3];
|
||||
cmd_buf[0] = CMD_CONTROL;
|
||||
cmd_buf[1] = sub_cmd & 0xFF;
|
||||
cmd_buf[2] = (sub_cmd >> 8) & 0xFF;
|
||||
i2c_master_transmit(i2c_device_, cmd_buf, 3, 100);
|
||||
vTaskDelay(pdMS_TO_TICKS(15));
|
||||
}
|
||||
|
||||
bool Bq27220::Unseal() {
|
||||
ESP_LOGI(TAG, "Unsealing BQ27220...");
|
||||
|
||||
// Send unseal key sequence
|
||||
ControlCommandNoRead(UNSEAL_KEY1);
|
||||
ControlCommandNoRead(UNSEAL_KEY2);
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
|
||||
// Verify unsealed by checking if we can enter config update mode
|
||||
ESP_LOGI(TAG, "BQ27220 unsealed");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Bq27220::Seal() {
|
||||
ESP_LOGI(TAG, "Sealing BQ27220...");
|
||||
ControlCommandNoRead(CTRL_SEAL);
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
ESP_LOGI(TAG, "BQ27220 sealed");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Bq27220::EnterConfigUpdate() {
|
||||
ESP_LOGI(TAG, "Entering config update mode...");
|
||||
ControlCommandNoRead(CTRL_ENTER_CFG_UPDATE);
|
||||
vTaskDelay(pdMS_TO_TICKS(1000)); // Wait for config update mode
|
||||
ESP_LOGI(TAG, "Entered config update mode");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Bq27220::ExitConfigUpdate() {
|
||||
ESP_LOGI(TAG, "Exiting config update mode with reinit...");
|
||||
// Use EXIT_CFG_UPDATE_REINIT (0x0091) to recalculate gauging parameters
|
||||
ControlCommandNoRead(CTRL_EXIT_CFG_UPDATE_REINIT);
|
||||
vTaskDelay(pdMS_TO_TICKS(1000)); // Wait for exit and reinit
|
||||
ESP_LOGI(TAG, "Exited config update mode");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Bq27220::WriteDataMemory(uint16_t addr, const uint8_t* data, uint8_t len) {
|
||||
// Write address + data to SelectSubclass (0x3E)
|
||||
// Format: [0x3E] [addr_low] [addr_high] [data_high] [data_low] (big endian for data)
|
||||
uint8_t* buf = new uint8_t[len + 3];
|
||||
buf[0] = 0x3E; // SelectSubclass command
|
||||
buf[1] = addr & 0xFF;
|
||||
buf[2] = (addr >> 8) & 0xFF;
|
||||
memcpy(buf + 3, data, len);
|
||||
i2c_master_transmit(i2c_device_, buf, len + 3, 100);
|
||||
delete[] buf;
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
|
||||
// Calculate checksum: sum of (addr_low + addr_high + data bytes), then 0xFF - sum
|
||||
uint8_t checksum = 0;
|
||||
checksum += (addr & 0xFF);
|
||||
checksum += ((addr >> 8) & 0xFF);
|
||||
for (int i = 0; i < len; i++) {
|
||||
checksum += data[i];
|
||||
}
|
||||
checksum = 0xFF - checksum;
|
||||
|
||||
// Write checksum and length to MACDataSum (0x60)
|
||||
uint8_t sum_buf[3];
|
||||
sum_buf[0] = 0x60; // MACDataSum
|
||||
sum_buf[1] = checksum;
|
||||
sum_buf[2] = len + 4; // Total length: 2 (address) + len (data) + 1 (checksum) + 1 (length)
|
||||
i2c_master_transmit(i2c_device_, sum_buf, 3, 100);
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Bq27220::SetDesignCapacity(uint16_t capacity_mah) {
|
||||
ESP_LOGI(TAG, "Setting design capacity to %d mAh...", capacity_mah);
|
||||
|
||||
// Step 1: Unseal the device
|
||||
if (!Unseal()) {
|
||||
ESP_LOGE(TAG, "Failed to unseal device");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 2: Enter config update mode
|
||||
if (!EnterConfigUpdate()) {
|
||||
ESP_LOGE(TAG, "Failed to enter config update mode");
|
||||
Seal();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 3: Write Full Charge Capacity (BIG ENDIAN - high byte first)
|
||||
uint8_t cap_data[2];
|
||||
cap_data[0] = (capacity_mah >> 8) & 0xFF; // High byte first
|
||||
cap_data[1] = capacity_mah & 0xFF; // Low byte second
|
||||
|
||||
ESP_LOGI(TAG, "Writing Full Charge Capacity: %d mAh", capacity_mah);
|
||||
if (!WriteDataMemory(DM_FULL_CHARGE_CAPACITY, cap_data, 2)) {
|
||||
ESP_LOGE(TAG, "Failed to write full charge capacity");
|
||||
ExitConfigUpdate();
|
||||
Seal();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 4: Write Design Capacity (BIG ENDIAN)
|
||||
ESP_LOGI(TAG, "Writing Design Capacity: %d mAh", capacity_mah);
|
||||
if (!WriteDataMemory(DM_DESIGN_CAPACITY, cap_data, 2)) {
|
||||
ESP_LOGE(TAG, "Failed to write design capacity");
|
||||
ExitConfigUpdate();
|
||||
Seal();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 5: Write design energy (capacity * 3.7V nominal) (BIG ENDIAN)
|
||||
uint16_t design_energy = (uint16_t)((capacity_mah * 37) / 10); // mWh
|
||||
uint8_t energy_data[2];
|
||||
energy_data[0] = (design_energy >> 8) & 0xFF; // High byte first
|
||||
energy_data[1] = design_energy & 0xFF; // Low byte second
|
||||
|
||||
ESP_LOGI(TAG, "Writing Design Energy: %d mWh", design_energy);
|
||||
if (!WriteDataMemory(DM_DESIGN_ENERGY, energy_data, 2)) {
|
||||
ESP_LOGE(TAG, "Failed to write design energy");
|
||||
ExitConfigUpdate();
|
||||
Seal();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 6: Exit config update mode with reinit
|
||||
if (!ExitConfigUpdate()) {
|
||||
ESP_LOGE(TAG, "Failed to exit config update mode");
|
||||
Seal();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Step 6: Seal the device
|
||||
Seal();
|
||||
|
||||
// Step 7: Verify the new design capacity
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
int new_capacity = GetDesignCapacity();
|
||||
ESP_LOGI(TAG, "Verified design capacity: %d mAh", new_capacity);
|
||||
|
||||
if (new_capacity == capacity_mah) {
|
||||
ESP_LOGI(TAG, "Design capacity set to %d mAh successfully!", capacity_mah);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Design capacity verification mismatch: expected %d, got %d",
|
||||
capacity_mah, new_capacity);
|
||||
ESP_LOGI(TAG, "This may be normal - device might need a power cycle or charge cycle");
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Note: Full charge cycle needed for gauge to recalibrate");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Bq27220::ResetLearning() {
|
||||
ESP_LOGI(TAG, "Resetting fuel gauge learning...");
|
||||
|
||||
if (!Unseal()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ControlCommandNoRead(CTRL_RESET);
|
||||
vTaskDelay(pdMS_TO_TICKS(500));
|
||||
|
||||
Seal();
|
||||
|
||||
ESP_LOGI(TAG, "Fuel gauge reset complete");
|
||||
return true;
|
||||
}
|
||||
163
main/boards/verdure-assistant/bq27220.h
Normal file
163
main/boards/verdure-assistant/bq27220.h
Normal file
@ -0,0 +1,163 @@
|
||||
#ifndef __BQ27220_H__
|
||||
#define __BQ27220_H__
|
||||
|
||||
#include "../common/i2c_device.h"
|
||||
|
||||
// BQ27220 Fuel Gauge Driver
|
||||
// Reference: Texas Instruments BQ27220 datasheet & esp-brookesia implementation
|
||||
class Bq27220 : public I2cDevice {
|
||||
public:
|
||||
// Battery Status structure (compatible with BQ27220 register format)
|
||||
struct BatteryStatus {
|
||||
bool dsg : 1; // The device is in DISCHARGE
|
||||
bool sysdwn : 1; // System down bit
|
||||
bool tda : 1; // Terminate Discharge Alarm
|
||||
bool battpres : 1; // Battery Present detected
|
||||
bool auth_gd : 1; // Detect inserted battery
|
||||
bool ocvgd : 1; // Good OCV measurement taken
|
||||
bool tca : 1; // Terminate Charge Alarm
|
||||
bool rsvd : 1; // Reserved
|
||||
bool chginh : 1; // Charge inhibit
|
||||
bool fc : 1; // Full-charged is detected
|
||||
bool otd : 1; // Overtemperature in discharge
|
||||
bool otc : 1; // Overtemperature in charge
|
||||
bool sleep : 1; // Device is in SLEEP mode
|
||||
bool ocvfail : 1; // OCV reading failed
|
||||
bool ocvcomp : 1; // OCV measurement complete
|
||||
bool fd : 1; // Full-discharge is detected
|
||||
};
|
||||
|
||||
Bq27220(i2c_master_bus_handle_t i2c_bus, uint8_t addr);
|
||||
|
||||
// Initialize device and verify device ID
|
||||
bool Init();
|
||||
|
||||
// Get battery state of charge (0-100%)
|
||||
int GetBatteryLevel();
|
||||
|
||||
// Get battery voltage in mV
|
||||
int GetVoltage();
|
||||
|
||||
// Get battery current in mA (positive = charging, negative = discharging)
|
||||
int GetCurrent();
|
||||
|
||||
// Get battery temperature in Celsius
|
||||
int GetTemperature();
|
||||
|
||||
// Get remaining capacity in mAh
|
||||
int GetRemainingCapacity();
|
||||
|
||||
// Get full charge capacity in mAh
|
||||
int GetFullCapacity();
|
||||
|
||||
// Get design capacity in mAh
|
||||
int GetDesignCapacity();
|
||||
|
||||
// Get state of health (0-100%)
|
||||
int GetStateOfHealth();
|
||||
|
||||
// Get battery status flags
|
||||
bool GetBatteryStatus(BatteryStatus* status);
|
||||
|
||||
// Get firmware version
|
||||
uint16_t GetFirmwareVersion();
|
||||
|
||||
// Get hardware version
|
||||
uint16_t GetHardwareVersion();
|
||||
|
||||
// Get average power in mW
|
||||
int GetAveragePower();
|
||||
|
||||
// Get time to empty in minutes
|
||||
int GetTimeToEmpty();
|
||||
|
||||
// Get time to full in minutes
|
||||
int GetTimeToFull();
|
||||
|
||||
// Get cycle count
|
||||
int GetCycleCount();
|
||||
|
||||
// Check if battery is charging
|
||||
bool IsCharging();
|
||||
|
||||
// Check if battery is discharging
|
||||
bool IsDischarging();
|
||||
|
||||
// Check if battery is fully charged
|
||||
bool IsFullyCharged();
|
||||
|
||||
// Configure design capacity (in mAh) - requires full charge cycle to take effect
|
||||
bool SetDesignCapacity(uint16_t capacity_mah);
|
||||
|
||||
// Reset fuel gauge learning
|
||||
bool ResetLearning();
|
||||
|
||||
private:
|
||||
// BQ27220 Standard Commands (from datasheet)
|
||||
static constexpr uint8_t CMD_CONTROL = 0x00;
|
||||
static constexpr uint8_t CMD_TEMPERATURE = 0x06;
|
||||
static constexpr uint8_t CMD_VOLTAGE = 0x08;
|
||||
static constexpr uint8_t CMD_BATTERY_STATUS = 0x0A;
|
||||
static constexpr uint8_t CMD_CURRENT = 0x0C;
|
||||
static constexpr uint8_t CMD_REMAINING_CAPACITY = 0x10;
|
||||
static constexpr uint8_t CMD_FULL_CHARGE_CAPACITY = 0x12;
|
||||
static constexpr uint8_t CMD_AVERAGE_CURRENT = 0x14;
|
||||
static constexpr uint8_t CMD_TIME_TO_EMPTY = 0x16;
|
||||
static constexpr uint8_t CMD_TIME_TO_FULL = 0x18;
|
||||
static constexpr uint8_t CMD_STANDBY_CURRENT = 0x1A;
|
||||
static constexpr uint8_t CMD_MAX_LOAD_CURRENT = 0x1E;
|
||||
static constexpr uint8_t CMD_AVERAGE_POWER = 0x24;
|
||||
static constexpr uint8_t CMD_CYCLE_COUNT = 0x2A;
|
||||
static constexpr uint8_t CMD_STATE_OF_CHARGE = 0x2C;
|
||||
static constexpr uint8_t CMD_STATE_OF_HEALTH = 0x2E;
|
||||
static constexpr uint8_t CMD_DESIGN_CAPACITY = 0x3C;
|
||||
static constexpr uint8_t CMD_MAC_DATA = 0x40;
|
||||
|
||||
// Control sub-commands
|
||||
static constexpr uint16_t CTRL_DEVICE_NUMBER = 0x0001;
|
||||
static constexpr uint16_t CTRL_FW_VERSION = 0x0002;
|
||||
static constexpr uint16_t CTRL_HW_VERSION = 0x0003;
|
||||
static constexpr uint16_t CTRL_SEAL = 0x0030;
|
||||
static constexpr uint16_t CTRL_RESET = 0x0041;
|
||||
static constexpr uint16_t CTRL_ENTER_CFG_UPDATE = 0x0090;
|
||||
static constexpr uint16_t CTRL_EXIT_CFG_UPDATE_REINIT = 0x0091;
|
||||
static constexpr uint16_t CTRL_EXIT_CFG_UPDATE = 0x0092;
|
||||
|
||||
// Data Memory addresses (from bq27220_reg.h)
|
||||
static constexpr uint16_t DM_FULL_CHARGE_CAPACITY = 0x929D; // Full Charge Capacity address
|
||||
static constexpr uint16_t DM_DESIGN_CAPACITY = 0x929F; // Design Capacity address
|
||||
static constexpr uint16_t DM_DESIGN_ENERGY = 0x92A1; // Design Energy address
|
||||
|
||||
// Unseal keys (default)
|
||||
static constexpr uint16_t UNSEAL_KEY1 = 0x0414;
|
||||
static constexpr uint16_t UNSEAL_KEY2 = 0x3672;
|
||||
|
||||
// Device ID
|
||||
static constexpr uint16_t DEVICE_ID = 0x0220;
|
||||
|
||||
// Read 16-bit register (little endian)
|
||||
uint16_t ReadReg16(uint8_t reg);
|
||||
|
||||
// Send control command and read response
|
||||
uint16_t ControlCommand(uint16_t sub_cmd);
|
||||
|
||||
// Send control command without reading response
|
||||
void ControlCommandNoRead(uint16_t sub_cmd);
|
||||
|
||||
// Unseal the device for configuration
|
||||
bool Unseal();
|
||||
|
||||
// Seal the device after configuration
|
||||
bool Seal();
|
||||
|
||||
// Enter config update mode
|
||||
bool EnterConfigUpdate();
|
||||
|
||||
// Exit config update mode
|
||||
bool ExitConfigUpdate();
|
||||
|
||||
// Write data to data memory
|
||||
bool WriteDataMemory(uint16_t addr, const uint8_t* data, uint8_t len);
|
||||
};
|
||||
|
||||
#endif // __BQ27220_H__
|
||||
@ -58,5 +58,11 @@
|
||||
|
||||
#define XCLK_FREQ_HZ 20000000
|
||||
|
||||
/* BQ27220 Fuel Gauge */
|
||||
#define BQ27220_I2C_SDA_PIN GPIO_NUM_1
|
||||
#define BQ27220_I2C_SCL_PIN GPIO_NUM_2
|
||||
#define BQ27220_I2C_ADDRESS 0x55
|
||||
#define BQ27220_DESIGN_CAPACITY 1000 // Battery design capacity in mAh
|
||||
|
||||
|
||||
#endif // _BOARD_CONFIG_H_
|
||||
|
||||
@ -16,6 +16,7 @@
|
||||
#include "i2c_device.h"
|
||||
#include "esp32_camera.h"
|
||||
#include "mcp_server.h"
|
||||
#include "bq27220.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
@ -80,6 +81,7 @@ private:
|
||||
Display* display_;
|
||||
Pca9557* pca9557_;
|
||||
Esp32Camera* camera_;
|
||||
Bq27220* bq27220_;
|
||||
|
||||
void InitializeI2c() {
|
||||
// Initialize I2C peripheral
|
||||
@ -99,6 +101,39 @@ private:
|
||||
|
||||
// Initialize PCA9557
|
||||
pca9557_ = new Pca9557(i2c_bus_, 0x19);
|
||||
|
||||
// Initialize BQ27220 fuel gauge
|
||||
bq27220_ = new Bq27220(i2c_bus_, BQ27220_I2C_ADDRESS);
|
||||
if (!bq27220_->Init()) {
|
||||
ESP_LOGE(TAG, "BQ27220 initialization failed!");
|
||||
} else {
|
||||
// Check if design capacity needs to be configured
|
||||
int design_capacity = bq27220_->GetDesignCapacity();
|
||||
int full_charge_capacity = bq27220_->GetFullCapacity();
|
||||
ESP_LOGI(TAG, "BQ27220 Design Capacity: %d mAh, Full Charge Capacity: %d mAh",
|
||||
design_capacity, full_charge_capacity);
|
||||
|
||||
// Check both design and full charge capacity
|
||||
if (design_capacity != BQ27220_DESIGN_CAPACITY ||
|
||||
full_charge_capacity != BQ27220_DESIGN_CAPACITY) {
|
||||
ESP_LOGW(TAG, "Capacity mismatch! Design: %d, FullCharge: %d, Expected: %d",
|
||||
design_capacity, full_charge_capacity, BQ27220_DESIGN_CAPACITY);
|
||||
ESP_LOGI(TAG, "Configuring BQ27220 to %d mAh...", BQ27220_DESIGN_CAPACITY);
|
||||
|
||||
if (bq27220_->SetDesignCapacity(BQ27220_DESIGN_CAPACITY)) {
|
||||
ESP_LOGI(TAG, "Capacity configured successfully!");
|
||||
// Re-read to verify
|
||||
design_capacity = bq27220_->GetDesignCapacity();
|
||||
full_charge_capacity = bq27220_->GetFullCapacity();
|
||||
ESP_LOGI(TAG, "After config - Design: %d mAh, FullCharge: %d mAh",
|
||||
design_capacity, full_charge_capacity);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to configure capacity!");
|
||||
}
|
||||
} else {
|
||||
ESP_LOGI(TAG, "BQ27220 capacity already configured correctly");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void InitializeSpi() {
|
||||
@ -298,6 +333,45 @@ public:
|
||||
virtual Camera* GetCamera() override {
|
||||
return camera_;
|
||||
}
|
||||
|
||||
virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
|
||||
static int last_level = -1;
|
||||
static bool last_charging = false;
|
||||
static int log_counter = 0;
|
||||
|
||||
if (bq27220_ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get basic battery info
|
||||
level = bq27220_->GetBatteryLevel();
|
||||
charging = bq27220_->IsCharging();
|
||||
discharging = bq27220_->IsDischarging();
|
||||
|
||||
// Only log when: level changes, charging state changes, or every 30 seconds
|
||||
log_counter++;
|
||||
bool should_log = (level != last_level) ||
|
||||
(charging != last_charging) ||
|
||||
(log_counter >= 30);
|
||||
|
||||
if (should_log) {
|
||||
log_counter = 0;
|
||||
last_level = level;
|
||||
last_charging = charging;
|
||||
|
||||
// Get additional battery info for logging
|
||||
int voltage = bq27220_->GetVoltage();
|
||||
int current = bq27220_->GetCurrent();
|
||||
int remaining = bq27220_->GetRemainingCapacity();
|
||||
int full_capacity = bq27220_->GetFullCapacity();
|
||||
|
||||
ESP_LOGI(TAG, "Battery: %d%% (%d/%d mAh), %dmV, %dmA, %s",
|
||||
level, remaining, full_capacity, voltage, current,
|
||||
charging ? "Charging" : (discharging ? "Discharging" : "Idle"));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_BOARD(VerdureAssistantBoard);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user