PID从原理到C语言实现

在工业应用中PID及其衍生算法是应用最广泛的算法之一,是当之无愧的万
能算法,如果能够熟练掌握PID算法的设计与实现过程,对于- -般的研发人员来
讲,应该是足够应对一-般研发问题了,而难能可贵的是,在我所接触的控制算法
当中,PID控制算法又是最简单,最能体现反馈思想的控制算法,可谓经典中的
经典。经典的未必是复杂的,经典的东西常常是简单的,而且是最简单的,想想
牛顿的力学三大定律吧,想想爱因斯坦的质能方程吧,何等的简单!简单的不是
原始的,简单的也不是落后的,简单到了美的程度。先看看PID算法的数学模型及公式:
在这里插入图片描述
在C语言的算法实现中我们采用最直观的离散型公式:
在这里插入图片描述
整合成常数模式:
在这里插入图片描述

位置型PID算法

从公式中我们可以发现,我们在C语言中首先要定义一个结构体,里面包含以下几个参数

	struct PID
{
	float SetValue;//设定值
	float ActualValue;//实际输出值(用于检测回馈)
	float err;//本次实际值与设定值之间的偏差e(k)
	float err_last;//上次实际值与设定值之间的差值e(k-1)
	double Kp;
	double Ki;
	double Kd;//比例,积分,微分系数
	float integral;//定义积分值(用于积分项累加偏差)∫error

	float Uk;//定义输出值u(k)
};

有了以上定义,我们就可以将我们的PID的处理表示出来:

	
float PID_realize(struct PID *pid,float needValue)//需要的实际值
{

	pid->SetValue=needValue;	//先输出需要的值
	pid->err=pid->SetValue-pid->ActualValue;//计算偏差,看看离我们需要的值还差多少
	pid->integral+=pid->err;//累计偏差值
	pid->Uk=(pid->Kp)*(pid->err)+(pid->Ki)*(pid->integral)+(pid->Kd)*(pid->err-pid->err_last);
	
	//执行调节过后的函数
	pid->err_last=pid->err;
	pid->ActualValue=pid->Uk;//这一步可以根据自己的实际需求进行获取实际值
	return pid->ActualValue;
}

有了这两个函数,我们的PID的基础算法就已经出来了,但是,在这之前,我们再给他一个初始化程序:

void PID_init(struct PID *pid,double kp,double ki,double kd)
{
	printf("PID开始初始化\r\n");
	//Kp,Ki,Kd的值根据自己需求更改
	pid->Kp=kp;		
	pid->Ki=ki;
	pid->Kd=kd;
	
	pid->SetValue=0;
	pid->ActualValue=0;
	pid->err=0;
	pid->err_last=0;
	pid->Uk=0.0;
	pid->integral=0;
	
	printf("初始化完成!\r\n");
}

好了,现在程序所需要的函数我们就已经造作完成了,我们写个主程序让他运行一下

int main()
{
	struct PID my_pid;
	int count=1,count2=0;
	PID_init(&my_pid,0.1,0.015,0.2);

	while(count<500)//我们让他运行500次看看
	{
		double ActualValue=PID_realize(&my_pid,1000.0);
		printf("%8.6lf\t",ActualValue);
		if(count2%5==0)
		{
			printf("\n");
		}
		count++;
		count2++;
	}

	return 0;
}

运行结果:
在这里插入图片描述

抗饱和积分型PID算法

从这上面我们可以看到,他运行500次之后,距离我们需要的1000仍然有1左右的偏差,这就是PID的位置型算法,只能基本上实现我们所需要的功能,但是所需要的时间比较长,这是因为位置型算法里面还存在一个方向偏差和起步震荡等问题:
下面,我们引入抗饱和积分型PID算法:
所谓的积分饱和现象是指如果系统存在-一个方向的偏差,PID 控制器的输出
由于积分作用的不断累加而加大,从而导致执行机构达到极限位置,若控制器输
出U(k)继续增大,执行器开度不可能再增大,此时计算机输出控制量超出了正
常运行范围而进入饱和区。一旦系统出现反向偏差,u(k)逐渐从饱和区退出。进
入饱和区越深则退出饱和区时间越长。在这段时间里,执行机构仍然停留在极限
位置而不随偏差反向而立即做出相应的改变,这时系统就像失控一样,造成控制
性能恶化,这种现象称为积分饱和现象或积分失控现象。
防止积分饱和的方法之一就是抗积分饱和法,该方法的思路是在计算u(k)
时,首先判断上一时刻的控制量u(k-1)是否已经超出了极限范围:如果
u(k-1)>umax,则只累加负偏差;如果u(k-1) <umin,则只累加正偏差。从而避
免控制量长时间停留在饱和区。直接贴出代码,不懂的看看前面几节的介绍。

