PID控制器改进笔记之六:改进PID控制器之参数设定

  前面我们发布了一系列PID控制器相关的文章,包括经典PID控制器以及参数自适应的PID控制器。这一系列PID控制器虽说实现了主要功能,也在实际使用中取得了良好效果,但还有很多的细节部分可以改进以提高性能和灵活性。这篇中我们来讨论改进PID控制器参数设置的问题。

1、问题提出

  在前面的文章中我们曾推导过PID控制器的公式,并且对其进行了离散化以适用于程序实现,具体的离散化公式如下:

  在编写程序时,我们将比例项的系数设定为Kp、积分项的系数设定为Ki、微分项的系数设定为Kd,其中:

  这其中T是采样周期,Ti是积分时间,Td是微分时间。所以在设置参数的时候我们需要先去顶比例系数Kp,然后在根据采样周期和积分微分时间来计算Ki和Kd。这么做虽然是公式变得简单了,但与我们传统的参数设置相比就显得不那么直观,所以有些使用者希望还是以传统的比例带PB、积分时间Ti、微分时间Td来配置相应的参数,这一篇中就来分析并解决这个问题。

2、分析设计

  对于上述这个问题,我们需要搞清楚Kp、Ki、Kd与PB、Ti、Td之间的关系。事实上,它们之间的关系并不复杂。首先比例系数Kp与比例带之间是互为倒数的关系,所以我们知道了其中一个就可以得到另一个。而Ti和Ki的关系以及Td和Kd的关系我们前面已经给出了。

接下来我们需要做的事,实际上就是让我们的PID控制器在不同的应用需求下呈现出不同的参数设置就可以设置不同的参数形式了。

3、软件实现

  我们已经分析了需要实现的内容,接下来我们就来考虑怎么实现。关于这一点,我们考虑我们的PID控制器的设计形式,需要修改的主要是三个方面的内容。第一个需要修改的地方就是PID控制器对象的定义。我们定义一个宏来实现条件编译,以实现在不同的需求下实现不同的参数定义,所以我们实现PID控制器的对象类型定义如下:

