AB编码器测速全解析:原理、定时器配置与工程实战
2026/9/10 4:41:12 网站建设 项目流程

作为一个常年跟电机、小车、自动化设备打交道的嵌入式工程师,我对 AB 编码器测速这个需求再熟悉不过了。不管你是做平衡车、AGV、机械臂关节还是简单的循迹小车,只要涉及到闭环控制,速度反馈就绕不开编码器。而增量式 AB 编码器,基本上是成本、精度、可靠性之间平衡得最好的一款方案。今天这篇,我不打算罗列手册,而是把我实际做过的项目里,关于 AB 编码器测速的硬件选型、定时器配置、速度换算、滤波处理和坑点排查一次性讲透,希望能帮你少走弯路。

1. 为什么用 AB 编码器测速:核心原理与方案取舍

1.1 AB 编码器到底在测什么

很多刚入门的同学会把编码器想象得很玄,其实按我的理解,增量式 AB 编码器本质上就是一个光栅/磁栅 + 两路正交输出的脉冲发生器。电机一转,码盘跟着转,光电或霍尔元件就会在 A、B 两个通道上输出方波信号。这两路方波的频率和电机转速成正比,而相位差刚好是 90 度(也就是正交),你拿到这两路信号之后,就可以同时解算出转速的大小转动的方向

这里有个容易混淆的点:AB 编码器是“增量式”的,它本身并不知道你当前绝对位置在哪,它只负责告诉你“我从上次采样到现在转了多少步、往哪个方向转”。所以它的输出不是一根线,而是两根,也就是 A 相和 B 相。你测量这两根线的脉冲数变化量,再除以采样周期,就得到了速度。想要绝对位置的话,就得额外加 Z 相(零位脉冲)或者直接上绝对式编码器,但那是另一个话题了。

1.2 测速逻辑:方向判断与计数原理

AB 编码器最关键的点在于方向判断。如果你只有一根脉冲线,只能测到转速,方向要靠额外的一个高低电平引脚来给,很多普通霍尔测速模块就是这么干的。但 AB 编码器不一样,它的 A、B 两路信号天然带有正交相位。电机正转时,A 相超前 B 相 90 度;反转时,A 相落后 B 相 90 度。你在程序里捕获 A、B 的相对电平变化,就能判断出方向,并且把这个方向信息体现在计数器的加/减上。

还有一个概念叫倍频,这是 AB 编码器的隐藏福利。因为 A、B 两路一共存在四种状态组合(A 高 B 低、A 高 B 高、A 低 B 高、A 低 B 低),一个完整脉冲周期内会有四个跳变沿。如果只在 A 相的上升沿计数,那叫 1 倍频;如果同时在 A/B 的上升沿和下降沿都计数,就是 4 倍频,分辨率直接翻四倍。实际做测速时,我几乎都是用 4 倍频模式,这样 300 线的编码器能跑出 1200 的每圈脉冲数,低速下的测速精度会好很多。

1.3 常用测速方案对比:M法、T法、M/T法

搞单片机的同学上手测速时经常会纠结一个问题:到底是统计单位时间内的脉冲数,还是测量单个脉冲的时间宽度?这两条路分别对应两个经典的测速方法:M 法测速T 法测速

M 法是在固定的时间窗口内数脉冲数量,适合中高速场景,速度越快、窗口内脉冲越多,量化误差越小;T 法是测量相邻两个脉冲之间的时间间隔,适合低速场景,转速越低脉冲间隔越长,时间测量越准。还有一种 M/T 法,把两者结合起来,在宽速度范围内都能取得不错的效果。不过从我做实际项目的经验来看,对于大多数直流减速电机小车项目,用一个合适的采样周期(比如 20ms~50ms)配合 M 法加高倍频计数,已经足够满足 PID 控制的带宽需求了。

2. 硬件准备与关键参数换算

2.1 编码器选型与电机减速比

说到选型,就绕不开**编码器线数(PPR)**这个概念。PPR 全称是 Pulse Per Revolution,指的是码盘转一圈 A 相输出多少个脉冲(注意不要和 4 倍频后的分辨率搞混)。市面上常见的增量式 AB 编码器有线数 100、200、300、360、500、1024 等。如果你的应用是小型平衡车或者实验室小车,300 线到 500 线已经非常充裕;如果是高精度转台或者机械臂关节,可能要上千线。

