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:便宜,但性能一般,适合成本敏感的项目
电路设计时要注意几点:
- 采样电阻要尽量靠近电机相线,减少走线电感
- 差分走线要等长、等宽,避免引入共模噪声
- 运放输出端加一个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采样和滤波,说白了就是「硬件打基础,软件做优化」。电路设计不好,软件再牛也救不回来;电路设计好了,滤波就是锦上添花。
各位在实际项目中,建议先从均值滤波开始,等系统稳定了,再考虑上卡尔曼滤波。别一上来就搞复杂的,容易翻车。