ROS2笔记
本文大部分topic逻辑图片来自于YouTube - Articulated Robotics的视频,ROS2学习和制作小车可以参考他的视频,非常详细!
DEBUG工具
rqt
是一个很好用的ROS2调试工具,能够显示各个node和topic之间的关系图,直接运行rqt
,在上方Plugins -> Introspection -> Node Graphe
即可打开节点关系图插件
初始化项目
构建机器人的初始化环境模板可以参考GitHub - joshnewans/my_bot, 基于该模板构建的带有差速控制的机器人和gazebo仿真环境的例子GitHub - joshnewans/articubot_one
初始化Python项目:
初始化cmake项目:
在cmake中也可以写Python程序,只需要在CMakeLists.txt
中加入
使用colcon构建项目(安装colconsudo apt install python3-colcon-common-extensions
):
--symlink-install
使用虚拟连接python代码,URDF,yaml配置文件(无需编译的文件),当项目中文件修改后,运行即是最新更新的文件。
Launch文件
按包名称获取绝对路径
获取URDF
解析xacro文件
读取URDF
URDF相关
URDF文件格式简述
YouTube - How do we describe a robot? With URDF! | Getting Ready to build Robots with ROS #7 讲解的非常好,URDF本质上就是描述的两两连接处(link)之间的变换(joint),如下图所示。
这个机器包含4个links:Base, Slider, Arm, Camera
,两两link之间存在相对位置关系,我们用欧拉角(三维旋转平移变换)来表述从父类到子类的变换,分别为:Slider Joint
(从Base到Slider,后面同理),Arm Joint, Cam Joint
,不难发现整个机器人仅存在一个link没有joint,通常称之为Base
或base_link
,它将作为我们机器人的原点。
两个URDF样例,可以参考:xacro手动编写的简易小车GitHub - demo/robot_main.xacro,sw2urdf转化的包含mesh的详细小车GitHub - cubot_v2/urdf/cubot_v2_sw2urdf.urdf
一个URDF就是xml文件,其中应包含如下的tag信息:
-
开头第一行为
<?xml version="1.0" ?>
-
所有内容需包含在
<robot name="your_robot_name"> ... </robot>
这个tag中URDF中仅需包含两种tag内容(不包含插件等):
-
<link name="**_link">
: 物体的实体定义,需要包含以下三个tag:<visual>
: 视觉渲染,包含以下几种tag:<geometry>
:几何体(包含cylinder, sphere, box
三种常用tag形状)<origin xyz="* * *" rpy="* * *">
:<geometry>
的中心原点,相对于joint
给出的坐标系<material>
:颜色
<collision>
:碰撞箱<geometry>
:几何体(可以和<visual><geometry>
保持一致,也可以进行简化)<origin xyz="* * *" rpy="* * *">
:<geometry>
的中心原点,相对于joint
给出的坐标系
<inertial>
:惯性矩阵<mass>
:质量大小(单位kg)<origin xyz="* * *" rpy="* * *">
:质心位置,相对于joint
给出的坐标系<inertial>
:3x3惯性矩阵定义,在wikipedia - List of moments of inertia中可以找到简单几何体的惯性矩阵(如果是复杂的,可以通过现在solidworks中建模,直接导出为URDF文件,参考上文SolidWorks模型转换为URDF)
-
<joint name="**_joint" type="...">
:两个link对应坐标系之间的欧拉角变换,以及变换的type
,其中type
包含常用的四种:revolute, prismatic, continuous, fixed
如下图所示,可以包含以下5中tag:<parent link="**_link">
:父级link<child link="**_link">
:子级link<origin xyz="* * *" rpy="* * *">
:子级link相对于父级link的坐标系变换<axis xyz="1 0 0">
:定义type的变换轴,例如revolute
将axis作为旋转轴(对于fixed
可以没有)<limit lower=* upper=* velocity=* effort=*>
:对关节变换进行限制(可以没有)
URDF模型显示
这里以GitHub - wty-yy/ros-car-cubot tag:v2-cubot-sim-demo为例,launch/rsp.launch.py
文件能够将xacro转为URDF配置,并通过robot state publisher将URDF发布到/tf
和/robot_description
两个topic上,并且会创建/robot_state_publisher robot_description
全局变量(该变量存储的就是URDF文件信息):
上图来自YouTube - Creating a rough 3D model of our robot with URDF 208s
如果我们此时打开rviz2
查看小车的模型会发现两个轮子无法正常显示,这是因为两者存在一个可变的TF(transform),也就是joint为continue
关系,TF在一个可选的范围内变换,模型并不知道当前变换的具体值,因此无法显示,这就需要一个joint_state_publisher
来产生一个虚拟的/joint_states
节点,告诉robot_state_publisher
当前TF具体是多少(如果是真机上使用,则需要用真机的/joint_states
了),执行launch/display.launch.py
即可看到完成的小车了。完整显示小车的节点关系图如上图所示,效果对比图如下所示。
no joint state | with joint state |
---|---|
SolidWorks模型转换为URDF
SolidWorks转ROS1的URDF文件
先安装sw插件ros/solidworks_urdf_exporter,用插件导出为ROS1的urdf,详细请参考CSDN - solidworks模型导出urdf(超详细)配合视频观看
此处需要注意的就是尽可能简化link的数量,并且对每个link都要加上各自的坐标系(通过先加入参考点,再定义xyz三个方向),在URDF导出时候每个links选择对应的坐标系即可
如果有的选了坐标系有的没选,就会报错哦
links | 对应坐标系 |
---|---|
ROS1的URDF转ROS2的URDF
这里使用fish1sheep的转换代码GitHub - sw2urdf_ROS2,我加入了一些简单的功能GitHub - wty-yy/sw2urdf_ROS2,使用方法(可参考项目的README.md
)
- 使用ros/solidworks_urdf_exporter插件导出为ROS1的URDF项目文件夹,记为
urdf_proj
- 修改
insert_urdf.py
中的base_link
为你机器人基准link的名称(第一个设置的link,默认叫base_link
,因为需要创建base_footprint作为基准,base_footprint_joint
所需的xyz
偏移量根据机器人需要设定) - 在ROS2的工作空间下,执行
python3 dir_ros2.py
,进入到urdf_proj
文件夹,点击确认,返回如下信息则说明成功:成功导出输出的信息 - 将转换完毕的ROS2的URDF项目文件夹
urdf_proj
复制到ROS2的工作路径下(例如/ros2_ws/src
),执行
rviz2 | gazebo |
---|---|
URDF无法在Gazebo中打开
参考Robotics Stack Exchange - Where does Gazebo set the GAZEBO_MODEL_PATH environment variable?, GitHub panda_simulator issues - How do I add an .stl to the gazebo simulation?
执行上述ros2 launch urdf_proj gazebo.launch.py
可能遇到Gazebo一直卡在启动界面的问题,这是因为STL
文件无法找到,也就是package://.../*.STL
中的package
不在环境变量GAZEBO_MODEL_PATH
中。
由于echo $GAZEBO_MODEL_PATH
是空的(我的Gazebo11就没有这个环境变量),如果直接赋路径会导致不包含默认路径,直接无法打开gazebo,所以先要将默认的路径添加进去(可以在~/.bashrc
中加入export GAZEBO_MODEL_PATH=/usr/share/gazebo-11/models:${HOME}/.gazebo/models
,gazebo-11
填写你的gazebo版本),再在launch文件中加入我们所需的路径
修改launch.py
文件方法如下,打开launch/gazebo.launch.py
文件(用sw转urdf插件自动就会生成),加入如下内容:
再执行ros2 launch urdf_proj gazebo.launch.py
就OK啦
Gazebo相关
使用Gazebo控制器插件控制仿真模型
将模型直接导入到Gazebo非常简单(不带任何控制方法),参考launch/launch_sim.launch.py,只需三步:
- 使用
robot_state_publisher
先初始化/robot_discruiption
和/tf
节点 - 打开一个gazebo空场景
ros2 launch gazebo_ros gazebo.launch.py
- 将模型导入进去
ros2 run gazebo_ros spawn_entity.py -topic robot_description -entity <your_robot_name>
打开rviz2
可以看到我们小车的模型,如果没有加上控制器,则无法显示小车轮子的状态,有两种控制器:
ros2_controller
:能够同时对真机和gazebo进行控制,配置较为复杂,后文会进行介绍gazebo_controller
:通过gazebo插件,能够直接对gazebo中的模型进行控制,这里先用这种方法
在urdf文件中加入gazebo controller插件,插入到URDF中全文可参考cubot_v2_sw2urdf.urdf #215,插件的使用说明参考gazebo_plugins.doc - GazeboRosDiffDrive,就可以通过gazebo生成虚拟的/tf
来显示轮子joint的状态(不再通过/joint_states
)。
注意:使用gazebo control时,由于小车的原点会发生变化(这样小车才能运动起来),需要重新设置一个世界原点坐标系,在gazebo中称为odometry_frame(里程计),我们将其对应的节点记为odom
,那么我们原来的小车原点base_link
或者base_footprint
就要相对odom
进行变化了,这些需要在插件中进行配置。
Gazebo Controller插件逻辑 | Gazebo和rsp逻辑关系 |
---|---|
Gazebo中摩擦力/颜色配置
teleop 远程控制器
控制器输入
当我们启动了/cmd_vel
topic后,需要向其发送对应类型的控制数据,例如Twist
就是包含三个线速度与三个角速度的控制数据,而控制小车只需要linear x和angular z即可(Gazebo仿真中启动)就可以通过键盘或者手柄来输入控制指令了。
键盘控制器输入
对于Twist数据可以通过teleop_twist_keyboard
来发送数据:
可以通过如下9个按键来控制小车了(需要先激活终端哦)
手柄控制器输入
参考代码GitHub - launch/joy.launch.py,配置文件GitHub - config/joystick.yaml
手柄数据需要先通过ros2 run joy joy_node
启动一个手柄信息读取topic,我们可以通过ros2 topic echo /joy
来看获取到的实时手柄信息,并记录下我们想要发送指令的按键编号,然后我们创建一个配置文件,写每个teleop_twist
功能和手柄按键旋钮的对应关系:
注意axis填的编号为连续轴的,button填的编号是按钮的,两个编号是分开计数的
我写了一个launch文件可以同时启动gazebo,rsp,joystick三者GitHub - launch/launch_all_sim_rsp_joy.launch.py,直接启动可以看到下图的节点关系:
这样我们就可以用手柄直接控制仿真中的小车啦!效果如下所示,手柄就可以直接后端控制哦
ROS2 controller
相关教程:(差分驱动小车为例)
- Gazebo仿真作为硬件接口YouTube - Solving the problem EVERY robot has (with ros2_control)
- 真机驱动作为硬件接口YouTube - Using ros2_control to drive our robot (off the edge of the bench…)
- 如何自定义硬件接口YouTube - You can use ANY hardware with ros2_control
- ROS2官方给出的ros2_control样例,自定义硬件接口可以在此基础上修改GitHub - ros2_control_demos
ROS2控制器原理简单可以用如下图来理解,分为三个部分,从左到右分别为控制指令,控制器,硬件接口(驱动),而每个需要使用到的代码语言,接口均需要保证正确才能跑通
详细分析每个部分如下图所示,当我们完成驱动器(driver)和控制器(controller)后,我们只需要完成两个配置文件(Yaml, URDF)的修改,即可启动对应的驱动和控制器,从下图看出,硬件接口(Hardware Interface)中只需仿真(Simulator)和真机(Robot)二选一。下面我们分别来介绍如何使用Gazebo和真机作为硬件接口。
GazeboSystem模拟硬件接口
完整代码:GitHub - v2.1-cubot-gazebo-ros2-control
使用方法:执行launch_sim.launch.py
或者launch_all_sim_rsp_joy.launch.py
,启动如下键盘控制
即可使用键盘输出指令给DiffDriveController来控制小车了(和v2-cubot-sim-demo中所使用的gazebo_ros_diff_drive
区别,在于按一次只会走一点距离然后停下,如果将小车转一圈回到原点,仿真和rviz2中看到的可能存在误差)
启动ros2 control仅需修改四个位置:
- 添加Yaml文件
config/my_controllers.yaml
,此文件将配置如下内容:controller_manager
中将要在launch中启动的controllers名称,例如这里启动了DiffDriveController
名称为diff_cont
和JointStateBroadcaster
名称为joint_broad
- 对controllers的配置信息,例如这里配置了
diff_cont
,定义了驱动关节,轮子间距、半径、控制频率等信息
- 修改URDF文件
description/ros2_control.xacro
:- 使用
gazebo_ros2_control/GazeboSystem
作为仿真驱动,并配置一些仿真参数,例如最大速度等(要做速度控制) - 由于
GazeboSystem
会帮我们启动controller_manager
节点,因此还需要将config/my_controllers.yaml
配置文件都放到26行插件初始化位置
- 使用
- 修改
description/robot.xacro
:替换掉原来的gazebo_control.xacro
- 修改
launch/launch_sim.launch.py
启动文件,添加两个controller节点启动命令:ros2 run controller_manager spawner [diff_cont|joint_broad]
,这两个controller名字正好和第三步的my_controllers.yaml
中设置的名称一致
仿真中的
use_sim_time
需要都给成true
,在launch/launch_sim.launch.py中设置
注意:我们无需启动controller_manager
,因为在执行ros2 run gazebo_ros spawn_entity.py -topic robot_description -entity my_cubot
时候,Gazebo读取URDF配置,启动了GazeboSystem仿真物理端口,顺便就把controller_manager
启动了,因此无需多次启动。
小心:在写config/my_controllers.yaml
文件时,千万不要将controller_manager
配置的update_rate
加上小数点,否则启动不起来,也不报错😑
由于Gazebo已经帮我们写好的仿真硬件接口了,所以看起来非常简单吧!
真机硬件接口
完整代码:GitHub - v2.2-cubot-real-ros2-control
如果我们使用真机作为硬件接口,就需要我们自己手动写仿真接口了,参考这个视频YouTube - You can use ANY hardware with ros2_control,Joshnewans是在GitHub - ros2_control_demos的基础上加入serial(串口)通讯实现和Arduino的硬件接口。
由于我重写了Arduino的pid代码GitHub - wty-yy/arduino_pid_controlled_motor,因此我也要稍微修改下GiHub - wty-yy/diffdrive_arduino,完成自定义驱动后,我们类似GazeboSystem修改如下四个位置:
- 修改URDF文件
description/ros2_control.xacro
:使用我们自定义的diffdrive_arduino/DiffDriveArduinoHardware
作为仿真驱动(驱动名称在驱动项目的diffdrive_arduino.xml文件中进行了定义),并配置一些仿真参数,例如joint名称、比特率、编码器与点击转速之比等 - 修改
description/robot.xacro
:替换掉原来的gazebo_control.xacro
- 添加Yaml文件
config/my_controllers.yaml
,此文件将配置如下内容:controller_manager
中将要在launch中启动的controllers名称,例如这里启动了DiffDriveController
名称为diff_cont
和JointStateBroadcaster
名称为joint_broad
- 对controllers的配置信息,例如这里配置了
diff_cont
,定义了驱动关节,轮子间距、半径、控制频率、最大转速度等信息
- 添加
launch/launch_robot.launch.py
启动文件,这个位置需要注意的位置最多:robot_state_publisher
中的use_sim_time
需要置为false
,22行- 由于我们没有GazeboSystem帮我们启动
controller_manager
,因此需要我们手动启动节点,并将URDF和配置文件导入,这里非常重要!如果后续启动出问题,一定要检查此处31行,由于humble版本的controller_manager
会默认从~/robot_description
节点下找URDF文件,所以我们需要重映射一下节点 - 添加两个controller节点启动命令需要跟随
controller_manager
启动,所以59行用到了OnProcessStart
函数:ros2 run controller_manager spawner [diff_cont|joint_broad]
,这两个controller名字正好和第三步的my_controllers.yaml
中设置的名称一致
成功启动后,我们将看到下图日志信息:
控制器启动指令:
控制测试
- 前进距离1m是否和rviz2显示一格一致,键盘控制
i
前进,,
后退 - 能否原地转圈,
j
逆时针,l
顺时针 - 使用手柄测试下能否连续控制小车
如果前两个测试不准,可以调整pid参数arduino_pid_controlled_motor/pid.h,调整思路(Deepseek给出):
- 先调整Kp:将Ki和Kd设为0,只调整Kp,直到系统能够快速响应但不过度振荡
- 加入Kd:在Kp调整好后,加入Kd,抑制振荡并加快系统稳定
- 最后调整Ki:加入Ki,消除稳态误差,但注意不要使Ki过大
我最后调的结果为:kp=15, ki=0, kd=0.1