PID控制的理解与具体实现

摘要

比例积分微分控制,简称PID控制,由于其 算法简单、***鲁棒性好***和 可靠性高,被广泛应用于工业过程控制,至今仍有 90% 左右的控制回路具有PID结构。
简单的说,根据给定值和实际输出值构成控制偏差,将偏差按比例积分微分通过线性组合构成控制量,对被控对象进行控制。
        所谓串级控制,就是采用两个控制器串联工作,外环控制器的输出作为内环控制器的设定值,由内环控制器的输出去操纵控制阀,从而对外环被控量具有更好的控制效果。
        万能的PID控制听起来很陌生,但是在我们的生活中随处可见。比如空调的温度控制,无人机的精准悬停,机械手臂的运动系统,人脸跟踪,甚至飞机和火箭的姿态调整等等,都要到这这个最基础的自动控制算法。

一定要认真理解PID控制算法是一种闭环系统!!!

一、概念理解与数学原理

首先我们先不引入过多的复杂概念,先结合一个具体的生活实例来形象生动的讲述PID的各个环节。
        众所周知,某校西区宿舍洗澡是要调控冷热水比例才能达到令人舒服的水温。我有个师兄,现在这位师兄已经放出了冷水,只需要再加入50个单位的热水就可以肆无忌惮的洗澡了。一般地,我们会先设置一个达成目标的时间。比如说5秒,那么这位师兄操纵机械臂旋转热水阀的平均速度是10单位/秒。按照这个速度匀速转动开关5s就可以完成目标了。而这种控制方法叫做***开放回路控制系统***,模式图如下:

这种控制系统的可以理解成是一种流水线的结构,输入是恒定的值,输出也是对应得到的恒定值,且输出值对输入值是没有影响的

然而要知道,机械臂有可能因为沾上了水而容易发生偏移,又或者机械臂转动速度并不是那么精准,是无法达到他洗澡要求的50单位热水。况且这位老同志突然想改成40单位热水或者60单位热水,那么我们又需要重新给这个系统赋予新的输入值,这是件很麻烦的事情,总不能老同志洗澡的时候,我们闯进去计算输入值给机械臂吧!
        在这里我们更需要一种***闭环回路控制系统***。在这样一个系统中,输入值不在是一个恒定的值,而是会受输出值的影响。根据当前的热水总量来重新调整机械臂的转动力,模式图如下:

这就是PID控制的基本模式图

那么这种调整是如何做出来的呢?

我们引入一个误差值来表示当前值与目标值的差值。当这个误差值越大时,机械臂的转动力就越大。并且我们把误差值转动开关的力的关系表示为一个线性关系,同时误差是关于时间的函数
F = K p ∗ e r r o r (1) F=K_p*error\tag{1} F=Kp​∗error(1)
e r r o r = f ( t ) (2) error=f(t)\tag{2} error=f(t)(2)
其中的 kp 是一个系数,由此公式我们可以看出在最初状态时,机械臂旋转力量是最大的,随着误差越来越小,旋转力也逐渐减小,热水量逐渐接近目标值。这就是PID算法中的 “P” 部分,即***比例部分,它表示的是转动速度v与误差error之间的比例关系***。用图像表示即为

同时通过图像,我们也注意到逐渐接近目标值时,虽然转动力最终会为零,但因为存在着 ***“惯性”***,在接近目标时难免会产生一点溢出,产生额外的误差。就好比在目标值处加速度虽然为零,但速度仍然存在,是会超过目标值的。说白了,就是春洲师兄手抖抖抖抖抖抖抖了况且这种振荡误差会因为接近目标值速度过快,即kp过大而愈发明显。 这是我们就需要介绍PID算法中的 ***“D”,算法,即微分算法。***。我们对误差进行微分就是造成震荡溢出的速度,通过这个运算我们可以抵消掉振荡产生的速度。加入微分运算后PID的简易数学表达为
F = K p ∗ e r r o r + K d ∗ d ( e r r o r ) d t (3) F=K_p*error+K_d*{d(error)\over dt}\tag{3} F=Kp​∗error+Kd​∗dtd(error)​(3)
K d = K p ∗ T d (4) K_d=K_p*T_d\tag{4} Kd​=Kp​∗Td​(4)
其中 Kd 为微分系数, Td 为微分时间常数。基本上通过 PD 两种运算我们就能得到一个平稳的图像。

