很早都想写一栏关于PID算法的专栏,整个大学期间把谈恋爱的时间都拿来搞PID算法了(这样你们还不信我是真的搞PID的嘛。。)。为了学习PID算法买过平衡小车之家的平衡车(最后拆成玩具了),买过正点原子的minifly(卖掉了)。做过2015年电赛风力摆,研究过2017年电赛板球系统(机械结构太难搭了就写了写代码),准备了这么久就是为了在电赛上拿个国奖,结果2019年电赛把控制类和电源类结合到了一起(电磁炮),而且控制方式很幼稚,完全不用闭环控制。最后折腾了好几天做了个残次品,也是自己学艺不精,拿了个省一(河南赛区)。

出来实习接手的第一个项目是扫地机器人,也是利用PID驱动,结合上层H6芯片,LSLAM算法和路径规划,去完成室内导航的目的,冥冥之中自有天意~以前一直不写是怕自己写不好,现在随便了,就当是记录一下自己的PID学习历程,希望对需要的人有所帮助。

什么是PID算法?

本来不想写这一步的,感觉每个讲PID的都来一遍,有点啰嗦。但是想到我刚开始学PID的时候,也是对着什么是PID看了半天。这里分享一下自己的理解吧。

一个人在操场跑步,在起点开始跑,在终点停下。为什么人能停在终点呢?凭感觉吗?更多的还是用眼睛看吧,当眼睛看见终点线,给脑子说,“快到啦快到啦”,脑子给腿说,“慢点儿慢点儿”,腿慢了下来,到终点线的时候刚好停下。

这其中就蕴含了PID算法的思想,传感器采集数据(眼睛),反馈给MCU(大脑),大脑判断距离终点线的距离越来越近(误差越来越小),告诉腿(执行机构)快到了慢一点,腿(执行机构做出反应)慢了下来,到终点刚好停下(无超调量)。

这就是一个最基本的PID模型,无论其他多么复杂的PID算法,本质上都是基于这个模型来做的拓展和延申。官方一点儿就是,PID算法是对误差的处理,使误差逐渐趋近于0的一种算法。换句话说,就是PID算法会使得系统向期望的方向发展,这句话比较绕口,但是确实是对PID比较好的一个总结了。

P <—> 比例控制<—>对当前状态的处理<—>提高响应速度,过大则无静差
I <—> 微分控制<—>对过去状态的处理<—>用于减小静差
D <—> 积分控制<—>对将来状态的预测<—>用于抑制震荡

判断PID算法优劣的三种标准

①.最大超调量:响应曲线最大峰值与稳态值的差,是系统稳定性的重要标准
②.上升时间:响应曲线从原始状态出发,第一次到达输出稳态值所需的时间,是评估系统快速性的一个重要标准
③.静差:是被控量的稳定值与给定值之差,一般用于衡量系统的准确性。
本来想网上作图给大家看,嫌麻烦,直接上我手写的笔记:

可以看到当我们改变了PID的参数时,系统的响应速度和响应时间都会有所变换,所谓PID调参,就是找到一条最符合自己所需系统的曲线,仅此而已。顺着这个图,讲一下I环和D环的作用把。
P环很好理解,我们将误差乘以P值加在执行机构上面,当误差越小时P环所得值也就越小,给到执行机构的值也就越小,执行机构反应也就越慢,很符合这种闭环反馈的思想。但是当有一种情况,误差很小的时候P环得出的值给到执行机构上面不足以驱动执行机构工作,这时系统距离期望值仍有一段距离,该距离便被称为静差。
I环的作用是对过去状态的累加,本质上是对误差的积分,当系统存在静差时,I环会持续累加静差值,直到累加值大到足以驱动执行机构工作。这是I环的优点,可以有效地处理静差。但是I环本质上是对过去状态的处理,所以加上I环以后整个系统会变得有些不可控制,这里可以采用积分分离式的思想,只有当误差小于一定范围才开始进行进行误差的累加。如果是对稳定性要求比较高的系统,比如平衡小车直立环,就尽量不要用I环,单PD控制器控制即可。
D环,这个环比较的神奇,它可以预测将来,本质上是对误差的微分。也可以理解成对系统的阻尼,打个比方,在一个理想的环境下有个单摆一直在摆动,它摆动的波形是个正弦波形。如果不加阻尼那么它便可以一直摆动,但是现实中总会有各种各样的阻尼作用迫使单摆运动停下来。D环起的就是这个作用。D环一般运用在角度环比较多,因为你不可以进入弯道才想到转弯,而是在进入弯道之前就要有这个转弯的意识。做智能车的小伙伴们注意了!!

