四旋翼飞行器的PID反馈控制——从水桶模型讲起,由浅入深

PID反馈控制

U ( t ) = K p ( e r r ( t ) + 1 T I ∫ e r r ( t ) d t + T D d e r r ( t ) d t ) U(t)=K_p(err(t)+\frac1T_I\int err(t)dt+\frac{T_Dderr(t)}{dt}) U(t)=Kp(err(t)+T1Ierr(t)dt+dtTDderr(t))

PID(proportion integration differentiation)是一种数学思想,P for 比例,I for 积分,D for 微分。常用于工程控制类领域中,调控某被控制量,这个被控制量可以是:温度,水位,速度等等。这些被控制量都有一个特点,当前状态到达目标状态需要一个过程,无法“一蹴而就”,这个过程可快可慢,可能会超过预设的目标值(超调),还可能一直在目标值附近徘徊(震荡)。而为了使这个过程尽可能符合人的预期,引入PID控制算法。

换言之,PID是给机器看的,机器是不会思考的,但PID可以帮助其“思考”,让它的行为更加“合理”。

水池模型

引入 K p K_p Kp

为了更好地理解PID的概念,我将从一个经典的“水池模型”讲起。

假设现在有一个水池,池子里现在没有水,现在向池子里注水,希望达到1m的高度。注意:这里的注水速率(m/s)不可突变,也就是说,不能前一秒还是10m/s的速率注水,下一秒速率马上就减为0了,这显然是不符合实际的。

那么此时有一个很自然的想法,我根据误差(err=1m-当前高度)去设定我当下的速率,当err=0(到达1m时),速率自然降为0。
v ( t ) = K p ∗ e r r ( t ) v(t)=K_p*err(t) v(t)=Kperr(t)
t t t t t t时刻,通过上面的公式,令err简单乘以一个比例系数( k p k_p kp,常数),得到当前速率 v ( t ) v(t) v(t)

具象一点,令Kp=0.5,采样周期为1s,那么t=0时, v ( 0 ) = 0.5 ∗ ( 1 − 0 ) = 0.5 v(0)=0.5*(1-0)=0.5 v(0)=0.5(10)=0.5,也就是说,此时的进水速率是0.5m/s。那么第1s时(一次采样周期后),水位来到0.5m,此时 v ( 1 ) = 0.5 ∗ ( 1 − 0.5 ) = 0.25 v(1)=0.5*(1-0.5)=0.25 v(1)=0.5(10.5)=0.25,那么第2s时水位来到0.75m。。。于是经过若干次采样周期,水位必然会来到1m。

那么是否到这里问题就解决了呢?看起来我们仅仅需要 K p K_p Kp就可以将系统控制得很好。

然而在现实中,常常会出现稳态误差的问题。

还是刚才这个例子,假设这个水池存在“漏水”的问题:每次加水的过程中,池子都要漏掉0.1m高度的水。那么这样的设定会产生什么问题呢?假设经过了若干次加水,现在水位来到了0.8m,那么很容易算出下一次的进水速率时0.1m/s,而这个速率恰好=漏水速率。也就是说,下1s池子的水位将不会变化,因为进去的水不多不少都被漏出去了。

系统将一直维持在0.8m的水位,永远无法到达预设的1m水位。而这,便是所谓的稳态误差

稳态误差实则是一个系统误差,是可预见的,是由于模型的缺陷导致,因为像例子中“漏水”的情况在现实中是几乎无可避免的。对于稳态误差的出现,笔者是这样理解的:
模型由 e r r 给与系统一个增长的“动力”,这个“动力”随着 e r r 的变化而变化,最终必然与那个“下降”的趋势(系统原先就有的)相等, 达成一个巧妙的平衡,但此时的平衡是 e r r 仍然存在的前提下的,因此这样的“稳定”是我们不想看到的。 模型由err给与系统一个增长的“动力”,这个“动力”随着err的变化而变化,最终必然与那个“下降”的趋势(系统原先就有的)相等,\\ 达成一个巧妙的平衡,但此时的平衡是err仍然存在的前提下的,因此这样的“稳定”是我们不想看到的。 模型由err给与系统一个增长的动力,这个动力随着err的变化而变化,最终必然与那个下降的趋势(系统原先就有的)相等,达成一个巧妙的平衡,但此时的平衡是err仍然存在的前提下的,因此这样的稳定是我们不想看到的。

