值得一看!从0编写一份PID控制代码

news2024/12/24 2:35:26

【推荐阅读】

浅析linux 系统进程冻结(freezing of task)

深入linux内核架构--进程&线程

纯干货,linux内存管理——内存管理架构(建议收藏)

轻松学会linux下查看内存频率,内核函数,cpu频率

概述Linux内核驱动之GPIO子系统API接口

一、前言

上一章节我分享了控制算法PID的基本概念,以及调参方式,相信大家对PID有了一个基本的了解,这一章我分享一下现在我使用的PID算法代码(代码是大疆工程师写的PID代码模板,写的非常棒),结合原理分析,让大家对其有一个更加深刻的理解,并且知道如何写PID算法。

二、PID初始化代码

工程或者比赛中我们用到的PID一般不止一个,这些PID只是参数的值不一样,参数类型,参数运算函数基本相同,所以定义一个结构体,将这些有关参数作为结构体的成员,定义一个新的PID参数时,就是建立一个新的结构体,运算和初始化时直接调用对应的成员变量就行,十分方便简洁,具体定义的结构体如下:

typedef struct
{
    //PID运算模式
    uint8_t mode;
    //PID 三个基本参数
    fp32 Kp;
    fp32 Ki;
    fp32 Kd;

    fp32 max_out;  //PID最大输出
    fp32 max_iout; //PID最大积分输出

    fp32 set;	  //PID目标值
    fp32 fdb;	  //PID当前值

    fp32 out;		//三项叠加输出
    fp32 Pout;		//比例项输出
    fp32 Iout;		//积分项输出
    fp32 Dout;		//微分项输出
    //微分项最近三个值 0最新 1上一次 2上上次
    fp32 Dbuf[3];  
    //误差项最近三个值 0最新 1上一次 2上上次
    fp32 error[3];  

} pid_type_def;

定义的结构体成员变量的功能已经备注在代码中,有了结构的体之后,我们使用结构体定义一个PID结构体,编写一个初始化代码,初始运行时调用一次,初始化各个参数,PID_init函数主体如下:

void PID_init(pid_type_def *pid, uint8_t mode, const fp32 PID[3], fp32 max_out, fp32 max_iout)
{
    if (pid == NULL || PID == NULL)
    {
        return;
    }
    pid->mode = mode;
    pid->Kp = PID[0];
    pid->Ki = PID[1];
    pid->Kd = PID[2];
    pid->max_out = max_out;
    pid->max_iout = max_iout;
    pid->Dbuf[0] = pid->Dbuf[1] = pid->Dbuf[2] = 0.0f;
    pid->error[0] = pid->error[1] = pid->error[2] = pid->Pout = pid->Iout = pid->Dout = pid->out = 0.0f;
}

模式枚举

enum PID_MODE
{
    PID_POSITION = 0,
    PID_DELTA
};

三、PID运算代码

PID初始化完成之后,就是编写具体的运算代码了,代码内容如下,具体含义注释在代码中:

fp32 PID_calc(pid_type_def *pid, fp32 ref, fp32 set)
{
    //判断传入的PID指针不为空
    if (pid == NULL)
    {
        return 0.0f;
    }
    //存放过去两次计算的误差值
    pid->error[2] = pid->error[1];
    pid->error[1] = pid->error[0];
    //设定目标值和当前值到结构体成员
    pid->set = set;
    pid->fdb = ref;
    //计算最新的误差值
    pid->error[0] = set - ref;
    //判断PID设置的模式
    if (pid->mode == PID_POSITION)
    {
        //位置式PID
        //比例项计算输出
        pid->Pout = pid->Kp * pid->error[0];
        //积分项计算输出
        pid->Iout += pid->Ki * pid->error[0];
        //存放过去两次计算的微分误差值
        pid->Dbuf[2] = pid->Dbuf[1];
        pid->Dbuf[1] = pid->Dbuf[0];
        //当前误差的微分用本次误差减去上一次误差来计算
        pid->Dbuf[0] = (pid->error[0] - pid->error[1]);
        //微分项输出
        pid->Dout = pid->Kd * pid->Dbuf[0];
        //对积分项进行限幅
        LimitMax(pid->Iout, pid->max_iout);
        //叠加三个输出到总输出
        pid->out = pid->Pout + pid->Iout + pid->Dout;
        //对总输出进行限幅
        LimitMax(pid->out, pid->max_out);
    }
    else if (pid->mode == PID_DELTA)
    {
        //增量式PID
        //以本次误差与上次误差的差值作为比例项的输入带入计算
        pid->Pout = pid->Kp * (pid->error[0] - pid->error[1]);
        //以本次误差作为积分项带入计算
        pid->Iout = pid->Ki * pid->error[0];
        //迭代微分项的数组
        pid->Dbuf[2] = pid->Dbuf[1];
        pid->Dbuf[1] = pid->Dbuf[0];
        //以本次误差与上次误差的差值减去上次误差与上上次误差的差值作为微分项的输入带入计算
        pid->Dbuf[0] = (pid->error[0] - 2.0f * pid->error[1] + pid->error[2]);
        pid->Dout = pid->Kd * pid->Dbuf[0];
        //叠加三个项的输出作为总输出
        pid->out += pid->Pout + pid->Iout + pid->Dout;
        //对总输出做一个先限幅
        LimitMax(pid->out, pid->max_out);
	}
	return pid->out;
}

