十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

从零搭建ROS2机器人:URDF建模、RVIZ2可视化与关节驱动实践

从零搭建ROS2机器人:URDF建模、RVIZ2可视化与关节驱动实践 创客营第6天早上我站在教室门口听见两个学员在争用键盘能不能让小乌龟走出五角星。这个画面其实挺触动我的——经过前5天的折腾他们已经不把ROS2当成一套需要背命令的工具而是当成一个活的东西在玩了。这正是少年创客营想要的状态。但今天要做的不是继续玩小乌龟。今天我们从运行别人的例程跨到拥有自己的机器人用URDF给机器人搭骨架用RVIZ2把它显示出来再写一个自己的节点让话题数据驱动关节转起来。这篇内容适合三类人看正在带青少年入门ROS2的老师、前5天刚好把环境和小乌龟跑通的学员、以及任何想从跑通例程迈向自己建模的ROS2初学者。1. 第6天为什么是分水岭前5天我们到底种下了什么1.1 前5天的课程主线少年创客营的进度不是随便排的。为了应对不同基础的学员我的原则是先让机器动起来再讲透理论所以前5天设计的是一条非常明确的爬坡路线天数核心内容完成标志Day 1Ubuntu 与 ROS2 环境安装终端里能运行ros2 --versionDay 2ROS2 六大基础概念节点、话题、服务、动作、参数、TF能说出话题和服务的区别Day 3turtlesim 实操键盘遥控、画图、话题回显能画出简单图形并解释数据流Day 4工作空间、功能包结构、colcon 编译能创建自己的功能包Day 5写第一个最简发布/订阅节点talker/listener两个终端能看到消息收发这个顺序可能和很多教材不一样。教材一般会把架构放在最前面但给青少年讲课时架构讲太久他们会睡着。我选择反过来先让他们用命令行遥控小乌龟让小乌龟真的按方向键动起来他们才会对节点在通信这件事产生体感然后再回过头讲概念。到第5天结束时绝大部分学员已经完成了一个关键转变能在自己的ros2_ws工作空间里创建一个包编译通过并且跑起来一个自己写的发布者节点。虽然他们写的节点干的事情很简单只是不断发一句话但写代码→编译→运行→被另一个程序收到这个闭环一旦打通后面所有内容都是在往这个闭环里加东西。1.2 Day 6 要解决的问题前5天有个隐藏问题一直没被正面处理学员玩的小乌龟也好自己写的 talker 也好都是别人定义好的东西。小乌龟长得那样是因为乌龟包早就写死了talker 发的消息也只是普通字符串看不见摸不着。第6天要解决的核心问题就是我们能不能从零拥有一个自己的机器人这个机器人不需要是真的硬件先在软件里把长什么样关节怎么动谁去控制它这三件事完整走一遍。走完这一步以后再碰真实机器人的电机、轮子、传感器思路完全一样。所以我把 Day 6 的目标拆成三条用 URDF 给机器人建模底盘是蓝色的、轮子是深色的、关节在哪、轮子绕哪个轴转。用 RVIZ2 把这个模型可视化出来并且能拖拽视角观察。写一个自己的节点发布 joint_states 话题驱动机器人的轮子转动。听起来很多但做起来是顺的因为第5天的发布者节点已经帮学员攒下了如何发布一个话题的肌肉记忆今天只是把话题内容从字符串换成关节状态数据。1.3 Day 6 的成品预览我在开营时给学员看了一段录好的效果视频一个蓝色底盘、两个深色轮子的小车静止在 RVIZ2 画面中央然后在某个瞬间两个轮子开始均匀转动身边终端里滚动着一行行日志。有学员问老师它为什么不动 我说因为控制它转圈的节点还没有运行。 等wheel_spinner一启动轮子就转了。那一刻所有人都看明白了——机器人模型本身是死的是话题里的数据让它活起来的。这就是机器人开发最核心的心智模型。这个成品预览非常重要。对于少年学员你给一串目标清单他记不住你给他一个做完之后我能看到什么的画面他就能自己惦记着往那个方向走。2. 把话题讲成厨房传菜30分钟吃透ROS2通信的核心2.1 节点、话题、消息厨房里的三个角色少年创客营里第一个难点就是话题通信。我发现如果直接讲发布者/订阅者模型学员容易陷入术语迷宫。后来我换了厨房的例子效果好了很多。节点可以理解成厨房里各司其职的厨师一个负责切菜一个负责炒菜一个负责装盘。每个厨师只关心自己的活切菜的不需要知道炒菜的怎么颠勺。话题是厨房里的传菜通道比如一条传送带。切菜师傅把切好的菜放上传送带炒菜师傅从传送带上拿菜。关键在于传送带本身不关心谁放菜、谁拿菜它只负责把东西从一头送到另一头。消息就是传送带上传递的一盘菜它必须服从一个固定的规格比如装菜的盘子必须是统一的尺寸如果某天换了个超大号盘子传送带就可能卡住。在 ROS2 里这条统一规格就是消息类型。这个类比能解释很多后续会踩的坑为什么发布者和订阅者的消息类型必须一致因为你的菜是装盘子里传的人家传送带只认这种盘子。为什么话题名拼错一个字就收不到因为传送带编号搞错了菜送到别的通道去了。2.2 命令行三件套先动手概念自然就通了讲完类比我的习惯是立刻让学员回到终端用小乌龟亲手感受数据在流动。第一步开一个终端启动小乌龟仿真器ros2 run turtlesim turtlesim_node第二步再开一个终端启动键盘遥控节点ros2 run turtlesim turtle_teleop_key这时候按方向键小乌龟已经在动了。但我让学员先别急着玩而是开第三个终端依次敲三组命令ros2 node list ros2 topic list ros2 topic echo /turtle1/cmd_velros2 node list会列出当前所有节点你能看到/teleop_turtle和/turtlesim。ros2 topic list会列出话题比如/turtle1/cmd_vel和/turtle1/pose。最有意思的是ros2 topic echo /turtle1/cmd_vel。执行这条命令后回到键盘窗口按方向键echo 窗口里会不断刷出速度数据linear: x: 2.0 y: 0.0 z: 0.0 angular: x: 0.0 y: 0.0 z: 0.0 ---这时候学员的表情一般是恍然原来我按了一下方向键背后真的有一条数据流从这个节点发到那个节点。消息不是虚拟的是真实存在、可以被偷看到的。这比任何PPT都管用。2.3 用 rqt_graph 验证看不见的管道如果时间允许我还会让学员跑一下可视化工具 rqt_graphrqt_graph屏幕上会出现两个椭圆一个写着/teleop_turtle一个写着/turtlesim中间有一条连线连线上标着/turtle1/cmd_vel。这条线就是话题管道的图形化表达。我会抛一个关键问题如果你先开 rqt_graph再开 teleop 节点图会变吗 答案当然会。ROS2 话题是动态的节点上线后话题才出现节点下线后话题就消失。这个特性对后面排查问题特别重要——很多RVIZ2里看不到模型的怪问题根源就是某个节点没启动话题根本不存在。第2天到第3天我们会反复用这三件套ros2 node list、ros2 topic list、ros2 topic echo。到了第6天学员已经能条件反射地用它来排查问题了这比记住任何架构图都实在。3. 写第一个发布者节点让数据在终端里清清楚楚3.1 创建功能包命令背后做了什么在给 URDF 模型写关节驱动之前我会带学员先写一个更简单的发布者节点。这个节点发布普通的字符串消息目的是复习第5天的知识点同时为后面的joint_states发布者热身。首先确保你在工作空间的 src 目录里cd ~/ros2_ws/src ros2 pkg create --build-type ament_python day6_publisher --dependencies rclpy std_msgs这条命令里--build-type ament_python表示创建一个 Python 包--dependencies rclpy std_msgs表示自动把依赖写进package.xml。依赖很关键后面如果我们用了sensor_msgs也要手动加。关于这个命令我发现很多学员会忽略--dependencies的作用。如果不加编译不会报错但运行时 import 会失败因为包的依赖信息在 package.xml 和 setup.py 里是缺的。所以每次创建包之前先想清楚我这个包要用什么消息类型都列在依赖里。3.2 talker.py一个最小可用的发布者创建完成后进入包的 Python 源码目录cd ~/ros2_ws/src/day6_publisher/day6_publisher新建一个talker.py写入下面的代码import rclpy from rclpy.node import Node from std_msgs.msg import String class Talker(Node): def __init__(self): super().__init__(day6_talker) self.publisher_ self.create_publisher(String, chatter, 10) self.timer self.create_timer(0.5, self.timer_callback) self.count 0 def timer_callback(self): msg String() msg.data Hello from Day 6: %d % self.count self.publisher_.publish(msg) self.get_logger().info(Publishing: %s % msg.data) self.count 1 def main(argsNone): rclpy.init(argsargs) node Talker() rclpy.spin(node) node.destroy_node() rclpy.shutdown() if __name__ __main__: main()这段代码非常短但它包含了一个 ROS2 节点所有必备的零件继承Node在构造函数里给节点取名叫day6_talker。创建一个发布者消息类型是std_msgs/String话题名是chatter队列长度是 10。用create_timer每 0.5 秒回调一次相当于设定一个每隔半秒做一次这件事的闹钟。回调函数里做的事造一个消息、填数据、发布出去、打印日志。这里我会特意强调create_timer的作用。没有它节点会一直在spin()里空转什么也不干。定时器就是给节点装上了一个节拍器这是所有周期发布型节点的标准写法。写完代码后还要改一下setup.py里的entry_points否则运行ros2 run时找不到这个可执行文件entry_points{ console_scripts: [ talker day6_publisher.talker:main, ], },3.3 编译运行并在另一个终端里抓到数据回到工作空间根目录编译cd ~/ros2_ws colcon build --packages-select day6_publisher source install/setup.bash ros2 run day6_publisher talker跑起来后发布者自己的终端会不断打印[INFO] ... Publishing: Hello from Day 6: 0 [INFO] ... Publishing: Hello from Day 6: 1这时候再开一个新终端用第2天学过的 echo 去抓这条数据流source ~/ros2_ws/install/setup.bash ros2 topic echo /chatter如果一切正常你会看到data: Hello from Day 6: 12 ---再运行ros2 topic info /chatter能从输出里看出话题类型、发布者数量和订阅者数量。这一步我坚持让每个学员做因为它是通信闭环的验收标准我的程序真的发出了数据另一个程序真的能收到。只有亲手跑通这个后面发布joint_states才有底气。3.4 为什么第6天选 Python 而不是 C几乎每次都有家长问为什么不用 C真实机器人不是都用 C 吗我的回答是第6天选 Python 不是因为 C 不好而是不想让语言细节挡住通信概念。Python 版 ROS2 节点改完代码直接colcon build再运行不需要处理头文件、CMakeLists、链接库这些事。对少年学员来说C 的编译报错信息光读懂就要花掉半节课。对比项PythonC上手速度快几乎零门槛需要处理编译和链接适合场景原型验证、教学、工具脚本性能敏感、生产级机器人调试成本低改完就能跑编译错误会劝退新手社区资料非常丰富同样丰富但英文文档多我会跟学员说清楚Python 适合把思路跑通等你理解了思路未来需要更高性能时再去接触 C 完全来得及。机器人开发里最重要的是通信思维不是语言本身。4. URDF建模用XML给机器人搭骨架4.1 URDF的本质连杆与关节的说明书URDF 全称是 Unified Robot Description Format翻译过来就是统一机器人描述格式。听起来很唬人本质就是一个 XML 文件用标签描述机器人由哪些部件组成、这些部件怎么连接。我给学员的类比是URDF 就像一份乐高拼装说明书。说明书不会直接给你一个拼好的机器人而是告诉你这里有一块蓝色底板那里有一个轮子轮子和底板之间用一根轴连接。这份说明书既不负责让机器人动起来也不负责模拟物理世界它只负责描述。URDF 里两个最核心的标签是link和joint。link表示机器人的一个部件比如底盘、轮子、机械臂的臂杆joint表示部件之间的连接关系决定两个部件相对位置怎么摆、能不能转动。4.2 搭一个最简单的底盘我们直接从零搭一个小车。先只做一块蓝色底板robot nameday6_bot link namebase_link visual origin xyz0 0 0.04 rpy0 0 0/ geometry box size0.4 0.25 0.08/ /geometry material nameblue color rgba0.2 0.3 0.9 1.0/ /material /visual /link /robot这里每一步都是有讲究的box size0.4 0.25 0.08/表示底盘是一个长方体长 0.4 米、宽 0.25 米、厚 0.08 米。origin xyz0 0 0.04 rpy0 0 0/表示这个长方体的几何中心相对base_link坐标系原点抬高 0.04 米。这样做的目的是让底盘下表面正好落在 z0 的平面上也就是地面。color rgba0.2 0.3 0.9 1.0/表示蓝色。只加一个 link 时RVIZ2 已经能显示这个底盘了但它还只是一块会悬浮的蓝色砖头。底盘没有轮子算不上机器人。4.3 加上轮子joint 是让骨架灵活的关键接下来给底盘加两个驱动轮。轮子本身是link轮子和底盘的连接关系用joint描述。以左轮为例joint nameleft_wheel_joint typecontinuous parent linkbase_link/ child linkleft_wheel/ origin xyz0 0.15 0.05 rpy0 0 0/ axis xyz0 1 0/ /joint三个地方需要重点解释typecontinuous表示这是一个可以无限旋转的关节适合轮子。ROS2 常用的关节类型有四种fixed固定不能动、continuous无限旋转、revolute有限角度转动、prismatic直线滑动。我们的小车驱动轮用continuous万向支撑轮用fixed。origin xyz0 0.15 0.05/表示轮子中心相对底盘中心的位置x 方向 0 表示在中点y 方向 0.15 表示在左侧z 方向 0.05 表示轮心离地 0.05 米。为什么要 0.05因为轮子半径是 0.05轮心离地 0.05轮子下缘才正好贴地。axis xyz0 1 0/表示关节绕 y 轴旋转。差速小车的轮子就是绕 y 轴转转起来车才能沿 x 方向前进或后退。这里的轴写错轮子就会在空中乱晃。轮子 link 的视觉部分也有一点小坑。URDF 的cylinder默认轴线沿 z 轴也就是圆柱是竖着的。但车轮要像方向盘一样横过来所以要在 visual 的origin里加一个绕 x 轴旋转 90 度的角度link nameleft_wheel visual origin xyz0 0 0 rpy1.5707963 0 0/ geometry cylinder radius0.05 length0.03/ /geometry material namedark color rgba0.2 0.2 0.2 1.0/ /material /visual /link4.4 完整URDF文件与语法检查把底盘、左右轮和一个支撑球组合起来得到完整的day6_bot.urdf?xml version1.0? robot nameday6_bot link namebase_link visual origin xyz0 0 0.04 rpy0 0 0/ geometry box size0.4 0.25 0.08/ /geometry material nameblue color rgba0.2 0.3 0.9 1.0/ /material /visual /link joint nameleft_wheel_joint typecontinuous parent linkbase_link/ child linkleft_wheel/ origin xyz0 0.15 0.05 rpy0 0 0/ axis xyz0 1 0/ /joint link nameleft_wheel visual origin xyz0 0 0 rpy1.5707963 0 0/ geometry cylinder radius0.05 length0.03/ /geometry material namedark color rgba0.2 0.2 0.2 1.0/ /material /visual /link joint nameright_wheel_joint typecontinuous parent linkbase_link/ child linkright_wheel/ origin xyz0 -0.15 0.05 rpy0 0 0/ axis xyz0 1 0/ /joint link nameright_wheel visual origin xyz0 0 0 rpy1.5707963 0 0/ geometry cylinder radius0.05 length0.03/ /geometry material namedark/ /visual /link joint namecaster_joint typefixed parent linkbase_link/ child linkcaster/ origin xyz0.1 0 0.02 rpy0 0 0/ /joint link namecaster visual origin xyz0 0 0 rpy0 0 0/ geometry sphere radius0.02/ /geometry material namegray color rgba0.7 0.7 0.7 1.0/ /material /visual /link /robot写完后如果安装了liburdfdom-tools可以立刻用check_urdf检查文件有没有语法问题sudo apt install liburdfdom-tools check_urdf day6_bot.urdf正常的输出会列出机器人包含哪些 link 和 joint以及哪些 link 是 root。如果 XML 标签没闭合或者坐标引用写错这里就会直接报出来。我建议大家每写完一个 URDF 就检查一次别等到 RVIZ2 里一片空白才开始慌。另外有个细节必须说明这个 URDF 只写了visual没有写collision和inertial。纯可视化阶段这是足够的但如果未来要把模型放进 Gazebo 做物理仿真这两个标签必须补上否则模型没有碰撞体积也没有质量仿真器会把它当幽灵处理。5. RVIZ2显示与关节驱动从静态骨架到能转的轮子5.1 用launch文件一次启动所有组件URDF 文件写好后要让它显示出来需要启动三个东西robot_state_publisher读取 URDF 并发布 TF 变换RVIZ2 靠 TF 才知道每个部件在哪。一个发布关节状态的节点我们先不启动joint_state_publisher后面解释为什么。rviz2可视化界面。如果每次都在四个终端里手动敲命令很容易漏启动一个。我通常在工程里放一个 launch 文件让ros2 launch一次搞定。在day6_publisher包下新建launch目录创建day6_display.launch.pyimport os from launch import LaunchDescription from launch_ros.actions import Node def generate_launch_description(): urdf_path /home/your_name/ros2_ws/src/day6_publisher/urdf/day6_bot.urdf with open(urdf_path, r) as f: robot_description f.read() return LaunchDescription([ Node( packagerobot_state_publisher, executablerobot_state_publisher, parameters[{robot_description: robot_description}], ), Node( packagerviz2, executablerviz2, namerviz2, ), ])注意把urdf_path换成你自己的绝对路径。真实工程里一般会用Command和 xacro 处理路径但在教学场景中直接用open(...).read()更直观学员能一眼看明白我们只是把 URDF 文件的文本内容传给了robot_description参数。要让这段代码能被包找到还需要在setup.py里把launch目录加进data_filesimport os from glob import glob data_files[ (os.path.join(share, package_name, launch), glob(launch/*.launch.py)), ],接下来编译并启动cd ~/ros2_ws colcon build --packages-select day6_publisher source install/setup.bash ros2 launch day6_publisher day6_display.launch.pyRVIZ2 窗口会弹出来但模型通常不会立刻出现还需要做两项设置。5.2 RVIZ2里的三步设置Fixed Frame、RobotModel、观察第一次打开 RVIZ2界面里只有一个黑色的 3D 空间没有小车。这不是出错而是 RVIZ2 不知道你想看什么。你需要三步第一步在左上角Global Options里把Fixed Frame改成base_link。Fixed Frame 是 RVIZ2 里所有坐标的基准必须设成 URDF 里真实存在的 link 名。如果这里填错了或者填了一个不存在的 link画面里往往什么都显示不出来。第二步点击左下角Add按钮弹出的对话框里选RobotModel。添加后左侧面板会出现一个RobotModel条目它的Description Topic默认就是/robot_description正好对应robot_state_publisher发布的那个话题。第三步把视角调整到合适位置。按住鼠标中键拖动旋转滚轮缩放Ctrl加鼠标左键平移。对新手来说把视角调到斜上方 45 度看小车最舒服。这时候蓝色底盘、两个深色轮子、灰色的支撑球应该都显示出来了。但轮子还是静止的因为还没有任何节点给关节发布位置数据。5.3 亲手发布joint_states轮子真正转起来要让轮子转需要向/joint_states话题发布sensor_msgs/msg/JointState消息。这条消息包含三个关键字段name关节名列表、position每个关节当前角度、velocity每个关节速度。我给学员写了一个简单的轮子驱动节点放在day6_publisher/day6_publisher/wheel_spinner.pyimport math import rclpy from rclpy.node import Node from sensor_msgs.msg import JointState class WheelSpinner(Node): def __init__(self): super().__init__(wheel_spinner) self.publisher_ self.create_publisher(JointState, joint_states, 10) self.timer self.create_timer(0.02, self.timer_callback) self.angle 0.0 def timer_callback(self): msg JointState() msg.header.stamp self.get_clock().now().to_msg() msg.name [left_wheel_joint, right_wheel_joint] self.angle 0.02 self.angle % (2 * math.pi) msg.position [self.angle, self.angle] self.publisher_.publish(msg) def main(argsNone): rclpy.init(argsargs) node WheelSpinner() rclpy.spin(node) node.destroy_node() rclpy.shutdown() if __name__ __main__: main()代码核心就两件事每条消息里把两个关节的名字告诉系统left_wheel_joint和right_wheel_joint。这两个名字必须和 URDF 里写的一模一样哪怕差一个下划线robot_state_publisher也认不出来。每条消息里给这两个关节设置相同的角度值。角度值每隔 0.02 秒增加 0.02 弧度相当于轮子每秒转 1 弧度肉眼能看到明显转动但又不会快到看不清。在setup.py的entry_points里加上wheel_spinner day6_publisher.wheel_spinner:main,然后重新编译开一个新终端运行source ~/ros2_ws/install/setup.bash ros2 run day6_publisher wheel_spinner回到 RVIZ2 窗口你会看到两个轮子开始转动。整个过程没有一行物理仿真代码只是往/joint_states里发角度数据TF 系统就会自动把轮子的旋转关系算出来。这就是 ROS2 里数据驱动可视化最典型的演示。我会特意让学员对比一下talker.py发布字符串wheel_spinner.py发布数组这两个节点结构几乎一样——都是创建发布者、创建定时器、在回调里填充消息并发布。一旦理解了这个套路以后控制真实电机、发导航目标、读取传感器全是在重复这个模式。5.4 一个常见的多发布者冲突现象很多教程会在这一步让你安装并运行joint_state_publisher_gui它会弹出一个滑条窗口可以手动拖动关节角度sudo apt install ros-${ROS_DISTRO}-joint-state-publisher-gui ros2 run joint_state_publisher_gui joint_state_publisher_gui这个工具对调试 URDF 非常有用尤其适合检查你的关节定义是否正确。但我故意没有把它加进 launch 文件因为有个坑joint_state_publisher_gui和我们的wheel_spinner都在发布/joint_states。话题是允许多个发布者的但两个发布者各发各的角度值robot_state_publisher会听到两套指令轮子就会抖动或者卡在一个奇怪的角度。实际现象是这样的开着joint_state_publisher_gui时你刚拖动滑条轮子会跟着转到某个位置但下一秒又被wheel_spinner的数据拉回去。看起来就像机器人在抽搐。这不是 ROS2 的 bug而是多个发布者同时控制同一个话题的正常表现。解决思路也简单同一时刻只保留一个发布者。想手动调试 URDF 就用 GUI想让轮子自动转就关掉 GUI 跑自己的节点。这也是一个很好的教学点——机器人调试时经常遇到多个来源抢同一个话题的问题学会定位谁在发布、发布了什么比背命令重要得多。6. 创客营翻车现场盘点5个最容易卡住学员的坑6.1 找不到功能包三大原因逐个查上课时最常听到的报错就是这个Package day6_publisher not found我的要求是每遇到这个报错必须按顺序自查三件事第一包到底创建在哪个目录有人会在src/day6_publisher里再创建一次src导致目录嵌套多了一层。用ls ~/ros2_ws/src确认包目录确实在src下一级。第二编译有没有真的完成回到工作空间根目录重新colcon build --packages-select day6_publisher观察输出有没有红色报错。Python 包最常见的编译失败原因是setup.py缩进错误或entry_points没配对。第三当前终端有没有source过环境一个终端如果是在编译之前打开的它可能没有加载新包的路径。要么重新打开终端要么手动执行source ~/ros2_ws/install/setup.bash也可以自检一下包是否被系统识别ros2 pkg list | grep day6如果这步有输出说明包已经安装到环境里剩下的问题是终端壳环境太旧。6.2 RVIZ2里一片空白多半不是URDF的问题模型在 RVIZ2 里不显示时学员的第一反应通常是把 URDF 翻来覆去地改。但根据我的观察90% 的空白都不是 URDF 语法问题而是下面几个原因之一Fixed Frame没设置成base_link。这是最高频的问题。刚打开 RVIZ2 时默认的 Fixed Frame 是map但我们的机器人没有map这个坐标系所以模型自然消失。没添加RobotModel显示项。只改 Fixed Frame 不够还要通过Add把RobotModel加进显示面板。robot_state_publisher没启动。可以直接在终端里查ros2 node list ros2 topic info /robot_description如果/robot_description话题不存在说明robot_state_publisher根本没跑起来或者 launch 文件里传参的方式有问题。排查顺序我建议固定为先看节点再看话题最后才去怀疑 URDF。这个顺序能省下大量时间。6.3 轮子纹丝不动joint名字和消息类型的双重检查wheel_spinner跑起来了日志也正常但 RVIZ2 的轮子就是不动。大多数情况是关节名对不上。URDF 里写的是left_wheel_joint代码里写成了wheel_left_joint这个错我见过不下十次。我的建议是在代码里打印一下你发出的关节名再用ros2 topic echo /joint_states看实际发布的数据。哪怕只看到一条name: [left_wheel_joint, right_wheel_joint] position: [0.02, 0.02]也能立刻发现名字是否匹配。另外还要检查消息类型。ros2 topic info /joint_states必须显示sensor_msgs/msg/JointState。如果你不小心把std_msgs/Float64MultiArray发到/joint_states上robot_state_publisher根本不会理你。最后一个隐形问题是发布频率太低。如果定时回调间隔是 1 秒轮子每秒只转 0.02 弧度肉眼基本看不出来。感觉像是没动实际在动。把频率提高到 50Hz 再看立刻就有区别。6.4 虚拟机卡顿低配环境如何勉强撑住少年创客营里有一半左右的学员是在虚拟机里装 ROS2 的。RVIZ2 对图形性能有一定要求虚拟机里经常出现画面卡顿、模型刷新延迟。如果只是慢不是不能跑可以试试几个应急手段在虚拟机设置里打开3D 加速显存尽量给到 128MB 以上。启动 RVIZ2 前设置软件渲染环境变量有时能避免 OpenGL 驱动不兼容导致的崩溃export LIBGL_ALWAYS_SOFTWARE1关闭 RVIZ2 里不必要的显示项比如 Grid 网格可以留着但把Performance里的渲染质量调低。这个方案只适合撑过课堂演示。如果真的要长期学 ROS2我更推荐装双系统虚拟机里做简单节点开发可以跑可视化还是太勉强。6.5 终端环境变量混乱手工source到底该做几次另一个反复出现的问题是明明编译成功了换个终端又找不到包。这是因为每个新终端都要source ~/ros2_ws/install/setup.bashROS2 的环境变量是跟着终端走的不是全局的。我会在第6天教大家一个一劳永逸的办法把 source 命令写进~/.bashrc末尾echo source ~/ros2_ws/install/setup.bash ~/.bashrc source ~/.bashrc以后每个新终端打开时都会自动加载工作空间环境再也不用担心漏敲source。但这里也有一个反面教训如果你有多个工作空间不要把它们的 source 都塞进 bashrc 还叠加在一起。ROS2 工作空间环境是后者覆盖前者的后 source 的路径会优先容易造成你用到的包被旧版本遮蔽。最稳妥的做法是bashrc 里只放你日常最常用的那个工作空间临时在终端里手动 source 其他工作空间。第6天课程结束时教室里的画面一般是这样的一半学员已经让轮子稳定转起来正在用ros2 topic echo /joint_states盯着自己发的数据看另一半卡在关节名对不上或者没 source 环境上但排查思路已经比第3天清晰得多。有个小学员问我老师为什么轮子转了小车却不往前走 这个问题的答案很关键RVIZ2 只是可视化工具它只负责把关节角度画出来不会模拟小车向前滚动。要让小车真的跑起来需要的是 Gazebo 这类物理仿真环境让车轮旋转、地面摩擦、底盘平移这些物理关系参与计算。到那天我们写的joint_states就不再只是让视觉跟着转而是真的能让机器人动起来。这也是我坚持在第6天把话题、URDF、可视化、关节驱动串成一条线的原因。技术点本身不复杂但对孩子们来说我自己从零画了一个机器人然后写了一小段代码它的轮子就转了这件事带来的冲击力远比任何知识点都深刻。
返回列表