选编码器的时候,我还要特别提醒一个关键点:编码器是装在电机轴上还是输出轴上。很多直流减速电机内部自带霍尔编码器,但它是测电机转子的转速,不是测减速箱输出轴的转速。你最后换算轮子线速度时,一定要把减速比也算进去。比如我做过的一个小车平台,电机是 1:30 减速比,编码器是 13 PPR,那么电机轴转一圈输出 13 个脉冲,经过减速箱后输出轴转一圈对应 390 个原始脉冲,4 倍频之后就是 1560 个计数,这个数字才是你做速度闭环时真正要用的比例因子。

2.2 MCU 定时器资源分配

STM32 和很多主流 MCU 都对 AB 编码器有专门的硬件支持,不需要你用外部中断 GPIO 去手动判断方向。以 STM32 为例,通用定时器 TIM2/TIM3/TIM4/TIM5 都支持编码器接口模式,你需要把编码器的 A、B 两路输出分别接到定时器的 CH1 和 CH2 通道上。这一点非常重要,很多人习惯性地把编码器接到普通 GPIO,然后自己写外部中断,效果不仅差,而且浪费 CPU 资源。

我这里建议优先用定时器硬件编码器模式,因为它的计数器会自动根据两路正交信号进行加减操作。A 相上升沿时如果 B 相是高电平,计数器加一;B 相是高电平时 A 相下降沿,计数器减一。你完全不用关心 A、B 谁先谁后,硬件帮你把方向判断和计数一起完成了。在项目设计阶段,就要先查好你选择的 MCU 型号哪些定时器支持编码器模式,别等画完板子才发现引脚复用有问题。

2.3 转速与线速度的换算公式

就我自己写代码的习惯来说,我平时会封装一个get_speed()函数,函数返回的并不是原始计数,而是已经换算好的国际单位。比如角速度用 rad/s,线速度用 m/s,这样上层 PID 代码就非常干净。换算的核心公式如下:

[ n_{rpm} = \frac{N_{count}}{PPR \times 4 \times T_{sample}} \times 60 ]

其中 (N_{count}) 是采样周期内计数器变化的总脉冲数;(PPR \times 4) 表示 4 倍频后每圈的总计数;(T_{sample}) 是采样周期(秒)。乘以 60 是为了把每秒圈数换算成每分钟圈数。

如果还要换算轮子的线速度,就用:

[ v_{m/s} = \frac{n_{rpm}}{60} \times 2\pi \times r_{wheel} \div i_{gear} ]

注意这里的 (i_{gear}) 是减速比,如果你用的编码器已经装在输出轴上,那么就不用再除了。我遇到过好几个同学在这里搞混,明明电机是 30:1 减速,换算线速度时没除减速比,结果 PID 调了半天,速度始终不对。

3. 代码实现:从寄存器到 HAL 库的完整落地

3.1 定时器编码器模式初始化

我以 STM32 HAL 库为例,展示一个可直接使用的编码器模式初始化代码。代码的目标是把 TIM3 的 CH1 和 CH2 配置为编码器接口,开启 4 倍频计数模式,定时器预分频设为 0,自动重载 ARR 设为最大值(例如 0xFFFF),保证计数范围足够大,不频繁溢出。

