rs_driver v1.5.7 源代码解析(一)

rs_driver 是RoboSense雷达的基本驱动程序。本文是rs_driver的源代码解析文档,原文地址在:
https://github.com/RoboSense-LiDAR/rs_driver/blob/v1.5.7/doc/src_intro/rs_driver_intro_CN.md

1 基本概念

1.1 机械式雷达、MEMS雷达

rs_driver支持RoboSense的两种雷达:

  • 机械式雷达。如RS16/RS32/RSBP/RSHELIOS/RS80/RS128。机械式雷达有控制激光发射角度的旋转部件,有360°扫描视场。
  • MEMS雷达。如RSM1。MEMS雷达是单轴、谐振式的MEMS扫描镜,其水平扫描角度可达120°。

1.2 通道 Channel

对于机械式雷达,通道指的是垂直方向上扫描的点数,每个通道上的点连成一条线。比如,RS16是16线雷达,也就是16个通道; RSBP是32线雷达,RS128是128线雷达。

MEMS雷达的通道与机械式雷达不同,它的每个通道可能对应一块区域,比如一个矩形区域。

1.3 MSOP/DIFOP

RoboSense雷达与电脑主机的通信协议有三种。

  • MSOP (Main data Stream Ouput Protocol)。 激光雷达将扫描出来的距离、角度、反射率等信息封装成MSOP Packet,输出给电脑主机。
  • DIFOP (Device Information Output Protocol)。激光雷达将自身的配置信息,以及当前的状态封装成DIFOP Packet,输出给电脑主机。
  • UCWP (User Configuration Write Protocol)。用户可以修改激光雷达的某些配置参数。

rs_driver处理前两类协议的包,也就是MSOP Packet和DIFOP Packet。

一般来说,激光雷达与电脑主机通过以太网连接,使用UDP协议。MSOP/DIFOP的格式,不同的雷达可能有较大差异。

1.4 点云帧

  • 机械式雷达持续旋转,输出点。扫描一圈360°得到的所有点,构成一帧点云。

    • 使用者可以指定一个角度,rs_driver按照这个角度,分割MSOP Pacekt序列得到点云。
  • 对于MEMS雷达,点云在MSOP Packet序列中的开始和结束位置,由雷达自己确定。

    • 一帧点云包含固定数目(比如N)的MSOP Packet。雷达对MSOP Packet从 1 到 N 编号,并一直循环。

2 rs_driver的组件

rs_driver主要由三部分组成: Input、Decoder、LidarDriverImpl。

01_components.png
  • Input部分负责从Socket/PCAP文件等数据源,获取MSOP/DIFOP Packet。Input的类一般有自己的接收线程recv_thread_
  • Decoder部分负责解析MSOP/DIFOP Packet,得到点云。Decoder部分没有自己的线程,它运行在LiarDriverImpl的Packet处理线程handle_thread_中。
  • LidarDrvierImpl部分将Input和Decoder组合到一起。它从Input得到Packet,根据Packet的类型将它派发到Decoder。得到点云后,通过用户的回调函数传递给用户。
    • LidarDriverImpl提供Packet队列。Input收到MSOP/DIFOP Packet后,调用LidarDriverImpl的回调函数。回调函数将它保存到Packet队列。
    • LidarDriverImpl提供Packet处理线程handle_thread_。在这个线程中,将MSOP Packet和DIFOP Packet分别派发给Decoder相应的处理函数。
    • Decoder解析完一帧点云时,通知LidarDriverImpl。后者再将点云传递给用户。

3 Packet接收

Input部分负责接收MSOP/DIFOP Packet,包括:

  • Input,
  • Input的派生类,如InputSock、InputPcap、InputRaw
  • Input的工厂类 InputFactory
02_classes_input.png

3.1 Input

