
简介基于STM32单片机与LTC6804-1电池监控芯片的完整软件程序源码面向嵌入式开发者及电池管理系统BMS学习者解决多节锂电池电压精确采集与通信问题。工程采用STM32作为主控通过I2C或SPI接口驱动LTC6804-1包含GPIO配置、外设初始化、电压读取转换、错误检测等模块适合入门到进阶的BMS开发参考。压缩包共188个文件大小4.84MB以C源文件、H头文件及编译中间文件为主同时包含uvproj工程文件、hex烧录文件、map映射文件和启动汇编文件可完整还原工程结构。目前已有633人学习下载。通过研究源码开发者可掌握STM32与LTC6804-1的驱动时序、寄存器配置及电压换算逻辑也能学习工程组织与调试文件的使用方法对实际项目开发具有直接参考价值。1. 一块 12 串电池组面前最省力的读法就是把活交给 LTC6804-1给 48V 或 72V 电池组做电压采样时直接用 STM32 单片机 ADC 去分压采集会遇到两个麻烦共模电压高、精度不够而且每一串都要做隔离和滤波电路板面积和成本都压不住。LTC6804-1 就是干这个的电池监控前端一片最多量 12 节串联电芯对外用 isoSPI 差分总线通信主机侧经过桥接芯片后STM32 只需要按普通 SPI 从机来读写寄存器。标题里的「读取电池电压软件程序源码」核心不在读电压那三五行代码而在 LTC6804-1 的命令帧、PEC 校验、配置寄存器和转换时序上。这几年做 BMS 毕业设计和量产项目的基本都会拿到一份类似参考程序但真正能用起来的都得先把下面这套协议理清楚。2. LTC6804-1 的 SPI 协议与 STM32 侧接口设计2.1 isoSPI 不是直接接 STM32 的 SPI 引脚很多人拿到 LTC6804-1 的源码后第一反应是找 SPI 引脚直接连 STM32这个方向是错的。LTC6804-1 物理层并不输出标准的 MOSI/MISO 电平它对外只有两线制的 isoSPI 差分脉冲接口设计目的是让电池监控前端和主控之间能通过变压器做电气隔离。单片机要想和它通信主机侧还要有一颗 LTC6820 之类的 isoSPI 转 SPI 桥接芯片把 STM32 输出的标准 SPI 信号转成隔离脉冲再送到 LTC6804-1。也就是说源码里所有SPI_Write、SPI_Read操作实际作用对象是 LTC6820而不是 LTC6804-1 本身。搞清楚这层关系后写软件就有底了STM32 侧看到的通讯时序依然是标准 SPI 主机模式有四根线——SCK、MOSI、MISO、CSB。桥接板的 SPI 模式一般要求 Mode 0时钟极性 CPOL0、相位 CPHA0数据在时钟上升沿采样。时钟频率要压到 1MHz 以下这是 LTC6804-1 isoSPI 链路能可靠工作的上限别贪快。2.2 命令帧格式与 PEC 校验这是源码里最不该跳过的一段LTC6804-1 的每一条命令都由几个固定部分组成命令字节、命令字节的 PEC 校验值后面根据需要再跟数据字节和数据整体的 PEC 校验值。PEC 不是简单的累加和而是一个多项式为 x^8x^2x1即 0x07的 CRC-8计算初始值为 0x41数据传输从最高位开始。手册里把这个计算叫 Packet Error Checking作用是对整包数据做完整性校验。源码里如果只抄命令表不抄 PEC 函数读回来的电压数据会时对时错。// 计算 LTC6804-1 的 8 位 PEC输入为待校验的数据缓冲区和长度 static uint8_t pec8_calc(uint8_t *buf, uint8_t len) { uint8_t crc 0x41; // LTC6804 PEC 规定的初始值 for (uint8_t i 0; i len; i) { for (int8_t bit 7; bit 0; bit--) { uint8_t in_bit ((buf[i] bit) 0x01) ^ ((crc 7) 0x01); crc 1; if (in_bit) { crc ^ 0x07; // 异或生成多项式 } } } return crc; }这段代码直接可用于 STM32 工程。参数说明buf是要计算校验的数据区可以是命令字节本身也可以是配置寄存器或电压寄存器读回的数据区len是参与校验的字节数。命令校验时 len 传 1配置数据校验时传 18电压数据校验时传 24。这里关键点有两个初值必须是 0x41多项式必须是 0x07两者和常见 CRC-8 约定不一样照搬 STM32 库里的 CRC 外设是算不出正确结果的。2.3 STM32CubeMX 里的 SPI 参数怎么填配置 STM32 的 SPI 外设时我一般直接在 CubeMX 里按下面这张表填生成的代码就不需要再改。配置项推荐值说明SPI ModeFull-Duplex MasterLTC6820 作为从机Hardware NSSDisable片选用普通 GPIO 控制Data Size8 bit命令和数据都是字节流Clock PolarityLow对应 SPO0即 CPOL0Clock Phase1 Edge对应 SPH0即 CPHA0Prescaler按 APB 算到 ≤1MHz例如 APB272MHz 时选 /128First BitMSB First协议固定要求片选引脚留意一点CSB 在 LTC6804-1 的数据手册里是低有效一帧命令加数据整个过程中要保持低电平帧与帧之间要有足够的高电平间隔。源码里通常用一个 GPIO 模拟片选不要在 SPI 硬件层开 NSS 自动控制否则帧边界会被 SPI 外设自作主张地打断。// 一个最基础的 SPI 单字节收发函数属于整个驱动的地基 uint8_t spi_rw(uint8_t tx_data) { uint8_t rx_data; HAL_SPI_TransmitReceive(hspi1, tx_data, rx_data, 1, 10); return rx_data; }参数说明hspi1是工程里实际使用的 SPI 句柄tx_data是主机要发送的字节rx_data接收从机在同一次时钟周期里送回来的数据。注意这里不要只用HAL_SPI_Transmit因为 LTC6804-1 的很多寄存器读取操作需要在发送命令的同时读取 MISO 上的数据只有收发同时进行才不会丢字节。超时时间 10ms 足够SPI 时钟 1MHz 时一个字节只有 8µs 左右。3. 配置寄存器写入与 ADC 启动转换的源码实现3.1 WRCONFIG 命令把放电、超时和 GPIO 配置写进 LTC6804-1LTC6804-1 有三组配置寄存器 CFGA、CFGB、CFGC每组 6 个字节内容包含 GPIO 引脚方向、被动均衡放电开关、放电超时时间 Dcto、基准使能等。上电后这些寄存器全部是默认值如果需要对某一节电池做均衡放电或者需要用 GPIO 做温度采集就必须先把配置写进去。写入命令是WRCONFIG命令码 0xFA。写配置的通用函数可以这样组织// cfgA / cfgB / cfgC 分别是三组 6 字节配置数据 void ltc6804_wrconfig(uint8_t *cfgA, uint8_t *cfgB, uint8_t *cfgC) { uint8_t cmd 0xFA; // WRCONFIG 命令码 uint8_t data[18]; uint8_t pec; memcpy(data[0], cfgA, 6); memcpy(data[6], cfgB, 6); memcpy(data[12], cfgC, 6); ltc6804_cs_low(); spi_rw(cmd); spi_rw(pec8_calc(cmd, 1)); // 命令字节自身的 PEC for (uint8_t i 0; i 18; i) { spi_rw(data[i]); // 连续写入 18 字节配置 } spi_rw(pec8_calc(data, 18)); // 18 字节数据的整体 PEC ltc6804_cs_high(); HAL_Delay(1); // 写配置后留出器件处理时间 }逻辑说明WRCONFIG一次要写完三组配置共 18 字节因此命令发出后主机要连续给出 18 个时钟周期。整体 PEC 是对 18 字节数据一起计算而不是对每组分别算。最后那句HAL_Delay(1)是给 LTC6804-1 内部处理留时间实测不延时直接发下一条命令也经常能通但批量生产时偶发写失败原因往往就在这里的时序余量不足。首版调试时 CFGA 到 CFGC 可以都填 0x00先把电压读通再逐步打开均衡和 GPIO。3.2 ADCV 启动转换模式位决定等待时间电压采集前要先发 ADCV 命令让 LTC6804-1 内部的 16 位 ADC 启动一轮测量。ADCV 命令基地址是 0x20命令字节的高两位是转换模式 MD用来选 ADC 滤波强度和数据更新速率。常用模式有 27Hz、7kHz、26kHz 和 2kHz 四档27Hz 模式测量最准但耗时最长适合正常采样7kHz 和 26kHz 适合产线快速测试。下面的启动函数把模式作为参数传入方便不同阶段切换。// mode: 027Hz, 126kHz, 27kHz, 32kHz按实际需要传参 void ltc6804_adcv_start(uint8_t mode) { uint8_t cmd 0x20; // ADCV 命令基础值 cmd | (mode 0x03) 6; // MD 位位于 bit7:bit6 // 这里默认 DCP 不使能放电CH 选择全部电池通道 ltc6804_cs_low(); spi_rw(cmd); spi_rw(pec8_calc(cmd, 1)); ltc6804_cs_high(); }参数说明mode只取低两位左移 6 位后放进命令字节的高两位。DCP 位用于在转换期间允许被动放电采集电压时通常不使能等均衡阶段再配合配置寄存器打开。CH 位决定测量哪些通道全部电池通道是常见做法如果你的电池串数不满 12 串也可以只测部分通道但源码里默认全通道扫描最简单多出来的通道读回 0 值不影响判断。ADCV 命令发出后ADC 需要一段时间完成转换27Hz 模式整体需要几十毫秒量级7kHz 模式在毫秒级驱动里在这个位置加对应的延时再去读电压寄存器。模式字段采样速率档位典型用途0027Hz正常电压采集数据平滑0126kHz产线快检噪声偏大107kHz折中档调试常用112kHz低速高精度测试提示ADCV 转换期间不要穿插对电压寄存器的读取也不要同时发其他配置命令否则转换结果可能不完整读回来的数据会被 PEC 校验拦下。3.3 初始化阶段最容易翻车的 3 个错误第一PEC 计算时用错了初始值和多项式。很多工程里有现成的 CRC-8 函数直接拿来算 LTC6804-1 的包校验结果命令被器件拒绝。第二SPI 初始化时开了硬件 NSS。CSB 必须全程由 GPIO 控制硬件 NSS 会在每次传输结束后自动拉高把一帧数据拆成多段LTC6804-1 就认为命令非法。第三片选释放后马上发下一条命令中间没有留出高电平间隔。LTC6804-1 的 isoSPI 链路对帧间隔有要求至少留出微秒级间歇最简单的做法就是在cs_high()之后加一个delay_us(5)。4. RDCV 读电池电压读取时序、数据解析与 PEC 校验4.1 用 RDCV 命令把 12 路电压一次读回RDCV 的命令码是 0x04作用是把 4 组电压寄存器 CVRGA、CVRGB、CVRGC、CVRGD 全部读回。每组寄存器保存 3 节电池的电压数据4 组加起来正好是 12 节。每组 6 字节共 24 字节数据后面还跟 1 字节 PEC 校验值。驱动函数完整流程是拉低片选发送 0x04 和它的 PEC然后连续读 25 个字节最后校验最后一个字节是不是 24 字节数据的 PEC。// 读回全部 12 节电压成功返回 0PEC 错误返回 1 uint8_t ltc6804_rdcv(float *cell_v) { uint8_t cmd 0x04; // RDCV 命令码 uint8_t rx[25]; uint8_t pec; ltc6804_cs_low(); spi_rw(cmd); spi_rw(pec8_calc(cmd, 1)); for (uint8_t i 0; i 25; i) { rx[i] spi_rw(0x00); // 时钟由主机提供从机返回数据 } ltc6804_cs_high(); pec pec8_calc(rx, 24); // 前 24 字节是电压数据 if (pec ! rx[24]) { return 1; // 校验失败数据不可信 } for (uint8_t reg 0; reg 4; reg) { for (uint8_t ch 0; ch 3; ch) { uint16_t raw ((uint16_t)rx[reg * 6 ch * 2] 8) | rx[reg * 6 ch * 2 1]; cell_v[reg * 3 ch] (float)raw * 0.0001f; } } return 0; }逻辑说明发送 RDCV 命令时LTC6804-1 会在后续时钟周期里把电压寄存器组的数据逐个放到 MISO 上因此主机在读阶段发送的 0x00 只起到提供时钟的作用真正有价值的是rx数组里收到的内容。PEC 覆盖的是 24 字节电压数据不包含命令字节所以校验前pec8_calc的len参数传 24。电压数据的字节顺序是高位在前rx[0]是第一节电池电压的高 8 位rx[1]是低 8 位。4.2 16 位原始值换算成电压值LTC6804-1 内部是 16 位 ADC电压寄存器里存的是 16 位无符号原始码每个 LSB 对应 0.0001V也就是 100µV。比如原始码 0x9C40 换算出来是 40000 × 0.0001 4.0000V。代码里直接乘 0.0001f 就行不需要再除以 4096 或 65535这一点很多人会搞混。判断读数是否合理可以把 12 节电压加总和电池组总电压的万用表读数对比误差一般在 0.1V 以内。单节电芯静止状态下应该在 3.0V 到 4.2V 之间如果某一路读出来是 0V先查那一节电池是否真的接到了芯片的 C 引脚上如果读出来是满量程附近的 6.5V 左右大概率是某一路输入开路芯片把悬空状态读成了最大码。4.3 读数忽高忽低时查什么先看片选时序。用逻辑分析仪抓 CSB 和 SCK正常波形应该是 CSB 在整个命令加数据期间保持低电平帧与帧之间有明显的拉高间隔。如果 CSB 在每个字节传输后都跳高说明 STM32 的 SPI 被配置成了硬件 NSS 自动片选。再看 SPI 空闲时的时钟电平Mode 0 要求 SCK 空闲为低有些板卡的桥接芯片配置需要高电平有效实际项目里最好用示波器确认 LTC6820 的 SDO、SDI、SCK 三根线定义跟单片机侧一致。最后看 PEC 错误率在ltc6804_rdcv的返回位置挂一个计数器连续出错超过 3 次就可以判定为硬件链路问题而不是软件偶发。5. 菊花链多片 LTC6804-1 与验证技巧5.1 LTC6804-1 和 LTC6804-2 的选型差异标题里点名的是 LTC6804-1说明方案默认支持菊花链。LTC6804-1 的 D0/D1 引脚可以首尾级联多片芯片共用同一条 SPI 总线主机只需要一个片选就能同时管理多组电池。LTC6804-2 则走独立寻址模式每片用不同地址适合各片分散布局的场景。做整车或储能柜时-1 的菊花链更常见因为线束少、隔离简单。源码里如果看到total_ic这个变量指的就是菊花链上挂了几片 LTC6804-1读电压时 24 字节数据会按片数翻倍PEC 的计算范围也要跟着扩大。5.2 用分压板验证读数的三个步骤验证源码写得对不对不建议一上来就接真实电池组。找一块电阻分压板把 12 路输入分别接到 1kΩ 和 10kΩ 电阻分出来的电压点上模拟 3.0V 到 3.6V 的伪电池电压。第一步写一个循环每秒读一次 12 节电压并打印到串口确认每路读数和万用表误差小于 5mV 量级。第二步把分压点交换一路比如把第 3 路和第 4 路的线对调看软件里第 3、4 路读数是否跟着交换这一步专门验证电压通道映射有没有错位。第三步拔掉某一根分压输入线软件里对应通道应该报出 6V 附近的异常值同时 PEC 校验不应报错这说明芯片自检通道正常。这三个步骤跑通之后再把源码里的 SPI 时钟从 125kHz 逐步提高到 1MHz每提高一档都观察串口打印的 PEC 错误计数。最终量产程序可以保留在 1MHz同时在主循环里对 PEC 错误做累计超过阈值就主动上报 DTC 并进入降级采样模式。调试到这一步标题里那个「读取电池电压软件程序」才算真正从参考代码变成了自己的 BMS 底层驱动。本文还有配套的精品资源点击获取