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

news2024/11/15 20:41:28

写在正文之前

在上一篇文章就说会在这两天会基于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 且时间差大于 0
            if isnan(self._last_derivative):  # 如果最后的导数值为 NaN,就对其作初始化
                derivative = 0  # 导数值设置为 0
                self._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._imax
            elif self._integrator > self._imax:
                self._integrator = self._imax
            output += 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')  # 最后的导数值初始化为 NaN

    def 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 且时间差大于 0
            if isnan(self._last_derivative):  # 如果最后的导数值为 NaN
                derivative = 0  # 设置导数为 0
                self._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._imax
            elif self._integrator > self._imax:
                self._integrator = self._imax
            output += self._integrator  # 将积分项加到输出中

        return output  # 返回计算的 PID 控制器输出值

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

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

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

相关文章

AR导航技术加持,图书馆阅读体验智慧升级

在信息爆炸的今天&#xff0c;图书馆作为知识的宝库&#xff0c;其藏书量和种类日益增多。然而&#xff0c;传统的图书馆导航方式已逐渐无法满足用户对快速、准确定位图书的需求。本文将探讨图书馆AR地图导航的实现原理、技术优势、功能特点以及市场前景&#xff0c;揭示为何AR…

【C++】——AVL树(详细解读)

目录 一 AVL树的概念 二 AVL树节点的定义 三 AVL树的插入 1.先和搜索二叉树一样&#xff0c;去找插入的结点 2.插入的时候&#xff0c;需要更新平衡因子 3.确定平衡因子的改变&#xff0c;判断AVL树的改变 三 AVL树的旋转 左单旋 右单旋 右左双旋 左右双旋 四 …

【包邮送书】深度学习与信号处理

欢迎关注博主 Mindtechnist 或加入【智能科技社区】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和技术。关…

计算机等级考试二级Java-第一篇:Java语言概述

1.java语言的历史和发展 1991年由sun公司的James Gosling负责开发的&#xff0c;一个分布式代码系统&#xff08;Oak),最初是为家用消费电子产品&#xff08;电冰箱&#xff0c;电视机等&#xff09;进行编程&#xff0c;它是java语言的前身。 1994年sun公司件目标市场转向In…

热敏晶振:成本效益的选择与温补晶振的比较

在精密电子系统的设计中&#xff0c;晶振作为时间基准源&#xff0c;其频率稳定性直接影响到整个系统的性能。其中&#xff0c;温补晶振(Temperature Compensated Crystal Oscillator&#xff0c;简称TCXO)与热敏晶振(Thermistor Compensated Crystal Oscillator)作为在特殊温度…

VS studio2019配置远程连接Ubuntu

VS studio2019配置远程连接Ubuntu 1、网络配置 &#xff08;1&#xff09;获取主机IP &#xff08;2&#xff09;获取Ubuntu的IP &#xff08;3&#xff09;在 windows 的控制台中 ping 虚拟机的 ipv4 地址&#xff0c;在 Ubuntu 中 ping 主机的 ipv4 地址。 ubuntu: ping…

centos7 安装单机MongoDB

centos7安装单机 yum 安装 1、配置yum源 vim /etc/yum.repos.d/mongodb.repo [mongodb-org-7.0] nameMongoDB Repository baseurlhttps://repo.mongodb.org/yum/redhat/$releasever/mongodb-org/7.0/x86_64/ gpgcheck1 enabled1 gpgkeyhttps://www.mongodb.org/static/pgp…

1.3.1 离散周期信号DFS

目录 离散周期序列的DFS表示 离散周期信号DFS的性质 线性特性 位移特性 对称特性 奇偶对称 共轭反转对称 实序列的对称特性 周期卷积 DFS——Discrete Fourier Series 傅里叶级数 离散周期序列的DFS表示 做题得到的小公式 离散周期信号DFS的性质 线性特性 位…

【日志等级类编写】

日志等级类编写 这篇文章接着上篇文章&#xff0c;继续来完成日志系统。 在一个日志文件当中&#xff0c;有各种各样的等级日志 debuginfowarnerrorfatal 我们使用的时候传入的是一个等级&#xff0c;我们需要将它转换为字符串。 class LogLevel {public:enum class Level…

白敬亭章若楠甜度报表的难哄大师

