PID算法C语言程序STM32单片机控制水温实验(二、积分项改进)

一、前言

在PID算法C语言程序STM32单片机控制水温实验中,记录了采用位置型PID算法来控制水温的实验,最终使水温稳定在设定温度附近,误差较小。本文将在原来的实验器材及程序基础上(程序显示升级为OLED显示),对PID算法进行改进,主要涉及积分分离、抗积分饱和、梯形积分、变速积分等。
完整工程已上传至[005]PID算法C语言程序STM32单片机控制水温实验(二、积分项改进)。
PID算法C语言程序STM32单片机控制水温实验(二、积分项改进)_第1张图片

二、PID算法改进

1.积分分离

PID积分项主要是为了消除静差,提高控制精度。但在过程的启动、结束或大幅度增减设定值时,短时间内系统输出有很大偏差,会造成积分累积,引起超调或者振荡。因此偏差值较大时,取消积分作用,以免于超调量增大;而偏差值较小时,引入积分作用,以便消除静差,提高控制精度。
编程的思路就是,首先设定一个误差阈值,再给积分项加一个系数,当误差的绝对值大于误差阈值时,系数为0,绝对值小误差述阈值时,系数为1。

2.抗积分饱和

积分饱和就是指系统存在同一个方向的偏差,PID控制器的输出由于积分作用的不断累加而扩大,导致控制器输出不断增大超出正常范围,进入饱和区。当系统出现相反方向的偏差时,并不能因为偏差反向就快速响应了,而由于之前积分项已经在另一个方向累积了很大了,需要首先从饱和区退出,即积分项需要在另一个方向慢慢减小,直到全部变成现在的方向。因此引入抗积分饱和的改进方法,在计算控制量U(k)的时候,先判断上一时刻的控制量U(k-1)是否已经超出了限制范围,若已经超出最大控制量,则只累加负偏差;若低于最小控制量,则只累加正偏差,从而避免控制量长时间停留在饱和区。
编程思路就是,再给积分项增加一个系数,当上一次的控制量已经达到最大控制量了,则判断误差的正负,如果误差为负,则系数为1,否则为0;当上一次的控制量低于最小控制量,再判断误差的正负,如果误差为正,则系数为1,否则为0。

3.梯形积分

这一点要用到微积分的基本原理,在计算曲线面积的时候,因为已经无限细分了,我们一般使用的是矩形的面积公式,但实际上我们的实际控制系统多为离散的,时间尺度比较大,采用采用梯形积分实际上更接近要计算的面积,如下图所示:
PID算法C语言程序STM32单片机控制水温实验(二、积分项改进)_第2张图片
因此在计算积分项时,将本次的误差积分,和上次的误差积分累加,再取平均值,作为积分项,即梯形的面积公式:(上底+下底)*高/2,高即为单位采样时间。
以上3个部分的主要代码如下,供参考:

#define INTEG_SEPRATION_ERR									(5 * 100)//执行积分分离的误差

uint16_t PID_HeaterOnTimeCalculate(uint32_t u32SetTemper, uint32_t u32WaterTemper)
{
	int32_t s32IntegSepar = 1;//积分分离系数,0或1
    int32_t s32IntegSatur = 1;//抗积分饱和系数,0或1
	uint16_t u16HeaterOn100ms = 0;
	sPID_Para.s32Error = (int32_t)u32SetTemper - (int32_t)u32WaterTemper;//误差
	if (sPID_Para.s32Error > (10 * 100))//误差大,输出100%
	{
		s32PID_Percent = 100;
		memset(&sPID_Para, 0x00, sizeof(sPID_Para));//PID参数清零
	}else if (sPID_Para.s32Error < (-5 * 100))//负误差过大,输出0
	{
		s32PID_Percent = 0;
		memset(&sPID_Para, 0x00, sizeof(sPID_Para));//PID参数清零
	}else
	{
		s32IntegSepar = (abs(sPID_Para.s32Error) > INTEG_SEPRATION_ERR) ? 0 : 1;//积分分离系数
		if (s32PID_Percent >= 100)//上次的控制量已经最大
		{
			if (s32IntegSepar > 0)//有积分作用
			{
				s32IntegSatur = (sPID_Para.s32Error < 0) ? 1 : 0;//抗积分饱和,只累计负偏差
			}
		}else
		{
			if (s32PID_Percent <= 0)//上次的控制量已经最小
			{
				if (s32IntegSepar > 0)//有积分作用
				{
					s32IntegSatur = (sPID_Para.s32Error > 0) ? 1 : 0;//抗积分饱和,只累计正偏差
				}
			}
		}
		sPID_Para.s32ErrSum	+= sPID_Para.s32Error;//积分
		sPID_Para.s32ErrDiffer = sPID_Para.s32Error - sPID_Para.s32LastError;//误差微分
		s32PID_Percent = (int32_t)u16PID_Coefficient[0] * sPID_Para.s32Error//比例
					+ s32IntegSepar * s32IntegSatur * (int32_t)u16PID_Coefficient[1] * ((sPID_Para.s32ErrSum + sPID_Para.s32LastErrSum) >> 1)//积分
					+ (int32_t)u16PID_Coefficient[2] * sPID_Para.s32ErrDiffer;//微分
		//printf("Percent:%d ",s32PID_Percent);
		s32PID_Percent /= 10000;//系数*100,温度*100,所以要除以10000
		//printf("Err:%d,LastErr:%d,ErrSum:%d,LastErrSum:%d,ErrDiffer:%d ",sPID_Para.s32Error,sPID_Para.s32LastError,sPID_Para.s32ErrSum,sPID_Para.s32LastErrSum,sPID_Para.s32ErrDiffer);
		//printf("Separ:%d,Satur:%d ",s32IntegSepar,s32IntegSatur);
		
		sPID_Para.s32LastError = sPID_Para.s32Error;//保存上次误差
		sPID_Para.s32LastErrSum = sPID_Para.s32ErrSum;//保存上次误差积分
		if (s32PID_Percent < 5)//最少5%,防止继电器开启时间过短
		{
			s32PID_Percent = 0;
		}else
		{
			if (s32PID_Percent > 100)
			{
				s32PID_Percent = 100;
			}
		}
	}	
	u16HeaterOn100ms = (uint16_t)(s32PID_Percent * PID_CONTROL_PERIOD / 100);//百分比应除以100
	return u16HeaterOn100ms;
}

