什么是PID

我相信能来看这篇文章的应该都知道什么是PID,PID就是一种控制算法,利用比例运算(P),积分运算(I)和微分运算(D)一起控制某一事件,当然也可以只运用其中一个也可以两两结合。

运用举例:

比如我们家里都会有的那个电热水器,有点热水器会有那个保温功能,假如我们设定的温度保持在60度。一开始热水器开始工作P发现此时水温距离目标温度还差的多就控制加热棒输出较大功率快速加热。随着温度越来越高,P会发现距离目标温度越来越近,输出功率也越来越小,但由于“惯性”实际温度会超过目标温度,此时P就会输出负功率,但实际中没有负功率,我们可以启动制冷,但又会由于惯性温度会低于目标温度,就这样实际温度在目标温度附近徘徊。

java 根据pid获取线程和句柄 java pid算法_网络


显然这虽然能达到目标,但是不是很理想。于是就引入了D。D会发现当快要到目标温度时温度会上升比较慢(此时温度与目标温度的差值和上次温度和目标温度的差值比较),就表示快要到达目标温度了D就会给出信号以减小功率的输出。但是可能会出现下面这种情况,温度维持在目标温度附近。

java 根据pid获取线程和句柄 java pid算法_网络_02


这种虽然温度不会大幅跳动了但是没有到达我们设定的目标温度,此时就引入了I。I的作用就是在一段时间内它发现温度一直没有到达目标温度,此时它就增大输出功率进行加热。

以上就是PID算法在实际的一个应用,那他的一个底层数学逻辑是怎么样的勒?

底层数学逻辑的实现

接下来我们就讨论一下他的一个底层数学逻辑是怎么实现的。我们先来看看他的一个公式:

java 根据pid获取线程和句柄 java pid算法_java 根据pid获取线程和句柄_03


可以看出P就是简单的比例运算,I就是积分运算,D就是微分运算;

P:比例运算很简单我就不做阐述了;

I:积分运算,积分两点之间的积分,为两点连线,和两点对x轴的垂线,以及两垂足之间的线段所组成的梯形的面积。

java 根据pid获取线程和句柄 java pid算法_java 根据pid获取线程和句柄_04

因为我们要编写这个代码,所以就得对其离散化理解。

java 根据pid获取线程和句柄 java pid算法_网络_05


D:微分运算,微分就是导线某点的切线斜率。

java 根据pid获取线程和句柄 java pid算法_java 根据pid获取线程和句柄_06

以上就是PID运算的一个数学理解。我们可以上面的总结一下得到如下的公式:

java 根据pid获取线程和句柄 java pid算法_c语言_07

其实上面讲解的这个PID也叫位置式PID,还有一种PID是 增量式PID。而增量式PID就是两次位置式PID相减。将两次位置式PID相减得到

java 根据pid获取线程和句柄 java pid算法_stm32_08

区别
1、增量式只与最近三次的误差有关,运算速度快且无累积误差,而位置式需要对误差进行积分,容易产生累积误差且运算速度慢。
2、增量式的结果是输出量的变化量,即便出现误差影响不会太大,而位置式的结果是输出量,一旦出现错误影响会很大。

C语言实现

我们先用VisualStudio C语言编辑器编写代码验证,代码如下:

#include "stdio.h"
#include "windows.h"


typedef  unsigned char uint8_t;
typedef  unsigned int uint32_t;

//限幅输出预处理命令
#define Astrict_Out(input,min,max)	{  		\
	if(input < min)					\
		input = min;				\
	else if(input >max)				\
		input =max;					\
}

enum PID_MODE
{
	PID_position = 0,   //位置式PID
	PID_increment      //增量式PID
};

typedef struct
{
	uint8_t mode; //PID运行模式
	float Kp;
	float Ki;
	float Kd;

	float max_Out;  //PID最大输出
	float min_Out;  //PID最大输出
	//float max_I_Out;  //PID最大积分输出

	float set_num;  //PID目标值
	float new_num;  //PID当前值

	float All_Out;  //三项叠加输出
	float P_Out;  //比例输出
	float I_Out;  //积分输出
	float D_Out;  //微分输出

	//微分项最近三个值,0最新,1上一次,2上上次
	float D_buf[3];  

	//误差项最近三个值 
	float Err_buf[3];
}PID_struct;

/*
PID初始化函数
参数:PID_struct *pid:PID结构体;
uint8_t mode:选择PID运算模式 0:位置式PID;  1:增量式PID
const float PID[3]:PID系数,Kp,Ki,Kd;
float max_out:PID最大输出值
float min_out:PID最小输出值
*/
void PID_Init(PID_struct *pid,uint8_t mode,const float PID[3],float max_out, float min_out)
{
	if (pid == NULL || PID == NULL)
		return;
	pid->mode = mode;
	pid->Kp = PID[0];
	pid->Ki = PID[1];
	pid->Kp = PID[2];
	pid->max_Out = max_out;
	pid->min_Out = min_out;
	pid->D_buf[0] = pid->D_buf[1] = pid->D_buf[2] = 0.0f;
	pid->Err_buf[0] = pid->Err_buf[1] = pid->Err_buf[2] = pid->P_Out = pid->I_Out = pid->D_Out = 0.0f;
}

