STM32 定时器编码器接口模式电机测速实战:从 CubeMX 配置到 M 法转速计算与溢出处理

文章目录

摘要:直流减速电机是智能小车动力核心,开环 PWM 无法感知转速,负载变化时速度漂移严重。本文基于 STM32F103C8T6 + 带 AB 相霍尔编码器减速电机,用定时器硬件编码器接口(四倍频)实现正反转识别与零开销计数,配合 100ms 采样与 M 法换算转速,含溢出与抗干扰处理。实测正转 CNT 增、反转减;M 法 300RPM 误差<1.5%,<30RPM 低速误差 8%;硬件模式 CPU 占用 <2%,较软件中断方案(峰值 18%)释放资源。附 CubeMX 参数、HAL 代码与六类排障。

前言

做智能车或机械臂时,最常被问到的问题就是:"电机到底转了多快、转了多少圈?"------这背后是把电机的机械旋转,准确、低延迟地翻译成 MCU 能处理的数字量。

传统开环 PWM 给多少占空比就"以为"转多少速度,但地面摩擦、电池电压、负载轻重都会让实际转速漂移。要上速度环/位置环,必须先有可靠的转速反馈。

本文目标:用 STM32 的定时器硬件编码器接口,搭一套可直接复用的电机测速代码。读完后你能掌握:CubeMX 编码器模式参数、AB 相正交解码原理、M 法转速公式、溢出回绕处理、以及六类高频排障。

前置条件:一块 STM32F103C8T6 最小系统板、一个带 AB 相霍尔编码器的直流减速电机(如 JGA25-370 系列)、串口调试助手,以及 STM32CubeMX + HAL 库开发环境。本文完整工程代码(含 CubeMX .ioc 与 HAL 工程)可在 CSDN 下载频道 获取(VIP 免费)。

一、为什么用硬件编码器接口,而不是软件计数

增量式编码器输出 A、B 两路正交方波(相位差 90°)。正转时 A 相超前 B 相,反转时 B 相超前 A 相。要从中得到"转速 + 方向",有两条路:

方案 实现方式 最高可测频率 方向判定 CPU 占用 适用场景
软件输入捕获 / EXTI 中断 在 A 相边沿进中断,读 B 相电平判向、cnt++/-- 受中断抖动限制,~5kHz 即丢帧 需自己写状态机 峰值 18%(实测) 单路霍尔、极低速、引脚受限
硬件编码器接口(本文) 定时器 TI1/TI2 接管 AB 相,硬件自动计数 + 判向 接近定时器时钟上限(F103 约 数 MHz) 硬件自动,零误差 <2% 标准 AB 相增量编码器

相关阅读:《STM32 编码器接口硬件正交解码原理与工程实践》 --- 讲透硬件正交解码的零开销本质

结论:只要电机配的是标准增量式编码器(带 A、B 相),硬件编码器接口一定是更优解。它省下的不只是 CPU,更是调试精力------你不用再为"为什么反转时数值乱跳"熬夜查手册。

二、核心原理:四倍频与方向判别

