文章目录

  • 引言
  • 一、基本原理
    • 1.1.简介
    • 1.2.开环与闭环
    • 1.3.PID 的公式
      • 1.3.1.比例项(Proportional)
      • 1.3.2.积分项(Integral)
      • 1.3.3.微分项(Differential)
    • 1.4.连续形式与离散形式的 PID 公式
      • 1.4.1.连续形式
      • 1.4.2.离散形式
    • 1.5.位置式 PID 与增量式 PID
      • 1.5.1.位置式
      • 1.5.2.增量式
    • 1.6.程序实现
  • 二、实验
    • 2.1.位置式PID定速控制
    • 2.2.增量式 PID 定速控制
    • 2.3.位置式 PID 定位置控制
    • 2.4.增量式 PID 定位置控制
  • 三、算法改进
    • 3.1.积分限幅
    • 3.2.积分分离
    • 3.3.变速积分
    • 3.4.微分先行
    • 3.5.不完全微分
    • 3.6.输出偏移
    • 3.7.输入死区

引言

在工业领域上,精准性和稳定性是非常重要的,人工的实时调节不能够完全胜任高精度和高稳定性的工作,于是用数学手段替代人工经验,自动校正系统误差(设定值与实际值的偏差)。

一、基本原理

1.1.简介

PID(比例-积分-微分)控制器是一种广泛应用于工业控制系统的反馈控制算法,PID 是一种闭环控制算法,它动态改变施加到被控对象的输出值(Out),使得被控对象某一物理量的实际值(Actual),能够快速、准确、稳定地跟踪到指定的目标值(Target)。PID 是一种基于误差(Error)调控的算法,其中规定:误差=目标值-实际值,PID 的任务是使误差始终为 0,对被控对象模型要求低,无需建模,即使被控对象内部运作规律不明确,PID 也能进行调控。PID 就像驾驶汽车时:

  • P:眼睛看到偏离车道,立即打方向盘修正(现在)
  • I:如果持续偏离,逐渐加大修正力度(过去)
  • D:预判车辆趋势,提前减速或减速防止过度转向(未来)

1.2.开环与闭环

  • 开环(Open Loop):控制器单向输出值给被控对象,不获取被控对象的反馈,控制器对被控对象的执行状态不清楚

在这里插入图片描述

  • 闭环(Closed Loop):控制器输出值给被控对象,同时获取被控对象的反馈,控制器知道被控对象的执行状态,可以根据反馈修改输出值以优化控制

在这里插入图片描述

1.3.PID 的公式

  • 定义误差:

在这里插入图片描述

  • PID输出值:

在这里插入图片描述

下图是 PID 闭环控制图,首先,用户输入目标值给被控对象并通过被控对象反馈出来的实际值算出误差,基于本次误差和历史误差算出输出值给被控对象,实现实际值跟踪目标值:

在这里插入图片描述

1.3.1.比例项(Proportional)

比例项的输出值仅取决于当前时刻的误差,与历史时刻无关。当前存在误差时,比例项输出一个与误差呈正比的值,当前不存在误差时,比例项输出 0,下面式子是只含有比例项的 PID 输出值:

在这里插入图片描述

Kp 越大,比例项权重越大,系统响应越快,但超调也会随之增加,如下图红框所示:

在这里插入图片描述

如果只是纯比例项控制,系统一般会存在稳态误差,Kp 越大,稳态误差越小,实际值与目标值存在恒定偏差,即稳态误差。产生稳态误差的原因:纯比例项控制时,若误差为 0,则比例项结果也为 0。被控对象输入 0 时,一般会自发地向一个方向偏移,产生误差。产生误差后,误差非 0,比例项负反馈调控输出,当调控输出力度和自发偏移力度相同时,系统达到稳态.

1.3.2.积分项(Integral)

积分项的输出值取决于 0 ~ t 所有时刻误差的积分,与历史时刻有关。积分项将历史所有时刻的误差累积,乘上积分项系数 Ki 后作为积分项输出值,用于弥补纯比例项产生的稳态误差,若系统持续产生误差,则积分项会不断累积误差,直到控制器产生动作,让稳态误差消失。下面是含有比例项和积分项的 PID 输出值:

在这里插入图片描述

Ki 越大,积分项权重越大,稳态误差消失越快,但系统滞后性也会随之增加,如下图的红框所示:

在这里插入图片描述

滞后性是实际值靠近目标值的增长速度慢,这样的现象会导致一些需要高平衡性的项目,带来较大的影响。

1.3.3.微分项(Differential)

