ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

BQ79616与BQ79600菊花链通信底层驱动设计与实现

BQ79616与BQ79600菊花链通信底层驱动设计与实现 简介面向BMS电芯电压采集场景这份资源围绕TI的BQ79616与BQ79600两款电池监控芯片提供了可直接参考的底层驱动程序源码。驱动覆盖I2C/SPI接口初始化、电压与温度数据读取、菊花链通信管理、中断处理、均衡控制及故障诊断等关键环节适合电动车或储能设备领域的嵌入式BMS开发者用于快速理解芯片寄存器操作并搭建自己的驱动层。压缩包为zip格式共7个文件由4个C源文件和3个头文件组成整体仅51KB结构紧凑其中既包含芯片控制逻辑也有通信校验、调试与采样控制等辅助模块便于项目复用与移植。目前已有2586人学习下载。资源可配合芯片数据手册使用尤其能帮助开发者理清寄存器配置、菊花链时序、CRC校验和异常中断响应等容易出错的细节显著缩短BMS底层驱动开发与验证周期。 多年折腾BMS的工程师对TI的采集前端肯定不陌生。BQ79616加BQ79600这个组合在动力电池采样板里出镜率很高前者是16通道的模拟前端AFE后者是协议桥接芯片两片配合起来就能把高压电池包里每一节电芯的电压和温度数据通过菊花链一根差分线回传主控MCU。底层驱动程序是整条链路能不能稳定跑起来的基础这篇博文就围绕这个芯片组合从芯片分工、通信协议、CRC校验讲起再完整梳理初始化、地址分配、数据读取和故障处理的实现思路最后把我实际调板子踩过的坑一并列出来。1. 先搞明白芯片组为什么这么设计1.1 十六通道采集芯片与桥接芯片的分工BQ79616是一颗16通道的电池监测芯片负责的活很纯粹采集电芯电压、采集温度、做被动均衡同时具备OV/UV/OT/UT等故障诊断能力。它最大支持16串电芯对常见的新能源汽车高压包来说一片BQ79616加外部均衡电路就能覆盖一个模组多片级联就能覆盖整个电池包。但这里有个现实问题BQ79616本身不直接和主控MCU通信它挂在菊花链daisy chain上对外暴露的是一对差分信号引脚。主控那边想要读写它需要一个“翻译官”这就是BQ79600的角色。BQ79600把主控侧的SPI或UART协议转成菊花链上的差分UART协议主控侧一条总线链上多片BQ79616通过帧里的设备地址来区分谁响应。这套方案的典型优势是隔离简单。菊花链是低压差分信号主控侧通过电容或变压器耦合就能实现高低压隔离不需要每片AFE单独拉一组隔离SPI板子布线和物料成本都能省下来。BMS这种对可靠性和成本都敏感的场合选这种拓扑非常务实。1.2 菊花链的物理层拓扑与信号路径菊花链在物理上就是一片一片串过去BQ79600的差分输出接第一片BQ79616的RX/TX第一片的输出再接第二片每片芯片内部会把收到的信号整形后转发给下一级。好处是链条可以拉得很长几十片串联都没问题缺点是中间任何一片出问题后面全部失联所以驱动里必须要有完善的通信诊断。实际硬件连接上BQ79616与BQ79600之间用的是两根差分线线上建议串共模电感芯片供电入口要加RC滤波。我见过不少通信不稳定的板子最后查下来都是电源纹波太大或者差分线参考地没处理好这个后面再说。主控和BQ79600之间的接口可以选择SPI或UART。SPI适合数据吞吐量要求高的场景UART则接线更少、代码更简单我自己的项目用的是UART方式默认波特率一般配置在1Mbps上下具体看BQ79600数据手册的时钟配置。驱动开发的第一步就是把这条链路的主控侧接口调通能正常往BQ79600里写寄存器、读寄存器。2. 底层驱动架构设计2.1 帧格式通信的第一步BQ79600和BQ79616的通信帧结构比较类似基本组成是命令头加数据再加CRC校验。底层驱动本质上就是三件事组帧、发帧、收帧解析所有上层功能都建立在这套帧收发机制上。UART模式下BQ79600侧一帧数据包含前导码、命令字节、数据字节和CRC字节。前导码用于同步命令字节里除了命令本身还会带上目标设备地址比如广播地址和单播地址。数据区长度根据命令不同而变化读寄存器命令的数据区一般是寄存器地址加长度信息写寄存器命令则要把要写入的值也带上。组帧看起来不复杂但有一点必须在驱动设计初期就定死字节序、位序和CRC覆盖范围。TI官方文档对帧格式的bit定义写得很细建议以你手里最新版数据手册为准不要凭印象写。我早期就吃过亏把命令字节里的bit顺序搞反了结果一帧都发不出去示波器上波形看起来还挺正常浪费了整整一天排查。2.2 CRC8校验的实现与验证通信帧里的CRC8是整个驱动最容易写错的地方。BQ79616/BQ79600用的是8位CRC多项式、初始值、是否取反都必须和芯片内部的硬件校验逻辑严格一致差一位都对不上。我自己实际项目里用的是查表法实现先按多项式生成256个CRC表项再逐字节计算。核心代码大致是这样static const uint8_t crc8_table[256] { /* 初始化时按多项式生成 */ }; static uint8_t bq796xx_crc8(const uint8_t *data, uint16_t len) { uint8_t crc 0x00; while (len--) { crc crc8_table[crc ^ *data]; } return crc; }查表法在MCU上很快但对于驱动开发来说更重要的不是效率而是先确认表项生成得多项式和初值对不对。建议写完CRC函数后先用TI文档里给出的测试向量跑一遍比如文档里会给出“理想帧”对应的CRC字节拿这个做单元测试能过再往下走。还有个小坑接收方向校验CRC时注意数据帧尾部的CRC字节本身不参与CRC计算只用来和计算值比较。有的工程师图省事把CRC字节也丢进计算函数里算出来的值当然永远不对。2.3 驱动状态机设计帧收发机制就位后就要考虑驱动整体的状态管理。菊花链通信不是简单的一问一答唤醒、配置、采集、故障处理都会改变链路状态用状态机来管理比散落的if-else可靠得多。我常用的状态划分是空闲、唤醒中、配置中、采集命令发起、等待响应、错误恢复。每次发命令前先检查状态响应超时后进入错误恢复流程而不是直接卡死在某个等待循环里。针对BMS这种对实时性有要求但又不能动不动就复位的场景状态机加超时重试是非常必要的。响应等待的超时时间需要根据命令类型区分。读寄存器这种轻量命令2到5毫秒一般是够的自动地址分配这种涉及多片芯片的操作超时就得放宽到几十毫秒要给链路逐级处理和内部复位留足时间。3. 核心初始化与数据读取流程3.1 芯片唤醒与初始化序列BQ79600挂在上电后默认处于低功耗状态主控要通信第一步是唤醒。UART模式下最简单的唤醒方式是把TX引脚拉低一段时间保持显性电平或者发送连续多个0x00字节具体时长要按数据手册走一般是百微秒量级。唤醒后不能立刻发业务命令芯片内部锁相环建立需要时间。我实际做法是唤醒后固定延时10毫秒然后读BQ79600的设备ID寄存器确认唤醒成功。设备ID能正确读出来说明链路物理层通了再继续往下配置。BQ79616侧也有自己的复位和唤醒流程。上电后链上所有BQ79616都处于复位状态需要一个广播命令把整条链“拉起来”。初始化序列大致是这样先发广播复位命令等芯片稳定再发广播配置命令设置ADC转换模式、故障阈值、均衡相关参数最后做一次地址分配把每片BQ79616的地址固定下来。3.2 自动地址分配驱动里最绕的一步菊花链上每一片BQ79616必须有一个唯一地址否则主控发单播命令时没法确认应答方是谁。上电后默认所有芯片的地址相同所以驱动要做的第一件事就是自动地址分配。地址分配的原理利用的是菊花链的物理转发特性主控发广播命令时所有芯片都能收到但真正响应并执行“地址分配”命令的只有链上第一片。第一片分配完成后会改变自身的转发逻辑第二片才能收到后续的分配命令这样一级一级分下去整条链的地址就唯一了。伪代码层面我习惯这样写地址分配流程static int bq796xx_assign_addresses(bq796xx_t *dev) { // 广播复位让所有设备回到同一起点 bq796xx_send_frame(dev, CMD_DEVICE_RESET, BRDCST_ADDR, NULL, 0); delay_ms(20); // 依次给链上每一片分配从1开始的递增地址 for (uint8_t addr 1; addr dev-device_count; addr) { bq796xx_send_frame(dev, CMD_SET_ADDRESS, addr, NULL, 0); delay_ms(5); // 读回确认分配失败要中断流程 } return 0; }实际调试时我会加一步“回读校验”分配完所有地址后逐个地址发送PING命令记录哪些地址有响应。这个操作能快速暴露链路问题比如某片芯片没焊好、供电异常或者第一片分配地址后没有正确转发。我建议在量产自检程序里也保留这一步省得整机测试时才发现采样数据少了一片。3.3 电压采集命令与数据读取地址分配完成后采集就是常规操作了。BQ79616内部有ADC主控通过配置寄存器选择转换模式然后触发一次转换延时等待转换完成再通过读寄存器把16路电压取回来。触发转换的命令一般是广播命令这样整条链上的所有BQ79616同时开始转换保证各模组电压数据的时间一致性。转换时间取决于ADC配置通常几毫秒到十几毫秒不等。数据读取则用单播命令逐片读取。每片BQ79616的16路电压数据对应8个数据寄存器每路2字节。驱动读取时的代码框架大概是static int bq796xx_read_all_voltages(bq796xx_t *dev) { for (uint8_t addr 1; addr dev-device_count; addr) { bq796xx_send_frame(dev, CMD_READ_REG, addr, reg_base, 2); // 解析响应帧校验CRC后按LSB换算成mV for (int ch 0; ch 16; ch) { uint16_t raw (resp[2*ch] 8) | resp[2*ch1]; dev-voltage[addr-1][ch] raw * LSB_VOLT; } } return 0; }LSB换算系数必须和数据手册确认。不同量程配置下LSB对应的毫伏数可能不一样换算错了整包电压全部漂移这种问题在实车上很难排查因为単看某一路电压好像都在合理范围但对不上位。3.4 故障处理与中断上报BQ79616本身具有OV、UV、OT、UT检测能力阈值配置好之后芯片会自动做比较判断不需要主控逐次读取后再算。故障状态会反映在故障状态寄存器里同时通过BQ79600的INT引脚通知主控。底层驱动里对故障处理的建议INT引脚接MCU的外部中断输入边沿触发。中断服务函数里只做一件事——置一个标志位把具体的故障判断逻辑放到主循环里处理避免在中断里做I2C/SPI/UART这种耗时的收发操作。故障处理后还有一个重要步骤清除故障标志。BQ79616的故障寄存器有的需要读后写1清零有的需要发送专门清除命令具体要看寄存器定义。不清理的话故障标志会一直挂着影响下一轮判断。4. 实测中的常见问题与排查思路4.1 CRC错帧率高菊花链通信最典型的故障就是CRC错帧率居高不下。这里需要先确认“错帧”是链路物理层引入的还是驱动层组帧本身有问题。第一步用示波器抓BQ79600主控侧的UART波形确认波特率和数据格式正确。第二步抓菊花链差分侧的波形看信号幅度、边沿时间和差分摆幅是否满足手册要求。如果差分波形畸变明显多半是PCB布线或隔离电路参数问题优先排查共模电感选型、串阻阻值和参考地跨接。如果波形正常但仍然CRC错误就要怀疑驱动组帧逻辑了。可以连续发同一帧命令1000次统计响应帧的CRC错误次数。如果错误集中在特定字节位那大概率是移位方向或者CRC初始值处理有误建议重新用测试向量验证CRC函数。4.2 唤醒后无响应BQ79600或者BQ79616唤醒后无响应是另一个高频问题。首先确认唤醒信号的时长是否满足要求特别是UART模式下通过发送0x00唤醒MCU的UART外设可能因为波特率配置不合适导致实际输出的显性电平时长不足。另外要注意菊花链上多片BQ79616同时上电时第一片的唤醒状态不一定能正确传到下一片。排查时可以先把链条拆开只保留一片BQ79616直连BQ79600确认为单点通信正常后再把整条链依次接回去。这种“二分定位”法比我见过的大多数重启大法都高效。4.3 电压数据偶发跳变电压数据偶发跳变一般不是驱动逻辑问题而是转换过程被异常打断了。第一种可能是在ADC转换期间主控发送了其他命令导致转换结果异常第二种可能是电源噪声耦合进了采样通道。驱动层面能做的优化是触发转换后严格等待转换完成标志或固定延时期间绝不发送任何非必要命令。另外BQ79616的采样通道外部RC滤波参数很关键RC时间常数太小抗混叠效果差数据就会跳太大采样建立时间不够电压会偏低。这个参数板子设计时就要算好软件只能做有限补偿。最后再分享一个小技巧排查轮询采集偶尔丢帧的问题时可以在驱动里加一个收发计数器和最后一次出错帧的缓存。把出错的那一帧数据原样存到RAM里出现问题时先把现场数据倒出来分析而不是急着改代码。很多时候错误帧的原始数据比十次复现日志都管用。底层驱动这种活慢就是快把每一帧通信的细节抠明白了整条链路的稳定性自然就上来了。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

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