多激光时间同步

最近在倒腾多激光时间同步的问题,场景如下:

一辆车上装有3个激光,需要开发一个把3个激光msg都收齐后合并为一帧发送出去的节点。

正常情况下,3个激光均以10HZ频率发送,每一帧3个激光的时间戳几乎相同(称为一组message),而同一个激光两帧之间差100ms。

msg header时间:该激光曝光时的时间(假定曝光是一瞬间完成的),另外多个激光设置成几乎是同时触发曝光,这样他们所携带的时间戳几乎是相同的。

msg 接收时间: callback实际接收到激光的时间(一般会有延时,如激光曝光时间戳是第10000ms,但由于激光内部点云处理也会耗时,所以下游实际接收时间会晚个几毫秒)

示意图如下:

在这里插入图片描述

我使用的是主激光方案,整体思想如下

选定一个激光作为主激光,当收到主激光msg时,判断一组message是否已经收齐,收齐了则pub。

如果没有收齐,则等待50ms;在等待过程中,如果一组message收齐了,则publish并退出等待,如果等待50ms后仍然没有收齐,则只发送已收到的message。

尝试了如下3种实现方案,前2种都有不可解释的问题,放弃了,最后采用了第3种方法:

sleep查询法

主lidar线程每隔5ms查询一次所有msg是否到齐,共循环10次。如果到齐则publish并退出循环。

void lidar_i_callback(const string& topic)
{
    receive_msg(); //处理接收到的msg
    
    if (topic != main_lidar) { // 只有main_lidar才pub
        return;
    }
    
	for (int i = 0; i < 10; i++) {
		receive_one_group_msg = judge_received_one_group_msg();
        if (receive_one_group_msg) {
        	pub(); // pub拼接后的点云
        	return;
        } else {
            usleep(5000); //sleep 50 ms
        }
    }
}

问题:在usleep过程中,所有激光的接受线程仿佛都被sleep住了。

现象是随着时间,非主激光接收得越来越晚。换言之,sleep并没有释放main_lidar线程,main_lidar_callback在sleep期间,其他lidar_callback也没有接收。

**以前听别人说过,有可能是所有callback实际只有一个CPU核,所以一个callback sleep了,所有的callback都会延时,但是我还查了下代码,代码中有明确指定CPU核数量就是Lidar个数。**存疑。

(不过查询法就算sleep没问题,也显得有点low)

条件变量法

主lidar线程使用条件变量wait_for(lock, time, pred)等待,其他激光到齐后,notify主激光线程publish。如果50ms内仍然没有到齐,则主激光强制发送已收到的msg,没收到的则舍弃。

std::mutex _lidar_topics_timestamp_mtx;
std::condition_variable _lidar_topics_timestamp_cv; // 条件变量

void lidar_i_callback(const string& topic)
{
    receive_msg(); //处理接收到的msg
    
    
    if (topic != main_lidar) { // 只有main_lidar才pub
    	receive_one_group_msg = judge_received_one_group_msg();
        if (receive_one_group_msg) { // 一旦接收完全就notify发布,最小延时
        	_lidar_topics_timestamp_cv.notify_all(); 
        }
    } else {
		std::unique_lock<std::mutex> lck(_lidar_topics_timestamp_mtx);
        _lidar_topics_timestamp_cv.wait_for(lck, std::chrono::milliseconds(50), judge_received_one_group_msg);
        pub(); // pub拼接后的点云
    }
}

问题:

  1. wait_for中的judge_received_one_group_msg()判断一直为true,根本没有等待,直接发送了。
  2. 如果让judge_received_one_group_msg()直接返回false,则wait_for()会wait,但无法收到notify信息,每次都是等待50ms后发出,相当于做了50ms的延时。

std::this_thread::sleep_for()法

不知为何stl的stleep_for就可以正常sleep。

  1. 设置_main_lidar_last_pub_time一个保存每次pub时的main lidar时间戳。

  2. 设置publish()函数动作。publish()默认已经接收到一组msg。首先判断当前组msg的main lidar时间戳是否等于_main_lidar_last_pub_time_,如果相等说明已经发过了,直接return。如果不相等,则证明没有发过,则先更新last_pub_main_timestamp_,再pub。

  3. 不论接收到哪个lidar,均查询是否一组msg已经到来,如果到来则直接调用publish()。如果没有到来,如果是主线程,就sleep_for(50),再调用publish()。

    这样如果主激光到来的时候,msg已经收齐了就直接发,没有收齐就sleep_for,其他msg在收齐后会立即pub,不会有延时。

std::atomic_uint64_t _main_lidar_last_pub_time{0}; // 保存每次pub时的main lidar时间戳。

void lidar_i_callback(const string& topic)
{
    receive_msg();
    
	receive_one_group_msg = judge_received_one_group_msg();
    if (receive_one_group_msg) { //无论哪个lidar_callback,只要收齐了就pub
        publish();
    } else {
        // 如果没收齐,且是主lidar线程,则等待50ms发送。
        //等待时间到后,publish()会判断这一组msg是否已经被其他lidar_callback发送过了,如果发送过了会直接return,这样不会重复发送。
        //如果这一组msg没有发送,则只发送已收到的msg。
        if (topic == main_lidar) { 
        	std::this_thread::sleep_for(std::chrono::milliseconds(50));
        	publish();
        }
    }
}

void publish()
{
  if (cur_group_msg_main_lidar_timestamp == last_pub_main_timestamp_) { // 判断是否已经pub过当前组消息
  	return;
  }
  last_pub_main_timestamp_ = cur_group_msg_main_lidar_timestamp; // 更新已经发布的时间戳,  必须放在pub()之前!
  pub(); // pub拼接后的点云
}

注意,publish()中,这两个的先后顺序不能颠倒,不然会出现重复发送的情况,因为pub()也需要时间。

假设main_lidar等待50ms,而pub()耗时60ms。则其他lidar在pub的过程中,main_lidar等待时间也到了,main_lidar也会调用publish(),而此时last_pub_main_timestamp_还没被更新,所以main_lidar认为此组消息还没被发过,就会重复发一次。

主激光方案缺点

如果主激光没有输出了,则所有激光都不会输出,这其实是不冗余的。

非主激光实现的困难

如果想设置成每个激光到来之后,都查一下是否已经收齐一组激光,如果是则发送,如果否则等待。

这里有个问题,是如果确实有一个激光(不知道是哪个)坏了或者来得晚的话,怎么处理。每个激光等多长时间,在哪个激光里强制发送,设计会非常复杂,我现在也没有想明白。

总结

多线程操作真的是麻烦,多线程值得深入学习一下。

C++熟练与否,非常影响做事效率。这个实现,我调试了4天才搞好。想的是一回事,实现起来又是另一回事。

各种highlevel一点的东西都不熟悉,还要一边学习一边应用,这样自然工作效率就特别低,且容易出bug。

此次功能涉及的知识点有:

lambda函数:捕获类的成员变量需传入this,引用传值&,拷贝传递参数=;

usleep(50) 和std::this_thread::sleep_for(std::chrono::milliseconds(50))区别;

条件变量,wait_for(lock, std::chrono::milliseconds(50)), pred), notify(),条件变量存在虚假唤醒问题;

时间戳需用double表示,double不能用判断,这样判断又有点麻烦了,所以使用uint_64表示时间戳,可以直接用

last_pub_main_timestamp_是多线程的,所以要定义为atomic原子变量,原子变量的store(),load()和直接赋值,判断有什么区别?

有没有开源的多激光时间同步代码或框架可以参考?

Apollo?google? 还没有调查。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值