单片机如何借助BQ28Z610实现高精度电池电量监测

📅 发布时间:2026/7/5 5:04:25 👁️ 浏览次数:
单片机如何借助BQ28Z610实现高精度电池电量监测
1. 为什么电压法测电量不准聊聊动态负载下的“坑”我刚开始玩无人机和机器人那会儿给电池做电量显示用的都是最“经典”的办法分压电阻。简单说就是用两个电阻串联接在电池正负极然后在中间那个点测量电压。因为电阻比例是固定的所以测出来的电压乘以一个系数就是电池的总电压。然后呢再查一下电池的放电曲线看看当前电压对应多少电量一个百分比就出来了。听起来很合理对吧成本也低几毛钱的电阻加一个单片机的ADC引脚就搞定了。但实际用起来尤其是在无人机起飞、机器人突然加速这种时候问题就全暴露出来了。最典型的就是“电压跌落”。你的四轴无人机静止时电池电压是12.6V看起来电量很足。但你一推油门四个电机全力转动瞬时电流可能高达几十安培。这么大的电流从电池里抽出来由于电池本身有内阻连接线也有电阻电压会瞬间被拉低可能一下子掉到11V甚至更低。这时候你的单片机ADC一采样好家伙电压只剩11V了按照放电曲线一查电量可能就显示只剩30%了。但实际上呢电流一稳定下来或者电机一停电压又会嗖地一下弹回去电量显示又变成了80%。用户看着这电量像坐过山车一样忽上忽下心里肯定在骂这什么破玩意儿一点都不准这就是电压法的致命缺陷它测量的是电池的“开路电压”或“轻载电压”反映的是电池的化学电势。而一旦接上动态变化的负载电压受负载电流、电池内阻、温度甚至电池老化程度的影响非常大根本无法真实反映电池里还剩多少“能量”。就像你用力捏着一个充满水的气球捏得越用力电流越大出口的水压电压看起来就越小但你不能说气球里的水电量就变少了。所以对于电机、舵机、继电器、大功率LED这种“电老虎”频繁工作的设备想靠电压来估算电量基本就是“玄学”只能设备完全静止时勉强参考一下。那么有没有一种方法能像水表一样实实在在地计量流出电池的“电量”呢有这就是库仑计也叫电量计。它的原理非常直观实时测量流入和流出电池的电流并对这个电流进行积分。电流乘以时间就是流过的电荷量库仑。把流出的电荷总量和电池标称的总电荷容量一比剩下的百分比不就出来了吗这就像记录水表走了多少字一样精准。而BQ28Z610就是德州仪器TI推出的一款非常经典、集成度极高的“智能”库仑计芯片专门为1-2节串联的锂电池组比如3.7V或7.4V设计。它不仅能高精度地计量电量还把电池保护、均衡、通信这些活儿全给包了对我们单片机开发者来说简直是省心利器。2. BQ28Z610的核心武器高精度ADC与库仑计数BQ28Z610之所以能成为电池管理的“大脑”靠的不是花架子而是几项实打实的硬核技术。咱们把它拆开揉碎了看主要就三把“刷子”高精度模拟前端AFE、强大的库仑计数器和灵活的I2C通信接口。首先说说它的模拟前端AFE。你可以把它想象成芯片的“眼睛”和“耳朵”专门负责采集电池最原始的电压、电流和温度信号。BQ28Z610的AFE厉害在哪呢它内部集成了两个独立的模数转换器ADC。这意味着它可以同步采样电池的电压和电流。这一点至关重要因为在动态负载下电压和电流都是瞬息万变的。如果用一个ADC先测电压、再测电流这微小的延时就会引入计算误差。而同步采样就像用高速相机同时拍下电压和电流的“合影”保证了计算电量的时间基准完全一致精度自然就上去了。更厉害的是它的电流检测精度。它支持使用非常小的电流感应电阻小到1毫欧mΩ。用这么小的电阻有什么好处最大的好处就是功耗低电阻本身几乎不发热不会影响测量。但电阻小了电流流过产生的压降信号也就非常微弱了。这时候就体现出BQ28Z610内部放大器和ADC的功力了它的输入失调电压误差典型值小于1微伏µV。这是个什么概念这意味着它能够清晰地分辨出流过1毫欧电阻上1毫安电流所产生的1微伏电压变化这种级别的灵敏度使得它无论是监测设备待机时的微安级漏电流还是电机启动时的数十安培浪涌电流都能做到游刃有余全程精准计量。其次是它的核心——库仑计数器。光有高精度的电流采样还不够如何把这些瞬间的电流值累加起来变成流过的总电量这才是关键。BQ28Z610内部有一个专门的硬件积分器以固定的、很高的频率比如每秒几千次对电流进行采样和累加。这个累加值就是流过的电荷量单位是毫安时mAh或毫瓦时mWh。芯片内部还会运行一个复杂的算法模型这个模型不仅考虑流进流出的电荷还会结合电池电压、温度、老化程度、放电速率等多种因素对电池的“健康状态”和“剩余容量”进行学习和预测。所以它给出的电量百分比是一个经过“智能修正”的值比单纯积分要准确得多尤其是在电池用了很久、容量衰减之后。最后是它和单片机沟通的桥梁——I2C接口。BQ28Z610的I2C是主模式速度支持到400kHz通信效率很高。它把自己内部计算好的各种数据比如剩余容量、电压、电流、温度、电池状态、错误标志等等都存放在一组标准的“寄存器”里。我们单片机只需要像读一本书一样通过I2C协议去访问特定的寄存器地址就能把这些信息读出来。整个过程单片机是“主”BQ28Z610是“从”我们发命令它回数据逻辑非常清晰。后面我们会用代码具体演示这个过程。3. 硬件连接把BQ28Z610“接”到你的STM32上理论懂了接下来就得动手了。要让BQ28Z610开始工作首先得把它正确地连接到你的电池组和单片机系统里。别看它只有12个引脚VSON封装该接的一个都不能少。我画个简单的示意图帮你理解锂电池组 (2串7.4V) | ---[BAT]--- BQ28Z610的P引脚 ---[BAT-]--- BQ28Z610的P-引脚 | | (电流感应电阻Rsense例如5mΩ) | 放在B-和P-之间 | BQ28Z610的SRP/SRN引脚 -- 跨接在Rsense两端用于检测电流 BQ28Z610的TS引脚 -- 接NTC热敏电阻贴在电池上监测温度 BQ28Z610的SDA/SCL引脚 -- 接STM32的I2C引脚如PB10/PB11 BQ28Z610的BAT引脚 -- 为芯片本身提供电源可从P经LDO降压得到电源与电池连接P, P-, BATP和P-直接接在你的2串锂电池组标称7.4V的正负极上。注意芯片本身的工作电压BAT引脚通常需要3.3V你需要一个低压差线性稳压器LDO从P最高可能8.4V降压到3.3V给它供电。电流检测SRP, SRN, B-这是精度的心脏。你需要挑选一个高精度、低温漂的电流感应电阻比如5毫欧的1%精度电阻。这个电阻要串联在电池的负极B-和芯片的P-引脚之间。芯片的SRP和SRN两个引脚就用非常短的线直接连接到这个电阻的两端。这样所有从电池流出的电流都会经过这个电阻产生一个微小的压降SRP/SNR就能精确测量这个压降从而算出电流。布线一定要短而粗避免引入额外的寄生电阻影响测量。温度检测TS电池的温度对容量和安全性影响巨大。你需要一个10kΩ的NTC热敏电阻一端接TS脚另一端接地。把这个热敏电阻用导热胶牢牢贴在电池表面最可能发热的位置。BQ28Z610内部有电流源可以测量热敏电阻的阻值换算成温度。通信接口SDA, SCL这就是和STM32的“对话线”。SDA是数据线SCL是时钟线。别忘了I2C总线需要上拉电阻通常用4.7kΩ或10kΩ的电阻将这两条线拉到3.3V。STM32端你可以选择任意的I2C外设比如I2C1或I2C2把对应的GPIO配置成复用开漏模式Alternate Function Open-Drain即可。保护与均衡BQ28Z610还集成了MOSFET驱动DSG, CHG可以外接MOSFET来切断充放电回路实现过压、欠压、过流、短路保护。对于两串电池它还能通过内部开关实现电芯间的被动均衡让两个电芯的电压保持一致延长电池组寿命。这些高级功能在你初次使用时可以先不接等基础电量读取稳定了再慢慢加上。4. 软件驱动手把手写STM32的I2C读写代码硬件连好了现在让STM32和BQ28Z610“对上话”。我们以STM32F2系列和HAL库为例整个过程就像教单片机认识一个新朋友。第一步初始化I2C外设。这相当于给单片机的“电话线”I2C总线通电并设置好拨号规则。// bq28z610_driver.c #include stm32f2xx_hal.h // 定义使用的I2C和GPIO这里以I2C2 PB10(SCL), PB11(SDA)为例 #define BQ28Z610_I2C I2C2 #define BQ28Z610_I2C_CLK_ENABLE() __HAL_RCC_I2C2_CLK_ENABLE() #define BQ28Z610_GPIO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE() #define BQ28Z610_SCL_PIN GPIO_PIN_10 #define BQ28Z610_SDA_PIN GPIO_PIN_11 #define BQ28Z610_GPIO_PORT GPIOB #define BQ28Z610_AF GPIO_AF4_I2C2 // 查看芯片手册PB10/11的复用功能是AF4 // BQ28Z610的I2C从机地址固定为0xAA7位地址格式实际发送时左移一位为0x55 #define BQ28Z610_I2C_ADDR (0xAA) static I2C_HandleTypeDef hi2c_bq; static void BQ28Z610_I2C_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct {0}; // 1. 使能GPIO时钟 BQ28Z610_GPIO_CLK_ENABLE(); // 2. 配置SDA和SCL为复用开漏模式并上拉 GPIO_InitStruct.Pin BQ28Z610_SCL_PIN | BQ28Z610_SDA_PIN; GPIO_InitStruct.Mode GPIO_MODE_AF_OD; // 复用开漏是关键 GPIO_InitStruct.Pull GPIO_PULLUP; // 内部或外部上拉 GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; GPIO_InitStruct.Alternate BQ28Z610_AF; HAL_GPIO_Init(BQ28Z610_GPIO_PORT, GPIO_InitStruct); } int BQ28Z610_Init(void) { // 1. 初始化GPIO BQ28Z610_I2C_GPIO_Init(); // 2. 使能I2C时钟 BQ28Z610_I2C_CLK_ENABLE(); // 3. 配置I2C参数 hi2c_bq.Instance BQ28Z610_I2C; hi2c_bq.Init.ClockSpeed 100000; // 标准模式100kHz稳定优先也可用400kHz hi2c_bq.Init.DutyCycle I2C_DUTYCYCLE_2; hi2c_bq.Init.OwnAddress1 0; // STM32作为主机自己的地址不用设或设一个不冲突的 hi2c_bq.Init.AddressingMode I2C_ADDRESSINGMODE_7BIT; hi2c_bq.Init.DualAddressMode I2C_DUALADDRESS_DISABLE; hi2c_bq.Init.OwnAddress2 0; hi2c_bq.Init.GeneralCallMode I2C_GENERALCALL_DISABLE; hi2c_bq.Init.NoStretchMode I2C_NOSTRETCH_DISABLE; if (HAL_I2C_Init(hi2c_bq) ! HAL_OK) { // 初始化失败可能是硬件连接问题 return -1; } return 0; }第二步读懂BQ28Z610的“语言”——命令码。BQ28Z610内部的数据是通过一系列16位的命令码Command Code来访问的。我们需要知道几个最常用的0x08/0x09: 电池电压Voltage单位毫伏mV。这是一个2字节的数据。0x10/0x11: 剩余容量RemainingCapacity单位毫安时mAh。0x12/0x13: 满充容量FullChargeCapacity单位毫安时mAh。这个值会随着电池老化而更新。0x16/0x17: 平均电流AverageCurrent单位毫安mA。0x2A/0x2B: 相对电量百分比StateOfCharge单位%。第三步编写读取函数。我们用HAL库的HAL_I2C_Mem_Read函数它可以一次性完成“发送命令码”和“读取数据”两个步骤。// 从BQ28Z610读取一个16位2字节的数据 static int16_t BQ28Z610_ReadWord(uint16_t command_code) { uint8_t data[2] {0}; uint8_t cmd_high (uint8_t)(command_code 8); // 命令码高字节 uint8_t cmd_low (uint8_t)(command_code 0xFF); // 命令码低字节 // 实际上对于很多I2C设备发送命令时只需要发送高字节作为内存地址 // BQ28Z610的文档指出通常使用命令码的高字节作为MemAddress HAL_StatusTypeDef status; status HAL_I2C_Mem_Read(hi2c_bq, (uint16_t)(BQ28Z610_I2C_ADDR 1), // HAL库需要7位地址左移1位 cmd_high, // 内存地址命令码高字节 I2C_MEMADD_SIZE_8BIT, data, // 存放读取数据的缓冲区 sizeof(data), // 读取2个字节 100); // 超时时间100ms if (status ! HAL_OK) { // 读取失败可以打印错误或返回一个特定错误值如0xFFFF return -1; } // BQ28Z610返回的数据是低位字节在前Little-Endian return (int16_t)((data[1] 8) | data[0]); } // 封装几个常用的读取函数 int BQ28Z610_GetVoltage_mV(void) { return BQ28Z610_ReadWord(0x08); // 读取0x08/0x09地址的数据 } int BQ28Z610_GetRemainingCapacity_mAh(void) { return BQ28Z610_ReadWord(0x10); // 读取0x10/0x11地址的数据 } int BQ28Z610_GetFullChargeCapacity_mAh(void) { return BQ28Z610_ReadWord(0x12); // 读取0x12/0x13地址的数据 } int BQ28Z610_GetStateOfCharge_Percent(void) { // 方法一直接读取芯片计算好的百分比0x2A/0x2B最方便 int soc BQ28Z610_ReadWord(0x2A); if (soc 0 soc 10000) { // 芯片返回的是0.01%为单位10000代表100.00% return soc / 100; // 转换为整数百分比 } return -1; // 方法二自己用剩余容量和满充容量计算更透明 // int remain BQ28Z610_GetRemainingCapacity_mAh(); // int full BQ28Z610_GetFullChargeCapacity_mAh(); // if (full 0) { // return (remain * 100) / full; // } // return -1; }第四步在主循环中调用并显示。初始化完成后你就可以在一个循环里定期读取电量并显示了。// main.c #include bq28z610_driver.h #include usart.h // 假设你有一个串口打印函数 int main(void) { HAL_Init(); SystemClock_Config(); USART1_Init(); // 初始化串口用于调试输出 BQ28Z610_Init(); // 初始化BQ28Z610 while (1) { int voltage BQ28Z610_GetVoltage_mV(); int soc BQ28Z610_GetStateOfCharge_Percent(); int current BQ28Z610_ReadWord(0x16); // 读取平均电流 printf(电压: %d.%03dV, 电量: %d%%, 电流: %dmA\r\n, voltage / 1000, voltage % 1000, soc, current); HAL_Delay(1000); // 每秒读取一次 } }把代码编译下载进去如果硬件连接正确你应该能在串口助手上看到稳定、准确的电量百分比了。即使此时你让电机疯狂转动看到电流大幅波动那个电量百分比也会非常“淡定”地缓慢下降不会再像电压法那样上蹿下跳。5. 进阶调优与避坑指南让电量计更“懂”你的电池代码跑通读到数据这只是第一步。要让BQ28Z610在你的产品中真正达到“高精度”和“高可靠”还有一些关键的配置和注意事项这些往往是数据手册里不会明说但实际项目中一定会遇到的“坑”。第一个大坑电池参数配置学习周期。BQ28Z610出厂时内部的电池模型参数是空的或者默认值。它需要经历至少一次完整的“充放电学习周期”才能“认识”你的这块电池。这个过程叫做“IT Enables”。你需要确保电池在安装后先进行一次完整的充电直到芯片报告充电完成然后再进行一次完整的放电放到设备自动关机或截止电压。在这个过程中芯片会记录电池的充电容量、放电容量、开路电压曲线等关键特性并更新内部的FullChargeCapacity等参数。如果不做这个学习它给出的电量可能一直不准。很多开发者抱怨电量计不准第一步就栽在这里。第二个关键点温度补偿。电池的容量受温度影响很大。低温下容量会严重缩水。BQ28Z610的TS引脚接的热敏电阻就是干这个用的。你需要在芯片的配置数据Data Flash里正确设置热敏电阻的类型通常是10kΩ的Beta值为3435的NTC及其对应的温度-容量补偿表。这样芯片在计算电量时会自动根据当前温度进行修正。如果你发现冬天电量掉得特别快或者显示不准八成是温度补偿没配好。第三个细节电流检测电阻的校准。虽然我们用了1%精度的电阻但PCB走线、焊点还是会引入微小的额外电阻。为了达到最高精度BQ28Z610支持对电流检测通道进行校准。你需要一个精准的电流源给电池回路注入一个已知的、稳定的电流比如500mA然后通过专门的配置工具比如TI的BQStudio软件和编程器比如TI的EV2400告诉芯片“现在实际的电流是XXXmA”芯片会自动计算并存储一个校准系数。这个操作一般在生产线上完成。第四个实践技巧通信稳定性。I2C总线对干扰比较敏感尤其是在有电机、开关电源这种强噪声的环境里。除了在硬件上做好电源滤波、信号线远离噪声源、使用屏蔽线等措施外在软件上也要增加重试机制。比如上面的BQ28Z610_ReadWord函数如果一次读取失败可以尝试重复读取2-3次。另外BQ28Z610的某些配置寄存器写入后需要一定时间生效读取某些数据如容量后芯片内部可能需要进行计算这时要插入适当的延时HAL_Delay(10)不要连续高速地访问。第五个高级功能安全与保护。BQ28Z610不仅仅是个电量计它还是个保镖。你可以通过配置设置电池的过压保护点比如每节4.3V、欠压保护点比如每节2.8V、过流保护点、短路保护延时等。当触发保护时它会自动关闭外部的MOSFET切断电路。同时它支持SHA-1认证主机你的STM32可以向它发送一个挑战码它用内置的密钥计算一个响应码返回这样可以防止使用非法的、不安全的电池包在高端产品中这个功能很有用。把这些都做好你的电池管理系统才算是从“能用”升级到了“好用”和“可靠”。BQ28Z610就像一位尽职尽责的电池管家你把它调教得越好它给你的回报就是越长久、越安心的续航体验。