第四章 电压采集与AFE驱动:从芯片到数据的完整链路

各位同学,今天我们进入BMS系统最核心的环节——电压采集。说白了,电池管理系统的所有决策,都建立在准确的电压数据之上。如果电压都测不准,那后面的SOC估算、均衡控制全是空中楼阁。

这一章,我会以ADI的AD7280A为例,带大家走通从AFE芯片工作原理、SPI通信、滤波算法到阈值设定的完整链路。嗯,这些都是我当年踩过坑的地方,希望能帮你们少走弯路。

4.1 AFE芯片工作原理——以AD7280A为例

AFE芯片,全称是模拟前端。它的任务很简单:把电池的模拟电压,变成数字信号送给MCU。但实际做起来,坑不少。

AD7280A是一款6通道的电池监控芯片,支持最多6节串联电池的电压采集。我最早用这颗芯片时,觉得它挺简单的,结果第一次调试就遇到了通信失败的问题——后来发现是菊花链的配置没搞对。

4.1.1 芯片内部架构

AD7280A内部主要包含这几个模块:

  • 多路复用器(MUX):轮流选通6个电池通道
  • Σ-Δ ADC:14位分辨率,转换时间约7μs
  • 内部基准:2.5V参考电压,精度±0.1%
  • SPI接口:支持菊花链拓扑,最多可级联32颗芯片
  • 辅助ADC输入:可测量温度或电流

这里有个关键点:AD7280A的ADC是逐次逼近型的,不是积分型。这意味着它对噪声比较敏感。我在项目中遇到过,如果PCB布局不好,采集到的电压会跳来跳去,根本没法用。

4.1.2 电压采集流程

一次完整的电压采集,大致分三步:

  1. 通道选择:通过SPI写入控制寄存器,选择要采集的电池通道
  2. 启动转换:设置转换控制位,ADC开始工作
  3. 读取结果:等待转换完成(约7μs),通过SPI读取数据寄存器

你可能会问:为什么要分三步?不能一次搞定吗?

其实有些AFE芯片确实支持自动扫描模式,但AD7280A需要手动触发。我个人习惯用定时器中断,每10ms触发一次转换,这样时序更可控。

4.2 SPI通信时序——别让时序成为你的噩梦

SPI通信,看起来简单,但实际调试时最容易出问题。我见过太多工程师,代码写好了,逻辑也对,就是通信不通——最后发现是时序参数没配对。

4.2.1 AD7280A的SPI时序要求

先看关键参数:

参数 最小值 典型值 最大值 单位
SCLK频率 - 1 10 MHz
CS低电平时间 50 - - ns
数据建立时间 20 - - ns
数据保持时间 20 - - ns
SCLK高电平时间 40 - - ns
SCLK低电平时间 40 - - ns

注意看,SCLK频率最大10MHz。我曾经为了追求速度,把SPI时钟设到12MHz,结果数据全是错的。后来老老实实降到8MHz,一切正常。嗯,有时候慢就是快。

4.2.2 通信协议细节

AD7280A的SPI帧格式是固定的:

  • 每次传输32位数据
  • 高8位是命令/地址
  • 低24位是数据
  • MSB优先传输

这里有个容易忽略的点:AD7280A的SPI模式是Mode 1(CPOL=0, CPHA=1)。什么意思?就是SCLK空闲时为低电平,数据在SCLK的下降沿采样。我见过有人配成Mode 0,结果死活读不到正确数据。

下面是一个典型的写寄存器代码:

// AD7280A写寄存器函数
// reg_addr: 寄存器地址 (8位)
// data: 要写入的数据 (24位)
void AD7280A_WriteReg(uint8_t reg_addr, uint32_t data)
{
    uint32_t tx_data = 0;
    
    // 构建32位数据帧
    // 高8位: 0x80 | reg_addr (写命令)
    // 低24位: data
    tx_data = ((0x80 | reg_addr) << 24) | (data & 0xFFFFFF);
    
    // 拉低CS
    HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET);
    
    // 发送32位数据
    HAL_SPI_Transmit(&hspi1, (uint8_t*)&tx_data, 4, HAL_MAX_DELAY);
    
    // 拉高CS
    HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET);
    
    // 等待至少50ns
    delay_ns(100);
}
⚠️ 重要提醒: 每次SPI传输后,必须等待至少50ns才能进行下一次传输。这是AD7280A内部处理数据的时间。我见过有人连续快速发送命令,结果芯片直接死机了。

