☰
STM32定时器编码器模式测速:从原理到代码实现
2026/9/28 15:07:39 网站建设 项目流程

在调试一款带霍尔编码器的直流减速电机时,我一开始图省事,直接用STM32的外部中断去数脉冲:A相接PA0,B相接PA1,每次上升沿进一次中断计数。低速空转的时候看起来挺正常,一旦电机转速拉起来,示波器上还能看到完整的方波,可单片机数出来的脉冲数却开始对不上,方向判断更是稀烂。后来我查了一圈才发现,问题不在硬件,而在测速方案本身——STM32定时器自带编码器模式,硬件自动完成脉冲计数和方向判别,根本不占CPU。这篇博文就把我从原理到代码完整走一遍,说说怎么用STM32定时器的编码器接口模式,配合光电码盘或者霍尔编码器,实现电机转速的精准测量。顺便先澄清一个容易踩的搜索坑:这里说的编码器是旋转编码器/码盘,不是深度学习里那个自编码器,也不是Transformer里的编码器结构。

1. 编码器测速的底层逻辑:为什么测脉冲比测电压靠谱

1.1 编码器到底在测什么

很多刚开始做电机控制的朋友会问:测转速为什么不直接量电机两端的电压?毕竟电压高转速就快。这个思路在没有负载的空转场合勉强能用,但一旦带上负载,电机电流增大、内阻压降变大,同样的电压实际转速会掉很多。更别说PWM调速时,占空比和平均电压的关系还会受到MOS管压降、死区时间等因素的干扰,想做精准测速基本不现实。

编码器的思路完全绕开了电压这个间接量。增量式编码器输出的是一组方波脉冲,每个脉冲对应电机轴转过的一个固定角度。只要数出单位时间内的脉冲个数,就能反推出转速。换句话说,测的是"角度变化量对时间的变化率",这是位置量的微分,物理意义直接、抗负载干扰能力强。这也是工业上几乎所有伺服电机都要装编码器的原因。

从大类上看,编码器分增量式和绝对式两种。增量式输出A、B两路方波,掉电后不知道轴在什么位置,但胜在便宜、接口简单,绝大多数直流电机测速场景都用它。绝对式编码器(比如磁编码器AS5047P)通过SPI直接读出一个绝对角度值,掉电不丢失位置,但成本高、接线多,一般用来做精确的位置闭环,普通测速项目用不上。本文后面说的都是增量式。

1.2 AB相与倍频:转一圈为什么能数出这么多脉冲

增量式编码器最核心的输出是A、B两路方波,两路信号频率相同、相位相差90度。正是这90度的相位差,让单片机可以判断电机到底是正转还是反转:如果A相超前B相90度,是正转;反过来B相超前A相,就是反转。判断方法有两种,一种是在A相上升沿读B相电平,另一种是把AB相接在定时器的编码器模式上,由硬件自动判断计数方向,后者正是STM32定时器最擅长的活。

有了AB相之后,计数分辨率还能翻倍。如果只在A相的上升沿计数,叫1倍频;在A相上升沿和下降沿都计数,就是2倍频;如果在A、B两相的上升沿和下降沿都计数,就是4倍频。4倍频之后,单位时间内的计数个数变成原始脉冲频率的4倍,能分辨的角度精度也提高到原来的4倍。

举个例子,EC11旋转编码器(就是鼠标滚轮里那种)通常一圈输出20个脉冲,4倍频后一圈能数到80个计数。而小车底盘常见的带码盘电机,码盘线数是13,电机本身转一圈码盘输出13个完整的A相周期,如果电机还带减速箱、减速比30:1,那么电机输出轴转一圈,A相输出的脉冲就是13,4倍频后就是13×4=52,再乘以减速比30,输出轴一圈总共能数到1560个计数。这个数值很重要,后面算转速全靠它。

1.3 为什么我不建议用外部中断做编码器测速

刚上手时我用外部中断测速,方法是:A相接外部中断引脚,每次A相跳变沿触发中断,在中断里读B相电平判断方向,再累加计数器。这个方法在低速下确实能跑,但有两个硬伤。