PID调参

PID调参其实蛮看经验的,并没有一个万能的标准或者公式,调参之前我们需要根据自己的系统估计参数大概的范围,从P开始调起。比如我们用PWM控制电机那么PWM的范围肯定知道吧,传感器采集数据一般是编码器,每隔一段时间对编码器采样一次便可以得到速度,得到的速度和期望的速度之间的差值我们也能估计对吧,算个大概让P环乘以误差得出的数足够驱动电机就够了。然后根据现象,先找对趋势,震荡太大就减小P值,响应太慢就增加P值。最终取到一个让系统有向期望方向变化的趋势,并且震荡在可接受的范围之内的P值就好了。
然后开始上I环,I环是对过去状态的处理,当系统的期望(希望传感器达到的值)与实际(传感器采集的值)过大时,I环的值很容易偏大,导致系统过冲,所以用I环我们一般会做一个积分分离式,即只有当误差小于一定的值,我们才开始累加偏差,否则清零即可。如果是做平衡车四轴之类的,I一般取P的1/200就好了,别的系统自己慢慢测试,找到一个比较理想的状态即可。注意上I环的时候可以把P环乘以0.8,经验之谈!
PI控制器可以满足大部分的控制系统了,并不是非要三个环全上才是好的。对于D环如超调量果我们对响应速度没有太大的要求,希望最大超调量尽可能地小或者没有,可以上个D环,响应慢点儿但是准确。
最后说一点,限幅是个好习惯,对积分累加限幅,对输出限幅都可以增加系统的稳定性,防止过冲等不良后果。

PID模板!!!

说了这么多,连个公式也没上。。其实是懒得操作,这里给大家一个更加福利的方式,直接上代码,做成了模板样式,改成自己需要的样式就能用。
位置式PID,即最原始的PID算法,做定量控制非常好用(比如控制直流减速电机旋转90°,舵机+陀螺仪做个摄像仪自稳器)代码如下:

#include "control.h"//先定义采集到的变量,一般是传感器采集的数据,用于误差的采集值填充。
float roll,pitch,raw;//在定义位置PID算法需要用到的变量。(variable是你自己采集的数据)
float err_variableA(e(k)),err_variableA_prev(e(k)-1),err_variableA_last(e(k)-2);
float kp_variableA,ki_variableA,kd_variableA;
float err_variableA_sum;
float PWMA;float err_variableB(e(k)),err_variableB_prev(e(k)-1),err_variableB_last(e(k)-2);
float kp_variableB,ki_variableB,kd_variableB;
float err_variableB_sum;
float PWMB;
......//定义我们的PID设定值。(即用测量值减去设定值,即为我们需要的err。)
u8 Set_variableA_angle = 0;
u8 Set_variableB_angle = 0;//在中断里面,我们去选择任务(通过蓝牙模块。)
int ****_IRQHandler(void) (可以为线中断,如MPU6050,也可以如定时器中断。)
{if(EXTI_GetITStatus(EXTI_Line4) != RESET){    TASK1_Start(); }EXTI_ClearITPendingBit(EXTI_Line4);
}void TASK1_Start()
{kp_variableA = 10 , ki_variableA = 0.01 , kd_variableA = 100;err_variableA = pitch - Set_variableA_angle ;err_variableA_sum += err_variableA ;if( err_variableA_sum  >  5000 ) err_variableA_sum  =  5000;if( err_variableA_sum  < -5000 ) err_variableA_sum  = -5000;PWMA = kp_variable * err_variableA +  ki_variable * err_variableA_sum + kd_variable * (err_variableA - err_variableA_prev);if(PWMA >  800) PWMA =   800;if(PWMA < -800) PWMA = - 800;err_variableB_last = err_variableA_prev ;err_variableA_prev = err_variableA;Motor1_Start(PWMA);//这一步就是看这个位置式PID的值是给到哪个驱动/电机上面,去操控元器件了。
}

