在ROS上快速验证PID算法

news2024/11/27 4:23:40

在ROS上快速验证PID算法

前言

最近有在外面出差授课的工作任务,其中有一个环节是给大家讲述PID相关的内容,在制作相关PPT的时候查询了很多资料,但是写着写着突然意识到一个问题,PID已经在控制专业学习过程以及工程开发时间中那么常见了,继续去照本宣科的讲解其理论是否还有必要,随即开始想不如开发一个小DEMO,在ROS环境里验证PID算法实例。这也是这篇文章的由来了。

PID算法简介

  • 比例(Proportional)(P):

    计算当前偏差(设定值与实际值的差距),并乘以比例增益(Kp)。

    输出对系统的当前状态进行纠正。

  • 积分(Integral)(I):

    累积偏差随时间的总和,并乘以积分增益(Ki)。

    解决由于比例控制引入的静态误差,确保系统最终能够达到设定值。

  • 微分(Derivative)(D):

    计算偏差变化率,并乘以微分增益(Kd)。

    抑制系统的振荡,提高系统的稳定性。

这里有一张比较形象的图可以用于简述其工作状态。

算法实现

首先实现其基类接口

    // pid.h
    #ifndef PID_H
    #define PID_H

    class PIDImpl;

    class PID {
    public:
        PID(double dt, double max, double min, double Kp, double Kd, double Ki);
        double calculate(double setPoint, double processValue);
        ~PID();

    private:
        PIDImpl* pimpl;
    };

    #endif //PID_H

再实现其算法计算


    #include "pid.h"
    #include <iostream>
    #include <cmath>

    class PIDImpl {
    public:
        PIDImpl(double dt, double max, double min, double Kp, double Kd, double Ki);
        ~PIDImpl();
        double calculate(double setPoint, double processValue);

    private:
        double _dt;
        double _max;
        double _min;
        double _Kp;
        double _Kd;
        double _Ki;
        double _pre_error;
        double _integral;
    };

    PID::PID(double dt, double max, double min, double Kp, double Kd, double Ki) {
        pimpl = new PIDImpl(dt, max, min, Kp, Kd, Ki);
    }

    double PID::calculate(double setPoint, double processValue) {
        return pimpl->calculate(setPoint, processValue);
    }

    PID::~PID() {
        delete pimpl;
    }

    PIDImpl::PIDImpl(double dt, double max, double min, double Kp, double Kd, double Ki) :
            _dt(dt),
            _max(max),
            _min(min),
            _Kp(Kp),
            _Kd(Kd),
            _Ki(Ki),
            _pre_error(0),
            _integral(0) {
    }
    /*
     - 比例(Proportional)(P):

        计算当前偏差(设定值与实际值的差距),并乘以比例增益(Kp)。
        输出对系统的当前状态进行纠正。

    - 积分(Integral)(I):

        累积偏差随时间的总和,并乘以积分增益(Ki)。
        解决由于比例控制引入的静态误差,确保系统最终能够达到设定值。

    - 微分(Derivative)(D):

        计算偏差变化率,并乘以微分增益(Kd)。
        抑制系统的振荡,提高系统的稳定性。
    */
    double PIDImpl::calculate(double setPoint, double processValue) {
        double error = setPoint - processValue;

        double Pout = _Kp * error;

        _integral += error * _dt;
        double Iout = _Ki * _integral;

        double derivative = (error - _pre_error) / _dt;
        double Dout = _Kd * derivative;

        double output = Pout + Iout + Dout;

        if (output > _max)
            output = _max;
        else if (output < _min)
            output = _min;

        _pre_error = error;

        return output;
    }

    PIDImpl::~PIDImpl() {
    }