同时,我们的程序也要做一点小小的改动:
首先是结构体定义:

	struct PID
{
	float SetValue;//设定值
	float ActualValue;//实际输出值(用于检测回馈)
	float err;//本次实际值与设定值之间的偏差e(k)
	float err_last;//上次实际值与设定值之间的差值e(k-1)
	double Kp;
	double Ki;
	double Kd;//比例,积分,微分系数
	float integral;//定义积分值(用于积分项累加偏差)∫error

	float Uk;//定义输出值u(k)
	
	float Umax;//正向最大偏差
	float Umin;//负向最大偏差
};

还有我们的处理函数:

float PID_realize(struct PID *pid,float needValue)//需要的实际值
{

	int index=0;
	pid->SetValue=needValue;	//先输出我需要的值
	pid->err=pid->SetValue-pid->ActualValue;//计算偏差
	pid->integral+=pid->err;//累计偏差值
	
	if(pid->ActualValue>pid->Umax)//抗积分饱和
	{
		if(abs(pid->err)>needValue)//积分分离过程
			{index=0;}
		else
			{
				index=1;
				if(pid->err<0)
				pid->integral+=pid->err;
			}
	}else if(pid->ActualValue<pid->Umin)
	{
		if(abs(pid->err)>needValue)//积分分离过程
			{index=0;}
		else
			{
				index=1;
				if(pid->err>0)
				pid->integral+=pid->err;
			}
	}else
	{
		if(abs(pid->err)>needValue)//积分分离过程
			{index=0;}
		else
			{
				index=1;
				pid->integral+=pid->err;
			}
	}

	
	pid->Uk=(pid->Kp)*(pid->err)+index*(pid->Ki)*(pid->integral)+(pid->Kd)*(pid->err-pid->err_last);
	
	//执行调节过后的函数
	pid->err_last=pid->err;
	pid->ActualValue=pid->Uk;//这一步可以根据自己的实际需求进行获取实际值
	return pid->ActualValue;
}

这里说一下,所谓积分分离,就是为了克服系统有较大的超调甚至是震荡现象,其基本思路是当被控量与设定值偏差较大时,取消积分作用;当被控量接近给定值时,引入积分控制,以消除静差,提高精度。
通俗一点就是,当得到的偏差err比较大时,令index=0,使积分项Ki∫err=0;
当得到的偏差err比较大时,令index=1,使积分项Ki
∫err加入计算当中。

接着说,既然我们多定义了两个变量,那我们就把相应的变量也初始化下:

void PID_init(struct PID *pid,double kp,double ki,double kd,float umax,float umin)
{
	printf("PID开始初始化\r\n");
	//Kp,Ki,Kd的值根据自己需求更改
	pid->Kp=kp;		
	pid->Ki=ki;
	pid->Kd=kd;
	
	pid->SetValue=0;
	pid->ActualValue=0;
	pid->err=0;
	pid->err_last=0;
	pid->Uk=0.0;
	pid->integral=0;
	
	pid->Umax=umax;
	pid->Umin=umin;

	printf("初始化完成!\r\n");
}

接着是我们的主函数:

int main()
{
	int count=1,count2=0;
	PID_init(&my_pid,0.2,0.15,0.2,1100,-100.0);//我们把Umax和Umin限制相对在100左右,同时提高下我们的积分系数

	while(count<500)
	{
		double speed=PID_realize(&my_pid,1000.0);
		printf("%8.6lf\t",speed);
		if(count2%5==0)
		{
			printf("\n");
		}
		count++;
		count2++;
	}

	return 0;
}

运行500次,看看我们的结果:
在这里插入图片描述
可以看到,我们最后的结果已经稳定在1000左右,而且稳定时间大大缩短,这就是抗饱和积分的好处,不仅提高了精度,同时将所需要的时间大大缩短。

当然,在实际应用中,Kp,Ki,Kd的参数需要自己手动实践调节,这次就和大家分享到这里。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值