不过细心一些我们就不难发现即使经过了这两种运算,误差也是不能被消除的。随着误差减小,春洲师兄的机械臂转动力也减小,最终会和阻力平衡,这时开关不会在转动,误差仍然存在,也就是说我们还需要抵消掉阻力造成的误差。 在这里,我们通过累积所有时间段的误差,得到一个均值再乘以某个系数来提供给春洲师兄更大的转动力来弥补阻力。数学表达式如下:
F = K p ∗ e r r o r + K d ∗ d ( e r r o r ) d t + K i ∗ ∫ 0 t ( e r r o r ) d t (5) F=K_p*error+K_d*{d(error)\over dt}+K_i*\int_0^t (error)dt \tag{5} F=Kp​∗error+Kd​∗dtd(error)​+Ki​∗∫0t​(error)dt(5)
K i = K p T i (6) K_i={K_p\over T_i}\tag{6} Ki​=Ti​Kp​​(6)
其中 Ti 为积分时间常数,Ki 为积分系数。综合以上六个式子,我们就得到了PID算法完整的数学表达式:
F = K p ∗ e r r o r + K d ∗ d ( e r r o r ) d t + K i ∗ ∫ 0 t ( e r r o r ) d t F=K_p*error+K_d*{d(error)\over dt}+K_i*\int_0^t (error)dt F=Kp​∗error+Kd​∗dtd(error)​+Ki​∗∫0t​(error)dt
K i = K p T i K_i={K_p\over T_i} Ki​=Ti​Kp​​
K d = K p ∗ T d K_d=K_p*T_d Kd​=Kp​∗Td​

简易模式图:

  • 关键的总结

比例作用是针对系统当前误差进行控制,积分作用则针对系统误差的历史,而微分作用则反映了系统误差的变化趋势,这三者的组合是“过去、现在、未来”的完美结合

二、性能指标

其实从上面的概念理解中,我们就很容易通过比例、积分、微分三个环节的作用来衡量这个PID控制是否优越。

  • 比例环节可以控制系统的从最开始到稳定值的时间以及震荡的程度
  • 积分环节可以控制系统在稳定值时与目标值之间的差距
  • 微分环节可以控制系统稳定值调整到目标值所需时间

于是就产生了四个衡量PID系统的指标:

1、上升时间 t r t_r tr​:从最开始到稳定值的时间

2、超调量 σ % \sigma\% σ%:震荡的程度

3、稳态误差 e s s e_{ss} ess​:在稳定值时与目标值之间的差距

4、调节时间 t e t_e te​:稳定值调整到目标值所需时间
我们就是通过这四个指标来衡量一个PID控制系统性能好坏,而影响这些指标的即是PID算法公式中的三个系数。所以我们需要通过对这几个参数进行整定来提高系统的性能。

三、参数选取与整定

我们把目光重新放到PID的数学表达式上,我们不难发现 Kd、Ki的值是和Kp有关的。因此这三个系数的值不是独立的,当Kp值最优时,另外两个不一定就是最优的值。我们寻求的不是局部最优,而是三个系数组合起来的全局最优。

  • Kp使得控制器的输入输出成比例关系,为了尽量减小偏差,同时也为了加快响应速度缩短调节时间,就需要增大Kp。但比例作用过大会使系统有较大惯性,造成溢出

  • Ki作用的引入有利于消除稳态误差,但使系统的稳定性下降。尤其在大偏差阶段的积分往往会使系统产生过大的超调,调节时间变长

  • Kd作用的引入使系统能够根据偏差变化的趋势做出反应,适当的微分作用可加快系统响应有效地减小超调,改善系统的动态特性,增加系统的稳定性。不利之处是微分作用对干扰敏感,使系统抑制干扰能力降低

因此,PID控制器的参数选取必须兼顾动态与静态性能指标要求,只有合理地整定Kp、Ki、Kd三个参数,才能获得比较满意的控制性能。

