六、ROS小车闭环控制:实现过程中的一些经验总结(C语言微积分,读写文档)

本文主要是介绍六、ROS小车闭环控制:实现过程中的一些经验总结(C语言微积分,读写文档),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

--------------------------------------------------------------------闲白区----------------------------------------------------------------------------

继续围绕闭环谈谈遇到的一些问题和解决办法。由于我的工作还没做完,虽然不是很复杂,也暂时不能公开所有内容,希望对有类似问题的朋友有所帮助。

我个人理解工程应用方面的编程,就好像搭积木,你学习别人的搭建方法,临摹别人的积木块变成自己的,自己又一路积累过来。

当需要搭建新的东西时,打磨一下已有的积木块就可以搭建了。最重要的是理解、分解之后,积累到自己的知识库。时间久了,直觉也就有了。

工程应用或者实验室使用常用的功能有:

1、微积分

2、通讯

3、文档记录

4、滤波

这四个百度一下或者github上翻翻就有很多成熟的东西,我们这里针对自己编写的ROS程序,对1,3进行进行一下分析。

而通讯部分,ROS已经帮你解决了linux内部的问题,如果拉windows入伙,那就是另一个问题了,我的方式是采用socket进行,这里面涉及到粘包之类的复杂问题,windows有很多成熟的库,用起来比较简单,而linux我翻了几天没有特别好的简单模式可循,等我后续找到再说。滤波部分,无外乎均值,中值,高通,低通,卡尔曼,等等,其实也是C程序的通用问题,我以往都是百度+github上找,目前还没涉及到,也暂且搁置。

因此,本篇只说微积分和文档记录,其他有时间再说。其实,也不算是新鲜的,跟我在windows里面实现的大同小异,只是这里要结合ROS小车来谈。(为啥不能把字体改小了呢,莫名其妙?!)

--------------------------------------------------------------------正文分割线----------------------------------------------------------------------------

相关程序已经可有可无的开源了,附带实现了一下基本控制算法,参见下一篇(七)。

〇、准备工作

在roboware中增加“.h”头文件,将你的变量、函数在这里声明,可以节约很多时间。

在include中,右键点击你的节点名字,选择添加头文件,然后点击任意一个地方,命名你的头文件,在出现的下拉栏里面选择添加到库文件,roboware就帮你做好工作了,可以去你的cmakelist中看看到底发生了什么变化。

在你想使用这个头文件的时候,直接:

#include <你的项目名/你的头文件.h>

例如我的:#include <learning_com/Data_def.h>,不需要把include这个路径写出来

涉及到的步骤截图如下:

步骤1:

步骤2:

一、微积分的C语言实现

本部分主要参考:《计算方法(C语言版)》。

1、保证程序有循环,ROS的话,在某个callback里面做就行了,将数据循环写入一个足够大的数组,用一个for循环,每次调用的时候将每个位置的数据依次移位就行;

2、在随时更新数组的情况下,使用书中的中心微分法和梯形积分法的函数为:

