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项目:

ros2 pkg create --build-type ament_python my_python_package

初始化cmake项目:

ros2 pkg create my_cmake_package

在cmake中也可以写Python程序,只需要在CMakeLists.txt中加入

install(
  DIRECTORY directory1 directory2 directory3 ...
  DESTINATION share/${PROJECT_NAME}
)

使用colcon构建项目(安装colconsudo apt install python3-colcon-common-extensions):

cd /ros2_ws
colcon build --symlink-install  # 编译所有包
colcon build --symlink-install --packages-select <package1> ...  # 编译指定包

--symlink-install使用虚拟连接python代码,URDF,yaml配置文件(无需编译的文件),当项目中文件修改后,运行即是最新更新的文件。

Launch文件

按包名称获取绝对路径

from ament_index_python.packages import get_package_share_directory, get_package_prefix
# /ros2_ws/install/<package_name>/share/<package_name>
path_install_pkg = get_package_share_directory('package_name')
# /ros2_ws/install/<package_name>
path_prefix_pkg = get_package_prefix('package_name')

获取URDF

解析xacro文件
import xacro
path_xacro_file = ".../xx.xacro"
robot_xacro = xacro.process_file(path_xacro_file)
robot_description = robot_xacro.toxml()  # 转为URDF的xml格式
读取URDF
with open(path_urdf_file, 'r') as urdf_file:
  robot_description = urdf_file.read()  # 直接读取即可

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,通常称之为Basebase_link,它将作为我们机器人的原点。
urdf joint and link example

两个URDF样例,可以参考:xacro手动编写的简易小车GitHub - demo/robot_main.xacro,sw2urdf转化的包含mesh的详细小车GitHub - cubot_v2/urdf/cubot_v2_sw2urdf.urdf

一个URDF就是xml文件,其中应包含如下的tag信息:

  1. 开头第一行为<?xml version="1.0" ?>

  2. 所有内容需包含在<robot name="your_robot_name"> ... </robot>这个tag中

    URDF中仅需包含两种tag内容(不包含插件等):

  3. <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
  4. <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常用的Joint类型

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文件信息):
URDF robot state publisher struct )

上图来自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
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 对应坐标系
links axises

ROS1的URDF转ROS2的URDF

这里使用fish1sheep的转换代码GitHub - sw2urdf_ROS2,我加入了一些简单的功能GitHub - wty-yy/sw2urdf_ROS2,使用方法(可参考项目的README.md

  1. 使用ros/solidworks_urdf_exporter插件导出为ROS1的URDF项目文件夹,记为urdf_proj
    # 用solidworks_urdf_exporter到处的格式应该如下
    path/to/urdf_proj
    ├── CMakeLists.txt
    ├── config
    ├── export.log
    ├── launch
    ├── meshes
    ├── package.xml
    ├── textures  # 可能没有也没关系
    └── urdf
  2. 修改insert_urdf.py中的base_link为你机器人基准link的名称(第一个设置的link,默认叫base_link,因为需要创建base_footprint作为基准,base_footprint_joint所需的xyz偏移量根据机器人需要设定)
  3. 在ROS2的工作空间下,执行python3 dir_ros2.py进入到urdf_proj文件夹,点击确认,返回如下信息则说明成功:
  4. 将转换完毕的ROS2的URDF项目文件夹urdf_proj复制到ROS2的工作路径下(例如/ros2_ws/src),执行
cp -r path/to/urdf_proj /ros2_ws/src
cd /ros2_ws
colcon build --symlink-install
source ./install/setup.sh

ros2 launch urdf_proj display.launch.py  # 启动RVIZ2显示机器人
# 或者
ros2 launch urdf_proj gazebo.launch.py  # 启动GAZEBO仿真
rviz2 gazebo
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插件自动就会生成),加入如下内容:

from ament_index_python.packages import get_package_prefix

package_name = 'cubot_v2_sw2urdf'
pkg_share = os.pathsep + os.path.join(get_package_prefix(package_name), 'share')
if 'GAZEBO_MODEL_PATH' in os.environ:  # 如果你修改了~/.bashrc, 就会执行这个
    os.environ['GAZEBO_MODEL_PATH'] += pkg_share
else:  # 注意此处gazebo-11修改为你的gazebo版本
    os.environ['GAZEBO_MODEL_PATH'] = "/usr/share/gazebo-11/models" + pkg_share

再执行ros2 launch urdf_proj gazebo.launch.py就OK啦

Gazebo相关

使用Gazebo控制器插件控制仿真模型