这里因为我是是做风力摆时写的模板,直接用了MPU6050的自己的中断(根据采样率大概是5ms产生一次线中断),大家的工程里如果上RTOS的话可以开个任务扫描,没有的话就直接开个定时器。
为什么要这样做呢?是利用了高采样率把离散的数字信号趋近于模拟信号给到执行机构,这样做的话可以保证系统的连续性和稳定性。
整体的控制光看TASK1_Start()这个任务就好了,我在中断里加入了蓝牙的扫描,利用蓝牙改变期望去改变系统的运行状况。
在Motor1_Start()这个任务里面其实就是改变PWM的占空比从而改变电机的转速,还要做一个正负的判断改变电机旋转的方向。

增量式PID,增量式PID是在原始的位置式PID数学推导过来的,它一般用在控制电机以恒定转速前进,反映到传感器上就是编码器的值能保持恒定,并且被外界影响较小。

float kp_variableA,ki_variableA,kd_variableA;
float err_variableA_sum;
float PWMA;
float err_variableB(e(k)),err_variableB_prev(e(k)-1),err_variableB_last(e(k)-2);
float kp_variableB,ki_variableB,kd_variableB;
float err_variableB_sum;
float PWMB;
......
//定义我们的PID设定值。(即用测量值减去设定值,即为我们需要的err。)
u8 Set_variableA_angle = 0;
u8 Set_variableB_angle = 0;u8 a = 0,b = 0,c = 0,d = 0,e = 0;//给蓝牙用的
extern u8 Question_Flag;//在中断里面,我们去选择任务(通过蓝牙模块。)
int ****_IRQHandler(void) (可以为线中断,如MPU6050,也可以如定时器中断。)
{if(EXTI_GetITStatus(EXTI_Line4) != RESET){TASK1_Start();}EXTI_ClearITPendingBit(EXTI_Line4);
}void TASK1_Start()
{kp_variableA = 10 , ki_variableA = 0.01 , kd_variableA = 100;err_variableA = pitch - Set_variableA_angle ;err_variableA_sum += err_variableA ;Sif( err_variableA_sum  >  5000 ) err_variableA_sum  =  5000;if( err_variableA_sum  < -5000 ) err_variableA_sum  = -5000; PWMA += kp_variableA * (err_variableA - err_variableA_prev) + ki_variableA * err_variableA + kd_variableA * (err_variableA - 2 * err_variableA_prev + err_variableA_last)if(PWMA >  800) PWMA =   800;if(PWMA < -800) PWMA = - 800;err_variableA_last = err_variableA_prev ;err_variableA_prev = err_variableA;Motor1_Start(PWMA);//这一步就是看这个增量式PID的值是给到哪个驱动/电机上面,去操控元器件了。
}

这就是增量式PID的模板,具体的数学推到其实挺简单的,有兴趣可以了解下。早PWMA中我们可以看到,增量式PID和本次误差,上次误差,上上次误差都有关系,所以增量式PID的系统更不容易被外界所干扰,能保持一种惯性。