Input定义接收MSOP/DIFOP Packet的接口。

  • 成员input_param_是用户配置参数RSInputParam,其中包括从哪个port接收Packet等信息。

  • Input自己不分配接收Packet的缓存。

    • Input的使用者调用Input::regCallback(),提供两个回调函数cb_get_pkt和cb_put_pkt, 它们分别保存在成员变量cb_get_pkt_cb_put_pkt_中。
    • Input的派生类调用cb_get_pkt_可以得到空闲的缓存;在缓存中填充好Packet后,可以调用cb_put_pkt_将它返回。
  • Input有自己的线程recv_thread_

    • Input的派生类启动这个线程读取Packet。
03_class_input.png

3.2 InputSock

InputSock类从UDP Socket接收MSOP/DIFOP Packet。雷达将MSOP/DIFOP Packet发送到这个Socket。

04_class_input_sock.png
  • 一般情况下,雷达将MSOP/DIFOP Packet发送到不同的目的Port,所以InputSock创建两个Socket来分别接收它们。
    • 成员变量fds_[2]保存这两个Socket的描述符。fds_[0]是MSOP socket, fds_[1]是DIFOP socket。但也可以配置雷达将MSOP/DIFOP Packet发到同一个Port,这时一个Socket就够了,fds_[1]就是为无效值-1
    • MSOP/DIFOP对应的Port值可以在RSInputParam中设置,分别对应于RSInputParam::msop_portRSInputParam::difop_port
  • 一般情况下,MSOP/DIFOP Packet直接构建在UDP协议上。但在某些客户的场景下(如车联网),MSOP/DIFOP Packet可能构建在客户的协议上,客户协议再构建在UDP协议上。这时,InputSock派发MSOP/DIFOP Packet之前,会先丢弃USER_LAYER的部分。成员变量sock_offset_保存了USER_LAYER部分的字节数。
    • USER_LAYER部分的字节数可以在RSInputParam中设置,对应于RSInputParam::user_layer_bytes
  • 有的场景下,客户的协议会在MSOP/DIFOP Packet尾部附加额外的字节。这时,InputSock派发MSOP/DIFOP Packet之前,会先丢弃TAIL_LAYER的部分。成员变量sock_tail_保存了TAIL_LAYER部分的字节数。
    • TAIL_LAYER部分的字节数可以在RSInputParam中设置,对应于RSInputParam::tail_layer_bytes
05_packet_layers.png

3.2.1 InputSock::createSocket()

createSocket()用于创建UDP Socket。

  • 调用setsockopt(), 设置选项SO_REUSEADDR
  • 调用bind()将socket绑定到指定的(IP, PORT)组上
  • 如果雷达是组播模式,则将指定IP加入该组播组。
  • 调用fcntl()设置O_NONBLOCK选项,以异步模式接收MSOP/DIFOP Packet

该Socket的配置参数可以在RSInputParam中设置。根据设置的不同,createSocket()支持如下几种模式。

msop_port/difop_port host_address group_address
6699/7788 0.0.0.0 0.0.0.0 雷达的目的地址可以为广播地址、或电脑主机地址
6699/7788 192.168.1.201 0.0.0.0 雷达的目的地址可以为电脑主机地址
6699/7788 192.168.1.201 239.255.0.1 雷达的目的地址可以为组播地址、或电脑主机地址

3.2.2 InputSock::init()

init() 调用createSocket(),创建两个Socket,分别接收MSOP Packet和DIFOP Packet。

3.2.3 InputSock::start()

start() 开始接收MSOP/DIFOP Packet。

  • 启动接收线程,线程函数为InputSock::recvPacket()

3.2.4 InputSock::recvPacket()

