ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

用STM32F103从零打造4串18650电池组BMS:采集、SOC、保护与均衡详解

用STM32F103从零打造4串18650电池组BMS:采集、SOC、保护与均衡详解 先说结论我花了大半年时间用一块STM32F103单片机从零搭了一套4串18650锂电池组的电池管理系统BMS。不是用现成的BMS芯片而是用普通单片机自己采集电压、电流、温度自己算SOC自己写保护逻辑和均衡策略。这套系统最终跑在了一个自制储能电源上充电、放电、保护、SOC估算都能正常工作误差控制在5%以内。如果你也想做单片机BMS、电池相关毕设或者单纯想搞明白电池管理系统内部到底在做什么这篇文章应该能给你一套完整的参考。1. 用单片机做BMS到底在做什么先给系统划个边界1.1 BMS不是只有一块保护板很多人一想到电池管理系统第一反应就是“不就是个保护板吗”。确实市面上几块钱的保护板能实现过充、过放、短路保护但那只完成了BMS最基础的一小部分。真正的电池管理系统至少要做四件事采集、估算、保护、均衡。采集是拿到每一节电芯的电压、回路电流、温度这些原始数据估算是在这些数据的基础上算出电池还剩多少电、当前健康状态如何保护是在异常工况下切断回路或者降功率均衡是解决电芯之间电压不一致的问题把容量短板补上来。我之前也天真地以为用单片机做BMS最复杂的是算法后来发现算法只是一部分更大的工作量在硬件可靠性和软件状态管理上。尤其是采样误差控制和保护逻辑的状态转移这两块做不好SOC算得再准也没有意义因为系统可能在关键时刻误动作或者该动作的时候不动作。1.2 为什么选单片机而不是专用BMS芯片先说清楚一个问题市面上有大量专用BMS AFE芯片比如BQ76940、LTC6804、SH367309这些它们把采集、均衡驱动甚至保护都集成好了单片机只要通过I2C或者SPI读数据、下命令就行。那为什么还要用单片机硬扛原因有三。第一是成本做实验和毕设阶段动辄几十块钱的AFE芯片加上配套电路比一片几块钱的STM32F103C8T6贵不少而且AFE芯片的采购周期对小批量用户不友好。第二是可控性和学习价值用AFE芯片时你拿到的只是寄存器数据底层采样原理、误差来源、滤波策略你依然是一头雾水而用单片机的ADC直接采集每一个环节都能看到、能改、能调试对理解BMS工作机理特别有帮助。第三是灵活性有些特殊场景需要对采样时序、保护阈值做非常规定制通用AFE不一定满足自己写代码反而自由。当然纯单片机方案也有它的边界ADC精度受限于单片机内部基准、采样通道切换需要时间、均衡驱动能力弱。所以这种方案更适合实验验证、小功率场景和教学项目真要上电动车、储能柜这种级别还是得用AFE加功能安全设计的完整方案。把边界认识清楚做项目时才能不钻牛角尖。1.3 我的目标系统4串18650动力电池组这套BMS瞄准的是4串18650锂电池组标称电压14.8V充满电16.8V容量大概10Ah。为什么选4串因为串数少采样通道少单片机ADC还能应付得过来同时4串又能覆盖串联均衡、单体压差管理这些核心问题对学习来说刚好。如果你做7串、10串甚至更多原理是一样的但ADC通道就不够了得加模拟开关或者换多通道AFE。系统功能定义如下实时采集4节电芯电压、总电流、2个温度点电芯表面和MOS管附近计算SOC和剩余容量实现过充、过放、过流、短路、过温保护充电过程中自动执行被动均衡把最高电压电芯的能量通过电阻放掉。数据通过串口上报到上位机方便观察曲线。整个系统由电池组自身供电通过一个DC-DC降压到3.3V给单片机用。2. 主控选型与硬件架构STM32F103还是STC82.1 两套方案的真实取舍做单片机BMS主控选型是第一道分水岭。我身边有人用STC8这种增强型51也有人直接用STM32两边都有道理但侧重点完全不同。STM32F103的好处是12位ADC、多通道、DMA、硬件定时器资源丰富跑状态机、滤波算法、通信协议都很从容STC8的优势是51内核资料多、上手门槛低、价格便宜毕设答辩时解释起来也简单。但STC8的ADC是10位采样精度天然差一截内部基准电压一致性也一般做BMS这种对精度敏感的场合需要额外的校准和外部基准方案。我的建议是如果你是为了学习原理、做实验验证STM32F103是更好的选择不是因为51不行而是因为你需要的是更精准的数据来观察算法效果一个粗糙的采样值会掩盖很多问题。如果你做的是课程设计、时间紧用STC8配合外部基准也能完成功能演示但不要指望SOC误差能压到很小。这里顺便提一个热词里很常见的问题单片机选型之后怎么快速把工程跑起来。我用的是标准库开发的STM32F103C8T6最小系统板SWD接口下载调试配合一块OLED显示电压和SOC。建议你也直接买现成的最小系统板来做验证不要一开始就折腾PCB打样先把算法和逻辑跑通再画板子不迟。2.2 采样方案对比电阻分压、专用AFE、运放ADC4串电池组的总电压最高到16.8V单片机ADC输入范围是0到3.3V所以必须设计信号调理电路。我对比过三种方案简单说下各自的坑。第一种是纯电阻分压每节电芯电压通过两个电阻分压后直接进ADC。优点是电路简单、成本极低缺点也很明显分压电阻会持续消耗电池电量而且电阻精度直接影响采样精度温漂问题在温度变化大的场合会更明显。另外还有一个致命问题如果某个采样点的滤波电容不合适充电机PWM纹波干扰会让ADC读数跳得你怀疑人生。第二种是专用AFE方案如BQ76940这类芯片它们内部有精密电阻网络和Σ-Δ ADC精度和稳定性都非常好还能直接驱动均衡MOS。但前面也说了这种方案的代码复杂度和对硬件设计的要求都更高而且失去了“自己控制采样链路”的学习过程。第三种是我最终采用的方案用高精度运放做差分放大把每节电芯的电压转换到ADC的合适范围内。具体来说用四颗运放把4节电芯电压分别衰减到0到3V区间再用一颗运放配合采样电阻把电流转换为电压信号。这个方案比纯分压多了一点点成本但输入阻抗高、抗干扰能力强、精度可控非常适合实验阶段的BMS。2.3 供电与隔离设计电池组供电给单片机需要一个降压电路但这里有一个很容易忽略的隐患BMS的地必须跟电池组的负极共地而单片机的地和采样电路的地也会连在一起。如果用开关电源DC-DC降压输出的纹波会直接污染ADC参考地导致采样值抖动。我用了一颗低纹波的LDO把电池电压降到5V再通过AMS1117降到3.3V中间加了LC滤波。实测下来ADC噪声明显小于直接用开关电源的情况。另外一个隔离问题也要考虑。如果后续要把BMS接到充电器通信或者上位机串口通信最好做隔离。热词里“modbus单片机帧接收数据程序”常被搜索如果你做的是工业级BMSRS485通信基本是标配那就必须用隔离电源和隔离收发器。我这套系统实验阶段只用USB转串口所以没做隔离但原理上要清楚不隔离的话电池组高压侧的地电位波动可能通过串口线传到电脑严重的会烧USB口。2.4 被动均衡电路均衡是BMS区别于普通保护板的重要功能。我用了最经典的被动均衡方案每节电芯并联一个均衡电阻和一个MOS开关充电过程中如果某一节电压高于平均值且压差超过阈值就打开这节电芯对应的MOS让多余的能量通过电阻耗散掉。电阻我选的是20欧姆均衡电流大概80毫安对4串10Ah电芯组来说足够再大虽然均衡快但发热厉害板子扛不住。这里说一个大家容易忽略的点被动均衡的本质是“用发热换一致性”所以只能在充电末期或者静置时做放电过程中做大电流均衡是没有意义的反而会加剧电芯发热。我在这套系统里的策略是充电时每30秒检测一次压差如果最大压差超过20mV就开启均衡直到压差小于10mV。这套逻辑跑下来经过几轮完整充放电循环电芯之间的压差能从40mV收敛到15mV以内。3. 电压电流温度采集的工程实现误差从哪来怎么压下去3.1 采样电路与ADC配置的具体做法硬件选型和画板只是一部分真正让数据可信的是软件上的采样配置和校准。先看电压采集。我用的STM32F103内部12位ADC参考电压用内部VREF实测发现内部基准在不同温度下会有几十毫伏的漂移。为了削弱这个影响我做了两件事第一软件里每次上电后用一颗TL431精密基准源作为校准参考反推当前实际参考电压第二采样结果做滑动平均滤波窗口取16个点。ADC配置上我开了扫描模式依次采集4路电芯电压、1路电流采样电压、2路温度NTC电压总共7个通道用DMA搬运结果避免CPU在中断里花太多时间。电流采集用的是采样电阻加运放放大方案。采样电阻选的是毫欧级的锰铜电阻阻值1毫欧允许电流20A四线制接到运放输入端避免走线电阻引入误差。运放放大倍数设在50倍左右这样10A电流对应500mV电压处在ADC的最佳采样区间。需要注意的是运放的零点失调电压会被放大50倍所以软件校准里必须有一个“零电流偏移补偿”的环节开机时假设回路电流为0把这个时刻的ADC读数记录下来后续所有电流值先减去这个偏移再放大。温度采集就简单一些用10K NTC热敏电阻做分压查表得到温度值。NTC的精度本身不高±1度到±2度都很正常但对保护逻辑来说足够。如果你对温度精度有更高要求可以换数字温度传感器比如DS18B20或者NTC加高精度ADC的补偿算法。3.2 校准流程没有标准源误差永远是个谜我在调试过程中最大的体会是采集电路硬件做得再好不做校准也是白搭。校准的思路不复杂用一台精度足够高的万用表我用的是一台四位半的台式万用表同时测量电芯电压和单片机计算的电压值然后在软件里做一个比例修正。具体步骤是这样的先给电池组充满电此时每节电芯电压约4.2V记录单片机读数和万用表读数然后放电到3.7V左右再记录一组数据。每一节电芯的修正系数就是“标准值/实测值”存到Flash里以后每次启动加载。这个做法虽然没有专业校准设备的精度但对于实验项目来说能把误差从±50mV压到±10mV以内已经完全够用。校准还有一个容易被忽略的细节校准时的采样滤波窗口要跟实际运行一致。如果你校准时用的是16点滑动平均运行时却改了滤波参数那校准系数就不匹配了。我一开始就是因为调试时临时改了滤波窗口结果校准后误差反而变大排查了整整两天才发现是这个原因。3.3 采样滤波与抗干扰ADC的江湖不止是算平均滤波看起来简单但选什么滤波器直接影响系统的响应速度。滑动平均能滤掉高频噪声但滞后也大比如电芯电压在过充保护临界点附近快速上升时滤波后的值可能让你晚几十毫秒才触发保护这在电池管理里是不能接受的。我最终用的是“中值滑动平均”的组合先取5次采样值排序去掉最大值和最小值剩下3个取平均再做4次这样的结果做滑动平均。这个组合能同时对付脉冲干扰和高频噪声滞后也只有很小的几百毫秒实测效果很好。电流采样还需要额外加一个低通滤波因为充放电时的PWM电流纹波成分很大不滤掉的话安时积分会被这些噪声污染SOC会越算越偏。还有一个容易被忽略的抗干扰手段是软件上的“采样合理性检查”每一路电压都设置了上下限超出合理范围的采样值直接丢弃而不是参与滤波。这样即使有偶发的外部干扰或者接触不良导致的跳变也不会污染滤波数据。4. SOC估算这块电池到底还剩多少电4.1 OCV查表法静置时的最准答案SOC估算可能是整个BMS里被搜索最多的话题热词里“电池管理系统soc计算”排得很靠前。SOC的算法很多但实际工程里用得最多的还是开路电压法加安时积分法融合。开路电压法的原理很简单锂电池在静置足够久以后开路电压和SOC之间存在确定的对应关系。磷酸铁锂的OCV曲线在中段非常平几乎是一条直线所以这个方法对磷酸铁锂效果差三元锂电池的OCV曲线在中段有一定斜率稍微好用一点。我使用的是三元锂18650充满后静置2小时测得的开路电压对应SOC大约在95%到100%之间。实现上我在程序里存了一张OCV-SOC对应表每0.1V一个档位一共几十个点程序通过线性插值查表。这张表不是我凭空编的而是把电池从满电状态静置放电每放出5%电量后静置半小时再测开路电压一点一点描出来的。这个过程很耗时但做一次之后就能长期使用。4.2 安时积分法动态过程的唯一选择电池在充放电过程中不可能总是静置所以OCV法没法实时用。这时候就得靠安时积分法SOC(t) SOC(t0) - ∫I dt / 额定容量。说白了就是把电流对时间积分算出充进去或者放出来的电量再用初始SOC去算当前SOC。用单片机实现安时积分有几个坑。第一个是时间基准我用了定时器产生1秒的中断在中断里把这段时间内的平均电流累加到容量计数器里。第二个是电流方向充电和放电的方向相反符号不能搞错否则SOC会越算越多或者越算越少。第三个是效率系数充进去的电不可能100%存下来锂电池的库仑效率一般在99%以上但在实验中我发现用100%效率去积分连续几个循环后SOC会偏高所以我给充电积分乘了一个0.98的系数虽然这么做在理论上不严谨但实测效果更贴近真实剩余电量。4.3 OCV与安时积分怎么融合单独用安时积分有一个致命弱点电流传感器有零漂积分会累积误差。哪怕你做了零电流校准传感器噪声和ADC量化误差也会随着时间不断累积。我做过一个测试不融合OCV修正连续放电2小时后积分算出的SOC和实际值差了接近10%。解决的办法就是定期用OCV修正SOC。具体策略是系统检测到电池组静置时间超过30分钟且电流绝对值小于50mA就认为进入了静置状态这时用OCV查表结果直接覆盖当前的SOC值。如果静置时间不够长OCV还没稳定修正幅度就要限制比如每次最多修正2%到3%。静置期间如果检测到电流突然增大说明电池开始工作了修正立即停止转回安时积分。这个“修正幅度限制”非常关键我一开始允许静置任意时间都全量修正结果有一次电池只是被充电器短暂激活了一下OCV读数还没稳定就误修正了一次SOC跳了8个百分点把我吓了一跳。限制修正幅度之后这类问题基本绝迹。4.4 温度和倍率修正别让SOC在冬天“缩水”锂电池的可用容量受温度影响很大0度时放出的电可能只有25度时的八成左右。如果SOC算法不把温度因素考虑进去冬天会看到一个诡异的现象电池明明还有50%的SOC但电压已经掉到过放保护阈值了。我处理这个问题的方法是在SOC显示层做修正而不是在积分层修正。积分层还是老老实实按实际电流积分但最终显示给用户和用于低电量预警的SOC会根据当前温度乘一个温度修正系数。修正系数表也是实验测出来的我在冰箱里把电池组冻到5度和0度放电对比实际容量得到一条修正曲线。另外大电流放电时电池的端电压会明显下降看起来像是没电了实际上只是“电压塌陷”。这个现象在小倍率放电时不明显但10A以上的大电流下很容易误判。所以我额外引入了一个动态修正当放电电流大于1C时按电流大小降低过放保护的触发电压阈值避免大电流下过早保护。5. 保护逻辑与状态机不能让电池在危险区工作5.1 保护阈值和迟滞为什么不能只设一个值BMS的保护功能听起来简单电压高了断开电压低了断开温度高了断开。但实际做起来最麻烦的是“什么时候恢复”。如果你把过充保护阈值设为4.25V断开充电后电池电压并不会立刻降到4.25V以下它会停留在4.22V左右很长时间。如果你在电压跌到4.25V以下就恢复充电MOS管会反复开关继电器会哒哒哒响个不停这就是典型的“保护振荡”。解决保护振荡的标准做法是加迟滞过充保护触发后必须等电压降到4.15V以下才能恢复充电过放保护触发后必须等电压回升到3.3V以上才能恢复放电。迟滞量不是拍脑袋定的要参考电芯的恢复特性和系统允许的波动范围。我实测下来0.05V到0.1V的迟滞对三元锂18650来说比较合适太小压不住振荡太大会让可用容量变少。保护阈值也不是一成不变的要区分场景。充电时的过压阈值我设在4.25V是因为充电过程中极化电压会让端电压略高于真实电压而静置时的单体过压告警我设在4.20V一旦超过说明电芯已经明显过充了。这两个值如果混用一个要么保护太灵敏要么保护太迟钝。5.2 状态机别让BMS在保护态和正常态之间乱跳所有保护逻辑不是一堆if-else堆在一起就能稳定的我用状态机来管理BMS的运行状态。系统共有五个状态待机、充电、放电、保护锁定、错误。待机是默认状态电流几乎为零充电和放电状态根据电流方向和使能信号切换如果任何保护条件触发系统进入保护锁定状态。保护锁定状态有一个特别重要的设计一旦进入必须在满足“所有保护条件都恢复”之后再经过一个延时我设为3秒才能解除锁定回到待机状态。这个延时的作用是确认电池确实恢复正常了而不是刚好处于临界抖动区间。在保护锁定状态下充电和放电MOS管都是关断的通信仍然工作但只上报状态和数据不接受启动指令。状态转移代码里我还加了一个“保护源记录”功能触发保护时把是哪种保护过充、过放、过流、过温记录到一个变量里上位机一读就知道刚才发生了什么。这个功能在调试阶段帮了我大忙尤其是排查那种“偶尔保护一下又恢复”的奇怪问题时没有这个记录你根本不知道是电压触发的还是电流触发的。5.3 看门狗与死机恢复BMS不能有“假死”状态热词里有“单片机死机后软件看门狗需要多次复位”这个说法这在实际项目里确实是个真实痛点。BMS是长期带电运行的单片机万一跑飞了看门狗会复位系统但如果复位后程序初始化逻辑没写对可能会反复复位看起来就像“必须多次复位才能正常工作”。我在BMS里用了两层看门狗策略。第一层是硬件看门狗IWDG独立于内核运行一旦主循环超过1秒没有喂狗自动复位单片机。第二层是我在软件里加的一个“运行状态自检”主循环每次循环会检查一个运行计数器的值如果发现这个值在短时间内没有递增说明程序卡在某个死循环里了主动触发软件复位。这两层下来跑了大半年没有出现一次真正意义上的“假死”。还有一个经验看门狗喂狗的位置不能放在中断里必须放在主循环里。如果你在定时器中断里喂狗即使主循环卡死了中断还在跑看门狗永远不会超时程序就永远卡死在那了。这个坑我踩过一次当时主循环里一个串口发送函数因为缓冲区满了阻塞住但定时器中断还在喂狗系统看起来活着实际已经完全不工作了。5.4 保护动作的执行机构MOS管驱动与继电器保护动作最终要靠执行机构完成我在放电回路里串了背靠背的NMOS管充电回路里也有一对MOS管。为什么用背靠背因为单个MOS管在关断状态下体二极管会把电流方向的一边导通导致关不断。背靠背连接让两个MOS管的体二极管方向相反两个都关闭时电流两个方向都断开了。MOS管的驱动电压要特别注意NMOS需要Vgs大于阈值才能完全导通而电池组的电压最高16.8V直接用单片机3.3V是驱动不动的。我用了一颗专用的栅极驱动芯片把控制信号升压后驱动MOS管。如果你是在做一个简单的低压系统也可以用三极管加电阻搭一个推挽电路来驱动但响应速度会慢一些不适合做短路保护。短路保护对所有BMS来说都是最难处理的场景之一。短路瞬间电流可以达到几十安甚至上百安而且持续时间只有几毫秒软件检测根本来不及。我的做法是采样电流信号除了进ADC还同时接一个硬件比较器电流超过设定阈值我设在30A时比较器直接拉低MOS管栅极驱动芯片的使能引脚在微秒级时间内切断回路。这个硬件保护通道不经过单片机可靠性高得多软件只负责在比较器触发后记录状态和恢复延时。6. 实测与调试避坑数据不会骗人6.1 踩过的三个大坑从跳变到烧采样电阻整个调试过程里我遇到了不少问题挑三个最有代表性的说说。第一个是电压跳变问题。刚开始上电OLED上显示的电芯电压一直在4.1V到4.3V之间来回跳明明电芯是稳定电压为什么读数会跳排查了很久最后发现是采样线的布线问题电芯采样线走得太长且跟充放电大电流线靠得太近充放电时产生的磁场在采样环路里感应出了干扰电压。解决办法是让采样线尽量短、使用双绞线或者屏蔽线并在ADC输入端加了RC滤波。从这个坑里得到的教训是BMS布线的第一原则是采样回路远离功率回路。第二个坑是电流采样电阻被烧了。某次测试快充电流到了15A采样电阻直接冒烟了。检查发现我选的是1毫欧、额定功率2W的锰铜电阻理论上15A的功耗是225毫瓦应该在额定范围内但实际忽略了脉冲电流的峰值可能到了30A以上。更换成额定功率5W的电阻之后问题解决。这也提醒了我选功率器件一定要看最恶劣工况而不是平均值。第三个坑跟均衡电路有关。被动均衡一开始用的是25欧姆电阻均衡电流只有60多毫安跑了一晚上压差几乎没变化。我把电阻改成20欧姆之后均衡速度上来了但电阻表面温度能有70多度放在PCB上让人不放心。最后在电阻四周多铺了铜皮辅助散热温度降到45度左右才敢继续用。6.2 实测数据SOC误差和均衡效果到底怎么样整套系统稳定运行后我做了一轮完整测试。把电池组充满静置2小时让OCV修正生效这时SOC显示100%。然后用电子负载以5A恒定电流放电放到过放保护动作全程记录单片机的SOC估算值和电子负载的实际放电量。结果是这样的放到50%SOC时实际放出电量是4.85Ah单片机估算SOC是51.5%误差1.5个百分点放到电池组电压到12V左右时单片机显示SOC 8.2%实际剩余电量大约0.82Ah误差约4个百分点。整体来看SOC误差在5%以内对非专业BMS来说这个精度已经够用了。均衡效果的验证更有意思。我故意把其中两节电芯分别充到4.22V和4.18V制造了40mV的压差然后开始充电。第一轮充电结束时压差降到18mV第二轮再充压差已经收敛到11mV。这个收敛速度比我预想的快一些因为充电电压较高的电芯会优先触发电均衡把多余电量放掉让整个电池组在充满时各电芯电压更接近。6.3 上位机与数据观察串口协议怎么设计调试BMS离不开数据观察。我设计了一个很简单的串口协议帧格式是帧头0xAA 0x55、数据长度、数据类型、数据体、校验和。数据类型包括电芯电压、电流、温度、SOC、状态标志。每500毫秒发送一帧波特率115200上位机用串口助手或者写一个简单的Python脚本解析再画成曲线图。这里说一下热词里提到的“modbus单片机帧接收数据程序”。如果你做的是工业场景串口协议基本会用Modbus-RTU它的帧结构是从站地址、功能码、数据、CRC校验。单片机作为从站接收主站查询指令返回寄存器数据。Modbus的好处是协议标准、兼容性好上位机组态软件直接就能读数据不用自己写解析。但Modbus的帧接收要注意一个点帧与帧之间需要有一个静默间隔一般是3.5个字符时间接收程序要根据这个间隔判断一帧数据是否接收完毕否则会把两帧数据混在一起解析错误。我这套系统因为是自己调试用就没有上Modbus而是用了更简单的私有协议但帧接收的思路是一样的用串口中断逐字节接收通过帧头和校验和判断数据有效性状态机处理一帧帧的切分。这个思路在任何串口协议里都会用到建议你把这个逻辑吃透后面接上位机、接云平台都靠它。结束前的几点体会这套基于单片机的BMS项目做下来我最大的感受是硬件电路和算法代码各占一半比重但真正拉开差距的是对细节的把控。比如SOC修正幅度要不要限制、保护迟滞设多大、均衡电阻选多少欧姆这些问题在书本上都有答案但不亲手试一遍你根本不知道这些参数在真实硬件上是什么表现。我建议你也从4串小系统开始做把采样、SOC、保护、均衡走通一遍再考虑往更大串数或者带通信的方向扩展。最后分享一个小技巧调试BMS时一定要在程序里记录最近一次保护触发的原因和触发时的关键数据电芯电压、电流、温度存到Flash里。很多问题不是复现不了而是你根本没记录当时发生了什么。有了这份记录再诡异的故障也能一步步定位到根因。这些经验都是拿时间和器件烧出来的希望你能少走点弯路。
返回列表