前两篇文章:
https://www.elecfans.com/d/1796839.html
https://www.elecfans.com/d/1798370.html
分别介绍PID速度控制和PID位置控制,用于控制电机继续以所需速度旋转以及旋转至所需位置(圈数)。这里只有一个期望值,但是如果想以期望的速度旋转到达期望的位置(不考虑启动和停止的加减速过程)怎么控制呢?那么我们需要将两者结合起来,即PID串级控制来控制电机。
PID串级控制的典型结构是位置环+速度环+电流环,如下图所示。
在PID串级控制中,最外环的输入是整个控制系统的期望值,外环PID的输出值是内环PID的期望值。
使用三环控制的前提是硬件支持。例如,位置环和速度环需要实时电机旋转位置和旋转速度作为反馈,这就需要电机配备编码器来测量速度和旋转位置;电流环需要有一个电流采样电路来实时获取电机电流作为反馈。
如果没有电流采样电路,可以去掉电流环,只用位置环+速度环。系统的期望仍然是旋转位置,内循环可以调节旋转速度。
另外,如果只是想控制电机转速来实现电机调速,可以采用速度环+电流环。系统的期望仍然是轮换位置。内环可以调节电机电流,增强系统旋转调节的抗干扰能力。
由于我的电机没有电流测量电路,所以本文采用位置环+速度环来学习PID串级控制。只需按照下面的图片操作即可:
由于是级联PID控制,每一级PID必须有自己的参数。本实验采用位置PID+速度PID。参数定义如下:
/*定义位置PID和速度PID结构的全局变量*/PID pid_location;PID pid_speed;/** * @brief PID参数初始化*@note None* @retval None*/void PID_param_init(){/* 位置相关初始化参数*/pid_location.target_val=TOTAL_RESOLUTION*10;pid_location.output_val=0.0;pid_location.err=0.0;pid_location.err_last=0.0;pid_location.integral=0.0;pid_location.Kp=0.05;pid_location.Ki=0;pid_location.Kd=0;/* 速度相关初始化参数*/pid_speed.target_val=10.0;pid_speed.output_val=0.0;pid_speed.err=0.0;pid_speed.err_last=0.0;pid_speed.integral=0.0;pid_speed.Kp=80.0;pid_speed. Ki=2.0;pid_speed.Kd=100.0;}
这里有两点需要注意:
闭环死区是指执行器的最小控制量,无法再调节以满足控制精度。如果继续调整,系统会在目标值前后频繁移动,无法稳定。
例如,某个系统的控制精度为1,但目标值需要为1.5,无论怎么调整,最终的结果只能控制在1或2,永远达不到预设值。小数点后的这1.5L范围就是闭环死区。系统不可控,误差始终存在,容易出现振荡。
通过积分分离实现反积分饱和。积分饱和是指执行机构已达到极限输出能力,但仍达不到目标值,静态误差在较长时间内无法消除。
例如,如果PWM 输出达到100%,仍然无法到达所需位置。如果此时误差不断累加,经过一段时间后,PID的积分项就会累加一个很大的值。如果此时达到目标值或者复位则已经达到目标值。由于积分的累积误差很大,系统无法立即调整到目标值,可能会导致超调或不平衡。
解决积分饱和问题的一种方法是使用积分分离。该方法仅当累积误差小于某个阈值时才使用积分项。如果累计误差太大,则不再继续累计误差,相当于只使用PD控制器。
闭环死区、积分分离的PID控制流程如下:
完整的位置PID代码如下:
/** * @brief 位置PID算法实现* @param effective_val: 实际值*@note None* @retval PID计算后输出*/#define LOC_DEAD_ZONE 60 /*位置环死区*/#define LOC_INTEGRAL_START_ERR 200 /*积分对应误差分离时的范围*/#define LOC_INTEGRAL_MAX_VAL 800 /*限制积分范围,防止积分饱和*/float location_pid_realize(PID *pid, float effective_val){/*计算目标值与实际值之间的误差*/pid-err=pid-target_val - Actual_val;/* 设置闭环死区*/if((pid-err=-LOC_DEAD_ZONE) (pid-err=LOC_DEAD_ZONE)){pid-err=0;pid-integral=0;pid- err_last=0;}/*积分项,积分分离,偏差较大时去掉积分作用*/if(pid-err -LOC_INTEGRAL_START_ERR pid-err LOC_INTEGRAL_START_ERR){pid-integral +=pid-err; /*限制积分范围,防止积分饱和*/if(pid-integral LOC_INTEGRAL_MAX_VAL){pid-integral=LOC_INTEGRAL_MAX_VAL;}else if(pid-integral -LOC_INTEGRAL_MAX_VAL){pid-integral=-LOC_INTEGRAL_MAX_VAL;}}/*PID算法实现*/pid-output_val=pid- Kp * pid-err + pid-Ki * pid-integral + pid-Kd * (pid-err - pid-err_last);/*错误转移*/pid-err_last=pid-err ;/*返回当前实际值*/return pid-output_val;}
//周期性定时器回调函数void AutoReloadCallback(){static uint32_t location_timer=0; //位置环周期static __IO intencoderNow=0; /*当前时刻的总值*/static __IO int encoderLast=0; /*上一时刻的总值*/intencoderDelta=0; /*当前时刻与上一时刻编码器的变化*/float effective_speed=0; /*实际测量的速度*/int effective_speed_int=0;int res_pwm=0;/*PID计算出的PWM值*/static int i=0;/*[1]读取编码器的值*/encoderNow=read_encoder() + EncoderOverflowCnt*ENCODER_TIM_PERIOD;/*获取当前的累加值*/encoderDelta=encoderNow -encoderLast; /*获取变化值*/encoderLast=encoderNow;/*更新上次累加值*//*[2]位置PID运算,获取PWM控制值*/if ((location_timer++ % 2)==0){float控制值=0; /*当前控制值*//*位置PID计算*/control_val=location_pid_realize(pid_location,encoderNow); /*目标速度值限制*/speed_val_protect(control_val); /*设置速度PID的目标值*/set_pid_target(pid_speed, control_val); } /* 速度(每秒多少转)=单位时间计数值/总分辨率*时间系数,乘以60就成为每分钟多少转*/actual_speed=(float)encoderDelta/TOTAL_RESOLUTION * 10 * 60 ; /*【3】速度PID运算,得到PWM控制值*/actual_speed_int=actual_speed;res_pwm=pwm_val_protect((int)speed_pid_realize(pid_speed,actual_speed));/*[4]PWM控制电机*/set_motor_rotate(res_pwm) ;/*[5]上传数据到上位机显示*/set_computer_value(SEND_FACT_CMD, CURVES_CH1,encoderNow, 1); /*发送实际数据到通道1电机[位置]值*/} PID通过定时器调用计算,每10ms计算一次。从代码中可以看出,内环(速度PID)控制的周期比外环(位置PID)控制的周期短。位置PID每两个周期计算一次,因为内环控制最终的输出。这个输出对应的是实际场景中的控制量(本次实验最终控制的是位置)。位置无法变异,需要时间积累。因此内循环输出尽可能快。
在
视频中,测试以不同的目标速度到达目标位置。视频后半部分测试引入干扰时的控制效果:
https://www.bilibili.com/video/BV1QK4y1g7yg?spm_id_from=333.999.0.0
上一篇
新型工业化理念,新型工业化内涵