recvPacket() 接收MSOP/DIFOP Packet。
在while()循环中,

  • 调用FD_ZERO()初始化本地变量rfds,调用FD_SET()将fds_[2]中的两个fd加入rfds。当然,如果MSOP/DIFOP Packet共用一个socket, 无效的fds_[1]就不必加入了。
  • 调用select()在rfds上等待Packet, 超时值设置为1秒。
    如果select()的返回值提示rfds上有信号,调用FD_ISSET()检查是fds_[]中的哪一个fd可读。对这个fd,
  • 调用回调函数cb_get_pkt_, 得到大小为MAX_PKT_LEN的缓存。MAX_PKT_LEN = 1500,对当前RoboSense雷达来说,够大了。
  • 调用recvfrom()接收Packet,保存到这个缓存中
  • 调用回调函数cb_put_pkt_,将Packet派发给InputSock的使用者。
    • 注意在派发之前,调用Buffer::setData()设置了MSOP Packet在Buffer的中偏移量及长度,以便剥除USER_LAYERTAIL_LAYER(如果有的话)。

3.3 InputPcap

InputPcap解析PCAP文件得到MSOP/DIFOP Packet。使用第三方工具,如WireShark,可以将雷达数据保存到PCAP文件中。

06_class_input_pcap.png
  • InputPcap基于第三方的libpcap库,使用它可以遍历PCAP文件,依次得到所有UDP Packet。

    • 成员变量pcap_变量保存Pcap文件指针,pcap_t定义来自libpcap库。
  • 与InputSock一样,在有的客户场景下,InputPcap也需要处理USER_LAYERTAIL_LAYER的情况。InputPcap的成员pcap_offset_pcap_tail_分别保存USER_LAYERTAIL_LAYER的字节数。

  • 但也有不同的地方。InputSock从Socket接收的Packet只有UDP数据部分,而InputPcap从PCAP文件得到的Packet不同,它包括所有Packet的所有层。pcap_offset_除了USER_LAYER的长度之外,还要加上其他所有层。

    • 对于一般的以太网包,pcap_offset_需要加上其他层的长度,也就是 14(ETHERNET) + 20(IP) + 8(UDP) = 42 字节。
    • 如果还有VLAN层,pcap_offset_还需要加上 4 字节。
07_packet_layers_full.png
  • PCAP文件中可能不止包括MSOP/DIFOP Packet,所以需要使用libpcap库的过滤功能。libpcap过滤器bpf_program,由库函数pcap_compile()生成。成员msop_filter_difop_filter_分别是MSOP Packet和DIFOP Packet的过滤器。
    • MSOP/DIFOP Packet都是UDP Packet,所以给pcap_compile()指定选项udp
    • 如果是基于VLAN的,则需要指定选项vlan
    • 如果在一个PCAP文件中包含多个雷达的Packet,则还需要指定选项 udp dst port,以便只提取其中一个雷达的Packet。

用户配置参数RSInputParam中指定选项udp dst port。有如下几种情况。

msop_port difop_port 说明
0 0 如果PCAP文件中只包含一个雷达的Packet
6699 7788 如果PCAP文件中包含多个雷达的Packet,则可以只提取指定雷达的Packet(该雷达MSOP/DIFOP端口不同)
6699 6699/0 如果PCAP文件中包含多个雷达的Packet,则可以只提取指定雷达的Packet(该雷达DIFOP/DIFOP端口相同)

3.3.1 InputPcap::init()

init()打开PCAP文件,构造PCAP过滤器。

  • 调用pcap_open_offline()打开PCAP文件,保存在成员变量pcap_中。
  • 调用pcap_compile()构造MSOP/DIFOP Packet的PCAP过滤器。
    • 如果它们使用不同端口,则需要两个过滤器,分别保存在mosp_filter_difop_filter_中。
    • 如果使用同一端口,那么difop_filter_就不需要了。

3.3.2 InputPcap::start()

start()开始解析PCAP文件。

  • 调用std::thread(),创建并启动PCAP解析线程,线程的函数为recvPacket()。

3.3.3 InputPcap::recvPacket()

recvPacket()解析PCAP文件。
在循环中,

  • 调用pcap_next_ex()得到文件中的下一个Packet。

