
简介本资源是一套基于STM32F103C8T6单片机实现的BMS电池管理系统完整工程代码与配套文档面向嵌入式初学者、电子设计竞赛学生及锂电池应用开发工程师解决多节锂电实时监测与安全保护的核心需求。压缩包共169个文件含65个头文件.h定义寄存器、协议接口与结构体、57个源文件.c涵盖ADC采样、I2C通信驱动BQ76930、定时器管理、USART/蓝牙数据透传、SOC估算等核心逻辑以及启动汇编.s、Keil工程配置.uvproj/.uvopt、PDF说明文档和可烧录hex文件整体仅1.12MB轻量易部署。已有1198人学习下载资源结构清晰模块化程度高包含电压/电流/温度采集、安时积分法SOC算法附赠、六重硬件保护逻辑过压/欠压/过流/短路/高低温及BQ76930被动均衡驱动可直接用于课程设计、毕业设计或小型储能项目原型开发。1. 一块C8T6做BMS硬件的选择和取舍1.1 为什么是STM32F103C8T6——BMS对主控的真实需求做BMS电池管理系统的第一反应很多人会去选高端的汽车级MCU或者干脆上带专用AFE模拟前端芯片的整体方案。但我个人一直觉得在项目早期验证算法、搭原型、做小批量产品时STM32F103C8T6是一个被低估得非常厉害的选择。这不是情怀是算过账的。先看BMS软件到底需要什么。抛开那些花哨的功能BMS的核心职责就四件事采集电池状态、估算剩余电量、执行保护逻辑、上报通信数据。这里面最重计算量的SOC估算也无非就是安时积分加查表或者在低阶控制器里跑一个简化卡尔曼滤波最频繁的中断是电流采样和ADC转换完成中断频率撑死几百Hz到1kHz。这些活儿72MHz主频的Cortex-M3完全是够用的而且余量还挺大。F103C8T6的资源清单是64KB Flash、20KB SRAM、3个USART、2个SPI、2个I2C、1个CAN 2.0B、12位ADC带16个通道、多个通用定时器。注意这个ADC的采样率最高能到1MHz而且支持DMA和注入组这在BMS里非常有用——你可以在一个转换周期内把多串电池电压、几个NTC温度、母线电流全部采完再由DMA搬运到内存完全不需要CPU干预。选型时还要算一笔经济账。F103C8T6在正常市场行情下单片价格几块钱到十几块钱不等一个最小系统板也就是几十元淘宝随手下单就能开搞。对比一下那些带BMS专用功能的MCU或者需要外扩AFE的方案学习成本和硬件成本都低得多。所以我在这套源码里把整个系统设计成“主控MCU 外部采样链路”的架构而不是“F103 车规级AFE”的重方案就是希望更多人能用一个最小系统板加简单的采样电路把这套BMS跑起来。1.2 采样方案怎么定片上ADC、外挂AFE还是独立ADC芯片BMS的精度很大程度上取决于采样链路不是取决于MCU主频。F103片上的ADC是12位参考电压默认是3.3V很多最小系统板用AMS1117稳压出来的3.3V实际上不一定准实测常在3.28V到3.32V之间飘。如果直接拿它采单节锂电池电压4.2V满电时用分压电阻降到3.3V以下12位分辨率对应的是3.3V/4096大约是0.8mV/LSB理论上够用。但实际情况是参考电压不稳、PCB走线噪声、分压电阻温漂各种因素叠加到一起实测系统级精度能到±10mV就算不错了。所以我在设计里给了两条路线路线A入门验证直接用F103片上ADC配合精密分压电阻和RC滤波单体电压精度控制在±20mV以内。适合做Prototype、学习原理、验证保护逻辑。路线B工程可用外挂一颗24位ADC比如ADS1220、ADS1256这类或者用集成了均衡和保护的AFE芯片。代码包里的驱动层做了抽象上层逻辑完全不用改。这里我特别想强调一下代码包如果只是把采集函数写在adc.c里直接给上层用那扩展性是很差的。我这套代码里所有的电压、电流、温度采集都统一封装成“传感器层接口”下面可以接片上ADC也可以接外部芯片上层拿到的都是处理好的物理量。你在F103C8T6最小系统板上验证完逻辑后续换BQ76940之类的AFE时只需要重写底层的采集驱动。1.3 代码包里的外围接口到底连了什么我按一个典型的13串锂电池组标称电压48V来设计硬件接线也就是给电动自行车、储能电源、便携电池包做的场景。整套系统里除了F103最小系统板还需要以下外围分压电阻网络用于把每节电池电压分压到ADC可采集范围同时做滤波。分压电阻的比值和精度直接影响测量精度我用的是0.1%精度的金属膜电阻。电流采样采样电阻加差分放大或直接用ACS712/INA240这类电流传感器。电流信号是SOC计算的命根子必须进高分辨率ADC而且要尽量远离功率地避免地噪声污染。NTC热敏电阻10k NTC加10k分压电阻接到ADC通道通过查表计算温度用于过温保护和充电电流降额。输出控制引脚放电MOSFETDSG、充电MOSFETCHG、均衡电阻EQ、蜂鸣器/指示灯。这些用GPIO控制但驱动必须经过三极管或MOSFET电平转换不能用MCU引脚直接推大电流负载。通信接口UART用于调试和上位机CAN用于和整车/储能变流器通信可选RS485用于BMS并联时多机通信。外围不多但每个器的选型影响整机精度。最小系统板上3.3V的基准质量就直接限制了整个系统的精度上限如果要做量产我建议外接一颗基准芯片比如REF3330这比在软件里做N次校准都可靠。2. 源码是怎么组织的从数据流看BMS代码架构2.1 一次电压采样的完整旅程拿到这套源码第一个建议不是打开main.c从头读而是先看数据流。我在代码里把一条采样链路写得很完整分压电路 - ADC引脚 - DMA搬运 - 滤波算法 - 校准补偿 - 上层查询。以单体电压采集为例F103的ADC1工作在扫描加DMA模式一次触发把多个通道全部采集完DMA把原始值搬到adc_raw数组。这个数组是volatile类型的因为它在中断或DMA完成后会被写主循环读取时要用临界区或关中断保护防止读到半个写入的值。DMA搬运完成之后软件要做的第一件事不是算电压而是做滤波。我实测过哪怕是加了RC滤波ADC采样值仍然有±3到±5个LSB的跳动直接拿来判断过压保护会很容易误触发。代码里用的是中值加均值复合滤波连续采5次去掉最大值最小值剩下3个取平均。这个算法在8位单片机上可能有点吃力但在F103上72MHz跑这个滤波只占极少的CPU时间平时CPU大部分时间都在休眠或空转。滤波之后是校准。校准的思路是两点校准用精密电源给采集电路输入两个已知电压记录ADC原始值算出斜率和偏移之后每一次采样都套用这个线性关系。实测下来13串电压测量精度能稳定在±5mV以内。这个数据看着不算夸张但对一个十几块钱的MCU主控方案来说已经能支撑比较可靠的SOC和保护判定了。2.2 SOC估算的两条腿开路电压查表与安时积分SOC荷电状态是BMS里被讨论最多的一个话题也是新手最容易翻车的地方。网上很多教程直接把安时积分公式丢给你然后说“开始算吧”我真不建议这么干——因为安时积分单独用误差会累积到你怀疑人生。先说安时积分的原理。它的数学表达很简单SOC(t) SOC(t0) ∫(I/Ah)dt × 100%。但这个公式里隐藏着几个坑。第一个坑是初始SOC必须已知否则算出来的所有后续值都是空中楼阁。第二个坑是电流传感器的零点漂移哪怕只有50mA的零漂对一块10Ah电池来说连续工作20小时就会造成10%的SOC误差。第三个坑是库仑效率锂电池充放电效率不是100%而且不同倍率、不同温度下还有差异。所以代码里把OCV开路电压查表法和安时积分法结合起来用。OCV查表的思路是电池静置足够久比如2小时以上之后端电压会接近开路电压查厂家给出的OCV-SOC曲线就能得到还算准的SOC。这个方法在静置状态很准但工作状态下电池有极化电压端电压不等于开路电压所以不能实时用。我的策略是正常工作时以安时积分为主每100ms累加一次电流每1s更新一次SOC。充电充满电压达到截止电压且电流降到截止电流时把SOC强制校正为100%。放电到保护电压时把SOC强制校正为0%或按保护值设定为5%。系统检测到“静置时间超过2小时”时用OCV查表值覆盖当前SOC。相当于给安时积分装了几个“锚点”每次跑偏了到锚点就拉回来。实测下来这套组合在完整充放电循环下的SOC误差能控制在5%以内日常动态工况下短期误差则在8%以内对于非车载仪表级别的要求是够用的。这里再补一个细节OCV表项怎么建。不能直接拿厂家给的电压-SOC点对点连线因为磷酸铁锂电池的电压平台特别平中间一段电压只变几十mVSOC可能从30%变到70%查表时一个小小的电压波动就会导致SOC跳变。我的代码里对OCV表做了“平台区平滑处理”简单说就是在电压变化平缓的区间用更大步长的SOC间隔来建表减少查表噪声。如果你用的是三元锂电池这个问题没那么严重但平滑处理仍然值得做。2.3 保护逻辑的优先级怎么排BMS保护逻辑是保命的东西代码里最忌讳的是把所有条件写成一大串if else。一个合格的BMS保护系统至少要有三级保护告警、一级保护、二级保护。动作力度不同恢复条件也不同。我在这套代码里把保护类型分成这么几类过压保护OV任意一串电池电压高于阈值。告警阈值4.15V一级保护4.20V二级保护4.25V。达到二级保护时立刻关闭充电MOS。欠压保护UV任意一串电池电压低于阈值。告警阈值2.90V一级保护2.80V二级保护2.50V这个值对磷酸铁锂要调低到2.2V左右。二级保护关闭放电MOS。过流保护OC分充电过流和放电过流。放电过流又分一级过流稍高于额定比如1.2倍延时5秒和二级过流比如2倍延时200ms短路保护直接由硬件比较器触发不走软件。过温保护OT和低温保护UT电池有温度窗口0℃以下不建议充电45℃以上不建议大倍率放电。压差过大保护电芯之间压差超过设定值如300mV说明电池不一致性严重要报警并限制功率。保护逻辑的执行顺序很重要。我把“二级保护”作为最高优先级一旦触发不管其他状态如何直接关断对应MOS管同时记录故障代码。然后是一级保护可以恢复但要满足回差条件。回差的意思是比如过压保护是4.25V不能电压一降到4.24V就立刻恢复否则在临界点上会反复跳动我一般设置0.05V的回差降到4.20V才恢复。如果你拿到源码我建议先改的就是这些保护阈值——因为电池类型、串数、电芯容量不同阈值和延时完全不一样。代码里的阈值全部集中在config.h或types.h里不散落在各个c文件改起来不会漏。3. 状态机与任务调度让BMS在单片机里跑稳的工程方案3.1 主状态机怎么设计待机、充电、放电、保护很多初学的代码喜欢用一个超级大的while(1)循环从上到下把所有功能跑一遍需要什么功能就在函数里加标志位。这种写法在小Demo里没问题但BMS这种带实时保护、状态切换、故障恢复的系统里很快就会变成一团乱麻。我在这套源码里用了经典的状态机架构。BMS主状态机分四个大状态STANDBY待机系统上电自检通信建立但MOS管全关。在这个状态下会周期性采样电压温度但不会做充放电控制。外部充电器接入或负载接入后经过一段时间判定进入充电或放电状态。CHARGING充电充电MOS打开放电MOS关闭。执行充电保护同时启动均衡。DISCHARGING放电放电MOS打开充电MOS关闭。执行放电保护和低电量策略如限制最大放电电流、提前告警。PROTECTION保护任何二级保护触发后进入的锁定状态。在这个状态里MOS管全部关闭故障码锁定等待人工干预或外部恢复信号。状态切换不是简单的if判断我在代码里用了一个结构体数组来维护状态转移表每个状态对应一个“进入函数”“执行函数”“退出函数”和“转移条件表”。这样做的好处是每个状态的逻辑是内聚的改充电状态不会影响放电状态而且你能清晰看到系统从哪个状态到哪个状态是允许的哪条路径是不允许的。比如从PROTECTION状态出来一般不允许直接进CHARGING必须先回STANDBY由人工或上位机发送清故障命令。这是从安全角度考虑的设计。3.2 定时器分时调度哪些该放主循环哪些必须进中断F103有多个定时器我用了TIM2做系统时基SysTick也行但如果SysTick被RTOS占了就用TIM2中断周期是1ms。在这个时基里做时间片轮转调度每1ms累加一个软定时器计数器每10ms执行一次电流采样和快速保护判断每100ms执行一次电压温度采样和滤波每500ms执行一次SOC计算和均衡策略每1s执行一次CAN/UART状态上报。为什么要分这么多时间片因为BMS的实时性分等级。电流采样和短路保护属于“毫秒级”实时必须尽可能快。而SOC更新和蜂鸣器报警属于“秒级”哪怕慢个几百毫秒用户也感知不到。把低频的事情塞进高频执行白白浪费CPU是一方面更麻烦的是会让中断和主循环的竞争变得复杂。我特别想提醒一个定时器中断里的坑千万不要在中断服务函数里做浮点运算、延时、printf这类重活。比如电流积分要在10ms中断里计算但这个计算量在F103上其实不大可以做但要把中间结果存成整型比如把电流值乘1000存成mA单位。浮点运算放主循环中断里只做标志位置位主循环看到标志位再去执行具体处理这是嵌入式开发的经典设计BMS里更是如此。3.3 看门狗和低功耗的处理BMS是长期上电的设备不能跑几个月死机一次所以代码里必须用独立看门狗IWDG。但这里有个鱼和熊掌的问题系统在STANDBY状态下功耗要低不能一直让CPU全速跑但看门狗需要被定期喂否则复位。我采用的方案是正常工作时看门狗超时设成1秒喂狗周期500ms低功耗模式下先把IWDG关掉或者用RTC定时唤醒的方式短暂喂狗。注意F103的IWDG一旦启动就不能软件关闭除非复位所以低功耗设计要提前规划不能靠“在待机前加一行关狗代码”这种不现实的方案搞定。另外我还踩过一个经典坑用IWDG之后进入STOP模式前没把DMA和ADC彻底关掉导致外部中断唤不醒看门狗也没法喂最后整机变成砖头。后来统一在“进入待机函数”里把所有外设时钟关闭、GPIO全部配置成下拉输入或模拟输入再进WFI指令才彻底解决。4. 实测验证和排查经验给代码兜底4.1 ADC采样噪声与精度校准这是所有拿到这套源码的人第一个要面对的坎。我自己在F103C8T6最小系统板上跑第一版采集程序时不用任何滤波直接读ADC串口打印出来的值用万用表对比偏差大到让我怀疑电路画错了。后来排查发现三个根因第一是最小系统板的3.3V基准其实不是标准3.3V而是3.29V而且纹波有20mV第二是杜邦线从分压板到MCU引脚引入的环境噪声第三是12位ADC本身的量化噪声和采样电容充放电带来的跳码。解决方法是三板斧硬件上把采样线改成双绞线并加100nF电容软件上做中值加均值滤波校准上做两点线性校正。之后我把单节电压测量值和一台优利德UT61E万用表对比13节电池的偏差全部落在±5mV以内。这里有个心得校准的时候不要只校一个点因为ADC的线性偏差是存在的特别是采样通道有串扰时单点校完低电压区域可能又偏了。还有一点F103的ADC输入阻抗不算高如果前面分压电阻太大比如单节4.2V分压到3.3V要用几十kΩ等效源阻抗就高ADC采样电容充电时间不够会产生加载误差。解决方法是加大采样保持时间ADC_SampleTime_239Cycles5代码里已经做了但如果你要改采样率记得同步检查。4.2 SOC估计漂移的修正SOC漂移是BMS算法里最高频的吐槽点。我实测过在电流采样精度能到±50mA、安时积分周期10ms的情况下一个完整的充放电循环开放式安时积分的SOC误差能累积到10%以上。这主要是因为库仑效率不是常数低温时电池充进去的电能有一部分损耗掉小电流充电和大电流充电的库仑效率也不同。我的修正策略是“锚点强制校正状态切换动态补偿”。前面讲过OCV锚点这里补充一个“放电末端补偿”当系统判断电池接近空电状态时SOC的显示值按放电曲线的形状做映射而不是继续线性下降。因为锂电池在低电量区的电压曲线很陡可以用电压辅助修正SOC把末端SOC估算误差压到3%以内。如果你把这套源码放到不同容量、不同化学体系的电池上我建议重点调两个参数一个是总容量Q_rated一定要用实际放电测试标定不要迷信标称值另一个是充电截止电流它影响“充满”判断的准确度间接决定SOC是否会被拉偏。4.3 通信和均衡电路的常见问题UART通信本身不复杂但BMS走线复杂功率回路和通信线挨得近的时候通信数据偶尔会出错严重时整个通信接口卡死。我的排查经验是改波特率反而没用真正管用的是两个手段一是通信线走双绞并靠近GND二是协议层增加帧头和校验字节校验失败就丢弃整帧不处理半包数据。代码里的通信模块已经带了CRC16校验启动后先对一下协议帧格式确认无误再拔插通信线。均衡电路的问题也很有代表性。被动均衡的原理是在每节电池上并联一个均衡电阻当某节电压偏高时软件控制MOS管导通让这节电池通过电阻放电把多余电量变成热量耗散掉。听起来简单实际坑不少。第一均衡电阻选太小发热大板子烫手选太大均衡电流小几小时都拉不开压差。我选的均衡电阻是20Ω均衡电流大约200mA对4.2V电芯这个值在发热和均衡速度之间比较平衡。第二均衡只能在充电阶段做因为放电阶段本来就在消耗能量压差自然会缩小再均衡就是浪费而且均衡会让满电单体“漏电”如果不充电时也均衡过几天电池组反而被均衡到没电。代码里实现了“充电阶段才允许均衡”和“压差低于20mV时停止均衡”两个限制条件。我实测过一组压差120mV的电池组经过3次完整充电周期的均衡压差降到15mV以内效果是明显的但要注意这个过程很慢不要指望一次充电就把压差拉平。5. 说明文档里到底写了什么为什么它能决定项目成败5.1 说明文档必须有的核心章节很多开源代码包只放源码放一个简单的README结果使用者连最基本的硬件连接都不清楚问题反馈率极高。这个项目把“说明文档”当作和源码同等重要的资产里面我按实际项目评审的角度写了几个核心部分第一是硬件连接说明。包括引脚分配表、接线示意图、器件选型建议。引脚分配表很关键比如你拿到代码想换一个引脚除了改GPIO初始化还必须检查定时器、DMA、外部中断是不是绑定了同一个引脚否则会出现“能编译能下载但功能完全不工作”的问题。第二是配置参数表。所有需要根据电池类型调整的参数都集中列在这里包括电池串数、满充电压、欠压保护值、过流延时、SOC初始值、均衡开启压差等。我把这些参数和config.h里的宏定义一一对应让使用者改参数时心里有数。第三是协议文档。UART调试协议和CAN应用层协议必须写清楚包括帧结构、字节序、CRC算法、每个寄存器的读写地址。没有协议文档上位机的人根本没法配合开发。第四是故障代码表。BMS系统需要自诊断能力每个保护触发后要记录故障码这个故障码不允许简单用“ERROR”这种字符串而是一个编号说明文档里要列出每个编号对应什么故障、什么触发条件、如何恢复。这样售后反馈的时候通过故障码就能快速定位问题。5.2 移植与持续演进从C8T6到其他平台最后聊聊这套源码的边界。你完全可以把这套BMS代码当作一个可移植的软件框架来用不只是绑死在F103上。我在代码架构上尽量做了分层硬件驱动层、业务逻辑层、应用服务层。如果你后续要移植到STM32F407、GD32或者其他Cortex-M内核芯片上理论上只需要改硬件驱动层GPIO、ADC、DMA、定时器、UART、CAN和config.h里的资源配置业务逻辑层里的状态机、SOC算法、保护逻辑几乎可以原封不动拿走。真到了换平台那一步有几个事情要提前做一是确认新MCU的ADC位数和参考电压源质量如果还是12位片上ADC校准方案可以沿用如果换了高位数ADC滤波算法不一定需要动但校准方式要调整。二是多留意Flash和SRAM的占用F103C8T6只有64KB Flash这套代码编译出来大约占30KB左右还有一半余量。如果你想加更复杂的功能比如更完善的故障诊断、数据记录、蓝牙通信F103可能就显得吃力了这种情况下换F103RE或F407是顺理成章的事。我在这套代码从零跑到最终稳定最深的体会有两个一个是不管算法理论多漂亮最后都要落到实际硬件的噪声、误差和可靠性上纸上谈兵做不了BMS另一个是说明文档真不是给用户看的花架子它帮助最大的人其实是三个月后的自己——那时候你已经忘记了当初为什么把某个阈值设在那个值上翻翻文档就能省下半天时间。这套源码能帮你在嵌入式BMS这条路上少走弯路但硬件的坑和调试的耐心还是得自己去趟一遍才踏实。本文还有配套的精品资源点击获取