限幅代码为预编译代码

#define LimitMax(input, max)   \
    {                          \
        if (input > max)       \
        {                      \
            input = max;       \
        }                      \
        else if (input < -max) \
        {                      \
            input = -max;      \
        }                      \
    }

以上就是PID运行时的处理代码了,这里PID有两种处理模式,一个增量式一个是位置式,两个算法具体说起来还比较复杂。

四、PID输出清空代码

有时候我们需要停止PID,需要清除中间变量,主要就是目标值和中间变量清零,具体代码如下

void PID_clear(pid_type_def *pid)
{
    if (pid == NULL)
    {
        return;
    }
	//当前误差清零
    pid->error[0] = pid->error[1] = pid->error[2] = 0.0f;
    //微分项清零
    pid->Dbuf[0] = pid->Dbuf[1] = pid->Dbuf[2] = 0.0f;
    //输出清零
    pid->out = pid->Pout = pid->Iout = pid->Dout = 0.0f;
    //目标值和当前值清零
    pid->fdb = pid->set = 0.0f;
}

 

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

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

相关文章

Servlet与表单、数据库综合项目实战【学生信息管理】

✅作者简介&#xff1a;热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏&#xff1a;JAVA开发者…

开关电源环路稳定性分析(07)——电压型补偿网络

大家好&#xff0c;这里是大话硬件。 在前面的文章中&#xff0c;已经分析了控制级和功率级的传递函数&#xff0c;这一节咱们来分析反馈级的传递函数。 在分析反馈网络的传递函数之前&#xff0c;我想&#xff0c;应该有几个问题需要做一下介绍。 1. 功率级和控制级传递函数…

游戏开发41课 unity shader 优化

Shader有专门语言用来编写&#xff0c;常见类型有DirectX的HLSL&#xff0c;OpenGL的GLSL以及NVIDIA的Cg&#xff0c;为了优化shader代码&#xff0c;我们需要知道代码从被编写到被执行的流程&#xff0c;知道什么样的代码是不好的。 注意点&#xff1a; 避免if、switch分支语…

关于前端低代码的一些个人观点

2022&#xff0c;低代码彻底火了&#xff0c;甚至火到没有点相关经验&#xff0c;都不好意思出去面试的程度&#xff0c;堪称lowcode“元年”。在整个互联网大裁员的背景下&#xff0c;无论你是否相信它是降本提效的利器&#xff0c;彷佛都不重要了。因为行业趋势总是这般浩浩荡…

为什么其他地方的智能网联汽车产业都跟着北京模式走?

随着新一轮科技革命和产业变革加速演进&#xff0c;智能网联汽车已成为全球汽车产业发展的战略方向&#xff0c;是全球大国竞争的重要科技和产业领域。在技术路线层面&#xff0c;我国率先提出车路云融合的智能网联汽车“中国方案”&#xff0c;该路线已逐步成为国际共识。在产…

大学生端午节网页作业制作 学生端午节日网页设计模板 传统文化节日端午节静态网页成品代码下载 端午节日网页设计作品

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

【云原生】二进制k8s集群(下)部署高可用master节点

内容预知 本次部署说明 本次部署的架构组件 1. 新master节点的搭建 1.1 对master02 进行初始化配置 1.2 将master01的配置移植到master02 2.负载均衡的部署 3. k8s的web UI界面的搭建 二进制部署k8s集群部署的步骤总结 &#xff08;1&#xff09;k8s的数据存储中中心的搭建…

Spring项目建立过程

1&#xff0c;导入依赖 导入Spring依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency> 2&#xff0c;实现部分 2.1 自动给我们请求返回一个登录页面&am…

【计算机网络】物理层

