第四节: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以上,不然高频电流分量会被衰减。

警告: 运放供电一定要和MCU的ADC参考电压匹配。我曾经见过有人用5V供电的运放,但ADC参考电压是3.3V,结果信号削顶了,电流波形直接失真。

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干预。

小技巧: 如果MCU支持,可以配置ADC为连续转换模式,配合DMA自动搬运数据。这样CPU只需要在DMA中断里读取结果,效率非常高。我在一个48kHz的FOC项目里就是这么干的,CPU负载不到10%。

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参数整定,那个更有意思。