4. ADC采样与调理:电流采样电路设计、ADC配置与数据滤波

各位同学,咱们今天聊点实在的。FOC控制里,电流采样是第一步,也是最容易出幺蛾子的一步。我见过太多项目,算法写得天花乱坠,结果一上电,电流波形跟心电图似的——全是噪声。说白了,ADC采回来的数据不准,后面什么PI调节、SVPWM全是白搭。

这一章,咱们就把电流采样这件事掰开揉碎了讲。从电路设计到ADC配置,再到数据滤波,一条龙搞定。

4.1 电流采样电路设计

先说说采样电阻。我个人习惯用毫欧级的采样电阻,比如10mΩ或者5mΩ。为什么?因为FOC里电流通常不大,几安培到几十安培,用大电阻会发热严重,而且压降太大影响电机效率。

关键点:采样电阻的功率要留够余量。比如电机峰值电流10A,采样电阻10mΩ,那瞬时功率就是P = I²R = 100 * 0.01 = 1W。我建议选2W以上的电阻,别问我为什么——我曾经在项目里用1W电阻,结果跑了几分钟就冒烟了。

采样电阻后面必须接差分放大器。为什么?因为电机相线上的共模电压很高,尤其是PWM开关瞬间,电压跳变能达到几十伏。单端放大器根本扛不住。

常用的运放型号有:

  • INA240:TI的,带宽高,共模抑制比好,我项目里用得最多
  • AD8418:ADI的,噪声低,适合高精度场合
  • LMV358:便宜,但性能一般,适合成本敏感的项目

电路设计时要注意几点:

  1. 采样电阻要尽量靠近电机相线,减少走线电感
  2. 差分走线要等长、等宽,避免引入共模噪声
  3. 运放输出端加一个RC低通滤波,截止频率一般设在1MHz左右

我的经验:采样电路布局时,把模拟地和功率地分开,单点接地。我曾经在一个项目里没注意这个,结果ADC读数跳得跟蹦迪似的,查了两天才发现是地线环路的问题。

4.2 ADC配置:采样率、分辨率、触发源

ADC配置是FOC控制的核心环节。配置不对,后面全是白费功夫。

4.2.1 采样率

FOC控制里,电流采样频率一般和PWM频率同步。比如PWM频率是20kHz,那电流采样频率也是20kHz。为什么?因为FOC的电流环更新频率通常等于PWM频率,采样频率低了,控制跟不上;高了,CPU负担太重。

我建议的采样率范围:

  • 低速电机(几百瓦):10kHz - 20kHz
  • 中速电机(几千瓦):20kHz - 40kHz
  • 高速电机(几万转):40kHz - 100kHz

注意:采样率不是越高越好。采样率太高,ADC转换时间变短,精度会下降。而且CPU要花更多时间处理中断,反而影响控制性能。

4.2.2 分辨率

ADC分辨率决定了电流采样的精度。12位ADC是主流,16位ADC用于高精度场合。

举个例子:

  • 12位ADC:满量程4096,假设参考电压3.3V,采样电阻10mΩ,那电流分辨率 = 3.3V / 4096 / 0.01Ω ≈ 80mA
  • 16位ADC:满量程65536,同样条件下,电流分辨率 ≈ 5mA

我个人习惯:

  • 普通伺服电机:12位够用
  • 精密控制(如机器人关节):16位起步
  • 电池供电设备:考虑功耗,12位即可

4.2.3 触发源

ADC采样时机非常关键。FOC里,电流采样必须在PWM周期的特定时刻进行,否则会采到开关噪声。

常用的触发方式:

  • PWM定时器触发:最常用。在PWM载波波峰或波谷触发ADC,此时开关噪声最小
  • 外部中断触发:用于特殊场合,比如需要同步外部信号
  • 软件触发:不推荐,因为软件触发有延迟,采样时机不准

避坑指南:我曾经在一个项目里用软件触发采样,结果电流波形上全是毛刺。后来改成PWM定时器触发,毛刺立刻消失了。记住:采样时机比采样精度更重要。

下面是一个典型的ADC配置代码(以STM32为例):

// ADC配置:12位分辨率,PWM定时器触发
void ADC_Config(void) {
    ADC_InitTypeDef ADC_InitStruct;
    ADC_CommonInitTypeDef ADC_CommonInitStruct;
    
    // 使能ADC时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
    
    // ADC通用配置
    ADC_CommonInitStruct.ADC_Mode = ADC_Mode_Independent;
    ADC_CommonInitStruct.ADC_Prescaler = ADC_Prescaler_Div4;
    ADC_CommonInitStruct.ADC_DMAAccessMode = ADC_DMAAccessMode_Disabled;
    ADC_CommonInitStruct.ADC_TwoSamplingDelay = ADC_TwoSamplingDelay_5Cycles;
    ADC_CommonInit(&ADC_CommonInitStruct);
    
    // ADC1配置
    ADC_InitStruct.ADC_Resolution = ADC_Resolution_12b;  // 12位分辨率
    ADC_InitStruct.ADC_ScanConvMode = DISABLE;
    ADC_InitStruct.ADC_ContinuousConvMode = DISABLE;     // 单次转换
    ADC_InitStruct.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_Rising;
    ADC_InitStruct.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T1_CC1;  // PWM触发
    ADC_InitStruct.ADC_DataAlign = ADC_DataAlign_Right;
    ADC_InitStruct.ADC_NbrOfChannel = 2;
    ADC_Init(ADC1, &ADC_InitStruct);
    
    // 配置采样通道
    ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_3Cycles);
    ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_3Cycles);
    
    // 使能ADC
    ADC_Cmd(ADC1, ENABLE);
}

4.3 采样数据滤波

