1. 从一个被问烂了的问题说起
“编码器读到的到底是什么?”这个问题我在不同场合被问过不下几十次。问的人有刚入行的嵌入式新人,有做运动控制多年的老工程师,也有做机器人算法的朋友。大家的困惑点其实不太一样:新人往往卡在“为什么我读到的数一直在跳”,老工程师关心的是“同样的脉冲数为什么算出来的速度不一样”,做算法的则更在意“这个速度值到底能不能直接喂给控制环”。
先把结论摆在前面:编码器本身只输出脉冲,速度是算出来的,不是读出来的。这句话听起来像废话,但真正理解它的人不多。你从定时器寄存器里读到的那个数字,是脉冲计数;你从CAN总线上收到的那个转速值,是别人用脉冲和时间算出来的;你在上位机看到的曲线,是再经过一次滤波和单位换算的结果。每一层都有信息损失,每一层都可能引入误差。
这篇内容我打算把从脉冲到速度这条链路完整拆一遍。适合谁看?如果你正在做电机控制、机器人关节、传送带测速、云台稳定这类项目,或者你只是单纯想搞清楚编码器到底怎么回事,那这篇应该能帮你省下不少翻手册和试错的时间。我会尽量用生活化的类比把原理讲透,同时给出可以直接抄的参数和代码,最后再聊聊那些手册上不会写的坑。
2. 编码器输出的物理本质:脉冲不是数字
2.1 增量式编码器的A/B/Z三兄弟
市面上最常见的增量式编码器,输出三路信号:A相、B相、Z相。很多人一上来就盯着定时器的计数值看,却忽略了这三路信号本身的物理含义。
A相和B相是两路方波,频率相同,但相位差90度。这个90度不是随便定的,它决定了你能分辨方向。想象两个人并排走路,左边的人先迈步还是右边的人先迈步,你就能判断他们是往前走还是往后走。编码器同理:A相超前B相90度是一个方向,B相超前A相90度是另一个方向。
Z相是每转一圈输出一个脉冲,叫零位信号。它的作用不是用来计数的,而是用来校准绝对位置的。你可以把它理解成钟表上的12点刻度,没有它,你只知道走了多少格,不知道现在几点。
注意:Z相脉冲的宽度通常很窄,如果定时器采样率不够或者没有做边沿捕获,很容易漏掉。我在一个云台项目里就吃过这个亏,开机找零找了半天找不到,最后发现是Z相脉冲只有几十微秒宽,主循环轮询根本抓不住。
2.2 脉冲计数到底在计什么
定时器的编码器模式,本质上是在对A相和B相的边沿做计数。这里有个关键选择:计的是单边沿还是双边沿。
- 单边沿计数:只对A相的上升沿计数,一圈的脉冲数等于编码器线数。
- 双边沿计数:对A相和B相的上升沿、下降沿都计数,一圈的脉冲数是线数的4倍。
假设你用一个1000线的编码器,单边沿计数一圈就是1000个脉冲,双边沿就是4000个脉冲。很多新手看到手册上写“1000线”,以为一圈就是1000个脉冲,结果程序里算出来速度差了4倍,就是这里没搞清楚。
我个人的习惯是:能用双边沿就用双边沿。原因很简单,分辨率高了4倍,低速时速度估算更平滑。代价是定时器的计数频率高了4倍,如果你的转速很高,要确认定时器不会溢出。
2.3 分辨率、精度、准确度:三个容易混的概念
这三个词经常被混用,但在编码器语境下必须分清楚:
| 概念 | 含义 | 受什么影响 |
|---|---|---|
| 分辨率 | 能分辨的最小变化量 | 线数、计数方式(单/双边沿) |
| 精度 | 测量值与真实值的接近程度 | 安装偏心、码盘刻线误差 |
| 准确度 | 多次测量的一致性 | 信号质量、电磁干扰 |
一个1000线的编码器,双边沿计数分辨率是4000脉冲/转,但如果安装时偏心0.1毫米,精度可能只有±5个脉冲。分辨率高不代表精度高,这是两码事。我在一个传送带测速项目里,客户抱怨“速度曲线毛刺大”,换了更高线数的编码器也没用,最后发现是联轴器同心度差,振动导致脉冲抖动。换了个弹性联轴器,问题直接消失。
3. 从脉冲到速度:三种主流算法拆解
3.1 M法测速:固定时间数脉冲
M法是最直观的:固定一个采样周期T,数这段时间内有多少个脉冲,然后除以T和每转脉冲数,得到转速。
公式很简单:
转速(rpm) = (脉冲数 / 每转脉冲数) / T * 60举个例子:每转4000脉冲,采样周期10毫秒,读到200个脉冲。
转速 = (200 / 4000) / 0.01 * 60 = 300 rpmM法的优点是实现简单,低速时也能用。缺点是低速时误差大。因为采样周期固定,转速越低,脉冲数越少,量化误差占比越大。比如同样10毫秒,转速只有30 rpm时,脉冲数只有20个,±1个脉冲就是±5%的误差。
实操心得:M法适合中高速场合。如果转速低于额定转速的10%,建议换T法或者M/T法。我一般会在代码里设一个阈值,高于阈值用M法,低于阈值切T法。
3.2 T法测速:固定脉冲数测时间
T法反过来:固定数N个脉冲,测这段时间有多长,然后算转速。
转速(rpm) = (N / 每转脉冲数) / 时间 * 60还是上面的例子,每转4000脉冲,数20个脉冲,测得时间1毫秒。
转速 = (20 / 4000) / 0.001 * 60 = 300 rpmT法的优点是低速时精度高,因为脉冲数固定,时间测量精度取决于定时器频率。缺点是高速时时间太短,定时器分辨率不够,误差反而大。
这里有个关键参数:定时器频率。假设定时器频率1 MHz,分辨率1微秒。转速3000 rpm时,每转4000脉冲,20个脉冲对应的时间是:
时间 = 20 / (3000/60 * 4000) = 0.1 毫秒 = 100 微秒100微秒用1微秒分辨率测,量化误差1%,还能接受。但如果转速到6000 rpm,时间只有50微秒,误差就变成2%了。
3.3 M/T法:取长补短
M/T法结合了前两者的思路:同时测脉冲数和时间,用高频时钟去数脉冲周期,再用低速时钟去数固定时间内的脉冲数。这样高速时用M法的思路,低速时用T法的思路,全速段精度都比较均衡。
具体实现上,常见做法是:设定一个采样周期T,在这个周期内数脉冲数M1,同时用高频时钟数第一个脉冲到最后一个脉冲之间的时间T1。转速计算:
转速(rpm) = (M1 / 每转脉冲数) / T1 * 60注意这里用的是T1而不是T。T1是实际测到的时间,比T更准确,因为它剔除了采样周期起止点与脉冲边沿不对齐带来的误差。
M/T法的代价是逻辑复杂一些,需要两个定时器配合。但在STM32这类MCU上,用编码器模式加一个输入捕获通道就能实现,不算太麻烦。
3.4 三种方法怎么选:一张表说清楚
| 方法 | 适用转速范围 | 优点 | 缺点 | 典型场景 |
|---|---|---|---|---|
| M法 | 中高速(>10%额定) | 实现简单,响应快 | 低速误差大 | 风扇、高速主轴 |
| T法 | 低速(<10%额定) | 低速精度高 | 高速误差大 | 云台、精密转台 |
| M/T法 | 全速段 | 精度均衡 | 逻辑复杂 | 机器人关节、伺服 |
我个人的经验是:如果MCU资源够,直接上M/T法。省得后面切换逻辑写一堆if-else,调试起来还容易出bug。如果资源紧张,那就根据实际转速范围选一个,别硬上。
4. 手把手实现:从定时器配置到速度输出
4.1 硬件连接与定时器配置
以常见的STM32为例,编码器A相接定时器的CH1,B相接CH2,Z相接一个外部中断引脚。定时器配置成编码器模式,计数方式选双边沿。
关键配置项:
- 定时器预分频器:设为0,让计数器跑最快。
- 计数模式:Encoder Mode,TI1和TI2都计数。
- 计数器周期:设为65535(16位定时器最大值),避免频繁溢出。
- 输入滤波:根据信号质量设,一般设6-10个时钟周期。
注意:输入滤波设太大,高速时会丢脉冲;设太小,干扰会导致误计数。我一般先用示波器看信号质量,再决定滤波参数。如果信号干净,滤波设2-4就行。
4.2 速度计算的代码实现
下面是一个M/T法的简化实现,基于定时器中断,每10毫秒执行一次:
#define PPR 4000 // 每转脉冲数(双边沿) #define SAMPLE_MS 10 // 采样周期10ms #define CLK_MHZ 72 // 定时器时钟72MHz volatile int32_t last_count = 0; volatile uint32_t last_time = 0; void speed_calc(void) { int32_t curr_count = TIM1->CNT; uint32_t curr_time = TIM2->CNT; // 高频时钟计数 int32_t delta_count = curr_count - last_count; uint32_t delta_time = curr_time - last_time; // 防止溢出处理 if (delta_count < 0) delta_count += 65536; float rpm = 0; if (delta_count > 0 && delta_time > 0) { // M/T法:用实际时间算 float time_s = (float)delta_time / (CLK_MHZ * 1000000); rpm = ((float)delta_count / PPR) / time_s * 60.0f; } last_count = curr_count; last_time = curr_time; // 输出rpm }这段代码有几个细节值得说:
第一,delta_count可能为负,因为编码器反转时计数器会减。这里用if (delta_count < 0) delta_count += 65536处理溢出,但更严谨的做法是用有符号数直接算,让硬件自动处理。
第二,delta_time用的是高频时钟计数,不是采样周期。这是M/T法的核心,用实际脉冲间隔时间而不是固定周期,精度更高。
第三,转速为0时delta_count为0,直接输出0,避免除零。
4.3 参数计算:采样周期怎么定
采样周期不是随便定的,它决定了速度环的带宽。根据香农采样定理,采样频率至少是信号最高频率的2倍。实际工程中一般取5-10倍。
假设你的速度环带宽是100 Hz,那采样频率至少500 Hz,采样周期就是2毫秒。但采样周期太短,低速时脉冲数太少,量化误差大。所以有个折中:
采样周期 = max(1 / (10 * 速度环带宽), 最小脉冲数 / (最高转速 * PPR / 60))举个例子:速度环带宽100 Hz,最高转速3000 rpm,PPR=4000。
- 第一项:1 / (10 * 100) = 1 毫秒
- 第二项:假设最小脉冲数10,10 / (3000 * 4000 / 60) = 0.05 毫秒
取大的那个,1毫秒。但1毫秒时低速脉冲数可能不够,所以实际取5-10毫秒比较稳妥。我一般用10毫秒,兼顾低速精度和响应速度。
4.4 单位换算:rpm、rad/s、脉冲/秒
不同场合用的单位不一样,控制算法里常用rad/s,上位机显示常用rpm,底层调试常用脉冲/秒。换算关系:
1 rpm = 2π / 60 rad/s ≈ 0.1047 rad/s 1 rad/s = 60 / (2π) rpm ≈ 9.549 rpm 脉冲/秒 = rpm / 60 * PPR我习惯在代码里统一用rad/s,输出到上位机时再转rpm。这样控制算法里不用反复换算,减少出错概率。
5. 那些手册不会告诉你的坑
5.1 计数溢出:最隐蔽的bug
16位定时器最大计数值65535,如果转速高、PPR大,很容易溢出。比如PPR=4000,转速3000 rpm,每秒脉冲数:
3000 / 60 * 4000 = 200000 脉冲/秒10毫秒采样周期内就是2000个脉冲,不会溢出。但如果采样周期1秒,就是20万,肯定溢出。所以采样周期和定时器位数要匹配。
更隐蔽的是,溢出发生在两次采样之间,你读到的delta_count可能是负数。如果代码里没处理,速度就会突然跳变。我见过一个项目,电机高速时速度曲线每隔几秒出现一个尖峰,查了两天才发现是计数器溢出。
避坑技巧:用32位定时器,或者用定时器的溢出中断累加高位。STM32的TIM2和TIM5是32位的,能省不少事。
5.2 信号抖动:脉冲边沿的毛刺
编码器信号经过长线传输,容易引入毛刺。毛刺会导致多计数,速度曲线出现高频噪声。
解决方法有三个层次:
- 硬件滤波:在A/B相线上加RC低通滤波,截止频率根据最高转速定。
- 定时器输入滤波:STM32的编码器模式支持数字滤波,设几个时钟周期的滤波窗口。
- 软件滤波:速度算出来后做滑动平均或低通滤波。
我一般先用硬件滤波和定时器滤波,软件滤波作为最后一道防线。软件滤波会引入相位滞后,对控制环不利,能少用就少用。
5.3 低速时的速度估算:从脉冲到速度的鸿沟
低速时,采样周期内可能只有几个脉冲,甚至一个都没有。这时候速度估算就变成了“有脉冲就是有速度,没脉冲就是零速”,曲线像台阶一样。
解决办法有两个:
一是用T法,测脉冲间隔时间。但脉冲间隔太长时,定时器可能溢出。
二是用观测器,比如龙伯格观测器或卡尔曼滤波,用电流和电压估算速度。这属于进阶内容,但效果确实好。我在一个直驱电机项目里,低速0.1 rpm时用脉冲法根本没法看,上了观测器之后曲线平滑多了。
5.4 常见问题速查表
| 现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 速度一直为0 | 编码器没接好 | 示波器看A/B相 | 检查接线 |
| 速度方向反了 | A/B相接反 | 交换A/B相 | 改接线或改代码 |
| 速度跳变 | 计数器溢出 | 看delta_count符号 | 换32位定时器 |
| 速度毛刺大 | 信号干扰 | 看脉冲波形 | 加滤波 |
| 低速不准 | 量化误差 | 看脉冲数 | 换T法或M/T法 |
| 高速不准 | 定时器分辨率不够 | 算时间量化误差 | 提高定时器频率 |
6. 从速度到控制:编码器数据的下游用法
6.1 速度环的输入从哪来
速度算出来之后,通常直接喂给PID速度环。但这里有个细节:速度值的更新率和控制环的执行率要匹配。
如果速度环跑1 kHz,但速度值10毫秒才更新一次,那控制环有9次用的是旧值。这会导致相位滞后,降低带宽。
解决办法是让速度计算和控制环同频,或者用预测算法补值。我一般让速度计算在定时器中断里跑,频率和控制环一致,这样最简单。
6.2 位置和速度的关系
位置是速度的积分,速度是位置的微分。编码器直接给的是位置(脉冲数),速度是算出来的。所以位置环和速度环的信息来源其实是一个,只是处理方式不同。
位置环用脉冲数直接算,精度取决于编码器分辨率。速度环用脉冲数差分,精度取决于采样周期和算法。两者要协调好,否则位置环和速度环会打架。
6.3 编码器分辨率对控制性能的影响
分辨率越高,速度估算越平滑,低速性能越好。但分辨率高也意味着脉冲频率高,对MCU的定时器频率和中断处理能力要求更高。
选型时要算一笔账:
最高脉冲频率 = 最高转速 / 60 * PPR假设最高转速6000 rpm,PPR=4000,最高脉冲频率400 kHz。STM32的定时器在72 MHz时钟下,编码器模式能处理这个频率,但中断里不能做太多事。
如果PPR=10000,频率就到1 MHz,很多MCU就吃不消了。这时候要么降速,要么换更高性能的MCU,要么用硬件计数器加DMA。
7. 实测数据与经验总结
7.1 一组实测对比
我在一个测试台上用同一个电机、同一个编码器(PPR=4000),分别用M法、T法、M/T法测速,采样周期10毫秒,结果如下:
| 设定转速 | M法误差 | T法误差 | M/T法误差 |
|---|---|---|---|
| 3000 rpm | 0.5% | 3% | 0.6% |
| 300 rpm | 2% | 0.8% | 0.9% |
| 30 rpm | 15% | 1.5% | 1.8% |
| 3 rpm | 无法测 | 3% | 3.5% |
数据很直观:M法高速好,T法低速好,M/T法全段均衡。3 rpm时M/T法误差略大,是因为脉冲间隔太长,定时器分辨率不够了。
7.2 几个我踩过的坑
第一个坑:编码器线数不是脉冲数。我刚开始做的时候,以为1000线就是1000脉冲,结果程序里算出来速度差4倍。后来才知道双边沿计数是4倍频。
第二个坑:Z相找零的时机。Z相脉冲很窄,如果主循环轮询,很容易错过。我后来改成外部中断触发,才稳定找到零位。
第三个坑:速度方向判断。A/B相接反了,速度方向就反了。但有时候接反了也能计数,只是方向不对。调试时一定要手动转一下电机,看速度符号对不对。
第四个坑:滤波参数设太大。输入滤波设太大,高速时丢脉冲,速度偏低。我一般先用示波器看信号,再决定滤波参数。
7.3 给新手的建议
如果你刚开始做编码器测速,我的建议是:
- 先用示波器看A/B/Z波形,确认信号质量。
- 用M法先跑通,确认计数和方向正确。
- 再根据转速范围决定要不要换M/T法。
- 速度算出来后,先在上位机看曲线,别急着闭环。
- 低速性能要求高的话,考虑加观测器。
最后再分享一个小技巧:在代码里加一个脉冲计数溢出检测。每次采样时判断delta_count是否超过阈值,超过就报警。这个简单的检查能帮你提前发现很多问题,比如编码器松动、信号干扰、计数器溢出等。
这个内容后续还可以这样扩展:如果你用的是绝对值编码器,那读到的就不是脉冲而是位置码,处理方式完全不同。另外,多圈绝对值编码器涉及圈数计数和电池备份,又是另一个话题。有机会再聊。