项目实训 - 智能车系统 - 第六周记录

项目实训 - 智能车系统 - 第六周记录

日期:3.28 – 4.03

项目进度

本周工作进展:

  • 完成了所有中间话题的移植
  • 将参数服务器改成读取文件的形式

(这周状态不太好,工作进展不是很大。,。)

1、剩余话题的移植

上周完成了TF框架的搭建,这周开始着手其他话题的移植。

在这里插入图片描述

先不管数据包发布的原始数据、以及发布给rviz的话题,剩余的节点之间的话题还有3个

  • /odometry/imu_Incremental
    • imu节点发布;imu节点、image节点接收(imu节点中维护了两个类,是一个类发布的话题,另一个类订阅)
  • /lio_sam/mapping/odometry_Incremental
    • map节点发布;imu节点接收
  • /lio_sam/mapping/odometry
    • map节点发布;imu节点接收

现在开始对这三个节点的移植。

对于这三个话题的移植具体过程不做具体说。展示一下移植后的流程图:

在这里插入图片描述

在移植过程中遇到了一些问题,在后面的bug记录中记录。

2、参数服务器移植

ros原本的参数服务器机制:将一些参数记录在.yaml文件中,启动ros项目时,参数服务器读取并解析yaml文件中的数据,赋值给变量,以便于每个节点使用。

我的做法是:使用简单的.txt文本文件替代yaml文件,通过文件流读取txt文件,按照我自己指定的规范解析参数;然后让每个类继承读取类

  • 后面知道c++有处理yaml文件的库,但是已经完成了我自己设想的做法,所以后续如果有时间的话会使用给yaml库重写

utility_struct.txt:

在这里插入图片描述

utility_struct.h

// 从文件中读取
    ParamServer_struct()
    {
        ifstream infile;
        infile.open("/home/cislc2019/catkin_ws/src/LIO-SAM-master/config/utility_struct.txt");
        if(!infile) {
            cout<<"open utility config files error!"<<endl;
            return ;
        }
        //读取最原始的文本数据,然后开始解析
        string temp_name;
        while(infile>>temp_name) 
        {
            inital_data_from_file.push_back(temp_name);
        }
        for(int i = 0; i < inital_data_from_file.size(); i++)
        {
            string key;
            string value;
            splitString(inital_data_from_file[i], key, value);
            key_value.insert(std::make_pair(key, value));
        }
//splitString函数:将字符串解析成键值对

    void splitString(string init_str, string& key_str, string& value_str)
    {
        int middle_number = init_str.find(":");
        key_str = init_str.substr(0,middle_number);
        value_str = init_str.substr(middle_number + 1,init_str.length() - middle_number - 1);
    }
        
//定义了一些类解析函数,按照不同类型将数据进行解析
    }
    /**
     * @brief 给string类型的变量赋值
     * 搜索vector,如果有对应的key,则转换成对应类型后赋值;否则赋默认值
     * 
     * @param key 参数名称
     * @param out 变量
     * @param default_value 默认参数值
     */
    void param_string(string key, string &out, string default_value) 
    {
        auto search = key_value.find(key);
        if(search != key_value.end()) {
            string value = search->second;
            
            //string不需要转换 直接赋值
            for(int i = 0; i < value.size(); i++)
            {
                if(value[i] == '-') value[i] = '/';
            }
            out = value;
            cout<<out<<endl;
        }
        else {
            //没有搜索到,赋值默认值 
            out = default_value;
        }
    }

    void param_bool(string key, bool &out, bool default_value)
    {
        auto search = key_value.find(key);
        if(search != key_value.end()) {
            string value = search->second;
            
            if(value == "true") {
                out = true;
            } else {
                out = false;
            }
        } 
        else {
            out = default_value;
        }
        cout<<out<<endl;
    }

    void param_float(string key, float &out, float default_value)
    {
        auto search = key_value.find(key);
        if(search != key_value.end()) {
            string value = search->second;
            //string转float
            float real_value = stof(value);
            out = real_value;
        } 
        else {
            out = default_value;
        }
        cout<<out<<endl;
    }
......

技术难点

正常工作

bug记录

bug1 ros::Time定义错误

发现是因为ros::Time类型中的sec和nsec是uint32_t,而我之前一直用的是int32_t

bug2 如何解决接收端connect函数晚于create函数的问题

由于多进程且多话题的原因,会出现接收端早于发布端初始化的情况。