微分项的输出值取决于当前时刻误差变化的斜率,与当前时刻附近误差变化的趋势有关。当误差急剧变化时,微分项会负反馈输出相反的作用力,阻碍误差急剧变化,斜率一定程度上反映了误差未来的变化趋势,这使得微分项具有 “预测未来,提前调控”的特性。微分项给系统增加阻尼,可以有效防止系统超调,尤其是惯性比较大的系统,下面式子是含有比例项、积分项和微分项的 PID 输出值:

在这里插入图片描述

Kd 越大,微分项权重越大,系统阻尼越大,但系统卡顿现象也会随之增加,如果微分项过大,就会导致比例项和积分项让被控对象启动,微分项却阻止它们。

在这里插入图片描述

上图的红线是目标值,蓝线是实际值,绿线是输出值,当实际值斜率很大的时候,说明变化速度很快,输出值就要反向输出,让实际值的速度减缓。

1.4.连续形式与离散形式的 PID 公式

1.4.1.连续形式

在这里插入图片描述

为了更好的在程序上实现 PID 式子,将积分符号和求导,变成简单的加、减、乘、除形式,因此该式子变为离散形式。

1.4.2.离散形式

比例项不变,积分项原本的意思就是从 0 时刻开始到 t 时刻的误差之和,离散之后就是相当于求曲线下面的面积,如下图所示:每个调控周期为 T,一共有 8 个 T,每个时刻的误差乘以调控周期 T,最后相加。j 是相当于 for 循环里面的变量 i,用于遍历。
在这里插入图片描述

微分项就是在某点求导,离散之后,就是求当前时刻和上一个时刻连线的斜率,如下图的红线,求 5T 时刻的斜率,离散之后还需知道上一次的 4T 时的误差,两点相减之后再除以调控周期 T 即可求得:

在这里插入图片描述

经过以上变化,得出离散的 PID 式子:

在这里插入图片描述

上面式子需要考虑调控周期 T,如果将调控周期 T 直接并入 Ki 和 Kd,即可获得下面式子:

在这里插入图片描述

该式子只需考虑当前误差(error(k)),历史误差(error(j)),上一个误差(error(k-1))三个误差,其中 Kp、Ki 和 Kd 由用户自己设置。

1.5.位置式 PID 与增量式 PID

1.5.1.位置式

位置式 PID 由连续形式 PID 直接离散得到,每次计算得到的是全量的输出值,可以直接给被控对象,例如,阀门控制项目,位置式 PID 每次都会反馈完整的阀门状态。

1.5.2.增量式

增量式 PID 由位置式 PID 推导得到,每次计算得到的是输出值的增量,如果直接给被控对象,则需要被控对象内部有积分功能;增量式 PID 也可在控制器内进行积分,然后输出积分后的结果,此时增量式 PID 与位置式 PID 整体功能没有区别,下面是增量式 PID 的推导:

在这里插入图片描述

当 k = k - 1,再让 out(k) - out(k-1),得到增量式,增量式需要用到当前时刻的误差、上一次的误差和上上次的误差:

在这里插入图片描述

1.6.程序实现

先确定一个调控周期 T,本次实验选择软件定时器设置中断,每中断 一次就是一次调控周期,这样设计,主函数能够运行更多的代码,提高了 CPU 利用率,下面是大致代码实现:

