ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

手把手教你用ROS+激光雷达+履带底盘从零搭建自主避障小车

手把手教你用ROS+激光雷达+履带底盘从零搭建自主避障小车 做ROS小车这件事说难不难说简单也真没想象中那么简单。前阵子我手头正好有一台钢制履带底盘又搞到一块激光雷达索性花了大半个月把它们组合在一起从硬件接线到ROS环境再到避障逻辑完整走了一遍最后小车真的能在房间里自己绕开障碍物跑来跑去。这篇文章就把整个项目的过程、踩过的坑、以及能直接跑的避障代码完整分享出来适合刚入门ROS、想从零手搓一台能自主移动的小车的朋友。在这套系统里核心其实就是三件事ROS负责把各个硬件模块串起来激光雷达负责感知周围环境避障算法负责根据感知结果决定小车怎么走。把这三件事理清楚剩下的就是接线、配环境、调参数这些体力活了。1. 项目概述与整体设计思路1.1 为什么选履带底盘做ROS小车市面上做ROS小车的底盘主要分轮式和履带式两种。轮式底盘比如麦克纳姆轮、普通阿克曼转向底盘在室内平整地面上表现不错结构成熟、资料多但通过性和负载能力相对有限。履带底盘就不太一样它的转向靠左右两侧履带的差速来实现和轮式差速从控制角度是一回事但履带接地面积大、附着力强能应付草地、碎石、甚至一些轻微的坡道。选择履带底盘还有一个实际考虑如果后续想扩展成室外巡检或者灾后搜救这种场景履带底盘的机械潜力明显更大。这一步的代价是履带车本身更重、转向摩擦阻力更大对电机扭矩和驱动板的电流能力要求更高。实际做下来我的体会是履带车调试避障逻辑时转弯响应比轮式车稍微肉一点因为履带滑移转向时地面摩擦会消耗一部分动力所以在避障参数里转向速度不能给得太小否则车头转不过去会直接怼上障碍物。1.2 激光雷达选型2D旋转雷达还是3D固态雷达激光雷达是这套系统里单价最高的传感器选型直接决定后续开发方向和预算。目前ROS小车项目里常见的有两大类一类是2D旋转激光雷达比如思岚RPLIDAR A1/A2/A3这类扫描出来是一圈平面距离数据。它们的优点是价格相对友好、ROS驱动成熟、数据处理简单非常适合做基础避障和2D SLAM建图。另一类是3D固态/混合固态雷达比如Livox Mid-360这类扫描出来是三维点云能感知高度方向的障碍物适合做更复杂的感知但价格高不少驱动配置和点云处理也对新手更不友好。我画了一张表方便按自己的预算和需求快速对比雷达型号类型扫描维度ROS驱动成熟度价格区间参考适合场景RPLIDAR A12D旋转单线平面高低入门避障、2D SLAMRPLIDAR A22D旋转单线平面高中低建图精度要求稍高的避障RPLIDAR A32D旋转单线平面高中较远距离感知Livox Mid-3603D混合固态三维点云中高高3D感知、复杂导航如果你只是想把避障代码跑通我建议从2D雷达开始。不是说3D雷达不好而是2D雷达的数据结构简单一个LaserScan消息里就是一个距离数组避障逻辑可以直接在数组上算碰到问题好排查。等2D的流程全部跑熟了再上3D雷达时你已经知道整个系统的数据流和排查方法至少不会一头雾水。1.3 整体系统架构这套履带车避障系统的架构并不复杂核心是一条单向数据流激光雷达持续扫描周围环境把距离数据打包成ROS标准消息LaserScan发布到话题 /scan 上主控电脑上的避障节点订阅这个话题对距离数据做分区处理判断前方、左方、右方障碍物的远近再根据避障策略计算出期望的线速度和角速度发布到 /cmd_vel 话题底盘驱动节点或者电机驱动板收到的指令订阅 /cmd_vel把速度指令换算成左右电机的PWM占空比驱动履带转动。这个架构几乎是所有ROS移动机器人的通用骨架。你以后想加SLAM建图、路径规划、遥控手柄本质上都是在往这条数据流里插入新的节点。比如建图就是在雷达数据流后面再接一个slam算法节点路径规划就是在避障节点位置替换成 move_base 这类导航框架。2. 硬件平台搭建与核心部件选型2.1 底盘与动力系统履带车的电机和驱动板底盘我用的是一台钢制履带底盘左右各一个直流减速电机带霍尔编码器。编码器这个配置特别重要虽然基础避障暂时用不到轮速反馈但后面做里程计、做SLAM建图时编码器是必须的所以选底盘时不要省这个钱。电机的额定电压一般是12V或24V功率取决于车重和履带摩擦。我这边实测下来12V供电时空载电流比较小但履带在地面原地转向时电流会明显上升所以驱动板不能选太弱的。常见的选择有L298N、TB6612还有大功率的BTS7960。L298N虽然经典但压降大、发热明显用在履带这种大电流负载上不太合适。我更推荐TB6612或者直接买一块集成好的机器人电机驱动扩展板输出能力够而且有现成的ROS节点可以直接通过串口或PWM控制省去自己写底层控制的麻烦。驱动板和主控之间的控制信号一般是PWM加方向引脚。比如左边电机两个引脚控制正反转一个引脚给PWM调速右边电机同理。如果你用的树莓派或者带PWM输出的主控直接GPIO输出就行。2.2 主控选型树莓派、Jetson还是工控机主控是整台车的大脑选型要看你的软件栈规模。如果你的目标就是跑通避障、看看雷达数据树莓派4B4GB及以上内存完全够用。ROS Noetic的master节点、雷达驱动、避障节点加起来CPU占用不会太高。但如果你打算后续跑Cartographer、跑导航、或者接3D雷达做点云处理树莓派就会比较吃力这时候Jetson系列Nano/Orin NX或者x86工控机是更好的选择。我自己用的是Intel NUC类的迷你工控机原因就一个字稳。x86架构跑Ubuntu和ROS生态最顺USB带宽足供电比树莓派抗造不用担心SD卡损坏也不用折腾交叉编译。缺点是功耗和体积比树莓派大但只要你的底盘不是特别迷你的那种塞一台NUC问题不大。选型时可以记住这个原则雷达数据量小、算法简单时树莓派够了数据量大、算法复杂时优先上x86或带GPU的平台。2.3 供电方案与电气连接供电这块是新手最容易翻车的地方。履带底盘上有两个主要的用电单元电机和主控含雷达。电机瞬间电流大电压波动明显如果主控和雷达直接从同一个电源取电电机一启动就会把母线电压拉低轻则雷达掉帧重则主控直接重启。正路是分路供电一块12V锂电池单独给电机驱动板供电另一路通过DC-DC降压模块给主控供电。如果只有一个电池那也要在电机驱动板的电源输入端并联一个大容量的电解电容比如470uF以上尽量吸收电机启停瞬间的尖峰。雷达如果走USB供电主控的USB口电流可能不够最好用带单独供电的USB HUB或者直接把雷达的供电线接到稳压模块上。连接顺序也有讲究先把雷达和主控的地线共地再插USB数据线。两个系统如果不共地USB通信经常会出现随机断连、数据错乱这种疑难杂症排查起来非常头疼。2.4 激光雷达的安装与通信方式激光雷达的安装位置有两个硬性要求一是要保证360度扫描视野不被车体自身遮挡二是扫描平面要尽量水平。履带车因为有悬挂和履带张紧车身姿态在运动时会有一点俯仰如果雷达装得不稳扫描平面一歪避障判断的距离就会失真严重的时候会漏掉低矮障碍物。我的做法是给雷达加了一个3D打印的支架固定在底盘前部的安装板上雷达本体离地面大约15cm到20cm。这个高度对室内常见的椅子腿、纸箱、墙壁底部来说基本都能扫到。通信方式上2D雷达一般是USB串口插上主控后会出现 /dev/ttyUSB0 这样的设备节点3D雷达比如Mid-360一般走网口需要给雷达配一个和主控同一个网段的静态IP。不管是哪种第一步都是确认主控能读到数据再进ROS。3. 软件环境准备ROS安装与工作空间初始化3.1 Ubuntu与ROS版本搭配ROS版本和Ubuntu版本是绑定的。目前最稳定、教程最多的是 Ubuntu 20.04 ROS Noetic这也是我推荐新手使用的组合。如果你的电脑已经是Ubuntu 22.04那对应的是ROS 2 Humble但很多老教程和驱动包还是基于ROS 1的Noetic写的对新手来说先别追求版本新能跑通更重要。Ubuntu 24.04对应的ROS版本比如Jazzy还在快速更新中一些第三方的雷达驱动包未必同步支持所以不建议现在踩这个坑。一句话总结桌面级ROS小车折腾环境的时间成本极高选最主流的组合等于省下大量查错时间。3.2 ROS安装手动配置与自动化脚本ROS的安装分成两部分添加软件源和安装核心包。手动安装需要先加ROS官方源、设置密钥、更新索引然后才能装ros-noetic-desktop-full。这个流程本身不复杂但国内网络环境下官方源经常慢到怀疑人生需要换成国内镜像源对新手来说每一步都可能卡住。我当时在项目里为了方便直接用社区里流传较广的ROS自动化安装脚本网上常说的“一键安装”。这类脚本会自动完成换源、装依赖、初始化rosdep等一系列步骤一条命令跑完省去大量手动配置工作。如果后续想在多台设备上装ROS这个方式几乎是最省心的一种。装完之后养成一个好习惯执行 sudo apt update sudo apt upgrade并且手动编译一下工作空间确认基本依赖没问题再往下走。3.3 创建工作空间与雷达驱动包准备ROS里的代码组织单位是工作空间workspace一般叫 catkin_ws。创建步骤很简单mkdir -p ~/catkin_ws/src cd ~/catkin_ws catkin_make如果编译过程中报缺依赖的错误就用rosdep install --from-paths src --ignore-src -r -y这条命令把缺失的依赖装齐。这一步极其常见新手别慌报错信息里一般直接写了缺哪个包。雷达驱动这块2D雷达厂商一般都会提供ROS驱动包下载源码放到 src 目录下编译后就能用。编译时注意主控的ROS版本和包的兼容性如果报错优先看是不是缺某个依赖没装。4. 激光雷达驱动配置与数据可视化4.1 雷达上线串口权限与话题确认雷达插上主控后第一步是确认系统识别到了设备ls /dev/ttyUSB*如果能看到设备节点说明USB转串口芯片已经工作。但ROS驱动需要读写该串口而默认串口设备属于 dialout 用户组当前用户很可能没有权限导致驱动启动时报 permission denied。解决办法是把当前用户加入 dialout 组sudo usermod -aG dialout $USER改完需要重新登录用户才能生效。这个权限问题几乎每个玩雷达的人都会遇到一次建议提前处理。3D雷达如果是网口通信还需要把雷达的IP和主控网口的IP配到同一网段。比如雷达出厂IP是192.168.1.208那主控的网口就手动设置为192.168.1.50这种同网段的静态IP然后在驱动launch文件里指定雷达的IP地址。配好后启动雷达驱动在另一个终端里用rostopic list查看话题应该能看到 /scan 或者 /livox/lidar 这样的点云话题。再用rostopic echo /scan打印几帧数据看到 ranges 数组里不断变化的距离值说明雷达数据已经顺利进入ROS了。4.2 用Rviz查看激光数据命令行看数据不直观把数据可视化出来才能验证雷达的扫描范围和安装角度是否正确。启动Rvizrosrun rviz rviz然后在左侧的 Displays 面板里点击 Add添加一个 LaserScan 显示插件在话题Topic栏填 /scan再修改 Fixed Frame 为激光雷达的坐标系名称一般是 laser 或者 laser_frame。如果设置正确就能看到一个以雷达为中心的一圈红色点云这就是雷达实时扫描到的环境轮廓。看到这圈点云的那一刻整个项目就成功一半了。因为这说明从硬件到驱动到ROS通信整条链路全部打通雷达有数据、驱动在发布、系统在转发、Rviz能收到。后面的避障代码本质上就是在操作这个红圈里的数据。4.3 理解LaserScan消息结构在往避障代码里写数据索引之前必须先搞清楚LaserScan消息里每个字段的含义。LaserScan消息最关键的部分是这几个字段angle_min / angle_max扫描的起始角度和结束角度单位是弧度。2D雷达一般是 -π-180°到 π180°但不是所有雷达都覆盖完整的360°有些角度范围只有270°这个要看雷达手册。angle_increment相邻两个扫描点之间的角度增量。range_min / range_max雷达有效测量的最小和最大距离。ranges一个浮点数组存的是每个角度上测到的距离值单位是米。ranges 数组的下标和角度的关系是角度 angle_min 下标 * angle_increment。比如某雷达angle_min是0angle_increment是1°换算成弧度那么ranges[0]就是0°方向测到的距离ranges[90]就是90°方向的距离。理解了这个对应关系避障代码就变得很简单从 ranges 数组里把前方某个角度范围内的距离值取出来取最小的那个和设定的安全距离比较就是有没有东西挡路的直接依据。5. 避障算法设计与代码实现5.1 避障原理把激光数据变成行驶决策避障问题可以抽象成一句话感知周围环境根据障碍物的位置决定下一秒往哪走。这个决策过程不需要机器学习不需要高深算法用最基本的分区判断就能实现。思路是把雷达扫描范围分成三个区域左方、前方、右方。每个区域取该角度范围内最近的障碍物距离。然后按照下面的优先级做决策如果前方障碍物距离大于安全距离说明前方没有障碍或很远小车直行如果前方有障碍就看看左右哪一侧更空旷转向空旷的一侧如果左右两侧都不满足避让条件都太近了小车原地后退或者旋转重新寻找可通行方向。这里的关键是安全距离和区域角度范围这两个参数。安全距离设太大小车离障碍物老远就停下拐弯表现在行为上就是敏感、走不了直线设太小小车离障碍物太近才反应可能会撞上。区域角度范围设宽了侧向感知太灵敏小车会频繁转向设窄了又容易漏掉侧面靠近的障碍物发生侧面刮蹭。我在实际测试里室内环境下安全距离设定在0.35米到0.5米之间比较合适前提是车速在0.15m/s左右。车速越快安全距离就要相应加大否则刹车距离不够。5.2 完整避障代码LaserScan分区决策下面是整个项目最核心的避障节点代码直接用rospy写依赖非常少只需要在ROS环境里装了rospy和sensor_msgs就能跑。这份代码我实测下来可以稳定运行你直接复制到脚本里改成可执行文件然后rosrun启动即可。#!/usr/bin/env python3 import math import rospy from sensor_msgs.msg import LaserScan from geometry_msgs.msg import Twist class ObstacleAvoidance: def __init__(self): rospy.init_node(obstacle_avoidance_node) # 发布速度指令到/cmd_vel self.cmd_pub rospy.Publisher(/cmd_vel, Twist, queue_size1) # 订阅激光雷达数据 self.scan_sub rospy.Subscriber(/scan, LaserScan, self.scan_callback) # 保存最近一帧雷达数据 self.latest_scan None # 可调参数 self.safe_dist rospy.get_param(~safe_dist, 0.4) # 安全距离米 self.linear_speed rospy.get_param(~linear_speed, 0.15) # 直线速度m/s self.turn_speed rospy.get_param(~turn_speed, 0.35) # 转向速度rad/s self.rate rospy.Rate(10) # 控制频率10Hz def scan_callback(self, msg): self.latest_scan msg def get_region_distance(self, scan, angle_min, angle_max): 计算某个角度范围内的最近障碍物距离。 angle_min / angle_max 的单位是弧度逆时针为正。 min_dist float(inf) # 雷达扫描点角度范围要处理好正负跨越 for idx, r in enumerate(scan.ranges): if not math.isfinite(r): continue if r scan.range_min or r scan.range_max: continue angle scan.angle_min idx * scan.angle_increment # 处理角度范围跨越 -pi/pi 的情况 # 比如要找[-pi, -2pi/3]范围归一化到[-pi, pi] while angle math.pi: angle - 2 * math.pi while angle -math.pi: angle 2 * math.pi if angle_min angle angle_max: if r min_dist: min_dist r return min_dist def decide_and_publish(self): if self.latest_scan is None: return scan self.latest_scan # 划分三个区域左方、前方、右方 # 以车头朝向为0度 front_region self.get_region_distance(scan, -math.pi / 6, math.pi / 6) # -30° ~ 30° left_region self.get_region_distance(scan, math.pi / 6, math.pi / 2) # 30° ~ 90° right_region self.get_region_distance(scan, -math.pi / 2, -math.pi / 6) # -90° ~ -30° twist Twist() if front_region self.safe_dist: # 前方安全直行 twist.linear.x self.linear_speed twist.angular.z 0.0 else: # 前方有障碍比较左右两侧距离 if left_region right_region: twist.linear.x 0.05 twist.angular.z self.turn_speed # 左转 elif right_region left_region: twist.linear.x 0.05 twist.angular.z -self.turn_speed # 右转 else: # 左右都不满足时原地左转找路 twist.linear.x 0.0 twist.angular.z self.turn_speed self.cmd_pub.publish(twist) def run(self): rospy.loginfo(obstacle avoidance node started) while not rospy.is_shutdown(): self.decide_and_publish() self.rate.sleep() if __name__ __main__: try: node ObstacleAvoidance() node.run() except rospy.ROSInterruptException: pass代码的核心在 decide_and_publish 函数里。每次从激光数据里采样三个方向的最近距离然后按决策逻辑计算速度指令。有几个参数我单独说明一下。safe_dist前方触发避障的距离阈值。0.4米在室内已经算比较激进的设置车速慢的话够安全。如果你想让小车更保守一点调到0.6米。linear_speed直行速度。履带车负载重、惯量大起步建议不超过0.2m/s不然避障来不及反应。turn_speed转向角速度。履带车原地转或绕行时如果转速太低车头转向力矩不够会划着弧线冲向前方障碍。0.35rad/s是一个比较合适的中间值。启动方式有两种。如果节点文件在功能包scripts目录下先加可执行权限chmod x obstacle_avoidance.py然后rosrun your_package obstacle_avoidance.py或者如果你只是验证代码逻辑也可以直接放在任意目录用python3 obstacle_avoidance.py启动前提是ROS环境已经source过source /opt/ros/noetic/setup.bash。5.3 参数调节与实测注意事项代码能跑起来和跑得好是两码事。我在调试这套避障参数时记录了一些真实遇到的问题和处理思路。第一个问题是角度范围的选择。激光雷达的0度朝向一般和车头方向重合但有些雷达装在车上会有安装偏角。如果你发现小车对着正前方的障碍物没反应但侧面很敏感那大概率是雷达的0度方向和车头方向没对齐。解决办法是先在Rviz里对比雷达点云和车体的实际朝向如果偏了要么物理调整安装角度要么在驱动launch文件里加一个frame旋转补偿或者在代码里把分区角度整体平移一个偏置角。第二个问题是障碍物漏检。低矮物体比如门槛、台阶在2D雷达扫描平面以下雷达扫不到导致小车正面撞上。这是2D雷达的结构性限制。如果你真的要经常跨门槛要么把雷达安装高度降低要么后续换3D雷达。室内平地上这个问题基本可以忽略。第三个问题是决策频率。我定的控制频率是10Hz也就是每0.1秒做一次决策。频率太低比如2Hz小车在两次决策之间冲出去的距离就会太长频率太高则没有必要因为雷达扫描频率本身一般也就是10Hz左右你决策得再快也无法获取更多新信息。10Hz是ROS小车非常常用的控制频率兼顾了实时性和稳定性。6. 实测效果与常见问题排查6.1 最容易踩的五个坑这套系统我前后调试了两周把主要的问题整理成一张表每个问题都对应了具体的排查方向现象可能原因排查与解决雷达话题没有数据串口权限不足 / 雷达IP不通检查/dev/ttyUSB0权限usermod加dialout组后重新登录网口雷达检查IP同网段、ping雷达IPRviz里点云是歪的雷达安装倾斜 / 坐标系未对齐检查雷达支架水平度确认TF中激光坐标系名称和Rviz的Fixed Frame一致小车直行时撞墙安全距离太小 / 速度太快增大safe_dist降低linear_speed检查前方分区角度是否覆盖车头正前方小车频繁原地转圈角度分区太窄或左右两侧都被认为是障碍扩大左右区域角度范围检查safe_dist是否设置过大小车转向响应迟钝转向速度过低 / 电机供电不足增大turn_speed检查电池电压是否跌落电机驱动板是否限流电机发出嗡嗡声但不转PWM频率不合适或占空比过低检查驱动板PWM频率常见在1kHz-20kHz之间适当提高基础占空比表里的最后一条PWM震动问题是履带车特有的。履带传动链长、摩擦力大太低的PWM占空比会让电机处于要转不转的边缘状态这时候齿轮箱里就会传出明显的嗡嗡声。解决办法是给速度指令加一个死区低于某个速度就直接给0高于这个速度就一次性给到一个足够克服摩擦的占空比。很多成品机器人驱动板在固件里已经做了这个处理。6.2 排查思路从数据流倒着找问题调试机器人有个万能的排查思路就是从数据流的下游往上游逐级检查永远不要一上来就怀疑算法。先说一个我自己的真实经历有一次小车直行时完全无视正前方的纸箱直直撞上去了。我第一反应是避障代码有bug翻来覆去看了好几遍决策逻辑没发现问题。后来用rostopic echo /scan打印数据发现雷达正前方距离值显示的是3.5米——可是明明纸箱就在半米外。最后拆开雷达外壳发现是雷达的透光罩上沾了一大块泥巴把正前方的激光挡住了。所以正确的排查顺序是先看话题数据对不对。rostopic echo /scan、rostopic hz /scan确认雷达在发、频率正常、数据里能看到障碍物。再看速度指令对不对。rostopic echo /cmd_vel用障碍物挡在车前看避障节点是否发布了转向指令。最后看执行机构对不对。拔掉雷达用手推一下小车或者直接给/cmd_vel发一条固定速度指令看电机是否按预期转动。这三步走完问题必然被定位到雷达感知、算法决策、底盘执行这三层中的某一层。绝大多数所谓玄学故障最后都能这样一步步查到根因。6.3 从避障到建图导航的扩展路径避障跑通之后如果你还想继续往下走方向很清晰建图和自主导航。这也是标题里提到自动驾驶小车的核心延伸。建图推荐用Cartographer或者Gmapping它们能把激光雷达数据拼成一幅二维地图。和避障的最大区别是建图需要同时用到雷达数据和里程计数据也就是左右电机编码器反馈的位移信息。如果你的底盘只有PWM调速、没有编码器反馈就需要先补一个里程计发布节点把编码器脉冲数换算成位移以odom坐标系发布出来。这一步是整个建图流程里工作量最大的部分。有了地图和定位之后导航就顺理成章了。move_base框架负责在地图上搜索一条从当前位置到目标点的路径同时持续接收激光雷达数据做局部避障——我在前面写的那个分区避障算法本质上就是move_base里局部代价地图的一个极简实现。所以从长远看这篇避障代码相当于给你搭好了一个最小系统骨架。日后无论是换更高性能的主控、上3D雷达、还是接语音模块、机械臂改动都不会超出往ROS节点图里插新节点这个框架。写在最后这套履带避障车做下来我最深的感受是ROS小车的难点从来不在某个单一环节而在于把硬件-驱动-算法-执行这条链路理顺。雷达不出数就怀疑驱动驱动没问题就看算法算法没错那就回头检查机械这个排查逻辑比任何单个知识点都值钱。如果你也想复刻这个项目我建议别想着一次到位先让雷达转起来再让车轮动起来最后再把两者合到一起。每走一步都确认一下当前阶段没问题再进入下一步。我见过太多人上来就急着跑避障代码结果雷达权限没配、坐标系没对上、速度参数没调最终只能在报错日志前干瞪眼。技术路线上接下来你还可以给这辆小车加上远程遥控、语音控制、目标点导航这些都是顺着这套ROS框架自然生长出来的能力。先把这篇文章里的核心代码跑通你就已经站在一个相当不错的起点了。
返回列表