硬件编码器接口把定时器 CNT 的时钟源和计数方向,完全托管给外部 AB 相信号:每检测到有效边沿,硬件采样另一相电平,按正交关系判定方向,并让 CNT 加 1 或减 1,全程不经过 CPU。
#mermaid-svg-kXSSmsyC2Jm1cD41{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-kXSSmsyC2Jm1cD41 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-kXSSmsyC2Jm1cD41 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-kXSSmsyC2Jm1cD41 .error-icon{fill:#552222;}#mermaid-svg-kXSSmsyC2Jm1cD41 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-kXSSmsyC2Jm1cD41 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-kXSSmsyC2Jm1cD41 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-kXSSmsyC2Jm1cD41 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-kXSSmsyC2Jm1cD41 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-kXSSmsyC2Jm1cD41 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-kXSSmsyC2Jm1cD41 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-kXSSmsyC2Jm1cD41 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-kXSSmsyC2Jm1cD41 .marker.cross{stroke:#333333;}#mermaid-svg-kXSSmsyC2Jm1cD41 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-kXSSmsyC2Jm1cD41 p{margin:0;}#mermaid-svg-kXSSmsyC2Jm1cD41 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-kXSSmsyC2Jm1cD41 .cluster-label text{fill:#333;}#mermaid-svg-kXSSmsyC2Jm1cD41 .cluster-label span{color:#333;}#mermaid-svg-kXSSmsyC2Jm1cD41 .cluster-label span p{background-color:transparent;}#mermaid-svg-kXSSmsyC2Jm1cD41 .label text,#mermaid-svg-kXSSmsyC2Jm1cD41 span{fill:#333;color:#333;}#mermaid-svg-kXSSmsyC2Jm1cD41 .node rect,#mermaid-svg-kXSSmsyC2Jm1cD41 .node circle,#mermaid-svg-kXSSmsyC2Jm1cD41 .node ellipse,#mermaid-svg-kXSSmsyC2Jm1cD41 .node polygon,#mermaid-svg-kXSSmsyC2Jm1cD41 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-kXSSmsyC2Jm1cD41 .rough-node .label text,#mermaid-svg-kXSSmsyC2Jm1cD41 .node .label text,#mermaid-svg-kXSSmsyC2Jm1cD41 .image-shape .label,#mermaid-svg-kXSSmsyC2Jm1cD41 .icon-shape .label{text-anchor:middle;}#mermaid-svg-kXSSmsyC2Jm1cD41 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-kXSSmsyC2Jm1cD41 .rough-node .label,#mermaid-svg-kXSSmsyC2Jm1cD41 .node .label,#mermaid-svg-kXSSmsyC2Jm1cD41 .image-shape .label,#mermaid-svg-kXSSmsyC2Jm1cD41 .icon-shape .label{text-align:center;}#mermaid-svg-kXSSmsyC2Jm1cD41 .node.clickable{cursor:pointer;}#mermaid-svg-kXSSmsyC2Jm1cD41 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-kXSSmsyC2Jm1cD41 .arrowheadPath{fill:#333333;}#mermaid-svg-kXSSmsyC2Jm1cD41 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-kXSSmsyC2Jm1cD41 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-kXSSmsyC2Jm1cD41 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kXSSmsyC2Jm1cD41 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-kXSSmsyC2Jm1cD41 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kXSSmsyC2Jm1cD41 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-kXSSmsyC2Jm1cD41 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-kXSSmsyC2Jm1cD41 .cluster text{fill:#333;}#mermaid-svg-kXSSmsyC2Jm1cD41 .cluster span{color:#333;}#mermaid-svg-kXSSmsyC2Jm1cD41 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-kXSSmsyC2Jm1cD41 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-kXSSmsyC2Jm1cD41 rect.text{fill:none;stroke-width:0;}#mermaid-svg-kXSSmsyC2Jm1cD41 .icon-shape,#mermaid-svg-kXSSmsyC2Jm1cD41 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-kXSSmsyC2Jm1cD41 .icon-shape p,#mermaid-svg-kXSSmsyC2Jm1cD41 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-kXSSmsyC2Jm1cD41 .icon-shape .label rect,#mermaid-svg-kXSSmsyC2Jm1cD41 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-kXSSmsyC2Jm1cD41 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-kXSSmsyC2Jm1cD41 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-kXSSmsyC2Jm1cD41 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} TIM3_CH1 PA6
TIM3_CH2 PA7
正转 DIR=0
反转 DIR=1
每 100ms 读取 ΔCNT
编码器 A相
TI1 边沿检测
编码器 B相
TI2 边沿检测
硬件正交解码逻辑
CNT ++
CNT --
TIM3_CNT 寄存器
M 法转速换算

四倍频(Encoder Mode 3,TI1 and TI2)是精度核心:A、B 两相的上升沿和下降沿都计数,一圈的脉冲数 = 编码器线数 × 4。例如 13 线编码器,每电机转产生 52 个计数,角度分辨率从单沿的 27.7° 提升到 6.9°。

相关阅读:《stm32 系列之编码器功能》 --- 用方向判定真值表解释 DIR 位与 CNT 增减关系

三、CubeMX 配置与接线

3.1 硬件接线

编码器引脚 STM32 引脚 说明
A 相(OUTA) PA6(TIM3_CH1) 默认复用,无需重映射
B 相(OUTB) PA7(TIM3_CH2) 默认复用,无需重映射
VCC 5V(独立 LDO) 严禁用开发板 5V 直驱电机
GND 与 MCU 共地 星型接地,避免电机噪声窜入

⚠️ 烧毁风险:编码器逻辑电源 5V 与电机驱动电源必须分立。曾有一版我贪方便用开发板 5V 给电机供电,电机一转开发板就复位------电流不够且地噪声反灌。电机电源与 MCU 电源务必单点共地、各自稳压。

3.2 CubeMX 参数

  • TIM3 → Combined Channels :设为 Encoder Mode
  • Encoder ModeTI1 and TI2(即 Mode 3,四倍频 + 双向判向)
  • Prescaler (PSC)0(不分频,保最高精度)
  • Counter Period (ARR)65535(16 位最大值)
  • TI1 / TI2 Filter10(一阶数字滤波,抑制毛刺)
  • GPIO :PA6 / PA7 设为 Pull-up(编码器未外接上拉时防悬空误触发)