如果pcap_next_ex()还能读出Packet,

  • 本地变量header指向Packet的头信息,变量pkt_data指向Packet的数据。
  • 调用pcap_offline_filter(),使用PCAP过滤器校验Packet(检查端口、协议等是否匹配)。

如果是MSOP Packet,

  • 调用cb_get_pkt_得到大小为MAX_PKT_LEN的缓存。MAX_PKT_LEN = 1500,对当前的RoboSense雷达来说,够大了。
  • 调用memcpy()将Packet数据复制到缓存中,并调用Buffer::setData()设置Packet的长度。复制时剥除了不需要的层,包括USER_LAYERTAIL_LAYER(如果有的话)。
  • 调用回调函数cb_put_pkt_,将Packet派发给InputSock的使用者。

如果是DIFOP Packet,处理与MSOP Packet一样。

  • 调用this_thread::sleep_for()让解析线程睡眠一小会。这是为了模拟雷达发送MSOP Packet的间隔。这个间隔时间来自每个雷达的Decoder类,每个雷达有自己的值。在Decoder部分,会说明如何计算这个值。

如果pcap_next_ex()不能读出Packet,一般意味着到了文件结尾,则:

  • 调用pcap_close()关闭pcap文件指针 pcap_

用户配置RSInputParam的设置决定是否重新进行下一轮的解析。这个选项是RSInputParam::pcap_repeat

  • 如果这个选项为真,调用pcap_open_offline()重新打开PCAP文件。这时成员变量pcap_回到文件的开始位置。下一次调用pcap_next_ex(),又可以重新得到PCAP文件的第一个Packet了。

3.4 InputRaw

InputRaw是为了重播MSOP/DIFOP Packet而设计的Input类型。将在后面的Packet Record/Replay章节中说明。

3.5 InputFactory

InputFactory是创建Input实例的工厂。

08_class_input_factory.png

Input类型如下。

enum InputType
{
  ONLINE_LIDAR = 1, // InputSock
  PCAP_FILE,        // InputPcap
  RAW_PACKET        // InputRaw
};

3.5.1 InputFactory::creatInput()

createInput() 根据指定的类型,创建Input实例。

  • 创建InputPcap时,需指定sec_to_delay。这是InputPcap回放MSOP Packet的间隔。
  • 创建InputRaw时,需指定cb_feed_pkt。这个将在后面的Packet Record/Replay章节中说明。

相关链接
rs_driver v1.5.7 源代码解析(一)
rs_driver v1.5.7 源代码解析(二)
rs_driver v1.5.7 源代码解析(三)
rslidar_sdk v1.5.7 源代码解析

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,063评论 6 510
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,805评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,403评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,110评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,130评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,877评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,533评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,429评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,947评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,078评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,204评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,894评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,546评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,086评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,195评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,519评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,198评论 2 357

推荐阅读更多精彩内容

  • 在解密华为激光雷达前先了解一下激光雷达信噪比的概念,任何传感器,最重要的参数就是信噪比,非相干激光雷达的信噪比SN...
    蜗牛也疯狂_6104阅读 2,692评论 0 2
  • 激光雷达组成主要包含:发射模块、扫描模块、接收模块、光学系统、计时模块,信号处理模块; 一、发射模块: 目前市场常...
    砍柴的秃驴阅读 523评论 0 0
  • 文章大纲 中国汽车雷达产业发展现状·概念界定·中国汽车雷达产业发展历史 中国汽车雷达国产机会分析·中国汽车雷达国产...
    驭势资本阅读 607评论 0 0
  • 基于激光雷达的地图创建与定位 一、自动驾驶汽车多种传感器各有优劣,多传感器融合是共识。 1、激光雷达能够获得高清的...
    囡囡虾米阅读 2,374评论 0 1
  • 文章大纲 自动驾驶给激光雷达带来新机遇 激光雷达原理 车载传感器的比较 激光雷达的分类 激光雷达的迭代历史 激光雷...
    驭势资本阅读 1,381评论 0 1