news 2026/6/3 14:01:05

PID控制代码原理简析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
PID控制代码原理简析

1.PID简介

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

开环与闭环

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

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

PID公式与系统框图

比例项

只含有比例项的PID输出值:out(t)=K_p∗error(t)

比例项的输出值仅取决于当前时刻的误差,与历史时刻无关。当前存在误差时,比例项输出一个与误差呈正比的值,当前不存在误差时,比例项输出0;K_p越大,比例项权重越大,系统响应越快,但超调也会随之增加;纯比例项控制时,系统一般会存在稳态误差,K_p越大,稳态误差越小。

积分项

含有比例项和积分项的PID输出值:out(t)=K_p∗error(t)+K_i∗∫error(t)dt

积分项的输出值取决于0~t所有时刻误差的积分,与历史时刻有关。积分项将历史所有时刻的误差累积,乘上积分项系数K_i后作为积分项输出值;积分项用于弥补纯比例项产生的稳态误差,若系统持续产生误差,则积分项会不断累积误差,直到控制器产生动作,让稳态误差消失;K_i越大,积分项权重越大,稳态误差消失越快,但系统滞后性也会随之增加。

微分项

含有比例项、积分项和微分项的PID输出值:out(t)=K_p∗error(t)+K_i∗∫error(t)dt+K_d∗derror(t)/dt

微分项的输出值取决于当前时刻误差变化的斜率,与当前时刻附近误差变化的趋势有关。当误差急剧变化时,微分项会负反馈输出相反的作用力,阻碍误差急剧变化;斜率一定程度上反映了误差未来的变化趋势,这使得微分项具有 “预测未来,提前调控”的特性;微分项给系统增加阻尼,可以有效防止系统超调,尤其是惯性比较大的系统K_d越大,微分项权重越大,系统阻尼越大,但系统卡顿现象也会随之增加

稳态误差

PID稳态误差:系统进入稳态时,实际值和目标值存在始终一个稳定的差值

稳态误差产生原因:纯比例项控制时,在稳态时,如果误差e(t)不为零,那么控制器就有一个固定的输出。但这个输出可能正好等于驱动系统到设定值所需的“力”(例如,维持某个流速所需的阀门开度),此时系统达到平衡,但误差却固定在一个非零值上。

例如:用一个弹簧(比例控制器)拉一个物体到指定位置,如果有摩擦力(负载),弹簧就会被拉长到一个固定长度,物体就停在了目标位置之前。这个“拉长的长度”就是稳态误差。

判断是否会产生稳态误差:给被控对象输入0,判断被控对象会不会自发偏移

判断稳态误差的方向:给被控对象输入0,自发偏移方向即为稳态误差方向

位置式PID和增量式PID

位置式PID由连续形式PID直接离散得到,每次计算得到的是全量的输出值,可以直接给被控对象。

增量式PID由位置式PID推导得到,每次计算得到的是输出值的增量,如果直接给被控对象,则需要被控对象内部有积分功能。

增量式PID也可在控制器内进行积分,然后输出积分后的结果,此时增量式PID与位置式PID整体功能没有区别。

位置式PID和增量式PID计算时产生的中间变量不同,如果对这些变量加以调节,可以实现不同的特性。

2.PID程序实现

实现PID程序可简单分为以下几步:

  1. 确定一个调控周期T,每隔时间T,程序执行一次PID调控
#include "stm32f10x.h" #include "Delay.h" int main(void) { while(1) { /*********/ /*PID调控*/ /*********/ Delay_ms(T); } }
#include "stm32f10x.h" #include "Delay.h" int main(void) { Timer_Init(); while(1) { } } void TIM2_IRQHandler(void) { if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET) { /*********/ /*PID调控*/ /*********/ TIM_ClearITPendingBit(TIM2,TIM_IT_Update); } }
#include "stm32f10x.h" #include "Delay.h" uint8_t Flag; int main(void) { Timer_Init(); while(1) { if(Flag == 1) { Flag = 0; /*********/ /*PID调控*/ /*********/ } } } void TIM2_IRQHandler(void) { if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET) { Flag=1; TIM_ClearITPendingBit(TIM2,TIM_IT_Update); } }
  1. 位置式PID实现