void Encoder_TIM3_Init(void) { TIM_Encoder_InitTypeDef encoder_cfg = {0}; TIM_MasterConfigTypeDef sMasterConfig = {0}; GPIO_InitTypeDef GPIO_InitStruct = {0}; __HAL_RCC_TIM3_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitStruct.Pin = GPIO_PIN_6 | GPIO_PIN_7; // PA6->TIM3_CH1, PA7->TIM3_CH2 GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); __HAL_AFIO_REMAP_TIM3_ENABLE(); // 部分型号需要,视具体引脚复用而定 htim3.Instance = TIM3; htim3.Init.Prescaler = 0; htim3.Init.CounterMode = TIM_COUNTERMODE_UP; htim3.Init.Period = 0xFFFF; htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; encoder_cfg.EncoderMode = TIM_ENCODERMODE_TI12; // 1路输入捕获,4倍频计数 encoder_cfg.IC1Polarity = TIM_ICPOLARITY_RISING; encoder_cfg.IC1Selection = TIM_ICSELECTION_DIRECTTI; encoder_cfg.IC1Prescaler = TIM_ICPSC_DIV1; encoder_cfg.IC1Filter = 0x0F; // 数字滤波,抗抖动 encoder_cfg.IC2Polarity = TIM_ICPOLARITY_RISING; encoder_cfg.IC2Selection = TIM_ICSELECTION_DIRECTTI; encoder_cfg.IC2Prescaler = TIM_ICPSC_DIV1; encoder_cfg.IC2Filter = 0x0F; HAL_TIM_Encoder_Init(&htim3, &encoder_cfg); sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig); HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_ALL); }

有几点我单独说明一下:

  • TIM_ENCODERMODE_TI12表示在 TI1 和 TI2 的上下沿都计数,也就是 4 倍频。
  • IC1FilterIC2Filter我建议不要设成 0,尤其是在电机碳刷或者 PWM 驱动附近,信号抖动很常见,滤波器能帮你滤掉毛刺。
  • 计数器的初始值放在 CNT 寄存器里,因为设置为向上计数模式,正转加、反转减。

3.2 速度读取与计算函数

速度采样的核心思路是:每个固定时间窗口读一次定时器的 CNT,与上一次的 CNT 做差,差值就是这段时间内 4 倍频后的脉冲变化数。用这个差值除以时间,再乘以比例系数就是速度。需要特别注意的是,CNT 是 16 位的,会溢出,所以差值不能直接减,要用整型回绕的方式处理。

#define ENCODER_PPR 13 #define GEAR_RATIO 30 #define WHEEL_DIAMETER 0.065f // 单位:米 #define SAMPLE_TIME 0.02f // 单位:秒 volatile int16_t last_cnt = 0; volatile int16_t speed_rpm = 0; volatile float speed_ms = 0.0f; void Encoder_Calc_Speed(void) { int16_t cur_cnt = (int16_t)__HAL_TIM_GET_COUNTER(&htim3); int16_t delta = cur_cnt - last_cnt; // int16_t 回绕,注意顺序 last_cnt = cur_cnt; // 转成每分钟转速 float rpm = (float)delta * 60.0f / (ENCODER_PPR * 4 * SAMPLE_TIME * GEAR_RATIO); speed_rpm = (int16_t)rpm; speed_ms = rpm / 60.0f * 3.1415926f * WHEEL_DIAMETER; }

这段代码看起来简单,但有个小陷阱:deltaint16_t来定义,是有意为之的。如果你的采样窗口内脉冲变化量超过了 32767,这就会出问题。但以我常用的 20ms 采样周期、13 PPR 编码器来说,即使电机轴到了 10000 RPM,单窗口脉冲数也远达不到 32767,所以这里很安全。如果你用的是高线数编码器加长采样周期,就要考虑改用 32 位定时器或者缩短采样窗口。

3.3 数据处理:滤波与单位转换

原始的速度值通常在精度上是不够的,尤其是低速时,量化误差会带来明显的数值跳动。我常用的滤波方式有两种:一种是滑动平均滤波,适合对小波动进行平滑;另一种是一阶低通滤波,计算量小、实时性好,适合做 PID 反馈。我平时更倾向于一阶低通滤波,因为实现简单,调节alpha系数就能改变平滑程度和响应速度的平衡。

float speed_filtered = 0.0f; #define ALPHA 0.3f float Speed_LowPass_Filter(float raw_speed) { speed_filtered = ALPHA * raw_speed + (1.0f - ALPHA) * speed_filtered; return speed_filtered; }

如果你把alpha调大,跟踪会变得很快但毛刺较多;调小则数据平滑但会有明显滞后。我的经验是,如果这个速度是做串级 PID 的内环反馈,滞后太大会导致系统振荡,所以alpha不要小于 0.2。如果是做显示或者上位机观看,可以调到 0.05 左右,让曲线更光滑。

4. 调试实录与常见问题排查

4.1 方向判断反了怎么办

这是遇到频率最高的问题。你写好代码,转电机,发现速度值是负数,或者 PID 加输出后速度反而往下掉。碰到这种情况不用慌,本质上就是 A、B 两路信号接反了。有两个解决办法。

第一个办法是硬件层面,把 A、B 两根线对调一下,这个最直接。第二个办法是软件层面,在初始化定时器时把IC1PolarityIC2Polarity都改成下降沿触发,这样方向就反过来了。我在这里提醒一下,用软件翻转方向时,要两个通道的极性一起改,不能只改一路。

4.2 转速跳动大、计数不稳

排查计数不稳,我一般会先抓波形看。用示波器或者逻辑分析仪看 A、B 两路信号,如果发现边沿有毛刺,说明信号质量不好。毛刺的产生原因主要有三个:电机电刷火花干扰、PWM 驱动线串扰、编码器供电纹波过大。

应对方法比较成熟:首先在编码器供电端加 100nF 去耦电容,有条件再加一个 10uF 电解电容;其次在 A、B 信号线上串接 510Ω 到 1kΩ 的限流电阻,配合滤波电容形成 RC 低通滤波环境;最重要的是 STM32 定时器输入滤波器ICxFilter一定要合理设置,我的经验是取 0x0F 到 0x3F 之间,既能滤掉高频噪声又不至于让真实信号失真。

4.3 高速丢步和计数器溢出

高速时遇到丢步,通常原因有两个。第一种是编码器线数太高,输出脉冲频率超过了定时器的计数容量或者 CPU 的读取速度;第二种是信号边沿太慢,在高速时单片机无法正确识别电平变化。

对于丢步问题,我强烈建议检查一下编码器输出是推挽输出还是开漏输出。如果是开漏,外部必须接上拉电阻,否则边沿可能会变得很缓,高速时边沿还没到达高电平就被下一个边沿打乱了。这时候可以试着把上拉电阻阻值调小一点,例如 1kΩ 到 4.7kΩ,能明显改善高速性能。计数器溢出问题则可以通过合理设计采样周期和 ARR 值来规避。至于超高速场景,我建议用专门的编码器计数芯片或者用带正交解码的 FPGA,普通单片机做高精度高速测速确实捉襟见肘。

4.4 编码器测速项目常见问题速查表

故障现象可能原因排查方向解决建议
速度为 0,但电机在转信号接错引脚 / 定时器未启动检查 GPIO 复用及定时器编码器模式是否开启重新核对引脚复用表,调用HAL_TIM_Encoder_Start
始终显示正数,反转也是正数未开启方向计数检查编码器模式是否为 TI12确认使用TIM_ENCODERMODE_TI12
速度值偏大或偏小减速比/PPR 参数不对核算每圈总计数手动转一圈,打印实际计数确认比例
数字跳动剧烈信号毛刺 / 滤波不足观察波形,检查供电加 RC 滤波,增大ICxFilter,加去耦电容
某一方向计数正常,另一方向跳变A/B 相位不对称检查编码器安装是否偏心确认码盘同轴度,排除装配问题
高转速时丢步信号边沿过慢/干扰示波器测边沿,检查上拉电阻降低上拉电阻阻值,适当减小 PPR 线数
数值偶尔跳变几天发生一次电源纹波叠加干扰检查地线回路和电机驱动共地单点接地,编码器供电与电机供电分开

5. 最后再分享一个调试经验

我在实际调试 AB 编码器测速的过程中,有一个印象很深的坑。之前做过一个底盘小车,用的是一块 STM32F103 最小系统板,编码器引脚正好和 JTAG 的调试口复用。结果程序下载好之后,一跑电机就死机,后来排查了很久发现是调试器和编码器信号冲突导致的上拉干扰。所以做项目之前,一定要看清楚引脚复用,别把编码器接到调试口复用引脚上,否则你连下载器都会受影响。

另外,如果你刚接触这个模块,我建议你先把编码器信号接上逻辑分析仪,手动慢慢转电机,观察 A、B 两路的波形是否符合预期。这一步看起来多花了十分钟,但能帮你排除掉一大堆后续定位成本。等波形确认无误,再写代码、再调 PID,思路会清晰得多。

还有一个小技巧:测速闭环调试时,不要一上来就做高刷新率。先让采样周期固定在 50ms,把速度值通过串口打印出来,确认整个链路是正确的;然后再把周期缩短到 20ms、10ms,逐步逼近控制需求。很多人一上来就写个 1ms 定时中断做速度环,结果把自己搞得很痛苦。AB 编码器测速这件事,硬件和软件各占一半,耐心排查信号、认真核对参数,基本都能跑起来。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询