简介:LTC6804与STM32F4xx集成的电池检测AFE驱动代码包,面向电池管理系统研发与嵌入式开发者。资源围绕高精度电池监控芯片LTC6804与STM32F4xx的SPI通信,提供完整C驱动实现与配套头文件,可用于多节电池串联电压采集、错误检测及安全阈值配置等场景。压缩包内共2个文件,1个C源文件、1个头文件,整体仅13KB,结构精简便于直接移植到工程中。C文件涵盖初始化、读取电池电压、错误检查等关键函数,头文件定义数据结构与外设接口,适合已具备STM32基础、需要快速集成LTC6804的开发者参考。资源已被596人学习下载,配有SPI通信、寄存器操作、电压采样流程等要点说明,可帮助理解AFE工作原理并缩短驱动调试时间。实际项目中需结合芯片手册配置采样顺序与电源管理,该代码包能作为稳定的起步模板,方便二次开发与功能扩展。
1. 电池包里的每一毫伏,都要过 LTC6804 这道关
一串 96 节电芯的电池包,SOC 估算不准往往不是算法问题,而是底层电压数据本身就差了十几毫伏。LTC6804 就是干这个的:16 位 delta-sigma ADC,单芯片盯 12 节串联电芯的电压,通过 SPI 把测量结果交给 STM32F4xx。真正上手后会意识到,这颗芯片的 ADC 指标并不难达到,难的是 SPI 时序、PEC 校验和寄存器配置这些外围活——大部分板子量不准,问题都出在驱动没把标志位当回事。这套以 16bit-LTC68042.c 和 LTC68042.h 为核心的驱动,覆盖了从 SPI 初始化、配置寄存器写入、电压读取到错误码解析的完整链路,适合正在写 BMS 底层驱动、或者要把 LTC6804 移植到 STM32F4 平台上的嵌入式工程师。搞懂了这套代码的边界,你排查电池电压异常时就不会再拿着万用表瞎猜。
2. STM32F4xx 与 LTC6804 的 SPI 连接:先解决电平和时序
2.1 引脚映射与电平匹配
接线不复杂,关键是别把 LTC6804 当普通 SPI 从机。STM32F4 这边我用 SPI1,四根线对应如下:
| STM32F4 (SPI1) | LTC6804 | 方向 | 说明 |
|---|---|---|---|
| PA5 (SCK) | SCK | 输出 | 串行时钟 |
| PA7 (MOSI) | SDI | 输出 | 命令与配置数据 |
| PA6 (MISO) | SDO | 输入 | 测量结果 |
| PB0 (GPIO) | CSB | 输出 | 从机片选,软件控制 |
LTC6804 的 SPI 口是 3.3V/TTL 电平,STM32F4 也是 3.3V,正常情况下可以直接相连。但如果电池组和主控板之间走线超过 10cm,或者上下电时序不一致,我最常遇到的是 SDO 在高阻态时被干扰拉出毛刺,CSB 一拉低就收到错帧。量产板上我一般会在四根线上串 33Ω 左右的电阻,并在 LTC6804 侧对地加 10pF 电容,让边沿变缓,换取更小的串扰;真要过认证或者做长距离菊花链,就得换成隔离器件,驱动代码反而不需要改。
2.2 SPI 模式与时序参数
LTC6804 支持 SPI Mode 0,也就是 CPOL=0、CPHA=0:SCK 空闲为低,数据在第一个边沿(上升沿)采样。有一个点容易被忽略——命令字节必须 MSB 先发,但芯片内部做 PEC 校验时是 LSB 先移位,这两个“先发”不是一回事。混在一起想很容易把自己绕进去。
时钟频率要从低往高调。数据手册给的 SPI 时钟上限看着不低,但我实际踩过坑:长线加隔离后,1MHz 以下才可靠。初始阶段我会把预分频设大,比如 APB2 168MHz 下用 256 分频,得到约 656kHz,等逻辑分析仪确认 MISO 数据在每个 SCK 上升沿之后不再抖动,再逐步降到 64 分频,也就是 2.625MHz——注意,如果用的是杜邦线飞线调试,2MHz 以上基本就会偶发 PEC 错误,这和芯片本身没关系,是边沿反射导致的采样窗口偏移。
2.3 HAL 库 SPI1 初始化(可直接搬)
以下是基于 STM32CubeMX 生成后手动改过的初始化代码,只保留跟 LTC6804 相关的部分:
static void ltc6804_spi_init(void) { GPIO_InitTypeDef gpio = {0}; __HAL_RCC_SPI1_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); gpio.Pin = GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7; gpio.Mode = GPIO_MODE_AF_PP; gpio.Pull = GPIO_NOPULL; gpio.Speed = GPIO_SPEED_FREQ_LOW; // 边沿别太陡 gpio.Alternate = GPIO_AF5_SPI1; HAL_GPIO_Init(GPIOA, &gpio); gpio.Pin = GPIO_PIN_0; gpio.Mode = GPIO_MODE_OUTPUT_PP; gpio.Pull = GPIO_PULLUP; gpio.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOB, &gpio); HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET); // CSB 空闲拉高 hspi1.Instance = SPI1; hspi1.Init.Mode = SPI_MODE_MASTER; hspi1.Init.Direction = SPI_DIRECTION_2LINES; hspi1.Init.DataSize = SPI_DATASIZE_8BIT; // LTC6804 按字节收发 hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; // CPOL=0 hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; // CPHA=0 hspi1.Init.NSS = SPI_NSS_SOFT; // CS 用 GPIO 控制 hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256; // 初始 656kHz hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; // 命令字节 MSB first hspi1.Init.TIMode = SPI_TIMODE_DISABLE; hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; hspi1.Init.CRCPolynomial = 7; HAL_SPI_Init(&hspi1); }几个参数解释:DataSize 必须 8 位,LTC6804 的数据寄存器按字节流组织,16 位帧模式会导致末端多出无效位;NSS 用软件控制是因为硬件 NSS 会在每次字节传输后自动拉高,而 LTC6804 要求 CSB 在整个命令加数据帧期间保持低电平,中间任何一次拉高都会让芯片认为命令被中断;BaudRatePrescaler 从 256 起步,时序跑顺后再往小调。GPIO 速度用 LOW 不是为了省电,而是降低辐射和边沿振铃,对长线调试很关键。
提示:如果 CubeMX 生成的代码里 SPI 初始化在 DMA 之前,而你又打算用 DMA 收发,注意 LTC6804 的 CSB 释放时机必须由 DMA 传输完成回调来控制,不能在调用
HAL_SPI_Transmit之后立刻拉高 CSB,否则最后一两个字节会被截断。
另外,我一般不在同一帧里用HAL_SPI_TransmitReceive做“边发边收”。LTC6804 的响应要等命令解析完才产生,命令发出后 MISO 上可能还有上一帧的残留数据。先发命令、再把 CSB 保持低电平、单独收,是这份驱动里更不容易出错的顺序。
3. 从命令字到配置寄存器:驱动最容易被抄错的一段
3.1 需要记住的五条命令
LTC6804 的命令都是 16 位,发送时先发高字节再发低字节,随后跟 2 字节 PEC。真正天天用的就五条:
| 命令 | 命令字 | 用途 |
|---|---|---|
| WRCONF | 0x001A | 写 6 字节配置寄存器 |
| RDCONF | 0x000A | 回读配置 |
| ADCV | 0x02 xx | 启动电芯电压转换 |
| RDCV | 0x0004 | 读取转换结果 |
| RDAUX | 0x000C | 读取辅助/温度通道 |
ADCV 的第二个字节不是固定的,“xx”部分承载了转换模式和通道选择:MD 位决定 14 位还是 16 位、快速还是慢速,CH 字段决定转哪一组通道。每个位的组合在数据手册里是一张表,16bit-LTC68042.c 的做法是用宏把常用组合提前算好,比如全通道 16 位快速、全通道 16 位慢速,使用时直接传宏名,避免在业务代码里做位运算。我建议你也这样封装,因为 ADCV 的位定义在 6804 和后续 6811 之间有微调,裸写位运算最容易埋雷,换型号时还得重新对着手册抠。
3.2 配置寄存器到底在配什么
配置寄存器共 6 字节,按字节拆开看是这样:
| 字节 | 关键位 | 作用 |
|---|---|---|
| CFGR0 | SNAP、ADCOPT、VUV、VOV | 同步采样、16 位快慢档、欠压/过压比较器 |
| CFGR1 | GPIO 引脚控制 | 控制外部电路或自检 |
| CFGR2 | 12 个放电位 | 对应每节电芯的平衡放电开关 |
我拿到一份新 BMS 板子,最先调的是 ADCOPT 和 VUV/VOV。ADCOPT 决定 16 位模式下 LSB 是 100μV 还是 16μV:快速档适合行车过程中频繁刷新电压,慢速档适合静置时做最终判据。VUV/VOV 是片上阈值比较器,配置好后 LTC6804 会在硬件层面对异常电压打标志,主控不需要逐周期比较电池电压——这是 BMS 软件里最容易省掉、但出问题之后最想有的功能。
阈值不是设得越严越好。VUV 设得离正常电压太近,温度一变化就会误报警。行业里常见做法:磷酸铁锂按 2.5V 和 3.65V 起设,三元按 2.8V 和 4.2V 起设,再留 50mV 左右的裕量。这个裕量要根据电芯的低温特性调,低温下内阻增大,负载瞬间会把电压拉低,阈值卡太死会频繁触发保护。
3.3 写配置的正确姿势:PEC 要算两遍
写 WRCONF 时,不光是命令字要带 PEC,后面的 6 字节配置数据也要再算一遍 PEC 并追加在尾部。很多人抄驱动只算了命令 PEC,配置数据裸发,结果寄存器写入偶发失败,回读配置时对不上。正确流程如下:
#define LTC6804_PEC_OK 1 #define LTC6804_PEC_ERR 0 uint8_t LTC6804_WriteConfig(uint8_t *cfg) { uint8_t cmd[4]; uint16_t pec; uint8_t pec_buf[2]; cmd[0] = 0x00; // WRCONF 高字节 cmd[1] = 0x1A; // WRCONF 低字节 pec = LTC6804_Pec15(cmd, 2); // 命令 PEC cmd[2] = (pec >> 8) & 0xFF; // PEC 高字节先发 cmd[3] = pec & 0xFF; pec = LTC6804_Pec15(cfg, 6); // 配置数据 PEC pec_buf[0] = (pec >> 8) & 0xFF; pec_buf[1] = pec & 0xFF; ltc6804_cs_low(); if (HAL_SPI_Transmit(&hspi1, cmd, 4, 100) != HAL_OK) { ltc6804_cs_high(); return LTC6804_PEC_ERR; } if (HAL_SPI_Transmit(&hspi1, cfg, 6, 100) != HAL_OK) { ltc6804_cs_high(); return LTC6804_PEC_ERR; } if (HAL_SPI_Transmit(&hspi1, pec_buf, 2, 100) != HAL_OK) { ltc6804_cs_high(); return LTC6804_PEC_ERR; } ltc6804_cs_high(); return LTC6804_PEC_OK; }LTC6804_Pec15是 15 位 CRC 计算函数,输入一串字节,输出 16 位变量但只用低 15 位。计算时按 LSB-first 逐位移位,和芯片内部的多项式对齐。注意cmd[2]先发 PEC 高字节,LTC6804 的命令帧和响应帧统一大端,别按小端习惯把低字节放前面,否则一次都校验不过。
这里有个隐含时序点:CSB 从拉低到第一个 SCK 上升沿之间,需要满足芯片的最小建立时间。HAL 库的HAL_SPI_Transmit内部会等待 TXE 标志,通常在微秒级,够用;但如果ltc6804_cs_low()后面紧跟 SPI 发送,中间恰好来了优先级更高的中断,就可能卡在边界上。我一般会在 CSB 拉低后加一个 1~2μs 的空循环做保险,副作用可以忽略。
写完配置不要急着发 ADCV,先回读一次 RDCONF,确认写入的 6 字节和读出来的一致。回读还有一层意义:能确认 SPI 双向链路本身是通的。如果回读频繁出错,问题一定在物理层,不在应用逻辑。
4. 读电压不是收数组:RDCV 解析与错误标志
4.1 RDCV 返回的数据长什么样
发送 RDCV(0x0004)后,LTC6804 会返回一串字节:每个电芯一组 3 字节,12 节电芯就是 36 字节,最后附 2 字节 PEC,总共 38 字节。每组 3 字节里,最高字节是 ADC 模式标志和状态位,低 16 位才是电压代码。这跟很多人想的“直接来 12 个 uint16_t”不一样,所以串口打印出来会看到一堆以 0x00 开头的数据,这并不代表测量失败。
注意,这里说的 3 字节格式对应 ADCOPT=0(16 位结果未扩展)的情况。把 ADCOPT 置 1 之后,每组数据会变成 4 字节,多出来的位是 ADC 的扩展分辨率,解析代码要跟着换。16bit-LTC68042.c 默认按 3 字节解析,如果你改了 ADCOPT,记得同步改数据步进,不然后续所有电压都会串位。
4.2 电压换算与标定
电压代码到真实电压的换算,基础公式是:
V = code × LSB
16 位快速档 LSB 取 100μV,16 位慢速档取 16μV。算两个典型值:读出代码 0x9C40(十进制 40000),快速档对应 4.0000V,慢速档对应 0.6400V——同一个代码不能直接套同一个系数,换算前先确认当前用的是哪个 ADC 模式。网上资料里偶尔提到的 2.5μV/计数,属于最慢积分档结合外部高精度基准的理论分辨率,驱动日常不会跑到那个档位,拿它去算电压会把数值差出几十倍。
量产时不能只靠这个公式。芯片内部参考电压有出厂离散性,加上 PCB 分压电阻的误差,整车厂做 BMS 都会在产线上做两点校准:分别施加一个低电压和一个接近满量程的高电压,读出代码后反推增益和偏置。校准值可以写回芯片的增益寄存器,驱动里读取电压后再叠加软件校准系数。最常见的错误是有人拿一块万用表量了一下,就把某个换算常数写死在代码里,换一块板子就偏出去十几毫伏。
4.3 解析函数与 PEC 校验
下面是一段简化的读取和校验代码,通信方向是先把 RDCV 命令发出去,再保持 CSB 为低,从从机读 38 字节:
int LTC6804_ReadCellVoltages(uint16_t vcell[12]) { uint8_t cmd[4] = {0x00, 0x04, 0, 0}; // RDCV 0x0004 uint8_t rx[38]; uint16_t pec, cal_pec; uint8_t flag; int i; pec = LTC6804_Pec15(cmd, 2); cmd[2] = (pec >> 8) & 0xFF; cmd[3] = pec & 0xFF; ltc6804_cs_low(); HAL_SPI_Transmit(&hspi1, cmd, 4, 100); HAL_SPI_Receive(&hspi1, rx, 38, 200); // 36 字节数据 + 2 字节 PEC ltc6804_cs_high(); cal_pec = LTC6804_Pec15(rx, 36); // 对有效数据算 PEC if (cal_pec != ((rx[36] << 8) | rx[37])) { return -1; // PEC 不匹配,丢弃整帧 } for (i = 0; i < 12; i++) { flag = rx[i * 3]; // 标志字节 vcell[i] = (rx[i * 3 + 1] << 8) | rx[i * 3 + 2]; if (flag & 0x40) { vcell[i] = 0xFFFF; // 该通道测量无效 } } return 0; }HAL_SPI_Receive是纯接收,不需要像某些 SPI 从机那样在接收时继续发 0xFF;但如果 CSB 拉高时间太短就发起下一次通信,接收缓冲里可能残留上一帧的尾巴。处理办法:每次读完把 CSB 拉高至少 150ns,我实际写驱动时一般留 2~5μs,顺便等芯片内部状态机复位。
flag & 0x40是“该通道转换无效或未完成”的典型标志位。不同 ADCOPT 下标志位布局有差异,这份驱动把标志字节的高位留给了错误状态。遇到 PEC 校验失败时,不要立刻连续重试,先读一次 RDCONF 看链路是否还活着;连续错 5 帧以上,基本可以断定是 SPI 时序退化而不是偶发干扰。有些工程师一看到 PEC 错误就加延时,实际上延时只能解决建立时间不够的问题,对于边沿反射导致的数据采样错误,降 SPI 时钟比加延时更有效。
4.4 欠压过压标志别全靠软件比较
LTC6804 内部有硬件比较器,配置好 VUV/VOV 后,转换完成时结果会直接放进状态寄存器。很多人忽略这一点,把 12 节电芯电压读回来再在 STM32 里逐个比较,白白增加主控负载。更合理的设计是:RDCV 的电压数据交给 SOC 算法用,过压欠压快速预警交给硬件标志,主控只在标志变化时介入。
不过要留个心眼:硬件比较器的阈值精度不足以作为最终保护依据,它更适合做快速预警。真正的保护逻辑,还是要拿 RDCV 读到的电压在 STM32 里做二次判断,两边互为冗余。我在实际项目里就是这样分工的,既保响应速度,又保判断精度。
5. 从“能读”到“敢用”:菊花链、隔离与校准收尾
5.1 是 LTC6804-1 还是 LTC6804-2:驱动看得见差别
看文件名 16bit-LTC68042.c,容易把它当成“LTC6804 的第二个版本驱动”。实际上 LTC6804-2 是 isoSPI 版本,芯片之间用差分线串联成菊花链,与 STM32 之间通常隔着一个 LTC6820 转换器。对 MCU 来说,LTC6820 呈现的仍然是 SPI 从机接口,所以这套驱动的寄存器访问函数可以原样用,但要清楚三个区别:CSB 的时序参数由 LTC6820 决定,SPI 时钟要降速到 500kHz 量级才保险;菊花链上每个芯片有独立地址位,命令会广播到所有芯片,但只有地址匹配的芯片把数据放到总线上;读回的数据长度随串联芯片数量成倍增加,驱动里的缓冲区不能只按一片的 38 字节算,否则 DMA 会溢出。
5.2 调试三板斧
我调试这套驱动只依赖三样东西:逻辑分析仪、PEC 返回值和回读配置。第一斧是确认 CSB 在整个帧内没有毛刺,逻辑分析仪能看到任何一次意外拉高;第二斧是打印每次读电压的 PEC 状态,统计错误率而不是只看有没有错误;第三斧是把 RDCONF 回读值和写入值做 diff,如果第三斧频繁失败,问题基本在硬件。记住,先解决物理层,再优化软件,顺序反了会浪费大量时间。
5.3 一个值得现在就做的校准动作
如果手头有可调电源和精密电压源,先做这个最小校准:把 LTC6804 的 V+ 和 V- 之间接一个 2.500V 基准,读回代码记为 c1;再把基准源调到 4.000V,读回代码记为 c2。计算:
gain = (4.000 - 2.500) / (c2 - c1) offset = 4.000 - gain * c2
把 gain 和 offset 存到 STM32 的 Flash,每次读电压后先做浮点修正,再参与 SOC 和均衡判断。这样做的改进往往比把 ADC 切到 16 位慢速档更明显,因为慢速档只能降低噪声,无法消除基准和分压电阻带来的固定偏差。修正后回读一次 RDCV,确认修正值和电压源读数差距落在 1mV 以内,再把这个修正流程固化到产线测试脚本里。
本文还有配套的精品资源,点击获取