int main(void)
{Timer_Init();while(1){}
}void TIM2_IRQHandler(void)
{if(TIM_GetITStatus(TIM2, TIM_IT_Update) == SET){//每隔时间T,程序执行到该中断服务函数//在该中断服务函数里执行PID调控TIM_ClearITPendingBit(TIM2, TIM_IT_Update);}
}
  • 位置式
float Target, Actual, Out;			//目标值、实际值、输出值
float Kp =, Ki =, Kd =;	//比例项、积分项、微分项
float Error0, Error1, ErrorInt;		//本次误差、上次误差、误差积分int main(void)
{Timer_Init();while(1){//用户在此处根据需求写入PID控制器的目标值Target =;}
}void TIM2_IRQHandler(void)
{if(TIM_GetITStatus(TIM2, TIM_IT_Update) == SET){//每隔时间T,程序执行到该中断服务函数//在该中断服务函数里执行PID调控//获取实际值Actual = 读取传感器();//获取本次误差和上次误差Error1 = Error0;Error0 = Target - Actual;//误差积分(累加)ErrorInt += Error0;//PID计算	Out = Kp * Error0 + Ki * ErrorInt + Kd * (Error0 - Error1);//输出限幅if(Out > 上限){Out = 上限;}if(Out < 下限){Out = 下限;}//执行控制输出至被控对象(Out);TIM_ClearITPendingBit(TIM2, TIM_IT_Update);}
}
  • 增量式:增量式不需要积分累加,但是需要知道上上次的误差,该程序是在控制器内进行积分,也就是输出的 Out 值已经累加好的
float Target, Actual, Out;			//目标值、实际值、输出值
float Kp =, Ki =, Kd =;	//比例项、积分项、微分项
float Error0, Error1, Error2;		//本次误差、上次误差、上上次误差int main(void)
{Timer_Init();while(1){//用户在此处根据需求写入PID控制器的目标值Target =;}
}void TIM2_IRQHandler(void)
{if(TIM_GetITStatus(TIM2, TIM_IT_Update) == SET){//每隔时间T,程序执行到该中断服务函数//在该中断服务函数里执行PID调控//获取实际值Actual = 读取传感器();//获取本次误差、上次误差和上上次的误差Error2 = Error1;Error1 = Error0;Error0 = Target - Actual;//误差积分(累加)ErrorInt += Error0;//PID计算	Out = Kp * (Error0 - Error1) + Ki * Error0 + Kd * (Error0 - 2 * Error1 + Error2);//输出限幅if(Out > 上限){Out = 上限;}if(Out < 下限){Out = 下限;}//执行控制输出至被控对象(Out);TIM_ClearITPendingBit(TIM2, TIM_IT_Update);}
}

二、实验

2.1.位置式PID定速控制

实验目的:使用电位器旋钮修改 Kp、Ki、Kd 和 Target,修改 Kp、Ki、Kd 使得实际值尽量贴合目标值。下面是代码实现:

int main(void)
{/*模块初始化*/OLED_Init();		//OLED初始化Key_Init();			//非阻塞式按键初始化Motor_Init();		//电机初始化Encoder_Init();		//编码器初始化RP_Init();			//电位器旋钮初始化Serial_Init();		//串口初始化,波特率9600Timer_Init();		//定时器初始化,定时中断时间1ms/*OLED打印一个标题*/OLED_Printf(0, 0, OLED_8X16, "Speed Control");OLED_Update();while (1){/*电位器旋钮修改Kp、Ki、Kd和目标值*//*RP_GetValue函数返回电位器旋钮的AD值,范围:0~4095*//* 除4095.0可以把AD值归一化,再乘上一个系数,可以调整到一个合适的范围*/Kp = RP_GetValue(1) / 4095.0 * 2;				//修改Kp,调整范围:0~2Ki = RP_GetValue(2) / 4095.0 * 2;				//修改Ki,调整范围:0~2Kd = RP_GetValue(3) / 4095.0 * 2;				//修改Kd,调整范围:0~2Target = RP_GetValue(4) / 4095.0 * 300 - 150;	//修改目标值,调整范围:-150~150/*OLED显示*/OLED_Printf(0, 16, OLED_8X16, "Kp:%4.2f", Kp);			//显示KpOLED_Printf(0, 32, OLED_8X16, "Ki:%4.2f", Ki);			//显示KiOLED_Printf(0, 48, OLED_8X16, "Kd:%4.2f", Kd);			//显示KdOLED_Printf(64, 16, OLED_8X16, "Tar:%+04.0f", Target);	//显示目标值OLED_Printf(64, 32, OLED_8X16, "Act:%+04.0f", Actual);	//显示实际值OLED_Printf(64, 48, OLED_8X16, "Out:%+04.0f", Out);		//显示输出值OLED_Update();	//OLED更新,调用显示函数后必须调用此函数更新,否则显示的内容不会更新到OLED上Serial_Printf("%f,%f,%f\r\n", Target, Actual, Out);		//串口打印目标值、实际值和输出值//配合SerialPlot绘图软件,可以显示数据的波形}
}void TIM1_UP_IRQHandler(void)
{/*定义静态变量(默认初值为0,函数退出后保留值和存储空间)*/static uint16_t Count;		//用于计次分频if (TIM_GetITStatus(TIM1, TIM_IT_Update) == SET){/*每隔1ms,程序执行到这里一次*/Key_Tick();				//调用按键的Tick函数/*计次分频*/Count ++;				//计次自增if (Count >= 40)		//如果计次40次,则if成立,即if每隔40ms进一次{Count = 0;			//计次清零,便于下次计次/*获取实际速度值*//*Encoder_Get函数,可以获取两次读取编码器的计次值增量*//*此值正比于速度,所以可以表示速度,但它的单位并不是速度的标准单位*//*此处每隔40ms获取一次计次值增量,电机旋转一周的计次值增量约为408*//*因此如果想转换为标准单位,比如转/秒*//*则可将此句代码改成Actual = Encoder_Get() / 408.0 / 0.04;*/Actual = Encoder_Get();/*获取本次误差和上次误差*/Error1 = Error0;			//获取上次误差Error0 = Target - Actual;	//获取本次误差,目标值减实际值,即为误差值/*误差积分(累加)*//*如果Ki不为0,才进行误差积分,这样做的目的是便于调试*//*因为在调试时,我们可能先把Ki设置为0,这时积分项无作用,误差消除不了,误差积分会积累到很大的值*//*后续一旦Ki不为0,那么因为误差积分已经积累到很大的值了,这就导致积分项疯狂输出,不利于调试*/if (Ki != 0)				//如果Ki不为0{ErrorInt += Error0;		//进行误差积分}else						//否则{ErrorInt = 0;			//误差积分直接归0}/*PID计算*//*使用位置式PID公式,计算得到输出值*/Out = Kp * Error0 + Ki * ErrorInt + Kd * (Error0 - Error1);/*输出限幅*/if (Out > 100) {Out = 100;}		//限制输出值最大为100if (Out < -100) {Out = -100;}	//限制输出值最小为100/*执行控制*//*输出值给到电机PWM*//*因为此函数的输入范围是-100~100,所以上面输出限幅,需要给Out值限定在-100~100*/Motor_SetPWM(Out);}TIM_ClearITPendingBit(TIM1, TIM_IT_Update);}
}

2.2.增量式 PID 定速控制

本次实验和位置式定速控制的实验目的一样,下面是中断服务函数代码:

float Target, Actual, Out;			//目标值,实际值,输出值
float Kp, Ki, Kd;					//比例项,积分项,微分项的权重
float Error0, Error1, Error2;		//本次误差,上次误差,上上次误差void TIM1_UP_IRQHandler(void)
{/*定义静态变量(默认初值为0,函数退出后保留值和存储空间)*/static uint16_t Count;		//用于计次分频if (TIM_GetITStatus(TIM1, TIM_IT_Update) == SET){/*每隔1ms,程序执行到这里一次*/Key_Tick();				//调用按键的Tick函数/*计次分频*/Count ++;				//计次自增if (Count >= 40)		//如果计次40次,则if成立,即if每隔40ms进一次{Count = 0;			//计次清零,便于下次计次/*获取实际速度值*//*Encoder_Get函数,可以获取两次读取编码器的计次值增量*//*此值正比于速度,所以可以表示速度,但它的单位并不是速度的标准单位*//*此处每隔40ms获取一次计次值增量,电机旋转一周的计次值增量约为408*//*因此如果想转换为标准单位,比如转/秒*//*则可将此句代码改成Actual = Encoder_Get() / 408.0 / 0.04;*/Actual = Encoder_Get();/*获取本次误差、上次误差和上上次误差*/Error2 = Error1;			//获取上上次误差Error1 = Error0;			//获取上次误差Error0 = Target - Actual;	//获取本次误差,目标值减实际值,即为误差值/*PID计算*//*使用增量式PID公式,计算得到输出值*/Out += Kp * (Error0 - Error1) + Ki * Error0+ Kd * (Error0 - 2 * Error1 + Error2);/*输出限幅*/if (Out > 100) {Out = 100;}		//限制输出值最大为100if (Out < -100) {Out = -100;}	//限制输出值最小为100/*执行控制*//*输出值给到电机PWM*//*因为此函数的输入范围是-100~100,所以上面输出限幅,需要给Out值限定在-100~100*/Motor_SetPWM(Out);}TIM_ClearITPendingBit(TIM1, TIM_IT_Update);}
}

增量式的现象和位置式的现象看起来是一样的,但是在增量式的实验中,当 PID 三个值确定之后,设定一个目标值,再让实际值跟上目标值,最后把 PID 三个值调整为 0,这个时候还有输出值,是因为增量式在程序里式叠加的,如果 PID 三项都为 0,输出值还保留着上一次的输出值,只是不再有变化而已,如下图所示:

在这里插入图片描述

2.3.位置式 PID 定位置控制

实验目的:使用电位器旋钮修改 Kp、Ki、Kd 和 Target,使得实际值转动到目标值,本次实验代码与位置式定速控制实验代码一样,只在中断服务函数里面修改实际值 += 获取编码器速度,因为 += 就是积分表达,速度的积分就是位置。

Actual += Encoder_Get();

有下面图可以看出,纯比例项调节也可以做到尽可能贴合目标值了,绿色的输出线在实际值与目标值变化的时候才有波动,当实际值和目标值重合时输出值为 0。
在这里插入图片描述

2.4.增量式 PID 定位置控制

实验目的:使用电位器旋钮修改 Kp、Ki、Kd 和 Target,使得实际值转动到目标值,本次实验代码与增量式定速控制实验代码一样,只在中断服务函数里面修改实际值 += 获取编码器速度。

Actual += Encoder_Get();

如果将 Ki 调到 0 并快速扭动改变目标值,就会出现实际值与目标值严重偏移的现象:

在这里插入图片描述

增量式定位置控制非常依赖这个积分项,没有积分项的增量式 PID,容易出现实际值和目标值偏移的问题,或者,如果上一次的输出值式错误的,同时后续的 Error 变化很小,则纯比例项调节很难纠正这个错误。此外,增量式的纯比例项调节,需要借助上一次的输出值,这使得即使是纯比例项调节,也会有一点积分项的特性,因此,增量式 PID 最好不要给 Ki 为 0。

三、算法改进

以下修改均在位置式 PID 定位置控制实验里。

3.1.积分限幅

要解决的问题:如果执行器因为卡住、断电、损坏等原因不能消除误差,则误差积分会无限制加大,进而达到深度饱和状态,此时 PID 控制器会持续输出最大的调控力,即使后续执行器恢复正常,PID 控制器在短时间内也会维持最大的调控力,直到误差积分从深度饱和状态退出。例如,用手指抵住正在旋转的转盘,过一段时间再放手,得到的波形如下图所示:
在这里插入图片描述
抵住的时间越久,积分项累积的输出值就越大,恢复到稳定输出值的时间就越久。积分限幅实现思路:对误差积分或积分项输出进行判断,如果幅值超过指定阈值,则进行限制,具体操作如下:

	/*误差积分(累加)*/ErrorInt += Error0;/*积分限幅*/if (ErrorInt > 500) {ErrorInt = 500;}		//限制误差积分最大为500if (ErrorInt < -500) {ErrorInt = -500;}		//限制误差积分最小为-500Out = Kp * Error0 + Ki * ErrorInt + Kd * (Error0 - Error1);

3.2.积分分离

积分项作用一般位于调控后期,用来消除持续的误差,调控前期一般误差较大且不需要积分项作用,如果此时仍然进行积分,则调控进行到后期时,积分项可能已经累积了过大的调控力,这会导致超调。如下图所示,定位置控制实验里,需要实际值尽可能贴近目标值,当目标值改变过快时,就会出现下面情况,积分项一开始正方向累计输出值过多,因此需要负方向输出抵消,拿实例来说明,转盘转动过快,出现转过目标位置的情况,因此需要往回转动回到目标位置。

在这里插入图片描述
在这里插入图片描述
积分分离实现思路:对误差大小进行判断,如果误差绝对值小于指定阈值,则加入积分项作用,反之,则直接将误差积分清零或不加入积分项作用,下面实现积分分离,在获得本次误差和上次误差下面加上一下代码:

	/*误差积分+积分分离*/if (fabs(Error0) < 50)		//如果当前误差值小于指定的阈值{ErrorInt += Error0;		//才进行正常的误差积分}else						//否则,即当前误差值过大{ErrorInt = 0;			//此时不进行积分,同时把误差积分直接清零}Out = Kp * Error0 + Ki * ErrorInt + Kd * (Error0 - Error1);

阈值 50,是通过多次实验得出,如果超过阈值,积分项完全消失,就不会出现超过目标值的情况;如果阈值设定的太大,实验现象的效果不是很好。

3.3.变速积分

如果积分分离阈值没有设定好,被控对象正好在阈值之外停下来,则此时控制器完全没有积分作用,误差不能消除,如下图所示:

在这里插入图片描述

这时可以通过调大阈值的方法来解决,也可以通过变速积分来解决。变速积分简单来说只是在积分和不积分之间加了缓冲区,误差值越大,积分越弱,误差值越小,积分越强,如下图所示:

在这里插入图片描述

代码实现如下所示:

/*定义一个系数C,表示积分的速度,C的值与误差绝对值大小呈反比,误差绝对值越大,积分速度越慢*/float C = 1 / (0.2 * fabs(Error0) + 1);		//根据公式计算得到系数C/*误差积分*/ErrorInt += C * Error0;		//积分的速度由C确定,C的取值范围是0~1Out = Kp * Error0 + Ki * ErrorInt + Kd * (Error0 - Error1);

3.4.微分先行

普通 PID 的微分项对误差进行微分,当目标值大幅度跳变时,误差也会瞬间大幅度跳变,这会导致微分项突然输出一个很大的调控力,如果系统的目标值频繁大幅度切换,则此时的微分项不利于系统稳定。微分项本来就是一个阻尼的作用,当 Kp、Ki 为 0 时,Kd 无论调成多少,按理来说都不会产生输出值,但是现实却是 Kp、Ki 为 0 时,也会有输出值,如下图所示:

在这里插入图片描述
在这里插入图片描述

微分先行实现思路:将对误差的微分替换为对实际值的微分。下面是将微分项修改之后的式子:

在这里插入图片描述

为什么需要加一个负号,因为是对实际值求微分,实际值在上升过程中需要一个反方向的输出才能平缓的到达目标值;而对误差求微分,误差逐渐变小的时候,斜率本来就是负的,因此对实际值求微分,需要添加一个负号。

在这里插入图片描述

下面是实现微分先行的代码:

	float DifOut, Actual1;				//定义一个微分项输出(方便查看波形),上次实际值Actual1 = Actual;			//获取上次实际值Actual += Encoder_Get();	//获取本次实际值/*获取本次误差和上次误差*/Error1 = Error0;			//获取上次误差Error0 = Target - Actual;	//获取本次误差,目标值减实际值,即为误差值/*PID计算,先得到微分项*/DifOut = - Kd * (Actual - Actual1);		//这一句是微分先行的微分项计算公式//计算结果要取负,因为实际值的变化趋势和误差变化趋势相反Out = Kp * Error0 + Ki * ErrorInt + DifOut;

3.5.不完全微分

传感器获取的实际值经常会受到噪声干扰,而 PID 控制器中的微分项对噪声最为敏感,这些噪声干扰可能会导致微分项输出抖动,进而影响系统性能,如下图所示,下图是噪声对 PID 三项的影响:

  • 对 P 项的影响:P 项只取决于当前误差,影响较小
  • 对 I 项的影响:原本是绿色波形下面的面积,受到噪声的影响之后变成红色波形下面的面积了,但是 I 项面积有正负抵消的作用,影响也不是很大
  • 对 D 项的影响:原本绿色的某一时刻的斜率是正的,受到噪声影响之后变成负的,因此受到噪声影响最大的是 D 项

在这里插入图片描述

不完全微分实现思路:可以给 PID 三项都加上滤波器,这样会导致系统响应较慢,产生一定的滞后性;只给微分项加入一阶惯性单元(低通滤波器),这是对输出值反应速度较高的做法。如下图所示:

在这里插入图片描述

下面式子是不完全微分 PID 的微分项输出,上面的是算出当前的 D 项输出:

在这里插入图片描述
在这里插入图片描述
其中加号左边是本次的 D 项输入,右边是上一次的 D 项输出,由 a 来解决那一边的比重较高,该式子整体的意思是本次算的 D 项输入和上次输出的 dout 取平均值。

下面是代码实现:

	/*模拟给实际值添加一些噪声干扰*/Actual += rand() % 41 - 20;		//使用随机数生成函数rand生成随机数,并将结果限定在-20~20的范围内/*定义一个滤波强度系数a,a的取值范围是0~1,值越大表示滤波作用越强*/float a = 0.9;DifOut = (1 - a) * Kd * (Error0 - Error1) + a * DifOut;		//这一句是不完全微分的微分项计算公式Out = Kp * Error0 + Ki * ErrorInt + DifOut;

不完全微分的微分项计算公式中,等号左边是当前的微分项输出,右边的是上一次的微分项输出。

3.6.输出偏移

对于一些启动需要一定力度的执行器,若输出值较小,执行器可能完全无动作,这可能会引起调控误差,同时会降低系统响应速度。例如,启动一个电机,当 PWM 为 2% 时,无法使得电机转动,因此引起了调控误差,如下图所示:

在这里插入图片描述

图中,目标值和实际值相差为 10,差值还是比较大的,可以用 Ki 来减小,但是当转盘停下来了,输出值还是不为 0,这是因为 P 项还有一点输出值来驱动转盘,但是这一点输出值不足以使得转盘转动。输出偏移实现思路:若输出值为0,则正常输出0,不进行调控;若输出值非0,则给输出值加一个固定偏移,跳过执行器无动作的阶段。下面是代码实现:

	/*输出偏移*/if (Out > 0)		//如果输出为正{Out += 6;		//则直接给输出值加上一个固定偏移}else if (Out < 0)	//如果输出为负{Out -= 6;		//则直接给输出值减去一个固定偏移}else				//输出为0{Out = 0;		//输出0}

在代码中,固定偏移量是在现实实验中尝试出来的,多次尝试,看出输出值为多少时,转盘才会启动。

3.7.输入死区

在某些系统中,输入的目标值或实际值有微小的噪声波动,或者系统有一定的滞后,这些情况可能会导致执行器在误差很小时频繁调控,不能最终稳定下来,特别是加上输出偏移的式子,输出值很难达到 0。解决方法是设置一个输入死区:若误差绝对值小于一个限度,则固定输出 0,不进行调控,也就是容忍一定的误差存在。当误差小于设定的值,输出值为 0;若大于设定的值,则有输出值。下面是代码实现:

	if (fabs(Error0) < 5)		//如果误差绝对值比较小{Out = 0;				//则直接输出0,不进行调控}else						//否则,即误差绝对值比较大,下面进行正常的PID调控{/*误差积分(累加)*//*如果Ki不为0,才进行误差积分,这样做的目的是便于调试*//*因为在调试时,我们可能先把Ki设置为0,这时积分项无作用,误差消除不了,误差积分会积累到很大的值*//*后续一旦Ki不为0,那么因为误差积分已经积累到很大的值了,这就导致积分项疯狂输出,不利于调试*/if (Ki != 0)				//如果Ki不为0{ErrorInt += Error0;		//进行误差积分}else						//否则{ErrorInt = 0;			//误差积分直接归0}/*PID计算*//*使用位置式PID公式,计算得到输出值*/Out = Kp * Error0 + Ki * ErrorInt + Kd * (Error0 - Error1);/*输出偏移*/if (Out > 0)		//如果输出为正{Out += 6;		//则直接给输出值加上一个固定偏移}else if (Out < 0)	//如果输出为负{Out -= 6;		//则直接给输出值减去一个固定偏移}else				//输出为0{Out = 0;		//输出0}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/918924.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/918924.shtml
英文地址,请注明出处:http://en.pswp.cn/news/918924.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MyBatis 动态数据源切换在 Spring Boot 环境下的实现方案

第一章 需求背景与技术选型1.1 多数据源场景概述在大型企业级应用中&#xff0c;单一数据库往往无法满足高并发和多业务线的需求&#xff0c;因此需要引入 多数据源 的架构设计。常见的多数据源场景包括&#xff1a;读写分离、多租户、分库分表以及数据源负载均衡等。读写分离&…

PCA降维理论详解

文章目录一、什么是PCA&#xff1f;二、为什么需要降维&#xff1f;三、PCA的数学原理与详细推导视角一&#xff1a;最大化投影方差&#xff08;Maximizing Variance&#xff09;视角二&#xff1a;最小化重构误差&#xff08;Minimizing Reconstruction Error&#xff09;四、…

Android RxJava变换操作符详解

RxJava作为响应式编程在Android开发中的利器&#xff0c;其强大的变换操作符能够帮助我们优雅地处理数据流。本文将深入讲解RxJava中最常用的变换操作符及其实际应用场景。一、RxJava变换操作符概述变换操作符(Transformation Operators)用于对Observable发射的数据序列进行变换…

开源数据发现平台:Amundsen 快速上手指南

Amundsen 是一个数据发现和元数据引擎&#xff0c;旨在提高数据分析师、数据科学家和工程师与数据交互时的生产力。目前&#xff0c;它通过索引数据资源&#xff08;表格、仪表板、数据流等&#xff09;并基于使用模式&#xff08;例如&#xff0c;查询频率高的表格会优先于查询…

【密码学实战】国密SM2算法介绍及加解密/签名代码实现示例

引言 在信息安全领域&#xff0c;密码算法是数据保护的核心基石。2010 年&#xff0c;中国国家密码管理局发布了 SM2 椭圆曲线公钥密码算法&#xff0c;作为国产密码标准的核心成员&#xff0c;它凭借高效安全的特性&#xff0c;逐步替代 RSA 等国际算法&#xff0c;广泛应用于…

QT开发中如何加载第三方dll文件

文章目录&#x1f527; 一、隐式加载&#xff08;静态链接&#xff09;操作步骤&#xff1a;⚙️ 二、显式加载&#xff08;动态链接&#xff0c;推荐使用QLibrary&#xff09;操作步骤&#xff1a;&#x1f4bb; 三、直接调用Windows API&#xff08;仅Windows&#xff09;⚠️…

后端学习资料 持续更新中

数据库&#xff1a; 该网址包含&#xff1a;图解MySql&#xff0c; 看明白谁也问不倒你~ 图解计算机网络、操作系统、计算机组成、MySQL、Redis&#xff0c;让天下没有难懂的八股文&#xff01;https://xiaolincoding.com/

《嵌入式Linux应用编程(六):并发编程基础:多进程exec函数族及多线程基础》

一、exec函数族在一个进程里面执行另一个文件本质&#xff1a;将文本区的指令代码替换成exec要执行的指令#include <unistd.h>参数&#xff1a;path:要执行的可执行文件的路径和名称arg:执行该可执行文件时需要传递的参数NULL&#xff1a;参数传递结束标志 返回值&#x…

【121页PPT】智慧方案智慧综合体智能化设计方案(附下载方式)

篇幅所限&#xff0c;本文只提供部分资料内容&#xff0c;完整资料请看下面链接 https://download.csdn.net/download/2501_92808859/91654007 资料解读&#xff1a;【121页PPT】智慧方案智慧综合体智能化设计方案 详细资料请看本解读文章的最后内容 一、项目概述与智能化总…

Linux网络基础(一)

目录 计算机网络背景 网络发展 初识 "协议" 网络协议初识 协议分层 软件分层的好处 打电话例子 OSI七层模型 TCP/IP五层(或四层)模型 参考资料 再识协议 为什么要有 TCP/IP 协议&#xff1f; 什么是 TCP/IP 协议&#xff1f; TCP/IP 协议与操作系统的关系(宏观上&…

MySQL多表查询案例

多表查询本文介绍了多表查询中的表关系概念和操作方法。主要内容包括&#xff1a;1.三种表关系类型&#xff08;一对多、多对多、一对一&#xff09;及其实现方式&#xff1b;2.多表查询的四种连接方式&#xff08;内连接、左外连接、右外连接、自连接&#xff09;及语法&#…

Dify 从入门到精通(第 36/100 篇):Dify 的插件生态扩展

Dify 从入门到精通&#xff08;第 36/100 篇&#xff09;&#xff1a;Dify 的插件生态扩展 Dify 入门到精通系列文章目录 第一篇《Dify 究竟是什么&#xff1f;真能开启低代码 AI 应用开发的未来&#xff1f;》介绍了 Dify 的定位与优势第二篇《Dify 的核心组件&#xff1a;从…

【已解决】在Spring Boot工程中,若未识别到resources/db文件夹下的SQL文件

在Spring Boot工程中&#xff0c;若未识别到resources/db文件夹下的SQL文件&#xff0c;通常与资源路径配置、构建工具设置或代码加载方式有关。以下是逐步排查和解决方案&#xff1a;​​1. 确认SQL文件存放路径​​Spring Boot默认从类路径&#xff08;classpath:&#xff09…

【Java】网络编程(4)

1. 再谈 UDP 报文长度&#xff1a;也是 2 个字节&#xff0c; 0 - 65535&#xff0c;也就是 64 kb。这表示一个 UDP 数据包一次最多只能传输 64 kb 的数据校验和&#xff1a;验证数据是否在传输过程中发生修改。数据在传输过程中可能受到信号干扰&#xff0c;发生 “比特翻转”…

QT(事件)

一、事件前言事件是QT的三大机制之一&#xff0c;一定程度上信号和槽也属于事件的一种 QT中的事件指哪些&#xff1a;窗口关闭&#xff0c;窗口显示&#xff0c;敲击键盘&#xff0c;点击鼠标左键、鼠标右键、鼠标滚轮&#xff0c;文件拖放等等1、事件循环QT中的所有事件&#…

基于 Vue2+Quill 的富文本编辑器全方案:功能实现与样式优化

在 Web 开发中&#xff0c;富文本编辑器是内容管理系统、博客平台等应用的核心组件。本文将详细介绍如何基于 Vue 和 Quill 构建一个功能完善、样式精美的富文本编辑器&#xff0c;重点解决字体字号选项冗长、样式不美观及功能完整性问题&#xff0c;提供可直接部署使用的完整方…

C#内嵌字符串格式化输出

内嵌字符串格式输出 double speedOfLight 299792.458;System.Globalization.CultureInfo.CurrentCulture System.Globalization.CultureInfo.GetCultureInfo("nl-NL"); string messageInCurrentCulture $"The speed of light is {speedOfLight:N3} km/s.&quo…

ThreeJS程序化生成城市大场景底座(性能测试)

一、简介基于矢量geojson数据构建建筑、植被、道路等&#xff0c;实现城市场景底座。涉及渲染的性能优化无非就是众所周知的那些事儿。视锥剔除、mesh合并、减少draw call、四叉树、八叉树、数据压缩、WebWorker、着色器优化等。下面是对东莞市数十万建筑以及海量3D树的渲染测试…

​电风扇离线语音芯片方案设计与应用场景:基于 8 脚 MCU 与 WTK6900P 的创新融合

​电风扇离线语音芯片方案设计与应用场景&#xff1a;基于 8 脚 MCU 与 WTK6900P 的创新融合一、引言在智能家居领域蓬勃发展的当下&#xff0c;用户对于家电产品的智能化和便捷性需求日益增长。传统的电风扇控制方式&#xff0c;如按键操作或遥控器控制&#xff0c;在某些场景…

(第四篇)spring cloud之Consul注册中心

目录 一、介绍 二、安装 三、整合代码使用 1、创建服务提供者8006 2、创建服务消费者80 3、Eureka、zookeeper和consul的异同点 一、介绍 Consul 是一套开源的分布式服务发现和配置管理系统&#xff0c;由 HashiCorp 公司用 Go 语言开发。它提供了微服务系统中的服务治理…