十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

AB编码器测速全解析:原理、定时器配置与工程实战

AB编码器测速全解析:原理、定时器配置与工程实战 作为一个常年跟电机、小车、自动化设备打交道的嵌入式工程师我对 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 倍频。IC1Filter和IC2Filter我建议不要设成 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; }这段代码看起来简单但有个小陷阱delta用int16_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 两根线对调一下这个最直接。第二个办法是软件层面在初始化定时器时把IC1Polarity和IC2Polarity都改成下降沿触发这样方向就反过来了。我在这里提醒一下用软件翻转方向时要两个通道的极性一起改不能只改一路。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 编码器测速这件事硬件和软件各占一半耐心排查信号、认真核对参数基本都能跑起来。
返回列表