核心概念:为什么测量电量这么难?
电池的剩余电量不是一个可以直接测量的物理量(如电压、电流),而是一个估算值,它主要受以下因素影响:

(图片来源网络,侵删)
- 放电率:同样的电池,大电流放电时,电压下降更快,能放出的总电量(容量)会变小,小电流放电则反之。
- 温度:低温下,电池内阻增大,可用容量会显著下降,高温则会加速老化。
- 老化程度:随着充放电次数增加,电池的实际最大容量会衰减。
- 电压平台:锂电池放电曲线有一个相对平坦的电压平台,在这个平台内,电压变化很小,仅凭电压很难精确判断电量。
任何单一的测量方法都存在局限性,通常需要多种方法结合,并通过算法进行修正。
常用电量测量方法对比
| 方法 | 原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 电压法 | 测量电池两端的电压,通过查表或公式换算成电量。 | - 最简单,只需要一个 ADC 通道。 - 成本低,功耗低。 |
- 精度差,尤其是在电压平台区。 - 无法区分温度和老化影响。 - 放电率影响大。 |
对电量精度要求不高的设备,如简单的 LED 手电筒、玩具等。 |
| 库仑计/ Coulomb Counting | 测量流入和流出电池的电流,对时间进行积分,计算净电荷量(电量)。 | - 精度较高,是目前最主流和精确的方法。 - 可以实时跟踪电量变化。 |
- 需要高精度电流传感器(如霍尔传感器、采样电阻)。 - 存在积分漂移问题,需要定期校准(如充满电时校准)。 - 计算量相对较大。 |
对电量精度要求高的设备,如手机、平板电脑、无人机、便携式仪器等。 |
| 阻抗谱法 | 向电池施加一个交流小信号,测量其阻抗(内阻),通过内阻变化估算电量。 | - 理论上可以反映电池健康状态。 | - 实现复杂,需要专门的电路和算法。 - 对噪声敏感。 - 在实际应用中很少单独使用。 |
主要用于实验室电池研究和电池健康状态评估。 |
| 混合法 | 结合电压法和库仑计法,取长补短。 | - 精度高,鲁棒性好。 - 库仑计负责动态跟踪,电压法用于校正和校准。 |
- 实现最复杂,需要复杂的算法。 | 高端应用,如电动汽车、高精度电池管理系统。 |
推荐方案:基于库仑计的混合法(最实用)
对于大多数 STM32 项目,“库仑计 + 电压校准” 是最佳选择,下面我们详细讲解这个方案的实现步骤。
第一步:硬件准备
- STM32 微控制器:选择带有高精度 ADC 和足够处理能力的型号(如 STM32F4, STM32L4, STM32H7 系列)。
- 电流检测电路:
- 方案A(低成本):在电池的负极和 GND 之间串联一个低阻值、高精度、低温漂的采样电阻(如 1mΩ, 5mΩ),通过一个运算放大器将电阻上的微小电压差放大,然后送入 STM32 的 ADC 通道。
- 方案B(高集成度):使用专用的电量计芯片,如 TI 的 BQ 系列(如 BQ27441)或 ADI 的 LTC 系列,这些芯片内部集成了高精度 ADC、库仑计算法和校准功能,通过 I2C/SPI 与 STM32 通信。这是最推荐、最省心的方案。
- 电压检测电路:使用 STM32 的 ADC 直接测量电池电压,如果电压超过 ADC 的参考电压(如 3.3V),需要使用电阻分压电路进行降压。
- 温度检测电路:使用一个 NTC 热敏电阻或 STM32 内置的温度传感器来监测电池温度。
第二步:软件实现(以“软件库仑计”为例,不使用专用芯片)
如果不想使用专用芯片,可以自己用 STM32 实现库仑计算法。
电流采样
// 假设 ADC 采样值为 adc_value,参考电压为 Vref (3.3V),放大倍数为 Gain,采样电阻为 Rsense
float calculate_current(uint16_t adc_value) {
float v_adc = (adc_value * Vref) / 4095.0; // 假设12位ADC
float v_sense = v_adc / Gain; // 运放放大后的电压除以增益,得到采样电阻上的电压
float current = v_sense / Rsense; // 欧姆定律,计算电流
return current;
}
库仑计核心算法
库仑计的基本原理是:ΔAh = I × Δt,我们需要在定时器中断中持续累加。