相关阅读:《STM32CubeMX 实战:TIM4 编码器模式在电机控制中的应用》 --- 对照 TIM4 的 CubeMX 步骤与引脚映射

四、核心代码实现(HAL 库)

c 复制代码
// encoder.c ------ 基于 TIM3 硬件编码器接口
#include "main.h"

static int32_t  s_total   = 0;   // 累计计数(有符号,含多圈/回绕)
static uint32_t s_last_raw = 0;  // 上次读取的原始 CNT(16 位)

void Encoder_Start(void)
{
    // CubeMX 已初始化 TIM3 为编码器模式(TI1+TI2,4 倍频)
    HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_ALL);
    s_last_raw = __HAL_TIM_GET_COUNTER(&htim3);
}

// 每 T 秒调用一次(由独立定时器或 SysTick 软定时触发)
// 返回本采样周期的计数增量(已处理 16 位回绕)
int32_t Encoder_Poll(void)
{
    uint32_t raw = __HAL_TIM_GET_COUNTER(&htim3);
    uint32_t d   = raw - s_last_raw;   // uint32 减法,自然处理 65535→0 回绕
    s_last_raw   = raw;
    s_total     += (int32_t)d;
    return (int32_t)d;
}

// M 法转速:d=本周期增量, L=编码器线数(未4倍频), T=采样周期(秒)
// 返回电机轴 RPM
float Encoder_GetRPM(int32_t d, uint16_t L, float T)
{
    return (float)d * 60.0f / (4.0f * (float)L * T);
}
c 复制代码
// main.c 片段 ------ 100ms 周期上报转速
uint32_t last_tick = 0;
Encoder_Start();

while (1)
{
    if (HAL_GetTick() - last_tick >= 100)   // 10Hz 采样
    {
        last_tick = HAL_GetTick();
        int32_t d = Encoder_Poll();
        float rpm = Encoder_GetRPM(d, 13, 0.1f);  // 13 线编码器,T=0.1s
        printf("delta=%ld rpm=%.1f\r\n", d, rpm);
    }
}

关键设计 :用 uint32_t d = raw - s_last_raw 做差,即使 CNT 从 65535 回绕到 0,无符号减法也能得到正确的正/负增量(硬件 DIR 位决定 CNT 增减,回绕只是模运算),无需开溢出中断即可应对中高速。

五、测试验证(量化数据)

5.1 正反转与方向验证

断开电机驱动,仅给编码器供电,手动正向/反向旋转输出轴:

操作 CNT 变化 串口 RPM 符号
正向慢转一圈 +52(13 线 × 4) +
反向慢转一圈 −52
正反转交替 正负交替正确累加 ± 交替

5.2 M 法测速精度(逻辑分析仪给定标准脉冲)

用逻辑分析仪向 PA6/PA7 注入标准正交脉冲,对比理论 RPM 与实测:

目标转速 理论 RPM 实测 RPM 误差 主因
低速 28.8 26.5 −8.0% M 法每周期最少 1 计数误差,低速 ΔCNT 太小
中速 300 295.4 −1.5% 量化误差占比下降
高速 1800 1782 −1.0% 接近理论极限

相关阅读:《STM32 编码器测速实战指南》 --- 讲清 M 法(高速稳)与 T 法(低速准)的适用分界

5.3 硬件模式 vs 软件中断方案

指标 软件 EXTI 中断计数 硬件编码器接口
CPU 峰值占用 18% <2%
高速丢脉冲率 8%(>100Hz 控制频率) 0(硬件零丢)
方向误判 偶有(中断响应抖动) 无(硬件锁存)

理论对照:M 法量化误差理论上限 = 1/ΔCNT。13 线编码器在 T=0.1s 下,28.8RPM 对应 ΔCNT≈12.5,理论误差 ±8%,与实测 −8.0% 吻合;300RPM 对应 ΔCNT≈130,理论误差 ±0.77%,实测 −1.5% 在合理范围内(含机械抖动)。

六、故障排查(六类)

1. CNT 一直是 0,完全不计数

最常见原因:① TIM3 时钟没开;② PA6/PA7 没配成复用推挽(仍是普通 GPIO);③ 编码器 VCC 没接或接错电压。先万用表量编码器 VCC、再查 CubeMX 引脚是否为 TIM3_CHx 复用。

2. 只能计数、不能判向(正反转数值都递增)

根因:CubeMX 里 Encoder Mode 选成了 TI1 only(Mode 1)或 TI2 only(Mode 2)。必须选 TI1 and TI2(Mode 3)才能用两相边沿判向。

