PID算法介绍以及代码实现过程说明

写在正文之前

在上一篇文章就说会在这两天会基于PID写一个文章,这里的原理部分值得大家都看一下,代码部分的实现是基于python的,但是对于使用其他编程语言的朋友,由于我写的很通俗易懂,所以也值得借鉴。

一、PID算法介绍

1、开环控制和闭环控制

开环控制和闭环控制的区别在于开环控制没有反馈调节,而闭环控制有反馈调节

PID就是闭环调节

2、PID的标准公式

3、PID的控制示意图

 

4、以无人机场景对PID各部分进行说明

(1)比例控制及稳态误差的存在

Proportion 比例控制
情景:无人机停在两米的高度,我们需要它停在十米的高度
Err = h - h0 =8
比例控制就是每次调节的高度是误差的Kp倍
假设Kp=0.5
Kp * err=4
则第一次调节的量是四米,第二次是两米,随着误差的减小,每次调节上升的量也逐渐减小
最终会接近十米高度,这整个过程就是比例控制
Kp越大,无人机调节越快
但是比例调节也存在弱点,假设无人机到达八米之后,存在一个向下的气流让它下降一米,这时它就会在八米的位置不变
这就是静态误差也叫稳态误差

(2)积分控制与过冲

Integration 积分控制
为了消除稳态误差,我们就要引入积分控制
积分控制是对过去的所有误差求和,在离散的情况,就是做累加
Ki:积分系数
此时的调节函数:Kp * err + Ki *  err的积分
假设积分系数为0.1,则在比例控制中出现的稳态误差得到解决,在八米时尽管有向下的气流无人机还是能上升1.2米
经过三次控制,累计误差已经到达了12.8,此时再进行下一次控制就会超过十米,这种现象叫过冲
此时就该微分控制出场了

(3)微分控制

Differential 微分控制
微分控制就是通过当前时刻与前一时刻误差量的差值对未来作预测
如果差值为正,就认为误差在逐渐扩大,需要加大控制强度使误差降下来
如果差值为负,则误差在减小,控制强度可以小一点让目标平稳缓和的到达指定值
 

二、代码实现过程说明

1、模块的导入

from pyb import millis  
from math import pi, isnan

millis用于获取当前的时间,以毫秒为单位

pi是圆周率常数

isnan函数用于检查一个值是否为NAN(Not a Number)

2、定义PID类

class PID:_kp = _ki = _kd = _integrator = _imax = 0_last_error = _last_derivative = _last_t = 0_RC = 1/(2 * pi * 20)  

这里定义了PID类的属性,包括
比例、积分、微分系数:_kp、_ki、_kd
积分器:_integrator(用于累积误差,用于计算积分项)
积分限制:_imax
最后的误差:_last_error
最后的导数:_last_derivative(这里的导数值指的是误差随时间的变化率,是通过计算当前误差与前一次误差之差再除以时间间隔得到的)
最后的时间戳:_last_t
RC 低通滤波器的时间常数

3、类中的初始化方法

def __init__(self, p=0, i=0, d=0, imax=0):# 初始化 PID 控制器的参数self._kp = float(p)  # 比例系数self._ki = float(i)  # 积分系数self._kd = float(d)  # 微分系数self._imax = abs(imax)  # 积分限制,防止积分饱和self._last_derivative = float('nan')  # 最后的导数值初始化为 NaN

关于这些参数的说明,在注释中已经给出,我这里只介绍它这里涉及的语法知识 

在这里我们可以看到定义变量的时候在变量面前加上了self,请注意,在类中的方法与普通函数区别,类中方法必须有一个额外的第一个参数名称,按照惯例这个名称是“self”

abs函数是取绝对值的函数

4、重置函数

def reset_I(self):self._integrator = 0  # 重置积分器self._last_derivative = float('nan')  # 重置最后的导数值为 NaN

虽然我这里说的是函数,但是更准确的表达应该是方法

这个类方法重置了积分器(误差的积累值)、 导数值(误差的变化率)

5、PID调节值计算函数

这个部分是整个PID类的重点,作PID的调节,主要就是这个函数

(1)函数的定义及参数的传入
def get_pid(self, error, scaler)

对传入的三个参数进行解释,其中self是调用变量需要的,其他的都是在之后计算涉及到的参数

self:self参数是必须传入的,只有传入了self参数才能使用以self开头的变量
error:误差值
scaler:缩放因子

(2)获取时间、时间差并初始化输出值
        tnow = millis()  # 获取当前时间dt = tnow - self._last_t  # 计算时间差output = 0  # 初始化输出值

这里利用了millis函数获取当前时间戳,和上一次获取的时间戳相减得到时间差, 有很多操作都涉及到了时间差

