资讯动态

STM32正交解码编码器测速实战:从定时器配置到转速计算与避坑

发布时间:2026/10/15 15:45:00 来源:尧图企业网站定制
简介这份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 ModeTI1 and TI2两路都参与计数实现四倍频Counter Period (ARR)6553516 位定时器拉满减少溢出Counter ModeUp编码器模式下方向由硬件判断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 ms300 RPM 以上较大低10 ms60~300 RPM中等中等50 ms60 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% 以内就算合格。我踩过的最大坑是编码器联轴器松动低速时看着正常一上高速就打滑速度读数忽高忽低查了半天代码才发现是机械问题。所以每次测速异常先排除机械再怀疑代码。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