资讯详情

PID控制算法

1 what? 什么是pid算法

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

2.1 KP控制

2.2KD控制

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。

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

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

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

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

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

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

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

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

        于是,当时的数学家们发明了这一历久不衰的算法——这就是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 比例控制,能较快地克服扰动的影响,作用于输出值较快的场景,但不能很好稳定在一个理想的数值。

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

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

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

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

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

        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;
}

标签: 传感器常开接近开关e2e

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台