物理层考虑的是在各种传输媒体上传输比特流&#xff0c;而不是指具体的传输媒体。物理层作用是尽可能地屏蔽媒体之间的差异。 物理层的主要任务是描述与传输媒体有关的一些特性&#xff1a; 机械特性、电气特性、功能特性、过程特性。 数据通信系统可分为&#xff1a;源系统、…

攻击类型分析

攻击类型分析 2018 年&#xff0c;主要的攻击类型 1 为 SYN Flood&#xff0c;UDP Flood&#xff0c;ACK Flood&#xff0c;HTTP Flood&#xff0c;HTTPS Flood&#xff0c; 这五大类攻击占了总攻击次数的 96&#xff05;&#xff0c;反射类攻击不足 3%。和 2017 年相比&…

Vue系列之组件化

文章の目录一、组件化开发思想1、现实中的组件化思想体现2、编程中的组件化思想体现3、组件化规范: Web Components二、组件注册1、全局组件注册语法2、组件语法3、组件注册注意事项4、局部组件注册写在最后一、组件化开发思想 1、现实中的组件化思想体现 标准分治重用组合 2…

k8s网络策略

网络策略介绍 网络策略官方文档&#xff1a;https://kubernetes.io/zh-cn/docs/concepts/services-networking/network-policies/ 网络策略是控制Pod之间如何进行通信的规则&#xff0c;它使用标签来筛选Pod&#xff0c;并在该组Pod之上定义规则来定义管控其流量&#xff0c;…

何为Spring Batch?怎么玩?

何为批处理&#xff1f; 何为批处理&#xff0c;大白话讲就是将数据分批次进行处理的过程。比如&#xff1a;银行对账&#xff0c;跨系统数据同步等。这些处理逻辑一般来说都不需要人工参与就能够自动高效地进行复杂的数据处理与分析。 典型批处理特点&#xff1a; 自动执行&…

ubuntu下使用vscode开发golang程序,从控制台到简单web程序

最近项目要使用go语言开发一个web程序&#xff0c;由于是第一次使用go开发&#xff0c;就将开发过程中的点滴做个记录吧。 1.安装go 1.1 安装 Ubuntu下安装go语言开发运行环境有如下两种方法 &#xff08;1&#xff09;方法一&#xff1a;使用命令 sudo apt install golan…

天天刷 B站,了解他们的评论系统是如何设计的吗?

今天给大家分享 B站的评论系统的 组件化、平台化建设 通过持续演进架构设计&#xff0c;管理不断上升的系统复杂度&#xff0c;从而更好地满足各类用户的需求。 基础功能模块 评论的基础功能模块是相对稳定的。 1. 发布评论&#xff1a;支持无限盖楼回复。 2. 读取评论&am…

异步编程解决方案 Promise

1. 回调地狱 2. Promise 的使用 3. Promise 的状态 4. Promise 的结果 5. Promise 的 then 方法 6. Promise 的 catch 方法 7. 回调地狱的解决方案 1. 回调地狱 回调地狱: 在回调函数中嵌套回调函数 因为 ajax 请求是异步的&#xff0c;所以想要使用上一次请求的结果作为请求…

ADI Blackfin DSP处理器-BF533的开发详解21:RTC实时时钟的原理及应用(含源码)

硬件准备 ADSP-EDU-BF533&#xff1a;BF533开发板 AD-HP530ICE&#xff1a;ADI DSP仿真器 软件准备 Visual DSP软件 硬件链接 功能介绍 ADSP-BF53x 处理器上集成了一个实时时钟&#xff08;RTC&#xff09;模块&#xff0c;板卡上设计了一个专门用于 RTC 时钟源的晶体32.7…

如何修复错误:无法下载 metadata repo appstream

如何修复错误&#xff1a;无法下载 metadata repo appstream 如果您出于某种原因仍在积极使用CentOS 8&#xff0c;您可能在尝试更新系统或只是安装软件包时遇到以下错误。 Error: Failed to download metadata for repo appstream: Cannot prepare internal mirrorlist: No …

Linux内存管理

平时我们说计算机的“计算”两个字&#xff0c;其实说的就是两方面&#xff0c;第一&#xff0c;进程和线程对于CPU的使用&#xff1b;第二&#xff0c;对于内存的管理。——这个是对计算机的理解的两个大方面&#xff0c;面试中问到的场景设计题可以尝试从这两个角度出发。 可…

Gradle学习笔记之项目生命周期及settings文件

文章目录Gradle项目的生命周期settings文件Gradle项目的生命周期 Gradle项目的生命周期分为初始化->配置->执行三步&#xff0c;如下图所示&#xff1a; 初始化阶段主要目的是初始化构建&#xff0c;分为执行初始化脚本和执行设置脚本两步&#xff0c;前者在每个项目构…