引入 K i K_i Ki

为解决稳态误差的问题,我们需要去打破那个“平衡”。而当平衡产生时,err是静止的,因此随之带来的系统增长趋势也是静止的,如果没有外界的作用,这样的平衡会一直持续下去。

那么如何才能让机器“知道”这样的平衡已经维持太久了,有点不太对劲了呢?

让err和时间做一个积分,产生一个“补偿”,加在原有的增益上。于是便引入了 K i K_i Ki

放在刚才的水池模型中,
v ( t ) = K p ∗ e r r ( t ) + K i ∗ ∫ 0 t e r r ( i ) ∗ d i v(t)=K_p*err(t)+K_i*\int_0^t err(i)*di v(t)=Kperr(t)+Ki0terr(i)di

也就是说,构成v(t)的不光是刚才的比例项,还有一个关于err积分的积分项,那么当达到0.8m的稳态误差时,由于err始终无法消除,积分项会随时间不断累加,v增大,打破平衡,水位会继续上升,直到到达我们的预设值1m。

引入 K d K_d Kd

问题似乎已经完美地解决了,那么为什么还要引入 K d K_d Kd呢?

想下这样一个问题:当水位到达1m的预设值时,水位会马上停止上升吗?

显然不会,因为虽然此时err=0,比例项为0,但由于积分项是过去多次err的累加,因此,此时的积分项并不为0,根据公式,此时的v不会为0,水位还会继续上升。而像这样超过设定值的情况,工程上的学名叫**“超调”**。

不过不必担心,超调之后,err会变为负数,v(t)的比例项会变为负数,而积分项随着时间的累积也必然会变为负数。也就是说,一段时间后,v(t)会变为一个负数。表现在水池模型中,水位会下降。

但是当水位下降到1m时,系统同样会面临一个相同的问题——也就是“刹不住车”,水位会继续下降,低于目标值。这样的现象在工程中叫欠调

然后水位又重新开始上升。。。反复几次最终趋于稳定,达到目标值。被控制量在超调和欠调之间反复切换的过程叫做震荡

对于一个好的系统,我们肯定是不希望它震荡的,我们希望被控制量能尽快地到达设定值,且是平稳地到达,“软着陆”。

就如同汽车来到一个红灯前一样,快到停止线时,肯定会给一脚刹车,然后稳稳地停在线上。

那么如何给系统一个“刹车”呢?有一个很自然的想法:当被控制量快到设定值时,假如err变化过快,我就减缓系统的增益。那么如何衡量err的变化快慢呢?这里显然是一个微分的思想:
d ( e r r ) d t \frac{d(err)}{dt} dtd(err)
由于水位上升过程中,err是逐渐减小的,因此上面这个微分是一个负数,我们令其乘上一个比例系数 K d K_d Kd,加在v上,自然便会有一个“刹车”的效果。同时,err变化得越快,这个“刹车”效果就越明显,而这显然是我们想要的。

至此,v(t)的表达式便升级为:
v ( t ) = K p ∗ e r r ( t ) + K i ∗ ∫ 0 t e r r ( i ) ∗ d i + K d ∗ d ( e r r ) d t v(t)=K_p*err(t)+K_i*\int_0^t err(i)*di+K_d*\frac{d(err)}{dt} v(t)=Kperr(t)+Ki0terr(i)di+Kddtd(err)
那么现在再回头看开头的公式:
U ( t ) = K p ( e r r ( t ) + 1 T I ∫ e r r ( t ) d t + T D d e r r ( t ) d t ) U(t)=K_p(err(t)+\frac1T_I\int err(t)dt+\frac{T_Dderr(t)}{dt}) U(t)=Kp(err(t)+T1Ierr(t)dt+dtTDderr(t))
其实这两个公式就是完全等价的,前者是3个自由系数 K p , K i , K d K_p,K_i,K_d Kp,Ki,Kd,后者是 K p , T I , T D K_p,T_I,T_D Kp,TI,TD,本质上是一回事,自由度是不变的。为了使用方便,我们常常用前面那个公式。

