PID控制算法

本文深入介绍了PID(比例-积分-微分)控制算法,包括其定义、工作原理和作用。PID算法结合比例、积分和微分控制,用于闭环反馈控制系统,以减小误差并提高稳定性。文章详细阐述了KP、KD和KI参数的意义,并通过C语言展示了PID算法的位式和增量式实现。同时,提供了参数调整的口诀,帮助理解和应用PID控制器。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

1 what?  什么是pid算法

2 why? 为什么要使用pid算法?PID算法的作用?

2.1 KP控制

2.2 KD控制

2.3 KI控制

3 how? 如何使用pid算法(过程+编码)

3.1  口诀

3.2  算法选择

3.3  代码实现


写在前面的话

        最近业务需要,电机控制相关,所以打算对PID算法进行重新学习。本文整理PID算法的概念,作用以及代码实现,有需要的同学可以自己编译文章最后的代码进行试验。        

1 what?  什么是pid算法

        PID即:Proportional(比例)、Integral(积分)、Differential(微分)的缩写。顾名思义,PID控制算法是结合比例、积分和微分三种环节于一体的控制算法,同时,pid算法还是一个闭环反馈控制算法。因此,要求硬件必须具有闭环反馈单元,也就是得有反馈。比如控制电机的转速,就得有测量转速的传感器,并将反馈信号(转速)反馈到控制单元。

        在工业过程中,连续控制系统的理想PID控制算法为:

        

Kp

比例增益,Kp与比例度成倒数关系;

Tt

积分时间常数;

TD

微分时间常数;

u(t)

PID控制器的输出信号;

e(t)

给定值r(t)与测量值之差。

        可以认为,PID的流程本质:通过误差反馈信号控制被控量,而控制器本身就是比例、积分、微分三个环节的效果加和。

2 why? 为什么要使用pid算法?PID算法的作用?

        比如,控制“恒温器”,让水温保持在45℃。

        也许你在想:

                小菜一碟,小于45℃就让它加热,大于45°c就断电,几行代码用STM32 分分钟写出来。

                没错 ! 在场景要求不高的情况下,确实可以这么干~ But! 如果换一种说法,你就知道问题出在哪里了:

        如果控制对象是一辆汽车呢?如果希望汽车的车速保持在 100km/h 巡航,你还敢这样干么?

设想一下,假如汽车的定速巡航电脑在某一时间测到车速是90km/h。

        域控制器接到欠速反馈之后,立刻命令发动机:加速!

结果,发动机那边突然来了个100%全油门,汽车急加速到了130km/h。

        域控制器接到超速速反馈之后,立刻命令制动系统:刹车!

        这样多来几次,地上会多几条胎印,如果车上有人,估计还会留下一摊呕吐物。实际应用过程中,没有一下厂家敢这么干。

        所以,在大多数场合中,用“开关量”来控制一个物理量,就显得比较简单粗暴了。有时候,是无法保持稳定的。因为单片机、传感器不是无限快的,采集、控制需要时间,也就是我们所常说的,滞后性。

        而且,控制对象具有惯性。比如你把恒温器开关拔掉,它的“余热”(即热惯性)可能还会使水温继续升高一小会。

        这时,就需要一种『算法』:

它可以将需要控制的物理量带到目标附近

它可以“预见”这个量的变化趋势

它也可以消除因为散热、阻力等因素造成的静态误差

        于是,当时的数学家们发明了这一历久不衰的算法——这就是PID。

2.1 KP控制

        需要控制定速巡航速度,有它现在的『当前值80km/h』,也有我们期望的『100km/h』。

当『当前值』 <  『目标值』:且两者差距不大时,发动机“轻轻地”加速一下。

当『当前值』 <  『目标值』:速度降低很多,两者差距大,发动机“稍稍用力”加速一下。

当『当前值』 <  『目标值』:要是当速度比目标速度低得多,就让发动机“开足马力”加速,尽快让速度到达巡航速度附近。

当『当前值』 >  『目标值』:这个时候,松开油门,不用加速了。

        这就是P的作用,跟开关控制方法相比,是不是“温文尔雅”了。但是kP越大,调节作用越激进,kP调小会让调节作用更保守。

2.2 KD控制

        仍然是巡航的场景,刚才有了KP的作用。不难发现,只有KP,车速时加速时减速,晃晃悠悠,整个系统不是特别稳定,总是在“抖动”。