最后如何实现其算法调用呢?

    //control.cpp
    #include "ros/ros.h"
    #include <tf/transform_listener.h>
    #include <nav_msgs/Odometry.h>
    #include <visualization_msgs/Marker.h>
    #include <std_srvs/Empty.h>
    #include "geometry.h"
    #include "pid.h"

    tf::Point Odom_pos;
    double Odom_yaw, Odom_v, Odom_w;
    ros::Publisher cmd_vel_pub, marker_pub;
    ros::Subscriber odom_sub;
    int num_slice = 50;
    double maxSpeed = 0.5, distanceConst = 0.5;
    // double dt = 0.1, maxT = M_PI, minT = -M_PI, Kp = 0.3, Ki = 0.05, Kd = 0.01;
    double dt = 0.1, maxT = M_PI, minT = -M_PI, Kp = 1, Ki = 0.02, Kd = 0.1;
    // double dtS = 0.1, maxS = maxSpeed, minS = 0.0, KpS = 0.08, KiS = 0.01, KdS = 0.005;
    double dtS = 0.1, maxS = maxSpeed, minS = 0.0, KpS = 0.1, KiS = 0.02, KdS = 0.1;


    void odomCallback(const nav_msgs::Odometry odom_msg) {
        tf::pointMsgToTF(odom_msg.pose.pose.position, Odom_pos);
        Odom_yaw = tf::getYaw(odom_msg.pose.pose.orientation);
        Odom_v = odom_msg.twist.twist.linear.x;
        Odom_w = odom_msg.twist.twist.angular.z;
    }

    void displayLane(bool isTrajectoryPushed, Geometry &geometry) {
        static visualization_msgs::Marker path;
        path.type = visualization_msgs::Marker::LINE_STRIP;
        path.header.frame_id = "odom";
        path.header.stamp = ros::Time::now();
        path.ns = "odom";
        path.id = 0;
        path.action = visualization_msgs::Marker::ADD;
        path.lifetime = ros::Duration();
        path.color.b = 1.0;
        path.color.a = 1.0;
        path.scale.x = 0.02;
        path.pose.orientation.w = 1.0;

        static int slice_index = 0;

        VECTOR2D *prev = nullptr, *current = nullptr;

        while (path.points.size() <= num_slice) {
            geometry_msgs::Point p;
            float angle = slice_index * 2 * M_PI / num_slice;
            slice_index++;
            p.x = 4 * cos(angle) - 0.5;
            p.y = 4 * sin(angle) + 1.0;
            p.z = 0;

            path.points.push_back(p);

            if (!isTrajectoryPushed) {
                VECTOR2D *temp = new VECTOR2D(p.x, p.y);
                geometry.trajectory.push_back(*temp);
                current = temp;

                if (prev != nullptr) {
                    geometry.path.push_back(geometry.getLineSegment(*prev, *current));
                }
                prev = current;
            }
        }

        if (prev != nullptr && current != nullptr && current != prev)
            geometry.path.push_back(geometry.getLineSegment(*prev, *current));

        marker_pub.publish(path);
    }

    int main(int argc, char **argv) {
        ros::init(argc, argv, "control");
        ros::NodeHandle n("~");
        tf::TransformListener m_listener;
        tf::StampedTransform transform;
        cmd_vel_pub = n.advertise<geometry_msgs::Twist>("cmd_vel", 1);
        marker_pub = n.advertise<visualization_msgs::Marker>("visualization_marker", 1);
        odom_sub = n.subscribe("odom", 10, odomCallback);
        ros::Rate loop_rate(10);

        geometry_msgs::Twist tw_msg;
        Geometry geometry;

        int frame_count = 0;
        PID pidTheta(dt, maxT, minT, Kp, Kd, Ki);
        PID pidVelocity(dtS, maxS, minS, KpS, KdS, KiS);

        double omega = 0.0;
        while (ros::ok()) {
            if (frame_count == 0)
                displayLane(false, geometry);
            else
                displayLane(true, geometry);

            double speed = maxSpeed;
            VECTOR2D current_pos;
            current_pos.x = Odom_pos.x();
            current_pos.y = Odom_pos.y();

            Geometry::LineSegment *lineSegment = geometry.getNearestLine(current_pos);
            Geometry::LineSegment lineSegmentPerpen = geometry.getMinimumDistanceLine(*lineSegment, current_pos);
            Geometry::LineSegment *nextLinesegment = geometry.getNextLineSegment(lineSegment);

            double targetDistanceWithEnd = geometry.getDistanceSquared(current_pos, lineSegment->endP);
            double targetDistanceWithStart = geometry.getDistanceSquared(current_pos, lineSegment->startP);
            double targetAngleWithPerpen = geometry.getGradient(current_pos, lineSegment->endP);

            double angleError = 0.0;

            VECTOR2D target = lineSegment->endP;
            double targetAngle = geometry.getGradient(current_pos, target);
            double distanceToClosestPath = abs(lineSegment->disatanceToAObj);
    double targetAnglePerpen = geometry.getGradient(current_pos, lineSegmentPerpen.endP);
            if (distanceToClosestPath < distanceConst) {

                double directional = targetAngle;

                double discripancy = targetAnglePerpen - directional;
                discripancy = geometry.correctAngle(discripancy);

                discripancy = 0.5* discripancy / distanceConst * abs(distanceToClosestPath);

                double combined = targetAngle + discripancy;

                angleError = combined - Odom_yaw;

            } else {
                angleError = targetAnglePerpen - Odom_yaw;
            }
            if (targetDistanceWithEnd < 0.5) {
                double futureAngleChange = nextLinesegment->gradient - lineSegment->gradient;
                futureAngleChange = geometry.correctAngle(futureAngleChange);
                futureAngleChange = futureAngleChange / distanceConst * abs(targetDistanceWithEnd);;
                double combined = targetAngle + futureAngleChange;
                angleError = combined - Odom_yaw;
            } else {
                angleError = geometry.getGradient(current_pos, lineSegmentPerpen.endP) - Odom_yaw;
            }

            double speedError = 0.0;

            if (targetDistanceWithStart < 0.7 || targetDistanceWithEnd < 0.7) {
                double targetDistance = std::min(targetDistanceWithStart, targetDistanceWithEnd);
                speedError = 0.3 * maxSpeed * exp(-abs(targetDistance));
                speed = pidVelocity.calculate(maxSpeed, -speedError);
            }

            angleError = geometry.correctAngle(angleError);
            double omega = pidTheta.calculate(0, -angleError);

            ROS_INFO("Odom_yaw %f, Angle Error: %f , omega: %f Speed %f", Odom_yaw, angleError, omega, Odom_v);

            tw_msg.linear.x = speed;
            tw_msg.angular.z = omega;
            cmd_vel_pub.publish(tw_msg);

            frame_count++;
            ros::spinOnce();
            loop_rate.sleep();
        }

        return 0;
    }