/*
PID运算函数
参数:
PID_struct* pid:PID结构体
float new_num:新的数值(比如最新采集到的温度)
float set_num:目标温度
返回值:
PID运算结果
*/
float PID_calc(PID_struct* pid, float new_num, float set_num)
{
	if (pid == NULL || pid == NULL)
		return 0.0f;
	 
	//存放过去两次计算的误差
	pid->Err_buf[2] = pid->Err_buf[1];
	pid->Err_buf[1] = pid->Err_buf[0];
	//设定目标值和当前值
	pid->set_num = set_num;
	pid->new_num = new_num;
	//计算最新误差
	pid->Err_buf[0] = set_num - new_num; //目标值减去最新值

	//判断PID模式
	if (pid->mode == PID_position)  //位置式
	{
		pid->P_Out = pid->Kp * pid->Err_buf[0]; //比例计算
		pid->I_Out += pid->Ki * pid->Err_buf[0];  //积分计算
		//存放过去两次计算的微分误差值
		pid->D_buf[2] = pid->D_buf[1];
		pid->D_buf[1] = pid->D_buf[0];
		//当前误差的微分用本次误差减去上一次误差来计算
		pid->D_buf[0] = pid->Err_buf[0] - pid->Err_buf[1];
		pid->D_Out = pid->Kd * pid->D_buf[0];  //微分输出

		pid->All_Out += pid->P_Out + pid->I_Out + pid->D_Out;  //叠加输出

		Astrict_Out(pid->All_Out, pid->min_Out, pid->max_Out);
	}
	else if(pid->mode == PID_increment)  //增量式PID
	{
		//以本次误差与上次误差的差值作为比例项带入计算
		pid->P_Out = pid->Kp * (pid->Err_buf[0]-pid->Err_buf[1]);
		//以本次误差作为积分项带入计算
		pid->I_Out = pid->Ki * pid->Err_buf[0];
		//迭代微分学的数组
		pid->D_buf[2] = pid->D_buf[1];
		pid->D_buf[1] = pid->D_buf[0];
		//以本次误差与上次误差的差值减去上次误差与上上次误差的差值作为微分项的输入带入计算 \
			Err_buf[0]-Err_buf[1] - (Err_buf[1]-Err_buf[2])
		pid->D_buf[0] = (pid->Err_buf[0] - 2.0f * pid->Err_buf[1] + pid->Err_buf[2]);
		pid->D_Out = pid->Kd * pid->D_buf[0];

		//三项叠加
		pid->All_Out += pid->P_Out + pid->I_Out + pid->D_Out;
		Astrict_Out(pid->All_Out, pid->min_Out, pid->max_Out);
	}
	return pid->All_Out;
}

/*
PID中间数值清除函数
*/
void PID_clear(PID_struct* pid)
{
	if (pid == NULL)
		return;
	//清除当前误差
	pid->Err_buf[0] = pid->Err_buf[1] = pid->Err_buf[2] = 0.0f;
	//微分项清零
	pid->D_buf[0] = pid->D_buf[1] = pid->D_buf[2] = 0.0f;
	//输出清零
	pid->All_Out = pid->P_Out = pid->I_Out = pid->D_Out = 0.0f;
	//目标值和当前值清零
	pid->set_num = pid->new_num = 0.0f;
}

PID_struct pid1;
int main(void)
{	
	int i =0;
	float n = 0;
	const float pid_data[3] = {11,0.5,1};  //初始化PID的系数,Kp,Ki,Kd
	PID_Init(&pid1, 1, pid_data, 100, 0);  //PID初始化
	scanf("%f",&n);  //通过键盘输入数值模拟单片机采集到的信息,如温度
	for (i = 0; i < 50; i++)
	{
		PID_calc(&pid1, n, 90);
		Sleep(200);
		printf("%f\n",pid1.All_Out);   //输出PID运算结果,用于控制,如改变PWM的占空比。
		scanf("%f",&n);
	}
	return 0;
}

我们通过键盘输入模拟数据采集进行PID运算

java 根据pid获取线程和句柄 java pid算法_c语言_09

可以看到效果可以。

然后我们一直到STM32上面:

//.c文件
#include "pid.h"

/*
PID初始化函数
参数:PID_struct *pid:PID结构体;		
uint8_t mode:选择PID运算模式 0:位置式PID;  1:增量式PID
const float PID[3]:PID系数,Kp,Ki,Kd;
float max_out:PID最大输出值
float min_out:PID最小输出值
*/
void PID_Init(PID_struct *pid,uint8_t mode,const float PID[3],\
								float max_out, float min_out)
{
	if (pid == NULL || PID == NULL)
		return;
	pid->mode = mode;  
	pid->Kp = PID[0];
	pid->Ki = PID[1];
	pid->Kp = PID[2];
	pid->max_Out = max_out;
	pid->min_Out = min_out;
	pid->D_buf[0] = pid->D_buf[1] = pid->D_buf[2] = 0.0f;
	pid->Err_buf[0] = pid->Err_buf[1] = pid->Err_buf[2] \
							= pid->P_Out = pid->I_Out = pid->D_Out = 0.0f;
}