/*定义PID对象类型*/
typedef struct CLASSIC
{float *pPV;          //测量值指针float *pSV;          //设定值指针float *pMV;          //输出值指针uint16_t *pMA;        //手自动操作指针\#if PID_PARAMETER_STYLE > (0)float *pKp;          //比例系数指针float *pKi;          //积分系数指针float *pKd;          //微分系数指针
\#elsefloat *pPb;          //比例带float *pTi;          //积分时间,单位为秒float *pTd;          //微分时间,单位为秒float ts;           //采样周期,单位为秒
\#endiffloat setpoint;        //设定值float lasterror;       //前一拍偏差float preerror;        //前两拍偏差float deadband;        //死区float result;         //PID控制器计算结果float output;         //输出值0-100%float maximum;        //输出值上限float minimum;        //输出值下限float errorabsmax;      //偏差绝对值最大值float errorabsmin;      //偏差绝对值最小值float alpha;         //不完全微分系数float deltadiff;       //微分增量float integralValue;     //积分累计量float gama;          //微分先行滤波系数float lastPv;         //上一拍的过程测量值float lastDeltaPv;      //上一拍的过程测量值增量ClassicPIDDRType direct;   //正反作用ClassicPIDSMType sm;     //设定值平滑ClassicPIDCSType cas;     //串级设定}CLASSICPID;

  我们定义了对象类型,可以得到我们需要的对象变量,但这个对象变量需要初始化才能使用。所以第二个需要修改的地方就是PID控制器对象初始化函数。我们使用条件编译,在不同的应用需求下我们初始化不同的对象参数,具体实现如下:

/* PID初始化操作,需在对vPID对象的值进行修改前完成               */
/* CLASSICPID vPID,普通PID对象变量,实现数据交换与保存            */
/* float vMax,float vMin,过程变量的最大最小值(量程范围)          */
void PIDParaInitialization(CLASSICPID *vPID,  //PID控制器对象float *pPV,     //测量值指针float *pSV,     //设定值指针float *pMV,     //输出值指针\#if PID_PARAMETER_STYLE > (0)float *pKp,     //比例系数指针float *pKi,     //积分系数指针float *pKd,     //微分系数指针
\#elsefloat *pPb;          //比例带float *pTi;          //积分时间float *pTd;          //微分时间float ts,      //采样周期,单位为秒
\#endifuint16_t *pMA,    //手自动操作指针float vMax,     //控制变量量程float vMin,     //控制变量的零点ClassicPIDDRType direct,   //正反作用ClassicPIDSMType sm,     //设定值平滑ClassicPIDCSType cas     //串级设定)
{if((vPID==NULL)||(pPV==NULL)||(pSV==NULL)||(pMV==NULL)||(pMA==NULL)){return;}vPID->pPV=pPV;vPID->pSV=pSV;vPID->pMV=pMV;vPID->pMA=pMA;\#if PID_PARAMETER_STYLE > (0)if((pKp==NULL)||(pKi==NULL)||(pKd==NULL)){return;}vPID->pKp=pKp;vPID->pKi=pKi;vPID->pKd=pKd;if(*vPID->pKp<=0.00001){*vPID->pKp=1.0;       //比例系数*vPID->pKi=0.01;       //积分系数*vPID->pKd=0.01;       //微分系数}
\#elseif((pPb==NULL)||(pTi==NULL)||(pTd==NULL)){return;}vPID->pPb=pPb;vPID->pTi=pTi;vPID->pTd=pTd;vPID->ts=ts;if(*vPID->pPb<=0.00001){*vPID->pPb=1.0;       //比例带*vPID->pTi=1.0;       //积分时间,单位为秒*vPID->pTd=0.0001;     //微分时间,单位为秒}
\#endifvPID->maximum=vMax;      //控制变量的量程vPID->minimum=vMin;      //控制变量的零点*vPID->pSV=*pPV;       //设定值vPID->setpoint=*pPV;     //设定值*vPID->pMA=1;         //初始化为自动模式vPID->direct=direct;     //设定PID对象的正反作用vPID->cas=cas;        //设定是否启用串级vPID->sm=sm;         //设定是否启用设定值平滑if(vPID->cas==CASCADE){vPID->sm=SMOOTH_DISABLE;}vPID->lasterror=0.0;     //前一拍偏差vPID->preerror=0.0;      //前两拍偏差vPID->result=vMin;      //PID控制器结果vPID->output=0.0;       //输出值,百分比*vPID->pMV=vPID->output;   //输出值,百分比vPID->errorabsmax=(vMax-vMin)*0.9;vPID->errorabsmin=(vMax-vMin)*0.1;vPID->deadband=(vMax-vMin)*0.001;   //死区vPID->alpha=0.2;   //不完全微分系数vPID->deltadiff=0.0;     //微分增量vPID->integralValue=0.0;}

  第三个需要修改的是PID控制器对象的实现。在前面我们已经描述PB、Ti、Td与Kp、Ki、Kd之间的数学关系。为了方便处理,我们通过条件编译在不同应用需求下将参数均转化为统一的Kp、Ki、Kd来进行计算。具体的实现方式如下:

/* 通用PID控制器,采用增量型算法,具有变积分,梯形积分和抗积分饱和功能     */
/* 微分项采用不完全微分,一阶滤波,alpha值越大滤波作用越强          */
/* CLASSICPID vPID,PID对象变量,实现数据交换与保存              */
/* float pv,过程测量值,对象响应的测量数据,用于控制反馈           */
void PIDRegulator(CLASSICPID *vPID)
{float thisError;float result;float factor;float increment;float pError,dError,iError;float kp,ki,kd;\#if PID_PARAMETER_STYLE > (0)kp=*vPID->pKp;ki=*vPID->pKi;kd=*vPID->pKd;
\#elseif((*vPID->pTi)<vPID->ts){*vPID->pTi=vPID->ts;}kp=1/(*vPID->pPb);ki=kp*(vPID->ts/(*vPID->pTi));kd=kp*((*vPID->pTd)/vPID->ts);
\#endifif(*vPID->pMA<1)   //手动模式{vPID->output=*vPID->pMV;//设置无扰动切换vPID->result=(vPID->maximum-vPID->minimum)*vPID->output/100.0+vPID->minimum;*vPID->pSV=*vPID->pPV;vPID->setpoint=*vPID->pSV;}else          //自动模式{if(vPID->sm==SMOOTH_ENABLE) //设定值平滑变化{SmoothSetpoint(vPID);}else{if(vPID->cas==CASCADE)  //串级处理{vPID->setpoint=(vPID->maximum-vPID->minimum)*(*vPID->pSV)/100.0+vPID->minimum;}else{vPID->setpoint=*vPID->pSV;}}thisError=vPID->setpoint-(*vPID->pPV); //得到偏差值result=vPID->result;if (fabs(thisError)>vPID->deadband){pError=thisError-vPID->lasterror;iError=(thisError+vPID->lasterror)/2.0;dError=thisError-2*(vPID->lasterror)+vPID->preerror;//变积分系数获取factor=VariableIntegralCoefficient(thisError,vPID->errorabsmax,vPID->errorabsmin);//计算微分项增量带不完全微分vPID->deltadiff=kd*(1-vPID->alpha)*dError+vPID->alpha*vPID->deltadiff;increment=kp*pError+ki*factor*iError+vPID->deltadiff;  //增量计算}else{if((fabs(vPID->setpoint-vPID->minimum)<vPID->deadband)&&(fabs((*vPID->pPV)-vPID->minimum)<vPID->deadband)){result=vPID->minimum;}increment=0.0;}//正反作用设定if(vPID->direct==DIRECT){result=result+increment;}else{result=result-increment;}/*对输出限值,避免超调和积分饱和问题*/if(result>=vPID->maximum){result=vPID->maximum;}if(result<=vPID->minimum){result=vPID->minimum;} vPID->preerror=vPID->lasterror; //存放偏差用于下次运算vPID->lasterror=thisError;vPID->result=result;vPID->output=(vPID->result-vPID->minimum)/(vPID->maximum-vPID->minimum)*100.0;*vPID->pMV=vPID->output;}
}

4、总结

  在这一篇中,我们只是为了实现不同使用者的需求,将PID控制器的参数定义做了相应的修改,而控制器本身的功能并没有什么变化。这样既保证原有的应用不会受到影响,新的应用也可以根据需要定义参数,使用Kp、Ki、Kd或是PB、Ti、Td由应用设计的需要选择。

  这里需要说一下,不论我们如何定义参数,采样周期的选择都需要认真考虑。即使我们采用相同的参数整定,当采样周期不同时,效果可能会有较大差异,所以在整定参数前应根据系统的特性采用合适的采样周期。

欢迎关注:

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

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

相关文章

软件设计开发笔记1:基于状态机的程序设计

在编码实现的过程中&#xff0c;我们会经常使用到条件判断结构&#xff0c;而且使用起来很方便。但是在需要转移的状态比较多&#xff0c;或是条件比较复杂时&#xff0c;我们就可能需要很长的条件判断结构来处理。不过&#xff0c;过于复杂的条件判断结构会给代码的编写和维护…

外设驱动库开发笔记38:RTD热电阻测温驱动

我们已经讨论过多种温度检测方式&#xff0c;但我们尚未关注热电阻温度检测&#xff0c;但热电阻测温在工业环境中是非常常见的。尽管有很多集成的数字式的热电阻接口元器件&#xff0c;但这些器件不但成本较高&#xff0c;灵活性也大打折扣。所以我们有时会使用更简单灵活的电…

外设驱动库开发笔记39:按键操作驱动

按键在我们的项目中是经常使用到的组件。一般来说&#xff0c;我们都是在用到按键时直接针对编码&#xff0c;但这样每次都做很多重复性的工作。所以在这里我们考虑做一般性抽象得到一个可应用于按键操作的通用性驱动程序。 1、功能概述 按键操作在我们的产品种经常用到&#…

外设驱动库开发笔记40:AT25xxx外部存储器驱动

我们在前面开发过AT24CXX系列EEPROM存储器&#xff0c;它使用的是I2C接口。不过有时候我们也会使用SPI接口的EEPROM存储器。在这一篇我们将来讨论AT25XXX系列EEPROM存储器的驱动设计、实现及使用。 1、功能概述 AT25XXX系列EEPROM存储器采用SPI接口&#xff0c;因其操作简单且…

闪存中的键值对:无文件系统 minINI

许多嵌入式系统应用需要以持久的方式存储某种数据&#xff1a;校准值、设置或日志信息。对于较少的数据量&#xff0c;使用外部存储器或文件系统是一种过度大材小用。在许多系统中&#xff0c;我使用minINI以“ini-file”的方式存储键值解析&#xff0c;但它需要使用某种文件系…

外设驱动库开发笔记41:ADS1256 ADC驱动

我们经常会碰到多通道AD采集的需求&#xff0c;有时候甚至需要高精度的ADC器件。本篇我们将来设计并实现ADS1256模数转换器的驱动。并简单讨论该驱动使用方式。 1、功能概述 ADS1256是TI公司推出的一款低噪声高分辨率的24位Sigma-Delta(E-v)模数转换器(ADC)。E-vADC与传统的逐…

PID参数自整定库之一:继电反馈整定算法

在前述的篇章中&#xff0c;我们实现了PID控制器并在后续对其进行了改进。但作为经典PID控制器还存在PID参数整定的问题。通常我们可以采取人工整定的办法&#xff0c;但人工整定涉及到比较专业的知识&#xff0c;而且找到合适的参数本身也不是一件容易的事&#xff0c;所以人们…

外设驱动库开发笔记42:DAC8552 DAC驱动

模拟信号输出是经常会遇到的应用需求&#xff0c;解决的办法应多种&#xff0c;但我们使用最多的还是数模转换。对于不同的数模转换器我们需要为其编写适用的驱动程序&#xff0c;在这一篇中我们就来考虑如何实现DAC8552高精度模数转换器的驱动程序。 1、功能概述 该DAC8552是…

软件设计开发笔记2:基于QT设计串口调试工具

串口通信是我们经常会遇到的问题。很多时候当我们设计一个串口应用时&#xff0c;我们希望有一个简便的、可视的方式来验证它。这一篇中我们就来基于QT设计一个串口调试工具。 1、概述 在开始软件设计之前&#xff0c;我们来简略地分析一下这样一个小软件其要包含的主要内容有…

外设驱动库开发笔记43:GPIO模拟SPI驱动

SPI总线是我们常用的串行设备接口&#xff0c;一般情况下我们都会适应硬件SPI接口&#xff0c;但有些时候当硬件端口不足时&#xff0c;我们也希望可以使用软件来模拟SPI硬件接口&#xff0c;特别是要求不是很高的时候。在这一篇中我们将来讨论如何使用GPIO和软件来模拟SPI通讯…

外设驱动库开发笔记44:DDC114 ADC驱动

在产品设计过程中&#xff0c;很多时候都会用到ADC器件&#xff0c;而在一些特殊场合还需要一些特别的ADC器件。我们在这篇中将讨论常用于医疗器件方面的&#xff0c;DDC114这款电流输入ADC&#xff0c;并为其设计一个驱动程序。 1、功能概述 模数转换器DDC114是一款电流输入型…

PID控制器改进笔记之七:改进PID控制器之防超调设定

我们已经设计了PID控制器&#xff0c;并根据实际使用的情况对器进行了诸多的改进。在这一篇中我们将讨论如何改进PID控制器超调的问题。 1、问题提出 在前面的文章中&#xff0c;我们曾推导过增量式PID控制器的公式&#xff0c;并且对其进行了离散化以适用于程序实现&#xff…

软件设计开发笔记3:基于QT的Modbus RTU主站

Modbus是一种常见的工业系统通讯协议。在我们的设计开发工作中经常使用到它。在这一篇中我们将简单实现一个基于QT的Modbus RTU主站上位工具。 1、概述 Modbus RTU主站应用很常见&#xff0c;有一些是通用的&#xff0c;有一些是专用的。而这里我们希望实现一个主要针对我们的…

外设驱动库开发笔记45:MS4515DO压力传感器驱动

很多时候我们需要检测流量和压力这些参数&#xff0c;比如我们要检测大气压&#xff0c;或者通过测量差压来获得输送流体的流量等&#xff0c;都需要用到压力传感器。这一篇我们就来讨论MS4515DO压力传感器的数据获取。 1、功能概述 MS4515DO是TE公司推出的一款基于PCB安装的小…

外设驱动库开发笔记48:MCP4725单通道DAC驱动

在产品设计过程中&#xff0c;我们经常会遇到数模转换的应用需求。在本篇种我们就来讨论一下MCP4725单通道数模转换器的驱动设计与实现。 1、功能概述 MCP4725是一个低功耗&#xff0c;高精度&#xff0c;单通道&#xff0c;12位缓冲电压输出数字到模拟转换器(DAC)与非易失性存…

如何确保不使用动态内存

在许多嵌入式应用程序中&#xff0c;内存分配必须是静态的&#xff0c;而不是动态的。意味着在应用程序中不应使用对malloc()或free()等内容的调用&#xff0c;因为它们可能会在运行时失败&#xff08;内存不足、堆碎片&#xff09;。 但是&#xff0c;当与第三方库甚至 C/C 标…

go 单元测试 testing 打印输出_2020,你需掌握go 单元测试进阶篇

本文说明go语言自带的测试框架未提供或者未方便地提供的测试方案&#xff0c;主要是用于解决写单元测试中比较头痛的依赖问题。也就是伪造模式&#xff0c;经典的伪造模式有桩对象(stub),模拟对象(mock)和伪对象(fake)。比较幸运的是&#xff0c;社区有丰富的第三方测试框架支持…

一文读懂Git工作流

Git是目前最流行的代码管理工具&#xff0c;相信大家也都是在用Git来管理自己团队的源代码。 团队一般为了规范开发&#xff0c;保持良好的代码提交记录以及维护 Git 分支结构清晰&#xff0c;方便后续维护等&#xff0c;都会迫切需要一个比较规范的 Git 工作流。 本文就是在…

xbox360fsd更新游戏封面_游戏类短视频创作指南

一&#xff0e;起步阶段1.内容发布垂直&#xff0c;整体风格一致&#xff0c;选定一个品类的游戏内容风格持续更新注意&#xff1a;冷启动时期不要频繁更换游戏类型2.账号IP化 根据自身风格特色打造独特的风格账号。有利延长账号生命周期&#xff0c;提升粉丝转化率。搞笑、中二…

开发者们都在关注的网站

开发者们都在关注的网站 &#x1f609; 综合类&#xff08;5个&#xff09; 1、GitHub 全球最大的编程开源社区&#xff0c;很多优秀的开源项目都在上边&#xff0c;不知道这个都不要说自己是程序员&#x1f602; 访问地址&#xff1a;https://github.com 2、CSDN 全球最大中…