ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

18650锂电池电压-容量曲线与SOC估算实战指南

18650锂电池电压-容量曲线与SOC估算实战指南 1. 为什么18650的电压-容量曲线值得花时间啃透18650锂电池的电压-容量关系是嵌入式电量计和BMS开发里最基础、也最容易被低估的一块内容。很多刚接触电池管理的朋友会觉得测一条放电曲线有什么难的接个电子负载、每隔几分钟记一次电压不就行了。但真正做过产品的人都知道从一条实验室里跑出来的漂亮曲线到设备屏幕上那个让人信得过的百分比数字中间隔着采样精度、温度补偿、老化修正、负载突变、静置回弹等一大堆坑。这篇文章就是围绕这条曲线把电量估算的完整链路拆开讲清楚。先说清楚这篇文章适合谁看。如果你正在用STM32、ESP32、S32K这类MCU做带电池的产品需要自己写电量显示逻辑那这篇内容基本可以当作实操参考。如果你在做BMS相关的硬件或固件需要理解AFE采回来的电压怎么变成SOC这里面的细节同样适用。哪怕你只是单纯想搞明白为什么手机电量从20%掉到10%感觉特别快看完也能有个清晰的答案。核心逻辑其实一句话就能概括锂电池的电压和剩余容量之间不是线性关系而是一条带平台区的非线性曲线电量估算的本质就是拿着实测电压去这条曲线上反查容量。但问题在于这条曲线会随温度、电流倍率、循环次数变化而且平台区电压变化极小ADC上几个LSB的误差就可能对应百分之十几的电量偏差。所以真正要做的不是“测一条曲线”而是建立一套能应对各种工况的估算体系。下面我会从整体设计思路开始把曲线怎么测、数据怎么处理、代码怎么写、问题怎么排查一层层展开。涉及参数计算的地方我会把过程写出来涉及代码的地方给可直接用的片段涉及踩坑的地方重点标注。2. 电量估算的整体设计与方案选型2.1 从开路电压法到库仑计法的取舍逻辑电量估算最朴素的方法就是开路电压法也就是OCV法。原理很直接电池静置足够长时间后端电压会稳定在一个和剩余容量对应的值上拿这个电压去查表就能得到SOC。这个方法不需要额外硬件成本为零在低端产品里用得非常多。但它有两个致命问题一是必须静置设备一工作电压就被负载拉低查出来的SOC会偏低二是平台区太平3.6V到3.7V之间可能对应40%的容量变化ADC稍微有点噪声SOC就跳来跳去。库仑计法走的是另一条路通过持续对电流积分来累计充放电电量。它的优点是动态响应好负载变化时也能跟踪缺点是会累积误差而且需要知道初始SOC才能开始积分。实际产品里最常见的做法是两者结合上电时用OCV查表得到初始SOC运行中用库仑计积分静置时再用OCV做修正。这个混合策略是我个人最推荐的方案下面所有的讨论都基于这个框架。选型上还有一个关键决策是采样方案。电流采样一般用采样电阻加专用电流检测放大器或者直接用AFE芯片内置的库仑计通道。电压采样可以用MCU内置ADC也可以用外置高精度ADC。如果产品对成本敏感、精度要求不高MCU内置的12位ADC配合分压电阻就够了。但如果要做1%以内的SOC精度建议用外置16位以上的ADC或者直接上带库仑计的AFE。这个取舍后面会详细算。2.2 电压-容量曲线在估算体系中的定位很多人把放电曲线当成一个孤立的参考数据实际上它在整个估算体系里承担的是“锚点”的角色。库仑计负责短期跟踪OCV曲线负责长期校准两者配合才能既快又准。理解这一点很重要因为它决定了你测曲线的方式。如果只是做OCV查表那曲线必须是在极小电流下测的接近真实开路状态。但如果你要做的是带负载的动态估算那还需要额外测一组不同倍率下的放电曲线用来做电压补偿。我见过不少项目只测了一条0.2C的曲线就上线结果设备一跑大电流电量显示直接崩掉就是因为没有考虑倍率对端电压的影响。另外曲线的分辨率也很关键。平台区每毫伏对应的容量变化可能只有零点几个百分点如果你的表格只按50mV一档来存那平台区的精度根本不够用。我的经验是平台区至少按10mV一档两端可以放宽到50mV。这个细节后面在数据表设计里会具体讲。2.3 温度、倍率、老化三个变量的处理优先级影响电压-容量关系的变量很多但资源有限必须排优先级。我的建议是温度补偿优先级最高倍率补偿次之老化修正最后做。原因是温度对平台区电压的影响最直接低温下内阻增大端电压整体下移如果不补偿冬天电量会显示得莫名其妙地低。倍率补偿主要影响大电流工况如果产品工作电流比较稳定可以先不做。老化修正需要长期数据积累一般产品生命周期内做一次容量衰减系数就够了。温度补偿的实现方式通常是测一组不同温度下的曲线然后在运行时根据当前温度做插值。如果资源紧张也可以只测高温、常温、低温三条曲线中间线性插值。倍率补偿则是在电压上叠加一个和电流成正比的修正项系数就是电池的内阻。这个内阻本身也随温度和SOC变化所以严格来说是个二维表但工程上用一个常温内阻值加温度系数近似就够了。3. 放电曲线实测与数据处理的核心细节3.1 测试平台搭建与关键设备选择测一条靠谱的放电曲线设备不需要多贵但几个关键点必须注意。电子负载要选支持恒流放电和容量累计的市面上常见的可编程电子负载基本都满足。电池夹具的接触电阻要尽量小我吃过这个亏一开始用弹簧夹接触电阻有几十毫欧大电流放电时压降明显曲线整体偏低。后来换成四线制开尔文夹问题就解决了。温度控制是另一个容易被忽略的点。如果只是常温测试放在恒温箱里当然最好没有的话至少保证环境温度稳定避免空调直吹。我一般会在电池表面贴一个NTC用数据记录仪同步记录温度这样后面做温度补偿时才有依据。数据记录方面电压采样间隔建议1秒一次容量累计用电子负载自带的积分功能或者自己用脚本按电流乘以时间累加。还有一点电池的一致性很重要。同一批次的18650容量差异可能有百分之几如果你只测一颗得到的曲线可能不代表整批。我的做法是至少测三颗取平均同时记录每颗的离散程度。如果离散太大说明电池来源有问题后面做产品会很难受。3.2 放电倍率与截止条件的确定放电倍率的选择取决于你的实际应用。如果是移动电源这类产品0.2C到0.5C比较有代表性。如果是电动工具可能要测1C甚至2C。我建议至少测两个倍率一个小电流0.1C或0.2C用来做OCV参考一个实际工作电流用来做动态补偿。截止电压一般设2.5V或2.75V具体看电池规格书。注意截止电压设得越低放出的容量越多但循环寿命会受影响这个取舍要在产品定义阶段就定好。测试流程上我习惯先做一次完整的充放电活化让电池进入稳定状态。然后静置两小时让电压充分回弹记录开路电压作为100%SOC的起点。接着开始恒流放电每隔一定容量或时间记录一次电压。放电结束后再静置两小时记录回弹后的电压。这个回弹电压对做OCV曲线很重要因为放电过程中的端电压是包含内阻压降的。3.3 从原始数据到可用SOC表的处理流程拿到原始数据后第一步是去噪。电子负载和ADC的读数都会有噪声直接用会导致曲线毛刺。我一般用滑动平均或者中值滤波窗口大小根据采样率定1秒采样的话用5点滑动平均就够了。注意滤波会引入延迟但离线处理曲线时无所谓。第二步是容量归一化。把累计放出的容量除以总容量得到0到1的SOC值。这里要注意总容量应该用实际放出的容量而不是标称容量。然后以SOC为横轴、电压为纵轴重新整理数据。因为放电过程中SOC是单调递减的所以可以直接插值。第三步是分段拟合。平台区用高密度采样点两端用低密度。我一般把曲线分成三段3.0V以下、3.0到4.0V、4.0V以上。中间段每10mV一个点两端每50mV一个点。这样一张表大概100到150个点用uint16存电压单位mVuint8存SOC单位0.5%总共不到500字节对大多数MCU都很友好。第四步是验证。把生成的表反查一遍看电压对应的SOC是否单调有没有跳变。我遇到过一次因为数据里有个异常点导致表里出现SOC回退设备上电量显示会突然跳一下。所以这一步不能省。4. 嵌入式端的电量估算实现与代码要点4.1 ADC采样电路设计与精度核算电压采样最常见的是电阻分压。假设电池最高4.2VMCU的ADC参考电压是3.3V那分压比至少要4.2/3.3≈1.27实际取1.5到2比较安全。比如用100k和100k分压得到一半电压4.2V对应2.1V留足了余量。但要注意分压电阻会持续耗电100k100k就是21微安对低功耗产品来说不小。可以用1M级别的电阻但ADC输入阻抗要匹配否则采样不准。STM32的ADC输入阻抗大概几十k用1M分压的话需要加一个缓冲运放或者加个小电容做电荷存储。精度核算很重要。假设用12位ADC参考3.3V那1个LSB就是0.8mV。经过2:1分压后对应电池端是1.6mV。前面说过平台区10mV对应可能5%的SOC那1.6mV就对应0.8%的SOC。看起来还行但这是理想情况。实际ADC有噪声、有偏移、有增益误差有效位数可能只有10位那误差就翻四倍。所以如果要做高精度要么用外置16位ADC要么在软件上做校准和滤波。校准方法很简单用高精度万用表测几个已知电压点和ADC读数对比拟合出增益和偏移。我一般取三个点1V、2V、3V用最小二乘法拟合。校准后的精度能提升不少。另外参考电压的稳定性也很关键如果MCU的VDD直接当参考那VDD波动会直接影响精度。建议用外部基准或者至少用内部基准并定期校准。4.2 查表与插值算法的C语言实现查表的核心是在有序数组里找到电压对应的区间然后线性插值。下面是一个可直接用的实现typedef struct { uint16_t voltage_mv; uint8_t soc_half_percent; // SOC以0.5%为单位 } OcvPoint; // 表按电压升序排列 static const OcvPoint ocv_table[] { {3000, 0}, {3200, 10}, {3300, 30}, /* ... */ {4200, 200} }; #define TABLE_SIZE (sizeof(ocv_table)/sizeof(ocv_table[0])) uint8_t ocv_lookup_soc(uint16_t voltage_mv) { if (voltage_mv ocv_table[0].voltage_mv) return ocv_table[0].soc_half_percent; if (voltage_mv ocv_table[TABLE_SIZE-1].voltage_mv) return ocv_table[TABLE_SIZE-1].soc_half_percent; for (int i 0; i TABLE_SIZE - 1; i) { if (voltage_mv ocv_table[i].voltage_mv voltage_mv ocv_table[i1].voltage_mv) { uint16_t v_low ocv_table[i].voltage_mv; uint16_t v_high ocv_table[i1].voltage_mv; uint8_t s_low ocv_table[i].soc_half_percent; uint8_t s_high ocv_table[i1].soc_half_percent; // 线性插值 uint32_t num (uint32_t)(voltage_mv - v_low) * (s_high - s_low); uint8_t soc s_low (uint8_t)(num / (v_high - v_low)); return soc; } } return 0; }这个实现是线性查找表不大的时候够用。如果表很长可以改成二分查找。注意插值用的是整数运算避免浮点对没有FPU的MCU很友好。SOC用0.5%为单位是为了在uint8里存下0到100%的范围同时保留足够分辨率。4.3 库仑计积分与OCV修正的融合策略库仑计的核心是电流积分。假设采样周期是100ms电流单位mA那每次采样的电量增量就是电流乘以时间。用mAh做单位的话增量是电流(mA) * 0.1(s) / 3600。为了用整数运算可以先把单位放大比如用微安时或者纳安时。static int64_t charge_uah 0; // 累计电量单位微安时 static uint32_t last_tick 0; void coulomb_update(int32_t current_ma, uint32_t tick_ms) { uint32_t dt tick_ms - last_tick; last_tick tick_ms; // current_ma为正表示充电负表示放电 charge_uah (int64_t)current_ma * dt * 1000 / 3600; }融合策略上我的做法是设备静置超过30分钟且电流小于某个阈值时用OCV查表得到的SOC去修正库仑计的累计值。修正时不要直接覆盖而是平滑过渡比如每次修正只改变10%的偏差避免电量显示跳变。另外充电时如果达到充满条件电压到4.2V且电流小于0.05C直接把SOC置为100%这是最可靠的锚点。4.4 温度补偿与内阻修正的工程近似温度补偿最简单的做法是准备三张表高温45度、常温25度、低温0度。运行时根据当前温度在两张表之间插值。如果资源不够也可以只存常温表然后对电压加一个温度修正项比如每降低10度电压加5mV。这个系数需要实测确定不同电池差异较大。内阻修正是在电压上叠加电流乘以内阻。内阻值可以从放电曲线里估算用不同倍率下的电压差除以电流差。比如0.2C时3.7V1C时3.6V电流差是0.8C假设电池2Ah那就是1.6A内阻就是0.1V/1.6A≈62毫欧。这个值随SOC变化低SOC时内阻会增大所以严格来说要做成表。工程上可以取一个中间值然后在低SOC区加一个修正系数。5. 常见问题排查与避坑经验实录5.1 电量跳变、显示不准的典型原因电量跳变是最常见的问题原因通常有几个。一是ADC噪声大平台区电压波动导致SOC查表结果跳来跳去。解决办法是加滤波同时对SOC做变化率限制比如每秒最多变化1%。二是库仑计累积误差长时间运行后偏差越来越大。解决办法是定期用OCV修正但修正时要平滑。三是温度突变比如从室内到室外电压整体下移SOC突然掉一截。解决办法是温度补偿要做及时温度采样也要滤波。还有一种情况是负载突变。大电流放电时电压被拉低如果直接用端电压查表SOC会瞬间偏低。这时候应该用内阻修正把电压补回去或者干脆在负载突变时暂停OCV修正只靠库仑计跟踪。5.2 ADC采样异常与硬件排查清单ADC采样异常往往出在硬件上。下面是我整理的一份排查清单按优先级排列现象可能原因排查方法读数始终为0或满量程分压电阻虚焊、ADC通道配置错误万用表测分压点电压检查寄存器配置读数跳动大参考电压不稳、地线干扰示波器看VREF和地加去耦电容读数整体偏高或偏低分压电阻误差、ADC增益误差用高精度万用表校准拟合增益偏移大电流时读数异常地线压降、采样点位置不对检查采样点是否在电池端而非负载端温度变化时读数漂移参考电压温漂、电阻温漂换低温漂电阻或用外部基准我踩过最坑的一次是采样点选在了负载端大电流时地线压降导致读数偏低电量显示一直不准。后来把采样点移到电池端就好了。这个细节在画板子的时候就要注意四线制采样是最稳妥的。5.3 曲线拟合与表格生成的实操心得曲线拟合这块我的经验是不要追求高阶多项式分段线性插值最实用。高阶拟合在数据点之间可能产生振荡反而引入误差。表格生成时我一般用Python脚本处理原始数据输出C数组。脚本里会做滤波、归一化、分段采样最后生成代码。这样每次换电池型号只要重新跑一遍脚本就行效率很高。还有一点表格的电压范围要留余量。比如实际曲线是3.0V到4.2V表格可以做到2.8V到4.3V两端做钳位。这样即使ADC有偏移也不会查表越界。另外SOC的单调性一定要检查我写了个简单的断言生成表格时如果发现SOC不单调就报错避免把错误数据烧进固件。5.4 从实验室到产品的最后一公里实验室曲线跑得再漂亮到产品上还是可能出问题。最大的差异是实际工况远比实验室复杂温度变化、负载波动、电池老化、个体差异每一个都会影响精度。我的建议是产品上线前做一轮实际场景测试记录真实使用中的电压、电流、温度然后离线分析SOC误差。如果误差在可接受范围内再小批量试产。另外用户对电量显示的感知很敏感。与其追求绝对精度不如保证显示的稳定性和一致性。比如同样剩20%不同设备显示应该差不多而且不能忽高忽低。这比单纯追求1%的精度更重要。我在实际项目里会把SOC做一点平滑和迟滞避免在临界点反复跳变用户体验会好很多。最后分享一个实用技巧如果产品有充电功能充电时的SOC估算可以简单一些因为充电曲线相对稳定而且充满条件很明确。把精力集中在放电估算上性价比更高。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进