4.3 电压采集滤波算法——让数据不再跳变

原始电压数据,直接拿来用?别闹。电池电压本身就有噪声,加上ADC的量化误差,不滤波根本没法用。

我常用的滤波方法有三种,各有适用场景:

4.3.1 中值滤波

连续采集5次,去掉最大值和最小值,取中间3个的平均。这个方法对脉冲噪声特别有效。我在项目中遇到过,电池连接器接触不良时,偶尔会出现一个离谱的电压值,中值滤波直接把它干掉了。

4.3.2 滑动平均滤波

维护一个长度为N的队列,每次新数据入队,计算队列平均值。N一般取4-16。这个方法平滑效果好,但会有延迟。N越大,延迟越大。

// 滑动平均滤波
#define FILTER_LEN 8

static uint16_t filter_buf[FILTER_LEN] = {0};
static uint8_t filter_index = 0;

uint16_t MovingAverageFilter(uint16_t new_value)
{
    uint32_t sum = 0;
    uint8_t i;
    
    // 更新缓冲区
    filter_buf[filter_index] = new_value;
    filter_index = (filter_index + 1) % FILTER_LEN;
    
    // 计算平均值
    for(i = 0; i < FILTER_LEN; i++)
    {
        sum += filter_buf[i];
    }
    
    return (uint16_t)(sum / FILTER_LEN);
}

4.3.3 一阶低通滤波

公式很简单:output = α * input + (1-α) * last_output。α一般取0.1-0.3。这个方法计算量小,适合实时性要求高的场景。

我个人习惯:在BMS中,电压采集用滑动平均滤波(N=8),温度采集用一阶低通滤波(α=0.2)。为什么?因为电压变化快,需要快速响应;温度变化慢,平滑更重要。

4.4 过压/欠压阈值设定——保护电池的第一道防线

阈值设定,说白了就是告诉AFE芯片:电压超过多少算过压,低于多少算欠压。这个值设错了,轻则保护失效,重则电池报废。

4.4.1 阈值计算

以磷酸铁锂电池为例:

  • 标称电压:3.2V
  • 充电截止电压:3.65V
  • 放电截止电压:2.5V

但实际设定时,不能直接用这些值。为什么?因为电池有内阻,大电流充放电时会有压降。我曾经遇到过,客户说电池过压保护了,但实测电压才3.6V——后来发现是充电电流太大,电池端电压虚高。

我的经验是:

参数 理论值 实际设定值 说明
过压阈值 3.65V 3.70V 留0.05V余量,防止误触发
过压恢复 3.55V 3.50V 回滞0.2V,防止反复触发
欠压阈值 2.50V 2.45V 留0.05V余量
欠压恢复 2.60V 2.65V 回滞0.2V

4.4.2 AD7280A的阈值配置

AD7280A内部有专门的比较器,可以硬件实现过压/欠压检测。配置方法很简单:

// 配置过压阈值 (以3.70V为例)
// AD7280A的阈值寄存器格式:
// 高8位: 地址
// 低24位: 阈值值 (单位: 19.5mV/LSB)
uint32_t ov_threshold = (uint32_t)(3.70 / 0.0195);  // 约190

// 写入过压阈值寄存器 (地址0x04)
AD7280A_WriteReg(0x04, ov_threshold);

// 配置欠压阈值 (以2.45V为例)
uint32_t uv_threshold = (uint32_t)(2.45 / 0.0195);  // 约126

// 写入欠压阈值寄存器 (地址0x05)
AD7280A_WriteReg(0x05, uv_threshold);
💡 实用技巧: 阈值设定后,一定要做回滞处理。没有回滞的阈值,就像没有刹车的车——电池电压在阈值附近波动时,保护会频繁触发和恢复,严重影响系统稳定性。

4.5 实战经验总结

好了,这一章的内容就到这里。最后分享几个我踩过的坑:

  • SPI通信不稳定:检查CS引脚的时序,确保每次传输后CS拉高时间足够长
  • 电压采集跳变:先检查电源噪声,再检查滤波算法是否生效
  • 阈值误触发:确认是否做了回滞处理,以及阈值是否考虑了电池内阻

下一章,我们会讲电流采集与库仑计。嗯,那又是一个容易踩坑的地方。各位先把这一章消化好,有问题随时交流。