第一,CPU负担太重。假设电机输出轴转速为3000RPM,经过减速比30后电机本体转速是90000RPM,每秒1500转。按单圈52个脉冲算,A相每秒产生78000个脉冲,也就是要进78000次中断。STM32F103主频72MHz,处理一次中断即使只花1微秒,也要占用接近全部CPU时间,别说还要跑控制算法和显示,连系统调度都会被拖垮。

第二,容易丢脉冲。中断里读写寄存器需要时间,如果两次跳变间隔小于中断响应时间,脉冲就丢了。高速时A、B两相的跳变间隔可能只有几百纳秒,外部中断方案在这个尺度上基本不可靠。

定时器的编码器模式则完全不同。AB相接在定时器的CH1、CH2引脚上后,计数是由硬件完成的,不需要进中断,不存在丢脉冲的问题。CPU只是周期性读取计数寄存器的值来算速度,负载极低。所以我的结论很简单:只要是用STM32做编码器测速,第一选择永远是定时器的编码器模式,不是外部中断。

2. 硬件选型与接线:EC11、码盘电机和霍尔编码器怎么选

2.1 三种常见编码器怎么选

提到编码器测速,很多人第一个搜到的是EC11旋转编码器。EC11确实是编码器,但它天生是给手动旋钮用的,旋转速度不快,机械寿命以万次旋转计,不适合装在电机上长时间高速旋转。它有20个脉冲每圈,4倍频后是80个计数,用来做音量旋钮、菜单选择、数控面板的输入没有问题,但拿它测电机转速,很快就会磨损,而且没有码盘和减速箱配合,转速分辨率也远远不够。

真正适合电机测速的有两类。

第一类是光电码盘电机,也叫光栅码盘测速模块。码盘是一片带黑白相间条纹的塑料圆盘,安装在电机转轴上,光电对管一边发光一边接收,码盘旋转时交替遮挡光线,输出方波。这种方案在小车底盘电机里非常常见,便宜、信号干净,码盘线数常见的13、20、36都有。

第二类是霍尔编码器电机,比如网上常见的JGA25-370电机。霍尔元件贴在电机内部或尾部,磁环随电机轴旋转,输出两路正交方波。相比光电码盘,霍尔方案不怕灰尘油污,信号稳定性更好,输出已经是规整的方波,直接接单片机即可,不用额外整形电路。我现在项目里用的就是JGA25-370,减速比30:1,编码器线数13,实测信号质量比光电码盘省心不少。

两类电机的选型要点可以看下面的表。

类型输出信号优点缺点常见场景
EC11旋转编码器A/B正交方波便宜、接线简单、有定位感不适合高速连续旋转人机交互旋钮、手动调节
光电码盘电机A/B正交方波便宜、无接触、信号干净怕油污灰尘遮挡小车底盘测速、传送带定位
霍尔编码器电机A/B正交方波抗污染、寿命长、信号规整价格略贵要求较高的测速闭环项目
绝对式磁编码器SPI或I2C输出绝对角度掉电保留位置、精度高成本高、驱动复杂精确位置控制、机械臂关节

2.2 接线里最容易翻车的三件事

编码器接线看着就四根线(电源、地、A相、B相),但最容易出问题的地方恰恰在这里。

第一是共地。编码器的GND必须和STM32的GND连在一起,否则A、B相的电平参考基准都不一样,单片机读到的信号不是高就是低,根本没法判断。这个坑我见过太多新手踩,尤其是电机驱动板和单片机分开供电的时候,经常忘记把电源的负极连到一起去。

第二是上拉电阻。很多编码器模块的输出是开漏结构,模块上如果没有自带上拉电阻,A、B相在无输出时是悬空的,电平不确定,计数会乱跳。这种情况下需要在A、B相到3.3V之间各接一个4.7k到10k的上拉电阻。买模块的时候先确认一下板子上有没有排阻,没有的话自己飞线焊两个。

第三是信号电平。常见编码器模块用5V供电,输出高电平也是5V,STM32的IO是3.3V容忍,直接接3.3V引脚的GPIO一般问题不大,但为了稳妥,最好加一级电阻分压或者用电平转换芯片。另外A相和B相互换会导致方向反,这只是接线问题,不是代码问题,真遇到方向反了把A、B对调一下即可。