/*
PID运算函数
参数:
PID_struct* pid:PID结构体
float new_num:新的数值(比如最新采集到的温度)
float set_num:目标温度
返回值:
PID运算结果
*/
float PID_calc(PID_struct* pid, float new_num, float set_num)
{
	if (pid == NULL || pid == NULL)
		return 0.0f;
	 
	//存放过去两次计算的误差
	pid->Err_buf[2] = pid->Err_buf[1];
	pid->Err_buf[1] = pid->Err_buf[0];
	//设定目标值和当前值
	pid->set_num = set_num;
	pid->new_num = new_num;
	//计算最新误差
	pid->Err_buf[0] = set_num - new_num; //目标值减去最新值

	//判断PID模式
	if (pid->mode == PID_position)  //位置式
	{
		pid->P_Out = pid->Kp * pid->Err_buf[0]; //比例计算
		pid->I_Out += pid->Ki * pid->Err_buf[0];  //积分计算
		//存放过去两次计算的微分误差值
		pid->D_buf[2] = pid->D_buf[1];
		pid->D_buf[1] = pid->D_buf[0];
		//当前误差的微分用本次误差减去上一次误差来计算
		pid->D_buf[0] = pid->Err_buf[0] - pid->Err_buf[1];
		pid->D_Out = pid->Kd * pid->D_buf[0];  //微分输出

		pid->All_Out += pid->P_Out + pid->I_Out + pid->D_Out;  //叠加输出

		Astrict_Out(pid->All_Out, pid->min_Out, pid->max_Out);
	}
	else if(pid->mode == PID_increment)  //增量式PID
	{
		//以本次误差与上次误差的差值作为比例项带入计算
		pid->P_Out = pid->Kp * (pid->Err_buf[0]-pid->Err_buf[1]);
		//以本次误差作为积分项带入计算
		pid->I_Out = pid->Ki * pid->Err_buf[0];
		//迭代微分学的数组
		pid->D_buf[2] = pid->D_buf[1];
		pid->D_buf[1] = pid->D_buf[0];
		//以本次误差与上次误差的差值减去上次误差与上上次误差的差值作为微分项的输入带入计算 \
			Err_buf[0]-Err_buf[1] - (Err_buf[1]-Err_buf[2])
		pid->D_buf[0] = (pid->Err_buf[0] - 2.0f * pid->Err_buf[1] + pid->Err_buf[2]);
		pid->D_Out = pid->Kd * pid->D_buf[0];

		//三项叠加
		pid->All_Out += pid->P_Out + pid->I_Out + pid->D_Out;
		Astrict_Out(pid->All_Out, pid->min_Out, pid->max_Out);
	}
	return pid->All_Out;
}

/*
PID中间数值清除函数
*/
void PID_clear(PID_struct* pid)
{
	if (pid == NULL)
		return;
	//清除当前误差
	pid->Err_buf[0] = pid->Err_buf[1] = pid->Err_buf[2] = 0.0f;
	//微分项清零
	pid->D_buf[0] = pid->D_buf[1] = pid->D_buf[2] = 0.0f;
	//输出清零
	pid->All_Out = pid->P_Out = pid->I_Out = pid->D_Out = 0.0f;
	//目标值和当前值清零
	pid->set_num = pid->new_num = 0.0f;
}

//.h文件
#ifndef __PID_H
#define __PID_H

#include "main.h"

//限幅输出预处理命令
#define Astrict_Out(input,min,max)	{  	\
									if(input < min)				\
										input = min;				\
									else if(input >max)		\
										input =max;					\
								}

enum PID_MODE  //PID模式枚举变量
{
	PID_position = 0,   //位置式PID
	PID_increment      //增量式PID
};

typedef struct
{
	uint8_t mode; //PID运行模式
	float Kp;
	float Ki;
	float Kd;

	float max_Out;  //PID最大输出
	float min_Out;  //PID最大输出

	float set_num;  //PID目标值
	float new_num;  //PID当前值

	float All_Out;  //三项叠加输出
	float P_Out;  //比例输出
	float I_Out;  //积分输出
	float D_Out;  //微分输出

	//微分项最近三个值,0最新,1上一次,2上上次
	float D_buf[3];  
	float Err_buf[3];  	//误差项最近三个值 
}PID_struct;

void PID_Init(PID_struct *pid,uint8_t mode,const float PID[3],\
								float max_out, float min_out);
float PID_calc(PID_struct* pid, float new_num, float set_num);
void PID_clear(PID_struct* pid);

#endif

以上就是我结合其他博主的理解总结我自己对PID算法的理解。