手把手教你用STM32F103驱动LTC6804:从SPI通信到电池电压采集的完整实战
在电池管理系统(BMS)开发中,LTC6804作为一款专业的电池监控芯片,能够精确测量多达12节串联电池的电压。本文将带你从零开始,基于STM32F103开发板,通过SPI接口实现与LTC6804的通信,完成电池电压采集的全过程。无论你是初次接触BMS开发,还是已有一定嵌入式基础想深入了解LTC6804,这篇实战指南都能为你提供清晰的路径。
1. 硬件准备与环境搭建
1.1 所需硬件清单
- 主控板:STM32F103C8T6最小系统板(蓝色药丸板)
- 电池监控板:LTC6804-1或LTC6804-2采集板
- 电源:3.3V稳压电源(为STM32供电)
- 电池组:待测的串联电池组(3-12节)
- 连接线:杜邦线若干
- 调试工具:ST-Link调试器、逻辑分析仪(可选)
1.2 硬件连接示意图
| STM32引脚 | LTC6804引脚 | 功能说明 |
|---|---|---|
| PA5 | SCK | SPI时钟 |
| PA6 | MISO | 主入从出 |
| PA7 | MOSI | 主出从入 |
| PA4 | CS | 片选信号 |
| GND | GND | 共地 |
提示:如果使用硬件SPI,务必确认STM32的SPI引脚映射关系。某些开发板可能使用不同的引脚分配。
2. SPI通信基础配置
2.1 硬件SPI初始化
void SPI1_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; SPI_InitTypeDef SPI_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_SPI1, ENABLE); // SCK, MOSI配置为复用推挽输出 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); // MISO配置为浮空输入 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); // CS配置为普通推挽输出 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIOA, &GPIO_InitStructure); SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; SPI_InitStructure.SPI_Mode = SPI_Mode_Master; SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_32; SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; SPI_InitStructure.SPI_CRCPolynomial = 7; SPI_Init(SPI1, &SPI_InitStructure); SPI_Cmd(SPI1, ENABLE); }2.2 软件SPI实现方案
在某些情况下,硬件SPI可能无法满足需求(如引脚冲突或特殊时序要求),这时可以考虑软件模拟SPI:
void Soft_SPI_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // SCK, MOSI, CS配置为推挽输出 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOA, &GPIO_InitStructure); // MISO配置为浮空输入 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, &GPIO_InitStructure); // 初始状态 GPIO_ResetBits(GPIOA, GPIO_Pin_4); // CS低电平 GPIO_ResetBits(GPIOA, GPIO_Pin_5); // SCK低电平 } uint8_t Soft_SPI_Transfer(uint8_t data) { uint8_t i, receive = 0; for(i = 0; i < 8; i++) { if(data & 0x80) GPIO_SetBits(GPIOA, GPIO_Pin_7); // MOSI高 else GPIO_ResetBits(GPIOA, GPIO_Pin_7); // MOSI低 GPIO_SetBits(GPIOA, GPIO_Pin_5); // SCK上升沿 data <<= 1; receive <<= 1; if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6)) receive |= 0x01; GPIO_ResetBits(GPIOA, GPIO_Pin_5); // SCK下降沿 } return receive; }3. LTC6804驱动开发
3.1 芯片初始化配置
LTC6804在上电后需要进行初始化配置,主要包括:
- 设置ADC转换模式
- 配置GPIO功能
- 设置放电控制
- 配置参考电压
void LTC6804_Initialize(void) { uint8_t config[6] = {0}; // 配置寄存器设置 config[0] = 0x00; // GPIO控制 config[1] = 0x00; // 放电控制 config[2] = 0x00; // 保留位 config[3] = 0x00; // 保留位 config[4] = 0x00; // 保留位 config[5] = 0x00; // 保留位 // 写入配置寄存器 LTC6804_wrcfg(config); // 启动ADC自检(可选) LTC6804_adax(ADC_CONVERSION_MODE, AUX_CH_ALL); delay_ms(10); }3.2 电压采集函数实现
LTC6804的核心功能是电池电压采集,主要涉及两个关键函数:
- 启动转换函数:
LTC6804_adcv() - 读取结果函数:
LTC6804_rdcv()
void LTC6804_adcv(uint8_t MD, uint8_t DCP, uint8_t CH) { uint8_t cmd[2]; // 构建命令字 cmd[0] = 0x00; cmd[1] = 0x02; // ADCV命令 // 设置转换参数 cmd[0] |= (MD << 7) | (DCP << 4) | CH; // 发送命令 LTC6804_wrcomm(cmd); // 启动转换 LTC6804_stcomm(); } void LTC6804_rdcv(uint8_t total_ic, uint8_t cell_codes[][12]) { uint8_t rx_data[8]; uint16_t temp; uint8_t ic, cell; for(ic = 0; ic < total_ic; ic++) { // 读取寄存器组 LTC6804_rdcomm(0x04, rx_data); // 读取CVA寄存器组 // 解析电池电压数据 for(cell = 0; cell < 12; cell++) { if(cell < 6) { temp = (rx_data[0] << 8) + rx_data[1]; cell_codes[ic][cell] = temp; } else { temp = (rx_data[3] << 8) + rx_data[4]; cell_codes[ic][cell] = temp; } } } }4. 数据处理与校准
4.1 原始数据转换为实际电压
LTC6804返回的是原始ADC值,需要转换为实际电压:
float Convert_ADC_to_Voltage(uint16_t adc_value) { // LTC6804的ADC分辨率为16位,参考电压为3.0V // 电压计算公式:Vcell = (adc_value * 0.0001) * 3.0 return (float)adc_value * 0.0003f; } void Process_Cell_Voltages(uint8_t total_ic, uint16_t cell_codes[][12], float cell_voltage[][12]) { uint8_t ic, cell; for(ic = 0; ic < total_ic; ic++) { for(cell = 0; cell < 12; cell++) { cell_voltage[ic][cell] = Convert_ADC_to_Voltage(cell_codes[ic][cell]); } } }4.2 校准与精度提升
为了提高测量精度,可以考虑以下校准方法:
- 偏移校准:测量已知电压(如0V)时的ADC输出,计算偏移量
- 增益校准:使用精确的参考电压源校准增益误差
- 温度补偿:考虑环境温度对测量结果的影响
typedef struct { float offset; float gain; float temp_coeff; } Calibration_Params; Calibration_Params calib_params = { .offset = 0.002f, // 2mV偏移 .gain = 1.005f, // 0.5%增益误差 .temp_coeff = 0.0001f // 0.1mV/℃ }; float Apply_Calibration(uint16_t raw_adc, float temperature) { float voltage = (float)raw_adc * 0.0003f; // 应用校准 voltage = (voltage - calib_params.offset) * calib_params.gain; voltage -= (temperature - 25.0f) * calib_params.temp_coeff; return voltage; }5. 常见问题与调试技巧
5.1 SPI通信失败排查
当遇到SPI通信问题时,可以按照以下步骤排查:
检查硬件连接
- 确认所有连线正确无误
- 检查电源和地线连接
- 测量信号线是否有短路或断路
验证SPI信号
- 使用逻辑分析仪或示波器观察SCK、MOSI、MISO信号
- 确认时钟极性和相位设置正确
- 检查片选信号是否正常触发
测试寄存器读写
- 尝试写入配置寄存器后立即读取,验证数据一致性
- 检查CRC校验是否正确(如果启用)
5.2 电压测量异常处理
如果测量到的电压值异常,可以考虑以下解决方案:
数据不稳定:
- 增加电源滤波电容
- 在电池输入端添加RC滤波
- 多次测量取平均值
测量值偏差大:
- 检查参考电压是否准确
- 进行系统校准
- 确认电池连接可靠,接触电阻小
特定通道异常:
- 检查该通道对应的电池连接
- 确认没有超出量程
- 检查PCB布线是否有干扰
5.3 优化建议
在实际项目中,还可以考虑以下优化措施:
- 增加看门狗:防止程序跑飞导致数据采集中断
- 实现数据缓存:在RAM中缓存多组数据,防止丢失
- 添加异常检测:自动识别断线、短路等异常情况
- 优化功耗:在不采集时进入低功耗模式
- 增强EMC设计:在工业环境中提高抗干扰能力
6. 完整示例代码
下面是一个完整的示例,展示了如何使用STM32F103通过SPI驱动LTC6804进行电池电压采集:
#include "stm32f10x.h" #include "ltc6804.h" #include "spi.h" #include "uart.h" #include "delay.h" float cell_voltage[1][12]; // 存储12节电池的电压值 int main(void) { uint16_t cell_codes[1][12]; // 系统时钟初始化 SystemInit(); delay_init(); // 外设初始化 USART1_Init(115200); SPI1_Init(); LTC6804_Initialize(); printf("LTC6804 Battery Monitor System\r\n"); while(1) { // 启动电压转换 LTC6804_adcv(ADC_CONVERSION_MODE_NORMAL, DCP_DISABLED, CELL_CH_ALL); delay_ms(10); // 等待转换完成 // 读取电压数据 LTC6804_rdcv(1, cell_codes); // 转换为实际电压值 Process_Cell_Voltages(1, cell_codes, cell_voltage); // 打印电压数据 for(int i = 0; i < 12; i++) { printf("Cell %d: %.3fV\r\n", i+1, cell_voltage[0][i]); } printf("-------------------\r\n"); delay_ms(1000); // 每秒采集一次 } }在实际项目中,我发现LTC6804的SPI通信对时序要求较为严格,特别是在菊花链模式下。当连接多个LTC6804时,建议降低SPI时钟频率至1MHz以下,并确保CS信号在数据传输之间有足够的时间间隔。另外,在PCB布局时,应尽量缩短SPI信号线的长度,并做好阻抗匹配,这样可以显著提高通信的可靠性。