2.3 电气上的小细节:电源滤波与PWM干扰

电机是出了名的干扰源。电机启动瞬间电流可能是正常工作的好几倍,会在电源线上拉出很大的纹波,如果编码器和单片机共用一组电源,纹波会直接窜进A、B信号里导致误计数。

我的标准做法是:电机电源和逻辑电源分开,各自独立供电。电机电源两端并联一个100uF电解电容和一个104陶瓷电容,吸收启动电流冲击;编码器模块和STM32共用3.3V逻辑电源,也在电源引脚附近放104电容。如果板子空间允许,A、B信号线上再串一个几十欧的电阻,配合STM32定时器的输入滤波功能,基本能把绝大多数毛刺过滤掉。

另外,PWM驱动线和编码器信号线不要平行走线。电机的PWM驱动线上电流变化很快,会通过寄生电容耦合到旁边的信号线上。实测中我发现,同样的代码,信号线和PWM线绑在一起走,转速值偶尔会跳一下;分开走之后,跳变基本消失。

3. 定时器编码器模式配置:CubeMX参数逐项拆解

3.1 为什么编码器模式比输入捕获省心

STM32定时器的编码器模式,本质上是一个由外部信号自动控制加减计数的计数器。A相接CH1,B相接CH2,定时器会根据A、B两相的相位关系,自动决定计数器是向上加还是向下减。这样,方向判断、脉冲计数、倍频这些工作全部由硬件完成,CPU只需要定期读一下CNT寄存器的值。

对比输入捕获模式,编码器模式的优势非常明显。输入捕获需要自己配置两个通道,捕获上升沿时间戳,然后软件计算频率和方向,代码量大,而且捕获中断同样有CPU占用问题。编码器模式只需要把定时器配置成编码器模式,启动定时器,然后直接读计数器值就行了,代码量少一个数量级。

不过要注意,不是所有定时器都能做编码器模式。STM32F103上,TIM1、TIM2、TIM3、TIM4、TIM8都支持编码器模式,但TIM6、TIM7这种基本定时器不行。选型的时候看清楚,工程里如果有多个电机要测速,尽量选一个定时器管一个电机,避免资源冲突。

3.2 CubeMX里的关键参数怎么填

在STM32CubeMX里配置编码器模式的路径是:选择定时器,比如TIM3,然后看Combined Channels(组合通道)这个选项,默认是Disabled,把它改成Encoder Mode(编码器模式)。

改成编码器模式后,下方会自动出现几个参数,这里逐个说明。

第一个是Encoder Mode。有TI1、TI2和TI1 and TI2三个选项。TI1表示只在A相跳变沿计数,等效1倍频;TI2表示只在B相跳变沿计数,也是1倍频;TI1 and TI2表示A、B两相的跳变沿都计数,等效4倍频。做测速项目直接选TI1 and TI2,分辨率最高,方向判断也最稳。

第二个是Period(自动重装载值)。编码器模式下这个值决定了计数器的计数范围。因为CNT是16位寄存器,最大能数到65535,通常直接把Period填成65535,让计数器自由转圈。注意编码器模式下计数器不会自动清零,数到65535后继续递增会回绕到0,这个特性后面代码里要处理。

第三个是Input Filter(输入滤波)。这是抗干扰的关键参数,默认是0,也就是不过滤。实际电机环境里,建议设置一个非零值,比如0x0F。它的原理是:只有当信号电平持续稳定多个采样周期后才认为信号有效,能滤掉窄毛刺。具体滤波强度要根据定时器时钟频率和信号频率来定,采样频率越高滤波效果越弱,采样频率越低滤波越强但可能滤掉正常信号。对编码器信号来说,0x0F是个比较稳妥的起步值。

第四个是Prescaler(预分频系数),编码器模式下保持0即可,不需要分频。

3.3 配置好之后还要做什么

CubeMX配置完生成代码后,只是把定时器的寄存器初始化好了,还需要在代码里启动它。HAL库的启动函数是:

HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_ALL);

第二个参数传TIM_CHANNEL_ALL,表示同时启动编码器模式使用的两个通道。启动之后,CNT寄存器就会随着A、B相信号自动增减,不需要额外中断。

启动之后还要注意两点。