(3)判断是否第一次运行及时间差是否过长
if self._last_t == 0 or dt > 1000:  dt = 0  self.reset_I()  

如果是第一次运行或者运行时间过长,我们就重置时间差、积分器、导数值

积分器:误差的累积                导数值:误差的变化率(怕大家看到这里忘了再强调一下)


这里之所以作这样的处理,是因为积分和微分的处理都和之前的状态有关,所以在时间过长的时候我们直接就重置积分器和导数值(它们中存储的信息不再具有实时性)

(4)更新时间戳
      self._last_t = tnow  # 更新最后时间戳delta_time = float(dt) / float(1000)  # 将时间差转换为秒

这里在更新最后的时间差的同时将时间差转换成秒,方便之后的运算

(5)PID操作

在这里的PID操作要做的事情就是对系数和数据进行运算并将相关值赋给output最后进行输出

PID操作的顺序一般是(如果三个部分都用上):P——>D——>I(比例、微分、积分)

P操作
output += error * self._kp 

比例项的处理是最简单的,只需要给误差乘上一个比例系数之后赋值给output

D操作

D操作和I操作就比P操作复杂很多了

我们要根据微分系数的值和时间差的值来进行判断决定下一步的处理

if abs(self._kd) > 0 and dt > 0:  # 如果微分系数绝对值大于 0 且时间差大于 0if isnan(self._last_derivative):  # 如果最后的导数值为 NaN,就对其作初始化derivative = 0  # 导数值设置为 0self._last_derivative = 0  # 重置最后的导数值else:derivative = (error - self._last_error) / delta_time  # 计算导数值(误差的变化率)# 使用低通滤波器平滑导数值derivative = self._last_derivative + ((delta_time / (self._RC + delta_time)) * (derivative - self._last_derivative))      #delta_time就是转换成秒的时间差self._last_error = error  # 更新最后的误差值self._last_derivative = derivative  # 更新最后的导数值output += self._kd * derivative  # 计算微分项并加到输出中

首先如果微分系数大于0且时间差大于零才进行判断

进入判断之后再对导数值进行判断

如果导数值已经初始化,就计算导数值,如果导数值未进行初始化,就对导数值进行初始化

对导数值的计算首先只是差值减去时间,但是利用低通滤波器平滑导数值

然后就是顺便更新最后的导数值和误差值,然后把通过低通滤波之后的导数值乘以微分项加到output中

I操作

如果给出代码,大家可能会发现有一点很奇怪,那就是在我们进行积分操作之前有一个缩放操作

output *= scaler 

这个缩放值一般是1,当然,根据情况可以赋不同的值来适应不同的控制系统需求和误差幅度

接下来才是I操作,积分操作和微分操作的逻辑很像

if abs(self._ki) > 0 and dt > 0:                                      self._integrator += (error * self._ki) * scaler * delta_time  if self._integrator < -self._imax:self._integrator = -self._imaxelif self._integrator > self._imax:self._integrator = self._imaxoutput += self._integrator

首先对微分系数和时间差进行判断,若积分系数不为0且时间差大于零,进入分支

分支中的处理代码的主要功能是把积分器的值在-imax和imax之间,防止积分饱和

在作完了防止积分饱和的代码之后,我们把积分器也加入到output中,最后将output的值返回,这就是我们的最后调控PID控制函数返回的值

