做电机控制,第一步往往是「知道转子转了没有、转多快、往哪转」。你在轴上贴个增量编码器,出来 A、B 两路相位差 90 度的方波。手动去读两个 IO 的电平变化、数边沿、判方向,中断能给你炸到怀疑人生,还容易漏脉冲。
STM32 的定时器自带编码器接口(Encoder Mode),硬件帮你数脉冲、判方向,CPU 只需要在想看的时候读一下计数器。这篇文章把正交解码原理、4 倍频、用定时器数脉冲算转速讲清楚,顺带把最常踩的坑列出来。
正交编码到底在算什么
增量编码器 A、B 两路方波,相位差 90 度。正转时 A 领先 B 90 度,反转时 B 领先 A 90 度。只看两路电平的跳变顺序,就能判断方向;数跳变次数,就能知道转了多少。
定时器编码器模式的逻辑是:把 A、B 两路接到同一个定时器的两个通道(TI1、TI2),在 A 的上升沿、下降沿、B 的上升沿、下降沿都计数。这样一个机械脉冲周期里数到 4 次边沿,叫4 倍频,分辨率直接翻四倍。

计数器是带符号的:正转递增,反转递减。溢出回绕也无所谓,读出来的值就是相对位移,减一下就知道这段时间转了多少。

CubeMX 与代码
CubeMX 里把对应定时器(TIM2~TIM5 多数支持)的 Combined Channels 选成 Encoder Mode。两个通道映射到 TI1、TI2,极性一般选 TI1FP1 和 TI2FP2(或都不反向,看编码器相位)。计数器周期设成足够大(比如 16 位就 65535,32 位定时器更大),避免短时间溢出。
c
// 启动编码器接口
HAL_TIM_Encoder_Start(&htim2, TIM_CHANNEL_ALL);
// 主循环里读取
int32_t cnt = (int16_t)__HAL_TIM_GET_COUNTER(&htim2); // 16位带符号
如果定时器是 32 位(如 TIM2、TIM5 部分型号),计数器直接当 int32_t 读,溢出周期更长,适合高速长时间累积。
怎么算转速
编码器的脉冲数对应机械角度。比如编码器每圈 1000 线,4 倍频后每圈 4000 个计数。测速就是「单位时间内的计数变化量 ÷ 每圈计数 ÷ 时间」:
c
static int32_t last_cnt = 0;
static uint32_t last_tick = 0;
void measure_speed(void)
{
int32_t cnt = (int32_t)__HAL_TIM_GET_COUNTER(&htim2);
uint32_t now = HAL_GetTick();
float dt = (now - last_tick) / 1000.0f; // 秒
int32_t delta = cnt - last_cnt; // 这段时间计数的变化
float rpm = (delta / 4000.0f) / dt * 60.0f; // 每圈4000计数
last_cnt = cnt;
last_tick = now;
// rpm 就是当前转速
}
关键点:delta 要用带符号的减法。正转和反转的计数器是连续增减的,直接减就能得到净位移,不用分别数正负。用定时中断(比如每 10ms 调一次)周期性算,就得到平稳的转速反馈,这正是 FOC 电流环之外的速度环输入。

十个坑,测速乱基本都在这
-
两个通道接到同一个定时器。A、B 必须进同一个 TIM 的 CH1/CH2,用两个不同定时器各自数一路是数不出方向和 4 倍频的。
-
极性配反导致方向反。正转读出来是负、反转是正。这不是错,是编码器接线和极性设置的问题。把 TI1、TI2 极性都翻转,或者直接对读数取负,让正转对应正数即可。
-
计数器溢出没处理。16 位计数器从 65535 回 0 或 0 回 65535,直接减 delta 会算出巨大负值或正值。用带符号类型(int16_t 读再转 int32_t),回绕减法仍正确;或者定时清零、用增量累计。
-
每圈计数算错。编码器标 1000 线,你当 1000 计数算转速,漏了 4 倍频,转速算出来只有实际四分之一。标称线数 × 4 才是每圈计数,除非你特意关掉倍频。
-
测速周期和电机响应不匹配。10ms 测一次和 1 秒测一次,得到的转速噪声和延迟完全不同。慢速用长周期平滑,快速用短周期跟手,别一刀切。
-
delta 用了无符号类型。cnt 是无符号读出来,相减结果被当成无符号,反转时溢出成大正数,转速符号全错。读出来先转成有符号再减。
-
编码器电源和地没共。编码器和 MCU 不共地,A、B 信号参考点漂移,电平识别错,计数乱跳。编码器 GND 必须和板子 GND 连起来。
-
长线传输没做差分或缓冲。编码器线拉几米,普通推挽信号受干扰,边沿抖动多计脉冲。距离远用 RS422 差分接收,或者至少加滤波和施密特触发。
-
Z 相(零位)没利用做绝对归零。增量编码器只能数相对位移,上电不知道绝对角度。接 Z 相到定时器外部触发或 IO,转一圈对齐一次零位,解决累计漂移。
-
高速时计数溢出丢脉冲。电机转太快,单位时间脉冲数超过定时器计数能力或 MCU 读取太慢,漏数。提高定时器位数、缩短读取间隔,或评估编码器最高响应频率是否超 MCU 支持。
把这件事收一下
编码器接口是电机闭环的入口:硬件数脉冲判方向,4 倍频提分辨率,定时读计数器算转速。CPU 几乎不参与计数,只在测量周期去读一下差值。真正的麻烦不在配置,在方向极性、溢出回绕、还有每圈计数到底乘没乘 4。把这几点钉死,转速反馈就稳了,速度环才有靠谱的输入。
正反转只看 A、B 谁领先 90 度,4 倍频把分辨率翻四倍。读计数器用带符号减法吃溢出,每圈计数别忘乘 4,编码器接地要共,测速才不飘。