#include "stm32f10x.h" #include "Delay.h" #include "Timer.h" float Target,Actual,Out; //目标值,实际值,输出值 float Kp = ,Ki = ,Kd = ; //比例项,积分项,微分项的权重 float Error0,Error1,ErrorInt, //本次误差,上次误差,误差积分 int main(void) { Timer_Init(); while(1) { Target = 用户指定值; } } void TIM1_UP_IRQHandler(void) { if (TIM_GetITStatus(TIM1, TIM_IT_Update) == SET) { /*每隔时间T,程序执行到这里一次*/ /*PID调控*/ Actual = Encoder_Get(); Error1 = Error0; //获取上次误差 Error0 = Target - Actual; //获取本次误差,目标值减实际值,即为误差值 ErrorInt += Error0; //进行误差积分 Out = Kp * Error0 + Ki * ErrorInt + Kd * (Error0 - Error1); //PID计算 /*输出限幅*/ if (Out > 100) {Out = 100;} //限制输出值最大为100 if (Out < -100) {Out = -100;} //限制输出值最小为100 Motor_SetPWM(Out); //输出值给到电机PWM TIM_ClearITPendingBit(TIM2,TIM_IT_Update); }
  1. 增量式PID实现

#include "stm32f10x.h" #include "Delay.h" #include "Timer.h" float Target,Actual,Out; //目标值,实际值,输出值 float Kp = ,Ki = ,Kd = ; //比例项,积分项,微分项的权重 float Error0,Error1,Error2, //本次误差,上次误差,上上次误差 int main(void) { Timer_Init(); while(1) { Target = 用户指定值; } } void TIM1_UP_IRQHandler(void) { if (TIM_GetITStatus(TIM1, TIM_IT_Update) == SET) { /*每隔时间T,程序执行到这里一次*/ /*PID调控*/ Actual = Encoder_Get(); Error2 = Error1; Error1 = Error0; //获取上次误差 Error0 = Target - Actual; //获取本次误差,目标值减实际值,即为误差值 Out += Kp * (Error0 - Error1) + Ki * Error0 + Kd * (Error0 - 2*Error1 + Error2); //PID计算 /*输出限幅*/ if (Out > 100) {Out = 100;} //限制输出值最大为100 if (Out < -100) {Out = -100;} //限制输出值最小为100 Motor_SetPWM(Out); //输出值给到电机PWM TIM_ClearITPendingBit(TIM2,TIM_IT_Update); }

3.PID算法改进

积分限幅:限制积分的幅度,防止积分深度饱和

积分分离:误差小于一个限度才开始积分,反之则去掉积分部分

变速积分:根据误差的大小调整积分的速度

微分先行:将对误差的微分替换为对实际值的微分

不完全微分:给微分项加入一阶惯性单元(低通滤波器)

输出偏移:在非0输出时,给输出值加一个固定偏移

输入死区:误差小于一个限度时不进行调控

积分限幅

问题描述:位置式控制中,如果执行器因为卡住、断电、损坏等原因不能消除误差,则误差积分会无限制加大,进而达到深度饱和状态,此时PID控制器会持续输出最大的调控力,即使后续执行器恢复正常,PID控制器在短时间内也会维持最大的调控力,直到误差积分从深度饱和状态退出积分限幅

实现思路:对误差积分或积分项输出进行判断,如果幅值超过指定阈值,则进行限制

