STM32F103编码器测速实战:从CubeMX配置到PID闭环控制
在智能小车、机械臂和云台等嵌入式项目中,精确获取电机转速是实现闭环控制的关键。本文将手把手带您完成基于STM32F103和HAL库的编码器测速全流程,涵盖硬件选型、CubeMX避坑配置、M/T法测速实现以及数据验证等核心环节。
1. 编码器选型与参数解析
选择适合的编码器是项目成功的第一步。市面上常见的增量式旋转编码器主要分为光电式和磁电式两种,它们在智能车竞赛和工业应用中最为普遍。
关键参数解读:
| 参数名称 | 说明 | 典型值示例 |
|---|---|---|
| 线数(PPR) | 编码器旋转一圈产生的脉冲数,直接影响分辨率 | 500线、1024线 |
| 输出相位 | A/B两相输出,相位差90度用于判断方向 | 正交方波 |
| 供电电压 | 常见5V或3.3V,需与控制器匹配 | 5V±10% |
| 最大响应频率 | 决定可测量的最高转速 | 100kHz |
| 机械安装方式 | 轴型或孔型,影响与电机的连接方式 | 6mm轴径 |
实际项目中选择编码器时,需要特别注意线数与电机最高转速的匹配关系。例如500线编码器在10000RPM时输出频率为:500×10000/60≈83.3kHz,应确保编码器的最大响应频率高于此值。
减速电机参数换算:带减速箱的电机需要额外考虑减速比参数。例如某电机参数为:
- 电机空载转速:10000 RPM
- 减速比:30:1
- 编码器线数:500 PPR
则输出轴的理论分辨率为:
每转脉冲数 = 编码器线数 × 减速比 × 倍频数 = 500 × 30 × 4 = 60,000 脉冲/转2. CubeMX配置避坑指南
使用STM32CubeMX配置编码器接口时,有几个关键设置点容易出错:
2.1 定时器基础配置
- 选择支持编码器模式的定时器(TIM1-TIM5)
- 设置预分频器(Prescaler)为0 - 确保计数器直接使用输入时钟
- 自动重装载值(AutoReload)设为最大值65535
- 计数模式选择"Encoder Mode TI1 and TI2"
常见配置误区:
- 误将Polarity理解为边沿触发(实际为信号反相设置)
- 忽略滤波器设置导致噪声干扰(建议2-5个时钟滤波)
- 未启用定时器溢出中断(影响长周期测量)
2.2 引脚配置示例
// 典型引脚配置(以TIM4为例) TIM4_CH1 -> PB6 // 编码器A相 TIM4_CH2 -> PB7 // 编码器B相特别注意:编码器模式仅适用于通道1和通道2,通道3和4无法使用此功能。
3. 编码器测速算法实现
3.1 M法测速(适合高速场景)
// 宏定义 #define ENCODER_PPR 500 // 编码器线数 #define GEAR_RATIO 30 // 减速比 #define SAMPLE_TIME 0.1f // 采样时间100ms float calculate_speed_method(uint32_t pulse_count) { // 四倍频模式下实际脉冲数 = 原始计数/4 float real_pulses = pulse_count / 4.0f; // 转轴转速 = (脉冲数/线数) / 采样时间 float shaft_rps = (real_pulses / ENCODER_PPR) / SAMPLE_TIME; // 输出轴转速 = 转轴转速 / 减速比 return shaft_rps / GEAR_RATIO; }3.2 T法测速(适合低速场景)
float calculate_speed_t_method(uint32_t period_ticks, uint32_t timer_freq) { // 脉冲周期(秒) = 计数值/定时器频率 float pulse_period = period_ticks / (float)timer_freq; // 转轴转速 = (1/线数) / 脉冲周期 float shaft_rps = (1.0f / ENCODER_PPR) / pulse_period; return shaft_rps / GEAR_RATIO; }3.3 方向判断与溢出处理
// 在定时器溢出中断中处理方向计数 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if(htim == &htim4) { if(__HAL_TIM_IS_TIM_COUNTING_DOWN(htim)) { overflow_count--; // 反向溢出 } else { overflow_count++; // 正向溢出 } } } // 获取完整32位计数值 int32_t get_encoder_total() { return (overflow_count * 65536) + __HAL_TIM_GET_COUNTER(&htim4); }4. 数据验证与调试技巧
4.1 示波器验证信号质量
- 检查A/B相信号的相位差是否为90度
- 观察信号上升/下降时间是否符合要求
- 确认无毛刺和信号抖动
常见问题排查表:
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 计数方向不稳定 | 信号相位差偏离90度 | 检查编码器安装或更换编码器 |
| 高速时计数丢失 | 信号边沿质量差 | 增加RC滤波或使用差分编码器 |
| 低速时测量不准 | 采样时间过长 | 改用T法或M/T混合法 |
| 计数突然归零 | 未处理定时器溢出 | 启用溢出中断并扩展计数器位数 |
4.2 软件滤波处理
// 移动平均滤波实现 #define FILTER_WINDOW 5 typedef struct { float buffer[FILTER_WINDOW]; uint8_t index; } SpeedFilter; float filter_speed(SpeedFilter* filter, float new_speed) { filter->buffer[filter->index] = new_speed; filter->index = (filter->index + 1) % FILTER_WINDOW; float sum = 0; for(int i=0; i<FILTER_WINDOW; i++) { sum += filter->buffer[i]; } return sum / FILTER_WINDOW; }5. 与PID控制的集成应用
将编码器测速结果用于电机PID速度控制时,需要注意以下几点:
- 采样时间匹配:PID计算周期应与速度测量周期一致
- 单位统一:确保设定值与反馈值使用相同单位(如RPM或RPS)
- 抗积分饱和:电机停止时需处理积分项累积问题
典型PID速度控制代码框架:
typedef struct { float kp, ki, kd; float integral; float prev_error; uint32_t last_time; } PIDController; float pid_update(PIDController* pid, float setpoint, float measurement) { uint32_t now = HAL_GetTick(); float dt = (now - pid->last_time) / 1000.0f; pid->last_time = now; float error = setpoint - measurement; pid->integral += error * dt; float derivative = (error - pid->prev_error) / dt; pid->prev_error = error; return pid->kp * error + pid->ki * pid->integral + pid->kd * derivative; }在实际云台控制项目中,采用编码器测速配合PID控制可将速度波动控制在±2%以内,显著提升系统稳定性。一个常见的调试技巧是先用纯P控制确定大致参数范围,再逐步加入I和D参数进行精细调节。