/*********************************************************/
/* 此处为中心微分法,给定i为0即为x0,                         */
/* 则Storage[0]为f(x0),此方法为求f(x)在x1处的微分值;         */
/* 数组中第0位为最新数据!                                  */
/* 形参分别代表:微分时间t,默认为0,标志位                   */
/********************************************************/
double CentralDerivative(double time, int i, int signature) {double def = 0;if (signature == 0) {def = (Storage[i] - Storage[i + 2]) / (2 * time);} else if (signature == 1) {} return def;
}/***************************************************************/
/* 此处为梯形积分法,给定i为0即为x0,                               */
/* 则Storage[0]为f(x0),此方法为求f(x)在x0~x1处的积分值;            */
/* 数组中0位为最新数据,注意积分是累加的                             */
/* 形参分别代表:积分时间,默认为0,标志位                          */
/*************************************************************/
double TrapezoidIntegral(double time, int i, int signature) {double intergral = 0;if (signature == 0) {intergral = (Storage[i] + Storage[i + 1]) *(time * 0.5); /// f(x0)~f(x1)的积分,1/2=0.5} else if (signature == 1) {}return intergral;
}

这里需要注意的是:微分是对数组中的第二个数进行微分,因此实际应用中要注意数据的对齐,通常来说,如果频率很高,由于数据的是连续的,这个影响并不大;这里面的积分实际上就是梯形的面积,因此是有误差的,而且这个面积需要进行累加,才是最后的积分,累加操作相信你自己就能实现。

显然,这都是近似计算,是有一定误差的,数据变化越剧烈,这个误差就越大。或许还有更高级的手段,感兴趣的朋友可以继续研究。

二、轨迹的设定和分解

这里由于程序结构略微复杂一点,不能很好写出来,能看懂意思,自己实现就好。

移动小车的闭环控制,没有期望轨迹就如同盲人摸象。

这里就需要说说ROS小车的运动方式了。

我们知道,核心的控制实现是我们发给TWIST信息中的“/cmd_vel”,如何给定请参考第一篇:

pub_ = n_.advertise<geometry_msgs::Twist>("/cmd_vel", 10);

这里面要注意的是,/cmd_vel有如下控制量:

cmd_vel.linear.x//x方向的线速度,对于一般的轮式机器人运动学就是V。单位m/s
cmd_vel.linear.y//差速转向的机器人此项无效,只对全向机器人有效
cmd_vel.linear.z//地上跑的肯定不行了,这个z轴是朝天的
cmd_vel.angular.x//欧拉角中的roll角转角速度,单位rad/s
cmd_vel.angular.y//pitch角转角速度
cmd_vel.angular.z//yaw角转角速度,偏航角速度,对于一般的轮式机器人运动学就是Omega

对于轮式机器人,受其运动能力限制,最多只有x,y的线速度和yaw偏航角可控。

欧拉角的解释,请百度一下。

那么我们知道了,对于我目前的差速ROS小车来说,只要控制前进速度V和转向速度Omega就好(也就是cmd_vel.linear.x和cmd_vel.angular.z),这里面涉及一些移动机器人运动学,这里面没什么理论创新,大同小异。

那么怎么得到想要的轨迹呢。

这里以圆形为例,由于x=r*cos(t),y=r*sin(t),r为半径,t为变化的时间:

 

//这是一个在某处被循环调用的函数,假设更新频率为30hz,Robo_State_cur为存储机器人运动学信息的结构体,
//在头文件中声明和初始化,而v代表速度,w代表角速度//获取期望位姿函数
void get_desired_pose() {//给入每个时刻的期望位置,通过期望位置得到得到期望的速度// traj_counter/30*pi*2 代表1秒跑完一圆,r=1为圆的半径,t=60,60秒一圈double r = 1;double t = 60;Robo_State_cur.X_c = r * cos(traj_counter / 30 * 2 * pi / 2 / t);//机器人X方向的期望位置Robo_State_cur.Y_c = r * sin(traj_counter / 30 * 2 * pi / 2 / t);//机器人Y方向的期望位置TransStorage(4); //更新相关数组time_interval = 1/30;double X_d = 0;double Y_d = 0;X_d = CentralDerivative(time_interval, 0, 0);//调用微分函数,从位置得到速度Y_d = CentralDerivative(time_interval, 0, 1);Robo_State_cur.V_c =sqrt(X_d * X_d + Y_d * Y_d); //m/s,机器人X方向的期望速度Robo_State_cur.W_c = Robo_State_cur.V_c / r;  //rad/s,机器人yaw角的期望角速度,因为是圆形期望轨迹,v=wrdouble temp = Robo_State_cur.Theta_c;Robo_State_cur.Theta_c = temp + TrapezoidIntegral(time_interval, 0, 2); //rad,机器人yaw角的累积角速度TransStorage(5); //更新相关数组traj_counter++;
}

三、读文档和写文档

读文档可以得到我们想要的数据,作为参数输入,参考这个就可以,当然涉及到数据分段,那又复杂了,暂且不谈:

https://blog.csdn.net/stpeace/article/details/12404925

写文档的话,虽然有bag文件,但是这样并不方便,因此这里参考以下两个整合一下:

https://blog.csdn.net/snoopy_neu/article/details/80962217

https://blog.csdn.net/Start_From_Scratch/article/details/52337722

文件头写成当前系统时间的方式,保证每次写文档都是新的名字,还是结合第一篇文章进行比照,会比较好理解:

//文档相关头文件
#include <fstream>
#include <iostream>
#include <string>#include <sys/time.h>
#include <time.h>///其他的东西
void SubscribeAndPublish::callback(const acl_msgs::ViconState &Vi_input) {ros::Rate loop_rate(30);geometry_msgs::Twist output;///其他的程序//***************写文档开始********************************************//if (counter == 0) {time_t t = std::time(0);struct tm *now = std::localtime(&t);std::string file_name;std::string file_path;file_path = "//home//peterli//catkin_ws_com//data_history//";//你想存储的绝对路径// the name of bag file is better to be determined by the system timefile_name = int2string(now->tm_year + 1900) + '-' +int2string(now->tm_mon + 1) + '-' + int2string(now->tm_mday) +'-' + int2string(now->tm_hour) + '-' + int2string(now->tm_min) +'-' + int2string(now->tm_sec) + ".txt";file_name = file_path + file_name; // 这样就是路径+文件名in.open(file_name,std::ios::trunc); // ios::trunc表示在打开文件前将文件清空,由于是写入,文件不存在则创建time_file = 0;} else {if (n_.ok()) {ROS_INFO("FILE is writing line: %d ", counter);time_file = time_file + time_interval;in << std::fixed << std::setprecision(6) << time_file / 1000 << "\t"<< Robo_State_des.V_c << "\t" << Robo_State_des.W_c << "\t"<< Robo_State_cur.V_a << "\t" << Robo_State_cur.W_a << std::endl;
//这里面包括了保留六位小数和前后对齐的操作,这样直接导入matlab就可以很方便绘图分析等} else {ROS_INFO("FILE is closed ");in.close(); //关闭文件,实际上我发现这个并没起作用,不过并不影响……}}//***************写文档结束********************************************//counter++;pub_.publish(output);loop_rate.sleep();
}//写文档字符转化
std::string int2string(int value) {std::stringstream ss;ss << value;return ss.str();
}

四、小结

这里没有说很多C++的基础用法和概念,比如定义、声明,如何定义结构体,如何定义全局变量,如何定义宏变量等等,建议初学者去看鸡啄米大神的博客,百度一下就有了。

 

附录:

还是稍微说一下Linux中的结构体定义,这个我也查了一下才成功。跟Windows的略微有点区别,感觉更随意了。

在前面说的头文件中(当然,放在CPP的最上面也可以)这样直接定义加上初始化即可:

typedef struct _paras{
double a;
int b;
string c;
}paras;paras paras1 = {0,0,a};

在你的CPP中就可以直接使用了,这已经是全局变量。

后面还有很多工作要做,感觉有什么有价值和共性的问题,再分享一下,希望可以帮助别人少走点弯路。

 

这篇关于六、ROS小车闭环控制:实现过程中的一些经验总结(C语言微积分,读写文档)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/349033

相关文章

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

作业提交过程之HDFSMapReduce

作业提交全过程详解 (1)作业提交 第1步:Client调用job.waitForCompletion方法,向整个集群提交MapReduce作业。 第2步:Client向RM申请一个作业id。 第3步:RM给Client返回该job资源的提交路径和作业id。 第4步:Client提交jar包、切片信息和配置文件到指定的资源提交路径。 第5步:Client提交完资源后,向RM申请运行MrAp

10. 文件的读写

10.1 文本文件 操作文件三大类: ofstream:写操作ifstream:读操作fstream:读写操作 打开方式解释ios::in为了读文件而打开文件ios::out为了写文件而打开文件,如果当前文件存在则清空当前文件在写入ios::app追加方式写文件ios::trunc如果文件存在先删除,在创建ios::ate打开文件之后令读写位置移至文件尾端ios::binary二进制方式

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

活用c4d官方开发文档查询代码

当你问AI助手比如豆包,如何用python禁止掉xpresso标签时候,它会提示到 这时候要用到两个东西。https://developers.maxon.net/论坛搜索和开发文档 比如这里我就在官方找到正确的id描述 然后我就把参数标签换过来

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount