项目实训 - 智能车系统 - 第七周记录
項目實訓 - 智能車系統 - 第七周記錄
日期:4.04 – 4.10
項目進度
本周工作進展:
- 更換了底層的通信框架(shm)
- 熟悉速騰雷達驅動,以及ros相關驅動,為二次開發驅動做準備
1、更換底層通信框架
上周解決的utility讀寫問題,本周運行之后又出現了之前的錯誤:進程在初始化后會隨機崩潰掉,然后重啟,有時候會穩定,有時候會移植崩潰。
暫時查不出原因來,于是回到程序本身,在多線程std::thread、mmap的通信框架里找問題。
然后讓師兄看了一下代碼,師兄發現了代碼的一個大問題。
我們本來用的mmap框架搭建通信機制。
經過師兄的調撥,認識到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<<fileName<<' '<<ret<<endl;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本身的問題
2、速騰雷達驅動 – 文檔閱讀
最開始,我的計劃是針對雷達的通信方式,直接從0開始寫一個驅動程序。于是開始閱讀速騰雷達的官方文檔。
網址: https://www.robosense.cn/resources-27
下面是一些記錄:
UDP通信協議
數據包存在于MSOP包中
發送間隔1.33ms
單回波和雙回波
https://www.zhihu.com/question/473355887/answer/2011499085
-
激光發射后 隨著距離的增大,會出現散射的現象;當激光到達透明物體或者物體邊緣時,會有部分散射的激光繼續前進
-
雙回波會收集兩次回波,但是可能會造成數據的重復收集
-
只反射了一次回波。但是接收器仍然將其識別為兩次回波。
數據包格式分析
幀頭 42byte
1 - 8byte:用于包頭檢測
- 序列:0x55,0xAA,0x05,0x0A,0x5A,0xA5,0x50,0xA0
21 - 30byte:存儲時間戳
31byte:表示激光雷達的型號
數據段
數據塊區間是 MSOP 包中傳感器的測量值部分,共 1200byte。它由 12 個 data block 組成,每個 block 長度為 100byte,代表一組完整的測距數據。Data block 中 100byte 的空間包括:2byte 的標志位,使用 0xffee 表示;2byte 的 Azimuth,表示水平旋轉角度信息,每個角度信息對應著 32 個的 channel data,包含 2 組完整的 16 通道信息。
3、rslidar 驅動 二次開發 準備
看了半天,發現從頭寫不太現實,于是還是去網上看一下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的環境下 大致流程是
- 安裝速騰雷達的驅動
- 二次開發一下?(可能是點云格式的一些問題)
- (如果直接需要速騰格式的)直接發布就行了
- (如果需要velodyne格式的)經過rs_to_velodyne進行轉換(流程就是訂閱上面發布的話題,轉換成對應格式繼續發布)
- 訂閱話題(后面要注意一下,是否需要將坐標系繼續轉換 (取決于算法的實現 需要再看))
所以我現在的流程
- 安裝速騰雷達的驅動(不使用里面的ros的相關功能)
- 二次開發(根據ros的實現,轉換為我們自定義的點云格式)
- 根據rs_to_velodyne的實現,繼續轉換點云格式
- 自定義發布
所以現在不需要我從頭開始寫UDP編程了,接著速騰雷達的驅動進行二次開發
- 安裝驅動
看到了一個同學的博客
https://blog.csdn.net/weixin_53073284/article/details/122680587
現根據他的博客,安裝了一個驅動,這個驅動應該是專門為ros設計的
git clone https://github.com/BluewhaleRobot/ros_rslidar_robosense.git
這個在我編譯的時候出了點小問題
- 一直提示我一個頭文件下的類沒有定義
- 經過查找是鏈接的時候出的問題
解決:
- 先找到pcl庫的路徑
- 小技巧:在編譯的時候打印依賴的庫的信息
- 找到了對應的文件
- 然后發現缺失的那個頭文件 不在打印的信息里面
- 所以在鏈接的地方添加這個路徑即可
核心驅動包 :
兩個主要的節點
- rslidar_driver
- rslidar_pointcloud
針對代碼的具體分析體現在代碼中,不在博客中記錄了。
這里只記錄一下一些概念和主要流程:
msgs格式
rslidarScan.msg
# LIDAR scan packets.Header header # standard ROS message header rslidarPacket[] packets # vector of raw packetsrslidarPacket.msg
# Raw LIDAR packet.time stamp # packet timestamp uint8[1248] data # packet contents1248字節是一個UDP協議包的有效荷載字長
Scan則是一個packet的數組,保存一組packet
基于發布的話題
msop_output_ = node.advertise<rslidar_msgs::rslidarScan>("rslidar_packets", 10);可以認為,訂閱了該話題的節點 是用于處理數據的
關于水平轉角的一個信息
- 差值
rawdata.h
最上面定義了一些靜態的雷達相關參數(暫時沒看)
struct raw_block
// block typedef struct raw_block //一個block 2byte 的標志位,使用 0xffee 表示;2byte 的 Azimuth,表示水平旋轉角度信息,每 個角度信息對應著 32 個的 channel data,包含 2 組完整的 16 通道信息。 {uint16_t header; ///< UPPER_BANK or LOWER_BANKuint8_t rotation_1; uint8_t rotation_2; /// combine rotation1 and rotation2 together to get 0-35999, divide by 100 to get degreesuint8_t data[BLOCK_DATA_SIZE]; // 96 32 * 2(一個block出去標志位和水平轉角信息的數據大小) } raw_block_t;struct raw_packet
整個數據包
- revolution和status參數還沒搞懂是干嘛的
loadConfigFile函數
從配置文件中加載參數
- 原本的項目中沒有對應的配置文件
- 我從其他包中找到了對應的參數
- 但是可能還要根據雷達具體的參數來判斷(比如角度等等)
在項目接受的點云數據中:基本上每幀的width都是 16000左右
與樓下這個數字基本上接近
- 代表的是有多少個點
- 16:16線
- 24:一個包掃描了24次(對應于不同的水平角度)
- 84:進行了84次掃描
- /2不知道為啥
unpack函數
從第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...
-
計算方法:
- azimuth_diff代表的是第i個block的第一個通道的偏角 與 第i+1個block的第一個通道的偏角的差值(最后一個block用的與前一個block的差值)
- 后面計算每個通道的水平偏角
- RS16_DSR_TOFFSET 代表每個通道掃描的時間間隔
- RS16_FIRING_TOFFSET 代表第一個組16通道掃描的時間
- RS16_BLOCK_TDURATION 代表一組block掃描的時間(RS16_FIRING_TOFFSET 的二倍)
- 通過上面的公式計算出每個通道的水平偏角
然后直接將intensity信息賦值
注:
這里發布的點云信息不具備ring信息和stamp信息
- 這里結合rs_to_velodyne的代碼
- time可以直接用ros的時間戳
- 如果不行的,手動賦值
- 通過起始的ros時間戳+差值的方式(均勻
- 看了lio的項目,雀氏是需要每個點的時間戳的
- 雀氏可以手動賦值
- 先看一下原始代碼吧
- ring直接賦值通道數就可以了應該(而且應用的時候貌似也不需要ring?)
認為:
- rs雷達的time格式是時間點
- velodyne雷達的time格式是該點的時間戳 與 這一組點云的開始點的時間戳的 差值
而項目里實際需要的就是時間戳
- 在項目中應該有轉換成實際時間戳的操作
優化方向
-
接收的點云數據 加上角度信息
-
rslidar_node.cpp中啟動該節點,首先初始化一個rslidar_driver::rslidarDriver類
- 構造函數中,從配置文件中讀取一系列參數,并且初始化話題的發布(msop和difop,我們需要的是msop)
- 發布的話題的消息類型是:rslidar_msgs::rslidarScan,在后面解釋
- 并且初始化socket通信,在input.cc中,通過socket網絡編程與雷達建立起通信連接。
- 構造函數中,從配置文件中讀取一系列參數,并且初始化話題的發布(msop和difop,我們需要的是msop)
-
節點后面while循環執行樓上類的poll函數,該函數用于輪詢雷達設備
- 初始化rslidarPacket,讀取npackets次數據包(84),通過input中的getpacket函數
-
rslidar_pointcloud中的節點訂閱rslidar_driver發布的話題
-
將原始數據解析成點云數據
-
發布
4、rs_to_velodyne
還需要注意的是,大多數slam項目所適配的點云格式都是velodyne雷達(一個國外的牌子),而我們項目所使用的是國內的速騰雷達,產生的點云格式與velodyne有些許差別,需要轉換。github上找到了相關程序,不過是針對ros的,需要改動。
https://github.com/HViktorTsoi/rs_to_velodyne
大體思路就是:
- 訂閱發布的rslidar點云話題
- 轉換
- 發布velodyne格式點云話題
梳理完成,可以開始進行驅動的編寫了
技術難點
rdlidar雷達驅動
socket通信
bug記錄
沒啥大bug
其他
無
總結
以上是生活随笔為你收集整理的项目实训 - 智能车系统 - 第七周记录的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 腾讯大战360之观战指南
- 下一篇: 以太坊学习路线——(五)DApp开发:简