4. 编码器接口驱动:增量式编码器原理、ABZ信号解码、正交解码(QEI)、速度与位置计算、Z相归零
编码器,说白了就是运动控制系统的「眼睛」。没有它,你的电机转没转、转了多少、转到哪了,全是黑盒。我刚开始做驱动开发那会儿,就吃过这个亏——电机跑得欢,位置反馈却丢了,结果机械臂直接撞限位。嗯,从那以后,我对编码器驱动再也不敢马虎了。
这一章,咱们就聊聊增量式编码器。它是工业现场最常见的类型,成本低、精度高、接口简单。我会把ABZ信号怎么解码、正交解码(QEI)硬件怎么用、速度和位置怎么算、Z相归零怎么玩,全给你捋一遍。
4.1 增量式编码器原理
增量式编码器,说白了就是输出脉冲串。它不像绝对式编码器那样直接告诉你绝对位置,而是告诉你「我动了多少步」。你想想看,这就像你走路时数步子——每走一步计一个数,但如果你忘了从哪开始的,那就尴尬了。
它的核心结构很简单:
- 码盘:一个带刻度的圆盘,转动时光电传感器检测明暗变化
- A/B两相:相位差90°,用来判断方向和倍频
- Z相:每转一圈输出一个脉冲,用于归零
我个人习惯把A相叫「主脉冲」,B相叫「方向脉冲」。为什么?因为A相每来一个上升沿,就代表走了一步;而B相的电平高低,决定了这一步是正转还是反转。
关键点:增量式编码器只能测相对位置,断电后位置丢失。所以每次上电必须先做归零操作。
4.2 ABZ信号解码
ABZ信号解码,是编码器驱动的第一道关卡。信号从编码器出来,通常是差分信号(RS-422),经过接收器转成单端信号,然后进MCU的定时器或QEI模块。
解码的核心逻辑其实就一句话:检测A相边沿,同时读B相电平。
举个例子:
// 伪代码:ABZ信号解码
if (A上升沿) {
if (B == 高电平) {
位置计数器++; // 正转
} else {
位置计数器--; // 反转
}
}
if (Z上升沿) {
位置计数器 = 0; // 归零
}
我在项目中遇到过一个问题:编码器线缆太长,信号有毛刺,导致A相边沿误触发。后来加了硬件RC滤波,又在软件里做了「去抖」——连续采样三次,两次一致才认。嗯,这招很土,但很管用。
注意:Z相脉冲宽度通常只有1个脉冲周期,采样频率必须足够高,否则容易漏掉。我建议用边沿中断捕获,别用轮询。
4.3 正交解码(QEI)
正交解码,英文叫Quadrature Encoder Interface,简称QEI。说白了,就是硬件帮你做ABZ解码,不用CPU一条一条指令去读。
为什么需要QEI?你想想看,如果电机转速很高,比如3000转/分,编码器分辨率1000线,那每秒就有3000×1000×4 = 1200万个脉冲。用软件解码,CPU基本就废了。QEI硬件模块可以独立处理这些脉冲,CPU只需要定期读计数器值就行。
QEI的核心功能:
- 4倍频:利用A/B两相的上升沿和下降沿,将分辨率提高4倍
- 方向判断:自动根据相位差判断正反转
- 位置计数:32位或16位计数器,可配置上下限
- Z相复位:Z相脉冲到来时,自动清零计数器
下面是我用STM32的TIM编码器模式做的一个配置示例:
// STM32 TIM编码器模式配置
TIM_Encoder_InitTypeDef sEncoder;
sEncoder.EncoderMode = TIM_ENCODERMODE_TI12; // 双沿采样,4倍频
sEncoder.IC1Polarity = TIM_ICPOLARITY_RISING;
sEncoder.IC2Polarity = TIM_ICPOLARITY_RISING;
HAL_TIM_Encoder_Init(&htim3, &sEncoder);
HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_ALL);
// 读取位置
int32_t position = (int16_t)__HAL_TIM_GET_COUNTER(&htim3);
这里有个坑:TIM的计数器是16位的,最大只能计65535。如果编码器分辨率高、行程长,很容易溢出。我建议用32位定时器,或者在软件里做溢出处理——每次读计数器时,跟上一次比较,如果差值超过阈值,就认为发生了溢出。
小技巧:QEI模块通常支持「索引脉冲复位」功能。配置好之后,Z相一来,计数器自动归零。这样你就不用每次手动清零了,省一个中断。
4.4 速度与位置计算
位置计算很简单:直接读QEI的计数器值,乘以每个脉冲对应的位移量,就是当前位置。
比如:编码器分辨率1000线,4倍频后每转4000个脉冲。丝杠导程5mm,那每个脉冲对应的位移就是5/4000 = 0.00125mm。位置值 = 计数器 × 0.00125mm。
速度计算就有讲究了。常用的方法有两种:
| 方法 | 原理 | 适用场景 | 优缺点 |
|---|---|---|---|
| M法(测频法) | 固定时间窗口内数脉冲数 | 中高速 | 低速时精度差 |
| T法(测周法) | 测量两个脉冲之间的时间 | 低速 | 高速时精度差 |
| M/T法 | 结合M法和T法 | 全速范围 | 实现稍复杂 |
我个人习惯用M/T法。具体做法是:
- 固定一个时间窗口(比如10ms)
- 在这个窗口内,同时记录脉冲数和最后一个脉冲的时间戳
- 速度 = 脉冲数 / (时间窗口 + 最后一个脉冲的剩余时间)
这样做的好处是,低速时精度高,高速时响应快。我在一个伺服驱动器项目里用过,效果不错。
// M/T法速度计算示例
uint32_t pulse_count; // 时间窗口内的脉冲数
uint32_t last_pulse_time; // 最后一个脉冲的时间戳(us)
uint32_t window_time = 10000; // 10ms窗口
float speed = (float)pulse_count /
((float)window_time + (float)last_pulse_time) * 1000000.0f;
// 单位:脉冲/秒
注意:速度计算对实时性要求很高。如果CPU被其他任务打断,导致时间窗口不准,速度值会剧烈抖动。我建议用硬件定时器来触发速度计算,别用软件延时。
4.5 Z相归零
Z相归零,是增量式编码器绕不开的话题。因为增量式编码器没有绝对位置,每次上电后,你必须让机械结构运动到某个参考点,然后清零位置计数器。
Z相归零的典型流程:
- 电机以低速向归零方向运动
- 检测到Z相上升沿时,立即停止运动
- 将位置计数器清零
- 记录当前机械位置作为参考点
这里有个细节:Z相脉冲通常很窄,如果电机速度太快,可能还没等中断响应,脉冲就过去了。我建议用QEI的「索引脉冲捕获」功能——硬件自动锁存当前计数器值,然后触发中断。这样即使CPU响应慢,也不会丢脉冲。
我曾经在一个项目中遇到过Z相抖动的问题。编码器在Z相位置附近来回晃动,导致反复触发归零。后来我加了「归零锁定」逻辑:一旦Z相触发,就屏蔽后续的Z相中断,直到下一次主动解除锁定。嗯,这招很暴力,但很有效。
核心要点:Z相归零不是简单的「清零」,而是一个完整的「寻零-锁定-确认」过程。归零完成后,最好再反向运动一小段,确认Z相位置是否一致。这叫「归零验证」,能避免因机械间隙导致的误差。
4.6 知识体系总览
下面这张图,是我画的编码器驱动知识体系。你可以把它当作一个检查清单——开发时对照着看,不容易漏东西。
这张图从左到右、从上到下,基本覆盖了编码器驱动的全部要点。你开发时,可以把它贴在工位旁边,每完成一项就打个勾。嗯,我当年就是这么干的。
公众号:蓝海资料掘金营,微信deep3321