1.PID
-
问题 现在我们有下述任务:
- 目标:在小球上施加一个水平方向的力(称为控制力),使小球在偏离目标位置时回到目标位置
- 已知条件:小球的实时坐标、目标位置坐标
计算PID的第一步就是计算误差(Error):误差=目标值-反馈值。在这个例子中,目标值是目标位置坐标,反馈值是小球实时位置坐标,那么误差就是小球当前位置与目标间的距离。 分为三个步骤使用误差分别算出一个分力,并将三个分力一起施加在小球上:
1. P(比例环节 Proportion)
这个环节产生的分力是:
- 分力大小与误差成正比,且当小球在目标左边的时候分力向右,当小球在目标右边的时候分力向左,其中Kp是比例系数。
2. D(微分环节differential)
那么如何让小球能够静止在目标点呢?这就要请出PID的另一个环节:微分环节D(Differential) 。
- 微分环节也会计算出一个分力,计算方法是:
也就是说,这个分力与误差的变化速度有关。假设目标位置不变,小球向右运动时误差减小,即误差变化速度为负,分力向左;反之当小球向左运动时分力向右;综合看来,微分环节产生的分力始终阻碍小球的运动。微分系数Kp可以影响这个“阻力”的大小,因此如果我们把系数调大一些,就可以让小球的运动收敛得更快一些。
- 微分在数学上表示某个量的变化率,在控制理论中,微分作用可以被视为对误差变化的“敏感反应”,即快速反应误差的变化,从而进行适当的控制调整
3. I(积分环节integral)
但现在,我们更希望在小球有一些外部干扰时也能实现上面的效果,比如我们在小球上加上一个水平向右的恒力,此时会发生什么呢?
-
小球在运动过程中仍然会像之前一样接近目标点,但在最终停下来时我们会发现,小球无法精确停在目标点上,而是像上图一样停在离目标点有一定距离的地方。此时控制力与干扰恒力平衡,小球静止。距离目标点有一定距离,小球静止,微分环节产生的分力为零,控制力完全由比例环节产生,且若距离更小则比例环节的输出更小,更无法平衡干扰力,因此小球无法继续向目标点接近。
-
此时就需要我们的第三个环节出场了:积分环节I(Integral) ,它的计算方法是:
也就是说积分环节产生的分力正比于误差的积分,当误差持续存在时,这个分力会逐渐变大,试图消除误差。(也即消除稳态误差)
- 物理意义:积分项相当于对误差进行“记忆”,它会随着时间的推移积累误差值,从而影响控制输出。在控制理论中:积分作用可以被视为对误差信号的“累积”。
总结
- PID计算过程
-
PID三个环节的作用
-
比例环节:起主要控制作用,使反馈量向目标值靠拢,但可能导致振荡,提高响应速度,减少静差。(静差:稳态误差)
-
积分环节:消除稳态误差,但会增加超调量,消除静差.只要有偏差就会积分,越积越大,直到偏差为零。
-
微分环节:产生阻尼效果,抑制振荡和超调,但会降低响应速度
-
-
公式
- Ki=Kp * (1/Ti) * T
- Kd=(1/T) * Td
PID代码
-
积分和微分应该怎么算呢?毕竟微积分都是连续的,而我们采样得到的是离散的数据点。其实也很简单,离散状态下的积分计算其实就是把过去采样得到的所有误差加在一起,而微分计算就是把这一轮计算得到的误差与上一轮的误差相减。
-
最后,我们一般还会对PID的积分和输出进行限幅(规定上下限),积分限幅可以减小积分引起的超调,输出限幅可以保护执行机构或被控对象。
//首先定义PID结构体用于存放一个PID的数据
typedef struct
{
float kp, ki, kd; //三个系数
float error, lastError; //误差、上次误差
float integral, maxIntegral; //积分、积分限幅
float output, maxOutput; //输出、输出限幅
}PID;
//用于初始化pid参数的函数
void PID_Init(PID *pid, float p, float i, float d, float maxI, float maxOut)
{
pid->kp = p;
pid->ki = i;
pid->kd = d;
pid->maxIntegral = maxI;
pid->maxOutput = maxOut;
}
//进行一次pid计算
//参数为(pid结构体,目标值,反馈值),计算结果放在pid结构体的output成员中
void PID_Calc(PID *pid, float reference, float feedback)
{
//更新数据(error也即误差值)
pid->lastError = pid->error; //将旧error存起来
pid->error = reference - feedback; //计算新error
//计算微分
float dout = (pid->error - pid->lastError) * pid->kd;
//计算比例
float pout = pid->error * pid->kp;
//计算积分
pid->integral += pid->error * pid->ki;
//积分限幅
if(pid->integral > pid->maxIntegral) pid->integral = pid->maxIntegral;
else if(pid->integral < -pid->maxIntegral) pid->integral = -pid->maxIntegral;
//计算输出
pid->output = pout + dout + pid->integral;
//输出限幅
if(pid->output > pid->maxOutput) pid->output = pid->maxOutput;
else if(pid->output < -pid->maxOutput) pid->output = -pid->maxOutput;
}
PID mypid = {0}; //创建一个PID结构体变量
int main()
{
//...这里有些其他初始化代码
PID_Init(&mypid, 10, 1, 5, 800, 1000); //初始化PID参数
while(1)//进入循环运行
{
float feedbackValue = ...; //这里获取到被控对象的反馈值
float targetValue = ...; //这里获取到目标值
PID_Calc(&mypid, targetValue, feedbackValue); //进行PID计算,结果在output成员变量中
设定执行器输出大小(mypid.output);
delay(10); //等待一定时间再开始下一次循环
}
}
2.直立环:
理论&公式
-
直立环:让小车角度趋于0;
-
由图可得,只使用PD(比例微分)便可以将小车直立。
3.速度环,串级PID
-
速度环:让小车速度趋于0的同时角度也为0(Pi控制)
-
直立环的角度,角速度反馈基于MPU6050
-
速度环的速度反馈基于ENCODER
-
之所以引入速度环概念:50:44 ,是因为直立环只保证了小车能够直立,但没有保证它静止不动。小车立起来之后,那就自然会有向左移动和向右移动的趋势,因为电机的移动速度不受控。如果加上速度环配合直立环一起工作,那么就可以让小车在直立起来的同时,还能够防止小车左右移动。
- 公式推导:
4.调参
1. 确立机械中值
从后往前轻推小车,直到小车倒下, 最终角度大概为0
2.直立环(负反馈)
-
Kp极性:如果小车,向右偏,小车向左走,则极性反了 -
Kp大小:调整kp大小直到出现大幅度低频振荡- Kp 的值应根据被控系统的动态特性进行调整。对于快速响应的系统,Kp 可以设置得较高;而对于响应较慢的系统,Kp 应该适当降低,以避免过度振荡。(出现大幅度低频振荡)
- 我的在-710左右
-
Kd极性:当拿起小车旋转时,小车轮子没有实现跟随,这个时候说明极性是反的!(也即左偏左转,右偏右转) -
kd大小:是在抖动的kp基础上,调试kd大小。依旧是从小到大实验,一直到小车出现高频的剧烈抖动,出现高频抖动时,快速关闭电源(kd过大就会出现 小幅度高频抖动) -
最终Kd和Kp*0.6(经验)
3.速度环(正反馈)
Ki=kP/200,因此只需要调节Kp即可Kp极性:当用手去转动一个车轮,另外一个车轮会同向转动- 调速度环极性时,注释掉直立环 速度环大小时,不用注释掉直立环
kp大小:增大知道小车保持平衡的同时,速度接近于零且回位效果好
4.转向环
-
Kp极性:负反馈 -
kp大小:增大kp直至走直线效果好,且无剧烈抖动,(比较小在个位数)