ADC采回来的数据,直接拿去用?别闹。电机运行环境里,电磁干扰、PWM开关噪声、采样电阻热噪声,都会让数据变得乱七八糟。必须滤波。

常用的滤波方法有三种:均值滤波、中值滤波、卡尔曼滤波。我一个个说。

4.3.1 均值滤波

最简单,也最常用。把连续N次采样值加起来,除以N。

优点:计算量小,适合实时性要求高的场合。
缺点:对脉冲噪声不敏感,一个大的毛刺会污染整个平均值。

代码实现:

// 均值滤波
#define FILTER_LEN 8
uint16_t filter_buf[FILTER_LEN];
uint8_t filter_index = 0;

uint16_t MeanFilter(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-16之间。太短了滤波效果差,太长了延迟大。我在伺服电机项目里一般用8点均值,效果不错。

4.3.2 中值滤波

把N次采样值排序,取中间值。对脉冲噪声有奇效。

优点:能有效去除毛刺噪声。
缺点:需要排序,计算量大,实时性差。

代码实现:

// 中值滤波
#define MEDIAN_LEN 5
uint16_t MedianFilter(uint16_t new_value) {
    uint16_t buf[MEDIAN_LEN];
    uint16_t temp;
    uint8_t i, j;
    
    // 更新缓冲区
    for(i = 0; i < MEDIAN_LEN - 1; i++) {
        buf[i] = buf[i + 1];
    }
    buf[MEDIAN_LEN - 1] = new_value;
    
    // 冒泡排序
    for(i = 0; i < MEDIAN_LEN - 1; i++) {
        for(j = 0; j < MEDIAN_LEN - 1 - i; j++) {
            if(buf[j] > buf[j + 1]) {
                temp = buf[j];
                buf[j] = buf[j + 1];
                buf[j + 1] = temp;
            }
        }
    }
    
    return buf[MEDIAN_LEN / 2];  // 返回中间值
}

注意:中值滤波的窗口长度建议选奇数,比如3、5、7。偶数的话,中间值不好取。另外,排序算法可以用更快的,比如快速排序,但嵌入式里资源有限,冒泡排序够用。

4.3.3 卡尔曼滤波

这个就高级了。卡尔曼滤波能根据系统模型和测量噪声,动态估计真实值。说白了,它不只是滤波,还能预测。

优点:精度高,能处理动态变化的噪声。
缺点:计算量大,需要调参,对新手不友好。

卡尔曼滤波的核心公式:

  • 预测:x̂ₖ = A·x̂ₖ₋₁ + B·uₖ
  • 预测协方差:Pₖ = A·Pₖ₋₁·Aᵀ + Q
  • 卡尔曼增益:Kₖ = Pₖ·Hᵀ·(H·Pₖ·Hᵀ + R)⁻¹
  • 更新:x̂ₖ = x̂ₖ + Kₖ·(zₖ - H·x̂ₖ)
  • 更新协方差:Pₖ = (I - Kₖ·H)·Pₖ

代码实现(简化版):

// 一维卡尔曼滤波
typedef struct {
    float Q;      // 过程噪声协方差
    float R;      // 测量噪声协方差
    float P;      // 估计误差协方差
    float K;      // 卡尔曼增益
    float x;      // 状态估计值
} KalmanFilter_t;

void Kalman_Init(KalmanFilter_t *kf, float Q, float R, float init_x) {
    kf->Q = Q;
    kf->R = R;
    kf->P = 1.0f;
    kf->x = init_x;
}

float Kalman_Update(KalmanFilter_t *kf, float measurement) {
    // 预测
    kf->P = kf->P + kf->Q;
    
    // 卡尔曼增益
    kf->K = kf->P / (kf->P + kf->R);
    
    // 更新
    kf->x = kf->x + kf->K * (measurement - kf->x);
    
    // 更新协方差
    kf->P = (1.0f - kf->K) * kf->P;
    
    return kf->x;
}

我的经验:卡尔曼滤波的Q和R参数需要根据实际系统调试。Q越大,滤波器越相信测量值;R越大,滤波器越相信预测值。我一般先设Q=0.01,R=1,然后根据波形调整。调参是个耐心活,别急。

4.4 三种滤波方法对比

方法 计算量 延迟 抗脉冲噪声 适用场景
均值滤波 实时性要求高的场合
中值滤波 有脉冲噪声的场合
卡尔曼滤波 高精度、动态噪声场合

我个人习惯:

  • 普通电机控制:均值滤波就够了
  • 有刷电机或继电器干扰大的场合:中值滤波
  • 精密伺服或机器人:卡尔曼滤波

4.5 本章知识体系

下面这张图,把本章的核心逻辑串起来了。从电流采样电路到ADC配置,再到数据滤波,每一步都有坑,每一步都有技巧。

ADC采样与调理知识体系 电流采样电路 采样电阻 + 差分运放 ADC配置 采样率 / 分辨率 / 触发源 数据滤波 均值 / 中值 / 卡尔曼 采样电阻选型(10mΩ) 差分放大器(INA240) RC低通滤波(1MHz) 采样率:10kHz-100kHz 分辨率:12位/16位 触发源:PWM定时器 均值滤波(8点) 中值滤波(5点) 卡尔曼滤波(Q/R调参) 核心原则 采样时机 > 采样精度 > 滤波效果 先保证采样时刻正确,再追求精度和滤波

嗯,这一章的内容就到这里。ADC采样和滤波,说白了就是「硬件打基础,软件做优化」。电路设计不好,软件再牛也救不回来;电路设计好了,滤波就是锦上添花。

各位在实际项目中,建议先从均值滤波开始,等系统稳定了,再考虑上卡尔曼滤波。别一上来就搞复杂的,容易翻车。

专注资料整理