简介:这份STM32编码器测速代码面向嵌入式初学者、电赛控制类参赛者及电机控制开发者,围绕STM32定时器的编码器模式,解决增量编码器信号采集与电机转速精确测量的问题。资源包共184个文件,约4.75MB,以34个c源文件与35个h头文件构成核心驱动与工程逻辑,配合34个o目标文件、33个crf中间文件及uvprojx、uvoptx等Keil工程文件,另有axf、map、lst等编译产物,便于直接打开工程编译验证。压缩包中的示例工程基于STM32F1系列(C8T6),演示了GPIO输入配置、定时器捕获比较、A/B相脉冲计数、方向判断与RPM换算等关键环节,并涉及中断服务程序对脉冲边沿的处理。目前已有10793人学习下载,适合作为电赛控制类项目的参考模板,帮助读者理解编码器接口与测速算法的落地实现,快速搭建可复用的测速框架。
1. 正交解码到底在解什么:从编码器脉冲到转速值的完整链路
很多人第一次写 STM32 编码器测速代码,都会卡在同一个地方:定时器配置看起来没问题,中断也在跑,可读出来的数要么不动,要么乱跳,要么方向永远是对的但速度差一倍。问题往往不在代码本身,而在于没搞清楚「正交解码」这四个字到底在硬件层面做了什么。
编码器输出的是 A、B 两路方波,相位差 90 度。STM32 的通用定时器和高级定时器自带编码器接口模式,硬件会自动根据 A、B 的边沿组合做四倍频计数,同时根据相位先后判断方向,把结果写进 CNT 寄存器。也就是说,你不需要写任何外部中断去手动计数,硬件已经帮你把「转了多少格、往哪个方向转」算好了。你要做的,是把这个计数值在固定时间窗口内取出来,换算成转速。
这套方案适合谁?做电机闭环控制、云台稳像、机器人里程计、传送带测速的开发者,只要你的 MCU 带编码器接口(大多数 STM32F1/F4/G0/H7 都有),就能用。下面从选型、配置到代码,一步步拆开讲。
2. 定时器选型与 CubeMX 配置:把编码器接口模式跑起来
2.1 哪些定时器能接编码器,怎么选
不是所有定时器都能做编码器接口。STM32 里通常 TIM1、TIM2、TIM3、TIM4、TIM5、TIM8 这类带「Encoder Interface」功能的通用/高级定时器才行,具体要看对应型号的参考手册。选型时我一般按三条来定:
第一,看引脚是否方便布线。编码器 A、B 两路要接到定时器的 CH1 和 CH2,这两个通道必须能映射到同一组复用功能上。第二,看计数位宽。16 位定时器在高速正反转场景下容易溢出,如果你电机转速高、编码器线数多,优先选 32 位的 TIM2 或 TIM5。第三,看是否和 PWM 输出冲突。做闭环控制时,测速定时器和驱动 PWM 的定时器要分开,别抢同一个。
一个常见的坑是:有人把编码器接到 TIM1 的 CH1/CH2,结果发现 TIM1 同时还在输出互补 PWM,配置互相打架。稳妥做法是测速和驱动各用一个定时器。
2.2 CubeMX 里的关键几项配置
在 CubeMX 里把定时器设为 Encoder Mode 后,有几个参数必须手动确认,默认值不一定对:
| 配置项 | 推荐值 | 说明 |
|---|---|---|
| Encoder Mode | TI1 and TI2 | 两路都参与计数,实现四倍频 |
| Counter Period (ARR) | 65535 | 16 位定时器拉满,减少溢出 |
| Counter Mode | Up | 编码器模式下方向由硬件判断 |
| Input Filter (IC1/IC2) | 6~10 | 滤掉编码器抖动毛刺 |
| Pull-up/Pull-down | 按编码器输出类型定 | 开漏输出必须上拉 |
输入滤波器这一项特别关键。编码器信号在电机换向、长线传输时会有毛刺,滤波器值太小会多计数,太大又会在高速时丢脉冲。我一般从 6 开始试,高速丢数就降到 3,低速抖动就加到 10。
配置完成后生成代码,HAL 库会自动调用HAL_TIM_Encoder_Start()的初始化路径,但启动函数要你自己在 main 里调。
2.3 最小可运行代码:读计数、算方向、清零
下面这段是能直接跑的最小骨架,放在主循环里定时调用即可:
/* 编码器测速最小实现,定时器已配置为 Encoder Mode */ #include "stm32f1xx_hal.h" extern TIM_HandleTypeDef htim2; // 编码器接口定时器 #define ENCODER_PPR 500 // 编码器每转脉冲数(单相) #define GEAR_RATIO 1.0f // 减速比,直连填 1.0 #define SAMPLE_PERIOD_MS 10 // 采样周期,单位毫秒 static int16_t last_cnt = 0; /* 启动编码器接口,只需调用一次 */ void Encoder_Init(void) { HAL_TIM_Encoder_Start(&htim2, TIM_CHANNEL_ALL); __HAL_TIM_SET_COUNTER(&htim2, 0); // 清零计数器 last_cnt = 0; } /* 周期调用,返回转速,单位 RPM */ float Encoder_GetSpeed_RPM(void) { int16_t cur_cnt = (int16_t)__HAL_TIM_GET_COUNTER(&htim2); int16_t diff = cur_cnt - last_cnt; // 有符号差值,正负代表方向 last_cnt = cur_cnt; /* 四倍频:硬件已按 4 倍计数,这里除以 4 还原单相脉冲数 */ float pulses = diff / 4.0f; float revs = pulses / (ENCODER_PPR * GEAR_RATIO); float rpm = revs * (60000.0f / SAMPLE_PERIOD_MS); return rpm; }逻辑说明:__HAL_TIM_GET_COUNTER读的是硬件四倍频后的计数值,所以要先除以 4 还原成单相脉冲数,再除以每转脉冲数得到转数,最后按采样周期换算成每分钟转速。diff用有符号 16 位,正负直接反映正反转,不需要额外读方向引脚。
参数说明:ENCODER_PPR必须按你编码器手册填,填错速度会成比例偏差;SAMPLE_PERIOD_MS要和实际调用周期一致,否则 RPM 换算全错;GEAR_RATIO是电机到输出轴的减速比,直连就填 1.0。
提示:这段代码假设采样周期内计数不会超过 int16 范围。高速场景下要么缩短采样周期,要么改用 32 位定时器。
3. 从计数值到转速:四种测速算法的取舍与参数
3.1 M 法、T 法、M/T 法到底怎么选
测速算法不是随便挑一个就行,它直接决定低速和高速下的精度表现。
M 法(固定时间数脉冲):在固定时间窗口内数编码器脉冲。高速时脉冲多,精度高;低速时脉冲少,量化误差大,转速可能一直显示 0。实现最简单,就是上面那段代码。
T 法(固定脉冲测时间):测两个脉冲之间的时间间隔,用定时器捕获。低速时时间长,测得更准;高速时时间短,捕获分辨率不够,误差反而大。
M/T 法:同时数脉冲和测时间,高低速都能兼顾,但实现复杂,要两个定时器配合。
我一般这样定:转速范围宽、要求全速段精度,用 M/T 法;只在中高速段工作,M 法足够;极低速(比如每分钟几转)才考虑 T 法。
3.2 采样周期的三个档位与对应误差
采样周期不是越短越好。太短,低速时脉冲数太少,量化误差大;太长,动态响应慢,闭环控制会振荡。下面是我在 500 线编码器上实测的经验档位:
| 采样周期 | 适用转速范围 | 低速量化误差 | 动态延迟 |
|---|---|---|---|
| 5 ms | 300 RPM 以上 | 较大 | 低 |
| 10 ms | 60~300 RPM | 中等 | 中等 |
| 50 ms | 60 RPM 以下 | 小 | 高 |
量化误差的估算方法是:一个脉冲对应的转速 = 60 / (PPR × 4 × 采样周期秒)。比如 500 线、10ms 周期,一个脉冲约等于 3 RPM,也就是说低于 3 RPM 根本分辨不出来。
3.3 溢出处理与方向判断的代码细节
16 位定时器在 65535 处会回绕,如果采样周期内正好跨过回绕点,直接相减会得到一个巨大的错误值。处理办法是用有符号差值,让回绕自然抵消:
/* 带溢出保护的速度计算,适用于 16 位定时器 */ float Encoder_GetSpeed_Safe(void) { static uint16_t last = 0; uint16_t now = __HAL_TIM_GET_COUNTER(&htim2); /* 用 uint16 相减再转 int16,回绕时结果依然正确 */ int16_t diff = (int16_t)(now - last); last = now; /* 单次采样跳变超过阈值,判定为异常,丢弃本次 */ if (diff > 20000 || diff < -20000) { return 0.0f; } float rpm = (diff / 4.0f) / ENCODER_PPR * (60000.0f / SAMPLE_PERIOD_MS); return rpm; }这里的关键是(int16_t)(now - last)。无符号相减在回绕时结果仍然正确,转成有符号后正负就是方向。阈值判断是防止编码器断线或强干扰导致的跳变,超过 20000 基本可以确定是异常值,直接丢弃比输出错误转速更安全。
方向判断不需要额外读引脚,diff的符号就是方向。如果你需要单独的方向标志位,可以diff >= 0 ? 1 : -1。
4. 避坑与排查:编码器测速最常见的五个翻车现场
4.1 计数只增不减,方向永远为正
现象:无论电机正转反转,CNT 一直增加,速度符号不变。
原因:A、B 两相接反了,或者其中一相没接好。编码器接口靠两相相位差判断方向,缺一相或接反,硬件无法识别方向。
解决:交换 A、B 两相的接线,或者检查 CubeMX 里 CH1/CH2 的引脚映射是否和实际接线一致。用示波器看两路波形,确认相位差是 90 度而不是 0 或 180 度。
4.2 静止时计数缓慢漂移
现象:电机不转,CNT 值却在小幅波动,速度显示零点几 RPM。
原因:编码器信号毛刺被当成有效边沿,或者输入滤波器设得太小。
解决:把 IC1/IC2 的 Input Filter 从默认值调到 6~10;检查编码器供电是否干净,信号线是否和电机动力线捆在一起;必要时在 A、B 对地加 100nF 电容做硬件滤波。
4.3 高速时速度明显偏低
现象:低速测得准,一上高速读数就比实际小一截。
原因:采样周期内计数溢出,或者输入滤波器太大把高速脉冲滤掉了。
解决:先确认定时器是 16 位还是 32 位,16 位在高速下容易溢出,换 32 位定时器或缩短采样周期;再把滤波器值降下来,从 10 降到 3 试试。
4.4 转速换算结果差整数倍
现象:速度值总是实际值的 2 倍或 4 倍。
原因:四倍频没还原。硬件按四倍频计数,代码里忘了除以 4,或者编码器手册给的 PPR 是四倍频后的值,你又除了一次。
解决:确认编码器手册里 PPR 的定义是单相脉冲数还是四倍频后计数。单相 500 线,四倍频后是 2000 计数每转,代码里除以 4 再除以 500 才对。
4.5 闭环控制时电机啸叫或振荡
现象:接入速度环后电机发出高频啸叫,或者转速来回摆动。
原因:采样周期太长导致相位滞后,或者速度反馈噪声大,PID 参数过激。
解决:把采样周期从 50ms 缩到 10ms 甚至 5ms;在速度反馈上加一阶低通滤波;先把 PID 的 P 降下来,I 慢慢加,别一上来就给大参数。
5. 进阶技巧:用滑动平均和定时器中断把测速做稳
5.1 为什么主循环轮询不够稳
前面所有代码都假设你在固定周期调用测速函数。如果放在主循环里靠HAL_Delay控制节奏,一旦有其他任务插进来,采样周期就飘了,RPM 换算直接失准。做闭环控制时这种抖动是致命的。
正确做法是把测速放进定时器更新中断,让硬件保证采样周期严格固定。用一个专门的基准定时器(比如 TIM6),每 10ms 触发一次中断,在中断里读编码器计数、算速度、更新全局变量。
/* 基准定时器中断回调,每 SAMPLE_PERIOD_MS 触发一次 */ volatile float g_speed_rpm = 0.0f; void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == TIM6) { g_speed_rpm = Encoder_GetSpeed_Safe(); } }这样主循环只管读g_speed_rpm,采样节奏由硬件保证,不受任务调度影响。
5.2 滑动平均滤波的窗口长度怎么定
速度反馈里的噪声主要来自编码器抖动和量化误差。加一个滑动平均能明显改善,但窗口太长会引入滞后。我的经验是窗口长度取 4~8,对应 40~80ms 的等效延迟,对大多数电机控制够用。
/* 8 点滑动平均,窗口越小响应越快,越大越平滑 */ #define FILTER_WIN 8 static float buf[FILTER_WIN] = {0}; static uint8_t idx = 0; float Speed_Filter(float raw) { buf[idx] = raw; idx = (idx + 1) % FILTER_WIN; float sum = 0.0f; for (int i = 0; i < FILTER_WIN; i++) { sum += buf[i]; } return sum / FILTER_WIN; }窗口长度和响应速度是 trade-off。如果你做的是高动态的云台或机械臂,窗口取 4;如果是传送带这种慢过程,取 8 甚至 16 都行。别在启动阶段用滤波后的值做判断,前几个周期缓冲区还没填满,输出会偏低。
5.3 验证测速准不准的一个笨办法
调完之后怎么确认读数是对的?我的习惯是手动转一圈。把电机断电,在输出轴上做个标记,手动匀速转一整圈,同时用调试器看累计计数。500 线编码器四倍频后,转一圈 CNT 应该变化 2000。如果对不上,先查 PPR 和倍频,再查机械连接有没有打滑。
另一个办法是给定一个已知转速,比如用另一台校准过的测速仪对比,误差在 2% 以内就算合格。我踩过的最大坑是编码器联轴器松动,低速时看着正常,一上高速就打滑,速度读数忽高忽低,查了半天代码才发现是机械问题。所以每次测速异常,先排除机械,再怀疑代码。
希望帮到你。
本文还有配套的精品资源,点击获取