PID学习

直立环PD,速度环PI,转向环PD

PID的实例

e943be3ba6e54841a4917c9ee180214d.png

对小球进行速度控制

pid目标值:需要小球达到的速度

pid反馈值:小球的实时速度

pid输出值:施加在小球上的力

对电机的转速进行控制

pid目标值:需要电机达到的转速

pid反馈值:电机的实时转速

pid输出值:电机中流过电流的大小

fbcab02a65754d048f3ed05447d9a107.png

毕竟微积分都是连续的,而我们采样得到的是离散的数据点。其实也很简单,离散状态下的积分计算其实就是把过去采样得到的所有误差加在一起,而微分计算就是把这一轮计算得到的误差与上一轮的误差相减。

最后,我们一般还会对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运算结果放在结构体参数Out,Error里
void PID_Cala(PIDM *pid , float Reference, flaot Feedback)
{
    pid->LastError = pid->Error;//在计算之前先把上一次的误差存储起来
    pid->Error = Reference - Feedback;//计算新的error目标值-测量值
    //计算积分P
    float Pout=pid->Error*pid->kp;
    //D
    float Dout = pid->Error*pid->kd;
    //I,这个i的积分是累加的
    pid->integral += pid->Error*pid->ki;
    //对输出进行限幅
    if(pid->Output > pid->MaxOutput) pid->Output = pid->MaxOutput;
    else if(pid->Output < pid->MaxOutput) pid->Output = pid->MaxOutput;
}

PID mypid = {0};//创建结构体变量,这里创建了


int main()
{


    PID_Init(&mypid, 10 ,1 ,5 ,800 ,1000);
    while(1)
    {
        float FeedbackValue = 
        float TargetValue = 
    PID_Calc(&mypid, TargetValue, FeedbackValue);
    执行器输出大小(mypid.Output);
    Delay_ms(10)
    }
}
    

 



     

串级PID

更进一步——串级PID

从单级到串级

fe6dae60ea4b490e9c15d4aedecb2469.png

当我们在进行小球的位置控制时,我们可能经常会发现一个问题,如果小球与目标之间的距离较远的话,小球在运动过程中的速度会很快,会导致较大的超调,而且不论怎么修改参数都很难让系统的表现更好一些。

如果运动过程中的速度没这么快就好了,这样就不会冲过头了。没错,这就要用到串级PID了。

我们上面所说的算法其实就是单级PID,目标值和反馈值经过一次PID计算就得到输出值并直接作为控制量,但如果目标物理量和输出物理量之间不止差了一阶的话,中间阶次的物理量我们是无法控制的。比如:目标物理量是位置,输出物理量是加速度,则小球的速度是无法控制的。

而串级PID就可以改善这一点。串级PID其实就是两个单级PID“串”在一起组成的,它的信号框图如下:

d02efad272b743e9a03dca1816fa35d7.png

串级PID为一个整体,有三个输入一个输出而此时被控对象也需要提供两个反馈量,那么它们都应该对应些什么物理量呢?

09ec83234336449daa5e38f554e442c1.png

目标值:小球目标位置

外环反馈:小球实时位置

内环反馈:小球实时速度

输出值:施加在小球上的控制力//直接反馈的是小球实时速度

内环与小球构成了一个恒速系统,PID内环负责小球的速度控制;而如果把内环和小球看作一个整体被控对象,外环又与这个对象一起构成了一个位置控制系统,外环负责位置控制;总体来说,外环负责根据小球位置误差计算出小球需要达到的速度,而内环负责计算出控制力使小球达到这个目标速度,两个环协同工作,就可以完成任务了。对应的位置有对应的速度

目标值:需要电机达到的角度

外环反馈值:电机的实时角度

内环反馈值:电机的实时速度

输出值:电机电流大小

分析:外环负责电机角度控制,根据电机目标角度和反馈角度计算出目标转速;内环负责转速控制,根据速度反馈和目标转速计算出电流

其实就是对外环PID的输出进行限幅,因为外环PID输出的是目标速度,限制外环输出就相当于限制了小球目标速度的最大值,内环也就会维持小球的速度不超过这个最大值了。

可以看到,使用串级PID后小球不再像之前那样“着急”地奔着目标而去,而是以近似匀速运动到达目标点。由于位置误差很大,外环输出在大部分时间内都处于限幅的最大值,因此小球在运动中接近匀速,这个速度就是所设定的外环输出限幅。而且由于运动速度变慢了,超调也几乎消失了。这就是我们想要的“控制位置的同时还能控制速度”的效果。

//串级PID的结构体,包含两个单级PID
typedef struct
{
    PID Inner; //内环
    PID Outer; //外环
    float Output; //串级输出,等于inner.output
}CascadePID;


//串级PID的计算函数
void PID_CascadeCalc(CascadeCalc *pid, float OuterRef, float OuterFdb, float InnerFdb)
{
    PID_Calc(&pid->Outer, OuterRef, OuterFdb);//计算外环
    PID_Calc(&pid->Inner, pid->Outer.Output, InnerFdb);//计算内环
    pid-Output = pid->Inner.Output;//内环输出就是串级PID的输出
}


CascadePID myoid = {0};

int main()
{

    PID_Init(&mypid.Inner, 10, 0, 0, 0, 1000);初始化内环
    PID_Init(&mypid.Outer, 5, 0, 5, 0, 100);初始化外环
    while(1)
    {
    float OuterTarget = //内环目标值
    float OutFeedback = //外环反馈值
    float InnerFeedback = //内环反馈值
    PID_CascadeCalc(&mypid, OutTarget, OutFeedback, InnerFeedback);
    执行机构输出大小(mypid.Output);
    Delay_ms(10);
    }
}
    

    

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值