串级PID,串级PID的本质是将一个环的输出作为另一个环的期望,这样的话可以保证内环的稳定性。串级PID一般用在两个环具有相关性的场景,比如说平衡小车或者四轴,改变其速度(外环),那么我的倾斜角度(内环)的期望也会随之改变,整个系统还是在一个稳定的情况下前进后退左转右转。如果两个环没什么太大的关联性,并不建议用串行PID,就算调了个勉强能用的参也没什么意义。
下面上模板:

#include "control.h"
//因为我们要用两个环,外环为速度环,内环为角度环,速度环由编码器M法测出赋值给speed
//内环由mpu6050测出数据进行数据融合得出角度,从而得出倾斜角度赋值给angle
float speed,angle;
//定义一些PID算法需要用到的参数
float kp_speed,ki_speed,kd_speed;
float kp_angle,ki_angle,kd_angle;float err_speed,err_speed_prev,err_speed_last,err_speed_sum;
float err_angle,err_angle_prev,err_angle_last,err_angle_sum;float PWM_speed,PWM_angle;
u8 Set_speed_value = 0,Set_angle_value = 0;
//然后先写对应的中断函数(线中断/定时器中断)
u8 a = 0,b = 0,c = 0,d = 0,e = 0;extern u8 Question_Flag;int ****_IRQnHandler(void)
{if(****_GetITStatus(****_Line*) != RESET){TASK1_Start();}****_ClearITPendingBit(****_Line*);
}
//先写外环速度环PID控制函数
float Speed_PID()
{kp_speed = 10,ki_speed = 0.1,kd_speed = 100;err_speed = speed - Set_speed_value;err_speed_sum += err_speed;if(err_speed_sum >  5000) err_speed_sum =  5000;if(err_speed_sum < -5000) err_speed_sum = -5000;PWM_speed = kp_speed * err_speed + ki_speed * err_speed_sum + kd_speed * (err_speed - err_speed_last);if(PWM_speed >  800)  PWM_speed =  800;if(PWM_speed < -800)  PWM_speed = -800;err_speed_last = err_speed_prev;err_speed_prev = err_speed;return PWM_speed;
}
//在写内环角度环PID,注:
void TASK1_Start()
{float err_out_speed;kp_angle = 30,ki_angle = 0.01,kd_angle = 500;err_out_speed = Speed_PID();err_angle = angle - err_out_speed;err_angle_sum += err_angle;if(err_angle_sum >  5000)err_angle_sum =  5000;if(err_angle_sum < -5000)err_angle_sum = -5000;PWM_angle = kp_angle * err_angle + ki_angle * err_angle_sum + kd_angle * (err_angle - err_angle_last);if(PWM_angle >  800)  PWM_angle =  800;if(PWM_angle < -800)  PWM_angle = -800;err_angle_last = err_angle_prev;err_angle_prev = err_angle;Motor1_Start(PWM_angle);
}

至此,三个常用的模板已经给大家介绍完毕了,学习PID光看是没用的,自己去构建个系统,调调参数什么的。我刚开始学的时候,拿了个陀螺仪(MPU6050)拿了个舵机,不到一天就调出来了一个比较完美的自稳系统,后面会把以前参加比赛或者做的项目代码放出来一起学习!