设想一个弹簧:现在在平衡位置上。拉它一下,然后松手。这时它会震荡起来。因为阻力很小,它可能会震荡很长时间,才会重新停在平衡位置。

请想象一下:要是把上图所示的系统浸没在水里,同样拉它一下 :这种情况下,重新停在平衡位置的时间就短得多。

        我们需要一个控制作用,让被控制的物理量的“变化速度”趋于0,即类似于“阻尼”的作用。

        因为,当比较接近目标时,P的控制作用就比较小了。越接近目标,P的作用越温柔。什么意思呢,就像巡航速度快接近设定的巡航速度,这个时候,理想速度和实际速度得差值变得更小,再乘上一个kp,这个值得影响变得更小,晃动的程度也变小了。

        但是有很多内在的或者外部的因素,使控制量发生小范围的摆动。

        kD的作用就是让这个小范围摆动的物理量趋于0,只要什么时候,这个量具有了速度,kD就向相反的方向用力,尽力刹住这个变化。

        kD参数越大,向速度相反方向刹车的力道就越强。(反应到车速场景上来说,就是油门的加减)。

2.3 KI控制

        这个参数的话,用巡航场景不是特别好理解,可以借助水温的场景进行理解。

        还是说水温的时,比如冬天你讲恒温器放到东北的室外,仍然要把水温烧到45°c。

        但是有个尴尬的情况:在KP的作用下,水温慢慢升高。直到升高到35℃时,天气太冷,水散热的速度,和P控制的加热的速度相等了。这就尴尬了。

KP这样想:我和目标已经很近了,只需要轻轻加热就可以了。

KD这样想:加热和散热相等,温度没有波动,我好像不用调整什么。

        于是,水温永远只能达到35°c,但这些都只是计算机的逻辑。作为一个有思想,有常识的人类,我们明白,如果要将这水烧到45°c,我们还需要继续加热,可是加热 的力度应该多大呢。

        数学家们设置了一个积分量,只要偏差存在,就不断地对偏差进行积分(累加),并反应在调节力度上。

        这样一来,即使35℃和45℃相差不太大,但是随着时间的推移,只要没达到目标温度,这个积分量就不断增加。系统就会慢慢意识到:还没有到达目标温度,该增加功率啦!

        到了目标温度后,假设温度没有波动,积分值就不会再变动。这时,加热功率仍然等于散热功率。但是,温度是妥妥的45℃。所以说KI的作用就是,减小静态情况下的误差,让受控物理量尽可能接近目标值。

        kI的值越大,积分时乘的系数就越大,积分效果越明显。

3 how? 如何使用pid算法(过程+编码)

3.1口诀

参数整定找最佳,从小到大顺序查,

先是比例后积分,最后再把微分加,

曲线振荡很频繁,比例度盘要放大,

曲线漂浮绕大湾,比例度盘往小扳,

曲线偏离回复慢,积分时间往下降,

曲线波动周期长,积分时间再加长,

曲线振荡频率快,先把微分降下来,

动差大来波动慢,微分时间应加长,

理想曲线两个波,前高后低四比一,

一看二调多分析,调节质量不会低

3.2  算法选择

        最简单的闭环控制只有 P 控制,将当前结果反馈回来,再与目标相比,为正的话,就减速,为负的话就加速。pid 是比例(P)、积分(I)、微分(D)控制算法。但并不是必须同时具备这三种算法,也可以是 PD,PI,甚至只有 P 算法控制。PID 算法的结构

比例控制 P:

        采用 P 比例控制,能较快地克服扰动的影响,作用于输出值较快的场景,但不能很好稳定在一个理想的数值。

        它适用于一阶惯性对象,负荷变化不大,工艺要求不高、如用于压力、液位、串级副控回路,控制要求不高、被控参数允许在一定范围内有余差的场景。如:热水器水位控制等。

比例积分控制(PI):

        比例积分控制也是应用最广泛的控制算法之一。积分能在比例控制的基础上消除余差;

        它适用于被控参数不允许有余差的场景。如:油库供油管流量控制系统等。

比例微分控制(PD):

        微分控制具有超前预判的功能,对于惯性较大的对象,为了使控制及时,常常希望能根据被控变量变化的快慢来控制。响应快,偏差小,能增加系统稳定性,有超前控制作用,可以克服对象的惯性

        微分作用与偏差变化率成比例,即它是根据偏差变化趋势产生控制作用,因而有“预先控制”的性质。俗称超前调节。微分作用的超前特性,只对广义对象的容量滞后有效。而对很大的纯滞后无效。

