第四节:ADC采样与信号调理
各位同学,今天我们来聊聊电机控制里一个特别关键的环节——ADC采样。说实话,我见过不少工程师,算法写得飞起,结果一上电机就抖得不行,查来查去,最后发现是电流采样出了问题。嗯,这节内容,咱们就把这个坑填上。
4.1 电流采样电阻与运放电路设计
先说说采样电阻。电机控制里最常用的就是分流电阻法,说白了就是在电流回路上串一个小电阻,测它两端的电压。这个电阻怎么选?我个人的习惯是看三个参数:阻值、功率、温漂。
- 阻值:不能太大,否则发热严重;也不能太小,否则信号太弱。一般取几毫欧到几十毫欧。比如10mΩ,10A电流时压降100mV,刚好。
- 功率:P = I²R,这个要算清楚。我遇到过有人用0805封装的电阻扛5A电流,结果直接冒烟了。
- 温漂:最好选±50ppm/℃以内的,不然温度一变化,电流检测就偏了。
采样电阻两端的信号通常只有几十到几百毫伏,MCU的ADC直接采肯定不行。这时候就需要运放来放大。我常用的方案是差分放大电路,比如下面这个结构:
// 典型差分放大电路参数
// R1 = R3 = 1kΩ, R2 = R4 = 10kΩ
// 增益 = R2/R1 = 10倍
// 输入:采样电阻两端电压(±100mV)
// 输出:放大后电压(±1V),送入ADC
这里有个细节要注意:运放的共模输入范围。电机驱动是高压环境,共模电压可能很高。我建议选轨到轨输入的运放,比如LMV321、MCP6001这类。另外,运放的带宽要够,至少1MHz以上,不然高频电流分量会被衰减。
4.2 三相电流同步采样
电机控制里,三相电流必须同时采样。为什么?因为你要算Clark变换和Park变换,如果三相电流不是同一时刻采的,算出来的角度和转矩就不准。说白了,电机就会抖。
怎么实现同步?硬件上,我习惯用三个采样电阻分别放在三相下桥臂,配合三个运放通道。然后让MCU的ADC同时触发三个通道的采样。有些MCU有同步采样功能,比如STM32G4的ADC可以同时启动多个注入组。
软件上,要注意采样时刻。我一般把采样点放在PWM的中间时刻,也就是计数器的中点。这时候电流纹波最小,采样值最稳定。你想想看,如果放在PWM边沿,开关噪声正好最大,采出来的数据根本没法用。
// 三相电流同步采样配置示例(伪代码)
// 假设使用ADC1的注入组,三个通道分别对应U、V、W相
ADC1->JSQR = (3 << 20) | // 注入序列长度=3
(ADC_CH_U << 15) | // 第1个转换:U相
(ADC_CH_V << 10) | // 第2个转换:V相
(ADC_CH_W << 5); // 第3个转换:W相
// 触发源设置为TIM1的TRGO事件(PWM中点触发)
ADC1->CR2 |= ADC_CR2_EXTEN_0 | ADC_CR2_EXTSEL_2; // TIM1 TRGO触发
4.3 ADC触发源配置
ADC什么时候开始采?这个触发源配置很关键。我常用的触发源有几种:
| 触发源 | 适用场景 | 注意事项 |
|---|---|---|
| 定时器TRGO事件 | FOC控制,PWM中点采样 | 需要和PWM频率同步 |
| 定时器CC事件 | 霍尔传感器换相时刻 | 适合方波控制 |
| 外部GPIO触发 | 特殊工况,如过流保护 | 响应快,但占用引脚 |
| 软件触发 | 调试阶段,手动采样 | 不推荐用于实时控制 |
我个人最常用的是定时器TRGO事件。具体做法是:让PWM定时器在计数到中点时产生一个TRGO信号,直接触发ADC开始转换。这样采样时刻和PWM周期严格同步,不需要CPU干预。
4.4 过采样与均值滤波
采回来的数据直接能用吗?说实话,不太行。电机驱动环境里,开关噪声、地弹、电源纹波都会污染ADC结果。我一般会做两件事:过采样和均值滤波。
过采样:说白了就是多采几次,然后取平均。比如你采16次,取平均,等效分辨率可以提高2位。但要注意,过采样次数不能太多,否则会引入延迟。我一般取4次或8次,效果不错。
// 过采样与均值滤波示例
#define OVERSAMPLE_RATE 8
uint16_t adc_oversample(uint16_t *buffer, uint8_t channel) {
uint32_t sum = 0;
for (int i = 0; i < OVERSAMPLE_RATE; i++) {
sum += adc_read(channel); // 连续读取8次
}
return (uint16_t)(sum / OVERSAMPLE_RATE); // 取平均
}
均值滤波:这个更常用。我维护一个环形缓冲区,存最近N次采样值,每次取平均。N一般取4到16。N太小,滤波效果差;N太大,响应变慢。我习惯取8,平衡了噪声抑制和动态响应。
// 滑动均值滤波
#define FILTER_LEN 8
typedef struct {
uint16_t buffer[FILTER_LEN];
uint8_t index;
uint32_t sum;
} sliding_avg_t;
uint16_t sliding_avg_filter(sliding_avg_t *filter, uint16_t new_sample) {
filter->sum -= filter->buffer[filter->index];
filter->buffer[filter->index] = new_sample;
filter->sum += new_sample;
filter->index = (filter->index + 1) % FILTER_LEN;
return (uint16_t)(filter->sum / FILTER_LEN);
}
嗯,这节内容就到这里。ADC采样看似简单,但实际项目中坑不少。我当年第一次做电机驱动,就是没注意采样时刻,结果电流波形全是毛刺,查了三天才发现是触发源配错了。希望大家能少走弯路。
下一节我们聊聊电流环的PI参数整定,那个更有意思。