1?引言
想起去年元旦收到群里面小伙伴兒的建議,希望我們也能夠出一個基于NVIDIA Jetson nano 的ROS小車搭建過程,于是我們就慢慢書寫了前面已經(jīng)發(fā)布的推文集:????
-
《開源!手把手教你搭建Arduino+英偉達Jetson的ROS小車(上)》中,我們介紹了一臺Jetson nano小車所需要的硬件部分;
-
《開源!手把手教你驅(qū)動Arduino+ROS小車的電機》中,我們介紹了如何使用Arduino及配套的擴展板實現(xiàn)兩個直流減速電機的差速控制;
-
《開源!手把手教你如何調(diào)節(jié)編碼電機速度PID》中,我們分享了如何通過Arduino自帶的串口繪圖器完成電機的PID調(diào)速。
-
《開源!手把手教你驅(qū)動Arduino+ROS小車大功率直流霍爾編碼電機》中,我們分享了如何通過Arduino驅(qū)動大功率的直流霍爾編碼電機。
-
《開源!手把手教你搭建Arduino+英偉達Jetson的ROS小車(中)》中,我們分享了如何將Arduino開發(fā)板和英偉達等裝配有ROS Melodic 的“上位機”中調(diào)試相應的ROS包,完成相應的控制和發(fā)布數(shù)據(jù)。
-
《開源!手把手教你將超聲波數(shù)據(jù)用Rviz可視化》中,介紹了如何使用我們在Arduino 擴展板上預留的超聲波接口驅(qū)動傳感器,將獲得的數(shù)據(jù)在Rviz中用ROS Topic的方式可視化。
在這個過程中,也有小伙伴兒咨詢?yōu)樯稕]有基于STM32的底層驅(qū)動講解,這里說明一下我們當時的原由:
-
我們是有計劃在后續(xù)分享和發(fā)布STM32版本的控制器;
-
前期使用Arduino的目的是考慮到部分學習ROS的小伙伴兒非電子專業(yè),對操作寄存器編程方式不熟練,所以使用Arduino作為底層控制板,來講解整個ROS小車的搭建過程,可以大大降低學習和入門難度;
-
Arduino本身就是一個開源的項目,我們也想借助它,讓我們的開源社區(qū)可以更加壯大,有更多的人能通過模塊化以及低成本的方式接觸機器人,熟悉ROS開發(fā);
-
最后,我們也想在基于 NEOR mini 開源無人車仿真平臺的基礎上,陸陸續(xù)續(xù)再開源一款基于Arduino底層控制器的真車NEOR?mini,這個目標現(xiàn)在已經(jīng)基本實現(xiàn)。已經(jīng)完成了基本的功能測試。提前給大家放一張夜晚的圖:
上述所提到的一切都是基于Arduino mega 2560 及專用的擴展板開發(fā)而來的結(jié)果,起初,我們的擴展板僅僅是為了簡化單片機和其他部件的連線:
后來,在多位小伙伴的支持與鼓勵下,我們在這之上添加了板載5V/3A供電,為新增的兩路舵機輸出提供足夠的電流;
再到后來,越來越多的小伙伴想通過我們發(fā)布的《2021年度免費開放資源分享——從零開始搭建ROS小車系列》推文,自己動手做一輛樹莓派ROS小車,并且咨詢我們有關(guān)電機驅(qū)動部分。于是乎我們便直接將TB6612FNG模塊兒以排母結(jié)合的方式集成到了擴展板上。
到此,這塊Arduino mega 2560 擴展板成功運用在了二自由度云臺、兩輪差速、阿克曼ROS小車的底盤上。所以通過上述的推文分享和開源代碼,我想大家已經(jīng)可以和我一樣自己動手搭建一輛ROS小車底盤了。
在余下的推文中,我們將分享基于最新版本的Jetson nano主機,復現(xiàn)ROS中經(jīng)典的建圖、導航、CSI攝像頭驅(qū)動示例。
(全套亞克力-37電機版本)
2?軟硬件平臺
-
硬件
最新版本 Jetson Nano(工業(yè)級 , 2路CSI接口)
? ? ? ?最新版本思嵐雷達(A1M8)
? ? ? ?9Dof IMU (帶磁力計)
? ? ? ?Arduino 版本的兩輪差速底盤
-
軟件
? ? ? ? Arduino IDE + C 語言 開發(fā)的底層電機驅(qū)動;
? ? ? ??適配 Jetson nano 的Ubuntu 操作系統(tǒng);
? ? ? ? ROS melodic + gcam 攝像頭驅(qū)動;
3?Gmapping 實車建圖????
Gmapping 訂閱雷達測距數(shù)據(jù)、里程計位姿信息、雷達和小車運動學解算中心的相對位置,即可輸出2D柵格地圖。那么該如何正確使用該功能包呢,這里分享一下我的經(jīng)驗。對待開源的ROS 功能包,一般都可以在 ros wiki上搜索其介紹,這類ROS 包大多已經(jīng)十分成熟,無需更改源碼自行編譯,我們在使用的過程中當做一個黑盒即可;談到黑盒就得清楚其需要的輸入是什么?最后對應的輸出是什么?在ROS中,節(jié)點的輸入就是需要訂閱(Subscriber)哪些數(shù)據(jù);輸出就是會發(fā)布(Publisher)哪些數(shù)據(jù)。
-
Subscribed Topics
scan(sensor_msgs/LaserScan):激光雷達發(fā)布的測距數(shù)據(jù);
# sensor_msgs/LaserScan .msg
Header header # 數(shù)據(jù)頭,記錄 ROS msg 的信息
uint32 seq
time stamp
string frame_id # 對應哪一個坐標系 frame 如:base_laser
float32 angle_min # 雷達掃描的起始角度[rad]
float32 angle_max # 雷達掃描的終止角度 [rad]
float32 angle_increment # 激光每次掃描增加的角度 [rad]
float32 time_increment # 激光測量的時間間隔
float32 scan_time # 激光掃描的時間間隔 [seconds]
float32 range_min # 最近測距 距離 [m]
float32 range_max # 最遠測距 距離 [m]
float32[] ranges # 距離數(shù)值 集合 [m] (Note: values < range_min or > range_max should be discarded)
float32[] intensities # 強度信息
tf(tf/tfMessage):監(jiān)聽雷達、移動底座解算中心、里程計三個坐標系之間的tf轉(zhuǎn)換;比如:base_laser ——> base_link(靜態(tài))、odom——>base_link(動態(tài))。
-
Published Topics
map_metadata(nav_msgs/MapMetaData):地圖元數(shù)據(jù),只包含地圖的規(guī)格等信息,沒有實際的地圖信息;
# nav_msgs/MapMetaData .msg
time map_load_time # 地圖加載的時間
float32 resolution # 地圖的分辨率 [m/cell]
uint32 width # Map 寬度 [cells]
uint32 height # Map 高度 [cells]
geometry_msgs/Pose origin # 地圖的位姿數(shù)據(jù)
map(nav_msgs/OccupancyGrid):完整的地圖數(shù)據(jù),包含規(guī)格和實際的地圖內(nèi)容信息。
# nav_msgs/OccupancyGrid .msg
std_msgs/Header header # 具體包含的內(nèi)容同上
nav_msgs/MapMetaData # 地圖元數(shù)據(jù)
int8[] data # 地圖內(nèi)容數(shù)據(jù),數(shù)組長度 = width * height
那么接下來看看我們提供的 gmapping_ekf.launch文件:
<launch>
<!-- launch the ros_arduino_bridge node and publish odom data for robot_localization node -->
<include file="$(find ros_arduino_python)/launch/arduino.launch" />
<!-- launch the imu node and publish the imu data for robot_localization node-->
<include file="$(find imu_901)/launch/imu_901.launch" />
<!-- fusion the odom and imu data and publish the odom——> base_link tf for gmapping -->
<include file="$(find launch_file)/launch/robot_localization.launch" />
<!-- launch the gmapping launch file .
Listen tf:
odom ——> base_link
laser_link ——> base_link
Subscribe Topisc:
scan (sensor_msgs/LaserScan)
Publish Topisc:
map
-->
<!-- launch the lidar node,publish the scan topic and laser_link ——> base_link tf(static)-->
<include file="$(find launch_file)/launch/rplidar.launch" />
<include file="$(find launch_file)/launch/gmapping.launch" /
</launch>
對以上分別做以下解釋:
arduino.launch?文件發(fā)布里程計數(shù)據(jù)(wheel_odom)、訂閱速度話題(cmd_vel)控制車移動,不發(fā)布 tf ;
imu_901.launch?文件發(fā)布機器人姿態(tài)數(shù)據(jù) (imu);
robot_localization.launch? 文件融合 wheel_odom 和 imu 信息,輸出融合之后的里程計及 base_link 和 odom 之間的 tf 變換;
rplidar.launch?文件發(fā)布雷達測距信息 (scan),雷達和base_link之間的靜態(tài)tf‘變換。
gmapping.launch? 文件訂閱 雷達測距信息(scan),位姿信息(tf),發(fā)布柵格地圖(map)。
另外一篇詳細的過程可見以前發(fā)布的一篇推文《搭建ROS機器人之——手把手教你用gmapping實現(xiàn)2D建圖》
最后,地圖建好之后,保存地圖:
# 當覺得建圖效果可以之后,新打開終端,然后運行如下命令
rosrun map_server map_saver -f your_map_name
?(感謝小伙伴:? “舊人歸入夢” 的 溫馨提示?)
4?move_base 實車定點導航示例
首先來一張 ROS wiki 中關(guān)于 navigation 棧的架構(gòu)解析:??
由上圖可知,中間方框部分為實現(xiàn)導航功能的 move_base 核心;其左右分別為需要訂閱的數(shù)據(jù)及類型;下方為實際規(guī)劃出的實時速度,其將傳給底盤控制節(jié)點(ros_arduino_python)。結(jié)合圖和對應 ROS wiki 中的講解,我們構(gòu)建了如下所示的navigation_ekf.launch 文件:
<launch>
<!-- launch the lidar node,publish the scan topic and laser_link ——> base_link tf(static)-->
<include file="$(find launch_file)/launch/rplidar.launch" />
<!-- launch the ros_arduino_bridge node and publish odom data for robot_localization node -->
<include file="$(find ros_arduino_python)/launch/arduino.launch" />
<!-- launch the imu node and publish the imu data for robot_localization node-->
<include file="$(find imu_901)/launch/imu_901.launch" />
<!-- fusion the odom and imu data and publish the odom——> base_link tf for gmapping -->
<include file="$(find launch_file)/launch/robot_localization.launch" />
<!-- Run the map server and load the static map -->
<node name="load_map_to_amcl" pkg="map_server" type="map_server" args="$(find launch_file)/map/map.yaml"/>
<!--- Run AMCL -->
<include file="$(find launch_file)/launch/amcl.launch"/>
<!-- move_base 節(jié)點-->
<node pkg="move_base" type="move_base" respawn="false" name="move_base" output="screen">
<rosparam file="$(find launch_file)/params/costmap_common_params.yaml" command="load" ns="global_costmap" />
<rosparam file="$(find launch_file)/params/costmap_common_params.yaml" command="load" ns="local_costmap" />
<rosparam file="$(find launch_file)/params/local_costmap_params.yaml" command="load" />
<rosparam file="$(find launch_file)/params/move_base_params.yaml" command="load" />
<rosparam file="$(find launch_file)/params/global_costmap_params.yaml" command="load" />
<rosparam file="$(find launch_file)/params/dwa_local_planner_params.yaml" command="load" />
<rosparam file="$(find launch_file)/params/global_planner_params.yaml" command="load"/>
</node>
</launch>
其中:
map_server? 節(jié)點既可以保存地圖,也可以加載建圖所保存的靜態(tài)地圖;
amcl.launch?文件將通過實時的 雷達點云信息和加載的先驗地圖,完成機器人全局位姿估計;
move_base? 節(jié)點是導航的核心,其主要的功能是基于先驗地圖和全局位姿估計,完成全局路徑規(guī)劃;在此基礎上再根據(jù)機器人的運動學約束模型,執(zhí)行局部的路徑規(guī)劃,并且發(fā)布適配機器人的速度話題數(shù)據(jù)。
在規(guī)劃的過程中,為了避免機器人外觀碰撞到靜態(tài)的全局和動態(tài)的局部障礙物,引進了膨脹層的概念,繼而在規(guī)劃層面提前留有余地,并通過代價地圖體現(xiàn),可在Rviz中可視化。
在不同的場景下,因為機器人總是向前規(guī)劃的緣故,有時機器人會陷入“局部困境”中無法逃離,基于此又引入了恢復行為(Recovery behavior)操作,具體的現(xiàn)象就是機器人在無路可走的情況下,會開始原地轉(zhuǎn)圈,轉(zhuǎn)圈的目的就是為了尋找新的路徑,以擺脫“局部困境”。
根據(jù)傳感器的性能(分辨率、幀率、精度),處理器的性能,小車運行速度等因素,又將全局和局部的規(guī)劃進行了參數(shù)提煉,分別為路徑規(guī)劃的頻率、代價地圖的更新頻率。可以聯(lián)想得到的是,若目標車速要求快,那么一定需要較快的規(guī)劃速率,這樣才能確保安全;一定需要較快的地圖更新速率以降低障礙物的漏檢率,但與此相矛盾的又是對傳感器的性能要求,對處理器計算性能的要求。
綜上,我們需要根據(jù)不同的情況,實際所具備的不同條件,調(diào)節(jié)合適的參數(shù),以達到最好的預期效果。
5?用程序發(fā)布導航目標點
相關(guān)講解推文:
????《搭建ROS機器人之——用程序發(fā)布導航目標點(Python版本)》
????《搭建ROS機器人之——用程序發(fā)布導航目標點》
6 展望與彩蛋
關(guān)于如何在 Jetson nano 中用ROS發(fā)布 CSI攝像頭數(shù)據(jù),我們已經(jīng)將開源代碼放置在我們的倉庫中, 感興趣的同學們可以嘗試如何驅(qū)動自己的攝像頭:
http:// https://github.com/COONEO/Arduino_Jetson_nano_ROS_Car.git
文章來源:http://www.zghlxwxcb.cn/news/detail-470107.html
創(chuàng)作不易,如果喜歡這篇內(nèi)容,請您也轉(zhuǎn)發(fā)給您的朋友,一起分享和交流創(chuàng)造的樂趣,也激勵我們?yōu)榇蠹覄?chuàng)作更多的機器人研發(fā)攻略,讓我們一起learning by doing!?文章來源地址http://www.zghlxwxcb.cn/news/detail-470107.html
到了這里,關(guān)于開源!手把手教你搭建Arduino+英偉達Jetson的ROS小車(下)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!