文章目录
-
- 前言
- 一、为什么用硬件编码器接口,而不是软件计数
- 二、核心原理:四倍频与方向判别
- [三、CubeMX 配置与接线](#三、CubeMX 配置与接线)
-
- [3.1 硬件接线](#3.1 硬件接线)
- [3.2 CubeMX 参数](#3.2 CubeMX 参数)
- [四、核心代码实现(HAL 库)](#四、核心代码实现(HAL 库))
- 五、测试验证(量化数据)
-
- [5.1 正反转与方向验证](#5.1 正反转与方向验证)
- [5.2 M 法测速精度(逻辑分析仪给定标准脉冲)](#5.2 M 法测速精度(逻辑分析仪给定标准脉冲))
- [5.3 硬件模式 vs 软件中断方案](#5.3 硬件模式 vs 软件中断方案)
- 六、故障排查(六类)
- 七、总结
- 参考资料
摘要:直流减速电机是智能小车动力核心,开环 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 Mode :
TI1 and TI2(即 Mode 3,四倍频 + 双向判向) - Prescaler (PSC) :
0(不分频,保最高精度) - Counter Period (ARR) :
65535(16 位最大值) - TI1 / TI2 Filter :
10(一阶数字滤波,抑制毛刺) - 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 法(测单脉冲周期反推转速)。
七、总结
核心要点
- 标准 AB 相编码器优先用定时器硬件编码器接口,零 CPU 开销、方向零误判。
- Encoder Mode 必须选
TI1 and TI2(Mode 3)才能四倍频 + 判向。 - M 法转速公式:
RPM = ΔCNT × 60 / (4 × 线数 × 采样周期),注意 ΔCNT 是带符号增量。 uint32_t差值天然处理 16 位回绕,中高速无需溢出中断。- 编码器电源独立、星型接地,是抗干扰的命门。
适用边界: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 等);引脚随芯片数据手册重映射调整即可,编码器接口逻辑完全一致