如何解决接收端connect函数晚于create函数的问题

  • 先connect的话,得到的共享内存首地址会与后面的create的首地址不一样(其实是建立了两块共享内存)
  • 解决方案:在connect的多线程函数里,在第一次执行实际代码段之前一直重复连接,并定义一个标记变量由于判断,
  • 当发现可以进入实际代码段后,意味着匹配到了真正的create出来的首地址
  • 这时候就可以修改标记变量,不在重复连接
    while(true) 
    {
        if(!is_real_point) { //判断是否连接到了正确的共享内存
            unMap(subLaserOdometry_sharemem, sizeof(nav_msgs_Odometry_List));
            subLaserOdometry_sharemem = connectShareMemory("/home/cislc2019/catkin_ws/src/LIO-SAM-master/src/communication_LVI_SAM/files/mapOptmization_files/lio_sam-mapping-odometry",sizeof(nav_msgs_Odometry_List));
            queue = (nav_msgs_Odometry_List* ) subLaserOdometry_sharemem->point;
        }
        now_number = queue->count;
        if(now_number != last_number)
        {     
            is_real_point = true; //修改标记,不再进行连接操作
            ...

bug3 话题的发布和接收频率不匹配问题

问题:话题的发布频率较快,而处理函数速度较慢,导致频率无法匹配

  • 在回调函数中加了锁,所以可以认为即使是多线程处理回调函数,同一时刻也应该只有一个回调函数再执行
  • 在我们自定义的函数中,回调函数的处理频率会逐步落后于发布端
  • 但是ros原生机制下,则不会落后

经过观察打印后发现:发送端存在等待机制

  • 会有一个发送端的时间间隔远大于其他打印
  • 结合tcp的确认接收机制
  • 以及noDelay设置为true和false,雀氏会影响send的等待频率

解决方案

  • 在发送端使用一个变量强制阻塞发送端,直到处理完成
  • 具体机制如下
    • 定义一个全局变量:表示接收端的处理进度
    • 在发送端定义一个类变量,标识当前发送的进度
    • 当二者差值达到一定数值后,阻塞发送端,直到进度相同
    • 差值:5
    • 注意,没写判定是否int越界的:暂时也不用

但是当时导致项目跑不通的真正原因时把tf删了,rviz搜索不到base_link了

但是雀氏意外的发现了ros的一些隐藏的小机制

  • ros的topic话题机制使用的是tcp通信
    • 正常情况下,如果发送队列阻塞的数目达到一定数量,即使没有到达发送队列的预设长度,也会阻塞发送,直到接收端完全处理所有的缓冲数据;
      • 针对这个现象,虽然没有读源码,但是分析应该是tcp的确认帧机制导致的发送队列阻塞(计网没白学)
    • 对于sub函数里的一个参数: ros::TransportHints().tcpNoDelay(false/true),用于设置是否对tcp进行较低延迟的传输
      • 虽然对于官方的解释没咋看懂,但现象是:使用这个参数会使发送端的缓冲数据在较少的情况下也会阻塞
        • 相当于减少了发送端缓冲队列的拥挤程度

经过测试,发送端不进行自定义的阻塞也可以运行,但是为了更加的还原原始项目,所以保留了发送端的阻塞

bug4 O3优化问题

有关多线程的编译优化的问题。

当项目开了O3优化后,会对项目做出一些比较激进的优化

  • 之前一直存在的问题:

  • 问题代码:

        int last_number = 0;
        int now_number = 0;
        int now_pos = 0;
    
        lio_sam_msgs_cloud_info_List*  queue_cloud_info = (lio_sam_msgs_cloud_info_List* ) subCloud_sharemem->point;
        while(true) {
    
                now_number = queue_cloud_info->count;
                now_pos = queue_cloud_info->now;
    
                //now_number = 1;
                // if(number == 0) {
                //     std::cout<<number<<' '<<now_number<<'m';
                //  number++;
                // }
    
            if(now_number != last_number) {
                //std::cout<<"sub start 11"<<endl;
    
                // 说明有数据更新了
                last_number = now_number;
                ...
            }
        }
    

    经过O3优化后,if(now_number != last_number) { 被优化掉,一直不执行(之前的博客记录过)。

    原因是知道的:第5行的赋值操作,优化认为这个赋值一直没有变化过,所以后续不会进入if

    • 但是实际上这地方是通过共享内存来进行赋值,所以实际上值是会变化的,但是优化认为没有变化
  • 解决:在第五行的queue_cloud_info前加一个关键字volatile,该关键字的作用是强制声明该变量是会变化的,从而不会将下面的死代码优化掉

    • 小插曲:之前在now_number前加过volatile,没用(经过师兄的点拨发现问题其实在共享内存那里)

另一个问题:

  • 跟楼上的问题类似,也是O3优化将死代码优化掉

       if(tcp_publisher_number - tcp_subscriber_number > 5) {
            //认为发送端应该等待接收端了 所以进行阻塞
            while(tcp_publisher_number != tcp_subscriber_number + 1) {
                 //cout<<"in send : "<<tcp_publisher_number<<' '<<tcp_subscriber_number<<endl;
                //在这个过程中接收端会逐渐处理 缓存的数据,知道处理完成接收的所有数据,开始等待
                // if(temp_num == 0) {
                //     cout<<"in send cout"<<tcp_publisher_number<<' '<<tcp_subscriber_number<<endl;
                //     temp_num++;
                // }
                ;
            }
        }
    
  • tcp_publisher_number和tcp_subscriber_number都是全局变量

  • 这里的tcp_subscriber_number不会在当前函数内被修改,而是在同一时间执行的某函数(多线程)修改,但是优化还是认为这个值不会被修改,所以继续优化掉

  • 解决:

    volatile int tcp_publisher_number = 0;
    

到今天为止

  • 所有的中间话

其他

到第六周为止的工作总结

  • 所有的中间话题都已经移植完成
  • 自定义的tf框架也已经写出来了,后续可以代替ros的tf机制
  • 重写了一些pcl和tf库的类和函数
  • 重写了ros里的一些函数
  • 定义了若干用于中间移植的临时的函数
  • 给可视化(我的上游)写了个小的数据转换接口
  • 重写了参数服务器

后续工作

  • 传感器获取的数据的处理
    • 雷达
    • imu
  • 与可视化工具的对接
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: ros::transporthints().tcpnodelay()是ROS中的一个函数,用于设置TCP传输的延迟。它可以通过设置TCP_NODELAY选项来禁用Nagle算法,从而减少数据传输的延迟。这个函数可以在ROS节点的传输设置中使用,以优化ROS节点之间的通信效率。 ### 回答2: ros::transporthints().tcpnodelay()是ROS中一个常用的传输选项,在ROS中用于提高网络通信的效率。其作用是禁用TCP的Nagle算法,即在接收端接收到一个数据包之后,不需要等待一个较小的数据包的到来,而是立即发送数据包,这样可以降低网络延迟,提高实时性,减少数据传输的时间。 在ROS中,ros::Transporthints可以用来设置一些传输选项,其构造函数为: ros::TransportHints(const std::string& transport, const std::string& tcp_nodelay, bool anonymous_name) 其中,第二个参数tcp_nodelay就是用来设置是否启用TCP的Nagle算法,默认为false,即不启用。如果需要启用,可以将其设置为true,示例代码如下: ros::TransportHints hints; hints.tcpNoDelay(true); ros::Publisher pub = n.advertise<MsgType>("topic", 10, hints); 需要注意的是,启用tcp_nodelay会增加网络带宽的使用,因为每个小数据包都会立即发送,同时也会增加进程的CPU使用率,可能会对系统的资源造成一定压力。 综上所述,ros::transporthints().tcpnodelay()在ROS中的作用主要是提高网络通信的实时性和效率,但需要根据具体情况决定是否启用。 ### 回答3: ROS是一个强大且广泛使用的机器人操作系统,提供了许多功能强大的工具,其中之一是Transport Hints。 Transport Hints是一种机制,它允许用户为ROS通信协议中的某些特定实例设置参数。其中,ros::TransportHints().tcpNoDelay()参数被用于改善ROS通信的性能。 TCP No Delay是一种Socket选项的名称,它的作用是在发送数据前不等待TCP缓冲区的填充,以达到实时传输数据的目的。在ROS中,Transport Hints().tcpNoDelay()选项可以用来控制数据传输时缓冲的填充和数据包的发送。 具体来说,这个选项会取消ROS通信中多个TCP socket的Nagle算法,从而避免了可能的延迟和缓存过多的数据。这样可以更快地向订阅者发送数据,减少网络传输时间和延迟,提高系统的实时性和反应速度。此外,对于一些对实时性要求较高的应用,例如无人机导航和视觉SLAM等,这个选项尤为重要。 总之,Transport Hints().tcpNoDelay()选项是ROS通信协议中的一个重要工具,可以帮助用户优化ROS通信性能,提高系统的实时性和稳定性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值