/*获取实际值*/ Actual=读取传感器(); /*获取本次误差和上次误差*/ Errorl = Error0; Error0 = Target -Actual; /*积分项输出*/ Intout += Ki*Error0; /*积分项输出限幅*/ if(Intout>积分项输出上限){Intout=积分项输出上限;} if(Intout<积分项输出下限){Intout=积分项输出下限;} /*PID计算*/ Out = Kp*Error0 + Ki*Intout + Kd*(Error0-Error1); /*输出限幅*/ if(Out>输出上限){Out=输出上限:} if(out<输出下限){out=输出下限;} /*执行控制*/ 输出至被控对象(Out): 44 45/输出限幅*/ 46 if(out>输出上限){Out=输出上限;】 47 if(out<输出下限){Out=输出下限;】 48 49/执行控制*入 50输出至被控对象(Out):

积分分离

问题描述:定位置控制中,积分项作用一般位于调控后期,用来消除持续的误差,调控前期一般误差较大且不需要积分项作用,如果此时仍然进行积分,则调控进行到后期时,积分项可能已经累积了过大的调控力,这会导致超调积分分离。

实现思路:对误差大小进行判断,如果误差绝对值小于指定阈值,则加入积分项作用,反之,则直接将误差积分清零或不加入积分项作用。

if(Ki != 0) { if(fabs(Error0 < 30) && fabs(Error0 > -30)) { ErrorInt += Error0; } else ErrorInt = 0; } else ErrorInt = 0; Out = Kp * Error0 + Ki * ErrorInt + Kd * (Error0 - Error1);

变速积分

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

实现思路:变速积分是积分分离的升级版,变速积分需要设计一个函数值随误差绝对值增大而减小的函数,函数值作为调整系数,用于调整误差积分的速度或积分项作用的强度

/获取实际值*/ Actua1=读取传感器(); /*获取本次误差和上次误差*/ Errorl = Error0; Erroro = Target - Actual; /*变速积分*/ C=1/(k*fabs(Error0) + 1); /*误差积分*/ ErrorInt += C*Error0; /*PID计算*/ Out = Kp * Error0 + Ki * ErrorInt + Kd * (Error0 - Error1); /*输出限幅*/ if(out>输出上限)(out=输出上限;)】 if(out<输出下限)(Out=输出下限;) /*执行控制*/ 输出至被控对象(Out);

微分先行

问题描述:普通PID的微分项对误差进行微分,当目标值大幅度跳变时,误差也会瞬间大幅度跳变,这会导致微分项突然输出一个很大的调控力,如果系统的目标值频繁大幅度切换,则此时的微分项不利于系统稳定微分先行。

实现思路:将对误差的微分替换为对实际值的微分。

Actual_0 = Actual; Actual += Encoder_Get(); Error1 = Error0; Error0 = Target-Actual; DifOut = - Kd * (Actual_0 - Actual); Out = Kp * Error0 + Ki * ErrorInt + DifOut; if (Out > 100) {Out = 100;} //限制输出值最大为100 if (Out < -100) {Out = -100;} //限制输出值最小为100 Motor_SetPWM(Out); //输出值给到电机PWM

不完全微分

问题描述:传感器获取的实际值经常会受到噪声干扰,而PID控制器中的微分项对噪声最为敏感,这些噪声干扰可能会导致微分项输出抖动,进而影响系统性能不完全微分。

实现思路:给微分项加入一阶惯性单元(低通滤波器)。

/*获取实际值*/ Actual=读取传感器(); /*获取本次误差和上次误差*/ Errorl= Error0; Error0 = Target-Actual; /*误差积分*/ ErrorInt += Error0; /*不完全微分*/ Difout = (1-a)*Kd*(Error0 - Error1)+a*Difout; /*PID计算*/ Out=Kp*Error0+ Ki*ErrorInt+Difout; /*输出限幅*/ if(Out>输出上限){Out=输出上限;} if(Out<输出下限){Out=输出下限;} /*执行控制*/ 输出至被控对象(Out);

输出偏移

问题描述:对于一些启动需要一定力度的执行器,若输出值较小,执行器可能完全无动作,这可能会引起调控误差,同时会降低系统响应速度输出偏移。

实现思路:若输出值为0,则正常输出0,不进行调控;若输出值非0,则给输出值加一个固定偏移,跳过执行器无动作的阶段。

/*获取实际值*/ Actual=读取传感器(); /*获取本次误差和上次误差*/ Error1 = Error0; Erroro = Target -Actual; /误差积分*/ ErrorInt += Error0; /*PID计算*/ Out = Kp * Error0 + Ki * ErrorInt + Kd * (Error0 - Error1); /*输出偏移*/ if (Out > 0) { Out += 偏移值: } else if (Out < 0) { Out -= 偏移值; } else { 0ut=0; } /*输出限幅*/ if(out>输出上限){Out=输出上限;} if(Out<输出下限){Out=输出下限;} /*执行控制*/ 输出至被控对象(Out);

输入死区

问题描述:在某些系统中,输入的目标值或实际值有微小的噪声波动,或者系统有一定的滞后,这些情况可能会导致执行器在误差很小时频繁调控,不能最终稳定下来输入死区。

实现思路:若误差绝对值小于一个限度,则固定输出0,不进行调控。

/获取实际值*/ Actual=读取传感器(); /*获取本次误差和上次误差*/ Errorl = Error0; Error0 = Target - Actual; /输入死区* if(fabs(Error0)<死区阈值) { Out =0; } else { /*误差积分*/ ErrorInt += Error0; /*PID计算*/ Out = Kp * Error0 + Ki * ErrorInt + Kd * (Error0 - Error1); } /*输出限幅*/ if(Out>输出上限){Out=输出上限;} if(Out<输出下限){Out=输出下限;} /*执行控制*/ 输出至被控对象(Out);

4.多环PID

单环PID只能对被控对象的一个物理量进行闭环控制,而当用户需要对被控对象的多个维度物理量(例如:速度、位置、角度等)进行控制时,则需要多个PID控制环路,即多环PID,多个PID串级连接,因此也称作串级PID。

多环PID相较于单环PID,功能上,可以实现对更多物理量的控制,性能上,可以使系统拥有更高的准确性、稳定性和响应速度。

双环PID的程序实现

#include "stm32f10x.h" //Device header #include "Delay.h" #include "Timer.h" /*定义内环变量*/ float InnerTarget,InnerActual,Innerout; float InnerKp= 值,InnerKi= 值,InnerKd=值 : float InnerError0,InnerErrorl,InnerErrorInt; /*定义外环变量*/ float OuterTarget,OuterActual,Outerout; float OuterKp= 值,OuterKi= 值,OuterKd= 值: float OuterError0,OuterErrorl,OuterErrorInt; int main(void) { Timer Init () while (1) { /*用户在此处根据需求写入外环PID控制器的目标值*/ OuterTarget=用户指定的一个值; } } void TIM2 IROHandler (void) { static uint16 t Count1,Count2; if (TIM GetITstatus(TIM2,TIM IT Update)=SET) Count1++; if(Count1>=内环周控时间) Count1 0; /*内环每隔时间T1,程序执行到这里一次*/ /*执行内环PID控制*/ InnerActual=读取内环实际值(): InnerErrorl = InnerError0; InnerErroro = InnerTarget - InnerActual: InnerErrorInt += InnerError0; Innerout = InnerKp * InnerErroro + InnerKi * InnerErrorInt + InnerKd * (InnerError0 - InnerErrorl); if(Innerout>上限){Innerout=上限;) if(Innerout<下限){Innerout=下限;) /*内环PID的输出值作用于执行器*/ 输出至被控对象(Innerout); Count2++; if(Count2>=外环调控时间) { Count2 = 0; /*外环每隔时间T2,程序执行到这里一次*/ /*执行外环PID控制*/ OuterActual=读取外环实际值(): OuterErrorl = OuterError0; OuterErroro = OuterTarget - OuterActual; outerErrorInt += OuterError0; Outerout = OuterKp * OuterError0 + OuterKi * OuterErrorInt + OuterKd *(OuterError0 - OuterErrorl); if(Outerout>上限){Outerout=上限;} if(Outerout<下限){Outerout=下限;} /*外环PID的输出值作用于内环PID的目标值*/ InnerTarget = Outerout; } TIM clearITPendingBit (TIM2,TIM IT Update): }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/29 0:06:11

一文详解大模型微调|如何微调(Fine-tuning)大语言模型?

本文介绍了微调的基本概念&#xff0c;以及如何对语言模型进行微调。 从 GPT3 到 ChatGPT、从GPT4 到 GitHub copilot的过程&#xff0c;微调在其中扮演了重要角色。什么是微调&#xff08;fine-tuning&#xff09;&#xff1f;微调能解决什么问题&#xff1f;什么是 LoRA&…

作者头像 李华
网站建设 2026/5/28 15:54:19

flask基于python的3D打印技术专利分析系统

文章目录摘要项目简介大数据系统开发流程主要运用技术介绍爬虫核心代码展示结论源码文档获取定制开发/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;摘要 该系统基于Flask框架和Python技术栈&#xff0c;构建了一个针对3D打印技术专利数据的分析平…

作者头像 李华
网站建设 2026/5/29 2:40:39

36氪专访预约通道:讲述VibeThinker创业背后的故事

VibeThinker-1.5B&#xff1a;小模型如何在数学与编程推理中“以小博大”&#xff1f; 在大模型动辄千亿参数、训练成本破百万美元的今天&#xff0c;一个仅用不到8000美元训练、参数量只有15亿的AI模型&#xff0c;竟然能在国际数学竞赛和算法题库上击败数十倍于它的对手——这…

作者头像 李华
网站建设 2026/5/29 19:51:59

Docker镜像大小优化实战(从小白到专家的压缩秘技)

第一章&#xff1a;Docker镜像大小优化的核心意义在现代云原生架构中&#xff0c;Docker镜像作为应用交付的核心载体&#xff0c;其体积直接影响部署效率、资源消耗与安全边界。过大的镜像不仅增加拉取时间&#xff0c;还可能引入不必要的依赖和潜在漏洞&#xff0c;因此优化镜…

作者头像 李华
网站建设 2026/5/30 21:14:12

跟我学C++中级篇——取地址操作

一、取地址 在C/C开发中&#xff0c;指针操作既是一个难点&#xff0c;同时也是一个无法绕开的知识点。一个对象的指针&#xff0c;可以说就是一个对象的地址。那么如何取得这个对象指针呢&#xff1f;或者说如何取得对象地址呢&#xff1f;在传统的开发中&#xff0c;开发者可…

作者头像 李华
网站建设 2026/5/28 16:20:26

基于LSTM模型的订单流数据量化交易策略构建

1. 金融市场微观结构与订单流数据特性 1.1 市场微观结构核心要素解析 金融市场微观结构理论关注交易机制如何影响价格形成过程&#xff0c;其核心要素包含订单簿动态、交易发起方特征、流动性供给模式及信息传递效率。在高频交易环境下&#xff0c;每笔交易都携带买卖双方的行…

作者头像 李华