资讯动态

STM32编码器测速实战:从脉冲计数到速度闭环的完整链路解析

发布时间:2026/10/27 1:06:36 来源:尧图企业网站定制
1. 从脉冲到速度一个被低估的认知断层很多人第一次用STM32接编码器代码跑通了串口也能打印出数但盯着那个不断跳动的数字心里其实是虚的——这个数到底代表什么它和电机转了多少圈、现在跑多快中间隔着什么我见过太多项目卡在这一步硬件没问题程序也能跑但速度环调不稳或者位置算出来总是差那么一点。根子往往不在PID参数上而在于对“编码器读到的到底是什么”这件事没有真正想透。编码器输出的本质是脉冲而我们在控制里需要的是速度和位置。从脉冲到速度中间要经过方向判别、计数、单位换算、时间基准、滤波这一整套链路。任何一个环节理解偏了后面全是歪的。这篇文章就围绕这条链路展开把增量式编码器、AB相正交解码、STM32定时器编码器模式、M法/T法测速、PWM调速之间的关系一层层拆开。适合正在做电机控制、云台、机器人底盘、CNC进给的朋友也适合刚接触STM32定时器、对编码器模式一知半解的初学者。看完你应该能自己回答我的编码器每转一圈到底产生多少脉冲我的速度值是怎么算出来的为什么我的速度读数会抖。2. 编码器输出的物理本质与信号形态2.1 增量式编码器的三码道结构市面上最常见的增量式编码器内部核心是一个带狭缝的码盘加光电对管或者磁编用霍尔/磁阻元件对着多极磁环。所谓“三码道”指的是A相、B相、Z相三路输出。A和B是两路方波它们的频率相同但相位差90度电角度这就是“正交”的由来。Z相每转一圈输出一个脉冲用来做机械零位校准。为什么是90度而不是180度因为只有正交才能同时判方向和倍频。如果A、B同相你只能数脉冲个数无法知道是正转还是反转。90度相位差让两路信号组合出四种状态00、01、11、10正转时按这个顺序循环反转时逆序。STM32的编码器接口就是靠这个状态机来硬件判向和计数的不需要你在中断里手动判断A和B谁先谁后。注意有些低成本编码器标称“AB相”但实际输出的是两路同频同相或相位差不是严格90度的信号这种接上STM32编码器模式后计数会乱。选型时务必确认是标准正交输出。2.2 分辨率、线数与倍频的关系编码器参数里常看到“500线”“1000线”“2500线”这个“线”指的是码盘一圈的物理狭缝数也就是每转A相或B相输出的脉冲数。但STM32编码器模式默认工作在四倍频因为它对A、B的每个边沿都计数。所以实际计数分辨率是线数的4倍。举个例子一个500线的编码器A相每转输出500个脉冲B相同样500个但相位差90度。四倍频后定时器每转会计数 500 × 4 2000 个计数。如果电机轴和编码器轴之间还有减速比比如减速箱是30:1那输出轴转一圈编码器转30圈定时器计数就是 2000 × 30 60000。这个数字直接决定了你的位置精度和速度计算的最小分辨单位。很多人算速度时直接用“脉冲数/时间”但忘了乘4、忘了减速比、忘了轮径最后速度值差几倍甚至几十倍。我建议在代码里把这些系数单独定义成宏别散落在各处。2.3 集电极开路、推挽与差分输出编码器输出级常见三种集电极开路OC、推挽、差分RS422。OC输出需要外接上拉电阻才能形成高电平上拉电阻值影响边沿陡峭程度和功耗。推挽输出驱动能力强适合短距离直接接MCU。差分输出抗干扰最好长线传输时用但需要接收芯片转成单端再进STM32。如果你用OC输出的编码器直接接STM32没加上拉读到的可能是常低或者波形严重变形计数自然不对。上拉电阻一般选1k到4.7k太小功耗大太大边沿变缓高速时丢脉冲。这个细节在调试时经常被忽略尤其是从别人那里拿来的板子编码器接口没焊上拉查半天以为是程序问题。3. STM32定时器编码器模式的工作机制3.1 为什么用硬件编码器接口而不是外部中断最原始的做法是把A相或B相接外部中断在中断里读另一相电平判方向然后手动加减计数。这种方法在低速时能用但脉冲频率一高中断开销就爆炸。假设编码器1000线、四倍频、电机3000转/分脉冲频率是 1000×4×3000/60 200kHz。每5微秒一次中断STM32基本不用干别的了。STM32的定时器编码器模式把判向和计数全部交给硬件CPU只需要定期读计数器值。这是它最大的价值。你配置好TIM的编码器模式后A、B两相接到定时器的两个输入通道上硬件自动根据边沿和电平关系增减CNT。你读CNT就是位置读两次CNT做差就是位移。3.2 编码器模式的配置要点以STM32F1/F4常见的TIM2、TIM3、TIM4为例配置步骤大致是使能定时器时钟和GPIO时钟把对应引脚配成浮空输入或上拉输入配置TIM_EncoderInterfaceConfig选择编码器模式TI1、TI2或both edge设置ARR为最大计数值使能定时器。关键点有几个。第一ARR决定计数范围。16位定时器ARR最大65535如果你正反转来回转CNT会在0和65535之间回绕。读位置时要做溢出处理否则从65535跳到0会算出一个巨大的负速度。第二输入滤波。TIM的ICFilter可以设置滤波带宽编码器信号有毛刺时能滤掉但设太大高速时会丢脉冲。第三极性。如果发现正转计数减小把其中一个通道的极性反过来就行不用改硬件接线。// 以TIM3为例的编码器模式初始化片段 TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_ICInitTypeDef TIM_ICInitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // PA6-TIM3_CH1, PA7-TIM3_CH2 GPIO_InitStructure.GPIO_Pin GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOA, GPIO_InitStructure); TIM_TimeBaseStructure.TIM_Period 65535; TIM_TimeBaseStructure.TIM_Prescaler 0; TIM_TimeBaseStructure.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseInit(TIM3, TIM_TimeBaseStructure); TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising); TIM_ICStructInit(TIM_ICInitStructure); TIM_ICInitStructure.TIM_ICFilter 6; TIM_ICInit(TIM3, TIM_ICInitStructure); TIM_Cmd(TIM3, ENABLE);这段代码里TIM_EncoderMode_TI12表示A、B两相都计数也就是四倍频。TIM_ICFilter6是中等滤波具体值要根据你的脉冲频率和干扰情况调。3.3 计数方向与溢出处理读位置时我习惯用int16_t或int32_t来接收CNT然后做差分。如果两次读取之间CNT从65500跳到100说明正向溢出了一次实际位移是 (65536 - 65500) 100 136。反过来从100跳到65500就是负溢出。这个逻辑必须写对否则高速时位置会突然跳变。更稳妥的做法是用一个累计位置变量每次读CNT后计算差值把差值累加进去同时更新上一次的CNT。这样即使溢出也能正确处理。注意差值要用有符号数计算并且考虑16位回绕。4. 从脉冲计数到速度值的换算方法4.1 M法测速固定时间读脉冲数M法是最直观的每隔固定时间T读一次编码器计数得到Δcount速度 Δcount / T。这个方法的优点是低速时也能测缺点是T固定时低速分辨率差。比如T10ms编码器四倍频后每转2000计数电机1转/分时10ms内只走了 2000/60×0.01 ≈ 0.33 个计数四舍五入就是0或1速度读数会跳。M法适合中高速场合。T的选择要权衡T太小低速分辨率差T太大速度响应滞后。一般T取5ms到50ms根据你的控制周期来定。如果速度环周期是1ms你可以每10个周期算一次速度或者用滑动平均。4.2 T法测速固定脉冲数测时间T法反过来记录编码器走过固定脉冲数比如1个脉冲所用的时间速度 固定脉冲数 / 时间。低速时一个脉冲的时间长定时器计数值大分辨率高高速时时间短分辨率差。所以T法适合低速。实际工程里常用M/T法结合高速用M法低速用T法中间过渡。但实现复杂一些。对于大多数STM32电机控制项目M法加滑动滤波已经够用。4.3 单位换算从count到rpm和m/s假设编码器500线四倍频减速比30轮径65mm。每输出轴转一圈的计数是 500×4×30 60000。如果10ms内读到Δcount600那转速 600/60000 转/10ms 0.01转/10ms 1转/秒 60rpm。线速度 60rpm × π × 0.065m / 60 0.204 m/s。把这些系数写成宏#define ENCODER_LINES 500 #define GEAR_RATIO 30 #define WHEEL_DIAMETER_M 0.065 #define COUNTS_PER_REV (ENCODER_LINES * 4 * GEAR_RATIO) #define SPEED_SAMPLE_TIME 0.01f // 10ms float rpm (delta_count / (float)COUNTS_PER_REV) / SPEED_SAMPLE_TIME * 60.0f; float mps rpm * 3.1415926f * WHEEL_DIAMETER_M / 60.0f;这样改参数时只改宏不用满代码找系数。4.4 速度滤波为什么你的速度读数在抖原始M法速度一定有抖动因为编码器脉冲和采样时钟不同步Δcount本身有±1的量化误差。低速时这个误差占比大速度读数可能在一半范围内跳。解决办法有几种滑动平均、一阶低通、卡尔曼。滑动平均最简单取最近N次速度的平均值N取5到10。一阶低通是 y α×x (1-α)×y_prevα取0.1到0.3。卡尔曼效果好但调参麻烦。我一般先用滑动平均如果还抖再上低通。注意滤波会引入相位滞后速度环带宽高时不能滤太狠否则系统会振荡。5. PWM调速与编码器闭环的配合5.1 PWM频率与编码器采样的关系PWM调速的本质是改变电机平均电压从而改变转速。PWM频率一般选10kHz到20kHz低于20kHz可能听到啸叫高于20kHz开关损耗大。编码器采样周期和PWM周期是独立的但速度环周期通常是PWM周期的整数倍比如PWM 10kHz速度环1kHz每10个PWM周期调一次速度。这里有个坑如果PWM频率和编码器脉冲频率产生某种整数关系速度读数可能出现周期性波动。我遇到过PWM 8kHz、编码器脉冲频率接近8kHz时速度值有规律地跳。后来把PWM改成12kHz就好了。所以调试时如果发现速度有规律抖动不妨换个PWM频率试试。5.2 死区、限幅与积分饱和PWM输出到H桥驱动时上下桥臂之间要留死区防止直通。死区时间一般几百纳秒到1微秒具体看MOS管和驱动芯片。死区会损失一点有效电压低速时影响明显可能需要死区补偿。速度环PID输出要限幅到PWM的占空比范围比如0到100%。积分项要抗饱和否则电机堵转时积分会累积到很大松开后猛冲。常用做法是积分分离或者输出限幅时停止积分。5.3 编码器反馈极性对速度环的影响如果编码器反馈的速度符号和PWM输出的方向相反速度环会变成正反馈一给指令就飞车。判断方法手动给一个正PWM看编码器速度读数是否为正。如果为负要么把编码器计数方向反过来要么把PWM输出极性反过来。这个必须在闭环前确认否则上电就失控。6. 常见问题与排查技巧实录6.1 编码器计数不动或只增不减先查硬件A、B相有没有接反、有没有上拉、供电是否正常。用示波器看A、B波形应该是两路方波相位差90度幅值干净。如果波形有毛刺加滤波电容或调TIM的ICFilter。如果波形正常但计数不动检查TIM编码器模式配置特别是TIM_EncoderInterfaceConfig的参数和GPIO复用功能是否使能。只增不减通常是方向判别失效可能是A、B接在了同一个通道或者其中一个通道没配成编码器输入。也有可能是编码器本身AB相相位差不是90度这种只能换编码器。6.2 速度值跳变或溢出速度跳变最常见的原因是CNT溢出没处理。16位定时器在正反转频繁时很容易溢出。解决办法是用32位累计位置或者换32位定时器。另一个原因是采样周期不稳定比如在中断里算速度但中断被高优先级任务延迟了。用定时器硬件触发采样或者记录实际时间间隔来补偿。如果速度值偶尔出现极大值检查是否有丢脉冲或额外脉冲。丢脉冲可能是滤波太狠或信号边沿太缓额外脉冲可能是干扰。示波器抓波形最直接。6.3 低速时速度读数为零或抖动这是M法的固有缺陷。低速时Δcount太小量化误差大。解决办法增大采样时间T或者低速切换到T法或者用位置差分加低通。如果项目对低速性能要求高建议用T法或M/T法。另外编码器分辨率越高低速性能越好但成本也高。6.4 PWM干扰编码器信号PWM开关时dv/dt很大容易通过空间或电源耦合到编码器信号线上。表现是电机一转速度读数就乱不转就正常。解决办法编码器线用屏蔽线并单端接地PWM线远离编码器线电源加去耦电容必要时用差分编码器。我遇到过PWM背光干扰显示的情况原理类似都是开关噪声耦合。问题现象可能原因排查方法解决措施计数不动接线错误、无上拉、模式未使能示波器看波形、查GPIO配置修正接线、加上拉、使能TIM只增不减AB相接错、相位差不对交换AB相测试换编码器或改接线速度跳变CNT溢出、采样不稳打印CNT原始值32位累计、硬件触发采样低速为零M法分辨率不足增大T或换T法提高编码器线数、M/T法PWM干扰开关噪声耦合电机转时看波形屏蔽线、远离、去耦6.5 实操心得先开环验证再闭环我的习惯是先把编码器读数调通手动转电机轴看计数是否正比于转角方向是否正确。然后开环给PWM看速度读数是否随占空比变化。确认这两步都没问题再上闭环。很多人跳过开环验证直接调PID结果方向反了或者计数不对PID怎么调都不对浪费大量时间。另一个心得是编码器线不要和电机线捆在一起走尤其是大电流电机线。我见过一个底盘项目编码器线和电机线在同一线槽里电机一加速速度就乱跳分开走线后立刻正常。7. 几个容易混淆的概念澄清7.1 编码器分辨率与精度不是一回事分辨率是能分辨的最小角度由线数和倍频决定。精度是读数与真实角度的偏差受码盘刻线误差、安装偏心、信号质量影响。高分辨率不等于高精度。一个500线编码器如果安装偏心精度可能还不如一个安装良好的250线编码器。选型时不要只看线数。7.2 绝对式与增量式的选择增量式编码器便宜、接口简单但断电后位置丢失需要回零。绝对式编码器断电后位置保留但贵、接口复杂SSI、BiSS、I2C等。如果项目不需要断电记忆位置增量式足够。如果需要可以考虑带Z相回零或者加电池备份的绝对值编码器。磁编码器AS5047P这类芯片是绝对值输出SPI或ABI接口适合空间受限的场合。7.3 编码器模式与PWM模式的资源冲突STM32的同一个定时器不能同时做编码器模式和PWM输出。如果你用TIM3做编码器那TIM3的PWM输出就不能用了。选定时器时要规划好一般TIM1、TIM8做PWMTIM2、TIM3、TIM4做编码器。如果编码器路数多可以考虑用外部计数芯片或者软件解码但软件解码高速时吃力。8. 从脉冲到速度的完整代码框架把前面这些串起来一个典型的速度采集框架是这样的TIM编码器模式初始化定时器中断或主循环定时采样读CNT算差分累计位置算速度滤波输出给速度环。下面是一个简化的框架示意。volatile int32_t g_position 0; volatile int16_t g_last_cnt 0; volatile float g_speed_rpm 0.0f; void Encoder_Update(void) // 每10ms调用一次 { int16_t now_cnt (int16_t)TIM3-CNT; int16_t diff now_cnt - g_last_cnt; g_last_cnt now_cnt; g_position diff; float delta_count (float)diff; float rpm (delta_count / (float)COUNTS_PER_REV) / SPEED_SAMPLE_TIME * 60.0f; // 一阶低通 static float rpm_filtered 0.0f; rpm_filtered 0.2f * rpm 0.8f * rpm_filtered; g_speed_rpm rpm_filtered; }这个框架里g_position是累计位置g_speed_rpm是滤波后速度。注意diff用int16_t接收因为CNT是16位差分结果在-32768到32767之间正好用int16_t。如果采样周期内位移超过这个范围说明速度太快或者采样太慢需要缩短周期或换32位定时器。9. 调试工具与观测手段调试编码器问题示波器是最有用的。四通道示波器同时看A、B、PWM和电源能快速定位是信号问题还是电源问题。如果没有示波器可以用逻辑分析仪便宜的那种也能看波形和时序。STM32的定时器还可以配置成输入捕获模式测量脉冲宽度和周期用来验证编码器信号频率。串口打印也是常用手段但要注意打印本身会占用时间高速时可能影响采样。建议用DMA串口或者把数据存到数组里批量打印。另外用SWD调试器实时看变量比串口更方便不占用CPU时间。我个人习惯在调试初期用示波器确认波形中期用SWD看变量后期用串口记录数据做分析。不同阶段用不同工具效率最高。10. 写在最后的一点个人体会编码器这个东西原理不复杂但细节特别多。从线数、倍频、减速比、轮径到CNT溢出、滤波、PWM干扰每一个环节都可能让你调半天。我的经验是先把物理量和计数的关系在白纸上算清楚写进代码宏定义里然后开环验证编码器读数确认方向和比例都对最后再上闭环调PID。跳过任何一步后面都要还债。还有一点别迷信高线数。线数越高同样转速下脉冲频率越高对MCU和信号质量要求越高。够用就行留点余量。我见过用2500线编码器做低速云台脉冲频率不高但信号处理没做好照样丢脉冲。反过来500线编码器配合好的滤波和M/T法低速也能做得很稳。最后分享一个小技巧如果速度读数在某个特定转速下特别抖试试改变速度采样周期让它和PWM周期错开。很多时候抖动是采样和开关频率拍频造成的错开就好了。这个在电机控制里很常见但文档里很少写。

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

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

免费获取报价 →
↑