下面我们介绍正定参数的方法。
        最省事儿的办法就是先增大 Kp ,当能看到明显振荡时,引入Kd并适当减小Kp。如果一直打不到预期值,则引入 Ki并慢慢增大。
        还有一种是正规的方法,临界比例度法(Z-N法)。先只引入比例算法,从大到小逐渐改变调节器的比例度,得到等幅振荡的过渡过程,此时的比例度称为临界比例度,用 δ k {\delta_k} δk​表示。相邻两个波峰间的时间间隔,称为临界振荡周期,用 T K {T_K} TK​表示。用这两个值根据表格通过计算即可求出调节器的三个整定参数。

四、代码的具体实现

鉴于代码量繁杂,以下只展示关键代码部分。我们通过运用PID控制算法来让电机匀速稳定转动 。下面代码参考“平衡小车之家”的测试代码,其中串口的收发数据帧协议是由正点原子提供

1、主函数

u8 flag_Stop=1;    //收发数据的停止标志位
int Encoder;       //编码器脉冲计数
int moto;          //电机PWM变量int main(void){ NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断分组Stm32_Clock_Init(9);      //系统时钟设置函数delay_init();             //延时函数初始化LED_Init();               //初始化LED的GPIO口uart_init(115200);        //串口初始化MOTO_Init();              //初始化电机所接GPIO口pwm_Init(7199,0);         //初始化PWM波Encoder_Init_TIM2();      //初始化定时器TIM3_Int_Init(99,7199);   //初始化中断函数 每10ms一次中断while(1){printf("%d\r\n",Encoder); //通过串口打印脉冲数来形象看到PID控制delay_ms(10);}}

2、串口的初始化与串口收发数据函数


#if EN_USART1_RX   //如果使能了接收
//串口1中断服务程序
//注意,读取USARTx->SR能避免莫名其妙的错误
u8 USART_RX_BUF[USART_REC_LEN];
//接收缓冲区最大存储USART_REC_LEN个字节.
//接收状态
//bit15, 接收完成标志
//bit14, 接收到0x0d
//bit13~0,   接收到的有效字节数目
u16 USART_RX_STA=0;       //接收状态标记   void uart_init(u32 bound){         //GPIO端口设置GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);    //使能USART1,GPIOA时钟//USART1_TX   GPIOA.9GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;   //复用推挽输出GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9//USART1_RX     GPIOA.10初始化GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10  //Usart1 NVIC 配置NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;       //子优先级3NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;         //IRQ通道使能NVIC_Init(&NVIC_InitStructure);    //根据指定的参数初始化VIC寄存器//USART 初始化设置USART_InitStructure.USART_BaudRate = bound;//串口波特率USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;   //收发模式USART_Init(USART1, &USART_InitStructure); //初始化串口1USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断USART_Cmd(USART1, ENABLE);                    //使能串口1 }void USART1_IRQHandler(void)                 //串口1中断服务程序{u8 Res;
#if SYSTEM_SUPPORT_OS       //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.OSIntEnter();
#endifif(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //接收中断(接收到的数据必须是0x0d 0x0a结尾){Res =USART_ReceiveData(USART1); //读取接收到的数据if((USART_RX_STA&0x8000)==0)//接收未完成{if(USART_RX_STA&0x4000)//接收到了0x0d{if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始else USART_RX_STA|=0x8000;  //接收完成了 }else //还没收到0X0D{   if(Res==0x0d)USART_RX_STA|=0x4000;else{USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;USART_RX_STA++;if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收   }      }}          }
#if SYSTEM_SUPPORT_OS   //如果SYSTEM_SUPPORT_OS为真,则需要支持OS.OSIntExit();
#endif
}
#endif

3、初始化定时器2为编码器接口模式