第一,初始计数值要清零。上电瞬间CNT可能是一个随机值,用__HAL_TIM_SET_COUNTER(&htim3, 0)把计数器清零。

第二,读取计数值要趁早。因为CNT是16位的,如果两次读取间隔太长,CNT回绕造成的误差会很大。以单圈1560个脉冲算,高速旋转时每秒可能产生几万个计数,如果100ms才读一次,CNT早就转了好几圈,根本没法算速度。所以读取周期要控制在10ms甚至更短,或者用定时器级联组成32位计数器,后者适合要求更高的场合。

4. 测速算法三选一:M法、T法、M/T法的适用边界

4.1 M法(测频法):适合高速的"数脉冲"

M法也叫测频法,做法很简单:固定一个时间窗口,比如10ms,统计这个窗口内编码器输出的脉冲个数N,然后用公式算出转速。

M法的公式推导很直观。电机的输出轴转一圈,编码器产生的计数总数是:

P = 码盘线数 × 倍频系数 × 减速比

对于一个13线、4倍频、减速比30的电机,P = 13 × 4 × 30 = 1560。

如果采样窗口是T秒(单位是秒),窗口内数到N个计数,那么转速n(单位RPM)等于:

n = N / (P × T) × 60

代入T=0.01、P=1560,就得到:

n = N × 6000 / 1560 = N × 3.846

也就是说,10ms窗口内每数到1个计数,转速大约是3.846RPM。这个换算系数可以直接写在代码里,省去每次运算的麻烦。

M法的误差来源主要在于低速时脉冲个数少。比如转速只有10RPM时,10ms窗口内大约只有0.26个计数,要么数到0、要么偶尔数到1,算出来的速度在0和3.8RPM之间跳变,相对误差极大。所以M法天生适合中高速场景,速度越低,精度越差。

热词里提到的"STM32测频法",指的就是M法。它的优点是实现简单、代码量小,中高速下精度完全够用,是大多数直流电机测速项目的首选。

4.2 T法(测周期法):低速场景的救命稻草

T法也叫测周期法,思路和M法反过来:不是数固定时间内的脉冲数,而是测量相邻两个脉冲之间的时间间隔。脉冲间隔越长,说明转速越慢;间隔越短,转速越快。

T法的实现通常借助输入捕获功能。配置定时器一个通道为输入捕获,捕获上升沿,每次进入捕获中断时读取捕获寄存器,得到上一次上升沿到本次上升沿之间的定时器时钟周期数ΔT。如果定时器时钟频率是F(比如72MHz),那么两个脉冲之间的真实时间就是ΔT/F秒。

转速的计算公式是:

n = 60 / (ΔT / F × P_origin)

这里的P_origin是电机输出轴转一圈产生的原始脉冲数(不是4倍频后的计数,而是倍频系数为1时的脉冲数)。比如13线、减速比30的电机,原始脉冲数是13×30=390,那么:

n = 60 / (ΔT / 72e6 × 390) = 60 × 72e6 / (ΔT × 390)

代入ΔT=72000,得到n约等于153.8RPM,也就是说这个转速下脉冲间隔大约1ms。

T法的优势在低速非常明显:速度越低,脉冲间隔越长,测得的ΔT数值越大,精度越高。但速度一高,脉冲间隔变短,ΔT太小,测出的转速误差反而变大。比如转速3000RPM时,脉冲间隔只有几微秒,ΔT只有几百个时钟周期,任何一点时钟误差都会被放大。

T法还有个实现上的坑:单个脉冲间隔会带来比较大的噪声抖动,实际使用中通常测多个脉冲的时间间隔平均,或者直接测固定数量的脉冲所需的总时间,这样更稳定。

4.3 M/T法:宽范围测速的正解

M法和T法各有短板,M法高速准低速差,T法低速准高速差。工程上如果要求从低速到高速全范围都比较准,就要上M/T法。

M/T法的核心思路是:在一个固定闸门时间Ts内,同时记录两个数据,一是这个窗口内完整的脉冲个数M,二是从窗口开始到窗口结束期间,第一个脉冲和最后一个脉冲之间的精确时间ΔT。然后把M和ΔT关联起来算频率。