3. 数值乱跳、随机跳变

根因:PA6/PA7 内部上拉没开,且编码器未外接上拉,引脚悬空受电机电磁干扰。打开 GPIO 内部上拉;若已有外部上拉则关内部上拉省功耗。同时检查星型接地------80% 的跳变是地线接触不良。

4. 高速时转速算出负值 / 剧烈跳变

根因:ARR=65535 下高速跑一会儿 CNT 回绕,若用 int16 或没做回绕处理就会算错。本文用 uint32_t 差值 + int32_t 累加已规避;超长时高速运行建议改用 int64 累计或开溢出中断记录翻表次数。

5. 编码器一接电机,MCU 就重启

根因:编码器/电机电源与 MCU 未隔离、或图省事用开发板 5V 直驱电机。电机启停电流大、地噪声反灌。务必电机电源独立、与 MCU 单点共地。

6. 低速测不准(误差 8%+)

这是 M 法固有短板:低速时一个采样周期内脉冲数极少,最少 1 个计数的误差被放大。解决:降低采样周期 T(如改 50ms)、或低速段切换 T 法(测单脉冲周期反推转速)。

七、总结

核心要点

  1. 标准 AB 相编码器优先用定时器硬件编码器接口,零 CPU 开销、方向零误判。
  2. Encoder Mode 必须选 TI1 and TI2(Mode 3)才能四倍频 + 判向。
  3. M 法转速公式:RPM = ΔCNT × 60 / (4 × 线数 × 采样周期),注意 ΔCNT 是带符号增量。
  4. uint32_t 差值天然处理 16 位回绕,中高速无需溢出中断。
  5. 编码器电源独立、星型接地,是抗干扰的命门。

适用边界:M 法适合中高速(≥30RPM);低速需换 T 法或缩短采样周期。编码器线数未知时,先手动转一圈记录 ΔCNT 实测标定。

扩展方向 :本文只做测速,把转速反馈接上 PID 即构成速度闭环(见 《STM32 编码器正交解码与 PID 速度闭环控制实战》);双电机差分 + 航向角即可做小车里程计与定向行走。如需获取本文完整代码和更多 STM32 实战项目,可开通 CSDN 技术会员

📝 版本备注

  • 硬件平台:STM32F103C8T6 + JGA25-370(13 线霍尔编码器,1:30 减速)
  • 软件版本:STM32CubeMX 6.12 + HAL 库(F1 v1.1.4)+ Keil MDK 5.38
  • 兼容说明:代码适用于全部带通用定时器的 STM32(F1/F4/L4 等);引脚随芯片数据手册重映射调整即可,编码器接口逻辑完全一致

参考资料

  1. STM32 编码器接口硬件正交解码原理与工程实践
  2. stm32 系列之编码器功能(四倍频与方向判定真值表)
  3. STM32CubeMX 实战:TIM4 编码器模式在电机控制中的应用
  4. 霍尔编码器测速(CubeMX + 定时器配置)
  5. STM32 编码器测速实战指南(M 法 / T 法适用分界)
  6. STM32 编码器正交解码与 PID 速度闭环控制实战
相关推荐
星河单片机3 小时前
STM32驱动WaterSensor水位传感器+OLED显示完整原理与实验
stm32·单片机·嵌入式硬件
子文雨4 小时前
基于 uC3845 与 ATtiny13 双芯片架构的 8.4V/2.5Ah 锂电池智能充电器设计
单片机·嵌入式硬件·算法
快秃头的码农5 小时前
esp32-s3移植lvgl总结
嵌入式硬件·esp32-s3
恒锐丰科技林技术员6 小时前
EG1191 宽压降压 DC‑DC 芯片解析:高性价比工业电源方案
经验分享·嵌入式硬件·硬件工程
千秋岁rr6 小时前
8TIM编码器接口
stm32
智购科技无人售货机工厂店6 小时前
2026自动售货机大屏交互设计原则:从信息层级到视觉动线的工程实践~YH
运维·python·单片机·嵌入式硬件·自动化·交互
艾芯微科技9 小时前
SKY13370‑374LF|0.5‑6GHz SPDT 射频开关,WLAN/LTE 射频前端实用器件
网络·单片机·嵌入式硬件·集成测试·51单片机
jianqiang.xue11 小时前
收官:从v0.1到“研发新常态“,一套嵌入式智能体的落地与边界
stm32·单片机·物联网·架构·esp32
HUI-47411 小时前
EG1131|SOT23‑6 30V/1.5A 电流模式同步降压 DC‑DC,小体积消费类电源优选
嵌入式硬件·硬件工程