#白敬亭章若楠&#xff0c;甜度爆表的难哄大师#&#x1f389;&#x1f389;&#x1f389;各位小伙伴们&#xff0c;你们还记得那个让我们心跳加速、嘴角上扬的CP组合吗&#xff1f;没错&#xff0c;就是白敬亭和章若楠&#xff01;他们可是凭借一部新剧&#xff0c;再次让我们感…

分享几个小红书获取笔记详情API接口调用实例

item_get_video-获得小红书笔记详情 smallredbook.item_get_video 公共参数 名称类型必须描述keyString是调用key&#xff08;API支持测试&#xff0c;获取测试key&#xff09;secretString是调用密钥api_nameString是API接口名称&#xff08;包括在请求地址中&#xff09;[i…

第1章 基础知识

第1章 基础知识 1.1 机器语言 机器语言就是机器指令的集合&#xff0c;机器指令展开来讲就是一台机器可以正确执行的命令 1.2 汇编语言的产生 汇编语言的主题是汇编指令。汇编指令和机器指令的差别在于指令的表示方法上&#xff0c;汇编指令是机器指令便于记忆的书写格式。…

技术干货|SimLab 电子产品热流体仿真

电子产品热仿真特点有哪些&#xff1f; 结构复杂&#xff0c;电子设备包含几十~上千个元器件 体积小&#xff0c;功率密度高、关注热敏感元器件 多种冷却方式&#xff0c;自然冷却、风扇冷却、液冷、热管等 多维度&#xff0c;芯片级&#xff0c;板级&#xff0c;系统级 单…

纯干货丨知乎广告投放流程和避坑攻略

精准有效的广告投放企业获客的关键&#xff0c;知乎作为中国最大的知识分享平台&#xff0c;拥有着高质量的用户群体和高度的用户粘性&#xff0c;为广告主提供了独一无二的品牌传播与产品推广平台。然而&#xff0c;如何在知乎上高效、精准地进行广告投放&#xff0c;避免不必…

恭喜!Z医生喜提世界名校—斯坦福大学访问学者邀请函

➡️【院校简介】 斯坦福大学&#xff08;Stanford University&#xff09;&#xff0c;全称为小利兰斯坦福大学&#xff0c;简称“斯坦福”&#xff0c;位于美国加州旧金山湾区南部帕罗奥多市境内&#xff0c;临近高科技园区硅谷&#xff0c;是私立研究型大学&#xff0c;全球…

nbcio-vue升级迁移flowable到最新的jeeg-boot-vue3的问题记录(二)

因为这个项目license问题无法开源&#xff0c;更多技术支持与服务请加入我的知识星球。 8、用生成的代码修改api与列表字段&#xff0c;但还是显示不出来&#xff0c;api获取数据是正常的 使用BasicTable的api的时候&#xff0c;调用的api不能 // 我的发起的流程 export cons…

鸿蒙期末项目(3)

服务器搭建完成之后&#xff0c;编写了诸多api用于数据传输工作&#xff08;略&#xff09; 编写完成之后&#xff0c;回到鸿蒙开发工具&#xff0c;开始编写搜索页面的代码。 打开搜索页面时&#xff0c;先会展示历史搜索记录&#xff08;如果有的话&#xff09;&#xff0c;…

OpenAI推迟ChatGPT高级语音模式发布!谷歌将推出明星网红AI聊天机器人|AI日报

文章推荐 时序预测双飞轮&#xff0c;全面超越Transformer&#xff0c;纯MLP模型实现性能效能齐飞 OpenAI将终止对我国提供API服务&#xff0c;国内大模型将迎来“六小强”格局&#xff01;&#xff5c;AI日报 推迟ChatGPT高级语音模式发布&#xff01;OpenAI将计划在秋季向…

java注解的概念及其使用方法详细介绍

1_注解&#xff1a;概述 路径 什么是注解注解的作用 注解 什么是注解&#xff1f; 注解(Annotation)也称为元数据&#xff0c;是一种代码级别的说明注解是JDK1.5版本引入的一个特性&#xff0c;和类、接口是在同一个层次注解可以声明在包、类、字段、方法、局部变量、方法参…

WPF----进度条ProgressBar(渐变色)

ProgressBar 是一种用于指示进程或任务的进度的控件&#xff0c;通常在图形用户界面&#xff08;GUI&#xff09;中使用。它提供了一种视觉反馈&#xff0c;显示任务的完成程度&#xff0c;帮助用户了解任务的进展情况。 基本特性 Minimum 和 Maximum 属性&#xff1a; 这些属…