简化版的M/T法公式可以写成:

n = (M - 1) / (ΔT × P_origin) × 60

这里的ΔT是窗口内最后一个脉冲的时间戳减去第一个脉冲的时间戳,单位是秒。这样算出的转速不受窗口固定时间Ts的误差影响,因为实际测量时间用的都是硬件时间戳,精度很高。

M/T法在STM32上的实现比前两个复杂一些。通常需要两个定时器配合,一个做编码器计数,另一个做时间测量,还要合理处理脉冲时间戳的记录时机。如果项目对测速精度要求很高,比如做伺服或者闭环速度控制,M/T法才值得上。普通小车底盘、风机、水泵这类场景,M法加一个合适的滤波算法已经足够,没必要把系统搞复杂。

4.4 换算公式与减速比:为什么P值是最重要的参数

不管用哪种算法,最后都绕不开单圈总脉冲数P这个参数。它的准确与否直接决定了测速的绝对精度。

拿JGA25-370电机为例,商品信息里写的基本参数是:减速比30:1,编码器线数13。但这里有几个地方容易理解错。

第一,编码器线数13指的是电机本体(高速端)转一圈,编码器输出13个A相脉冲周期。电机输出轴(低速端)转一圈,高速端其实转了30圈,所以输出轴一圈对应的原始脉冲数是13×30=390。

第二,4倍频之后,输出轴一圈的总计数是390×4=1560。这个1560就是我在代码里用的P值。

第三,如果你的测速对象不是电机的输出轴,而是轮子,那还要再乘一个轮子相关参数。比如轮子直径是65mm,轮子转一圈前进的距离是π×65≈204.2mm,换算线速度的公式是:

v = n / 60 × π × D

其中D是轮径,n是轮轴转速,v的单位和D一致。

提示:买电机时一定仔细看商品详情页的减速比和编码器线数,不同批次可能不一样。如果资料没写,可以用手摇电机输出轴一圈,观察编码器输出波形的脉冲个数来实测推算。

5. 完整代码走读:从定时器初始化到转速换算

5.1 CubeMX自动生成的定时器初始化

在CubeMX里配置好TIM3为编码器模式后,生成的初始化代码大概是下面这个样子。我用的芯片是STM32F103RCT6,HAL库版本是1.8.0,其他芯片和库版本的寄存器结构基本一致。

/* tim.c 中生成的代码 */ void MX_TIM3_Init(void) { TIM_Encoder_InitTypeDef sConfig = {0}; TIM_MasterConfigTypeDef sMasterConfig = {0}; htim3.Instance = TIM3; htim3.Init.Prescaler = 0; htim3.Init.CounterMode = TIM_COUNTERMODE_UP; htim3.Init.Period = 65535; htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE; sConfig.EncoderMode = TIM_ENCODERMODE_TI12; sConfig.IC1Polarity = TIM_ICPOLARITY_RISING; sConfig.IC1Selection = TIM_ICSELECTION_DIRECTTI; sConfig.IC1Prescaler = TIM_ICPSC_DIV1; sConfig.IC1Filter = 15; sConfig.IC2Polarity = TIM_ICPOLARITY_RISING; sConfig.IC2Selection = TIM_ICSELECTION_DIRECTTI; sConfig.IC2Prescaler = TIM_ICPSC_DIV1; sConfig.IC2Filter = 15; if (HAL_TIM_Encoder_Init(&htim3, &sConfig) != HAL_OK) { Error_Handler(); } sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE; HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig); }

这里面最关键的参数就是TIM_ENCODERMODE_TI12,它对应4倍频模式。IC1Filter和IC2Filter都设成了15,这是前面说的输入滤波,能过滤掉A、B相上的毛刺信号。

5.2 定时中断读取与速度计算

启动编码器定时器之后,再配置一个基础定时器TIM6做10ms周期中断,用来周期性读取CNT寄存器并计算转速。

/* main.c 中的启动代码 */ int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_TIM3_Init(); MX_TIM6_Init(); HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_ALL); HAL_TIM_Base_Start_IT(&htim6); while (1) { /* 主循环可以干别的事情,速度值在中断里更新 */ } }