·

此处首先利用ROS中Marker工具绘制了一个半径为4的圆,其是有50个点组成的。为了验证PID,此处将50个点全部存储起来,用于当作基于小车当前位置的最近点。所以可以看到此处还调用了odom话题去获取当前位置。

剩下的就是将目标点和当前位置计算一个相对合适的速度和角速度了。实现上非常简单,但是可以在仿真环境下快速验证PID算法对小车控制的作用。

附件

具体内容请参考

- 在ROS上快速验证PID算法 - 古月居 (guyuehome.com)

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

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

相关文章

量化交易入门(二十五)什么是RSI,原理和炒股实操

前面我们了解了KDJ&#xff0c;MACD&#xff0c;MTM三个技术指标&#xff0c;也进行了回测&#xff0c;结果有好有坏&#xff0c;今天我们来学习第四个指标RSI。RSI指标全称是相对强弱指标(Relative Strength Index),是通过比较一段时期内的平均收盘涨数和平均收盘跌数来分析市…

【YOLOv5改进系列(9)】高效涨点----使用CAM(上下文增强模块)替换掉yolov5中的SPPF模块

文章目录 &#x1f680;&#x1f680;&#x1f680;前言一、1️⃣ CAM模块详细介绍二、2️⃣CAM模块的三种融合模式三、3️⃣如何添加CAM模块3.1 &#x1f393; 添加CAM模块代码3.2 ✨添加yolov5s_CAM.yaml文件3.3 ⭐️修改yolo.py文相关文件 四、4️⃣实验结果4.1 &#x1f39…

HTB devvortex靶机记录

做这个靶机的师傅们我先提一句&#xff0c;不知道是否是因为网速还是其他因素影响&#xff0c;登录后台管理后&#xff0c;有大概率会被其他人挤下去&#xff0c;所以做这道题的师傅可以考虑在没人的时候去做。 打开靶场以后老规矩nmap扫一遍 这里爆出了80端口和22端口&#xf…

解决Veeam做Replication复制或备份任务并发数量少问题

Veeam执行replication复制或者备份任务时&#xff0c;一直都只有两个任务并发在跑&#xff0c;其他同时间任务只能等待前两个任务处理完才可以开始。 解决方法&#xff1a; 进入Veeam-Bacup Infrastructure-Backup Proxies&#xff0c;可以看到VMware Backup Proxy&#xff0…

【并发】第二篇 ThreadLocal详解

导航 一. ThreadLocal 简介二. ThreadLocal 源码解析1. get2. set3 .remove4. initialValue三. ThreadLocalMap 源码分析1. 构造方法2. getEntry()3. set()4. resize()5. expungeStaleEntries()6. cleanSomeSlots()7. nextIndex()8. remove()9. 总结ThreadLocalMap四. 内存泄漏…

@EnableWebMvc 导致自定义序列化器失效

目录 前言 一. 自定义序列化器失效 1.1 EnableWebMvc 的作用 1.2 EnableWebMvc 带来了什么后果 1.3 原理分析 1.4 问题解决 二. 总结 前言 在使用Swagger的时候用 到了EnableWebMvc&#xff0c;发现之前为了解决Long类型、日期类型等自定义序列化器失效了 Configurati…

TransformControls 是 Three.js 中的一个类,用于在网页中进行 3D 场景中物体的交互式操作。

demo案例 TransformControls 是 Three.js 中的一个类&#xff0c;用于在网页中进行 3D 场景中物体的交互式操作。让我们来详细讲解它的输入参数、输出、属性和方法&#xff1a; 输入参数&#xff1a; TransformControls 构造函数通常接受两个参数&#xff1a; camera&#…

企业培训系统功能介绍

在当今知识经济时代&#xff0c;企业的竞争力在很大程度上取决于员工的专业能力和综合素质。为了适应不断变化的市场需求和技术进步&#xff0c;企业需要对员工进行持续有效的培训。一个高效的企业培训系统对企业人才培训至关重要。以下介绍一下企业培训系统的主要功能&#xf…