(图片来源网络,侵删)
// 全局变量
float total_charge_mAh = 0.0; // 累积的电量,单位 mAh
float battery_capacity_mAh = 2000.0; // 电池额定容量,单位 mAh
uint32_t last_update_time = 0; // 上一次更新的时间戳
// 在定时器中断(例如每 100ms)中调用
void update_coulomb_counting(void) {
uint32_t current_time = HAL_GetTick(); // 获取当前时间 (ms)
float delta_time_seconds = (current_time - last_update_time) / 1000.0;
last_update_time = current_time;
// 1. 读取当前电流
uint16_t current_adc_value = read_adc(); // 读取ADC值
float current_ma = calculate_current(current_adc_value); // 计算电流,单位 mA
// 2. 计算电量变化 (mAh = mA * s / 3600)
float delta_charge_mah = (current_ma * delta_time_seconds) / 3600.0;
total_charge_mAh += delta_charge_mAh;
// 3. 限制电量范围
if (total_charge_mAh > battery_capacity_mAh) {
total_charge_mAh = battery_capacity_mAh; // 防止过充
}
if (total_charge_mAh < 0) {
total_charge_mAh = 0; // 防止过放
}
}
电量百分比计算
float get_soc_percentage(void) {
return (total_charge_mAh / battery_capacity_mAh) * 100.0;
}
校准(至关重要!)
由于积分误差和测量误差,total_charge_mAh 会慢慢偏离真实值,校准是必须的。
- 满电校准:当检测到电池电压达到满电电压(如 4.2V)并且充电电流小于一个阈值(如 0.1C)时,可以认为电池已充满,将
total_charge_mAh强制设置为battery_capacity_mAh。 - 空电校准:当电池电压降到截止电压(如 3.0V)时,设备关机,下次开机时,可以将
total_charge_mAh设置为 0,但这不太可靠,因为空电时电压受负载影响大。
结合电压法进行修正
库仑计在长期运行后会漂移,我们可以利用电压作为参考来修正它。
- 建立电压-SoC 查找表:通过实验或查阅电池 datasheet,得到不同温度下的电压-SoC 曲线,存入 STM32 的内存中。
- 修正算法:
- 当电池处于静置状态(电流接近 0)一段时间后,此时电池电压最接近其开路电压。
- 读取此时的电压值
V_oc。 - 根据
V_oc和当前温度,从查找表中查到一个预期的 SoC (SoC_v)。 - 比较
SoC_v和库仑计计算的 SoC (SoC_c)。 - 如果两者差异较大,可以按一定比例或步长调整
total_charge_mAh,使其向SoC_v靠拢。
// 伪代码:静置时的修正
if (is_battery_idle() && abs(current_ma) < IDLE_CURRENT_THRESHOLD) {
float v_oc = read_battery_voltage();
float soc_v = look_up_soc_from_voltage(v_oc, get_temperature());
float soc_c = get_soc_percentage();
// 采用低通滤波器平滑修正,避免突变
float correction_factor = 0.1; // 修正系数,可调
total_charge_mAh += (soc_v - soc_c) * battery_capacity_mAh * correction_factor / 100.0;
}
推荐实践:使用专用电量计芯片(如 BQ27441)
对于绝大多数开发者,使用专用芯片是最高效、最可靠的方案。
- 硬件连接:按照芯片手册,将 BQ27441 通过 I2C 接口连接到 STM32,还需要连接电池电压、电流采样电阻和温度传感器。
- 软件库:芯片厂商(如 TI)通常会提供现成的库文件(如
bq274xx.c/h)。 - 使用方法:你只需要调用库函数即可获取高精度的电量、电压、电流、温度等信息。
#include "bq274xx.h"
// 初始化
if (BQ274xx_Init() != BQ274XX_OK) {
// 初始化失败处理
}
// 在主循环中
while(1) {
// 获取电量百分比
int soc = BQ274xx_GetSOC();
printf("Battery SoC: %d%%\n", soc);
// 获取剩余容量 (mAh)
int rm = BQ274xx_GetRemainingCapacity();
printf("Remaining Capacity: %dmAh\n", rm);
// 获取电池状态 (健康、满电、空电等)
int flags = BQ274xx_GetFlags();
if (flags & BQ274XX_FLAG_SOC_FAULT) {
printf("SoC Calculation Fault!\n");
}
HAL_Delay(1000);
}
优点:

(图片来源网络,侵删)
- 精度高:芯片内部集成了复杂的算法,已经处理了温度、放电率、老化等因素。
- 开发快:无需自己编写复杂的库仑计和校准算法。
- 功能丰富:除了 SoC,还能提供电池健康状态、内阻、满充容量等更多信息。
缺点:
- 增加少量成本(芯片本身和少量外围元件)。
- 需要理解芯片的配置寄存器,但厂商库已经大大简化了这一过程。
| 方法 | 实现难度 | 精度 | 推荐指数 |
|---|---|---|---|
| 电压法 | 低 | 低 | ★☆☆☆☆ (仅用于简单指示) |
| 软件库仑计 | 高 | 中 (需良好校准) | ★★★☆☆ (适合学习,但工程上复杂) |
| 专用电量计芯片 | 中 | 高 | ★★★★★ (工程首选,省心省力) |
给你的建议:
- 如果你的项目是学习或原型验证,且对精度要求不高:可以从电压法开始,快速实现一个基本功能。
- 如果你的项目需要较高的电量精度,并且你希望深入理解算法:可以尝试自己实现软件库仑计,并务必做好校准和电压修正。
- 如果你的项目是最终产品,对可靠性、精度和开发周期有要求:强烈推荐使用专用电量计芯片(如 BQ27441),这是工业界和消费电子领域的标准做法,能让你事半功倍。
