PID控制算法 – 1、Sample Time(采样时间)

news2024/11/14 16:52:22

前面介绍的PID代码虽然能跑起来,但是还存在一些问题。

PID控制算法 – 0、PID原理_资深流水灯工程师的博客-CSDN博客

对应的代码也重新贴一下,方便比较

/*工作变量*/
unsigned long lastTime;
double Input, Output, Setpoint;
double errSum, lastErr;
double kp, ki, kd;
void Compute()
{
   /*计算上次PID调用到这次调用之间的时间间隔*/
   unsigned long now = millis(); //获得当前时间,这是Arduino的做法,其他平台自己可以去替换
   double timeChange = (double)(now - lastTime); //计算时间间隔
 
   /*计算误差、误差的积分、误差的微分*/
   double error = Setpoint - Input;
   errSum += (error * timeChange);
   double dErr = (error - lastErr) / timeChange;
 
   /*计算PID的输出*/
   Output = kp * error + ki * errSum + kd * dErr;
 
   /*保留一些变量,留着下次用,记录误差和时间*/
   lastErr = error;
   lastTime = now;
}
 
void SetTunings(double Kp, double Ki, double Kd)
{
   kp = Kp;
   ki = Ki;
   kd = Kd;
}

主要有两个问题:

1、PID计算函数Compute()不是周期性的调用,相当于是轮询模式,调用的时间间隔不是一致的;

2、时间间隔的不一致,也就导致跟时间间隔相关的积分部分和微分部分每次都需要额外的计算;

解决方案

让PID计算函数Compute()周期性的调用,这计时所谓的采样频率,也叫采样周期。这样就会省很多事。怎么个省法?还是来进行一下时间分析。

PID算法调用时间的分析

积分和微分与时间是直接相关的,现在可不比之前,现在有固定的采样周期了,记录每次调用PID算法的时间间隔就是固定的采样周期SampleTime

所以原先的累计误差errSum是这么表示:errSum = \int e(t)d_{t}= e(0)*d_{t0} + e(1)*d_{t1} +e(2)*d_{t2}+...+e(t)*d_{t}

那现在是固定的采样周期,累计误差errSum可以这么表示:

errSum = \int e(t)d_{t}= (e(0)+ e(1)+e(2)+...+e(t))*Sampletime

那整个积分部分

K_{i}*\int e(t)d_{t} = K_{i}*errSum

K_{i}*errSum = K_{i}*Sampletime*(e(0)+e(1)+e(2)+...+e(t))

聪明的小朋友肯定知道把K_{i}*Sampletime看成一个整体,因为Sampletime是一个固定的常量;

误差的微分dERR可以这么表示: dErr=\frac{de(t)}{d_{t}} =(error - lastErr)/Sampletime

那整个微分部分可以这么表示:

K_{d}*\frac{de(t)}{d_{t}} =K_{d}*(error-lastErr)/Sampletime

聪明的小朋友肯定又会把K_{d}/Sampletime看成一个整体了。

那比例系数、积分系数、微分系数可以直接把采样时间Sampletime绑定一起

void SetTunings(double Kp, double Ki, double Kd)
{
  double SampleTimeInSec = ((double)SampleTime)/1000;
   kp = Kp;
   ki = Ki * SampleTimeInSec;
   kd = Kd / SampleTimeInSec;
}

既然比例系数、积分系数、微分系数已经算上了时间,那误差、误差积分、误差微分就改变了

//误差还是当年的误差
error = Setpoint - Input;

//误差的积分已不是当年的积分
errSum += error;

//误差的微分也不是当年的微分
dErr = (error - lastErr);

PID的输出还是那个公式:

Output = K_{p}*e(t) + K_{i}*\int e(t)d_{t} + K_{d}*\frac{de(t)}{d_{t}}

完整的周期性调用PID代码实现

/*工作变量*/
unsigned long lastTime;
double Input, Output, Setpoint;
double errSum, lastErr;
double kp, ki, kd;
int SampleTime = 1000; //1 sec
void Compute()
{
   unsigned long now = millis();//记录当前是时间
   int timeChange = (now - lastTime);//计算时间间隔,是为了判断采样时间是否到了
   if(timeChange>=SampleTime)//时间间隔大于采样时间就可以进行PID计算
   {
      /*计算误差、误差的积分、误差的微分*/
      double error = Setpoint - Input;
      errSum += error;
      double dErr = (error - lastErr);

      /*计算PID输出*/
      Output = kp * error + ki * errSum + kd * dErr;

      /*还是记录本次PID计算的误差和时间,留给下次使用*/
      lastErr = error;
      lastTime = now;
   }
}

void SetTunings(double Kp, double Ki, double Kd)
{
  double SampleTimeInSec = ((double)SampleTime)/1000;
   kp = Kp;
   ki = Ki * SampleTimeInSec;
   kd = Kd / SampleTimeInSec;
}

