
简介面向电池管理系统BMS开发者的驱动与工程源码包围绕LTC6803、LTC6812及LTC6804多节电池监控芯片提供与主机通信、电压温度采集、均衡控制等底层驱动实现适用于电动车、储能及无人机等高压电池组项目。压缩包共524个文件以298个h头文件和170个cpp源文件为核心另有Arduino工程、PCB/原理图、PDF文档及辅助脚本等整体约3.78MB目录结构完整便于按模块查阅。目前已有961人学习适合具有一定嵌入式基础、需要基于ADI/LTC芯片自研BMS或移植官方方案的中高级工程师。通过分析代码可掌握I2C/隔离SPI配置、多节点电池数据解析、均衡策略触发及错误处理流程并可直接复用部分驱动到实际项目中缩短开发周期。1. 一块读不回电压的 BMS 采样板把问题指向了 LTC6804 驱动去年在产线上测一块 16 串的 BMS 采样板主控是 STM32F103电芯采集用的是 LTC6812通过 SPI 挂下来。上电后第一条读电压命令回的全是 0xFF换了两块板子都一样量 CS、CLK 波形都正常。查到最后问题出在驱动里 CRC 校验字节选错了长度整个菊花链都没让出总线。BMS 硬件设计里最绕不开的一环就是 LTC6803、LTC6804、LTC6812 这三颗 ADI 电芯监测芯片的驱动开发通过 SPI 或者 isoSPI 菊花链完成电芯电压、温度采样、被动均衡和故障上报。这篇文章按我在量产项目里的常见做法把这个方向拆成选型、驱动分层、均衡诊断和量产验证四块给正在写芯片驱动的工程师一条能直接跟的路。2. LTC6803、LTC6804、LTC6812选型、菊花链与命令时序这一章先把三颗芯片的关系和通信模型立住。很多新手拿到板子直接翻寄存器手册跳过选型和链路拓扑结果驱动写到一半发现芯片是 -2 版本菊花链方向全是反的或者把 LTC6803 的老代码硬套到 LTC6812 上最后一位电压永远是 0。2.1 三颗芯片的通道数、精度与接口对照LTC6803、LTC6804、LTC6812 是同一产品线上的三代电芯监测芯片常见项目里按电芯串数区分LTC6803 做到 6 节LTC6804 做到 12 节LTC6812 把单芯片通道数推到 16 节。虽然寄存器地址和命令码不是完全兼容但驱动的整体结构是一样的配置寄存器、触发 ADC、等待转换完成、读回电压、算 CRC。芯片单芯片通道ADC 位数常见应用位置驱动特征LTC6803612 位低压 BMS、老产品维护寄存器简单无内置均衡超时控制LTC6804-1 / -21216 位12-13 串主从板命令集与 6812 基本一致LTC6812-1 / -21616 位16 串以上乘用车、储能单芯片多 4 通道数据帧更长选型时优先看两件事电芯串数和要不要做隔离。16 串以下用一片 LTC6804-1 加一片 LTC6812 组合最省成本16 串以上直接上两片 LTC6812 级联。如果是储能柜那种 32 串以上的方案就按多片 6812 级联设计别用 6803 叠通道不够还得加从板成本反而高。还要注意型号后面 -1 和 -2 的差别它决定 isoSPI 菊花链的链接方向-1 适合从底部往上串-2 适合从顶部往下串买错版本就得改板。2.2 菊花链拓扑与 isoSPI 隔离驱动看起来像一片实际是一条链你要驱动的往往不是一颗芯片而是一串芯片。常见接法是主控 SPI 先接到 LTC6820由 LTC6820 把电信号转成 isoSPI 差分信号再进第一颗 6804/6812 的 isoSPI 口然后从这颗芯片的另一个 isoSPI 口继续往下一颗传。两片之间用双绞线走差分信号这样既省了光耦又把共模干扰挡在链路外面。驱动代码看起来像只操作一片实际上一帧命令会沿着链传到底再把每颗芯片的应答原路带回来这个黑匣子刚接触时最容易搞懵。级联时必须先把每颗芯片的地址逻辑理顺。常见做法是链上第一颗配成 0x01第二颗 0x02写配置时按顺序把每一颗的 6 字节配置帧连在一起发出去。很多驱动在单板上调通了一上菊花链就乱多半是发送配置帧时没有按链路顺序拼接芯片各自抢总线。注意isoSPI 通信速率不是越高越好。LTC6820 在长线双绞下建议按线缆长度降档10 米以上线缆尤其明显。驱动里预留一个速率参数调试阶段从最低档往上加比一上来就跑满速省事得多。2.3 命令时序ADCON、RDCV 与 CRC-15先背下来再写驱动LTC6804 系的读电压流程分三步。第一步发配置命令把均衡开关、过压欠压阈值写进芯片。第二步触发 ADC常用 ADOW 命令让链上所有芯片同步做电芯电压和温度通道转换。第三步等转换完成延时后发 RDCV 把电压寄存器读回来。命令作用驱动里关注点WRC FGA写配置寄存器组 A6 字节配置 2 字节 PECADOW启动 ADC 转换滤波深度、是否同步转换RDCV读电芯电压组数据长度随芯片通道数变化RDCFG读回配置用于自检和故障标志读取这里有一个底层约定不能跳过LTC6804 和 LTC6812 的命令帧和数据帧都带 CRC 校验命令帧是 8 位 CRC数据帧是 15 位 PEC。写驱动时最容易漏的就是时序里那两颗 CRC。调试初期可以先用逻辑分析仪抓 SPI 波形数一数每一帧是不是比协议多两个字节或少两个字节抓一次比对一次手册比自己盯着代码空想要快得多。转换时间是个必调参数。常见配置下 ADC 一次转换在 1ms 到 3ms 之间滤波模式开到最深时可能到十几毫秒。读电压函数里延时给短了转换没完成读回的是上一次的值这在热管理里会表现为温度滞后很难查。经验值是取芯片手册典型值的 1.5 倍再叠加 1ms 到 2ms 的电容充电余量宁多勿少。3. 驱动分层从寄存器映射到第一次读回电芯电压BMS 的芯片驱动不适合上来就写大函数。我的习惯是先写三层硬件抽象层、芯片命令层、应用层。硬件抽象层管 SPI 收发和延时芯片命令层提供 wrcfg、adow、rdcv 这些函数应用层才去算电压、做滤波。这样做的好处是换主控时只重写最底下那层芯片命令层和应用层原样搬走。3.1 先把寄存器地图和配置字定义出来驱动里先用枚举把命令码收起来再用一个uint8_t数组表示一片芯片的 6 字节配置寄存器。不要一上来就写业务逻辑先把命令码和数据结构定好后面改起来才不用到处翻魔数。/* 芯片命令码LTC6804 / LTC6812 共用 */ enum ltc681x_cmd { CMD_WRCFG 0x00, /* 写配置寄存器组 A */ CMD_RDCFG 0x02, /* 读配置寄存器组 A */ CMD_ADOW 0x10, /* 启动 ADC含滤波模式位 */ CMD_RDCV 0x04, /* 读电芯电压寄存器组 */ }; /* 一颗芯片的配置寄存器6 字节位定义对照手册 */ static uint8_t ltc_cfg[6] {0}; /* 均衡开关位cfg[0] 位0-7 对应 第1-8节cfg[1] 对应 第9-16节 */ #define DCC_SET(cfg, cell, on) do { \ if (on) (cfg)[(cell) / 8] | (1u ((cell) % 8)); \ else (cfg)[(cell) / 8] ~(1u ((cell) % 8)); \ } while (0)逻辑说明命令码集中放在枚举里后续所有收发函数都从这里取命令排查时一眼能看到命令字。配置寄存器的位定义用手册对照着注释写清楚特别是 DCC 均衡位和过压欠压阈值字节不然三个月后回来自己都记不清某个 bit 是干嘛的。参数说明cfg[0]和cfg[1]的位分布在不同芯片上略有差异LTC6803 只有 6 通道只用到低位LTC6812 的 16 通道则要占满两个字节。宏里按 8 位一组划分适配 16 通道芯片时不用改宏本身改的是通道上限。3.2 最小读电压函数从 PEC 校验到数据拼接接下来是核心。LTC6804 系通信帧格式是命令 4 字节数据 18 字节末尾带 2 字节 PEC。写驱动时用 SPI 全双工直接收发即可命令发出后立刻接收响应。static uint8_t spi_xfer(uint8_t *tx, uint8_t *rx, uint16_t len); static uint16_t pec15_calc(const uint8_t *data, uint8_t len); /* 读回一片芯片的电压寄存器组 0 */ int ltc681x_rdcv(uint8_t *cell_raw, uint16_t *cell_mv) { uint8_t tx[4] {CMD_RDCV, 0x00, 0x00, 0x00}; uint8_t rx[18]; uint16_t pec_rx, pec_calc; int i; /* 多片菊花链时第二字节要按手册拼上地址位 */ tx[3] (uint8_t)(pec15_calc(tx, 3) 0xFF); spi_xfer(tx, rx, 4); /* 先发命令 */ spi_xfer(rx, rx, 18); /* 再读数据帧 */ pec_rx (rx[17] 8) | rx[16]; pec_calc pec15_calc(rx, 16); /* 16 字节电压数据 2 字节 PEC */ if (pec_rx ! pec_calc) return -1; for (i 0; i 12; i) { cell_mv[i] (uint16_t)rx[2 i * 2] | ((uint16_t)rx[2 i * 2 1] 8); } return 12; /* 返回有效通道数 */ }逻辑说明命令发出后芯片会在片选拉低期间把数据帧推出来所以驱动里做两次 SPI 传输第一次写命令第二次读数据。PEC 校验必须放在拼电压之前一旦校验不过直接返回 -1让上层决定重试还是报警。这个细节很多人漏一旦帧被干扰读回乱跳的数据全堆到应用层兜着事倍功半。参数说明上面代码按 12 通道的 LTC6804 写。如果换成 16 通道的 LTC6812数据帧长度要变成 24 字节循环次数改成 16二次校验长度改成长度减 2。这些和通道数相关的常量建议都做成宏换芯片型号时只改宏定义。3.3 读回来的电压不能直接用滤波与对齐第一次调通时读回来的电压会抖这不是驱动有问题是 ADC 的正常噪声。常见做法是连续采 3 到 5 次去掉最大值和最小值后取平均。温度通道和电压通道的转换顺序不同要注意按芯片手册的寄存器排列对齐。#define ADC_SAMPLE_TIMES 5 uint16_t cell_mv_filtered[16]; uint16_t raw_buf[5][16]; for (int s 0; s ADC_SAMPLE_TIMES; s) { ltc681x_rdcv(cell_raw, raw_buf[s]); } /* 去掉最大最小取中间 3 次平均 */ for (int c 0; c 16; c) { /* 排序取中位平均简单起见这里示意 */ cell_mv_filtered[c] (raw_buf[1][c] raw_buf[2][c] raw_buf[3][c]) / 3; }逻辑说明多次采样的方式要按场景选。静态下的电压波动主要来自 ADC 量化噪声平均能压住但如果是在充电末端电压本身在缓慢上升这时取平均会引入相位滞后。驱动层只做滑动平均真正异常检测放到应用层。3.4 换芯片适配LTC6803 与 LTC6812 的寄存器差异如果你是从老代码改过来的最阴的是 LTC6803 和 LTC6804/6812 命令不同、配置字节数也不同。LTC6803 只有 6 通道数据帧短一截LTC6812 是 16 通道数据帧长一截。我踩过把 6803 的数组长度搬到 6812 上的坑最后一位总是 0查了半天是数组越界把 PEC 覆盖了校验还偶发通过非常难抓。换芯片型号时建议把数据长度、通道数、命令码三个宏单独放在一个头文件里用条件编译区分。不要在一个大函数里写for (i 0; i 6; i)这种硬编码一旦换芯片必翻车。4. 均衡与故障诊断让驱动从「能读数」变成「能上车」读电压只是第一步BMS 驱动最终要落两个功能被动均衡和故障诊断。很多工程师把读数调通就以为驱动写完了结果测试时均衡电流过大导致芯片复位或者过压误报一天三次直接被退回。4.1 被动均衡用配置字的 DCC 位控制放电开关LTC6804 系做被动均衡不额外加重电路直接用芯片内部的放电开关把目标电芯的正负极通过旁路电阻泄放电流。要打开某一节就在配置寄存器对应 DCC 位置 1。均衡控制一般放在应用层驱动层只提供打开和关闭某一节均衡的接口。/* 打开第 5 节电芯的均衡放电 */ void ltc681x_set_dcc(uint8_t cfg[6], uint8_t cell, uint8_t on) { if (cell 15) return; DCC_SET(cfg, cell, on); } /* 把配置下发到链路6 字节 2 字节 PEC */ int ltc681x_wrcfg(uint8_t addr, uint8_t *cfg) { uint8_t tx[8]; tx[0] CMD_WRCFG; tx[1] addr; /* 菊花链地址 */ memcpy(tx[2], cfg, 6); tx[8] pec15_calc(cfg, 6) 0xFF; return spi_xfer(tx, NULL, 8); }逻辑说明DCC 位按字节分布cfg[0]对应 0-7 节cfg[1]对应 8-15 节。驱动层不需要知道均衡电阻多大、电流多少只要把位写对再调用 WRC FGA 下发即可。真正的均衡策略在上层压差超过阈值才开均衡同时开均衡的节数要限制避免整板升温。参数说明均衡策略里有两个参数容易拍脑袋。第一个是均衡开启压差常见值在 20mV 到 50mV 之间太小会让均衡频繁启停太大则均衡结束时电压落差还很明显。第二个是均衡单次时长建议控制在 5 分钟以内超时后强制关闭等温度降下来再重新策略。驱动用户手册 DCTO 位可以配置均衡超时但应用层最好也留一个软超时兜底。4.2 过压、欠压与温度诊断内置比较器和软件判别的取舍诊断有两种做法读电压回来自己判或者用芯片内置比较器。量产我建议用内置比较器因为它在 ADC 转换之外独立工作速度更快还能在主控休眠时自动报警。配置里设好 VUV 和 VOV 阈值芯片会把对应状态位翻转主控只需轮询状态寄存器。/* 检查过压欠压状态位返回故障位图 */ uint32_t ltc681x_check_fault(uint8_t *cfgrx) { uint32_t fault 0; /* cfgrx[3] 和 cfgrx[2] 的对应位是过压/欠压标志位位置见手册 */ if (cfgrx[3] 0x01) fault | FAULT_OVP; if (cfgrx[2] 0x80) fault | FAULT_UVP; return fault; }逻辑说明读回配置寄存器组 B把状态位拆出来。这里要注意状态位的位置在不同型号上有差异不能直接抄参考代码一定要对着手册的 CFGRB 位定义核对一遍。用内置比较器的好处是阈值判断发生在芯片内部不占用主控算力也避免了 ADC 滤波带来的延迟。参数说明故障标志需要定期清。主控读完故障位后要写一次配置寄存器把标志位清除否则故障一直挂着下次真正过压时反而分不清是新故障还是旧故障。清除动作可以放在读回状态后的同一个函数末尾这样应用层只需要调用一次。4.3 阈值标定与主控状态机驱动接口收敛成四个函数阈值不能拍脑袋。单串磷酸铁锂充电截止 3.65V过压报警设 3.7V 就不合理因为均衡刚结束的静置电压可能瞬间超过 3.65V。我一般取过压阈值等于充电截止电压加 150mV欠压阈值等于放电截止电压减 100mV温度阈值按电芯规格书的连续工作温度范围再各留 10 度余量。驱动要做到能被主控状态机直接调用。我一般把驱动接口收敛成四个函数init、read_all、balance_step、fault_check循环周期 100ms。这样均衡、诊断、通信都在一个节奏里跑也方便在休眠模式下降低采样频率。typedef struct { uint16_t cell_mv[16]; uint8_t fault_flags; uint8_t balance_state; } bms_sample_t; void bms_periodic_1s(bms_sample_t *s) { ltc681x_read_all(s); /* 读电压 温度 */ ltc681x_balance_step(s); /* 执行均衡策略 */ s-fault_flags ltc681x_check_fault(NULL); }逻辑说明四个函数把驱动细节全部封装掉应用层不关心 SPI 时序也不直接碰寄存器。主控的状态机只需要在每个周期调用bms_periodic_1s拿到结果后决定是否报警、是否切断充放电路径。这样做的好处是后期加加密、加日志都不影响上层逻辑。5. 芯片驱动避坑实录5 个让采样板翻车的现场这一章写我踩过的坑每个都是真实项目里查了半天才定位的问题。BMS 驱动的问题往往是偶发的不是一次跑不通而是跑十天冷不丁出一回。这类玄学问题最后大多落在时序、供电和接地三个方向。5.1 现象上电读回 0xFF换板无用原因CS 拉低后立刻发 CLKSPI 时序不满足。芯片上电后 isoSPI 接口要一小段时间稳定命令帧紧跟 CS 下降沿就会被忽略。另一个常见原因是宽体封装焊接时引脚搭锡VREG 引脚和地短路芯片根本没起来。解决CS 拉低后延时至少 5us再启动时钟。上电初始化时加一段延时等 VREG 稳定后再发第一帧。焊接后用放大镜目检或者先量 VREG 对地阻抗正常应该在几十千欧以上。5.2 现象菊花链第五节以后电压全乱跳链条越长越明显原因末端反射和接地反弹。菊花链之间走线没有等长末端没有端接电阻差分信号反射叠加越靠后的芯片误码率越高。这个在四层板上基本见不到两层板特别容易出。解决两条链路走线长度差控制在 1cm 以内末端加 120 欧姆端接电阻。调试时把 isoSPI 速率降一个档位如果乱跳消失基本坐实是信号完整性问题再回头改板。5.3 现象均衡开了 10 分钟主控突然复位电压采样中断原因均衡电流让后端 LDO 输入掉压主控看门狗超时。芯片的 VREG 供电能力有限均衡电流一大内部基准也跟着跌落ADC 读值偏差。解决均衡时错峰同一时间最多开 4 节打开顺序按奇偶轮换。VREG 输入电容加到 22uF均衡电流按芯片手册典型值降额使用不要打满。驱动里加均衡过温保护板温超过阈值强制关闭所有均衡。5.4 现象LTC6803 老代码套到 LTC6812 上最后一节电压恒为 0原因通道数不匹配。6803 是 6 通道数据帧长度和寄存器组数跟 6812 完全不同老代码里for循环次数和数据帧数组长度都没改最后一个通道实际读的是 PEC 字节。解决把所有数据长度和通道数改成宏按芯片型号条件编译。不要直接复制老代码把数据帧格式对照手册重新核对一遍尤其是寄存器组地址和长度。5.5 现象过压误报一天三次充电末端必现原因ADC 滤波深度不够充电末端纹波大半个周期内瞬时电压超过阈值。芯片内置比较器没有滤波直接把瞬时值上报应用层又没有做确认逻辑。解决把 ADOW 命令里的滤波模式提到最深软件侧连续采 3 次取中间值三次都超阈值才报警。阈值按充电截止电压加 150mV 的余量设置不要贴着规格书极限写。6. 量产验证进阶把驱动压到 100 次连续采样零漂移最后一个习惯也是我最重要的教训驱动写得快验证不能急。BMS 驱动出问题往往是偶发的不是一次跑不通而是跑十天冷不丁出一回。我现在每次改完驱动都会做三件事。第一GPIO 模拟 SPI 对拍。把硬件 SPI 换成主控的普通 GPIO 模拟用同样的命令发一遍对比两次读回的电压。差值超过 2mV 就说明硬件 SPI 时序或隔离器延迟有问题。这个测试在样机阶段就能跑不用上整车。第二连续读 100 次电压看最大值和最小值的跨度。正常静态下16 位 ADC 连续 100 次采样漂移应该在 3mV 以内。超过 5mV 就要怀疑滤波配置、PCB 地线或菊花链端接。这个测试我一般写进上电自检产线每块板子跑一遍。第三写一个自动化工位脚本。我在产线电脑上通过串口发指令每次命令间隔 100ms连测 100 次把每次返回值存 CSV用脚本检查有没有 0xFF、CRC 错误告警、电压跳变超过 20mV 的样本。产线上只要能跑满 100 次不告警这批板子我才敢放行。这个习惯帮我拦下过一块很隐蔽的坏板低温下 VREG 电容容值衰减SPI 偶尔丢位室温怎么测都不复现。后来在工位里加了一条低温箱内连测 100 次的用例才把问题抓出来。做 BMS 芯片驱动能力不只看能不能点亮样机更看能不能在量产现场扛住偶发故障。希望这个验证习惯帮到你。本文还有配套的精品资源点击获取