PID算法原理及模板讲解相关推荐

  1. PID控制器开发笔记之一:PID算法原理及基本实现

           在自动控制中,PID及其衍生出来的算法是应用最广的算法之一.各个做自动控制的厂家基本都有会实现这一经典算法.我们在做项目的过程中,也时常会遇到类似的需求,所以就想实现这一算法以适用于 ...

  2. 【转】PID算法原理 一图看懂PID的三个参数

    这是目前发现写的最好的了,原文作者DF创客社区virtualwiz 以下为原文: LZ以前有个小小的理想,就是让手边的MCU自己"思考"起来,写出真正带算法的程序. 前段时间做一个 ...

  3. python之多继承广度优先C3算法原理通俗易懂的讲解

    python多继承比较复杂,python2的多继承查找顺序是深度优先,pyhon3的多继承查找顺序是采取C3算法的广度优先. C3算法原理: python解释器每遇到一个类就会按广度优先的原则将其父类 ...

  4. PID算法原理,调整规律及代码

    文章转载出处:http://www.51hei.com/bbs/forum.php?mod=viewthread&tid=30511&extra=page%3D1%26filter%3 ...

  5. C++ 十大经典排序算法原理及模板之STL方法实现以及稳定性分析

    写在前面: 1.本文中默认排序为升序,降序的原理类似. 2.如果程序直接复制到vs出现无法识别标记的问题,解决方法在这:vs无法识别标记的解决方法 3.本文的算法都是自己用stl实现的,疏漏之处还请指 ...

  6. 社区发现算法原理与louvain源码解析

    前言 社区发现(community detection),或者社区切分,是一类图聚类算法,它主要作用是将图数据划分为不同的社区,社区内的节点都是连接紧密或者相似的,而社区与社区之间的节点连接则是稀疏的 ...

  7. 浅谈动态调节PID算法

    啥是PID? PID,就是"比例(proportional).积分(integral).微分(derivative)",是一种很常见的控制算法. PID已经有107年的历史了 它并 ...

  8. PID 算法参数及调节

    我学习PID参数意义,参考了以下文章: 一文读懂PID控制算法(抛弃公式,从原理上真正理解PID控制) PID算法原理 一图看懂PID的三个参数 PID算法实现及参数整定图解(附代码) https:/ ...

  9. java a星寻路算法_用简单直白的方式讲解A星寻路算法原理

    很多游戏特别是rts,rpg类游戏,都需要用到寻路.寻路算法有深度优先搜索(DFS),广度优先搜索(BFS),A星算法等,而A星算法是一种具备启发性策略的算法,效率是几种算法中最高的,因此也成为游戏中 ...

最新文章

  1. Java: 面向对象程序设计(上)
  2. CString截取字符串全攻略
  3. RMAN之一:快速入门
  4. JavaApplet 绘制火柴棒和轮播图片
  5. jconsole 里的线程编号一直在增加_第三章_运行时数据区概述及线程
  6. Apache Flink 中文社区视频号上线!和大咖们线上见面~
  7. 平台: pSeries AIX 4.3 AIX 5L
  8. 软件开发文档-详细设计文档
  9. 高德地图---行政区划分
  10. 美团外卖订单中心的演进
  11. 阿里 vs. 腾讯,谁的收购更有眼光?
  12. 快来:互联网内容运营人员的文案写作技巧
  13. Emlog程序纯黑色调CYP音乐模板源码
  14. 辉芒微IO单片机FT60F021-RB
  15. 2019学位计算机模拟试题,2019年成人学位英语测试试卷(一)和试卷(二)
  16. 飞信免费发短信API接口调用方式
  17. ORACLE RAC TO RAC DG搭建过程中可能遇到的问题
  18. Three_Level_NPC_Inverter:基于MATLAB Simulink的三电平中性点钳位(NPC)逆变器仿真模型
  19. 科大讯飞语音听写-前端JS
  20. 计算机同一优盘记录,U盘使用后残留在电脑上的记录清理方法

热门文章

  1. 数据库设计报告——用教材管理系统来举例
  2. 士兵队列训练问题(队列)
  3. matlab中通过pcwrite将xyz数据转换成pcd格式文件
  4. python 网页截图不全_python网页截图(不受滚动条限制)
  5. BearPi-HM_Nano开发板WiFi编程开发——UDP客户端
  6. 【Windows】Shellcode免杀,过360、火绒、Defender 静态及主防
  7. 海康威视设备 JAVA SDK SpringBoot实现人脸、车辆、门禁图片抓取
  8. 北京做大数据分析的公司排名有哪些
  9. 当前主流服务器的品牌及详细信息
  10. C语言字符串指针(指向字符串的指针)详解