return output
(6)完整代码附上
from pyb import millis  # 导入 pyboard 的 millis 函数,用于获取当前时间(毫秒)
from math import pi, isnan  # 导入 pi 和 isnan 函数class PID:# 定义 PID 控制器的参数和状态变量_kp = _ki = _kd = _integrator = _imax = 0_last_error = _last_derivative = _last_t = 0_RC = 1/(2 * pi * 20)  # RC 低通滤波器的时间常数def __init__(self, p=0, i=0, d=0, imax=0):# 初始化 PID 控制器的参数self._kp = float(p)  # 比例系数self._ki = float(i)  # 积分系数self._kd = float(d)  # 微分系数self._imax = abs(imax)  # 积分限制,防止积分饱和self._last_derivative = float('nan')  # 最后的导数值初始化为 NaNdef get_pid(self, error, scaler):tnow = millis()  # 获取当前时间dt = tnow - self._last_t  # 计算时间差output = 0  # 初始化输出值if self._last_t == 0 or dt > 1000:  # 如果是第一次运行或者时间差大于 1 秒dt = 0  # 重置时间差self.reset_I()  # 重置积分器self._last_t = tnow  # 更新最后时间戳delta_time = float(dt) / float(1000)  # 将时间差转换为秒output += error * self._kp  # 计算比例项if abs(self._kd) > 0 and dt > 0:  # 如果微分系数大于 0 且时间差大于 0if isnan(self._last_derivative):  # 如果最后的导数值为 NaNderivative = 0  # 设置导数为 0self._last_derivative = 0  # 重置最后的导数值else:derivative = (error - self._last_error) / delta_time  # 计算误差的导 数# 使用低通滤波器平滑导数值derivative = self._last_derivative + ((delta_time / (self._RC + delta_time)) * (derivative - self._last_derivative))self._last_error = error  # 更新最后的误差值self._last_derivative = derivative  # 更新最后的导数值output += self._kd * derivative  # 计算微分项并加到输出中output *= scaler  # 按比例缩放输出值if abs(self._ki) > 0 and dt > 0:  # 如果积分系数大于 0 且时间差大于 0                                     self._integrator += (error * self._ki) * scaler * delta_time  # 计算积分项并加到积分器中# 限制积分器的值在 -imax 和 imax 之间,防止积分饱和if self._integrator < -self._imax:self._integrator = -self._imaxelif self._integrator > self._imax:self._integrator = self._imaxoutput += self._integrator  # 将积分项加到输出中return output  # 返回计算的 PID 控制器输出值def reset_I(self):self._integrator = 0  # 重置积分器self._last_derivative = float('nan')  # 重置最后的导数值为 NaN
 

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

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

相关文章

linux基于wifi,Xshell的远程连接

最近有个比赛&#xff0c;要使用ros小车但是系统是ubuntu20.04无桌面系统刚开始接触linux的我啥都不会&#xff0c;就一个简单的连接wifi都搞了3天才搞通。再此进行一个总结。参考博客原文链接&#xff1a;https://blog.csdn.net/qq_51491920/article/details/126221940 一、什…

短说V4.1.5及PC端V3.1.4正式版发布公告

Hi 大家好&#xff0c; 我是给你们带来惊喜的运营小番茄。 本期更新为短说 4.1.5和PC端3.1.4的正式版。 本次修复上个版本中的问题和功能优化&#xff0c;以及新增了如下功能&#xff1a; PC端支持发布秀米帖&#xff0c;可支持部分秀米格式&#xff1b;后台管理类消息新增…

【秋招刷题打卡】Day03-二分系列之-二分答案

Day03-二分系列之-二分答案 给大家推荐一下咱们的 陪伴打卡小屋 知识星球啦&#xff0c;详细介绍 >笔试刷题陪伴小屋-打卡赢价值丰厚奖励 < ⏰小屋将在每日上午发放打卡题目&#xff0c;包括&#xff1a; 一道该算法的模版题 (主要以力扣&#xff0c;牛客&#xff0c;…

43 mysql insert select 的实现

前言 我们这里 来探讨一下 insert into $fields select $fields from $table; 的相关实现, 然后 大致来看一下 为什么 他能这么快 按照 我的思考, 应该里里面有 批量插入才对, 但是 调试结果 发现令我有一些意外 呵呵 果然 只有调试才是唯一的真理 测试数据表如下 CREATE…

数字社交的领航者:解析Facebook的引领作用

在当今数字化社会中&#xff0c;社交网络已经成为了人们日常生活不可或缺的一部分。而在众多社交平台中&#xff0c;Facebook凭借其巨大的用户基础和创新的技术应用&#xff0c;被公认为数字社交领域的领航者之一。本文将深入解析Facebook在数字社交中的引领作用&#xff0c;探…

这三款工具很好用,赶快试试

FileZilla FileZilla是一款免费开源的FTP软件&#xff0c;分为客户端版本和服务器版本&#xff0c;具备所有的FTP软件功能。它是一个快速、可信赖的FTP客户端以及服务器端开放源代码程序&#xff0c;具有多种特色和直觉的界面。FileZilla客户端版是一个方便高效的FTP客户端工具…

第一后裔The First Descendant开服时间、配置要求一览

第一后裔是一款采用虚幻5引擎打造的第三人称合作射击动作RPG&#xff0c;玩家将化身为一名继承者&#xff0c;通过各种任务和故事不断成长&#xff0c;为守护人类与对抗侵略者战斗。该作即将上线&#xff0c;为了不让玩家们错过这款精彩的游戏&#xff0c;本文整理了第一后裔上…

测评:【AI办公】版本更迭与AI加持下的最新ONLYOFFICE桌面编辑器8.1

你是否还在为没有一款合适的在线桌面编辑器而苦恼&#xff1f;你是否还在因为办公软件的选择过少而只能使用WPS或者office&#xff1f;随着办公需求的不断变化和发展&#xff0c;办公软件也在不断更新和改进。ONLYOFFICE 作为一款全功能办公软件&#xff0c;一直致力于为用户提…