速度计算的核心逻辑放在TIM6的中断回调函数里。这里有一个关键点:读取CNT时,先把uint16_t强转成int16_t再处理。这样做的原因是,CNT在编码器模式下既可能向上加也可能向下减,直接读回的无符号数在正反转切换时很难处理,转成int16_t后,差值的正负就自然代表了计数方向,而且只要两次采样间隔内的计数变化不超过32767,就不会出错。

/* encoder.c 中的速度计算 */ #define ENCODER_LINES 13 #define GEAR_RATIO 30 #define PULSES_PER_REV (ENCODER_LINES * 4 * GEAR_RATIO) // 1560 static int16_t last_cnt = 0; static float speed_filtered = 0.0f; volatile float motor_speed_rpm = 0.0f; volatile int16_t motor_speed_dir = 1; void Motor_Speed_Calc(void) { int16_t now_cnt = (int16_t)__HAL_TIM_GET_COUNTER(&htim3); int16_t delta = now_cnt - last_cnt; last_cnt = now_cnt; if (delta >= 0) { motor_speed_dir = 1; } else { motor_speed_dir = -1; delta = -delta; } /* 10ms窗口:RPM = delta * 6000 / 1560 */ float speed_raw = (float)delta * 6000.0f / (float)PULSES_PER_REV; /* 一阶低通滤波,系数0.2,能明显抑制转速波动 */ speed_filtered = 0.8f * speed_filtered + 0.2f * speed_raw; motor_speed_rpm = speed_filtered; }
/* stm32f1xx_it.c 或回调文件中 */ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == TIM6) { Motor_Speed_Calc(); } }

5.3 方向判断与数据滤波

方向判断有两种做法。上面代码里是用两次计数的差值正负来判断方向,简单直观。另一种更可靠的做法是直接读定时器的计数方向标志位:

if (READ_REG(htim3.Instance->CR1) & TIM_CR1_DIR) { motor_speed_dir = -1; } else { motor_speed_dir = 1; }

这个方法的优点是实时性更好,不受采样周期影响,缺点是代码可读性稍差。实际项目里两种我都用过,如果采样周期稳定在10ms量级,差值法完全够用。

滤波这一步千万不要省。电机旋转本身就有机械波动,编码器输出脉冲也不可能绝对均匀,直接拿原始计算值做控制,速度曲线会像锯齿一样跳。我惯用的一阶低通滤波,系数0.2到0.3之间比较合适,10ms采样周期下滤波时间常数大约是40到50ms,既能平滑掉高频抖动,又不会让响应慢到跟不上转速变化。如果需要更平滑,可以叠一个滑动平均,但注意会增加延迟。

6. 实测数据与踩坑实录:转速值为什么一直在跳

6.1 计数器溢出这个隐藏的大坑

第一次用编码器模式跑起来,我发现一个诡异的现象:电机正转的时候,转速偶尔会突然跳成一个巨大无比的值,然后又恢复正常。查了半天才发现是计数器溢出问题。

CNT是16位寄存器,最大值65535。电机高速旋转时,10ms窗口内产生的计数可能达到好几千,但绝对值不会超过65535,所以10ms读取一次不会溢出。问题出在电机只反转不转正、停在某个位置附近抖动,或者代码运行到读取语句前被更高优先级中断打断,导致两次读取CNT的间隔远大于10ms,CNT已经回绕了一圈甚至几圈。

解决思路有两条。最简单是保证读取周期稳定,别在中断里放耗时的操作。更彻底的方案是用32位计数,把两个定时器级联起来,或者读取时做特殊处理,比如用临界区保护。对绝大多数项目,保证10ms定时中断稳定触发就够了,这条坑我列出来主要是提醒你,别在主循环里也去读CNT算速度,两个地方抢着读很容易踩到溢出问题。

6.2 电机一启动转速值乱跳:噪声惹的祸

有一次我把编码器模块直接固定在电机尾端,离驱动板PWM输出线很近,结果一启动电机,转速值就无规律乱跳,测得的正反转方向也是乱变。

用示波器看A、B相波形,能看到在正常方波的边沿上叠加了很多几十纳秒宽的小毛刺。这些毛刺被定时器的编码器模式当成有效跳变沿,计数器就被无规律地加减,转速自然乱跳。

