STM32读取LTC6804-1电池电压:SPI协议与PEC校验详解
2026/9/16 23:06:29 网站建设 项目流程

简介:基于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_WriteSPI_Read操作,实际作用对象是 LTC6820,而不是 LTC6804-1 本身。

搞清楚这层关系后,写软件就有底了:STM32 侧看到的通讯时序,依然是标准 SPI 主机模式,有四根线——SCK、MOSI、MISO、CSB。桥接板的 SPI 模式一般要求 Mode 0,时钟极性 CPOL=0、相位 CPHA=0,数据在时钟上升沿采样。时钟频率要压到 1MHz 以下,这是 LTC6804-1 isoSPI 链路能可靠工作的上限,别贪快。

2.2 命令帧格式与 PEC 校验,这是源码里最不该跳过的一段

LTC6804-1 的每一条命令都由几个固定部分组成:命令字节、命令字节的 PEC 校验值,后面根据需要再跟数据字节和数据整体的 PEC 校验值。PEC 不是简单的累加和,而是一个多项式为 x^8+x^2+x+1(即 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对应 SPO=0,即 CPOL=0
Clock Phase1 Edge对应 SPH=0,即 CPHA=0
Prescaler按 APB 算到 ≤1MHz例如 APB2=72MHz 时选 /128
First 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-1

LTC6804-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: 0=27Hz, 1=26kHz, 2=7kHz, 3=2kHz(按实际需要传参) 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 节电压,成功返回 0,PEC 错误返回 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_calclen参数传 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 底层驱动。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询