void Encoder_Init_TIM2(void)
{TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;  TIM_ICInitTypeDef TIM_ICInitStructure;  GPIO_InitTypeDef GPIO_InitStructure;RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);//使能定时器2的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//使能PA端口时钟GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1;    //端口配置GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //浮空输入GPIO_Init(GPIOA, &GPIO_InitStructure);                         //根据设定参数初始化GPIOBTIM_TimeBaseStructInit(&TIM_TimeBaseStructure);TIM_TimeBaseStructure.TIM_Prescaler = 0x0; // 预分频器 TIM_TimeBaseStructure.TIM_Period = ENCODER_TIM_PERIOD-1; //设定计数器自动重装值TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;//选择时钟分频:不分频TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_CenterAligned1;TIM向上计数  TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);TIM_EncoderInterfaceConfig(TIM2, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);//使用编码器模式3TIM_ICStructInit(&TIM_ICInitStructure); //把TIM_ICInitStruct 中的每一个参数按缺省值填入TIM_ICInitStructure.TIM_ICFilter = 10;  //设置滤波器长度TIM_ICInit(TIM2, &TIM_ICInitStructure);//根据 TIM_ICInitStruct 的参数初始化外设   TIMxTIM_ClearFlag(TIM2, TIM_FLAG_Update);//清除TIM的更新标志位TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);//使能定时器中断TIM_SetCounter(TIM2,0);//设置TIMx 计数器寄存器值TIM_Cmd(TIM2, ENABLE); //使能定时器
}

4、单位时间读取脉冲数

int Read_Encoder(u8 TIMX)//读取计数器的值
{int Encoder_TIM;//printf("%d\r\n", TIM2->CNT);switch(TIMX){case 2:Encoder_TIM=(short)TIM2->CNT; TIM2 -> CNT=0;break;case 3:Encoder_TIM=(short)TIM3->CNT; TIM3 -> CNT=0; break;case 4:Encoder_TIM=(short)TIM4->CNT; TIM4 -> CNT=0;break;default: Encoder_TIM=0;}return Encoder_TIM;
}

5、定时器3中断函数(对脉冲计数进行处理)