将模型直接导入到Gazebo非常简单(不带任何控制方法),参考launch/launch_sim.launch.py,只需三步:

  1. 使用robot_state_publisher先初始化/robot_discruiption/tf节点
  2. 打开一个gazebo空场景ros2 launch gazebo_ros gazebo.launch.py
  3. 将模型导入进去ros2 run gazebo_ros spawn_entity.py -topic robot_description -entity <your_robot_name>

打开rviz2可以看到我们小车的模型,如果没有加上控制器,则无法显示小车轮子的状态,有两种控制器:

  1. ros2_controller:能够同时对真机和gazebo进行控制,配置较为复杂,后文会进行介绍
  2. 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 control插件逻辑 gazebo和rsp逻辑关系

节点关系图gazebo+diff drive+rsp+teleop twist keyboard

Gazebo中摩擦力/颜色配置

<gazebo reference="**_link">
  <!-- link对应的颜色设置, 在URDF中设置的颜色无效 -->
  <material>Gazebo/Blue</material>
  <!-- link对应的摩擦, mu1,mu2一起配置 -->
  <mu1 value="0.001"/>
  <mu2 value="0.001"/>
</gazebo>

teleop 远程控制器

控制器输入

当我们启动了/cmd_vel topic后,需要向其发送对应类型的控制数据,例如Twist就是包含三个线速度与三个角速度的控制数据,而控制小车只需要linear x和angular z即可(Gazebo仿真中启动)就可以通过键盘或者手柄来输入控制指令了。

键盘控制器输入

对于Twist数据可以通过teleop_twist_keyboard来发送数据:

sudo apt install ros-${ROS_DISTRO}-teleop-twist-keyboard
ros2 run teleop_twist_keyboard teleop_twist_keyboard

可以通过如下9个按键来控制小车了(需要先激活终端哦)

Moving around:
   u    i    o
   j    k    l
   m    ,    .

手柄控制器输入

参考代码GitHub - launch/joy.launch.py,配置文件GitHub - config/joystick.yaml

# 安装手柄相关包
sudo apt install "ros-${ROS_DISTRO}-joy*"

手柄数据需要先通过ros2 run joy joy_node启动一个手柄信息读取topic,我们可以通过ros2 topic echo /joy来看获取到的实时手柄信息,并记录下我们想要发送指令的按键编号,然后我们创建一个配置文件,写每个teleop_twist功能和手柄按键旋钮的对应关系:

注意axis填的编号为连续轴的,button填的编号是按钮的,两个编号是分开计数的

# 修改teleop_twist配置参数到手柄对应按键上, 控制小车, 手柄为xbox series
teleop_node:
  ros__parameters:
    # 设置控制前进后退的轴,通常是右摇杆的 Y 轴
    axis_linear.x: 4  # 右摇杆的 Y 轴(前后方向)

    # 设置控制角速度的轴,通常是右摇杆的 X 轴
    axis_angular.yaw: 3  # 右摇杆的 X 轴(控制旋转)

    # 设置启动小车要一直按下的按钮
    enable_button: 4  # 启动的按钮 右上角LB
    enable_turbo_button: 5  # 启动涡轮加速的按钮 左上角RB

    # 设置线性和角速度的缩放比例
    scale_linear: 0.5  # 线速度的比例
    scale_angular: 0.5  # 角速度的比例
    scale_linear_turbo: 1.0  # 涡轮控制下线速度的比例
    scale_angular_turbo: 1.0  # 涡轮控制下角速度的比例

我写了一个launch文件可以同时启动gazebo,rsp,joystick三者GitHub - launch/launch_all_sim_rsp_joy.launch.py,直接启动可以看到下图的节点关系:
Gazebo + RSP + joystick

这样我们就可以用手柄直接控制仿真中的小车啦!效果如下所示,手柄就可以直接后端控制哦

ROS2 controller

相关教程:(差分驱动小车为例)

  1. Gazebo仿真作为硬件接口YouTube - Solving the problem EVERY robot has (with ros2_control)
  2. 真机驱动作为硬件接口YouTube - Using ros2_control to drive our robot (off the edge of the bench…)
  3. 如何自定义硬件接口YouTube - You can use ANY hardware with ros2_control
  4. ROS2官方给出的ros2_control样例,自定义硬件接口可以在此基础上修改GitHub - ros2_control_demos
# 安装控制器相关包
sudo apt install ros-${ROS_DISTRO}-ros2-control ros-${ROS_DISTRO}-ros2-controllers

ROS2控制器原理简单可以用如下图来理解,分为三个部分,从左到右分别为控制指令控制器硬件接口(驱动),而每个需要使用到的代码语言,接口均需要保证正确才能跑通
ros2 control控制小车为例

