日期:4.04 – 4.10
本周工作进展:
上周解决的utility读写问题,本周运行之后又出现了之前的错误:进程在初始化后会随机崩溃掉,然后重启,有时候会稳定,有时候会移植崩溃。
暂时查不出原因来,于是回到程序本身,在多线程std::thread、mmap的通信框架里找问题。
然后让师兄看了一下代码,师兄发现了代码的一个大问题。
我们本来用的mmap框架搭建通信机制。
经过师兄的调拨,认识到mmap作为通信框架会出现问题(虽然暂时没有遇到)
先简单梳理一下mmap的工作机制
总结一下:mmap机制,共享通过内存–缓存–硬盘–内存–进程
于是我们考虑换掉mmap,使用shm重写一下接口(至于为什么不用socket通信,我只能说那样要改的太多了,只能说开始取巧偷懒了,现在就要填坑了)。
方便的是,由于我们的读写更新速度比较快,所以使用死循环的方式应该会优于使用信号量的方式,所以我们只需要在最底层的通信接口内部,用shm替代mmap
以下是新的通信框架
/**
* @brief used to define share memory
* @param fd:共享内存区域ID
* @param point:共享内存区域首地址
*
*/
struct MemoryDefinition
{
int fd;
void* point;
};
key_t get_id(const string fileName)
{
key_t ret = 0;
for(int i = 0; i < fileName.size(); i++)
{
ret += fileName[i];
}
//cout<
return ret + 1000000;
}
/**
* @brief Create a shm object
*
* @param shm_key 共享内存键值
* @param shm_num 共享内存字节长度
* @param shm_flag 权限 (默认权限为读写权限)
* @return void*
*/
MemoryDefinition* createShareMemory(const string shm_key, size_t shm_num)//, int shm_flag = 1)
{
MemoryDefinition* shm_point = new MemoryDefinition();;
int shm_flag = IPC_CREAT | 0666;
int fd = shmget(get_id(shm_key), shm_num, shm_flag);
void* point;
if(fd != -1) {
//
point = shmat(fd, 0, 0);
if((int*)point != (int*)-1) {
//初始化
memset(point, 0, shm_num);
shm_point->fd = fd;
shm_point->point = point;
return shm_point;
} else {
perror("get shareMemory error");
}
} else {
perror("get shareMemory error");
}
return shm_point;
}
/**
* @brief 连接共享内存
*
* @param shm_key 共享内存键值
* @param shm_num 共享内存字节长度
* @param shm_flag 权限 (默认权限为读写权限)
* @return void*
*/
MemoryDefinition* connectShareMemory(const string shm_key, size_t shm_num)//, int shm_flag = 1)
{
MemoryDefinition* shm_point = new MemoryDefinition();
int shm_flag = IPC_CREAT | 0666;
int fd = shmget(get_id(shm_key), shm_num, shm_flag);
void* point;
if(fd != -1) {
//
point = shmat(fd, 0, 0);
if((int*)point != (int*)-1) {
shm_point->fd = fd;
shm_point->point = point;
cout<<shm_key<<' '<<fd<<' '<<point<<endl;
return shm_point;
} else {
perror("get shareMemory error");
}
} else {
perror("get shareMemory error");
}
return shm_point;
}
void unMap(const MemoryDefinition* definition)
{
shmdt(definition->point);
}
#endif
修改后项目能够正常进行
预想的超过共享内存上限值的问题没有发生(查查多少MB)
而且改成shm后,今天最开始要解决的进程崩溃问题好了。说明不是读取文件流的问题,是mmap本身的问题
最开始,我的计划是针对雷达的通信方式,直接从0开始写一个驱动程序。于是开始阅读速腾雷达的官方文档。
网址: https://www.robosense.cn/resources-27
下面是一些记录:
UDP通信协议
数据包存在于MSOP包中
发送间隔1.33ms
单回波和双回波
https://www.zhihu.com/question/473355887/answer/2011499085
激光发射后 随着距离的增大,会出现散射的现象;当激光到达透明物体或者物体边缘时,会有部分散射的激光继续前进
双回波会收集两次回波,但是可能会造成数据的重复收集
只反射了一次回波。但是接收器仍然将其识别为两次回波。
1 - 8byte:用于包头检测
21 - 30byte:存储时间戳
31byte:表示激光雷达的型号
数据块区间是 MSOP 包中传感器的测量值部分,共 1200byte。它由 12 个 data block 组成,每个 block 长度为 100byte,代表一组完整的测距数据。Data block 中 100byte 的空间包括:2byte 的标志位,使用 0xffee 表示;2byte 的 Azimuth,表示水平旋转角度信息,每个角度信息对应着 32 个的 channel data,包含 2 组完整的 16 通道信息。
看了半天,发现从头写不太现实,于是还是去网上看一下ros环境下的使用流程。
整理出一些有用的文章:
ROS功能包在线把速腾聚创点云格式转为velodyne点云格式 https://blog.csdn.net/weixin_53073284/article/details/123344981
libpcap详解 https://blog.csdn.net/superbfly/article/details/51199161 (数据包捕获函数库,是Unix/Linux平台下的网络数据包捕获函数库。它是一个独立于系统的用户层包捕获的API接口,为底层网络监测提供了一个可移植的框架。)
https://github.com/RoboSense-LiDAR/rslidar_sdk Rlidar_SDK是运行在Ubuntu操作系统上的激光雷达驱动软件开发工具包,它包含了激光雷达驱动核、ROS支持、ROS 2支持和Protobuf-UDP通信功能。对于希望通过ROS或ROS 2获得点云的用户,可以直接使用此软件开发工具包。对于那些想要做高级开发或将激光雷达驱动器集成到他们自己的项目中的人,请参考激光雷达驱动器核心rs_Driver。
https://blog.csdn.net/weixin_53073284/article/details/123344981 ROS功能包在线把速腾聚创点云格式转为velodyne点云格式
https://blog.csdn.net/weixin_53073284/article/details/122680587 速腾rs16激光雷达安装驱动使用方法
所以如果在ros的环境下 大致流程是
所以我现在的流程
所以现在不需要我从头开始写UDP编程了,接着速腾雷达的驱动进行二次开发
看到了一个同学的博客
https://blog.csdn.net/weixin_53073284/article/details/122680587
现根据他的博客,安装了一个驱动,这个驱动应该是专门为ros设计的
git clone https://github.com/BluewhaleRobot/ros_rslidar_robosense.git
这个在我编译的时候出了点小问题
解决:
#add_dependencies(talker ${${PROJECT_NAME}_EXPORTED_TARGETS})
target_link_libraries(range_node
range_conversion
${catkin_LIBRARIES}
/usr/lib/x86_64-linux-gnu/libpcl_visualization.so
)
#message(STATUS "?????????????????????????????????????????????????????????????????? ${catkin_LIBRARIES}")
核心驱动包 :
两个主要的节点
针对代码的具体分析体现在代码中,不在博客中记录了。
这里只记录一下一些概念和主要流程:
rslidarScan.msg
# LIDAR scan packets.
Header header # standard ROS message header
rslidarPacket[] packets # vector of raw packets
rslidarPacket.msg
# Raw LIDAR packet.
time stamp # packet timestamp
uint8[1248] data # packet contents
1248字节是一个UDP协议包的有效荷载字长
Scan则是一个packet的数组,保存一组packet
基于发布的话题
msop_output_ = node.advertise("rslidar_packets", 10);
可以认为,订阅了该话题的节点 是用于处理数据的
RS-LiDAR-16 每隔一组 16 线激光测距才输出一次水平旋转角度信息,因此在单回 波模式下,对于没有输出水平旋转角度信息的那组 16 线激光测距需要通过插值来获得。 有很多种方式可以插值,下面的方法是最简单和直接的一种。 对于一个 Packet 中的数据,Block 1 和 Block 2 的第一个数据采集的时间间隔是 ~100us,可以认为在这个期间雷达是匀速旋转的。因此可以计算第 N+1 组 16 线激光测 距的第一个数据的水平角度是第 N 组 16 线激光测距的第一个数据的水平角度和第 N+2 组 16 线激光测距的第一个数据的水平角度的均值
最上面定义了一些静态的雷达相关参数(暂时没看)
// block
typedef struct raw_block //一个block 2byte 的标志位,使用 0xffee 表示;2byte 的 Azimuth,表示水平旋转角度信息,每 个角度信息对应着 32 个的 channel data,包含 2 组完整的 16 通道信息。
{
uint16_t header; ///< UPPER_BANK or LOWER_BANK
uint8_t rotation_1;
uint8_t rotation_2; /// combine rotation1 and rotation2 together to get 0-35999, divide by 100 to get degrees
uint8_t data[BLOCK_DATA_SIZE]; // 96 32 * 2(一个block出去标志位和水平转角信息的数据大小)
} raw_block_t;
整个数据包
从配置文件中加载参数
在项目接受的点云数据中:基本上每帧的width都是 16000左右
与楼下这个数字基本上接近
从第43byte开始解析(前42byte是head部分)
然后逐个block解析(12个)
更新温度信息(不重要)
计算水平偏角
配合深度信息计算xyz
一组通道中的每个通道的水平偏角,按照源代码的计算方式来看,是不同的
int azi1, azi2; // 插值计算一个block中的后半段的角度
azi1 = 256 * raw->blocks[block + 1].rotation_1 + raw->blocks[block + 1].rotation_2;
azi2 = 256 * raw->blocks[block].rotation_1 + raw->blocks[block].rotation_2;
azimuth_diff = (float)((36000 + azi1 - azi2) % 36000);
for (int firing = 0, k = 0; firing < RS16_FIRINGS_PER_BLOCK; firing++) // 2
{
for (int dsr = 0; dsr < RS16_SCANS_PER_FIRING; dsr++, k += RAW_SCAN_SIZE) // 16 3
{
//计算出每个通道的水平偏角
azimuth_corrected_f = azimuth + (azimuth_diff * ((dsr * RS16_DSR_TOFFSET) + (firing * RS16_FIRING_TOFFSET)) /
RS16_BLOCK_TDURATION); //
//取整
azimuth_corrected = ((int)round(azimuth_corrected_f)) % 36000; // convert to integral value...
计算方法:
然后直接将intensity信息赋值
注:
这里发布的点云信息不具备ring信息和stamp信息
认为:
而项目里实际需要的就是时间戳
优化方向
接收的点云数据 加上角度信息
rslidar_node.cpp中启动该节点,首先初始化一个rslidar_driver::rslidarDriver类
节点后面while循环执行楼上类的poll函数,该函数用于轮询雷达设备
rslidar_pointcloud中的节点订阅rslidar_driver发布的话题
将原始数据解析成点云数据
发布
还需要注意的是,大多数slam项目所适配的点云格式都是velodyne雷达(一个国外的牌子),而我们项目所使用的是国内的速腾雷达,产生的点云格式与velodyne有些许差别,需要转换。github上找到了相关程序,不过是针对ros的,需要改动。
https://github.com/HViktorTsoi/rs_to_velodyne
大体思路就是:
梳理完成,可以开始进行驱动的编写了
rdlidar雷达驱动
socket通信
没啥大bug
无