void SetSampleTime(int NewSampleTime)
{//改变采样时间后,只需要将ki和kd等比例替换一下就行
   if (NewSampleTime > 0)
   {
      double ratio  = (double)NewSampleTime
                      / (double)SampleTime;
      ki *= ratio;
      kd /= ratio;
      SampleTime = (unsigned long)NewSampleTime;
   }
}

在第10和11行,该算法可以决定是否需要进行PID计算。

我们现在知道采样之间的时间是相同的,因此不需要不断地乘以时间变化。 只需适当地调整Ki和Kd(第31和32行),结果在数学上是等效的,但效率更高。

虽然这样做有点瑕疵。 如果用户决定在操作过程中更改采样时间,则需要重新调整Ki和Kd以反映此新更改。 这就是第39-42行的全部内容。

在第29行将采样时间转换为秒。其实要不要没什么区别,但是这样允许用户以1 / sec和s的单位输入Ki和Kd,而不是1 / mS和mS。

上面的代码主要做了以下更改:

  1. PID算法会以固定的时间间隔进行计算【第11行】
  2. 不需要再乘除时间变化。采样时间是一个常数,可以将其从计算代码中移出【第15、16行】,然后将其于调整常数一起输入【第31、32行】。这样做省去了每次PID计算中的一次乘法和一次除法运算。

细心的小朋友肯定会发现,这种方法总是要搞个时间戳,记录每次调用PID算法的时间,通过判断时间间隔的大小来确定是否进行PID计算,在轮询系统中也不一定能保证采样时间是固定的,总归会有一点点偏差。通过定时器中断的方式周期性的调用PID计算函数不香吗?香,当然香,个人认为通过定时器中断来进行PID计算更好,也不复杂,在单片机的应用中,只需要掌握定时器中断就行了,有机会后面再展开吧。

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

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

相关文章

【初识C语言(2)】字符串+转义字符+注释

文章目录 1. 字符串2. 转义字符转义字符表常见转义字符 3. 注释 1. 字符串 “hello world.\n” 上面这种由双引号引起的一串字符就被称为字符串; 字符串的存储 C 语言当中没有字符串类型,如果想要将字符串存储起来的话就需要用到字符串数组。 #include…

LLaMA及其子孙模型概述

