【PID学习】PID算法改进
目录
一、对积分部分的改进(Ki)
1.积分限幅:
2.积分分离:
3.变速积分:
二、对微分部分的改进(Kd)
1.微分先行:
2.不完全微分:
三、对输入输出部分的改进(Out)
1.输出偏移:
2.输入死区:
一、对积分部分的改进(Ki)
1.积分限幅:
限制积分的幅度,防止积分深度饱和
- 要解决的问题:如果执行器因为卡住、断电、损坏等原因误差长时间得不到消除,则误差积分会随着时间的推移无限制加大,进而达到深度饱和状态,此时PID控制器会持续输出最大的调控力,即使后续执行器恢复正常,PID控制器在短时间内也会维持最大的调控力,直到误差积分从深度饱和状态退出
- 积分限幅实现思路:对误差积分或积分项输出进行判断,如果幅值超过指定阈值,则进行限制
积分限幅思路1 :单独对误差积分进行限幅
/*获取实际值*/Actual = 读取传感器();/*获取上次和本次误差*/Last_Error = Current_Error;Current_Error = Target - Actual;/*误差积分(累加)*/Sum_Error += Current_Error;/*积分限幅*/if (Sum_Error > 误差积分上限) {Sum_Error = 误差积分上限;}if (Sum_Error < 误差积分下限) {Sum_Error = 误差积分下限;}/*PID计算*/Out = Kp * Current_Error + Ki * Sum_Error + Kd * (Current_Error - Last_Error);/*输出限幅*/if (Out > 输出上限) {Out = 输出上限;}if (Out < 输出下限) {Out = 输出下限;}/*执行控制*/输出至被控制对象函数(Out);
积分限幅思路2:对整个积分项输出限幅
/*获取实际值*/Actual = 读取传感器();/*获取上次和本次误差*/Last_Error = Current_Error;Current_Error = Target - Actual;// /*误差积分(累加)*/
// Sum_Error += Current_Error;
// /*积分限幅*/
// if (Ki * Sum_Error > 误差积分上限) {Ki * Sum_Error = 误差积分上限;}
// if (Ki * Sum_Error < 误差积分下限) {Ki * Sum_Error = 误差积分下限;}
// /*PID计算*/
// Out = Kp * Current_Error + Ki * Sum_Error + Kd * (Current_Error - Last_Error);/**************上下两部分等价******************//*积分项输出*/IntOut += Ki * Current_Error;/*积分项输出限幅*/if (IntOut > 积分项输出上限) {IntOut = 积分项输出上限;}if (IntOut < 积分项输出下限) {IntOut = 积分项输出下限;}/*PID计算*/Out = Kp * Current_Error + IntOut + Kd * (Current_Error - Last_Error);/*输出限幅*/if (Out > 输出上限) {Out = 输出上限;}if (Out < 输出下限) {Out = 输出下限;}/*执行控制*/输出至被控制对象函数(Out);
2.积分分离:
误差小于一个限度才开始积分,反之则去掉积分部分
- 要解决的问题:积分项作用一般位于调控后期,用来消除持续的误差,积分项调控过程具有滞后性,调控前期一般误差较大且不需要积分项作用,如果此时仍然进行积分,则调控进行到后期时,积分项可能已经累积了过大的调控力,这会导致超调
- 积分分离实现思路:对误差大小进行判断,如果误差绝对值小于指定阈值,则加入积分项作用,反之,则直接将误差积分清零或不加入积分项作用
积分分离思路1:判断误差,如果误差过大直接将积分清0
/*获取实际值*/Actual = 读取传感器();/*获取上次和本次误差*/Last_Error = Current_Error;Current_Error = Target - Actual;/*误差积分(累加) + 积分分离*/if (fabs(Sum_Error) < 积分分离阈值){Sum_Error += Current_Error;}else{Sum_Error = 0;} /*PID计算*/Out = Kp * Current_Error + Ki * Sum_Error + Kd * (Current_Error - Last_Error);/*输出限幅*/if (Out > 输出上限) {Out = 输出上限;}if (Out < 输出下限) {Out = 输出下限;}/*执行控制*/输出至被控制对象函数(Out);
积分分离思路2:判断误差,使用标志位决定是否加入积分作用
/*获取实际值*/Actual = 读取传感器();/*获取上次和本次误差*/Last_Error = Current_Error;Current_Error = Target - Actual;/*误差积分(累加)*/Sum_Error += Current_Error;/*积分分离*/ if (fabs(Sum_Error) < 积分分离阈值){C = 1;}else{C = 0;} /*PID计算*/Out = Kp * Current_Error + C * Ki * Sum_Error + Kd * (Current_Error - Last_Error);/*输出限幅*/if (Out > 输出上限) {Out = 输出上限;}if (Out < 输出下限) {Out = 输出下限;}/*执行控制*/输出至被控制对象函数(Out);
3.变速积分:
根据误差的大小调整积分的速度
- 要解决的问题:如果对上面的积分分离阈值没有设定好,设定值小于真实阈值,被控对象正好在阈值之外停下来,则此时控制器完全没有积分作用,误差不能消除
- 变速积分实现思路:变速积分是积分分离的升级版,变速积分需要设计一个函数值随误差绝对值增大而减小的函数,函数值作为调整系数,用于调整误差积分的速度或积分项作用的强度。即误差越大积分越弱,误差越小积分越强(和积分分离思路一致,当误差过大时不积分防止超调,变速积分不再是简单的一刀切判断误差,而是采用渐进的方法控制积分值)
变速积分思路1:调整误差积分速度
/*获取实际值*/Actual = 读取传感器();/*获取上次和本次误差*/Last_Error = Current_Error;Current_Error = Target - Actual;/*变速积分*/C = 1 / (k * fabs(Current_Error) + 1) /*误差积分(累加)*/Sum_Error += C * Current_Error;/*PID计算*/Out = Kp * Current_Error + Ki * Sum_Error + Kd * (Current_Error - Last_Error);/*输出限幅*/if (Out > 输出上限) {Out = 输出上限;}if (Out < 输出下限) {Out = 输出下限;}/*执行控制*/输出至被控制对象函数(Out);
变速积分思路2:调整积分项作用强度(实质和思路1一样,只不过C出现的位置变了)
/*获取实际值*/Actual = 读取传感器();/*获取上次和本次误差*/Last_Error = Current_Error;Current_Error = Target - Actual;/*变速积分*/C = 1 / (k * fabs(Current_Error) + 1) /*误差积分(累加)*/Sum_Error += Current_Error;/*PID计算*/Out = Kp * Current_Error + C * Ki * Sum_Error + Kd * (Current_Error - Last_Error);/*输出限幅*/if (Out > 输出上限) {Out = 输出上限;}if (Out < 输出下限) {Out = 输出下限;}/*执行控制*/输出至被控制对象函数(Out);
二、对微分部分的改进(Kd)
1.微分先行:
将对误差的微分替换为对实际值的微分
- 要解决的问题:普通PID的微分项对误差进行微分,当目标值大幅度跳变时,误差也会瞬间大幅度跳变,这会导致微分项突然输出一个很大的调控力,相当于帮助P控制器进行正向调控,如果系统的目标值频繁大幅度切换,则此时的微分项不利于系统稳定
- 微分先行实现思路:将对误差的微分替换成对实际值的微分
普通PID的微分项输出:
dout(k) = Kd * ( error(k) - error(k-1) )
微分先行PID的微分项输出:
dout(k) = - Kd * ( actual(k) - actual(k-1) )
/*获取上次实际值和本次实际值*/Last_Actual = Current_Actual;Current_Actual = 读取传感器();/*获取上次和本次误差*/Last_Error = Current_Error;Current_Error = Target - Actual;/*误差积分(累加)*/Sum_Error += Current_Error;/*微分先行*/DifOut = - Kd * ( Current_Actual - Last_Actual);/*PID计算*/Out = Kp * Current_Error + C * Ki * Sum_Error + DifOut;/*输出限幅*/if (Out > 输出上限) {Out = 输出上限;}if (Out < 输出下限) {Out = 输出下限;}/*执行控制*/输出至被控制对象函数(Out);
2.不完全微分:
给微分项加入一阶惯性单元(低通滤波器)
- 要解决的问题:传感器获取的实际值经常会受到噪声干扰,而PID控制器中的微分项对噪声最为敏感,这些噪声干扰可能会导致微分项输出抖动,进而影响系统性能
- 不完全微分实现思路:给微分项加入一阶惯性单元(低通滤波器)
注意:滤波需要时间,会导致信号产生时延
普通PID的微分项输出:
dout(k) = Kd * ( error(k) - error(k-1) )
不完全微分PID的微分项输出:
α取值0~1,用于调控最终PID微分输出参数本次和上次的比值,相当于两个值做加权平均
dout(k) = ( 1 - α ) * Kd * ( error(k) - error(k-1) ) + α * dout( k - 1 )
/*获取本次实际值*/Current_Actual = 读取传感器();/*获取上次和本次误差*/Last_Error = Current_Error;Current_Error = Target - Actual;/*误差积分(累加)*/Sum_Error += Current_Error;/*不完全微分*/DifOut = (1 - a) * Kd * (Current_Error - Last_Error) + a * DifOut/*PID计算*/Out = Kp * Current_Error + C * Ki * Sum_Error + DifOut;/*输出限幅*/if (Out > 输出上限) {Out = 输出上限;}if (Out < 输出下限) {Out = 输出下限;}/*执行控制*/输出至被控制对象函数(Out);
三、对输入输出部分的改进(Out)
1.输出偏移:
在非0输出时,给输出值加一个固定偏移
- 要解决的问题:对于一些启动需要一定力度的执行器,若输出值较小,执行器可能完全无动作,这可能会引起调控误差,同时会降低系统响应速度
- 输出偏移实现思路:若输出值为0,则正常输出0;若输出值非0,则给输出值加一个固定偏移,跳过执行器无动作的阶段
注意:输出偏移由于存在正反两面的调控,会导致系统存在振荡
if( out(k) == 0 ) out(k) = 0
if( out(k) > 0 ) out(k) += offset
if( out(k) < 0 ) out(k) -= offset
/*获取实际值*/Actual = 读取传感器();/*获取上次和本次误差*/Last_Error = Current_Error;Current_Error = Target - Actual;/*误差积分(累加)*/Sum_Error += Current_Error;/*PID计算*/Out = Kp * Current_Error + Ki * Sum_Error + Kd * (Current_Error - Last_Error);/*输出偏移*/if (Out > 0){Out += 偏移值;}else if (Out < 0){Out -= 偏移值;}else{Out = 0;}/*输出限幅*/if (Out > 输出上限) {Out = 输出上限;}if (Out < 输出下限) {Out = 输出下限;}/*执行控制*/输出至被控制对象函数(Out);
2.输入死区:
误差小于一个限度时不进行调控
- 要解决的问题:在某些系统中,输入的目标值或实际值有微小的噪声波动,或者系统有一定的滞后,这些情况可能会导致执行器在误差很小时频繁调控,不能最终稳定下来
- 输入死区实现思路:若误差绝对值小于一个限度,则固定输出0,不再进行调控
if( fabs( out(k) ) < A ) out(k) = 0
if( fabs( out(k) ) >= A) out(k) = out(k)
输入死区 + 输出偏移代码思路
/*获取实际值*/Actual = 读取传感器();/*获取上次和本次误差*/Last_Error = Current_Error;Current_Error = Target - Actual;/*输入死区*/if ( fabs(Current_Error) < 死区阈值 ){Out = 0;}else{/*误差积分(累加)*/Sum_Error += Current_Error;/*PID计算*/Out = Kp * Current_Error + Ki * Sum_Error + Kd * (Current_Error - Last_Error); /*输出偏移*/if (Out > 0){Out += 偏移值;}else if (Out < 0){Out -= 偏移值;}else{Out = 0;} }/*输出限幅*/if (Out > 输出上限) {Out = 输出上限;}if (Out < 输出下限) {Out = 输出下限;}/*执行控制*/输出至被控制对象函数(Out);