实际调参
从实际的 PID 系统曲线来理解 PID 各个系数的调节效果。
① 先调整比例系数,积分、微分系数设置为 0,此时的系统只有比例环节参与控制,此时系统的曲线出现大幅振荡。
首先确定硬件上是否出现了故障,例如电压不稳定、电机堵转等,排除了这些之后,那就说明比例系数调节的过大了,这个时候我们可以把比例系数慢慢地减小,并同时观察曲线的变化。
② 当我们调小比例系数之后,曲线的大幅度振荡现象消失,但是曲线依旧存在小幅度的超调现象,并且此时通过调节比例系数已经无法优化曲线。
此时,我们可以慢慢地增大微分系数,并同时观察曲线的变化,从而找到最合适的参数。
增大微分系数之后,如果系统的曲线已经较为理想,则说明这个系统只需要比例和微分环节的控制。
③ 如果在纯比例环节的控制下,系统的实际值始终达不到目标值,存在静态误差。
此时,可以逐渐增大积分系数,并同时观察曲线的变化,如果消除静差的时间过长,则可以再适当增大积分系数,但是需要注意兼顾系统的超调量。
经过调整之后,如果系统的曲线已经较为理想,则说明这个系统只需要比例和积分环节的控制。
④ 如果系统在比例和积分环节的控制下出现小幅度的超调现象,可以慢慢地增大微分系数,并同时观察曲线的变化,从而找到最合适的参数。
以上就是在实际调参中经常遇到的一些问题以及解决方法。在实际应用中,控制系统是多样且复杂的,这一些方法只能作为参考,并不是通用的,因此在 PID 调参过程中,要注意经验的积累。
参考Code
PID初始化代码
定义一个新的PID参数时,就是建立一个新的结构体,运算和初始化时直接调用对应的成员变量就行,十分方便简洁,具体定义的结构体如下:
typedef struct{
//PID运算模式
uint8_t mode;
//PID 三个基本参数
__IO float Kp;
__IO float Ki;
__IO float Kd;
__IO float max_out; //PID最大输出
__IO float max_iout; //PID最大积分输出
__IO float2 set; //PID目标值
__IO float2 fdb; //PID当前值
__IO float out; //三项叠加输出
__IO float Pout; //比例项输出
__IO float Iout; //积分项输出
__IO float Dout; //微分项输出
//微分项最近三个值 0最新 1上一次 2上上次
__IO float Dbuf[3];
//误差项最近三个值 0最新 1上一次 2上上次
__IO float error[3];
} pid_type_def;
初始运行时调用一次,初始化各个参数
void Own_PID_init(pid_type_def *pid, uint8_t mode, const __IO float PID[3], __IO float max_out, __IO float max_iout){
if (pid == NULL || PID == NULL){
return;
}
pid- >mode = mode;
pid- >Kp = PID[0];
pid- >Ki = PID[1];
pid- >Kd = PID[2];
pid- >max_out = max_out;
pid- >max_iout = max_iout;
pid- >Dbuf[0] = pid- >Dbuf[1] = pid- >Dbuf[2] = 0.0f;
pid- >error[0] = pid- >error[1] = pid- >error[2] = pid- >Pout = pid- >Iout = pid- >Dout = pid- >out = 0.0f;
}
PID运算代码
__IO float PID_calc(pid_type_def *pid, __IO float ref, __IO float set)
{
//判断传入的PID指针不为空
if (pid == NULL){
return 0.0f;
}
//存放过去两次计算的误差值
pid- >error[2] = pid- >error[1];
pid- >error[1] = pid- >error[0];
//设定目标值和当前值到结构体成员
pid- >set = set;
pid- >fdb = ref;
//计算最新的误差值
pid- >error[0] = set - ref;
//判断PID设置的模式
if (pid- >mode == PID_POSITION)
{
//位置式PID
//比例项计算输出
pid- >Pout = pid- >Kp * pid- >error[0];
//积分项计算输出
pid- >Iout += pid- >Ki * pid- >error[0];
//存放过去两次计算的微分误差值
pid- >Dbuf[2] = pid- >Dbuf[1];
pid- >Dbuf[1] = pid- >Dbuf[0];
//当前误差的微分用本次误差减去上一次误差来计算
pid- >Dbuf[0] = (pid- >error[0] - pid- >error[1]);
//微分项输出
pid- >Dout = pid- >Kd * pid- >Dbuf[0];
//对积分项进行限幅
LimitMax(pid- >Iout, pid- >max_iout);
//叠加三个输出到总输出
pid- >out = pid- >Pout + pid- >Iout + pid- >Dout;
//对总输出进行限幅
LimitMax(pid- >out, pid- >max_out);
}
else if (pid- >mode == PID_DELTA)
{
//增量式PID
//以本次误差与上次误差的差值作为比例项的输入带入计算
pid- >Pout = pid- >Kp * (pid- >error[0] - pid- >error[1]);
//以本次误差作为积分项带入计算
pid- >Iout = pid- >Ki * pid- >error[0];
//迭代微分项的数组
pid- >Dbuf[2] = pid- >Dbuf[1];
pid- >Dbuf[1] = pid- >Dbuf[0];
//以本次误差与上次误差的差值减去上次误差与上上次误差的差值作为微分项的输入带入计算
pid- >Dbuf[0] = (pid- >error[0] - 2.0f * pid- >error[1] + pid- >error[2]);
pid- >Dout = pid- >Kd * pid- >Dbuf[0];
//叠加三个项的输出作为总输出
pid- >out += pid- >Pout + pid- >Iout + pid- >Dout;
//对总输出做一个先限幅
LimitMax(pid- >out, pid- >max_out);
}
return pid- >out;
}
#define LimitMax(input, max)
{
if (input > max)
{
input = max;
}
else if (input < -max)
{
input = -max;
}
}
PID数据清空代码
有时候需要清除中间变量,例如目标值和中间变量清零。
void PID_clear(pid_type_def *pid)
{
if (pid == NULL)
{
return;
}
//当前误差清零
pid- >error[0] = pid- >error[1] = pid- >error[2] = 0.0f;
//微分项清零
pid- >Dbuf[0] = pid- >Dbuf[1] = pid- >Dbuf[2] = 0.0f;
//输出清零
pid- >out = pid- >Pout = pid- >Iout = pid- >Dout = 0.0f;
//目标值和当前值清零
pid- >fdb = pid- >set = 0.0f;
}
全部0条评论
快来发表一下你的评论吧 !