3、编码器接口与电机测速:正交编码器原理、STM32定时器编码器模式、速度采集与滤波

做机器人运动控制,绕不开一个核心问题:电机到底转了多少?

你想想看,你给电机发了PWM,它转了。但实际转了多少圈?有没有丢步?速度稳不稳?这些问题,光靠开环控制是回答不了的。这时候,编码器就派上用场了。

我个人习惯把编码器比作电机的「眼睛」。没有它,你的控制器就是个盲人。今天我们就来聊聊这个「眼睛」是怎么工作的,以及如何在STM32上把它用好。

3.1 正交编码器的工作原理

正交编码器,说白了就是能告诉你位置和方向的传感器。它输出两路方波信号,通常叫A相和B相。

核心概念:两路信号相位差90°,通过判断谁先跳变,就能知道电机是正转还是反转。

我在项目中遇到过一个问题:电机抖动时,编码器会输出错误的脉冲。后来发现,是没理解正交信号的真正含义。

3.1.1 信号时序与判向逻辑

来看一个典型的正交信号时序:

  • 正转:A相上升沿时,B相为低电平
  • 反转:A相上升沿时,B相为高电平
  • 四倍频:利用A、B相的上升沿和下降沿,一个周期可以计4个脉冲

嗯,这里要注意。四倍频虽然提高了分辨率,但也放大了噪声。我一般建议:如果电机振动大,先用二倍频,稳定后再切四倍频。

信号边沿 正转计数 反转计数
A上升沿,B=0 +1 -1
A下降沿,B=1 +1 -1
B上升沿,A=1 +1 -1
B下降沿,A=0 +1 -1

这张表你记牢了,编码器接口的底层逻辑就通了。

3.2 STM32定时器编码器模式

STM32的定时器有个隐藏技能——编码器模式。说白了,就是硬件帮你把A、B相的逻辑判断、计数、方向识别全干了。你只需要读寄存器就行。

我记得第一次用这个功能时,还傻傻地用外部中断去读编码器。后来发现定时器自带这个模式,真是相见恨晚。

3.2.1 模式配置与寄存器

STM32的编码器模式有三种:

  • 模式1:只在TI1(A相)边沿计数
  • 模式2:只在TI2(B相)边沿计数
  • 模式3:在TI1和TI2所有边沿计数(四倍频)

我一般用模式3,分辨率最高。但要注意,计数器溢出问题。

经验之谈:定时器是16位的,最大计数值65535。如果电机转得快,很容易溢出。我习惯把定时器设为中心对齐模式,配合DMA定期读取,避免丢数。

配置代码其实不复杂:

// 以STM32F407 TIM3为例
TIM_EncoderInterfaceConfig(TIM3, TIM_ENCODERMODE_TI12, 
                           TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
TIM_ICStructInit(&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM_ICInitStructure.TIM_ICFilter = 0x0F;  // 滤波,后面会讲
TIM_ICInit(TIM3, &TIM_ICInitStructure);

// 同样的配置给Channel 2
TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
TIM_ICInit(TIM3, &TIM_ICInitStructure);

// 使能定时器
TIM_Cmd(TIM3, ENABLE);

这段代码的核心是 TIM_ENCODERMODE_TI12,告诉定时器:你帮我盯着A和B,我自己算方向。

3.2.2 计数器读取与方向判断

读编码器值很简单:

int16_t encoder_value = TIM_GetCounter(TIM3);

方向呢?看 TIM_SMCR 寄存器的 DIR 位。但我不建议频繁读寄存器,效率低。我一般这样处理:

// 用上一次值和当前值比较,判断方向
static int16_t last_enc = 0;
int16_t curr_enc = TIM_GetCounter(TIM3);
int16_t delta = curr_enc - last_enc;

if (delta > 0) {
    // 正转
} else if (delta < 0) {
    // 反转
}
last_enc = curr_enc;

这样做的好处是:一次读取,既得位置,又得方向。

3.3 速度采集与滤波

位置有了,速度怎么算?

最简单的办法:单位时间内的脉冲增量。但实际做起来,坑不少。

注意:直接差分算速度,噪声会被放大。尤其是低速时,脉冲间隔长,算出来的速度一跳一跳的。我曾经因为这个,让机器人走直线走出了「醉汉步」。

3.3.1 M法、T法、M/T法

测速有三种经典方法:

方法 原理 适用场景 缺点
M法 固定时间,数脉冲数 中高速 低速时精度差
T法 固定脉冲数,测时间 低速 高速时更新慢
M/T法 结合两者 宽速域 实现稍复杂

我个人的习惯是:机器人关节电机用M/T法,因为要兼顾低速启停和高速运行。如果是轮式机器人,M法就够了。

3.3.2 滤波算法实战

算出来的速度,必须滤波。不然控制器的输出会抖得像筛子。

我常用的三种滤波:

  1. 限幅滤波:如果当前速度与上次速度的差值超过阈值,就丢弃。适合剔除突发噪声。
  2. 滑动平均:取最近N个速度值的平均。N一般取4~8,太大延迟高。
  3. 一阶低通滤波:公式简单,计算量小。

一阶低通的代码:

#define ALPHA 0.2f  // 滤波系数,越小越平滑

float lowpass_filter(float input, float last_output) {
    return ALPHA * input + (1.0f - ALPHA) * last_output;
}

这个 ALPHA 怎么调?我一般先设0.2,看波形。如果噪声大,降到0.1;如果响应慢,升到0.3。没有固定值,得试。

调试技巧:用DAC输出滤波前后的速度值,接示波器看。比串口打印直观一百倍。我每次调滤波参数,都是这么干的。

3.4 本章知识结构

下面这张图,把编码器测速的完整链路画出来了。从硬件信号到软件滤波,每一步都环环相扣。

编码器测速知识结构图 编码器测速知识结构图 正交编码器 A相 / B相 / Z相 信号特性 相位差90° / 四倍频 电气接口 推挽 / 开漏 / 差分 STM32定时器编码器模式 模式1(TI1) / 模式2(TI2) / 模式3(TI1+TI2) | 16位计数器 | DIR方向位 M法(测频法) 固定时间ΔT,数脉冲数 T法(测周法) 固定脉冲数M,测时间 M/T法(综合法) 高低速兼顾 速度滤波 限幅滤波 | 滑动平均 | 一阶低通滤波(ALPHA系数) 关键链路:编码器信号 → 定时器捕获 → 速度计算 → 滤波输出 → 控制器使用

从这张图你能看到,整个测速链路其实就四个环节。每个环节都有坑,但也都对应着成熟的解决方案。