nt Target_velocity=50;  //设定速度控制的目标速度为50个脉冲每10ms
void TIM3_IRQHandler(void)
{if(TIM_GetFlagStatus(TIM3,TIM_FLAG_Update)==!RESET){TIM_ClearITPendingBit(TIM3,TIM_IT_Update);   //===清除定时器1中断标志位Encoder=Read_Encoder(2);                     //取定时器2计数器的值Led_Flash(100);                              //LED闪烁moto=Incremental_PI(Encoder,Target_velocity);    //===位置PID控制器Xianfu_Pwm();Set_Pwm(moto);}
}

6、PID算法核心代码(其实代码很简单)

float Incremental_PI (int Encoder,int Target)
{   float Kp=75,Ki=10;    static float Bias,Pwm,Last_bias;Bias=Encoder-Target;                //计算偏差Pwm+=Kp*(Bias-Last_bias)+Ki*Bias;   //增量式PI控制器Last_bias=Bias;                       //保存上一次偏差 return Pwm;                         //增量输出
}

7、其他辅助实现PID的函数

void Set_Pwm(int moto)//赋值给PWM寄存器
{if(moto>0) AIN1=0,   AIN2=1;else      AIN1=1,   AIN2=0;PWMA=myabs(moto);
}void Xianfu_Pwm(void) //限制幅度的函数{int Amplitude=7100;  //===PWM满幅是7200 限制在7100if(moto<-Amplitude)  moto = -Amplitude;if(moto>Amplitude)   moto =  Amplitude;}int myabs(int a) //取绝对值
{          int temp;if(a<0)  temp=-a;  else temp=a;return temp;
}

我们每隔10ms就将脉冲值通过串口打印出来,并使用一款可以将串口数据绘制成图像的软件(SerialPlot)来形象感受PID。

通过图像 我们不难看到响应时间较短,静差几乎消除,稳定性较好。这是一个性能较为优越的PID系统。

PID控制的理解与具体实现相关推荐

  1. 【自动控制理论(一)】对PID控制的理解

    PID公式 以上是离散PID计算公式,PID的连续和离散,以及离散中的位置和增量,原理上都是完全一样的.一般控制都是离散PID,而且离散的表达形式更加直观,这里只给出离散的形式. PID控制的理解 非 ...

  2. PID控制 通俗理解和简单实践

    目录 简介 PID实战简介 任务:控制机器车的轮胎达到目标转速. 恒定值控制器(BangBang) P-Proportional 比例控制器(P) I-Integral 积分控制器(PI) D-Der ...

  3. PID控制的理解与参数整定

    PID控制器的一般结构 当控制器为比例控制器(P)时,可以减少因扰动而引起的稳态误差,但不能将稳态误差减少到0,增加一项正比于误差的积分项(I)时,可以消除系统的稳态误差,但会影响系统的动态性能,可再 ...

  4. 关于电机双闭环PID控制一些理解

    双闭环结构 目前网上流传的一些关于双闭环的资料有很多我觉得是不对或者不够清楚的,在这边分享一下自己的理解,希望大家也能指点一下. 双闭环的作用 串级控制系统是改善控制质量的有效方法之一,在过程控制中得 ...

  5. 手把手教你看懂并理解Arduino PID控制库——调参改变

    2019独角兽企业重金招聘Python工程师标准>>> 引子 本文将分析<手把手教你看懂并理解Arduino PID控制库>中第三个问题:PID控制参数突变对系统的影响. ...

  6. 理论应用实例水杯_PID理解起来很难?系统讲解PID控制及参数调节,理论加实际才好...

    在实际工程中,应用最为广泛的调节器控制规律为比例.积分.微分控制,简称PID控制,又称PID调节.PID控制器问世至今以其结构简单.稳定性好.工作可靠.调整方便而成为工业控制的主要技术之一. PID调 ...

  7. 参数整定临界比例度实验_PID理解起来很难?系统讲解PID控制及参数调节,理论加实际才好!...

    在实际工程中,应用最为广泛的调节器控制规律为比例.积分.微分控制,简称PID控制,又称PID调节.PID控制器问世至今以其结构简单.稳定性好.工作可靠.调整方便而成为工业控制的主要技术之一. PID调 ...

  8. 模糊PID控制的规则表一点理解

    目录 前言 参考 过程 前言 最近在学习模糊PID控制,对于模糊PID控制的规则表有一点疑惑,然后上网查了一下资料,记录一下. 参考 怎么理解模糊pid控制表?@人间苦旅 过程 模糊PID控制器的输入 ...

  9. 对于pid控制的个人理解 附c语言代码

    一.对于PID控制算法的引入 位式控制算法(二位式)所比较的只有输出值与设定值,输出方式只有两种,用开关量控制控制对象,但是用"开关量"来控制一个物理量,就显得比较简单粗暴了.有时 ...

最新文章

  1. html动态加载js方法,如何通过JavaScript动态加载js
  2. Visual Studio 2008 可扩展性开发(九):总结篇
  3. javascript 数组以及对象的深拷贝方法
  4. 基于malloc与free函数的实现代码及分析
  5. 万亿市场下,电商代运营还需另求“第二曲线”
  6. linux下编译安装ntfs,linux下编译安装ntfs
  7. 安装Debian-9(Stretch)服务器图文教程
  8. jsp学习之路之Myeclipse部署tomcat服务器并实现Hello World一个小网页
  9. Linux 查询股价工具,find 查找工具
  10. 背景透明及引发的文字透明问题
  11. 股基交易额市场份额(VMS)
  12. mysql报错:1194-table “xxx“ is marked as crashed and should be repaired
  13. C++A类继承B C类_长期投资指数基金到底选择A类收费还是C类收费
  14. android socket 长连接_java-socket长连接demo体验
  15. 28款GitHub最流行的开源机器学习项目,推荐GitHub上10 个开源深度学习框架
  16. 梦想照进现实|CSDN 实体奖牌 第五期
  17. 达人评测 i7 13700和i7 12700选哪个
  18. C++上机实验三第2题
  19. git基本命令及核心
  20. pta——出生年,查验身份证(c语言)

热门文章

  1. buuctf——Warmup
  2. 【HackTheBox】 meow
  3. 零基础学习3D游戏建模要美术基础吗
  4. 安装Kali Linux系统 全流程详解
  5. 幼儿教师计算机word知识点,幼儿园教师计算机培训计划
  6. Scala中的集合排序总结
  7. AIC和BIC相关知识
  8. protege 和webprotege使用
  9. c语言coin函数库,Coin Test | C/C++程序员之家
  10. Socket error Event: 32 Error: 10053.