PID算法
- PID算法介绍
- 用途
- pid数学表达式及其含义
- P算法
- D算法
- I算法
- PID总结
- 数学公式转换代码设计
- 实际运用
- PID代码实现
PID算法介绍
PID控制器是一种广泛应用于工业控制系统的反馈控制器,它通过比例(Proportional)、积分(Integral)、微分(Derivative)三个部分的组合来调节控制量,以实现对系统输出的精确控制。
大白话将:是一种闭环算法,有效果反馈,输入受输出的影响。
拿电烙铁举例,有的可以通过温度自动调节,有的是恒温,不能调节。
开环控制再举例:
闭环控制再举例:
用途
为什么空调不是PID控制?因为空调假如是制冷,根据阈值控制,到达一定值可能就会送自然风,可问题就出现在这个送自然风上,它是采用了两种模式,而PID只针对一种,也即是,假如速度超过了阈值,就降低速度到预期值,假如速度还未超过预期值,就提高速度,都针对速度一种而言。而空调就相当不同,这点应该理解。
工业项目中的用途:
pid数学表达式及其含义
PID算法:就是“比例(proportional)、积分(integral)、微分(derivative)”
在使用过程中,不会完整的使用该表达式,会进行拆分,使用需要的。例如,可能只用Kp*ek+Kd(ek-ek-1)也可能只用别的。
Kp:比例增益,是调适参数;
Ki:积分增益,也是调适参数;
Kd:微分增益,也是调适参数;
P算法
Kp比例控制考虑当前误差,误差值和一个正值的常数Kp(表示比例)相乘。需要控制的量,比如的温度,有它现在的当前值假如是90,也有我们期望的目标值假如是100。
当两者差距不大时,就让加热器“轻轻地”加热一下。
要是因为某些原因,温度降低了很多,就让加热器“稍稍用力”加热一下。
要是当前温度比目标温度低得多,就让加热器“开足马力”加热,尽快让水温到达目标附近。
这就是P的作用,跟开关控制方法相比,是不是“温文尔雅”了很多。
实际写程序时,就让偏差(目标减去当前)与调节装置的“调节力度”,建立一个一次函数的关系,就可以实现最基本的“比例”控制了~
Kp越大,调节作用越激进,Kp调小会让调节作用更保守。
D算法
设想有一个弹簧:现在在平衡位置上,拉它一下,然后松手,这时它会震荡起来,因为阻力很小,它可能会震荡很长时间,才会重新停在平衡位置。
请想象一下:要是把上图所示的系统浸没在水里,同样拉它一下 :这种情况下,重新停在平衡位置的时间就短得多。
此时需要一个控制作用,让被控制的物理量的“变化速度”趋于0,即类似于“阻尼”的作用。
因为,当比较接近目标时,P的控制作用就比较小了,越接近目标,P的作用越温柔,有很多内在的或者外部的因素,使控制量发生小范围的摆动。
D的作用就是让物理量的速度趋于0,只要什么时候,这个量具有了速度,D就向相反的方向用力,尽力刹住这个变化。
两次误差只差有正有负的,但不管正负,都是减弱P的算法的,让其尽恢复平衡。
I算法
案例:可以玩一玩该网址链接的小项目:无人机PID模拟:
无人机PID模拟调参
PID总结
数学公式转换代码设计
实际运用
速度环一般需要加滤波进行过滤,一般采用一阶滤波,为什么加上滤波?因为防止有突发的情况数值突然增大,和之前的一个值对比误差特别大,这时候应该舍弃这个值吗?不应该,因为它也是数
值的一部分,假如第一次数据是1,第二次的数据是10,突变为10,变换就很明显,这时候可以用一阶滤波来进行处理:
float a=0.3;//权重
filt_value=a*value + (1-a) * last_value;
a:
权重比例
value:
现在最新的值
last_value:
上次的值
filt_value:
滤波后的真实值。
带入上面的数据,新的值10, 10 x 0.3 =3 , 1*0.7 =0.7 3+0.7=3.7 所以滤波后的值也就是3.7而已,比之前的数据滤掉了很多。
如果第一次是2,第二次还是2 ,带入公式,仍然是2。
PID代码实现
Pid.c
#include "Pid.h"
/**
* @brief PID参数初始化
* @note 无
* @retval 无
* @author:i want to舞动乾坤
*/
void PID_init()
{
PosionPID.target_val=3600;
PosionPID.output_val=0.0;
PosionPID.Error=0.0;
PosionPID.LastError=0.0;
PosionPID.integral=0.0;
PosionPID.Kp = 10;
PosionPID.Ki = 0.5;
PosionPID.Kd = 0.8;
//速度环初始化
PosionPID.VKp=+190;
PosionPID.VKi=0.95;
PosionPID.VspeedSum=0;
PosionPID.LastFilt_Vspeed=0;
}
/**
* @brief 位置PID算法实现
* @param pid:指向PID结构体的指针变量,measure:实际测量值
* @note 无
* @retval 通过PID计算后的输出
* @author:i want to舞动乾坤
*/
float PID_realize(PID *pid, float measure)
{
/*计算目标值与实际值的误差*/
pid->Error = pid->target_val - measure;
/*积分项*/
pid->integral += pid->Error;
/*PID算法实现*/
pid->output_val = pid->Kp * pid->Error +
pid->Ki * pid->integral +
pid->Kd *(pid->Error -pid->LastError);
/*误差传递*/
pid-> LastError = pid->Error;
/*返回当前实际值*/
return pid->output_val;
}
/**
* @brief 速度环PID算法实现
* @param pid:指向PID结构体的指针变量,Speed:实际测量的速度
* @note 无
* @retval 通过PID计算后的输出
* @author:i want to舞动乾坤
*/
//速度环:
int velocity_PID_value(PID *pid,int Speed)
{
float a=0.3; //滤波系数(反映滤波程度)
pid->FiltVspeed = a * Speed + (1-a)* (pid->LastFilt_Vspeed); //一阶速度滤波
pid->VspeedSum += pid->FiltVspeed; //速度的累加
I_xianfu(pid,3000); //累加限幅
pid->LastFilt_Vspeed = pid->FiltVspeed; //此次速度记录为“上次速度”
pid->VspeedOutPut_Val = pid->VKp * pid->FiltVspeed //计算输出值
+ pid->VKi * pid->VspeedSum ;
return pid->VspeedOutPut_Val; //返回输出值
}
/**
* @brief 对PID的I算法限幅实现
* @param pid:指向PID结构体的指针变量,max:最大限幅值
* @note 无
* @retval 无
*/
void I_xianfu(PID *pid ,int max)
{
if(pid->VspeedSum > max) pid->VspeedSum = max;
if(pid->VspeedSum < - max) pid->VspeedSum =-max;
}
Pid.h
#ifndef __PID_H__
#define __PID_H__
typedef struct PID
{
float target_val; //目标值
float Error; /*第 k 次偏差 */
float LastError; /* Error[-1],第 k-1 次偏差 */
float PrevError; /* Error[-2],第 k-2 次偏差 */
float Kp,Ki,Kd; //比例、积分、微分系数
float integral; //积分值
float output_val; //输出值
float FiltVspeed; //第k次的速度值
float LastFilt_Vspeed;//第k-1次的滤波后的速度
float VKp; //速度环的Kp
float VKi; //速度环的Ki
float VspeedSum; //速度值的累加
float VspeedOutPut_Val;//输出值
}PID;
struct PID PosionPID;//定义结构体
void PID_init();
float PID_realize(PID *pid, float measure);
int velocity_PID_value(PID *pid,int Speed);
void I_xianfu(PID *pid ,int max);
#endif
如果看不懂指针成员操作,可以看下面的简易版的
Pid.c
/******************************************************
本程序只供学习使用,未经作者许可,不得用于其它任何用途
******************************************************/
#include "pid.h"
extern float Kp,Ki,Kd; //直立环参数
float err; //此次误差
float last_err; //上次误差
float err_sum=0; //误差累加
float err_difference; //误差的差值
extern float VKp,VKi; //速度环参数
float filt_velocity; //滤波后的速度
float last_filt_velocity;//上一次的滤波后的速度
float velocity_sum=0; //速度的累加
//直立环:
int vertical_PID_value(float measure,float calcu)
{
err = measure - calcu; //误差
err_sum+=err; //误差的累加
err_difference = err - last_err; //误差的差值
last_err = err; //此次误差记录为“上次误差”
return Kp*err + Ki*err_sum + Kd*err_difference;
}
//速度环:
int velocity_PID_value(int velocity)
{
float a=0.3; //滤波系数(反映滤波程度)
filt_velocity = a*velocity + (1-a)*last_filt_velocity; //一阶速度滤波
velocity_sum += filt_velocity; //速度的累加
I_xianfu(3000); //累加限幅
last_filt_velocity = filt_velocity; //此次速度记录为“上次速度”
return VKp*filt_velocity + VKi*velocity_sum;
}
//I限幅:
void I_xianfu(int max)
{
if(velocity_sum>max) velocity_sum=max;
if(velocity_sum<-max) velocity_sum=-max;
}
Pid.h
#ifndef __PID_H
#define __PID_H
int vertical_PID_value(float measure,float calcu); //直立环
int velocity_PID_value(int velocity); //速度环
void I_xianfu(int max); //pwm限幅
#endif
参考大佬文章:
一文搞懂PID控制算法
使用stm32实现电机的PID控制