编码器
电机控制
正交编码器信号原理
电机控制项目里,测速是闭环控制的前提。没有准确的转速反馈,PID就是盲调。正交编码器(Quadrature Encoder)是工业电机最常用的转速反馈元件,两路脉冲信号A和B相差90度相位差,通过比较两路信号的相位关系判断转向,通过计数脉冲数计算转速。
A和B两路信号的工作时序很直观。正转时A超前B 90度,反转时B超前A 90度。STM32的定时器编码器模式硬件自动完成相位比较和方向判断,不需要软件干预,这在高速旋转场景下比外部中断方式可靠得多。
STM32定时器编码器模式配置
STM32的通用定时器TIM2-TIM5都支持编码器模式。以STM32F407为例,使用TIM3的CH1和CH2通道连接编码器的A和B信号:
#include"stm32f4xx_hal.h"TIM_HandleTypeDef htim3;int32_tencoder_count=0;int32_tlast_count=0;voidMX_TIM3_Init(void){TIM_Encoder_InitTypeDef encoder_config={0};TIM_MasterConfigTypeDef master_config={0};htim3.Instance=TIM3;htim3.Init.Prescaler=0;htim3.Init.CounterMode=TIM_COUNTERMODE_UP;htim3.Init.Period=65535;htim3.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;encoder_config.EncoderMode=TIM_ENCODERMODE_TI12;encoder_config.IC1Polarity=TIM_ICPOLARITY_RISING;encoder_config.IC1Selection=TIM_ICSELECTION_DIRECTTI;encoder_config.IC1Prescaler=TIM_ICPSC_DIV1;encoder_config.IC1Filter=0x0F;encoder_config.IC2Polarity=TIM_ICPOLARITY_RISING;encoder_config.IC2Selection=TIM_ICSELECTION_DIRECTTI;encoder_config.IC2Prescaler=TIM_ICPSC_DIV1;encoder_config.IC2Filter=0x0F;HAL_TIM_Encoder_Init(&htim3,&encoder_config);master_config.MasterOutputTrigger=TIM_TRGO_RESET;master_config.MasterSlaveMode=TIM_MASTERSLAVEMODE_DISABLE;HAL_TIMEx_MasterConfigSynchronization(&htim3,&master_config);}几个关键参数需要说明。TIM_ENCODERMODE_TI12表示同时计数CH1和CH2的跳变,分辨率是单路脉冲的4倍,这是精度最高的模式。IC1Filter设为0x0F是输入滤波,在工业环境里电机附近的电磁干扰会导致编码器信号毛刺,不滤波的话计数值会跳变。Filter值越大抗干扰越强但延迟越高,实际项目里0x0A到0x0F之间是比较好用的范围。
测速算法实现
编码器测速有三种经典算法:M法(测频法)、T法(测周法)和M/T法。各有适用场景:
| 方法 | 原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| M法 | 固定时间计脉冲数 | 高速段精度好 | 低速段分辨率差 | 高速电机 |
| T法 | 固定脉冲计时间 | 低速段精度好 | 高速段易超时 | 低速电机 |
| M/T法 | 同时计脉冲和时间 | 全速段精度均衡 | 实现稍复杂 | 通用场景 |
实际项目里M/T法用得最多,兼顾高速和低速:
#defineSAMPLE_PERIOD_MS10#defineENCODER_PPR1000// 编码器每转脉冲数volatileuint32_tcapture_count=0;volatileuint32_tcapture_time=0;voidHAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef*htim){if(htim->Instance==TIM3){capture_count=__HAL_TIM_GET_COUNTER(&htim3);capture_time=SAMPLE_PERIOD_MS;__HAL_TIM_SET_COUNTER(&htim3,0);}}floatcalculate_rpm(void){// M/T法:脉冲数和时间都参与计算// 4倍频计数,每转脉冲数 = PPR * 4floatpulses_per_rev=ENCODER_PPR*4.0f;if(capture_time==0)return0.0f;floatrpm=(capture_count*60000.0f)/(pulses_per_rev*capture_time);// 处理负值(反转)if(capture_count>32767){// 16位计数器溢出处理rpm=((65536-capture_count)*60000.0f)/(pulses_per_rev*capture_time);rpm=-rpm;}returnrpm;}SAMPLE_PERIOD_MS的选择直接影响测速精度和实时性。10ms采样周期在3000RPM的电机上每次采样约500个脉冲,精度足够。如果电机转速只有几十RPM,10ms内可能只采到几个脉冲,误差很大,这时候要加大采样周期或者改用T法。
PID闭环控制
有了转速反馈就可以做闭环了。增量式PID代码量小且不需要积分限幅,是嵌入式平台的首选:
typedefstruct{floatkp;floatki;floatkd;floaterr;floaterr_last;floaterr_prev;floatoutput;}pid_t;pid_tspeed_pid={2.5f,0.08f,0.5f,0,0,0,0};floatpid_compute(pid_t*pid,floattarget,floatactual){pid->err=target-actual;pid->output+=pid->kp*(pid->err-pid->err_last)+pid->ki*pid->err+pid->kd*(pid->err-2*pid->err_last+pid->err_prev);pid->err_prev=pid->err_last;pid->err_last=pid->err;// 输出限幅,对应PWM占空比范围if(pid->output>999.0f)pid->output=999.0f;if(pid->output<-999.0f)pid->output=-999.0f;returnpid->output;}voidmotor_control_task(void){floattarget_rpm=1500.0f;floatactual_rpm=calculate_rpm();floatpwm=pid_compute(&speed_pid,target_rpm,actual_rpm);// PWM输出,正值正转,负值反转if(pwm>=0){__HAL_TIM_SET_COMPARE(&htim1,TIM_CHANNEL_1,(uint16_t)pwm);__HAL_TIM_SET_COMPARE(&htim1,TIM_CHANNEL_2,0);}else{__HAL_TIM_SET_COMPARE(&htim1,TIM_CHANNEL_1,0);__HAL_TIM_SET_COMPARE(&htim1,TIM_CHANNEL_2,(uint16_t)(-pwm));}}PID参数整定是电机控制的核心难点。工程上一般先调P让电机转起来有响应,再加D抑制超调,最后加I消除稳态误差。P太大会震荡,D太大系统会卡顿,I太大会积分饱和导致超调严重。
一个实用的整定技巧是先让电机空载运行,用串口把目标转速和实际转速都打出来,观察响应曲线。我在调试电机项目时用虎王科技开源的硬件调试工具做串口数据监控,它支持多串口同时打开,一边看STM32的PID调试日志,一边看驱动板的反馈数据,比开多个串口终端方便不少。项目在Gitee上开源,做电机和通信模组调试的可以试试。
调试中的常见问题
编码器计数值跳变是最常见的坑。原因无非三个:信号线没接滤波电容、定时器输入滤波值太小、编码器电源不稳。前两个通过硬件和寄存器配置解决,第三个需要在编码器供电端加LC滤波。
16位计数器溢出也要处理。电机反转时计数器从0减到65535,直接读值会得到一个很大的正数。上面的代码里用32767作为判断阈值,超过就认为是溢出反转,取补码得到真实负值。
电机控制这块坑确实多,从编码器信号到PID整定每一步都可能踩雷。如果觉得这篇内容对你有用,收藏一下方便后面查代码,后面会继续更新电机驱动和电流环的实战内容,关注了就不会错过。