文章目录 LLaMAAlpacaVicunaKoalaBaize (白泽)骆驼(Luotuo)BELLEGuanaco LLaMA 与原始transformer的区别: 预归一化[GPT3]。为了提高训练稳定性,对每个Transformer子层的输入进行归一化,而不是对输出进行归一化。使用了Zhang和Sennrich(201…

Redis进阶篇(附面试快速答法)

文章目录 Redis使用场景1、缓存穿透布隆过滤器小总结面试快速答法 2、缓存击穿小总结面试快速答法 3、缓存雪崩面试快速答法 4、双写一致性小总结面试快速答法 5、持久化机制面试快速答法 6、数据过期策略小总结面试快速答法 7、数据淘汰策略小总结面试快速答法 8、分布式锁小总…

Pytest集成Allure Report

目录 安装 用法 基本报告 支持 Pytest features Xfail 条件标记 Fixtures and Finalizers 参数化 Allure Features Steps 附件 描述 标题 链接 重试 Tags BDD 标签 严重性标记 Behave 安装 使用 Features 严重性 步骤和场景状态 步骤数据 安装 Pytest可从…

SpringBoot 如何使用 @RequestBody 进行数据校验

SpringBoot 如何使用 RequestBody 进行数据校验 在 Web 开发中,前台向后台发送数据是非常常见的场景。而在 SpringBoot 框架中,我们通常使用 RequestBody 注解来接收前台发送的 JSON 数据,并将其转化为 Java 对象。但是,接收到的…

你一定想知道的 如何进行动态内存管理?

文章目录 引言malloc函数calloc函数realloc函数free函数-避免内存泄漏常见的动态内存错误 引言 如果我们被问道&#xff1a;如何创建一个可以根据用户需求来开辟大小的数组&#xff1f; 可能有些博友会写出如下代码&#xff1a; #include <stdio.h> int main() {int n…

c++11 标准模板(STL)(std::basic_streambuf)(二)

定义于头文件 <streambuf> template< class CharT, class Traits std::char_traits<CharT> > class basic_streambuf; 类 basic_streambuf 控制字符序列的输入与输出。它包含下列内容并提供到它们的访问&#xff1a; 1) 受控制字符序列&#xff…

专项练习9

目录 一、选择题 1、在 JavaScript 中&#xff0c;用于阻止默认事件的默认操作的方法是 2、以下代码执行后&#xff0c;result 的值为&#xff08;&#xff09; 3、不能从字符串 const str qwbewrbbeqqbbbweebbbbqee;中能得到结果 ["b", "bb", "bbb…

实时在线云消费机、考勤门禁控制器、网络读卡器服务端C# Socket源码

消费机UDP通讯协议介绍&#xff1a; 设备向服务器发送的指令格式&#xff0c;每个字段用半角逗号(,)分隔。序号指令名称指令格式指令说明示例1响应服务器的搜索100,包序列号,终端IP,子网掩码,网关IP,远程电脑主机IP,端口号,终端硬件号响应电脑发出的搜寻局域网内所有终端设备指…

【Python 基础篇】Python 异常处理

文章目录 引言一、Python异常概述二、常见的内置异常三、异常处理语句四、异常捕获和处理五、实例演示六、总结 引言 在软件开发中&#xff0c;错误和异常是难以避免的。当我们编写Python代码时&#xff0c;有时候会遇到各种各样的问题&#xff0c;例如无效的输入、文件不存在…

hello算法笔记之树

一、二叉树 与链表类似&#xff0c;二叉树的基本单元是节点&#xff0c;每个节点包含一个「值」和两个「指针」。 在二叉树中&#xff0c;除叶节点外&#xff0c;其他所有节点都包含子节点和非空子树。 一些术语&#xff1a; 「根节点 Root Node」&#xff1a;位于二叉树顶…

VNC虚拟网络控制台(概述、windows系统连接linux系统演示)

第三阶段基础 时 间&#xff1a;2023年6月22日 参加人&#xff1a;全班人员 内 容&#xff1a; VNC虚拟网络控制台 目录 一、VNC概述 二、VNC基本上是由两部分组成 三、VNC特点 四、工作流程 五、安装 六、操作演示Windiws10系统远程控制linux 服务端&#xff1a;…

window版安装kafka并提供启动快捷脚本

kafka下载地址&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1DpcGXvpTYAcG_fvS-p9-3g?pwd1234 提取码&#xff1a;1234 官网&#xff1a;https://kafka.apache.org/downloads 注意不需要单独安装zk&#xff0c;里面包括zk Kafka解压包目录不要太深了&#xff0c…

养老院人员跌倒检测识别算法

养老院人员跌倒检测识别预警系统通过yolov5python网络模型技术&#xff0c;养老院人员跌倒检测识别预警算法对跌倒事件进行识别和分析&#xff0c;当检测到有人员跌倒时&#xff0c;将自动发出警报提示相关人员及时采取措施。YOLOv5是一种单阶段目标检测算法&#xff0c;该算法…

CTF-Show密码学【Base64、栅栏密码、16进制】

题目内容 密文&#xff1a;53316C6B5A6A42684D3256695A44566A4E47526A4D5459774C5556375A6D49324D32566C4D4449354F4749345A6A526B4F48303D 提交格式&#xff1a;KEY{XXXXXXXXXXXXXX}工具下载&#xff1a;https://www.lanzoui.com/i9fn2aj萌新_密码13 分析和解决过程 初步分析…

【Python 基础篇】Python 面向对象编程:理解与实践

文章目录 一、引言二、类与对象三、封装与访问控制四、继承与多态&#xff08;第一部分&#xff09;五、方法重写与多态&#xff08;第二部分&#xff09;六、抽象类与接口1、抽象类2、接口 七、类的关联与组合1、关联关系2、组合关系 八、面向对象设计原则1、SOLID原则2、设计…

实验 4:排序与查找

东莞理工的学生可以借鉴&#xff0c;请勿抄袭 1.实验目的 通过实验达到&#xff1a; 理解典型排序的基本思想&#xff0c;掌握典型排序方法的思想和相应实现算法&#xff1b; 理解和掌握用二叉排序树(BST)实现动态查找的基本思想和相应的实现 算法。 理解和掌握哈希(HASH)存…

【备战秋招】每日一题:2023.04.26-实习-第三题-MC方块

在线评测链接:P1231 题目内容 MC最新版本更新了一种特殊的方块&#xff0c;幽匿催发体。这种方块能够吸收生物死亡掉落的经验并感染周围方块&#xff0c;使其变成幽匿块。Steve想要以此为基础尝试搭建一个经验仓库&#xff0c;他来到了创造超平坦模式&#xff0c;在只有草方块…

[进阶]junit单元测试框架详解

单元测试 就是针对最小的功能单元(方法&#xff09;&#xff0c;编写测试代码对其进行正确性测试。 以前是如何进行单元测试的&#xff1f;有什么问题&#xff1f; 只能在main方法编写测试代码&#xff0c;去调用其他方法进行测试。无法实现自动化测试&#xff0c;一个方法测…

python数字猜谜2.0

改进了一下数字猜谜&#xff1a; 开头&#xff0c;可选等级&#xff1a; import random guess -1 c 0 print("数字猜谜游戏&#xff01;") n input("选择等级 A B C&#xff1a;") if (n "A") or (n "a"):guess random.randint…