而在实际工程场景中,很少有真正“连续”的情况出现,往往都是“离散”的,传感器会有一个采样周期。因此公式可以改写为离散的形式:
u ( k ) = K p ∗ e r r ( k ) + K i ∗ ∑ n = 0 k e r r ( n ) ∗ d t + K d ∗ ( e r r ( k ) − e r r ( k − 1 ) ) d t u(k)=K_p*err(k)+K_i*\sum_{n=0}^k err(n)*dt+K_d*\frac{(err(k)-err(k-1))}{dt} u(k)=Kperr(k)+Kin=0kerr(n)dt+Kddt(err(k)err(k1))
由此我们便得到了一个PID反馈模型,可以使系统快速,精准,平稳地达到我们所期望的状态。

PID实现四轴飞行器的姿态修正

在四旋翼飞行器的飞控算法中,PID反馈控制运用十分广泛。飞行器在空中极易受到外界因素的干扰和影响,气流的扰动,空气密度的变化,甚至是电池电压的变化都会对飞行器的姿态造成影响。

如果没有飞控的介入,四旋翼飞行器是绝对无法飞行的。有些人觉得四旋翼飞行器有四个螺旋桨,非常稳定,四个螺旋桨一起转升力抵消重力飞机就升起来了。其实不然,如果没有飞控,就算在无比理想的情况下(无风,无气流)飞机也绝对无法起飞,必定会失控。因为首先,实际飞机的重心绝不在正中心;其次,电机之间的运行状况可能会有极小的差别,哪怕是一模一样的品牌和型号。多种因素的叠加导致螺旋桨的升力与重力不在一条直线上,这将导致飞行器受到一个被翻转的扭矩,而这样的翻转无疑是发散的,也就是说就越翻越大,最终翻机。后果无疑是灾难性的!

因此,我们希望,当飞行器偏离预设的姿态时,系统能自动产生一个纠正的“回力”,保持预设的姿态。那么PID算法用在这里就十分合适。

四轴的PID又分为单环PID和双环PID。

单环PID:

这里先介绍单环的PID,经过上面的介绍,很容易想到这里的err就是当前姿态与期望姿态的误差。那么具体分解,姿态分为三个欧拉角pitch,roll,yaw。为方便理解,我们先只介绍一个角,pitch角,另外两个角的处理是十分类似的,最终做一个简单的叠加就可以实现整机的控制。

在这里插入图片描述

单环PID原理并不复杂,只需搞清楚每一个成分,“对号入座”即可。

对于pitch这个维度来说, e r r = p i t c h e x p − p i t c h c u r err=pitch_{exp}-pitch_{cur} err=pitchexppitchcur。而 e r r err err的微分,也就是角度关于时间的导数,即角速度,我们可以直接通过陀螺仪读取。

于是我们可以很容易实现这部分代码:

//pitch
	Out_p.exp=pitch_exp;
	Out_p.cur=Angle.pitch;
	Out_p.err_sum+=(Out_p.exp-Out_p.cur);
	Pitch_Out=-1*(Out_p.Kp*(Out_p.exp-Out_p.cur) + Out_p.Ki*Out_p.err_sum*dt + Out_p.Kd*Gyro.y*RtA);

p i t c h _ e x p pitch\_exp pitch_exp是通过遥控器输入的,它反映了操控者对飞机姿态的期望; A n g l e . p i t c h Angle.pitch Angle.pitch是由imu姿态解算得到。dt为迭代周期,也就是这部分代码的执行周期。

注意:最后一行代码的最后一项, O u t _ p . K d ∗ G y r o . y ∗ R t A Out\_p.Kd*Gyro.y*RtA Out_p.KdGyro.yRtA,多乘了一个 R t A RtA RtA(弧度制到角度制的倍数),是为了和前面的项相统一,角度均为角度制,这样可以令PID的三个参数在一个数量级,方便后续调参。

那么这个PID迭代公式的输出是什么呢?因为他是直接作用于电机(电调)的,因此不难想到他是PWM的一个补偿,而这个补偿是带符号的,因为姿态的偏差自然是有正有负的。不同位置的电机,使用这部分补偿的符号是不同的(详见后文双环PID)。

双环PID:

单环PID只做引入,实际上,要想飞行器能够真正地平稳飞行,并拥有良好的抗干扰性和鲁棒性,必须使用双环PID(可以参考文末的实拍视频进行对比)。

在这里插入图片描述

双环PID分为角度环(外环)和角速度环(内环)。角度又有三个维度:pitch、roll、yaw,为了方便解释和理解,我们先只考虑pitch一个维度。

