ARTICLE DETAIL

资讯详情

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

STM32编码器模式测速实践:LAT1447直流减速电机完整方案

STM32编码器模式测速实践:LAT1447直流减速电机完整方案 搞过直流减速电机测速的朋友都知道测速本身不是难事难的是“测出来能不能用、稳不稳、方向对不对”。LAT1447 这类带 AB 相霍尔编码器的减速电机在智能小车、机器人底盘、云台等场景里非常常见配合 STM32 的定时器编码器模式能做到高精度、带方向的速度反馈。这篇文章我就直接把我实测过程中的完整思路、配置细节、计算公式和踩过的坑都整理出来希望对正在做电机闭环控制的人有用。我手上这款 LAT1447本质是一个直流减速电机尾部集成了双路霍尔编码器输出 A、B 两相正交方波。相比普通 N20 电机它的减速比和编码器线数有更多可选规格我手里这款是减速比 30:1、电机轴编码器 11 PPR 的版本也就是输出轴每转会产生 330 个脉冲。如果你手里的型号是其他减速比公式里替换对应的值就行。1. 测速前先摸清LAT1447的编码器信号1.1 硬件引脚与信号特征LAT1447 一般引出 6 根线电机供电两根M、M-编码器供电两根VCC、GND编码器输出两根A、B。霍尔编码器工作在 3.3V~5V 电压范围输出信号是开漏或者推挽取决于具体批次。我实测手头这块是推挽输出可以直接接 STM32 的 GPIO。如果你的板子上的编码器是开漏输出必须加上拉电阻否则采样到的波形边沿会很不干净导致计数抖动。A、B 两相信号的相位差是 90 度这个正交关系非常关键。电机正转时A 相上升沿对应 B 相高电平反转时A 相上升沿对应 B 相低电平。正是靠着这个相位关系STM32 定时器才能自动分辨方向而不仅仅是数脉冲。1.2 为什么不能用单纯的外部中断测速我看到不少入门教程教人把 A、B 两相接外部中断在每个上升沿累加计数。这套方法在极低速下确实能工作但有几个明显痛点一是两个引脚的中断处理函数里要判断另一相电平代码侵入性很强二是系统中断一多特别是你在跑 PID 控制、OLED 刷新、串口打印的时候中断延迟会导致脉冲丢失三是无法做 4 倍频细分分辨率天然低一截。STM32 的定时器编码器模式就不一样它是硬件层面的正交解码器不需要 CPU 干预自动根据 A、B 相位判断方向还能把计数分辨率提高到 4 倍。定时器溢出中断可以顺带提供采样节拍这样测速功能对主循环的打扰几乎为零。2. 测速方案对比与选型思路2.1 M法、T法和M/T法的适用场景测速的本质是把“单位时间内的脉冲数”或者“单个脉冲的宽度时间”转成转速。业内常用的三种方法各有各的适用范围。M法是在固定采样周期内数脉冲个数公式为转速 计数值 / (编码器总分辨率 × 采样时间) × 60。它适合中高速因为高速时采样周期内脉冲数量多误差小。但低速时一个采样周期可能只采到几个脉冲量化误差被放大测得的速度值会一跳一跳的。T法测的是脉冲的时间间隔用定时器捕获两个相邻脉冲之间的时间。它适合低速测量速度越低脉冲间隔越长时间测量越精确。但高速时脉冲间隔太短定时器分辨率不够误差反而变大。M/T法则是在低速时用T法、高速时用M法通过判断当前脉冲频率在两种模式间切换。这套方法最准但在 STM32 上实现复杂一些需要同时用编码器接口和输入捕获而且切换逻辑本身也有滞后。2.2 本项目的最终选择定时器编码器模式 M法我在这个项目里选的是定时器编码器模式配合 M 法原因很实际LAT1447 这种减速电机加上 30:1 减速比之后输出轴转速通常在 50~300 RPM 之间配合 11 PPR 编码器4 倍频后总分辨率是 1320 个脉冲/转。假设采样周期 20ms每转 1320 个脉冲最慢 50 RPM 时每秒 1100 个脉冲20ms 内也有 22 个脉冲M 法完全够用不会出现严重的量化跳变。如果你要用这种方案测很低的速度比如输出轴低于 5 RPM建议把采样周期拉长到 100ms或者干脆换 T 法。我在后面的扩展部分也会提到怎么做。3. STM32测速硬件连接与工程配置3.1 接线方式与硬件注意事项LAT1447 的 A、B 相要接到 STM32 定时器的两个通道上。比如用 TIM2就需要把 A 相接到 PA0B 相接到 PA1因为这两 Pin 对应 TIM2 的通道 1 和通道 2。只有特定引脚才能复用成指定定时器的输入捕获通道这点接线前必须查数据手册不能随便接。这里有几个硬件细节我特意强调一下编码器 VCC 接 3.3V 还是 5V我实测 5V 供电时输出波形更陡峭但必须确认你的编码器输出电平不会超过 STM32 引脚容忍范围。STM32 大部分引脚是 5V 容忍的但保险起见最好用 3.3V 供电或者把编码器输出串联 1K 电阻再进引脚。电源滤波不能省。电机启动瞬间电流很大会让电源电压波动编码器信号也跟着毛刺频出。我是在电机供电端并联了 100uF 电解电容和 104 瓷片电容编码器供电单独走一条线效果立竿见影。A、B 信号线尽量短不要跟电机驱动线扎在一起。如果无法避免用双绞线并且靠近 STM32 一侧加上拉电阻到 3.3V能有效改善信号质量。3.2 STM32CubeMX配置定时器编码器模式我用的是 STM32F103C8T6开发环境是 STM32CubeMX HAL 库。配置思路在 F4、F0 系列上完全通用。在 CubeMX 里选中 TIM2把 Channel1 和 Channel2 都设为 Encoder Mode。这时会出现 Encoder Mode 下拉框我的选择是 “Encoder Mode TI1 and TI2”意思是同时使用 A、B 两相进行正交解码这样计数分辨率直接翻 4 倍。其他几个关键参数Prescaler 预分频设为 0因为编码器计数需要的是原始脉冲不能分频。Counter Period 自动重载值设为 65535让计数器自由运行定时器初始化后 CNT 会从 0 到 65535 循环。Input Filter 输入滤波设为 10这个参数对应芯片内部的数字滤波可以滤掉窄毛刺。我实测设为 10 在实际电机运行中不会丢失正常脉冲但能滤掉大部分干扰。两个通道的极性都设为 Rising Edge因为正交解码会自动处理相位关系。3.3 初始化代码实现CubeMX 生成的代码骨架已经能工作但我还是建议在初始化里显式确认编码器模式参数。void MX_TIM2_Init(void) { TIM_Encoder_InitTypeDef sEncoderConfig {0}; TIM_MasterConfigTypeDef sMasterConfig {0}; htim2.Instance TIM2; htim2.Init.Prescaler 0; htim2.Init.CounterMode TIM_COUNTERMODE_UP; htim2.Init.Period 65535; htim2.Init.ClockDivision TIM_CLOCKDIVISION_DIV1; htim2.Init.AutoReloadPreload TIM_AUTORELOAD_PRELOAD_DISABLE; sEncoderConfig.EncoderMode TIM_ENCODERMODE_TI12; sEncoderConfig.IC1Polarity TIM_ICPOLARITY_RISING; sEncoderConfig.IC1Selection TIM_ICSELECTION_DIRECTTI; sEncoderConfig.IC1Prescaler TIM_ICPSC_DIV1; sEncoderConfig.IC1Filter 10; sEncoderConfig.IC2Polarity TIM_ICPOLARITY_RISING; sEncoderConfig.IC2Selection TIM_ICSELECTION_DIRECTTI; sEncoderConfig.IC2Prescaler TIM_ICPSC_DIV1; sEncoderConfig.IC2Filter 10; HAL_TIM_Encoder_Init(htim2, sEncoderConfig); sMasterConfig.MasterOutputTrigger TIM_TRGO_RESET; sMasterConfig.MasterSlaveMode TIM_MASTERSLAVEMODE_DISABLE; HAL_TIMEx_MasterConfigSynchronization(htim2, sMasterConfig); }注意一点HAL_TIM_Encoder_Init 之后计数器默认是停止的。要用 HAL_TIM_Encoder_Start 启动编码器接口这里不需要开启中断因为我们只需要读 CNT 寄存器走轮询方式即可HAL_TIM_Encoder_Start(htim2, TIM_CHANNEL_ALL);如果你希望每 20ms 取一次速度建议用另一个定时器比如 TIM3产生 20ms 周期中断在中断回调里读取 TIM2 的 CNT 寄存器。这样测速的时间基准和计数采集完全分离不会互相干扰。4. 速度计算与数据处理细节4.1 从CNT差值到真实转速的计算公式编码器模式启动后TIM2 的 CNT 会随着电机转动不断变化正转增加反转减少。测速的核心就是每隔固定时间读取一次 CNT和上次的 CNT 做差差值就是这段时间内的脉冲数。在我这里采样周期 Ts 取 20ms也就是 0.02 秒。编码器总分辨率是 4 × 11 × 30 1320 脉冲/转。转速计算公式如下int32_t delta (int32_t)__HAL_TIM_GET_COUNTER(htim2) - last_count; last_count __HAL_TIM_GET_COUNTER(htim2); // delta 是 20ms 内的脉冲数 // 每秒转数 delta / 1320 / 0.02 // 每分钟转数 delta / 1320 / 0.02 * 60 int32_t speed_rpm (delta * 100) / (1320 / 6);这里我用了整数计算避免浮点除法换算思路是把“除以 0.02”变成“乘以 50”再把“乘以 60”合并成“乘以 3000”。公式变成speed_rpm delta * 3000 / 1320;如果高速时 delta 较大比如达到 4000那么 4000*3000 等于 12000000int32 完全放得下没有溢出风险。如果想省掉除法可以先把 3000/1320 约分但实测直接整数除法在 STM32F103 上开销可以忽略。4.2 方向判断与计数器溢出处理编码器模式下计数器是循环计数的也就是从 65535 再加 1 会回到 0反过来减 1 会跳到 65535。如果直接做差值就会出现严重错误。比如上一次 CNT 是 65534这次是 2直接减得到 -65532实际应该是 4。处理溢出最稳妥的办法是使用 STM32 的 32 位计数器模式。如果你的定时器是 16 位可以配置为双 16 位级联成 32 位但操作复杂。更通用的办法是在代码里判断int32_t delta (int16_t)((int16_t)(__HAL_TIM_GET_COUNTER(htim2)) - (int16_t)last_count);把 16 位计数器强制转换为 int16_t再做差。因为两个数的差值不会超过 16 位范围符号自然保留。这样即使发生计数回绕差值也在 ±32767 内符号正确。这个方法很巧妙也是我在调试中最常用的写法。方向判断就简单了delta 为正就是正转为负就是反转。如果你发现实际方向反了有两种解决方式一种是调换 A、B 接线另一种是在代码里把 delta 取反。我通常先试调换接线因为硬件层面换线最干净代码里不需要额外补偿。4.3 实测数据的抖动与滤波处理当你真正把小车跑起来观察速度曲线时会发现原始 RPM 数值存在明显抖动。这是因为 M 法测速的量化误差、电机负载波动、编码器安装偏心都会叠加进结果。直接拿这个速度去喂 PID会带来震荡。我第一版程序没有加任何滤波PID 参数怎么调都抖后来在速度环前面加了一阶低通滤波问题立刻缓解。代码很简单float speed_filtered 0.0f; float alpha 0.7f; // 滤波系数 while (1) { int32_t raw_speed Get_Speed_RPM(); speed_filtered alpha * raw_speed (1.0f - alpha) * speed_filtered; HAL_Delay(20); }alpha 越大响应越快但滤波越弱alpha 越小越平滑但滞后越大。我电机控制周期 20ms 时取 0.7基本能兼顾响应和稳定。如果你做的是位姿控制对速度响应要求极高可以用滑动窗口平均值不过低通滤波的实时性更好。5. 常见问题与排查技巧实录5.1 电机不动但计数器一直在跳这个是最早把我坑到的问题。现象是电机完全静止通过调试软件查看 CNT 寄存器仍然在不同数值之间变化甚至漂移几百。排查后发现是编码器供电电压不稳加信号线过长再加上电机启动瞬间大电流引起的共地干扰。解决方法我上面提过一是编码器供电单独走一路不跟电机共电源二是给编码器供电端并 10uF 和 0.1uF 电容三是把信号滤波系数从默认的 0 调到 10 以上。完成后静止时 CNT 几乎纹丝不动。5.2 速度值周期性跳大像是有毛刺如果你把测到的 RPM 用串口打印出来发现每隔一段时间会跳一个很大的值比如正常 100 RPM突然跳到 200 又恢复正常。这个大概率是电机换相产生的电磁干扰打到了编码器信号上导致定时器误认为多了一个上升沿。我在这种情况下加了两层保护硬件上在编码器信号和 GND 之间并联 10nF 电容软件上把输入滤波参数提高到 15~20。注意输入滤波参数也不是越大越好我试过设为 30 以上极低速时脉冲宽度小于滤波窗口计数反而丢。5.3 低速时速度值一档一档跳变M 法低速下的量化误差是固有的20ms 采样周期下脉冲少的时候差值只能在 0 和 1 之间变化算出来的 RPM 自然一跳一跳。如果你的应用需要平滑低速控制我的建议是把采样周期加长到 50ms 或者 100ms单位时间内的脉冲数变多了量化步长就变小了。以我这款电机为例最低转速 50 RPM输出轴每秒脉冲数是 110020ms 采样周期下有 22 个脉冲量化步长是 1320/3000 的倒数乘以 22大概 4.5 RPM。改成 100ms 采样量化步长能到 0.9 RPM 左右。代价是反馈滞后增大PID 控制周期也要跟着放慢。5.4 电机反转时测速值不为负遇到过几次反转时读到的速度还是正值检查代码发现了两个原因。一个是我在转换速度时用无符号除法负的 delta 被当成很大的正数处理。解决办法是先把 delta 强转为 int32_t再做除法。另一个原因是 CubeMX 初始化后计数器的计数方向模式是向上计数导致反转时 CNT 下降但方向标志位没有被正确读取。实际上编码器模式会硬件自动处理方向不必额外设置 CounterMode但要确保没有手动改过。重启工程后重新生成初始化代码问题就消失了。6. 从测速到闭环控制的扩展思路6.1 把速度值接入PID控制器测速不是终点最终目的是做闭环控制。我在项目中用的是位置式 PID控制周期 20ms输入为期望速度与实际速度的误差输出是电机 PWM 占空比。有了准确的测速值PID 参数整定才有意义。一个关键建议PID 的输入一定用滤波后的速度值但滤波系数要配合 PID 参数。滤波越强系统相位滞后越大PID 的 P 和 I 就必须相应调小否则会震荡。我的整定经验是先设 I 为 0只用 P从很小值往上加直到系统出现轻微震荡然后回调 30%再慢慢加 I。6.2 串口调试与数据可视化调试过程中我强烈建议把原始 CNT、delta、滤波前速度、滤波后速度和 PID 输出量全部通过串口发出来在电脑上用串口助手绘图。不少串口助手自带波形显示功能观察曲线比看数字直观得多。我在调试时的数据帧格式很简单printf(CNT:%d DELTA:%d RAW:%d FILT:%d PID:%d\r\n, cnt, delta, raw_speed, (int)speed_filtered, pid_out);打印频率 50Hz 不会对实时性造成影响。实际用下来这条几百毫秒的“数据快照”能帮我快速定位到底是测速抖动还是 PID 参数问题省下大量瞎猜时间。说实话LAT1447 加上 STM32 编码器模式这套组合我前前后后调了小半个月才彻底跑顺。回头去看真正重要的不是几个库函数怎么调而是理解正交编码器的物理信号、明白每种测速法的量化特性、学会用硬件和软件手段对抗干扰。把这个底层逻辑吃透了换任何型号的编码器电机、任何系列的 STM32你都能很快上手。
返回列表