例积分微分控制规律(PID):

        PID 控制是一种较理想的控制规律,它在比例的基础上引入积分,可以消除余差,再加入微分作用,又能提高系统的稳定性。它适用于控制通道时间常数或容量滞后较大、控制要求较高的场合。如过热蒸汽温度控制,PH值控制等。

3.3 代码实现

        代码的实现过程中,使用了位式和增量式两种算法进行实现,下面的代码直接可以使用,有需要的小伙伴可以自行下载实验,对于仿真的实验后续如果有空也会更新上来。

        其实算法的实现过程,可以根据具体的场景进行变化,比如同一个过程,但是涉及到不同的场景,比如开车:加速+巡航+减速,在不同场景中,我们可以叠加不同的算法,也叫做算法分离。

#include <stdio.h>
#include <stdlib.h>

/*
对于位式和增量式那种算法好:具体场景具体分析
*/

typedef struct
{
    float kp;           // 比例系数
    float ki;           // 积分系数
    float kd;           // 微分系数
    float err_last;     // 上次误差
    float err_sum;      // 误差累计
    float result;
}pid_pos_typedef;

typedef struct
{
    float kp;           // 比例系数
    float ki;           // 积分系数
    float kd;           // 微分系数
    float err_last;     // 上一次的误差
    float err_pree;     // 上二次的误差
    float result;
}pid_delta_typedef;
void pid_delta_init(pid_delta_typedef* pid, float kp, float ki, float kd);
float pid_delta_calc(pid_delta_typedef* pid, float currVal, float objVal);

void pid_pos_init(pid_pos_typedef* pid, float kp, float ki, float kd);
float pid_pos_calc(pid_pos_typedef* pid, float currVal, float objVal);


// 位置式pid算法初始化
void pid_pos_init(pid_pos_typedef* pid, float kp, float ki, float kd)
{
    pid->kp = kp;
    pid->ki = ki;
    pid->kd = kd;
    pid->result = 0;
    pid->err_last = 0;
    pid->err_sum = 0;
}

// 位置式pid算法计算
float pid_pos_calc(pid_pos_typedef* pid, float currVal, float objVal)
{
    float err_c = objVal - currVal;       	// 当前误差
    pid->err_sum += err_c;                  // 误差累计
    pid->result = pid->kp * err_c + pid->ki * pid->err_sum + pid->kd * (err_c - pid->err_last);
    pid->err_last = err_c;
    return pid->result;
}

// 增量式pid算法初始化
void pid_delta_init(pid_delta_typedef* pid, float kp, float ki, float kd)
{
    pid->kp = kp;
    pid->ki = ki;
    pid->kd = kd;
    pid->result = 0;
    pid->err_last = 0;
    pid->err_pree = 0;
}

// 增量式算法计算
float pid_delta_calc(pid_delta_typedef* pid, float currVal, float objVal)
{
    float err_c;            // 当前误差
    float err_p;            // p误差
    float err_i;            // i误差
    float err_d;            // d误差
    float increment;        // 增量

    err_c = objVal - currVal;
    err_p = err_c - pid->err_last;
    err_i = err_c;
    err_d = err_c - 2 * pid->err_last + pid->err_pree;
	
    increment = pid->kp * err_p + pid->ki * err_i + pid->kd * err_d;

    pid->err_pree = pid->err_last;
    pid->err_last = err_c;
    pid->result += increment;
    return pid->result;
}

void mydelay(int ms){
	int i;
	for(;i<1000000*ms;i++)
	{
	}
}

int main(void)
{
	unsigned int i = 0;
	float currVal = 0;				// 当前值
	float objVal = 10;				// 目标值
	pid_delta_typedef pid_delta;
	pid_pos_typedef pid_pos;
	pid_delta_init(&pid_delta, 0.2, 0.001, 0.0001);
	pid_pos_init(&pid_pos, 0.2, 0.001, 0.0001);

	printf("------ Start. \n");
	while(1)
	{
		i++;
		/*从这边可以对比出到第是哪一种算法会更加好*/
		//currVal = pid_delta_calc(&pid_delta, currVal, objVal);
		currVal = pid_pos_calc(&pid_pos, currVal, objVal);
		printf("[%d]  objVal[%f]  currVal:%f \n", i, objVal, currVal);


		if(currVal>9.998990){
			mydelay(100);//方便观察
		}
		if(currVal >= 9.999)
			break;
	}

	printf("------ End. \n");
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

汽车程序猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值