首先由遥控器输入一个期望的姿态角度,如果不做操作,那默认就是期望pitch和roll和yaw都为0,即姿态水平。外环借由姿态解算imu得到当前实际的欧拉角。实际角度和期望角度共同作为外环的输入,经过PID处理,产生一个输出,也就是期望的角速度。

类似的,这个期望的角速度和实际的角速度(陀螺仪直接采集得)又共同作为内环的输入。内环经过PID处理生成一个输出,这个输出是直接作用于电调,作用电机。

//pitch外环
	Out_p.exp=pitch_exp;
	Out_p.cur=Angle.pitch;//
	Out_p.err_sum+=(Out_p.exp-Out_p.cur);
	w_pitch_out=-1*(Out_p.Kp*(Out_p.exp-Out_p.cur) + Out_p.Ki*Out_p.err_sum*dt + Out_p.Kd*Gyro.y*RtA);//方向适配
//pitch内环
	In_p.exp=w_pitch_out;//输入w期望值
	In_p.cur=Gyro.y*RtA;//从陀螺仪获取当前值
	In_p.err_sum+=(In_p.exp-In_p.cur);//累计误差err
	Pitch_Out=In_p.Kp*(In_p.exp-In_p.cur) + In_p.Ki*In_p.err_sum*dt + In_p.Kd*(In_p.exp-In_p.cur-In_p.LastErr);//pid表达式,给出输出
	In_p.LastErr=In_p.exp-In_p.cur;//更新上一次的误差

如代码所示,内环的代码必须执行在外环代码之后,因此这里涉及一个简单的同步问题,尤其是涉及操作系统时必须注意。Pitch_Out直接作用于电机,并根据不同的螺旋桨位置,前面带一个正号或负号。

代码写好后,PID的编写远没有真正完成,外环、内环的Kp、Ki、Kd还是未知数,也就是说还有6个未知数(仅对于pitch),剩下的工作就是上飞机调试。调试成功的一个重要指标是:内环的实际角速度对于期望角速度有一个良好的跟随性。

由于需要不断尝试参数,因此不可能每次改变参数都烧录一次代码,这就需要远程改变参数,这里我们使用了蓝牙模块,可以实现不下飞机修改参数。同时,为了监测角速度的跟随性,我们还使用了一个可视化的航模调试工具:匿名四轴上位机。

在这里插入图片描述

如下图所示,这是我们的飞机调试完成后,所达到的角速度跟随效果:

pitch(黄线是预期角速度,蓝线是实际角速度):

在这里插入图片描述

roll(蓝线是预期角速度,紫线是实际角速度):

在这里插入图片描述

可以看到,实际角速度对于期望的角速度有一个较好的跟随性,前者略微落后于后者是十分正常的。

Pitch和Roll可以分开独立调试,Yaw情况较为简单,宽容度高,给一个不太激进的参数,甚至肉眼去调试即可。

最后的三个输出加上一个油门的基础值(speed),作用于电机,不同位置的螺旋桨,3个维度PID输出值带的符号是不同的,详见如下代码:

//电机代码
	Power1=speed+1000-1+Pitch_Out-Roll_Out+Yaw_Out;//螺旋桨1
	Power2=speed+1000-1+Pitch_Out+Roll_Out-Yaw_Out;//螺旋桨2
	Power3=speed+1000-1-Pitch_Out-Roll_Out-Yaw_Out;//螺旋桨3
	Power4=speed+1000-1-Pitch_Out+Roll_Out+Yaw_Out;//螺旋桨4
	
	PWM_SetCCR_1(Power1);//作用占空比,驱动电机
	PWM_SetCCR_2(Power2);
	PWM_SetCCR_3(Power3);
	PWM_SetCCR_4(Power4);

到这里,就可以尝试飞行了。

祝你好运!

实拍视频:

双环PID:
https://www.bilibili.com/video/BV1Qb4y137Jw/?spm_id_from=333.999.0.0&vd_source=b943c4e86faa94cd849c14cc66f912f3

单环PID:
https://www.bilibili.com/video/BV1CN4y1s74W/?spm_id_from=333.999.0.0&vd_source=b943c4e86faa94cd849c14cc66f912f3

飞行测试:
https://www.bilibili.com/video/BV1tG411671j/?spm_id_from=333.999.0.0

  • 22
    点赞
  • 37
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值