详细分析每个部分如下图所示,当我们完成驱动器(driver)和控制器(controller)后,我们只需要完成两个配置文件(Yaml, URDF)的修改,即可启动对应的驱动和控制器,从下图看出,硬件接口(Hardware Interface)中只需仿真(Simulator)和真机(Robot)二选一。下面我们分别来介绍如何使用Gazebo和真机作为硬件接口。
ros2 controller struct

GazeboSystem模拟硬件接口

sudo apt install ros-${ROS_DISTRO}-gazebo-ros2-control

完整代码:GitHub - v2.1-cubot-gazebo-ros2-control

使用方法:执行launch_sim.launch.py或者launch_all_sim_rsp_joy.launch.py,启动如下键盘控制

ros2 run teleop_twist_keyboard teleop_twist_keyboard --ros-args -r /cmd_vel:=/diff_cont/cmd_vel_unstamped

即可使用键盘输出指令给DiffDriveController来控制小车了(和v2-cubot-sim-demo中所使用的gazebo_ros_diff_drive区别,在于按一次只会走一点距离然后停下,如果将小车转一圈回到原点,仿真和rviz2中看到的可能存在误差)


启动ros2 control仅需修改四个位置:

  1. 添加Yaml文件config/my_controllers.yaml,此文件将配置如下内容:
    • controller_manager中将要在launch中启动的controllers名称,例如这里启动了DiffDriveController名称为diff_contJointStateBroadcaster名称为joint_broad
    • 对controllers的配置信息,例如这里配置了diff_cont,定义了驱动关节,轮子间距、半径、控制频率等信息
  2. 修改URDF文件description/ros2_control.xacro
    • 使用gazebo_ros2_control/GazeboSystem作为仿真驱动,并配置一些仿真参数,例如最大速度等(要做速度控制)
    • 由于GazeboSystem会帮我们启动controller_manager节点,因此还需要将config/my_controllers.yaml配置文件都放到26行插件初始化位置
  3. 修改description/robot.xacro:替换掉原来的gazebo_control.xacro
  4. 修改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修改如下四个位置:

  1. 修改URDF文件description/ros2_control.xacro:使用我们自定义的diffdrive_arduino/DiffDriveArduinoHardware作为仿真驱动(驱动名称在驱动项目的diffdrive_arduino.xml文件中进行了定义),并配置一些仿真参数,例如joint名称、比特率、编码器与点击转速之比等
  2. 修改description/robot.xacro:替换掉原来的gazebo_control.xacro
  3. 添加Yaml文件config/my_controllers.yaml,此文件将配置如下内容:
    • controller_manager中将要在launch中启动的controllers名称,例如这里启动了DiffDriveController名称为diff_contJointStateBroadcaster名称为joint_broad
    • 对controllers的配置信息,例如这里配置了diff_cont,定义了驱动关节,轮子间距、半径、控制频率、最大转速度等信息
  4. 添加launch/launch_robot.launch.py启动文件,这个位置需要注意的位置最多:
    1. robot_state_publisher中的use_sim_time需要置为false22行
    2. 由于我们没有GazeboSystem帮我们启动controller_manager,因此需要我们手动启动节点,并将URDF和配置文件导入,这里非常重要!如果后续启动出问题,一定要检查此处31行,由于humble版本的controller_manager会默认从~/robot_description节点下找URDF文件,所以我们需要重映射一下节点
    3. 添加两个controller节点启动命令需要跟随controller_manager启动,所以59行用到了OnProcessStart函数:ros2 run controller_manager spawner [diff_cont|joint_broad],这两个controller名字正好和第三步的my_controllers.yaml中设置的名称一致

成功启动后,我们将看到下图日志信息:
ros2 controller成功启动

控制器启动指令:

ros2 launch cubot joy.launch.py  # 手柄控制器启动指令
ros2 run teleop_twist_keyboard teleop_twist_keyboard --ros-args -r  /cmd_vel:=/diff_cont/cmd_vel_unstamped  # 键盘控制器启动指令

控制测试

  1. 前进距离1m是否和rviz2显示一格一致,键盘控制i前进,,后退
  2. 能否原地转圈,j逆时针,l顺时针
  3. 使用手柄测试下能否连续控制小车

如果前两个测试不准,可以调整pid参数arduino_pid_controlled_motor/pid.h,调整思路(Deepseek给出):

  1. 先调整Kp:将Ki和Kd设为0,只调整Kp,直到系统能够快速响应但不过度振荡
  2. 加入Kd:在Kp调整好后,加入Kd,抑制振荡并加快系统稳定
  3. 最后调整Ki:加入Ki,消除稳态误差,但注意不要使Ki过大

我最后调的结果为:kp=15, ki=0, kd=0.1


ROS2笔记
https://wty-yy.github.io/posts/30945/
作者
wty
发布于
2025年1月12日
许可协议