我的处理办法分三步。第一步把CubeMX里IC1Filter和IC2Filter从0改到15,毛刺被过滤掉大半。第二步在编码器电源引脚旁边加104电容,进一步吸收高频噪声。第三步把PWM驱动线和编码器信号线物理分开,重新布线。三步做完,示波器上的毛刺基本消失,转速值也稳定了。

注意:输入滤波不是越大越好。滤波值太大,会把正常的高频脉冲一起滤掉,导致计数丢失。建议先用电机的最大转速算一下A相的最高频率,再根据定时器时钟频率选择滤波值,原则是滤波采样频率至少是信号最高频率的5倍以上。

6.3 正转时偶尔报负速度:方向判断的边界情况

还有一个很典型的坑:电机明明一直在正转,方向标志却偶尔变成负的,持续一小段时间又变回来。

出现这个现象有两个常见原因。一是编码器A、B相接线接触不良,信号偶尔丢一拍,硬件计数方向就错了;二是电源纹波导致计数器误判。排查的时候先用示波器看A、B相有没有异常抖动,再看供电是否稳定。如果硬件没问题,就要在软件里做方向死区处理:只有连续若干次采样都是反向,才真正切换方向标志。这个方法可以避免单次误判导致的方向抖动。

另外一个方向相关的小技巧是:不用改代码,直接交换A、B两根线的接线就能改变方向判断逻辑。系统联调的时候如果发现方向反了,先别急着改代码,把A、B对调一下往往更快。

6.4 定时器资源冲突与开发环境坑

编码器模式占用了定时器的CH1和CH2,这个定时器就不能再输出PWM了。同一个定时器既做编码器计数又做PWM驱动,硬件上就是不支持的。所以在系统设计阶段就要把定时器资源分配好。

我常用的分配方案是:

定时器用途说明
TIM1PWM输出高级定时器,用于电机驱动,频率20kHz
TIM3编码器模式(左轮)CH1接A相,CH2接B相
TIM4编码器模式(右轮)CH1接A相,CH2接B相
TIM610ms定时中断基础定时器,只做周期触发

这样左右轮各占一个定时器,PWM另用高级定时器,互不干扰。如果用到的电机数量更多,就得考虑定时器不够用的情况,这时才需要回头评估外部中断方案的可行性,把低速的编码器放在中断里,高速的放在定时器模式里。

另外提一个开发环境的小问题。有些读者会遇到Keil装了却识别不到STM32芯片,或者ST-Link连接不上目标板。这个多数是ST-Link驱动没装好、或者开发板没共地的问题。用ST-Link调试编码器项目时,务必确认编码器、电机驱动板和ST-Link三者的GND都连在一起,否则调试器可能时好时坏,编码器信号更是没法看。

6.5 实测数据:M法和T法在不同转速下的表现

为了直观展示算法选型的差异,我在同一个JGA25-370电机上分别用M法和T法测了一组数据,采样周期都是10ms,参考转速用高精度光学转速表标定。

目标转速M法测量值T法测量值说明
约30RPM27~35RPM波动29.4~30.6RPM稳定M法10ms窗口内脉冲约2~3个,跳动明显
约150RPM148~153RPM稳定146~159RPM波动两者都可用,T法抖动略大
约600RPM598~602RPM稳定590~620RPM波动高速下T法脉冲间隔太短,误差放大
约1500RPM1495~1505RPM稳定明显偏慢T法在当前配置下已基本不可用

这组数据印证了前面的理论推导:低速用T法准,高速用M法稳。如果只打算用一种算法,个人建议M法配一阶低通滤波,因为大部分电机测速应用都在中高速区间,M法实现简单、稳定性好,低速误差通过滤波也能缓解大半。真要做宽范围高精度测速,再考虑上M/T法。

最后再分享一个我实际调试中觉得特别实用的小技巧:把算出来的转速值通过串口以固定频率发到上位机画波形,或者直接通过虚拟示波器插件看曲线,调试效率比盯着串口助手的数字高太多了。你能直观看到电机从启动、加速到稳定的完整过程,也能马上发现滤波参数调得合不合适,强烈建议在做闭环控制之前先把这个测速链路调稳。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询