ARTICLE DETAIL

资讯详情

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

STM32定时器编码器模式实现电机测速与正交解码实战

STM32定时器编码器模式实现电机测速与正交解码实战 最近在调一台直流减速电机的闭环控制手头既没有专用的编码器计数模块也不想在代码里写一堆外部中断去读AB相。翻了一圈发现STM32CubeMX里定时器有个编码器模式把硬件正交解码完全封装好了直接配置一下就能数脉冲。实测跑下来整个测速链路确实省心很多但配置过程中也踩了不少坑很多坑在教程里根本不会提到。这篇就把我的配置过程、转速换算思路和踩坑记录完整整理出来给正在做电机测速、转台角度反馈或者旋钮输入的朋友一个参考。这个方案适合用STM32F1/F4/G0/G4系列、通过CubeMX生成HAL库代码、需要同时判断方向和测量转速的场景。不管你是刚上手的小白还是已经用外部中断做过一版测速、想换成硬件解码的老手这篇文章都会有点价值。1. 为什么编码器模式是电机测速的最优解1.1 从“外部中断GPIO”的土办法说起很多人在做电机转速测量的第一版时习惯用外部中断GPIO读电平这种土办法把编码器的A相接外部中断引脚每次A相跳变沿触发中断然后在中断里读B相电平电平高就加一电平低就减一。这个思路本身没问题简单直接在低速、脉冲频率不高的场合也能跑通。但实际项目里很快就会遇到几个麻烦。第一个是CPU负载编码器分辨率稍微高点比如1000线的光电编码器四倍频后每转4000个脉冲如果电机转速是3000转/分每秒就是20万个中断。这么高频率的中断主循环啥也别干了光进出中断堆栈就够呛。第二个是漏计数中断嵌套、临界区保护稍微没做好脉冲就丢了转速数据忽高忽低。第三个是方向判断要自己写逻辑代码看似简单边界情况却不少。定时器的编码器模式被设计出来就是为了解决这些问题的。它把正交解码做成了纯硬件功能所有边沿检测、方向判断、加减计数全部由定时器电路完成CPU只是在需要的时候读一下计数器而已。1.2 正交解码的硬件原理定时器里到底发生了什么要理解编码器模式得先理解正交编码器输出的是什么信号。常见的增量式编码器输出两路方波一路叫A相一路叫B相。当电机正转时A相上升沿到来时B相电平是低电平反转时则相反。A相和B相的相位差固定是90度所以叫“正交”信号。定时器的作用就是捕捉这个相位关系。STM32的通用定时器内部硬件会同时监视两个输入通道TI1和TI2在每个有效边沿到来时它会检测另一个通道的电平状态然后决定计数器是加一还是减一。正转时检测到方向位为正向就累加反转时方向位翻转计数器就递减完全不需要软件干预。这里有个经常被忽视的点编码器模式支持三种计数方式。只对TI1的边沿计数是1倍频只对TI2的边沿计数也是1倍频同时对TI1和TI2的所有边沿计数就是4倍频。用4倍频时原本电机每转输出N个脉冲的四倍频计数器每转能得到4N个计数。这个倍数直接影响后面转速换算的参数配置前一定要确认好。1.3 编码器模式 vs 输入捕获 vs PWM输入别选错很多新手会把编码器模式、输入捕获和PWM输入混在一起因为这几个功能在CubeMX里都跟定时器输入有关。简单区分一下输入捕获适合单路脉冲测频率或测脉宽不关心方向常用于测外部方波频率、红外遥控解码。PWM输入模式其实是输入捕获的一种特殊用法用两个通道同时捕获周期和占空比适合解码遥控器或转速传感器的PWM信号。编码器模式专门处理两路正交信号既能测速度也能测位置还能判断方向。如果你手里的是输出单路脉冲的霍尔传感器或者NPN型转速探头老老实实用输入捕获测频就行。只有当你拿到的是AB两相正交输出的编码器才需要用到定时器的编码器模式。选错模式的结果就是配置界面长得完全不一样生成的代码也完全不匹配。2. STM32CubeMX完整配置流程与参数解读2.1 引脚分配与定时器通道绑定打开STM32CubeMX首先要确定用哪个定时器。编码器模式必须占用同一个定时器的两个输入通道比如TIM3的CH1和CH2对应PA6和PA7TIM4的CH1和CH2对应PB6和PB7。这里要注意必须是同一个定时器下的两个通道不能把A相接TIM3的CH1、B相接TIM4的CH2这样配不出编码器模式。在CubeMX左侧的Pinout Configuration面板里找到Timers展开你要用的定时器比如TIM3在Combined Channels下拉框里选择Encoder Mode。这一步选择完成CubeMX会自动把两个通道分配成编码器输入模式引脚绑定也能在芯片图上看到。还有一个容易踩的坑如果编码器是开漏输出CubeMX里对应的引脚默认可能是浮空输入模式。此时必须手动把这两个引脚的GPIO模式改成Pull-up否则信号高电平无法稳定读取计数会疯狂跳变。这个细节很多教程不提但实际上非常关键。2.2 关键参数逐项拆解配置完通道进入Parameter Settings你会看到一大串参数。逐个说明一下我的配置和原因Prescaler预分频器编码器模式下我直接填0。很多人以为这是用来降频的实际上编码器模式的计数时钟来源是外部输入的边沿信号预分频器对正交解码不起分频作用。填0保持每个有效边沿都计数。Counter Mode计数方向模式编码器模式下这个字段基本没用因为方向完全由硬件根据AB相相位关系决定。Counter Period自动重装载值。我习惯填65535也就是让计数器在0到65535之间自由循环。这样计数器相当于一个16位环形计数器配合有符号差值计算处理正反转溢出会很方便。后面转速计算的章节会详细解释。Internal Clock Division时钟分频保持默认DIV1即可编码器模式下一般用不到。Auto Reload Preload自动重装载预装载选Disable。这个参数在PWM场景影响很大但编码器模式就是单纯数脉冲不需要预装载缓冲。Encoder Mode这里选择TI1 and TI2也就是双通道四倍频计数。如果选TI1 only就只有A相边沿计数相当于二倍频而且方向判断会退化成只看B相电平虽然能用但在抗干扰和精度上都弱一些。Input Filter这一项容易被忽略。如果你的电机带碳刷或者PWM调速编码器信号会有毛刺可以在IC1Filter和IC2Filter里填一个滤波值比如0x0F甚至0x3F。滤波的原理是只有信号电平持续稳定N个时钟周期才认为电平有效毛刺宽度不够会被过滤掉。代价是滤波会引入一点延迟对普通电机测速来说完全无感但对抗干扰帮助极大。2.3 生成的初始化代码怎么改才不出问题CubeMX生成代码后在tim.c里能看到MX_TIM3_Init函数包含一个TIM_Encoder_InitTypeDef结构体和一个TIM_MasterConfigTypeDef结构体。前者配置编码器模式后者配置主从模式一般来说不用改。改代码之前要先明白一件事CubeMX生成的MX_TIM3_Init只是初始化定时器寄存器并没有启动编码器计数。真正的启动需要手动调用HAL_TIM_Encoder_Start(htim3, TIM_CHANNEL_ALL);这条语句建议放在main函数里外设初始化都完成之后。启动后计数器就开始跟着编码器转了。还有一个我实际用得比较顺手的初始化习惯编码器启动前先把计数器清零确保每次上电都从零开始测__HAL_TIM_SET_COUNTER(htim3, 0); HAL_TIM_Encoder_Start(htim3, TIM_CHANNEL_ALL);如果你在调试过程中反复用HAL_TIM_Encoder_Stop再重新Start建议注意一下Stop会停止硬件解码期间到达的脉冲就丢了。调试归调试正式运行的逻辑里不要频繁启停编码器通道。3. 转速计算模型与软件实现3.1 每转计数值counts_per_rev到底怎么算有了计数器读数转速计算的前提是搞清楚“每个计数对应的物理角度”。这个参数叫counts_per_rev也就是输出轴每转一圈计数器会产生多少个计数脉冲。计算公式如下counts_per_rev 编码器线数PPR x 倍频数 x 减速比倍频数由编码器模式决定选TI1 and TI2就是4选TI1 only就是2。电机减速比要看减速箱参数如果编码器装在电机轴上而不是输出轴上一定要把这个因素算进去。举个例子我手头这颗直流减速电机编码器是11线的霍尔编码器减速比1:30用四倍频模式。那么counts_per_rev 11 x 4 x 30 1320也就是说输出轴转一整圈定时器计数增加1320。这个数字是后续所有转速计算的地基。如果是光电编码器比如常见的光电编码器线数可能是200线或者500线对应四倍频之后是800和2000。有些编码器直接标的是每转脉冲数PPR有些标的是分辨率买的时候要看清楚别把线数和四倍频后的数值搞混。3.2 采样周期怎么定一条公式解决测速精度测速的核心思路是在一段固定时间内读取计数器增量然后用增量除以时间得出转速。设采样周期为T秒测得计数变化量为delta_count那么每秒转数RPS和每分钟转数RPM分别为RPS delta_count / counts_per_rev / T RPM RPS x 60如果采样周期用毫秒表示采样周期为T_ms那么RPM delta_count x 60000 / counts_per_rev / T_ms采样周期的选择直接影响测量精度和响应速度。原理上计数器的量化误差是正负1个计数采样周期越长1个计数在总转速中的占比越小转速读数越稳定。但采样周期太长转速变化的响应就慢了闭环控制的时候会感觉反应迟钝。我总结的实用经验是对于几十转到几千转的直流减速电机采样周期选100ms左右比较折中。如果做闭环PID调速可以选10ms到50ms转速跳动稍微大一点但响应快。如果是纯粹测速记录数据100ms到200ms读数会很好看。低速时比如每分钟只有几十转最好把采样周期加长到500ms否则每个周期只有几个计数转速算出来一跳一跳的。3.3 正反转与溢出处理不能只会读计数值读计数器很多人会但处理正反转和溢出才是真正拉开水平的地方。我的做法是先把计数器当成有符号环形计数器处理。计数器Period设置成65535意味着它从0数到65535然后再回到0。如果电机正转这一瞬间的计数值是65530下一瞬间可能变成2看起来数值变小了实际是继续往前走了4个计数。如果直接用uint16_t做减法答案是2减65530在无符号运算里会得到一个很大的数转速就废了。解决办法很简单把差值强转成int16_t。因为int16_t的范围是-32768到32767正好对应无符号16位计数器的回绕逻辑。代码是这样的uint16_t now __HAL_TIM_GET_COUNTER(htim3); int16_t diff (int16_t)(now - last_count); last_count now;只要每次采样周期内实际脉冲数不超过32767个这个差值就是准确的正转是正数反转是负数完全不用手工判断有没有溢出。手算验证一下上次是65530这次是2now - last_count在uint16_t运算里是8转成int16_t还是8反转时上次是2这次是65530now - last_count在uint16_t运算里是65528这个数转成int16_t就是-8完全正确。这个技巧看着简单但解决了很多新手在反转和回绕时算错转速的问题。3.4 完整代码示例基于上面的思路贴一份我实际用的测速代码框架用的TIM3编码器模式采样周期100ms。// 定义的每转计数值 #define COUNTS_PER_REV 1320 #define SAMPLING_MS 100 // 上一次的计数值 uint16_t last_count 0; // 主循环中执行 void speed_measure_task(void) { uint16_t now __HAL_TIM_GET_COUNTER(htim3); int16_t diff (int16_t)(now - last_count); last_count now; // 计算RPM注意中间量转浮点避免整数截断 float rotation (float)diff / COUNTS_PER_REV; float rpm rotation * 60000.0f / SAMPLING_MS; printf(diff%d, RPM%.2f\r\n, diff, rpm); }主循环里每隔100ms调用一次这个函数。定时可以用HAL_Delay也可以用单独的定时器标志位看你的系统结构。用HAL_Delay简单但因为任务自身耗时实际周期会稍微大于100ms对一般测速工程来说误差可以接受如果想要严格的时间基准建议用硬件定时器产生采样中断在中断里置标志位主循环里检测到标志位再执行测速任务。采样周期不固定的话最好用时间戳计算实际用时。现代STM32都有DWT计数器或者SysTick可以读出一个精确的时间戳然后float elapsed_ms (float)(timestamp_now - timestamp_last) / cycles_per_ms; float rpm (float)diff / COUNTS_PER_REV * 60000.0f / elapsed_ms;4. 避坑清单实测中踩过的10个坑下面这些坑是我这个项目里实际踩过、或者帮别人排查时遇到过的高频问题整理成清单照着排查能省很多时间。坑现象原因解决方案counts_per_rev算错转速偏大或偏小整倍数倍频数没算对或忘了减速比用公式重新核对PPR x 倍频数 x 减速比开漏输出没上拉计数随机跳变、转速乱跳引脚浮空高电平不确定CubeMX里把两个输入引脚设为Pull-upA/B相接到不同定时器配置界面没有Encoder Mode编码器模式必须在同一定时器的两个通道换引脚让A/B接到同一个定时器的CH1/CH2正转时计数却递减差值为负AB相接反了或极性配置反了接线对调A/B或把输入极性都反转反转时转速变成巨大正数溢出差值处理错误直接做uint16_t无符号减法用(int16_t)(now - last_count)处理低速时转速跳变各采样周期只有几个计数量化误差太大加大采样周期或选更高分辨率编码器高速时偶发错误计数信号毛刺干扰EMI干扰、共地不良配置IC Filter加滤波电容保证共地反复启停编码器通道计数丢失、测量不连续HAL_TIM_Encoder_Stop期间丢脉冲启动后不要频繁Stop工作需要时保持常开采样周期不固定转速有轻微波动HAL_Delay不准任务时间抖动用固定周期定时器置标志位或用时间戳折算上电初始计数值不为零角度位置不对编码器停在任意位置软件层做归零启动位置校准流程逐个展开说其中最经典的几个。第一个坑也是最容易忽略的输入极性配置。CubeMX的Encoder Mode配置界面里IC1Polarity和IC2Polarity默认都是Rising Edge。如果你发现电机正转但计数器在向下减两个通道的极性都改成Falling Edge就等价于交换了A/B相不用重新改接线。注意必须两个极性一起改只改其中一个会导致计数逻辑完全错乱因为硬件解码依赖两路信号的相位关系只翻转一路等于把正交信号破坏了。接线到底怎么接最省事你只要记住一个原则如果正转读数反了优先接线对调不要只想着改代码。第二个坑和滤波器相关。我一开始没配置数字滤波裸板测试时电机一启动转速读数就乱跳。用示波器看编码器输出高电平上全是毛刺明显是电机驱动带来的共模干扰。解决办法有两步硬件上在编码器电源和地之间加了一个10uF电解电容和104陶瓷电容软件上把IC1Filter和IC2Filter都配置成0x0F。滤波值太大也会有问题信号边沿会被钝化最高可测脉冲频率会下降普通减速电机场景不用顾虑这个但如果你做高速电机滤波值需要根据最大信号频率去查手册校准。第三个坑是很多人会忽略的“共地”。编码器驱动板和单片机之间如果电源不共地A/B信号的电平参考点是漂的轻则计数乱跳重则IO口损伤。电机控制项目里电源地、驱动地、单片机地最终要用星形或者粗导线统一到一个参考点。这个和配置软件无关但往往是最难排查的硬件问题之一。5. 更多的玩法与扩展5.1 位置测量与方向判断不只用来测速编码器模式计数不限于速度测量它本质上是位置测量。你只要一直保留当前计数值就能得到电机当前转过的绝对脉冲位置。结合counts_per_rev还能算出输出轴转过的角度angle_deg (current_count % COUNTS_PER_REV) * 360.0f / COUNTS_PER_REV;方向判断同样顺手。读取方向位可以这样if (__HAL_TIM_IS_TIM_COUNTING_DOWN(htim3)) { // 当前是反转 } else { // 当前是正转 }这个方向位是编码器输入的实时硬件状态比自己在软件里记录差值正负更直观。很多HMI旋钮、云台限位反馈、升降机位置检测其实就是靠这套思路实现的只是把电机换成了手轮或者转轴。5.2 多电机测速的定时器分配如果项目里有多个电机每个电机的编码器都需要一个独立的定时器因为编码器模式占用两个输入通道且同一个定时器只能做一组正交解码。以STM32F103之类的中型单片机为例TIM1、TIM2、TIM3、TIM4、TIM5等都能做编码器模式理论上可以同时接好几路电机。但要注意高级定时器的编码器模式配置相比通用定时器稍微复杂一点而且通道可能和PWM输出冲突。实际做双电机或者四电机驱控一体板时常见的方案就是把每个电机的PWM控制分配给不同的定时器输出通道编码器输入再用另外一组定时器事先做好引脚矩阵规划尽量多用复用功能不用改动引脚模块的映射。5.3 高速高分辨率场景下的32位计数扩展如果电机转速极高或者编码器分辨率很高16位计数器在单个采样周期内可能溢出多次。比如1000线四倍频就是4000个计数每转如果电机3000转/分每秒20万个计数100ms采样周期就是2万个计数还在int16_t安全范围内但要是采样周期200ms4万个计数就很接近32767上限了会出差错。这时有两种解法。第一种缩短采样周期保证单周期增量不超32767但响应变快读数也会变得更敏感。第二种开启定时器的Update中断在中断里判断方向并累计溢出次数把16位计数扩展成32位volatile int32_t overflow_count 0; void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM3) { if (__HAL_TIM_IS_TIM_COUNTING_DOWN(htim3)) { overflow_count--; } else { overflow_count; } } } int32_t get_extended_count(void) { uint16_t cnt __HAL_TIM_GET_COUNTER(htim3); return overflow_count * 65536 cnt; }注意这个读取流程存在一个极小的竞态窗口读cnt的瞬间可能刚发生溢出overflow_count和cnt不完全匹配。工程上如果对精度要求不是极端严格这个窗口的概率很低可以接受如果要求严格可以在读之前短暂关中断读完再开。我之前在一个转台定位项目里就是这么干的编码器装在转台上转台可以连续多圈旋转16位计数器早就回绕好几次了但32位扩展之后把角度位置和方向都捞了回来代码量也不大只在原来的更新中断里加了几行。这个方法强烈推荐给做云台、机械臂关节、3D打印挤出机这类需要多圈绝对位置反馈的朋友。做编码器模式测速这件事最深的体会是CubeMX把最难懂的寄存器配置变成了下拉框和勾选项但这反而让很多人跳过了“它到底是怎么工作”的思考。码了这么多字核心其实就三句话确认好每转计数值用好有符号差值处理采样周期和滤波参数要按现场调。这几件事做到位编码器测速基本不会再出幺蛾子。
返回列表