PID算法的C代码实现
PID算法是一个非常经典的东西。要打造自平衡小车,飞行器的PID是一个需要克服的障碍。因此,本节我们将根据自己的学习经验,仔细讲解PID,尽量使其通俗易懂。并给出清晰的PID例子,帮助你理解PID。
1. 首先介绍一下PID名称的由来:
P:比例,即输入偏差乘以一个常数。
I:积分,即对输入偏差进行积分运算。
D:求导,对输入偏差进行微分运算。
注:输入偏差=被控对象读取值-设定值。比如我想把温度控制在26度,但现在温度传感器上读到的是28度。那么这个26度就是“设定值”,28度就是“被控对象的读取值”。接下来我们看看这三个要素对PID算法的影响。就明白了。如果你不明白,就不要强求。
P,比如当前输出为1,目标输出为100,那么P的作用就是尽快达到100。仅将 P 视为一个系数;关于我的什么对于学过高等数学的人来说,积分 0 是一个常数。我让误差为0,起到和谐的效果;那么D呢?大家都知道微分就是导程,导程代表正切,对吧?切线方向是到达最高点最快的方向。通过这种方式理解的最优解可以尽快达到,然后分化就可以起到加速适应过程的作用。
2。那么你应该知道PID算法有两种类型:一种是位置式的,另一种是增量式的。增量式常用于汽车。为什么?位置 PID 输出与所有过去的状态相关联。计算时必须加上e(每次的控制误差)。这个计算量非常大,显然没有必要。另外,汽车的PID控制器的输出不是绝对值,而是△,代表增加或减少的多少。也就是说,通过增量式PID算法,每次输出的是PWM应该增加或减少多少,而不是实际的PWM值。所以只要理解增量PID即可。
3。接下来我们来说说PID参数的设置,即常数系数Kp、Ti、Td等如何确定。 PID公式中,然后转移到PID算法中。如果你想使用PID,你需要自己调整PID参数以适合你的项目。通常,四轴飞行器和自平衡飞行器的参数需要自己调整,这是一个繁琐的过程。这次我们可以忽略它。关于如何确定PID参数,网上有很多经验可以借鉴。比如经典的试错公式:
找到最佳的参数设置,从小到大的顺序检查。
首先是分数,然后是积分,最后是微分。
曲线经常波动,所以必须放大比例刻度盘。
曲线围绕一条大曲线徘徊,比率刻度盘朝下。
曲线偏差慢慢恢复,积分时间减少。
曲线的摆动周期较长,积分时间也会较长。
曲线摆频较快,先减小差值。
如果动态差值较大,则振荡较慢,应延长差值时间。
理想的曲线有两波,前高后低四比一。
一次查看,两次调整,多次分析,调整质量不会低。
4。然后我们使用示例来帮助解释常用的 PID 模型。 (PID控制不一定要求三者都出现,也可以只有PI和PD控制,关键看控制对象。)(以下内容仅是PID模型的介绍,您无需阅读对理解PID没有用)
例子:我们要控制一个人,让他走110步,然后停止使用PID控制方法。
1)P比例控制就是让它按照一定的比例运行然后停止。例如,如果比率系数为108,则您将走一次108步,然后停止行走。
注:P比例控制是最简单的控制方法。控制器输出与误差输入信号成比例。然而,当只有比例控制时,系统输出存在稳态误差。比如上面只能到108,但是无论如何也到不了110。
2)积分PI控制是按照一定的步速,直到步骤112,然后返回继续行走。当到达108步位置时,返回到110步位置,在110位置来回摆动几次,最后停在110步位置。注:在积分控制I中,控制器的输出与输入误差信号的积分成正比。对于自动控制系统,如果进入稳态后出现稳态误差,则称该控制系统存在稳态误差,或者简称为微分系统。为了消除稳态误差,必须在控制器中引入“积分项”。积分项对误差的影响取决于时间积分。随着时间的增加,积分表达式会增加。这样,即使误差很小,积分项也会随着时间的推移而增加,从而促使控制器的输出功率增加,进一步减小稳态误差,直到等于0。因此,一个比例+积分(PI)控制器可以使系统进入稳定状态后在稳定状态下无误差。
3)微分PD控制是指以一定的配速行走一百步左右,然后慢慢接近110步的位置。如果它最终能准确地停在110步位置,那么它就不是静态微分控制。 ;如果在步骤110附近停止(例如步骤109或步骤111),则存在静态微分控制。
注:在微分控制D中,控制器输出与误差输入信号的微分(即误差的变化率)成正比。
自动控制系统在克服误差的调节过程中可能会出现振荡甚至不稳定。原因是存在较大的惯性分量(连杆)或具有误差抑制作用的滞后分量,其变化总是滞后于误差变化。解决办法是“引导”纠错效果的变化,即当误差接近于零时,纠错效果应该为零。这意味着仅仅将“比例 P”引入控制器通常是不够的。比例项的作用只是增加误差的幅度。现在必须添加一个“微分项”,它可以预测误差变化的趋势。 。这样,比例+微分控制器就可以提前使误差抑制控制效果为零甚至负值,从而避免被控量严重超调。因此,对于惯性较大或滞后的被控对象,采用比例P+微分D(PD)控制器可以改善系统在调节过程中的动态特性。
5。用小明解释一下PID:
小明接到这个任务:水箱有一点漏水(而且漏水率不一定是固定的),需要水面高度保持在某个位置。如果发现水面高度低于所需位置,请向水箱加水。接到任务后,小明站在水箱旁。时间长了,他觉得无聊,就跑进房间看小说,每隔30分钟检查一下水位。水漏得太快了。每次小明来查看,水都快没了。她离要求的身高还很远。小明改成每3分钟检查一次。结果每次都没有漏水,也不需要加水。 ,来得太频繁是无用功。尝试几次后每 10 分钟检查一次。这个审查时间称为抽样周期。起初,小明用勺子加水。水龙头距离水库有十几米。他常常要跑好几次才能补充足够的水。于是,小明改用水桶加水。他一边补充一边把水桶装满了。运行次数减少,加水速率增加。差一点就到了,但是水箱溢出了好几次,我也好几次不小心弄湿了鞋子。小明再次尽力了。我没有用勺子或水桶,但我用了紫菜。几次之后我发现这样就可以了,不用跑太远。不要让水多次溢出。这个加水工具的大小称为比例系数。
小明也意识到,虽然水不会溢出,但有时会高于要求的位置,仍然存在弄湿鞋子的风险。他想出了另一种方法,在水箱顶部放一个漏斗。每次加水的时候,他都不是直接倒进水箱里,而是倒进一个漏斗里,让它慢慢地倒。这样,溢出的问题解决了,但是加水的速度慢,有时跟不上漏水的速度。于是,他尝试更换不同尺寸的漏斗来控制加水速度,最终找到了自己满意的漏斗。漏斗时间称为积分时间。
小明终于松了一口气,但任务的要求突然变得更加严格,水位控制的时效性要求大大提高。当水位太低时,必须立即加水至所需位置,但不能太高,否则工资不发。小明又遇到麻烦了!于是他苦苦思索,终于请他想办法了。他总是在旁边放一个备用的水槽。当他意识到水位低时,他只是将容器装满水,而没有通过漏斗。这样保证了时效,但有时水位会很高。太多了。然后,他在水箱所需水位的上方钻了一个孔,然后将一根软管连接到下面的储水桶上,以便多余的水从上面的孔中排出。水流出的速率称为微分时间。
6.理解代码中的PID:(仔细看注释,很容易理解。注意下面的PID公式)
先看增量式PID公式:
PID=Uk+ KP* [ E(k) -E(k-1)]+KI*E(k)+KD*【E(k)-2E(k-1)+E(k-2)】
在单片机中使用 PID由于速度和 RAM - 通常不使用浮点数。这里我们以整型变量为例来描述PID在微控制器中的使用。由于它是使用整数实现的,因此不太准确。但对于一般场合来说,这个精度已经足够了。程序中将系数和温度提高了10倍,因此精度不是很高,但对于大多数场合来说已经足够了。如果不够,可以再增加10倍。或者如果不超出整个数据类型的范围,则进行 100 倍处理。下面的程序包括两部分:PID计算和输出。当偏差>10度时,将全速加热。当偏差在10度以内时,输出PID运算。
程序说明:以下程序请先看main函数。我们可以看到,PID_Output()函数是在初始化定时器0之后执行的。在PID_Output()函数中,首先使用iTemp变量来获取PID运算的结果,以决定是否开始加热电热丝。下面的 if 语句与计时器结合来确定 PID 算法执行的频率。 PID_Operation()函数看起来很复杂,但它实际上做了一件事:它使用PID公式根据提供的数据计算最终的PID值。
#include
typedef unsigned char uChar8;
typedef unsigned int uInt16;
typedef unsigned long int uInt32;
sbit ConOut = P1^1; //加热丝连接到端口P1.1
typedef struct PID_Value
{
uInt32 liEkVal[3]; //差异被存储,根据给定和反馈的差异反 UCHAR8 UEKFLAG [3]; //符号,1对应负数,0对应对应数字
uchar8 ukp_coe; // 比例系数
uchar8 uki_coe ; ;
uInt16 iCurVal; //实际值
}PID_ValueStr;
PID_ValueStr PID;总是在计算。相反,会计算每个指定的时间段。
/* ************************************************* * ************
/* 函数名称: PID_Operation()
/* 函数功能: PID 运算
/* 输入参数: 无(不可见输入、系数、设定值、等) .)
/* 导出参数: None (不可见输出, U(k))
/* 函数说明: U(k)+KP*[E(k)-E(k-1) ] +KI *E(k)+KD*[E(k)-2E(k-1)+E(k-2)]
**************** ** ** ***************************************** */
void PID_Operation(void )
{
uInt32 Temp[3] = {0}; //中间临时变量
uInt32 PostSum = 0; //正数和
uInt32 NegSum = 0; //负数和
if(PID.iSetVal > PID.iCurVal) //设定值是否大于实际值?
{
if(PID.iSetVal - PID.iCurVal > 10) //偏差是否大于10? ? Temp[0] = PID.iSetVal - PID .iCurVal; //偏差 PID.liEkVal[1]) //E(k)>E(k-1)否?
{
温度[0] = PID.liEkVal[0] - PID.liEkVal[1]; //E(k)>E(k-1)
PID.uEkFlag[0] = 0 ; ; ; //E(k) Temp[2]) //E(k-2)+E(k)>2E( k-1) 不?
{
Temp[2] = (PID.liEkVal[0] + PID.liEkVal[2]) - Temp[2];
PID.uEkFlag[2]=0; -2)+E(k)-2E(k-1) 是正数
}
,否则
Temp[2] = Temp[2] - (PID.liEkVal[0] + PID. liEkVal [2]);
PID.uEkFlag[2] = 1; E(k)-2E(k-1) 是负数
}
/* ============================ = == ========================================== */
温度[ 0] = ( uInt32)PID.uKP_Coe * Temp[0]; //KP*[E(k)-E(k-1)]
Temp[1] = (uInt32)PID.uKI_Coe * PID.liEkVal[0]; //KI*E(k)
Temp[2] = (uInt32)PID.uKD_Coe * Temp[2]; //KD*[E(k-2)+E(k)-2E(k-1 )]
/*接下来的代码是所有正项的叠加和负项的叠加* /
/* =========计算KP的值*[E(k)-E(k-1)] ========= */
if(PID.uEkFlag[ 0] == 0)
PostSum += Temp[0]; /* ===== ==== 计算KI*E(k)的值 ========= */
if(PID.uEkFlag[1] == 0)
PostSum + = Temp[1]; //正数和
else
; /* 无操作。输入 if 因为 PID.iSetVal > PID.iCurVal (即 E(K)>0),
那么不能为负数,所以反转 */
/* === ==== = =计算KD*[E(k-2)+E(k)-2E(k-1)]==========的值 */
if(PID.uEkFlag [2] = =0)
PostSum += Temp[2];使用 使用
‐ ‐ off out-out off
else
NegSum += Temp[2]; 。 ======计算U(k) ========= */
PostSum += (uInt32)PID.iPriVal;
if(PostSum > NegSum) //是控制量iPriVal = (uInt16 )Temp[0];
else PID.iPriVal = 100; 是正数。使用 z 0 输出 0(幅度输出下限)
PID.iPriVal = 0;
}
}
否则 PID.iPriVal = 0; *****************************************************
/*函数名称:PID_Output ()
/* 功能:PID 输出控制
/* 输入参数:None(不可见输入,U(k))
/* 输出参数:None(控制结束)
*** * ************************************************* **** ** * *** */
void PID_Output(void)
{
static uInt16 iTemp;
static uChar8 uCounter;
iTemp = PID.iPriVal;
if(iTemp == 0 )
ConOut = 1; //不加热
else ConOut = 0; //预热
if(g_bPIDRunFlag) //定时中断100ms(0.1S),预热周期10S(100份*0.1S)
{
g_bPIDRunFlag = 0;
if( iTemp) iTemp——; == uCounter)
{
PID_Operation(); //每隔0.1*100S调用一次PID运算。
uCounter = 0;
}
}
}
/* ************************** **** *** *********************************
/* 函数名称: PID_Output()
/* 函数功能:PID 输出控制
/* 输入参数:无(不可见输入,U(k))
/* 输出参数:无(控制端子)
******** **** * ********************************************** */
void Timer0Init(void)
{
TMOD |= 0x01; // 设置定时器0工作在模式1
TH0 = 0xDC;
TL0 = 0x00; // 赋予初始值
TR0 =1; {
Timer0Init();
while(1)
{
PID_Output();
}
}
void Timer0_ISR(void) 中断 1
{
静态 uInt16 uiCounter = 0;
TH0 = 0xDC;
TL0 = 0x00;
uiCounter++;
if(100 == uiCounter)
{
g_bPIDRunFlag = 1;
}
}
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网