4.变速积分

传统的PID控制算法中,积分增益为常数,在整个调节过程中不变,偏差大时容易出现饱和、超调,因此引进了PID变速积分。变速积分的基本思想是,改变积分项的累加速度,使其与偏差大小相对应:偏差越大,积分越慢; 偏差越小,积分越快。
变速积分PID控制算法的积分项表达式如下:
PID算法C语言程序STM32单片机控制水温实验(二、积分项改进)_第3张图片
函数f[e(k)]与系统当前偏差|e(k)|的关系,可为线性或非线性,一般取如下分段函数:
PID算法C语言程序STM32单片机控制水温实验(二、积分项改进)_第4张图片
所以f[e(k)]的取值范围是[0,1]。
变速积分PID计算程序主要代码如下:

#define THRESHOLD_A											(4 * 100)//变速积分阈值A
#define THRESHOLD_B											(4 * 100)//变速积分阈值B


uint16_t PID_HeaterOnTimeCalculate(uint32_t u32SetTemper, uint32_t u32WaterTemper)
{
	uint16_t u16HeaterOn100ms = 0;
	float fCoeff = 1.0;//变速积分系数
	uint32_t u32Abs = 0;//误差绝对值
	sPID_Para.s32Error = (int32_t)u32SetTemper - (int32_t)u32WaterTemper;//误差
	if (sPID_Para.s32Error > (10 * 100))//误差大,输出100%
	{
		s32PID_Percent = 100;
		memset(&sPID_Para, 0x00, sizeof(sPID_Para));//PID参数清零
	}else if (sPID_Para.s32Error < (-10 * 100))//负误差过大,输出0
	{
		s32PID_Percent = 0;
		memset(&sPID_Para, 0x00, sizeof(sPID_Para));//PID参数清零
	}else//以下为变速积分算法
	{
		u32Abs = abs(sPID_Para.s32Error);
		if (u32Abs <= THRESHOLD_B)
		{
			fCoeff = 1.0;
		}else if ((u32Abs > THRESHOLD_B) && (u32Abs <= (THRESHOLD_A + THRESHOLD_B)))
		{
			fCoeff = (float)(THRESHOLD_A + THRESHOLD_B - u32Abs) / (float)THRESHOLD_A;
		}else
		{
			fCoeff = 0.0;
		}
		//printf("fCoeff:%f ",fCoeff);
		sPID_Para.s32ErrSum	+= (uint32_t)(fCoeff * (float)sPID_Para.s32Error);//积分,当前误差*变速积分系数
		sPID_Para.s32ErrDiffer = sPID_Para.s32Error - sPID_Para.s32LastError;//误差微分
		s32PID_Percent = (int32_t)u16PID_Coefficient[0] * sPID_Para.s32Error//比例
					+ (int32_t)(u16PID_Coefficient[1] * sPID_Para.s32ErrSum)//积分
					+ (int32_t)u16PID_Coefficient[2] * sPID_Para.s32ErrDiffer;//微分
		//printf("Percent:%d ",s32PID_Percent);
		s32PID_Percent /= 10000;//系数*100,温度*100,所以要除以10000
		printf("Err:%d,LastErr:%d,ErrSum:%d,ErrDiffer:%d ",sPID_Para.s32Error,sPID_Para.s32LastError,sPID_Para.s32ErrSum,sPID_Para.s32ErrDiffer);

		sPID_Para.s32LastError = sPID_Para.s32Error;//保存上次误差
		//sPID_Para.s32LastErrSum = sPID_Para.s32ErrSum;//保存上次误差积分
		if (s32PID_Percent < 5)//最少5%,防止继电器开启时间过短
		{
			s32PID_Percent = 0;
		}else
		{
			if (s32PID_Percent > 100)
			{
				s32PID_Percent = 100;
			}
		}
	}	
	u16HeaterOn100ms = (uint16_t)(s32PID_Percent * PID_CONTROL_PERIOD / 100);//百分比应除以100
	return u16HeaterOn100ms;
}

三、实验效果

以上两段程序效果相近,仅展示变速积分的效果,温度变化曲线如下图:
PID算法C语言程序STM32单片机控制水温实验(二、积分项改进)_第5张图片

四、总结

该水温控制系统,传统PID与改进PID效果差不多,可能是因为该系统过于简单;另外P、I、D参数选取未经过大量实验,可能不是最优的,本例中的积分I参数非常小,所以积分项的改进效果不明显,但目的在于总结积分项的改进方法与C语言实现。有兴趣的可以试验整定参数,达到更好的效果。
如有错漏之处,欢迎指正。

你可能感兴趣的:(算法,单片机,STM32,1024程序员节)