3、电量监测基础:电压法、库仑计数法、阻抗跟踪法
做按摩仪电池管理,最头疼的问题是什么?
我个人觉得,不是充电慢,也不是电池寿命短,而是——你永远不知道设备还能撑多久。
用户正按得舒服呢,突然没电了。这体验,你说糟不糟心?
所以,电量监测是电池管理的核心。今天咱们就聊聊三种主流方法:电压法、库仑计数法、阻抗跟踪法。每种方法我都踩过坑,咱们一个一个说。
3.1 电压法:最简单,但坑最多
电压法,说白了就是查表。
你测一下电池两端电压,然后去查一个预设的电压-电量对应表,就能知道大概还剩多少电。
核心原理:锂电池的电压与剩余电量存在一定的对应关系。满电时约4.2V,没电时约3.0V。
听起来很简单对吧?但我告诉你,这方法在按摩仪上基本不靠谱。
为什么?因为按摩仪工作时,电机一启动,电流瞬间拉大,电池电压会骤降。你测到的电压根本不是真实电压,而是被负载拉低的“假电压”。
避坑指南:我曾经在一个项目里直接用电压法显示电量,结果用户一开机,电量从80%直接跳到30%。用户投诉说“这破机器电量显示是随机的吧?”——嗯,从那以后我再也不敢在动态负载下用纯电压法了。
电压法适合什么场景?
- 待机状态:设备不工作,负载很小,电压相对稳定
- 成本敏感:MCU资源紧张,不想加额外芯片
- 粗略估算:只需要显示“低、中、高”三档电量
代码实现也很简单,我贴一个我常用的查表函数:
// 电压-电量对应表(单位:mV)
// 温度25°C,空载条件下
const uint16_t voltage_table[] = {
4200, // 100%
4100, // 90%
3950, // 80%
3850, // 70%
3750, // 60%
3650, // 50%
3550, // 40%
3450, // 30%
3350, // 20%
3200, // 10%
3000 // 0%
};
uint8_t get_battery_level(uint16_t voltage_mv) {
for (int i = 0; i < 11; i++) {
if (voltage_mv >= voltage_table[i]) {
return 100 - i * 10;
}
}
return 0;
}
小技巧:实际使用时,建议做滑动平均滤波。连续采样5-10次,去掉最大值最小值,再取平均。这样能滤掉电压抖动。
3.2 库仑计数法:精度高,但会漂
库仑计数法,说白了就是数电子。
你想想看,电池放电,流出去的电荷量 = 电流 × 时间。如果能实时监测电流,再对时间积分,就能算出放了多少电。
核心公式:剩余电量 = 初始电量 - ∫(电流 × dt)
这个方法精度高,但有个致命问题——误差会累积。
我做过一个项目,用库仑计芯片监测电量。刚开始挺准的,误差在1%以内。但用了两周后,误差慢慢漂到了5%。为什么?因为电流采样有微小偏差,每次积分都带一点误差,日积月累就大了。
避坑指南:我曾经以为库仑计一劳永逸,结果产品出货后,用户反馈“电量显示越来越不准”。后来我加了定期校准机制——每次充满电时,强制把电量重置为100%。这才解决了漂移问题。
库仑计数法的硬件实现有两种:
- 专用库仑计芯片:如TI的BQ系列,精度高,但贵
- MCU+ADC采样:成本低,但需要MCU有足够算力
我贴一个MCU实现的简化代码:
// 库仑计数法实现(简化版)
// 每10ms执行一次
static int32_t remaining_capacity_mah = 2000; // 初始容量2000mAh
static uint32_t last_tick = 0;
void coulomb_counting_update(uint16_t current_ma) {
uint32_t now = get_system_tick();
uint32_t delta_ms = now - last_tick;
last_tick = now;
// 放电时电流为正,充电时为负
// 积分:容量(mAh) = 电流(mA) × 时间(h)
// delta_ms转小时:delta_ms / 3600000
int32_t delta_capacity = (int32_t)current_ma * delta_ms / 3600000;
remaining_capacity_mah -= delta_capacity;
// 防止过冲
if (remaining_capacity_mah < 0) remaining_capacity_mah = 0;
if (remaining_capacity_mah > 2000) remaining_capacity_mah = 2000;
}
// 充满电时调用
void coulomb_reset(void) {
remaining_capacity_mah = 2000; // 重置为满电
}
小技巧:库仑计法的精度取决于电流采样频率。按摩仪电机启动瞬间电流变化很快,建议采样频率不低于100Hz。另外,低功耗模式下可以降低采样率以省电。
3.3 阻抗跟踪法:最准,但也最复杂
阻抗跟踪法,是TI公司搞出来的技术。说白了,它结合了电压法和库仑计数法的优点,还考虑了电池内阻的变化。
你想想看,电池用久了,内阻会变大。同样放100mA电流,新电池电压降0.1V,旧电池可能降0.3V。如果不考虑内阻变化,电量估算肯定不准。
核心思路:实时监测电池内阻,根据内阻修正电压-电量对应关系,再用库仑计数法做动态积分。
这个方法我是在一个高端按摩仪项目里用的。说实话,效果确实好——电量显示误差能控制在2%以内,而且不会随时间漂移。
但代价是什么?
- 硬件复杂:需要高精度ADC,同时采集电压和电流
- 算法复杂:需要建立电池模型,做实时参数辨识
- 成本高:通常需要专用芯片,如TI的BQ34Z100
我简单说一下阻抗跟踪法的流程:
- 开路电压测量:设备待机时,测电池开路电压,查表得到初始电量
- 内阻计算:负载变化时,记录电压变化ΔV和电流变化ΔI,内阻R = ΔV / ΔI
- 动态修正:根据内阻修正电压-电量曲线,再用库仑计数法跟踪
- 定期自学习:每次完整充放电循环后,更新电池模型参数
避坑指南:我曾经以为阻抗跟踪法一劳永逸,结果发现温度影响很大。冬天电池内阻会增大,如果不做温度补偿,电量显示会偏大。后来我加了NTC温度传感器,根据温度查表修正内阻值,这才搞定。
这里贴一个简化的阻抗跟踪法伪代码:
// 阻抗跟踪法(伪代码)
// 每100ms执行一次
void impedance_track_update(uint16_t voltage_mv, uint16_t current_ma, int16_t temperature_c) {
static uint16_t last_voltage = 0;
static uint16_t last_current = 0;
static uint16_t internal_resistance = 100; // 初始内阻100mΩ
// 1. 检测负载变化
if (abs(current_ma - last_current) > 50) {
// 2. 计算内阻
uint16_t delta_v = abs(voltage_mv - last_voltage);
uint16_t delta_i = abs(current_ma - last_current);
if (delta_i > 0) {
// 内阻 = 电压变化 / 电流变化(单位:mΩ)
internal_resistance = delta_v * 1000 / delta_i;
}
}
// 3. 温度补偿
// 温度每降低10°C,内阻增加约15%
int16_t temp_factor = (25 - temperature_c) / 10 * 15;
uint16_t compensated_resistance = internal_resistance * (100 + temp_factor) / 100;
// 4. 修正电压(补偿IR压降)
uint16_t corrected_voltage = voltage_mv + (current_ma * compensated_resistance / 1000);
// 5. 用修正后的电压查表得到电量
uint8_t level = get_battery_level(corrected_voltage);
// 6. 更新历史值
last_voltage = voltage_mv;
last_current = current_ma;
}
小技巧:如果你不想用专用芯片,也可以用MCU实现简化版的阻抗跟踪。关键是要做好内阻的平滑滤波,因为单次计算的内阻值噪声很大。我习惯用一阶低通滤波,系数取0.1左右。
3.4 三种方法对比
最后,我做个总结对比,方便你选型:
| 方法 | 精度 | 成本 | 复杂度 | 适用场景 |
|---|---|---|---|---|
| 电压法 | 低(±20%) | 极低 | 简单 | 待机状态、粗略指示 |
| 库仑计数法 | 中(±5%) | 中 | 中等 | 动态负载、需要连续监测 |
| 阻抗跟踪法 | 高(±2%) | 高 | 复杂 | 高端产品、长寿命要求 |
我个人建议:
- 如果做低成本按摩仪,用电压法+待机校准就够了
- 如果做中端产品,用库仑计数法+定期充满校准
- 如果做高端旗舰,直接上阻抗跟踪法,用户体验好很多
嗯,电量监测这块就聊到这儿。下一章咱们讲讲充电管理,那又是另一番天地了。