基于STM32F103与BQ76920的BMS系统设计与C语言实现

发布时间:2026/8/31 22:40:44
基于STM32F103与BQ76920的BMS系统设计与C语言实现 简介这是一套面向嵌入式开发初学者与进阶者的锂电池管理系统实战项目基于STM32F103主控与TI BQ76920专用电池监控芯片完整实现电压/温度采集、均衡控制、过压/欠压/过温保护及SOC估算等核心BMS功能。资源适用于自动化、电子信息、通信工程等专业学生开展课程设计、毕业设计或企业级原型验证亦支持工程师在现有代码基础上二次开发。压缩包含1003个文件主体为576个C源码与265个头文件构建驱动层、应用逻辑与HAL适配辅以IAR工程配置.icf、PCB设计文件.kicad_pcb/.gbr、PDF文档说明及CMSIS-DSP数学库相关实现如arm_rfft_init_f32.c等整体26.69MB。已有941人学习下载所有代码均经实机测试运行通过配套README清晰指引开发环境搭建与功能验证流程兼顾原理理解与工程落地。1. 项目缘起为什么选择STM32F103BQ76920来做BMS如果你正在做一个需要用到多节锂电池的项目比如电动滑板车、便携式储能电源或者一个机器人底盘那你大概率绕不开一个核心部件——电池管理系统。这东西听起来高大上但说白了它的核心任务就三件看得清精确测量每节电池的电压、电流、温度、管得住防止电池过充、过放、过流、过温、算得准估算剩余电量也就是SOC。市面上有成品的BMS模块但对于想深入理解原理、做二次开发或者有特殊定制需求比如特定的通信协议、独特的保护逻辑的工程师和爱好者来说从零开始搭建一个是性价比最高、收获也最大的学习路径。我这次选择的方案是STM32F103C8T6作为主控搭配TI的BQ76920作为电池监控前端。这个组合在小型多串锂电池组比如3-5串的应用里非常经典。STM32F103江湖人称“蓝桥杯战神”或“学生党神器”价格便宜、资料海量、性能对于BMS的主控任务绰绰有余。而BQ76920则是TI专门为3-5串锂电池组设计的模拟前端芯片它集成了高精度的电压、电流、温度采集以及硬件级别的保护功能把最复杂、最危险的模拟信号处理部分给包圆了让主控MCU可以专注于逻辑控制和通信。很多人一上来就想用MCU的ADC直接去测电池电压但对于串联的电池组这几乎是个“自杀式”任务。你需要解决高压隔离、差分测量、共模电压等一系列棘手问题。BQ76920的出现完美地解决了这个痛点。它内部有专门的差分采样通道可以直接挂在电池串的两端通过I2C把处理好的数字数据安全地送给MCU。这个项目就是要把这两者结合起来用C语言写一个能跑起来的、功能完整的BMS固件。2. 硬件核心BQ76920如何成为STM32的“眼睛”和“保镖”在动手写代码之前必须吃透BQ76920这颗芯片。它不是简单的ADC而是一个功能完整的子系统。理解它的工作模式是项目成功的基础。2.1 BQ76920的三大核心功能模块电压采集与均衡这是它的看家本领。芯片有5个差分输入通道VC1-VC5可以直接连接3到5节串联电池的节点。内部是一个逐次逼近型ADC分辨率可达14位测量精度很高。更重要的是它集成了被动均衡功能。每个电池通道都连接着一个内部MOSFET开关当检测到某节电池电压过高时可以控制这个开关闭合让一个外接的电阻并联在电池两端放电从而实现电池间的电压平衡。这个功能对于延长电池组寿命至关重要。电流与温度监测芯片有一个专用的高侧电流采样输入SRP/SRN通过外接一个毫欧级的采样电阻比如1mΩ来测量充放电电流。它内部集成了可编程增益放大器能适应不同量程的电流测量。温度监测则通过外接负温度系数热敏电阻来实现芯片提供专门的偏置电压和测量通道。硬件保护与控制这是BQ76920作为“保镖”的核心。它内置了可独立配置的硬件比较器用于过压保护当任何一节电池电压超过设定阈值触发保护。欠压保护当任何一节电池电压低于设定阈值触发保护。过流放电/充电保护当电流超过设定阈值并持续一定时间触发保护。短路保护响应速度极快通常微秒级用于应对最危险的瞬间短路。 一旦触发保护芯片会立即关闭其控制的放电FET和充电FET通过驱动外部的MOSFET切断电池与外部电路的连接。这个动作是硬件完成的不依赖于MCU软件因此响应速度极快安全性极高。2.2 STM32与BQ76920的通信桥梁I2CSTM32F103通过I2C总线与BQ76920通信。这是整个系统的数据动脉。你需要配置STM32的I2C外设实现与BQ76920的寄存器读写。这里有一个非常关键的细节BQ76920的I2C地址是固定的0x087位地址写地址0x10读地址0x11。它不支持地址更改这意味着一条I2C总线上只能挂一个BQ76920。对于我们的多串应用这足够了。通信的要点在于时序和寄存器理解。每次上电或复位后BQ76920需要被“唤醒”并完成初始化配置。这个过程通常包括向SYS_CTRL1寄存器写入特定值使能ADC并退出睡眠模式。配置保护阈值寄存器如OV_TRIP,UV_TRIP,OCD_TRIP等。这些值需要根据你使用的具体电芯规格如三元锂、磷酸铁锂来精心计算。配置均衡控制寄存器CELLBAL1等设定均衡开启的电压阈值和条件。注意BQ76920的很多寄存器是只写的或者具有特殊的读写属性。在编写驱动时务必仔细阅读数据手册中关于每个寄存器的描述错误的操作可能导致芯片进入不可预测的状态。我建议将所有的寄存器地址和常用配置值用#define宏定义在头文件里这样代码既清晰又不易出错。3. 固件架构设计一个高效且清晰的C语言工程拿到原理图连好线下一步就是搭建代码框架。一个好的架构能让开发、调试和维护事半功倍。我的项目源码采用模块化设计核心分为以下几层3.1 驱动层与硬件直接对话这一层最底层负责“摆弄”芯片的寄存器。bsp_i2c.c/.h: 封装STM32的I2C底层读写函数。包括初始化、发送、接收、以及处理可能的错误如ACK失败、总线忙。这里要处理好I2C的中断或轮询方式我选择轮询以简化初始开发。drv_bq76920.c/.h: BQ76920的专用驱动。提供诸如BQ76920_ReadRegister(),BQ76920_WriteRegister()这样的基础函数。更重要的是要封装高级功能函数例如// 读取所有电池电压 uint16_t BQ76920_ReadCellVoltage(uint8_t cell_num); // 读取包电压总电压 uint32_t BQ76920_ReadPackVoltage(void); // 读取电流注意正负代表充放电 int16_t BQ76920_ReadCurrent(void); // 读取芯片内部温度或外部热敏电阻温度 float BQ76920_ReadTemperature(uint8_t sensor_id); // 控制均衡开关 void BQ76920_SetCellBalance(uint8_t cell_mask, bool enable);bsp_adc.c/.h: 虽然BQ76920负责了主要的电压电流采集但STM32自身的ADC可能用于采集一些辅助信号比如系统供电电压、额外的温度点等。bsp_gpio.c/.h: 用于控制状态指示灯、蜂鸣器、风扇等外设以及监测BQ76920的报警引脚。3.2 服务层数据处理与核心算法这一层是BMS的“大脑”它基于驱动层获取的原始数据进行计算、判断和管理。bms_core.c/.h:这是最核心的模块。它维护一个全局的BMS状态结构体例如typedef struct { float cell_voltage[5]; // 每节电池电压 float pack_voltage; // 总电压 float pack_current; // 电流正为放电负为充电 float pack_temperature; // 温度 int32_t remaining_capacity; // 剩余容量mAh int32_t full_capacity; // 满充容量mAh uint8_t soc; // 荷电状态0-100% uint8_t fault_status; // 故障状态位 bool is_charging; // 充电状态 bool is_discharging; // 放电状态 } BMS_State_t;这个模块里的函数会周期性执行比如每100ms调用驱动函数读取原始数据。进行滤波处理例如对电流做滑动平均滤波消除毛刺。执行SOC估算。这是BMS的难点。我采用了经典的“安时积分法”结合“开路电压法”进行修正。安时积分法通过持续累加电流对时间的积分来估算容量变化但会有累积误差。因此在电池静置一段时间后用测得的开路电压去查表电压-SOC对应关系表来修正SOC值。检查保护条件根据当前电压、电流、温度判断是否触发过压、欠压、过流、过温等并更新fault_status。bms_protection.c/.h: 专门处理保护逻辑。当bms_core检测到故障条件这个模块负责执行保护动作比如通过I2C命令让BQ76920关断FET或者通过GPIO控制外部接触器。它还需要实现保护恢复逻辑比如某些故障在条件消除后可以自动恢复有些则需要手动复位。3.3 应用层用户交互与系统调度这一层负责让整个系统“活”起来与人和其他设备交互。app_scheduler.c/.h: 一个简单的任务调度器。以固定的时间片如1ms运行调度bms_core的周期任务、状态显示更新、通信处理等。避免在while(1)里用delay让程序结构更清晰。app_ui.c/.h: 如果配有OLED屏幕或数码管这个模块负责显示电压、电流、SOC、故障信息等。app_communication.c/.h: 实现与上位机或其他控制器的通信。最常用的是UART通信定义一套简单的串口协议例如Modbus RTU或自定义的ASCII协议用于上报数据和接收控制命令。也可以考虑集成CAN总线这在汽车或工业应用中更常见。main.c: 程序的入口负责初始化所有硬件时钟、GPIO、I2C、UART等初始化各模块然后启动任务调度器。4. 关键代码剖析与避坑指南有了架构我们来深入几个最容易出问题的代码细节。4.1 I2C通信的稳定性超时与重试机制BQ76920的I2C通信并不总是那么可靠尤其是在有电机等大功率设备干扰的场合。你的I2C读写函数必须有超时和重试机制。// 建议的I2C发送函数框架 HAL_StatusTypeDef BSP_I2C_Write(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, uint16_t len) { HAL_StatusTypeDef status; uint8_t retry 3; // 重试3次 while(retry--) { status HAL_I2C_Mem_Write(hi2c1, dev_addr, reg_addr, I2C_MEMADD_SIZE_8BIT, data, len, 100); // 100ms超时 if(status HAL_OK) { return HAL_OK; } HAL_Delay(2); // 短暂延时后重试 } // 重试多次失败记录错误可能需要进行系统复位或安全处理 BMS_ReportError(I2C_COMM_ERROR); return status; }4.2 电压与电流的校准让数据更可信直接从BQ76920读出的ADC值是原始数据需要转换成实际的物理值。这个过程需要校准。电压校准BQ76920内部有基准电压但每个芯片有细微偏差。你可以在已知精确电压源比如标准电池下读取ADC值计算出一个校准系数voltage_gain actual_voltage / adc_reading。将这个系数存入STM32的Flash中每次转换时使用。电流校准电流的转换涉及采样电阻的精度和内部PGA的增益。最实用的方法是在零电流状态下读取一个“偏移量”因为运放有零漂。然后让电池组通过一个已知的负载如一个功率电阻放电测量一个精确的电流值计算出current_gain actual_current / (adc_reading - offset)。踩坑实录我最初忽略了零漂校准发现系统待机时电流读数也有几十毫安导致SOC积分误差巨大。后来在初始化时强制让系统进入“无充放电”状态连续采样100次电流值取平均作为current_offset问题才解决。4.3 SOC估算的实现安时积分法的细节安时积分法原理简单但实现时有几个坑积分间隔要固定必须在严格固定的时间间隔如100ms读取电流并进行积分。如果使用调度器确保这个任务的周期是精确的。电流单位要统一积分公式是容量(mAh) 电流(A) * 时间(h)。注意单位换算。我的做法是电流以mA为单位时间以ms为单位那么容量变化(uAh) 电流(mA) * 时间(ms) / 3600。最后再累加到总容量中。处理充放电状态充电时电流为负根据你的电路设计定义积分是增加容量放电时电流为正积分是减少容量。bms_core中的is_charging和is_discharging标志位就是用来辅助判断的它们可以由BQ76920的FET状态寄存器或电流方向推断出来。容量边界管理当积分得到的剩余容量超过满充容量时应钳位在满充容量当低于0时钳位在0。同时SOC百分比就是(remaining_capacity / full_capacity) * 100。4.4 保护逻辑的编写既要灵敏又要防误报保护逻辑是安全底线但不能动不动就“跳闸”。需要引入延时和滞回机制。以过压保护为例// 在 bms_protection.c 中 void BMS_CheckOverVoltage(BMS_State_t *bms) { for(int i 0; i CELL_NUM; i) { if(bms-cell_voltage[i] OV_THRESHOLD) { ov_counter[i]; // 计数器增加 if(ov_counter[i] OV_DELAY_CYCLES) { // 连续超过阈值一定周期 bms-fault_status | FAULT_OV; BMS_TriggerShutdown(); // 执行关断 return; } } else if (bms-cell_voltage[i] OV_THRESHOLD - OV_HYSTERESIS) { // 电压回落到阈值以下并带有滞回区间才清零计数器防止在阈值附近抖动 ov_counter[i] 0; } } }OV_DELAY_CYCLES对应一个时间比如3秒OV_HYSTERESIS是一个回差电压比如0.05V。短路保护则几乎没有延时需要立即响应。5. 项目调试与实战心得代码写完只是第一步调试才是真正的挑战。5.1 上电顺序与硬件保护务必注意上电顺序正确的顺序是先给BQ76920供电通过电池组然后再给STM32供电。如果顺序反了STM32的I2C引脚可能先于BQ76920上电产生不确定的电平可能导致通信失败甚至损坏。在原理图上可以用一个由电池电压驱动的LDO先给BQ76920供电再用另一个由系统总开关控制的LDO给STM32供电。调试时先把保护阈值设得宽松一些。比如先把过压保护设到4.5V远高于锂电的4.2V上限欠压保护设到2.5V远低于3.0V下限。等所有数据采集、通信、显示功能都调通后再逐步收紧到安全值。5.2 利用BQ76920的ALERT引脚BQ76920有一个开漏输出的ALERT引脚。当任何可配置的故障发生时这个引脚会被拉低。你可以将这个引脚连接到STM32的外部中断引脚。这样一旦发生故障MCU能立即被中断唤醒如果处于低功耗模式并读取BQ76920的故障寄存器来确定具体原因实现最快的软件响应。这比轮询方式更及时。5.3 串口调试是王道在app_communication中实现一个简单的命令行调试接口至关重要。通过串口助手你可以实时发送命令来读取任意寄存器、强制均衡、清除故障等。例如 read cell 1 // 读取第1节电压 3.812 V read status // 读取状态 SOC:85%, Current:0.0A, Fault:0x00这比单纯看屏幕显示要强大得多是定位问题的利器。5.4 文档说明的编写一个好的项目离不开文档。我的文档说明通常包括硬件连接图清晰地标明STM32最小系统、BQ76920外围电路采样电阻、均衡电阻、热敏电阻、FET驱动、电源部分的连接。软件环境说明使用的IDE如Keil MDK或STM32CubeIDE、编译器版本、主要的库如STM32 HAL库或标准外设库。工程目录结构解释每个文件夹和核心文件的作用。配置指南详细说明如何根据你的电池参数串数、容量、电芯类型修改bms_config.h文件中的宏定义例如保护阈值、均衡阈值、电流采样参数等。编译与下载步骤如何编译工程如何通过ST-Link等工具下载到芯片。测试流程上电后的测试步骤如何验证电压读取是否正确如何模拟故障测试保护功能。常见问题把你在开发中遇到的主要问题和解决方法记录下来这对后来者是无价之宝。这个基于STM32F103和BQ76920的BMS项目从硬件选型到软件架构再到调试细节几乎涵盖了一个嵌入式系统开发的完整流程。它不仅仅是一个电池管理方案更是一个绝佳的嵌入式学习平台。当你看到屏幕上稳定显示着每一节电池的电压SOC百分比随着使用缓缓下降并且在过放时系统能可靠地切断输出那种成就感是单纯的模块应用无法比拟的。希望这份详细的拆解能为你点亮自己动手搭建BMS的道路。本文还有配套的精品资源点击获取