一、电池电量计的几种类型
1. 电压检测型电量计(Voltage-Based)
原理:通过测量电池电压来估算剩余电量(SoC)。
特点:
-
无需检流电阻,电路简单,成本低。
-
精度较低(通常±5%~10%),受电池负载、温度、老化影响大。
典型芯片: -
CW2015(赛微)
-
MAX170xx(Maxim)
适用场景: -
低功耗设备(如TWS耳机、智能手表)。
-
对成本敏感且精度要求不高的应用。
2. 库仑计数型电量计(Coulomb Counting)
原理:通过测量充放电电流积分(库仑计数)计算电量。
特点:
-
精度较高(±1%~3%),但需外接检流电阻。
-
需要初始校准(如满充容量)。
-
长期使用会因电池老化产生累积误差。
典型芯片: -
TI BQ系列(如BQ27545、BQ28Z610)
-
LTC294x(Linear Tech)
适用场景: -
智能手机、平板电脑。
-
电动工具、无人机等需要高精度监测的场景。
3. 阻抗跟踪型电量计(Impedance Track, 混合算法)
原理:结合电压检测 + 库仑计数 + 电池阻抗模型,动态校准SoC。
特点:
-
精度最高(±1%以内),自动补偿电池老化、温度影响。
-
需预存电池特性参数(如放电曲线)。
典型芯片: -
TI BQ40Z50(支持多节电池)
-
BQ34Z100(适用于锂离子/铅酸电池)
适用场景: -
高端消费电子(如笔记本电池包)。
-
电动汽车、储能系统等复杂环境。
4. 基于模型的电量计(Model-Based)
原理:通过电池数学模型(如Kalman滤波、神经网络)预测SoC。
特点:
-
依赖算法和处理器性能,灵活性高。
-
常用于BMS(电池管理系统)中的软件方案。
典型实现: -
电动汽车BMS(如特斯拉的电池管理算法)。
-
部分MCU内置的电池监测功能(如STM32的Battery Monitor)。
5. 其他类型
-
温度补偿型:通过温度传感器修正电压/电流数据(如MAX1720x)。
-
无线电量计:集成蓝牙/Wi-Fi传输数据(如TI BQ27220)。
选型关键因素
-
精度需求:高精度选阻抗跟踪型,低成本选电压检测型。
-
电池类型:锂离子、磷酸铁锂(LiFePO4)、铅酸等特性不同。
-
系统功耗:库仑计数器需持续供电,电压型可间歇工作。
-
封装与集成:是否需外接检流电阻、NTC等。
对比总结
类型 | 精度 | 复杂度 | 成本 | 适用场景 |
---|---|---|---|---|
电压检测型 | 低(±5%) | 低 | 低 | TWS耳机、手环 |
库仑计数型 | 中(±3%) | 中 | 中 | 智能手机、平板 |
阻抗跟踪型 | 高(±1%) | 高 | 高 | 笔记本、电动汽车 |
模型算法型 | 可变 | 极高 | 高 | 定制化BMS系统 |
根据具体需求(如成本、精度、功耗)选择合适类型即可。
二、CW2015简介
CW2015 是一款由赛微微电子(CellWise)推出的锂电池电量计芯片,主要用于单节锂离子/锂聚合物电池的电量监测(SoC, State of Charge)。它通过高精度电压检测和专利算法,实时估算电池剩余电量,并通过I²C接口输出数据,广泛应用于便携式电子设备。
CW2015属于电压检测型电量计,电路和使用相对简单。
应用电路图:
三、驱动注意事项(重要)
1.关于电池模型
CW2015是需要电池模型的,在头文件中定义的64字节。
生成模型数据的具体算法或者软件我也没有找到,所以使用的是github找来的一个驱动里的,这是一个4.2V锂电池的模型。
如果你也用的4.2V锂电池,那是可以用用的,只是刚开始精度没有那么高。
因为CW2015是依靠电池端电压从而给出的电量,而且电量是一个百分比数值,所以不管你是用的多大容量的电池,都没有关系,模型一样可以用。
使用过程中CW2015会不断进行容量的矫正,要求不高的场合比如消费类电子产品是完全可以用的。要求高的场合,建议使用基于库伦检测原理的芯片,如LTC2944。
2.支持2节电池
CW2015官方资料支持2节锂电池,但是如何支持两节锂电池的资料较少。支持2节电池的应用中可能要考虑芯片的供电电压是否超最大值(手册上最高电压为6V)、写电池信息寄存器和新的电池模型等。
本驱动支持的是1节电池应用,如何支持2节请自行研究。
3.容量校准
实际应用中要通过手动启动校准和读取温度补偿等方法,使电池容量更为准确。
4.一些术语
//术语:
// OCV (Open Circuit Voltage) - 开路电压:电池在无负载状态下的端电压
// SOC (State of Charge) - 荷电状态:电池剩余电量百分比
// RRT(剩余运行时间)
// ATHD (低电量报警阈值设置)
四、驱动头文件CW2015.h
#ifndef __CW2015_H__
#define __CW2015_H__
//术语:
// OCV (Open Circuit Voltage) - 开路电压:电池在无负载状态下的端电压
// SOC (State of Charge) - 荷电状态:电池剩余电量百分比
// RRT(剩余运行时间)
// ATHD (低电量报警阈值设置)
#include <Wire.h>
#include <Arduino.h>
#define CW2015_ADDR 0x62
/* CW2015 Register Definitions */
#define REG_VERSION 0x00
#define REG_VCELL 0x02
#define REG_SOC 0x04
#define REG_RRT_ALERT 0x06
#define REG_CONFIG 0x08
#define REG_MODE 0x0A
#define REG_BATINFO 0x10
/* Mode Masks */
#define MODE_SLEEP_MASK (0x3<<6)
#define MODE_SLEEP (0x3<<6)
#define MODE_NORMAL (0x0<<6)
#define MODE_QUICK_START (0x3<<4)
#define MODE_RESTART (0xF<<0)
/* Config Flags */
#define CONFIG_UPDATE_FLG (0x1<<1)
#define ATHD (0x0<<3) // ATHD = 0%
/* Battery Parameters */
#define BATTERY_UP_MAX_CHANGE 720
#define BATTERY_DOWN_MIN_CHANGE 60
#define BATTERY_DOWN_MIN_CHANGE_SLEEP 1800
#define SIZE_BATINFO 64
/* Battery configuration info - replace with your battery's profile */
static const uint8_t cw_bat_config_info[SIZE_BATINFO] = {
0x15, 0x7E, 0x7C, 0x5C, 0x64, 0x6A, 0x65, 0x5C,
0x55, 0x53, 0x56, 0x61, 0x6F, 0x66, 0x50, 0x48,
0x43, 0x42, 0x40, 0x43, 0x4B, 0x5F, 0x75, 0x7D,
0x52, 0x44, 0x07, 0xAE, 0x11, 0x22, 0x40, 0x56,
0x6C, 0x7C, 0x85, 0x86, 0x3D, 0x19, 0x8D, 0x1B,
0x06, 0x34, 0x46, 0x79, 0x8D, 0x90, 0x90, 0x46,
0x67, 0x80, 0x97, 0xAF, 0x80, 0x9F, 0xAE, 0xCB,
0x2F, 0x00, 0x64, 0xA5, 0xB5, 0x11, 0xD0, 0x11
};
/* Battery Information Structure */
struct CW_Battery {
uint8_t usb_online;
uint32_t capacity;
uint32_t voltage;
uint8_t alt;
};
class CW2015 {
public:
CW2015(TwoWire &wire = Wire, uint8_t sda = SDA, uint8_t scl = SCL);
bool begin(); // Initialize the device
bool updateVoltage(); // Update battery voltage
bool updateCapacity(); // Update battery capacity
bool releaseAlert(); // Release ALRT pin after low battery interrupt
bool setAthd(uint8_t new_athd); // Set ATHD threshold
// Get battery information
uint32_t getVoltage() const { return battery.voltage; }
uint32_t getCapacity() const { return battery.capacity; }
bool isUSBPowered() const { return battery.usb_online; }
bool hasAlert() const { return battery.alt; }
void setUSBPowered(bool powered) { battery.usb_online = powered ? 1 : 0; }
bool powerOnReset();
bool config();
bool updateConfigInfo();
int getCapacity();
int getVoltage();
private:
TwoWire &_wire;
uint8_t _sda;
uint8_t _scl;
CW_Battery battery;
uint8_t no_charger_full_jump = 0;
uint32_t allow_no_charger_full = 0;
uint32_t allow_charger_always_zero = 0;
uint8_t if_quickstart = 0;
uint8_t reset_loop = 0;
bool writeRegister(uint8_t reg, uint8_t value);
bool readRegister(uint8_t reg, uint8_t *value);
};
extern CW2015 cw2015; // Global instance
#endif
五、驱动源文件CW2015.cpp
#include "CW2015.h"
CW2015::CW2015(TwoWire &wire, uint8_t sda, uint8_t scl)
: _wire(wire), _sda(sda), _scl(scl) {
}
bool CW2015::begin() {
_wire.begin(_sda, _scl);
// Check if device responds
_wire.beginTransmission(CW2015_ADDR);
if (_wire.endTransmission() != 0) {
Serial.println("CW2015 not found");
return false;
}
// Initialize battery data
battery.usb_online = 0;
battery.capacity = 0;
battery.voltage = 0;
battery.alt = 0;
// Configure the device
if (!config()) {
Serial.println("CW2015 configuration failed");
return false;
}
Serial.println("CW2015 initialized");
return true;
}
bool CW2015::writeRegister(uint8_t reg, uint8_t value) {
_wire.beginTransmission(CW2015_ADDR);
_wire.write(reg);
_wire.write(value);
return _wire.endTransmission() == 0;
}
bool CW2015::readRegister(uint8_t reg, uint8_t *value) {
_wire.beginTransmission(CW2015_ADDR);
_wire.write(reg);
if (_wire.endTransmission(false) != 0) {
return false;
}
if (_wire.requestFrom(CW2015_ADDR, (uint8_t)1) != 1) {
return false;
}
*value = _wire.read();
return true;
}
bool CW2015::powerOnReset() {
if (!writeRegister(REG_MODE, MODE_SLEEP)) return false;
delay(1);
if (!writeRegister(REG_MODE, MODE_NORMAL)) return false;
delay(1);
return config();
}
bool CW2015::config() {
uint8_t reg_val;
// Wake up from sleep mode
if (!writeRegister(REG_MODE, MODE_NORMAL)) {
Serial.println("Wakeup fail");
return false;
}
// Check ATHD
if (!readRegister(REG_CONFIG, ®_val)) return false;
if ((reg_val & 0xF8) != ATHD) {
reg_val &= 0x07; // Clear ATHD
reg_val |= ATHD; // Set ATHD
if (!writeRegister(REG_CONFIG, reg_val)) return false;
}
// Check config update flag
if (!readRegister(REG_CONFIG, ®_val)) return false;
if (!(reg_val & CONFIG_UPDATE_FLG)) {
Serial.println("Update flag for new battery info needed");
if (!updateConfigInfo()) return false;
} else {
// Verify battery info
uint8_t i;
for (i = 0; i < SIZE_BATINFO; i++) {
if (!readRegister(REG_BATINFO + i, ®_val)) return false;
if (cw_bat_config_info[i] != reg_val) break;
}
if (i != SIZE_BATINFO) {
Serial.println("Update flag for new battery info needed (2)");
if (!updateConfigInfo()) return false;
}
}
// Check SOC
for (uint8_t i = 0; i < 30; i++) {
if (!readRegister(REG_SOC, ®_val)) return false;
if (reg_val <= 100) break;
delay(100);
if (i >= 29) {
if (!writeRegister(REG_MODE, MODE_SLEEP)) return false;
return false;
}
}
return true;
}
bool CW2015::updateConfigInfo() {
uint8_t reg_val;
// Make sure not in sleep mode
if (!readRegister(REG_MODE, ®_val)) return false;
if ((reg_val & MODE_SLEEP_MASK) == MODE_SLEEP) return false;
// Update battery info
for (uint8_t i = 0; i < SIZE_BATINFO; i++) {
if (!writeRegister(REG_BATINFO + i, cw_bat_config_info[i])) return false;
}
// Verify battery info
for (uint8_t i = 0; i < SIZE_BATINFO; i++) {
if (!readRegister(REG_BATINFO + i, ®_val)) return false;
if (reg_val != cw_bat_config_info[i]) return false;
}
// Set config flags
if (!readRegister(REG_CONFIG, ®_val)) return false;
reg_val |= CONFIG_UPDATE_FLG; // Set UPDATE_FLAG
reg_val &= 0x07; // Clear ATHD
reg_val |= ATHD; // Set ATHD
if (!writeRegister(REG_CONFIG, reg_val)) return false;
// Reset
if (!writeRegister(REG_MODE, MODE_RESTART)) return false;
delay(1);
if (!writeRegister(REG_MODE, MODE_NORMAL)) return false;
return true;
}
bool CW2015::setAthd(uint8_t new_athd) {
uint8_t reg_val;
if (!readRegister(REG_CONFIG, ®_val)) return false;
new_athd = new_athd << 3;
reg_val &= 0x07; // Clear ATHD
reg_val |= new_athd; // Set new ATHD
return writeRegister(REG_CONFIG, reg_val);
}
int CW2015::getCapacity() {
uint8_t reg_val;
uint8_t cw_capacity;
if (!readRegister(REG_SOC, ®_val)) return -1;
cw_capacity = reg_val;
if ((cw_capacity < 0) || (cw_capacity > 100)) {
reset_loop++;
if (reset_loop > 5) {
if (!powerOnReset()) return -1;
reset_loop = 0;
}
return battery.capacity;
} else {
reset_loop = 0;
}
if (((battery.usb_online == 1) && (cw_capacity == (battery.capacity - 1))) ||
((battery.usb_online == 0) && (cw_capacity == (battery.capacity + 1)))) {
if (!((cw_capacity == 0 && battery.capacity <= 2) ||
(cw_capacity == 100 && battery.capacity == 99))) {
cw_capacity = battery.capacity;
}
}
if ((battery.usb_online == 1) && (cw_capacity >= 95) && (cw_capacity <= battery.capacity)) {
// Avoid not charging full
allow_no_charger_full++;
if (allow_no_charger_full >= BATTERY_UP_MAX_CHANGE) {
uint8_t allow_capacity = battery.capacity + 1;
cw_capacity = (allow_capacity <= 100) ? allow_capacity : 100;
no_charger_full_jump = 1;
allow_no_charger_full = 0;
} else if (cw_capacity <= battery.capacity) {
cw_capacity = battery.capacity;
}
}
else if ((battery.usb_online == 0) && (cw_capacity <= battery.capacity) &&
(cw_capacity >= 90) && (no_charger_full_jump == 1)) {
// Avoid battery level jump
if (battery.usb_online == 0) {
allow_no_charger_full++;
}
if (allow_no_charger_full >= BATTERY_DOWN_MIN_CHANGE) {
uint8_t allow_capacity = battery.capacity - 1;
allow_no_charger_full = 0;
if (cw_capacity >= allow_capacity) {
no_charger_full_jump = 0;
} else {
cw_capacity = (allow_capacity > 0) ? allow_capacity : 0;
}
} else if (cw_capacity <= battery.capacity) {
cw_capacity = battery.capacity;
}
} else {
allow_no_charger_full = 0;
}
if ((battery.usb_online > 0) && (cw_capacity == 0)) {
allow_charger_always_zero++;
if ((allow_charger_always_zero >= BATTERY_DOWN_MIN_CHANGE_SLEEP) &&
(if_quickstart == 0)) {
if (!powerOnReset()) return -1;
if_quickstart = 1;
allow_charger_always_zero = 0;
}
}
else if ((if_quickstart == 1) && (battery.usb_online == 0)) {
if_quickstart = 0;
}
return cw_capacity;
}
int CW2015::getVoltage() {
uint32_t ad_value = 0;
uint32_t ad_buff = 0;
uint32_t ad_value_min = 0;
uint32_t ad_value_max = 0;
uint8_t reg_val[2];
for (uint8_t i = 0; i < 3; i++) {
if (!readRegister(REG_VCELL, ®_val[0])) return -1;
if (!readRegister(REG_VCELL + 1, ®_val[1])) return -1;
ad_buff = (reg_val[0] << 8) + reg_val[1];
if (i == 0) {
ad_value_min = ad_buff;
ad_value_max = ad_buff;
}
if (ad_buff < ad_value_min) ad_value_min = ad_buff;
if (ad_buff > ad_value_max) ad_value_max = ad_buff;
ad_value += ad_buff;
}
ad_value -= ad_value_min;
ad_value -= ad_value_max;
ad_value = ad_value * 305 / 1000;
return ad_value;
}
bool CW2015::releaseAlert() {
uint8_t reg_val;
if (!readRegister(REG_RRT_ALERT, ®_val)) return false;
battery.alt = reg_val & 0x80;
reg_val &= 0x7F;
return writeRegister(REG_RRT_ALERT, reg_val);
}
bool CW2015::updateCapacity() {
int capacity = getCapacity();
if (capacity == -1) return false;
if ((capacity >= 0) && (capacity <= 100) && (battery.capacity != capacity)) {
battery.capacity = capacity;
}
return true;
}
bool CW2015::updateVoltage() {
int voltage = getVoltage();
if (voltage == -1) return false;
if (voltage == 1) {
// Keep previous voltage
} else if (battery.voltage != voltage) {
battery.voltage = voltage;
}
return true;
}
六、使用方法
1.全局定义
#define I2C_SDA_PinNum 5
#define I2C_SCL_PinNum 4
CW2015 cw2015(Wire1, I2C_SDA_PinNum, I2C_SCL_PinNum); // Global instance
2.初始化
//Wire1.begin(I2C_SDA_PinNum, I2C_SCL_PinNum,150000);
if (!cw2015.begin()) {
Serial.println("Failed to initialize CW2015");
while (1); // Halt if initialization fails
}
else{
Serial.println("CW2015 init OK!");
}
第一行总线的初始化视实际情况。
3.数据读取
// Update battery information
cw2015.updateVoltage();
cw2015.updateCapacity();
// Print battery info
Serial.print("Voltage: ");
Serial.print(cw2015.getVoltage());
Serial.print(" mV, Capacity: ");
Serial.print(cw2015.getCapacity());
Serial.println("%");