【Qt 学习笔记】Day1 | Qt 开发环境的搭建

博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;Qt 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Day1 | Qt 开发环境的搭建 文章编号&#xff1a;Qt 学习笔记 / 02 文…

下载huggingface中数据集/模型(保存到本地指定路径)

一. snapshot_download # 1.安装huggingface_hub # pip install huggingface_hubimport osfrom huggingface_hub import snapshot_downloadprint(downloading entire files...) # 注意&#xff0c;这种方式仍然保存在cache_dir中 snapshot_download(repo_id"ibrahimhamam…

线程池详解、核心参数、拒绝策略

什么是线程池 线程池是一种池化技术&#xff0c;它预先创建一组线程&#xff0c;用于执行异步任务。当有新任务到来时&#xff0c;线程池可以立即分配一个线程来处理&#xff0c;而不需要临时创建。这样可以减少因为频繁创建和销毁线程而导致的开销。 线程池的应用场景 高并…

【Go】五、流程控制

文章目录 1、if2、switch3、for4、for range5、break6、continue7、goto8、return 1、if 条件表达式左右的()是建议省略的if后面一定要有空格&#xff0c;和条件表达式分隔开来{ }一定不能省略if后面可以并列的加入变量的定义 if count : 20;count < 30 {fmt.Println(&quo…

前后端分离Springboot 整合使用activiti7教程(一)(全网最细)

activiti7关于SpringBoot前后端分离项目的详细教程 文章目录 activiti7关于SpringBoot前后端分离项目的详细教程一、Activiti工作流概述1.1 什么是工作流1.2 工作流应用场景1.3 什么是Activiti1.4 Activiti开发流程1.5 BPMN 2.0规范是什么1.6 BPMN 2.0 基本流程符号 二、Activi…

Java 实现扫描/识别图片文字 (OCR识别)

图片内容一般无法编辑&#xff0c;如果想要读取图片中的文本&#xff0c;我们需要用到OCR工具。本文将介绍如何在Java中实现OCR识别读取图片中的文字。 所需工具&#xff1a; IDEASpire.OCR for Java - Java OCR组件&#xff0c;支持识别多种语言、字体&#xff0c;可读取JPG、…

2024年大广赛联通沃派命题解析:赛题内容一览

2024大广赛又又又又又出新命题了&#xff0c;它就是助力青少年积极向上&#xff0c;乐观自信&#xff0c;探享多彩人生的5G时代潮牌——联通沃派&#xff0c;让我们来看看命题详情吧&#xff01; 联联通沃派是中国联通面向青少年群体推出的客户品牌&#xff0c;契合目标群体特…

module ‘numpy‘ has no attribute ‘int‘

在 NumPy 中&#xff0c;如果遇到了错误提示 "module numpy has no attribute int"&#xff0c;这通常意味着正在尝试以错误的方式使用 NumPy 的整数类型。从 NumPy 1.20 版本开始&#xff0c;numpy.int 已经不再是一个有效的属性&#xff0c;因为 NumPy 不再推荐使用…

win11 环境配置 之 Jmeter(JDK17版本)

一、安装 JDK 1. 安装 jdk 截至当前最新时间&#xff1a; 2024.3.27 jdk最新的版本 是 官网下载地址&#xff1a; https://www.oracle.com/java/technologies/downloads/ 建议下载 jdk17 另存为到该电脑的 D 盘下&#xff0c;新建jdk文件夹 开始安装到 jdk 文件夹下 2. 配…

不小心将某个分支的本地代码推到另外一个远程分支上

不小心将某个分支的本地代码推到另外一个远程分支上&#xff0c;是不会产生效果的&#xff0c;若提示了“Everything up-to-date ”&#xff0c;说明没有内容被提交到了远程&#xff0c;某个分支的本地代码仍然在本地仓库。 若想将改动的代码提交到另外一个远程分支上&#xf…

送朋友的生日祝福静态页面代码!(小白也能轻松GET!)

Hey亲爱的小白们&#xff01;&#x1f44b; 知道你们想给朋友一个独特又有心的生日祝福&#xff0c;却苦于没有编程基础吗&#xff1f;别担心&#xff0c;来白嫖&#xff01;&#x1f381; &#x1f680;【生日祝福静态页面代码】来啦&#xff01;只需简单几步&#xff0c;就能…

java多线程——概述,创建方式及常用方法

前言&#xff1a; 学习到多线程了&#xff0c;整理下笔记&#xff0c;daydayup!!! 多线程 什么是线程 线程&#xff08;Thread&#xff09;是一个程序内部的一条执行流程。若程序只有一条执行流程&#xff0c;那这个程序就是单线程的程序。 什么是多线程 多线程是指从软硬件上…