2024年全国青少年信息素养大赛图形化编程复赛样题_6547网

第 1 题 问答题 【编程实现】 按空格键随机切换背景&#xff0c;让背景对应的角色造型显示在舞台上。 【具体要求】 对角色编程&#xff0c;当按下空格键时&#xff0c;背景随机切换&#xff1b; 角色切换成对应的造型显示在舞台上&#xff1b; 角色说“我是”和它的造…

帮助你简易起步一个BLOG(博客搭建)项目

Blog项目 后端项目结构1. 项目初始化2. 详细步骤3.postman测试 前端1. 项目初始化2. 详细步骤 本章节是为了帮助你起步一个完整的前后端分离项目。 前端技术栈&#xff1a; react、vite、mantine、tailwind CSS、zustand、rxjs、threejs 后端技术栈&#xff1a;nodemon、nodej…

Transformer教程之神经网络和深度学习基础

在当今的人工智能领域&#xff0c;Transformer已经成为了一个热门的词汇。它不仅在自然语言处理&#xff08;NLP&#xff09;领域取得了巨大的成功&#xff0c;还在计算机视觉等其他领域展现出了强大的潜力。然而&#xff0c;要真正理解Transformer&#xff0c;我们首先需要扎实…

gdb用法

创建文件 // main.cpp文件 // 稳态误差 void pid_test_wentaiwucha() {float p 1.5;int t 1; // t 1s;int target 5; // 5m/sfloat output 0;float radis 3; // 稳态误差std::cout << "output: " << std::endl;fo…

并发 多线程

目录 thread thread 类总览 构造函数 join joinable ​编辑 detach swap yield swap 成员函数的调用 namespace::this_thread 线程同步--锁 互斥锁mutex 递归锁recursive_mutex 定时锁 Lock 锁辅助类 lock_guard​编辑 unique_lock std::lock 解决死锁问题 消息…

浅谈逻辑控制器之随机顺序控制器

浅谈逻辑控制器之随机顺序控制器 随机顺序控制器&#xff08;Random Order Controller&#xff09;作为一个独特的逻辑控制器&#xff0c;为测试脚本的执行增添了一层随机性&#xff0c;特别适用于模拟用户行为中不确定的访问模式。 随机顺序控制器概述 随机顺序控制器&…

代码随想录算法训练营第三十六天|62.不同路径、 63. 不同路径 II、343.整数拆分(可跳过)、96.不同的二叉搜索树(可跳过)

62.不同路径 题目链接&#xff1a;62.不同路径 文档讲解&#xff1a;代码随想录 状态&#xff1a;还行 思路&#xff1a;当前状态的只有可能是从上面或者左边过来的&#xff0c;所以 dp[i][j] dp[i-1] dp[j-1] 题解&#xff1a; public int uniquePaths(int m, int n) {if (…

Docker 安装Nginx部署网站 防火墙端口 数据卷挂载

拉取镜像 docker pull nginx#不写版本号 表示最新版本查看是否拉取成功 docker images#成功 nginx latest 605c77e624dd 2 years ago 141MB mysql 8.0 3218b38490ce 2 years ago 516MB mysql latest 3218b38490ce 2 years ago 5…

virtualbox(7.0) ubuntu(22) 和win11共享文件夹

在虚拟机中安装增强功能。在virtualbox中配置 执行命令将用户加入vboxsf组 sudo adduser your_usrname vboxsf 重启ubuntu即可

LeetCode 585, 438, 98

目录 585. 2016年的投资题目链接表要求知识点思路代码 438. 找到字符串中所有字母异位词题目链接标签思路代码 98. 验证二叉搜索树题目链接标签合法区间思路代码 中序遍历思路代码 585. 2016年的投资 题目链接 585. 2016年的投资 表 表Insurance的字段为pid、tiv_2015、tiv…

Charles网络抓包工具手机抓包配置(二)

目录 事前配置 配置手机连接 代理设置 Https请求设置 手机安装根证书 手机连接代理 证书获取​编辑 证书安装 成果 前言-与正文无关 ​ 生活远不止眼前的苦劳与奔波&#xff0c;它还充满了无数值得我们去体验和珍惜的美好事物。在这个快节奏的世界中&#xff0c;我们…

CesiumJS【Basic】- #020 加载glb/gltf文件(Primitive方式)

文章目录 加载glb/gltf文件(Primitive方式)1 目标2 代码实现3 资源文件加载glb/gltf文件(Primitive方式) 1 目标 使用Primitive方式加载glb/gltf文件 2 代码实现 import * as Cesium from "cesium";const viewer = new Cesium.Viewer