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 且时间差大于 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.mfbz.cn/a/770514.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java:JDK、JRE和JVM 三者关系

文章目录 一、JDK是什么二、JRE是什么三、JDK、JRE和JVM的关系 一、JDK是什么 JDK&#xff08;Java Development Kit&#xff09;&#xff1a;Java开发工具包 JRE&#xff1a;Java运行时环境开发工具&#xff1a;javac&#xff08;编译工具&#xff09;、java&#xff08;运行…

偏微分方程笔记(驻定与非驻定问题)

椭圆方程可以看成抛物方程 t → ∞ t\rightarrow\infty t→∞的情况。 抛物&#xff1a; 双曲&#xff1a;

学习aurora64/66b.20240703

简介 The AMD LogiCORE™IP Aurora 64B/66B core是一种可扩展的轻量级高数据速率链路层协议&#xff0c;用于高速串行通信。该协议是开放的&#xff0c;可以使用AMD设备技术实现。 Aurora 64B/66B是一种轻量级的串行通信协议&#xff0c;适用于多千兆位链路 (如下图所示)。它…

微信小程序禁止PC端打开防止白嫖广告或抓接口

前言 晓杰每日靠着微薄的小程序广告度日&#xff0c;继之前检测手机端微信跳过小程序广告插件检测后又发现小程序广告在电脑端经常没广告&#xff0c;导致收入备降&#xff01;虽然每天只有几块钱的收入&#xff0c;哈哈哈&#xff01;那么怎么做到禁止小程序使用电脑端微信打…

nginx的匹配及重定向

一、nginx的匹配&#xff1a; nginx中location的优先级和匹配方式&#xff1a; 1.精确匹配&#xff1a;location / 对字符串进行完全匹配&#xff0c;必须完全符合 2.正则匹配&#xff1a;location ^~ ^~ 前缀匹配&#xff0c;以什么为开头 ~区分大小写的匹配 ~* 不区分…

Unity | Shader基础知识(第十七集:学习Stencil并做出透视效果)

目录 一、前言 二、了解unity预制的材质 三、什么是Stencil 四、UGUI如何使用Stencil&#xff08;无代码&#xff09; 1.Canvas中Image使用Stencil制作透视效果 2.学习Stencil 3.分析透视效果的需求 五、模型如何使用Stencil 1.shader准备 2.渲染顺序 3.Stencil代码语…

【TypeScript】TS入门到实战(详解:高级类型)

目录 第三章、TypeScript的数据类型 3.1 TypeScript的高级类型 3.1.1 class 3.1.1.1 熟悉class类 3.1.1.2 class类继承的两种方式 3.1.1.3 class类的5种修饰符 3.1.2 类型兼容 3.1.3 交叉类型 3.1.4 泛型 3.1.4.1 创建泛型函数 3.1.4.2 泛型函数的调用 3.1.4.3 泛型…

c++纵横字谜

1.实现一个纵横字谜 2.支持14x14的网格 3.可以查看答案 4.猜测错误会提示答案信息 5.从txt读取词汇 6.每次游戏开始 随机生成纵横字谜 n’h

Jest是什么软件?

Jest是一个由Facebook开发的开源JavaScript测试框架&#xff0c;它专为JavaScript项目的测试而设计&#xff0c;特别适用于React和Node.js环境。Jest以其简单的配置、高效的性能和易用性而闻名&#xff0c;成为现代JavaScript项目中不可或缺的测试工具。以下是关于Jest的详细解…

Android Compose 十二:常用组件列表 上拉加载

列表 上拉加载 当前思路 判断 列表最后一个显示的条目 为 数据集合的长度-1 用来记录刷新状态 var refreshing by remember {mutableStateOf(false)}数据集合 val list remember{List(10){"条目》》${it}"}.toMutableStateList()}用来记录列表当前状态及状态变化…

探讨4层代理和7层代理行为以及如何获取真实客户端IP

准备工作 实验环境 IP角色192.168.1.100客户端请求IP192.168.1.100python 启动的HTTP服务192.168.1.102nginx服务192.168.1.103haproxy 服务 HTTP服务 这是一个简单的HTTP服务&#xff0c;主要打印HTTP报文用于分析客户端IP #!/usr/bin/env python # coding: utf-8import …

地理信息科学:生态保护的智慧经纬

在地球这颗蓝色星球上&#xff0c;每一片森林的呼吸、每一条河流的流淌&#xff0c;都是生命交响曲中不可或缺的音符。而地理信息科学&#xff08;GIS&#xff09;&#xff0c;正是我们手中解读自然密码、护航生态平衡的精密仪器。今天&#xff0c;让我们深入探讨GIS如何在生物…

新加坡博士申请|中国社科院-新加坡社科大学联合培养工商管理博士

新加坡博士申请|中国社科院-新加坡社科大学联合培养工商管理博士 【项目名称】中国社会科学院大学与新加坡新跃社科大学工商管理博士项目 【学制】最短3年&#xff0c;最长不超过7年 【学位证书】新加坡新跃社科大学工商管理博士学位 【招生对象】企业高管、咨询顾问及其他有…

智能舌诊应用开发:结合通义千问与OpenAI库

项目介绍 所有的项目都是基于 TailwindCSS 实现了响应式&#xff0c;同时支持网页端和移动端的显示效果。 这期尝试开发的 AI 应用是使用通义千问的大模型 API&#xff0c;开发一个 AI 看舌苔的应用。 整个项目的操作流程比较简单&#xff0c;第一屏用户上传自己的舌头的照片…

【JavaWeb程序设计】页面编程

目录 一、使用divCSS实现页面的布局 1. HTML结构代码 2. CSS样式代码 3. 运行截图 二、使用各类标签制作一个静态页面 1. 我做的页面运行截图 2. HTML结构代码 3. CSS代码 一、使用divCSS实现页面的布局 以下代码实现如图的页面布局&#xff0c;请完善相关代码 1. HT…

Docker的架构原理

例子可以想象成一个买手机的场景 clien可以想象 你个人 docker deamon &#xff1a;店员 images&#xff1a; 样机 regisitry&#xff1a; 手机仓库 container: 使用的手机 首先我要在店员买一个手机&#xff0c;店员发现是样机&#xff0c;但是仓库有&#xff0c;&…

SwiftUI九创建watchOS应用

代码下载 这篇教程让可以应用之前所学到的SwiftUI知识&#xff0c;把Landmarks应用从iOS平台迁移到watchOS平台上。在拷贝可以共用的数据和视图文件之前&#xff0c;需要先给项目中添加一个对应watchOS的Target编译目标。在所有 assets 就绪后&#xff0c;将自定义SwiftUI视图…

【TS】TypeScript 中的 any 与 unknown:理解与实践

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 TypeScript 中的 any 与 unknown&#xff1a;理解与实践一、引言二、any&#x…

找不到msvcr110.dll是怎么回事?彻底解决msvcr110.dll丢失的方法

当您的电脑提示遇到msvcr110.dll丢失时&#xff0c;您知道如何解决此问题吗&#xff1f;事实上&#xff0c;解决此类dll文件丢失的问题相对较为简单。只要我们深入了解msvcr110.dll丢失的具体情况&#xff0c;便可轻松解决此问题。以下为您介绍msvcr110.dll修复方法。 一&#…

vue table表格 ( parseTime-格式化时间)

<el-table-column label"发布时间" width"420px" prop"bidPublishDatetime"><template slot-scope"scope"><span>{{ parseTime(scope.row.bidPublishDatetime, {y}-{m}-{d}) }}</span></template></…