共计 2465 个字符,预计需要花费 7 分钟才能阅读完成。
最近在做一个基于 STM32 的电机控制项目,用到了 520 系列增量式编码器。本以为配置起来会很简单,结果在实际调试过程中遇到了不少坑——信号抖动、计数误差、响应延迟等问题接踵而至。经过一番折腾和优化,终于总结出一套稳定可靠的解决方案,现在把经验分享给大家。
一、问题背景:为什么你的编码器数据不准?
刚开始测试时,电机低速运转还算正常,但一旦转速超过 200RPM,编码器计数值就开始出现明显偏差。用逻辑分析仪抓取信号后发现两个典型现象:
- 毛刺干扰 :AB 相信号线上叠加了高频噪声(示波器显示峰峰值约 300mV)
- 脉冲丢失 :高速时出现 4 - 6 个脉冲宽度的信号畸变
经过排查,发现问题主要来自:
- 电机 PWM 驱动产生的电磁干扰
- 5V 编码器信号直接接入 3.3V 单片机导致的电平不匹配
- 长距离传输线引入的共模噪声
二、硬件设计:给信号穿上 ” 防弹衣 ”
解决思路是构建三级防护:

- 光电隔离层 :
- 使用 HCPL-2631 光耦隔离器
- 编码器侧供电 5V,MCU 侧 3.3V
-
添加 10KΩ 上拉电阻
-
RC 滤波网络 :
- 信号线串联 33Ω 电阻
-
对地并联 100pF 电容(滤除 >10MHz 噪声)
-
ESD 保护 :
- TVS 二极管 SMF05C(钳位电压 5.5V)
- 防止静电击穿
实测显示,改造后信号抖动幅度从 300mV 降到 50mV 以内。
三、定时器配置:让 STM32 的编码器模式物尽其用
使用 TIM3 的编码器接口模式,关键配置如下:
// 初始化代码(HAL 库 + 寄存器级优化)void Encoder_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; // ARR 最大值
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 = 6; // 设置滤波器(0x0~0xF)sConfig.IC2Polarity = TIM_ICPOLARITY_RISING;
sConfig.IC2Selection = TIM_ICSELECTION_DIRECTTI;
sConfig.IC2Prescaler = TIM_ICPSC_DIV1;
sConfig.IC2Filter = 6;
HAL_TIM_Encoder_Init(&htim3, &sConfig);
// 手动使能计数器(HAL 库遗漏的关键步骤!)__HAL_TIM_ENABLE(&htim3);
TIM3->CNT = 32768; // 初始值设在中点
}
几个容易踩坑的点:
- 滤波器设置 :ICxFilter 参数对应寄存器位是 4bit,值越大滤波效果越强但延迟也增加
- 计数器溢出 :Period 设置为 65535 时,16 位计数器不会自动重载
- GPIO 复用 :务必检查 AF 映射表,TIM3_CH1/CH2 在不同 STM32 型号上可能对应不同引脚
四、软件处理:让数据更平滑的秘诀
即使硬件优化后,仍需要软件滤波。这里采用移动平均 + 中值滤波的组合算法:
#define FILTER_WINDOW 5
volatile int32_t speed_buf[FILTER_WINDOW]; // volatile 防止编译器优化
int32_t Get_Filtered_Speed(void) {
static uint8_t index = 0;
int32_t temp_buf[FILTER_WINDOW];
// DMA 读取计数器值(避免打断编码器计数)speed_buf[index] = (int32_t)TIM3->CNT - 32768;
TIM3->CNT = 32768; // 重置计数器
// 中值滤波算法
memcpy(temp_buf, speed_buf, sizeof(speed_buf));
Bubble_Sort(temp_buf, FILTER_WINDOW); // 排序函数需自行实现
index = (index + 1) % FILTER_WINDOW;
return temp_buf[FILTER_WINDOW/2]; // 返回中值
}
实测表明,该算法在 500RPM 转速下可将数据波动幅度降低 83%。
五、效果验证:数字不说谎
通过 JScope 工具捕获的对比数据:
| 指标 | 优化前 | 优化后 | 提升幅度 |
|---|---|---|---|
| 信号抖动 | ±15LSB | ±2LSB | 86.7% |
| 响应延迟 | 8.2ms | 1.5ms | 81.7% |
| 脉冲丢失率 | 3.2% | 0.05% | 98.4% |
六、避坑指南:血泪经验总结
- 配置顺序陷阱 :
- 错误做法:先配置 GPIO 再初始化 TIM
-
正确顺序:TIM_Encoder_Init() → GPIO_Init() → 手动使能计数器
-
GPIO 复用冲突 :
- 现象:TIM3_CH1 与 USART2_TX 共用 PA2
-
解决方案:重映射到 PB4/PB5,或改用 TIM4
-
中断优先级问题 :
- 溢出中断必须低于 PWM 中断优先级
- 建议设置:PWM(0) > Encoder(1) > 其他 (3)
结语与思考
经过这一轮优化,编码器在 1000RPM 转速下也能稳定工作。不过新的需求又来了——客户要求增加第二编码器实现全闭环控制。我打算尝试用 TIM1 的 PWM 输入捕获功能来实现,但这会面临两个新挑战:
- 如何同步处理两个编码器的数据?
- 当两个编码器读数不一致时,应该相信哪一个?
如果你有好的解决方案,欢迎在评论区交流讨论。
正文完
