機器人的建模和仿真一般用于實機部署之前進行算法的虛擬測試
機器人建模的核心文件:URDF
URDF 基本組成
-
<link>:剛體部分(如底盤、機械臂連桿)
-
<joint>:連接關系(如旋轉、滑動)
-
<inertial>:慣性參數(質量、質心、轉動慣量)
-
<visual>:視覺外觀(mesh 文件,如 .dae、.stl)
-
<collision>:碰撞檢測模型
-
<transmission>:定義執行器與 joint 的關系
創建一個urdf的功能包無需其余依賴,使用ament_cmake方式進行構建生成可執行文件,在下面新建一個urdf文件夾用于存放urdf文件
<?xml version="1.0"?>
<!-- 聲明 XML 文件頭,URDF 是一種 XML 格式的描述語言 -->
<robot name="my_robot">
<!-- 定義一個機器人模型,名字為 "my_robot" -->
<link name="base_link">
<!-- link 是機器人中的剛體(不發生形變的物理部分) -->
<visual>
<!-- visual 定義用于可視化(顯示)的幾何信息 -->
<origin xyz="0.0 0.0 0.0" rpy="0.0 0.0 0.0"/>
<!-- origin 表示幾何形狀相對于 link 坐標系的位姿(位置+姿態) -->
<!-- xyz 表示平移(x, y, z),rpy 表示旋轉(roll, pitch, yaw) -->
<geometry>
<!-- geometry 定義該 link 的形狀 -->
<cylinder radius="0.10" length="0.12"/>
<!-- 定義一個圓柱體,半徑為 0.10m,高度為 0.12m -->
</geometry>
</visual>
<material>
<!-- material 定義外觀顏色或紋理 -->
<color rgba="1.0 1.0 1.0 0.5"/>
<!-- rgba: 紅、綠、藍、透明度(0=透明, 1=不透明) -->
</material>
</link>
<link name="imu_link">
<visual>
<origin xyz="0.0 0.0 0.0" rpy="0.0 0.0 0.0"/>
<!-- IMU 的外觀幾何原點在自身坐標系的原點 -->
<geometry>
<box size="0.02 0.02 0.02"/>
<!-- 定義一個 2cm 立方體,用于表示 IMU 模塊 -->
</geometry>
</visual>
<material>
<color rgba="0.0 0.0 0.0 0.5"/>
<!-- 顏色為半透明黑色 -->
</material>
</link>
<joint name="imu_joint" type="fixed">
<!-- joint 定義兩個 link 之間的連接關系 -->
<!-- name 為關節名稱,type 為關節類型 -->
<!-- type="fixed" 表示兩個 link 之間無相對運動 -->
<parent link="base_link"/>
<!-- 父連桿(主結構體) -->
<child link="imu_link"/>
<!-- 子連桿(被附著的模塊) -->
<origin xyz="0.03 0.0 0.0" rpy="0.0 0.0 0.0"/>
<!-- 表示 imu_link 坐標系相對于 base_link 坐標系的位姿 -->
</joint>
</robot>
urdf_to_graphviz:一個 ROS 工具,用于將 URDF 模型的結構(link-joint 關系)可視化為拓撲圖,作用為讀取 .urdf 文件,分析 link 與 joint 的連接關系,使用 Graphviz 生成 .gv(graphviz 源文件)和 .pdf(圖形文件)。
urdf_to_graphviz robot.urdf將會生成兩個文件,這兩個文件的名字和xml里面name屬性的賦值一致
![image]()
![image]()
使用rviz2打開可視化工具,讀取urdf文件,可視化展示,我們選擇從文件中獲取同時設置Fixed Frame(固定坐標系)為base_link
安裝兩個工具包,sudo apt install ros-$ROS_DISTRO-robot-state-publisher和sudo apt install ros-$ROS_DISTRO-joint-state-publisher
| 包名 |
節點名 |
功能作用 |
輸入 |
輸出 |
robot_state_publisher |
/robot_state_publisher |
根據 URDF 模型和 joint 狀態生成 TF 樹(坐標變換關系) |
/joint_states(joint 角度)
/robot_description(URDF) |
/tf、/tf_static |
joint_state_publisher |
/joint_state_publisher |
發布機器人各關節的角度狀態,用于驅動 URDF 中的關節 |
——(手動或 GUI 輸入) |
/joint_states |
這兩個包涉及的節點和話題入下圖所示
![image]()
相關節點
| 節點名稱 |
角色 |
主要功能 |
| /robot_state_publisher |
機器人狀態發布節點(核心) |
根據 URDF 模型(robot_description)和關節角度(joint_states)計算各個 link 的坐標變換(TF),并發布到 /tf 和 /tf_static |
| /joint_state_publisher |
關節狀態發布節點 |
向話題 /joint_states 發布各關節的角度、速度、位移等信息(一般由 GUI 或真實傳感器產生) |
| /transform_listener_impl_... |
TF 監聽節點(例如 RViz 或 TF 工具) |
訂閱 /tf 與 /tf_static,以便獲取機器人各個部分的空間位置關系(用于 3D 顯示或坐標變換) |
| /robot_description |
參數(非真正節點) |
存儲機器人的 URDF 模型(XML 字符串),供 robot_state_publisher 讀取 |
相關話題
| 話題名稱 |
發布者 |
訂閱者 |
作用 |
/robot_description |
launch 或 parameter server |
/robot_state_publisher |
提供 URDF 模型內容(機器人結構) |
/joint_states |
/joint_state_publisher |
/robot_state_publisher |
提供關節角度、速度、位移信息 |
/tf |
/robot_state_publisher |
/transform_listener_impl_xxxxx |
動態 TF(隨時間變化的坐標關系) |
/tf_static |
/robot_state_publisher |
/transform_listener_impl_xxxxx |
靜態 TF(固定坐標變換,如 base_link→imu_link) |
創建launch文件啟動這些節點
from launch import LaunchDescription # ROS2 啟動文件的核心類,用于定義要啟動的所有節點/動作
from launch.actions import DeclareLaunchArgument # 聲明可在命令行傳入的啟動參數
from launch.substitutions import Command, LaunchConfiguration # Launch 變量替換機制(動態獲取參數或命令結果)
from launch_ros.actions import Node # ROS2 節點啟動動作
from launch_ros.descriptions import ParameterValue # ROS 參數值類型
from ament_index_python.packages import get_package_share_directory # 獲取包路徑的工具函數
import os
def generate_launch_description():
# 獲取 URDF 模型路徑
urdf_package_path = get_package_share_directory('robot_description')
default_urdf_path = os.path.join(urdf_package_path,'urdf','robot.urdf')
# 聲明 Launch 參數
# 區分launch參數和節點參數,launch 參數管 “啟動怎么配”(啟動階段、全局控制),節點參數管 “運行怎么算”(運行階段、節點內部配置)
action_declare_arg_mode_path=DeclareLaunchArgument('model',default_value=str(default_urdf_path))
# 讀取 URDF 文件內容,cat有空格
command_result = Command(['cat ',LaunchConfiguration('model')])
# 告訴 ROS 這是一個字符串類型的參數值(即 URDF 模型文本)
robot_description_value = ParameterValue(command_result,value_type=str)
'''
啟動節點:
├─ joint_state_publisher → 發布 JointState
├─ robot_state_publisher → 發布 TF 樹
└─ rviz2 → 顯示模型
'''
robot_state_publisher = Node(
package='robot_state_publisher',
executable='robot_state_publisher',
parameters=[{'robot_description':robot_description_value}],
)
robot_joint_publisher = Node(
package='joint_state_publisher',
executable='joint_state_publisher',
)
robot_rviz_node = Node(
package='rviz2',
executable='rviz2',
)
return LaunchDescription(
[action_declare_arg_mode_path,
robot_state_publisher,
robot_joint_publisher,
robot_rviz_node,]
)
![image]()
在robot_description下新建目錄config,將配置保存到該目錄下得到.rviz文件然后修改代碼和cmakelist.txt
# 復制config目錄到install
install(DIRECTORY launch urdf config
DESTINATION share/${PROJECT_NAME}
)
from launch import LaunchDescription # ROS2 啟動文件的核心類,用于定義要啟動的所有節點/動作
from launch.actions import DeclareLaunchArgument # 聲明可在命令行傳入的啟動參數
from launch.substitutions import Command, LaunchConfiguration # Launch 變量替換機制(動態獲取參數或命令結果)
from launch_ros.actions import Node # ROS2 節點啟動動作
from launch_ros.descriptions import ParameterValue # ROS 參數值類型
from ament_index_python.packages import get_package_share_directory # 獲取包路徑的工具函數
import os
def generate_launch_description():
# 獲取 URDF 模型路徑
urdf_package_path = get_package_share_directory('robot_description')
default_urdf_path = os.path.join(urdf_package_path,'urdf','robot.urdf')
default_rviz_path = os.path.join(urdf_package_path,'config','display_robot.rviz')
# 聲明 Launch 參數
action_declare_arg_mode_path=DeclareLaunchArgument('model',default_value=str(default_urdf_path))
# 讀取 URDF 文件內容,cat有空格
command_result = Command(['cat ',LaunchConfiguration('model')])
# 告訴 ROS 這是一個字符串類型的參數值(即 URDF 模型文本
robot_description_value = ParameterValue(command_result,value_type=str)
'''
啟動節點:
├─ joint_state_publisher → 發布 JointState
├─ robot_state_publisher → 發布 TF 樹
└─ rviz2 → 顯示模型
'''
robot_state_publisher = Node(
package='robot_state_publisher',
executable='robot_state_publisher',
parameters=[{'robot_description':robot_description_value}],
)
robot_joint_publisher = Node(
package='joint_state_publisher',
executable='joint_state_publisher',
)
robot_rviz_node = Node(
package='rviz2',
executable='rviz2',
arguments=['-d',default_rviz_path],
)
return LaunchDescription(
[action_declare_arg_mode_path,
robot_state_publisher,
robot_joint_publisher,
robot_rviz_node,]
)
Xacro
Xacro是 ROS 中定義機器人模型最常用的增強版 URDF 語言,它是 URDF 的宏擴展語言,讓 URDF 文件可以使用變量、宏、條件、循環等高級功能。
| 項目 |
URDF |
Xacro |
| 文件后綴 |
.urdf |
.xacro |
| 是否支持變量 |
? 不支持 |
? 支持 |
| 是否支持復用結構(宏) |
? 不支持 |
? 支持 |
| 是否支持條件語句 |
? 不支持 |
? 支持 |
| 是否支持數學計算 |
? 不支持 |
? 支持 |
| 解析方式 |
直接加載 |
必須先轉成 URDF 再加載 |
引入xacro的命名空間
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="my_robot">
常見屬性
| 標簽 |
功能 |
示例 |
<xacro:property> |
定義變量 |
<xacro:property name="r" value="0.1"/> |
<xacro:macro> |
定義宏 |
<xacro:macro name="wheel" params="name r">...</xacro:macro> |
<xacro:include> |
引入文件 |
<xacro:include filename="wheel.xacro"/> |
<xacro:arg> |
定義參數 |
<xacro:arg name="use_lidar" default="true"/> |
<xacro:if> / <xacro:unless> |
條件判斷 |
<xacro:if value="$(arg use_lidar)">...</xacro:if> |
<xacro:insert_block> |
插入塊 |
<xacro:insert_block name="link_content"/> |
${} |
表達式或變量引用 |
${r/2} |
<?xml version="1.0"?>
<robot xmlns:xacro="http://ros.org/wiki/xacro" name="my_robot">
<!-- ========= 全局顏色屬性定義 ========= -->
<xacro:property name="base_color_rgba" value="0.2 0.2 0.2 1.0"/> <!-- 深灰底座 -->
<xacro:property name="imu1_color_rgba" value="0.1 0.6 1.0 1.0"/> <!-- 明亮藍色 IMU1 -->
<xacro:property name="imu2_color_rgba" value="1.0 0.5 0.0 1.0"/> <!-- 明亮橙色 IMU2 -->
<xacro:property name="imu3_color_rgba" value="0.2 1.0 0.2 1.0"/> <!-- 明亮綠色 IMU3 -->
<xacro:property name="imu4_color_rgba" value="1.0 0.2 0.6 1.0"/> <!-- 粉紅 IMU4 -->
<xacro:property name="imu5_color_rgba" value="1.0 1.0 0.1 1.0"/> <!-- 明亮黃色 IMU5 -->
<!-- ========= 定義底座 link 宏 ========= -->
<xacro:macro name="base_link" params="base_name xyz rpy radius length rgba">
<link name="${base_name}">
<!-- 可視部分 -->
<visual>
<origin xyz="${xyz}" rpy="${rpy}"/>
<geometry>
<cylinder radius="${radius}" length="${length}"/>
</geometry>
<material name="base_color">
<color rgba="${rgba}"/>
</material>
</visual>
</link>
</xacro:macro>
<!-- ========= 定義 IMU link 宏 ========= -->
<xacro:macro name="imu_link" params="imu_name xyz rpy size rgba joint_name joint_type parent_name child_name joint_xyz joint_rpy">
<link name="${imu_name}">
<visual>
<origin xyz="${xyz}" rpy="${rpy}"/>
<geometry>
<box size="${size}"/>
</geometry>
<material name="${imu_name}_color">
<color rgba="${rgba}"/>
</material>
</visual>
</link>
<!-- joint: 連接 IMU 與底座 -->
<joint name="${joint_name}" type="${joint_type}">
<parent link="${parent_name}"/>
<child link="${child_name}"/>
<origin xyz="${joint_xyz}" rpy="${joint_rpy}"/>
</joint>
</xacro:macro>
<!-- ========= 生成實際模型 ========= -->
<!-- 底座 -->
<xacro:base_link
base_name="base_link"
xyz="0 0 0"
rpy="0 0 0"
radius="0.1"
length="0.12"
rgba="${base_color_rgba}"/>
<!-- IMU 1(藍色) 正上方中心 -->
<xacro:imu_link
imu_name="imu_link_1"
xyz="0 0 0.15"
rpy="0 0 0"
size="0.02 0.02 0.02"
rgba="${imu1_color_rgba}"
joint_name="base_to_imu1"
joint_type="fixed"
parent_name="base_link"
child_name="imu_link_1"
joint_xyz="0 0 0.15"
joint_rpy="0 0 0"/>
<!-- IMU 2(橙色) 前方 -->
<xacro:imu_link
imu_name="imu_link_2"
xyz="0.1 0 0.12"
rpy="0 0 0"
size="0.02 0.02 0.02"
rgba="${imu2_color_rgba}"
joint_name="base_to_imu2"
joint_type="fixed"
parent_name="base_link"
child_name="imu_link_2"
joint_xyz="0.1 0 0.12"
joint_rpy="0 0 0"/>
<!-- IMU 3(綠色) 后方 -->
<xacro:imu_link
imu_name="imu_link_3"
xyz="-0.1 0 0.12"
rpy="0 0 0"
size="0.02 0.02 0.02"
rgba="${imu3_color_rgba}"
joint_name="base_to_imu3"
joint_type="fixed"
parent_name="base_link"
child_name="imu_link_3"
joint_xyz="-0.1 0 0.12"
joint_rpy="0 0 0"/>
<!-- IMU 4(粉紅) 左方 -->
<xacro:imu_link
imu_name="imu_link_4"
xyz="0 0.1 0.12"
rpy="0 0 0"
size="0.02 0.02 0.02"
rgba="${imu4_color_rgba}"
joint_name="base_to_imu4"
joint_type="fixed"
parent_name="base_link"
child_name="imu_link_4"
joint_xyz="0 0.1 0.12"
joint_rpy="0 0 0"/>
<!-- IMU 5(黃色) 右方 -->
<xacro:imu_link
imu_name="imu_link_5"
xyz="0 -0.1 0.12"
rpy="0 0 0"
size="0.02 0.02 0.02"
rgba="${imu5_color_rgba}"
joint_name="base_to_imu5"
joint_type="fixed"
parent_name="base_link"
child_name="imu_link_5"
joint_xyz="0 -0.1 0.12"
joint_rpy="0 0 0"/>
</robot>
![image]()
from launch import LaunchDescription # ROS2 啟動文件的核心類,用于定義要啟動的所有節點/動作
from launch.actions import DeclareLaunchArgument # 聲明可在命令行傳入的啟動參數
from launch.substitutions import Command, LaunchConfiguration # Launch 變量替換機制(動態獲取參數或命令結果)
from launch_ros.actions import Node # ROS2 節點啟動動作
from launch_ros.descriptions import ParameterValue # ROS 參數值類型
from ament_index_python.packages import get_package_share_directory # 獲取包路徑的工具函數
import os
def generate_launch_description():
# 獲取 URDF 模型路徑
urdf_package_path = get_package_share_directory('robot_description')
default_urdf_path = os.path.join(urdf_package_path,'urdf','robot.xacro')
default_rviz_path = os.path.join(urdf_package_path,'config','display_robot.rviz')
# 聲明 Launch 參數
action_declare_arg_mode_path=DeclareLaunchArgument('model',default_value=str(default_urdf_path))
# 讀取 xacro 文件內容,xacro有空格,通過命令轉換為urdf讓rviz工具可以分析
command_result = Command(['xacro ',LaunchConfiguration('model')])
# 告訴 ROS 這是一個字符串類型的參數值(即 URDF 模型文本
robot_description_value = ParameterValue(command_result,value_type=str)
'''
啟動節點:
├─ joint_state_publisher → 發布 JointState
├─ robot_state_publisher → 發布 TF 樹
└─ rviz2 → 顯示模型
'''
robot_state_publisher = Node(
package='robot_state_publisher',
executable='robot_state_publisher',
parameters=[{'robot_description':robot_description_value}],
)
robot_joint_publisher = Node(
package='joint_state_publisher',
executable='joint_state_publisher',
)
robot_rviz_node = Node(
package='rviz2',
executable='rviz2',
arguments=['-d',default_rviz_path],
)
return LaunchDescription(
[action_declare_arg_mode_path,
robot_state_publisher,
robot_joint_publisher,
robot_rviz_node,]
)
通過上面的例子可以看到xacro的出現本質上是面向對象的思想運用到urdf文件中,通過定義宏將部件簡化為一個模板,對于有相似屬性的部件,通過模板可以創建多個極大的簡化了代碼量,如果是urdf原始寫法,上面五個imu部件就要寫五給link標簽,而xacro就簡化為了一個模板,傳入不同的值就可以定義多個不同的imu部件。
注意有些名稱的定義都是靜態的,例如宏的名稱是靜態的,不能用變量插入,比如 <xacro:macro name="${var}" params="${var}"> ,<xacro:property name="${var}"/> 和 <xacro:${macro_name}/>這些都不能動態變化,而其余的標簽里面的屬性可以進行動態插入。
創建一個自己的機器人
該機器人以mybot整合,包含底盤,傳感器和執行器三大塊
![image]()
底盤部件為機器人的主體幾何
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
<xacro:macro name="base_xacro" params="base_name xyz rpy radius length base_color rgba">
<link name="${base_name}">
<visual>
<origin xyz="${xyz}" rpy="${rpy}"/>
<geometry>
<cylinder radius="${radius}" length="${length}"/>
</geometry>
<material name="${base_color}">
<color rgba="${rgba}"/>
</material>
</visual>
</link>
<!-- 虛擬部件用于貼合地面 -->
<link name="base_footprint"> </link>
<joint name="joint_name" type="fixed">
<parent link="base_footprint"/>
<child link="${base_name}"/>
<origin xyz="0.0 0.0 ${length/2.0+0.032-0.01}" rpy="0.0 0.0 0.0"/>
</joint>
</xacro:macro>
</robot>
傳感器包括相機,imu和雷達三大傳感器
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
<xacro:macro name="camera_xacro" params="camera_name xyz rpy size camera_color rgba joint_name joint_type parent_name child_name joint_xyz joint_rpy">
<link name="${camera_name}">
<visual>
<origin xyz="${xyz}" rpy="${rpy}"/>
<geometry>
<box size="${size}"/>
</geometry>
<material name="${camera_color}">
<color rgba="${rgba}"/>
</material>
</visual>
</link>
<joint name="${joint_name}" type="${joint_type}">
<parent link="${parent_name}"/>
<child link="${child_name}"/>
<origin xyz="${joint_xyz}" rpy="${joint_rpy}"/>
</joint>
</xacro:macro>
</robot>
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
<xacro:macro name="imu_xacro" params="imu_name xyz rpy size imu_color rgba joint_name joint_type parent_name child_name joint_xyz joint_rpy">
<link name="${imu_name}">
<visual>
<origin xyz="${xyz}" rpy="${rpy}"/>
<geometry>
<box size="${size}"/>
</geometry>
<material name="${imu_color}">
<color rgba="${rgba}"/>
</material>
</visual>
</link>
<joint name="${joint_name}" type="${joint_type}">
<parent link="${parent_name}"/>
<child link="${child_name}"/>
<origin xyz="${joint_xyz}" rpy="${joint_rpy}"/>
</joint>
</xacro:macro>
</robot>
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
<!-- 激光雷達組件宏 -->
<xacro:macro name="laser_xacro" params="
laser_name
parent_name
joint1_name joint1_type joint1_xyz joint1_rpy
joint2_name joint2_type joint2_xyz joint2_rpy
cylinder_xyz cylinder_rpy cylinder_radius cylinder_length cylinder_color cylinder_rgba
sensor_xyz sensor_rpy sensor_radius sensor_length sensor_color sensor_rgba">
<!-- 支撐桿 -->
<link name='${laser_name}_cylinder_link'>
<visual>
<origin xyz='${cylinder_xyz}' rpy='${cylinder_rpy}'/>
<geometry>
<cylinder radius='${cylinder_radius}' length='${cylinder_length}'/>
</geometry>
<material name='${cylinder_color}'>
<color rgba='${cylinder_rgba}'/>
</material>
</visual>
</link>
<!-- 激光雷達主體 -->
<link name='${laser_name}_sensor_link'>
<visual>
<origin xyz='${sensor_xyz}' rpy='${sensor_rpy}'/>
<geometry>
<cylinder radius='${sensor_radius}' length='${sensor_length}'/>
</geometry>
<material name='${sensor_color}'>
<color rgba='${sensor_rgba}'/>
</material>
</visual>
</link>
<!-- 關節1:底座連接支撐桿 -->
<joint name='${joint1_name}' type='${joint1_type}'>
<parent link='${parent_name}'/>
<child link='${laser_name}_cylinder_link'/>
<origin xyz='${joint1_xyz}' rpy='${joint1_rpy}'/>
</joint>
<!-- 關節2:支撐桿連接激光雷達 -->
<joint name='${joint2_name}' type='${joint2_type}'>
<parent link='${laser_name}_cylinder_link'/>
<child link='${laser_name}_sensor_link'/>
<origin xyz='${joint2_xyz}' rpy='${joint2_rpy}'/>
</joint>
</xacro:macro>
</robot>
執行器有左右兩個輪子和前后的萬向輪
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
<xacro:macro name="caster_xacro" params="caster_name xyz rpy radius caster_color rgba joint_name joint_type parent_name child_name joint_xyz joint_rpy">
<link name="${caster_name}">
<visual>
<origin xyz="${xyz}" rpy="${rpy}"/>
<geometry>
<sphere radius="${radius}"/>
</geometry>
<material name="${caster_color}">
<color rgba="${rgba}"/>
</material>
</visual>
</link>
<joint name="${joint_name}" type="${joint_type}">
<parent link="${parent_name}"/>
<child link="${child_name}"/>
<origin xyz="${joint_xyz}" rpy="${joint_rpy}"/>
<axis xyz="0 1 0"/>
</joint>
</xacro:macro>
</robot>
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
<xacro:macro name="wheel_xacro" params="wheel_name xyz rpy radius length wheel_color rgba joint_name joint_type parent_name child_name joint_xyz joint_rpy">
<link name="${wheel_name}">
<visual>
<origin xyz="${xyz}" rpy="${rpy}"/>
<geometry>
<cylinder radius="${radius}" length="${length}"/>
</geometry>
<material name="${wheel_color}">
<color rgba="${rgba}"/>
</material>
</visual>
</link>
<joint name="${joint_name}" type="${joint_type}">
<parent link="${parent_name}"/>
<child link="${child_name}"/>
<origin xyz="${joint_xyz}" rpy="${joint_rpy}"/>
<axis xyz="0 1 0"/>
</joint>
</xacro:macro>
</robot>
集中部署到機器人上
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="mybot">
<!-- ========== Include 部分 ========== -->
<xacro:include filename="$(find robot_description)/urdf/base.urdf.xacro" />
<xacro:include filename="$(find robot_description)/urdf/sensor/camera.urdf.xacro" />
<xacro:include filename="$(find robot_description)/urdf/sensor/imu.urdf.xacro" />
<xacro:include filename="$(find robot_description)/urdf/sensor/laser.urdf.xacro" />
<xacro:include filename="$(find robot_description)/urdf/actuator/wheel.urdf.xacro" />
<xacro:include filename="$(find robot_description)/urdf/actuator/caster.urdf.xacro" />
<!-- ========== Base ========== -->
<xacro:base_xacro
base_name="base_link"
xyz="0 0 0"
rpy="0 0 0"
radius="0.1"
length="0.12"
base_color="white"
rgba="1 1 1 0.5" />
<!-- ========== IMU ========== -->
<xacro:imu_xacro
imu_name="imu_link"
xyz="0 0 0"
rpy="0 0 0"
size="0.02 0.02 0.02"
imu_color="black"
rgba="0 0 0 0.5"
joint_name="imu_joint"
joint_type="fixed"
parent_name="base_link"
child_name="imu_link"
joint_xyz="0 0 0.02"
joint_rpy="0 0 0" />
<!-- ========== Camera ========== -->
<xacro:camera_xacro
camera_name="camera_link"
xyz="0 0 0"
rpy="0 0 0"
size="0.02 0.02 0.02"
camera_color="black"
rgba="0 0 0 0.5"
joint_name="camera_joint"
joint_type="fixed"
parent_name="base_link"
child_name="camera_link"
joint_xyz="0.1 0 0.075"
joint_rpy="0 0 0" />
<!-- ========== Laser ========== -->
<xacro:laser_xacro
laser_name="laser"
parent_name="base_link"
joint1_name="laser_stand_joint"
joint1_type="fixed"
joint1_xyz="0 0 0.1"
joint1_rpy="0 0 0"
joint2_name="laser_mount_joint"
joint2_type="fixed"
joint2_xyz="0 0 0.05"
joint2_rpy="0 0 0"
cylinder_xyz="0 0 0"
cylinder_rpy="0 0 0"
cylinder_radius="0.01"
cylinder_length="0.1"
cylinder_color="black"
cylinder_rgba="0 0 0 1.0"
sensor_xyz="0 0 0"
sensor_rpy="0 0 0"
sensor_radius="0.02"
sensor_length="0.02"
sensor_color="black"
sensor_rgba="0 0 0 1.0" />
<!-- ========== Wheel ========== -->
<xacro:wheel_xacro
wheel_name="wheel_left"
xyz="0 0 0"
rpy="1.57079 0 0"
radius="0.032"
length="0.04"
wheel_color="yellow"
rgba="1 1 0 0.8"
joint_name="wheel_left_joint"
joint_type="continuous"
parent_name="base_link"
child_name="wheel_left"
joint_xyz="0 0.1 -0.06"
joint_rpy="0 0 0" />
<xacro:wheel_xacro
wheel_name="wheel_right"
xyz="0 0 0"
rpy="1.57079 0 0"
radius="0.032"
length="0.04"
wheel_color="yellow"
rgba="1 1 0 0.8"
joint_name="wheel_right_joint"
joint_type="continuous"
parent_name="base_link"
child_name="wheel_right"
joint_xyz="0 -0.1 -0.06"
joint_rpy="0 0 0" />
<!-- ========== Caster ========== -->
<xacro:caster_xacro
caster_name="caster_front"
xyz="0 0 0"
rpy="0 0 0"
radius="0.016"
caster_color="yellow"
rgba="1 1 0 0.8"
joint_name="caster_front_joint"
joint_type="fixed"
parent_name="base_link"
child_name="caster_front"
joint_xyz="0.08 0 -0.076"
joint_rpy="0 0 0" />
<xacro:caster_xacro
caster_name="caster_back"
xyz="0 0 0"
rpy="0 0 0"
radius="0.016"
caster_color="yellow"
rgba="1 1 0 0.8"
joint_name="caster_back_joint"
joint_type="fixed"
parent_name="base_link"
child_name="caster_back"
joint_xyz="-0.08 0 -0.076"
joint_rpy="0 0 0" />
</robot>
![image]()
簡單回顧一下之前的TF,一般由機器人底盤作為整體的父坐標系,而其底盤的原點坐標為下為相對世界坐標系下的坐標,坐標系的關系通過joint進行轉換,這樣才能將所有不同坐標系下的部件整合到一個大坐標系下統一控制。
| 所在位置 |
含義 |
坐標關系 |
影響對象 |
link/visual/origin |
模型幾何中心(例如立方體或圓柱)在 link 坐標系下的位置與姿態 |
幾何體 ← 相對 ← link |
決定 link 的外觀模型 |
link/collision/origin |
碰撞體在 link 坐標系下的位置與姿態 |
碰撞體 ← 相對 ← link |
用于仿真碰撞檢測 |
joint/origin |
子 link 坐標系在父 link 坐標系下的位置與姿態 |
child_link ← 相對 ← parent_link |
決定 link 之間的裝配關系 |
剛體
剛體是一種理想化的物理模型,其特征是在任何情況下,物體內部任意兩點之間的距離始終保持不變,也就是說,無論受到多大的外力或產生何種運動,剛體都不會發生形變、壓縮或拉伸。在機器人仿真過程中,為了簡化計算與提高仿真效率,我們通常將各個部件理想化為剛體進行建模和分析。
| 屬性 |
說明 |
| name |
剛體的名稱(link 名稱需唯一) |
| visual |
用于顯示的幾何外觀(僅視覺效果,不參與物理仿真) |
| collision |
用于物理碰撞檢測的幾何體(通常比 visual 更簡單) |
| inertial |
表示剛體的質量和慣性張量(影響物體運動特性) |
碰撞屬性
<collision> 元素用于定義剛體在物理仿真中的真實形狀,不影響 RViz 可視化,決定“仿真怎么動、怎么撞”和<visual>分工不同
如以下代碼所示給部件加入碰撞屬性上面為可視化部件,下面為添加碰撞屬性,部件位于link坐標系下,其余部件添加碰撞屬性也是如此
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
<xacro:macro name="base_xacro" params="base_name xyz rpy radius length base_color rgba">
<link name="${base_name}">
<visual>
<origin xyz="${xyz}" rpy="${rpy}"/>
<geometry>
<cylinder radius="${radius}" length="${length}"/>
</geometry>
<material name="${base_color}">
<color rgba="${rgba}"/>
</material>
</visual>
<collision>
<origin xyz="${xyz}" rpy="${rpy}"/>
<geometry>
<cylinder radius="${radius}" length="${length}"/>
</geometry>
<material name="${base_color}">
<color rgba="${rgba}"/>
</material>
</collision>
</link>
<link name="base_footprint"> </link>
<joint name="joint_name" type="fixed">
<parent link="base_footprint"/>
<child link="${base_name}"/>
<origin xyz="0.0 0.0 ${length/2.0+0.032-0.01}" rpy="0.0 0.0 0.0"/>
</joint>
</xacro:macro>
</robot>
關閉可視化,打開碰撞屬性可視化
![image]()
質量和慣性
真實環境下的機器人的各個部件肯定是有質量和慣性,由于上面我們已經加入的碰撞屬性,因此對于碰撞的之后物體會發生哪些變化還需借助質量和關系進行一個判斷。
慣性矩陣:描述剛體對不同旋轉軸的慣性阻力的一個對稱矩陣,它表示物體的質量分布對旋轉運動的影響,相當于“質量在旋轉意義上的分布。
添加系新的xacro文件儲存物體們的慣性矩陣
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
<!-- ========== 長方體慣性矩陣 ========== -->
<!-- 參數: m=質量, w=寬, h=高, d=深 -->
<xacro:macro name="box_inertia" params="m w h d">
<inertial>
<!-- 慣性中心位姿 -->
<origin xyz="0 0 0" rpy="0 0 0"/>
<mass value="${m}"/>
<inertia
ixx="${(m/12.0)*(h*h + d*d)}"
ixy="0.0"
ixz="0.0"
iyy="${(m/12.0)*(w*w + d*d)}"
iyz="0.0"
izz="${(m/12.0)*(w*w + h*h)}"/>
</inertial>
</xacro:macro>
<!-- ========== 圓柱體慣性矩陣 ========== -->
<!-- 參數: m=質量, r=半徑, h=高度 -->
<xacro:macro name="cylinder_inertia" params="m r h">
<inertial>
<origin xyz="0 0 0" rpy="0 0 0"/>
<mass value="${m}"/>
<inertia
ixx="${(m/12.0)*(3*r*r + h*h)}"
ixy="0.0"
ixz="0.0"
iyy="${(m/12.0)*(3*r*r + h*h)}"
iyz="0.0"
izz="${(m/2.0)*(r*r)}"/>
</inertial>
</xacro:macro>
<!-- ========== 球體慣性矩陣 ========== -->
<!-- 參數: m=質量, r=半徑 -->
<xacro:macro name="sphere_inertia" params="m r">
<inertial>
<origin xyz="0 0 0" rpy="0 0 0"/>
<mass value="${m}"/>
<inertia
ixx="${(2.0/5.0)*m*(r*r)}"
ixy="0.0"
ixz="0.0"
iyy="${(2.0/5.0)*m*(r*r)}"
iyz="0.0"
izz="${(2.0/5.0)*m*(r*r)}"/>
</inertial>
</xacro:macro>
</robot>
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
<xacro:include filename="$(find robot_description)/urdf/common_inert.urdf.xacro" />
<xacro:macro name="caster_xacro" params="caster_name xyz rpy radius caster_color rgba joint_name joint_type parent_name child_name joint_xyz joint_rpy">
<link name="${caster_name}">
<visual>
<origin xyz="${xyz}" rpy="${rpy}"/>
<geometry>
<sphere radius="${radius}"/>
</geometry>
<material name="${caster_color}">
<color rgba="${rgba}"/>
</material>
</visual>
<collision>
<origin xyz="${xyz}" rpy="${rpy}"/>
<geometry>
<sphere radius="${radius}"/>
</geometry>
<material name="${caster_color}">
<color rgba="${rgba}"/>
</material>
</collision>
<!--- 添加對應的慣性矩陣 -->
<xacro:sphere_inertia m="0.05" r="${radius}"/>
</link>
<joint name="${joint_name}" type="${joint_type}">
<parent link="${parent_name}"/>
<child link="${child_name}"/>
<origin xyz="${joint_xyz}" rpy="${joint_rpy}"/>
<axis xyz="0 1 0"/>
</joint>
</xacro:macro>
</robot>
其余的部件都是通過這種方式進行添加的
![image]()
Gazebo
Gazebo 是一個功能強大的 三維機器人仿真平臺,可以在虛擬環境中模擬 物理特性 和 傳感器數據,它常與 ROS 配合使用,用于在電腦上測試和驗證機器人控制、導航與感知算法,而無需真實硬件。
sudo apt install gazebo??# 下載功能模塊和一些基礎模型
cd ~/.gazebo/
git clone https://gitee.com/ohhuo/gazebo_models.git
rm -rf gazebo_models/.git/??# 刪除版本控制信息,變成本地獨立文件夾
使用gazebo創建一個房間
![image]()
![image]()
退出構建模式并保存模型
![image]()
保存世界文件,前面我們只是保存了模型文件,并不包含家具等物品,只是單純的墻面,后面我還在該房屋內添加了一個正方體障礙物
![image]()
![image]()
下載工具包,機器人模型文件為urdf而仿真模型文件為sdf,二者如果要集成的話需要進行文件類型的轉換,ros2自帶工具進行二者的文件轉換,和xacro將xacro文件轉換為urdf文件是同一個道理=
sudo apt install ros-$ROS_DISTRO-gazebo-ros-pkgs
創建launch在 gazebo加載機器人模型
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument, IncludeLaunchDescription
from launch.substitutions import Command, LaunchConfiguration
from launch_ros.actions import Node
from launch_ros.descriptions import ParameterValue
from launch.launch_description_sources import PythonLaunchDescriptionSource
from ament_index_python.packages import get_package_share_directory
import os
def generate_launch_description():
# ==============================
# 1?? 獲取必要的路徑
# ==============================
# 獲取 robot_description 包的共享路徑(即 install/robot_description/share/robot_description)
urdf_package_path = get_package_share_directory('robot_description')
# 設定默認的 URDF(Xacro) 文件路徑
default_xacro_path = os.path.join(urdf_package_path, 'urdf', 'mybot.urdf.xacro')
# 設定默認的 Gazebo 仿真世界文件路徑
default_gazebo_path = os.path.join(urdf_package_path, 'world', 'custom_room.world')
# ==============================
# 2?? 聲明 Launch 參數
# ==============================
# 聲明一個參數 `model`,默認值為我們定義的 Xacro 文件路徑
# 這樣可以在命令行中通過 model:=xxx.xacro 指定其他模型
action_declare_arg_mode_path = DeclareLaunchArgument(
'model',
default_value=str(default_xacro_path)
)
# ==============================
# 3?? 使用 xacro 生成 URDF
# ==============================
# 使用 Command 執行 xacro 命令,把 xacro 文件動態轉換為 URDF 內容字符串
command_result = Command(['xacro ', LaunchConfiguration('model')])
# 將轉換后的 URDF 結果傳遞給 robot_state_publisher
robot_description_value = ParameterValue(command_result, value_type=str)
# ==============================
# 4?? 啟動 robot_state_publisher 節點
# ==============================
# 該節點負責發布機器人模型(TF 樹)到 /tf 和 /robot_description
robot_state_publisher = Node(
package='robot_state_publisher',
executable='robot_state_publisher',
parameters=[{'robot_description': robot_description_value}],
)
# ==============================
# 5?? 啟動 Gazebo 仿真環境
# ==============================
# IncludeLaunchDescription 表示嵌套啟動另一個 launch 文件
# 這里啟動 gazebo_ros 包內置的 gazebo.launch.py 文件
action_launch_gazebo = IncludeLaunchDescription(
PythonLaunchDescriptionSource([
get_package_share_directory('gazebo_ros'),
'/launch', '/gazebo.launch.py'
]),
# 向 Gazebo 傳入 world 文件路徑和 verbose 模式參數
launch_arguments=[
('world', default_gazebo_path),
('verbose', 'true')
],
)
# ==============================
# 6?? 向 Gazebo 中生成機器人實體
# ==============================
# spawn_entity.py 腳本會讀取 /robot_description 主題中的 URDF 數據,
# 并在 Gazebo 世界中生成對應的機器人模型(即“實體”)
action_spawn_entity = Node(
package='gazebo_ros',
executable='spawn_entity.py',
arguments=['-topic', '/robot_description', '-entity', 'mybot'],
)
# ==============================
# 7?? 返回 LaunchDescription(啟動描述)
# ==============================
# LaunchDescription 是 ROS2 Launch 系統的核心,它定義了要啟動的所有組件。
return LaunchDescription([
action_declare_arg_mode_path, # 聲明 model 參數
robot_state_publisher, # 啟動 robot_state_publisher
action_launch_gazebo, # 啟動 Gazebo 仿真環境
action_spawn_entity, # 向 Gazebo 中生成機器人模型
])
Cmakelists.txtx不要忘了修改
install(DIRECTORY launch urdf config world
DESTINATION share/${PROJECT_NAME}
)
![image]()
<gazebo> 標簽擴展原urdf文件
| 標簽名 |
作用 |
<material> |
指定 Gazebo 中顯示的材質(顏色/紋理) |
<mu1>, <mu2> |
摩擦系數 |
<plugin> |
加載插件(控制器、傳感器、ROS接口等) |
<sensor> |
定義傳感器(攝像頭、雷達、IMU 等) |
<kp>, <kd> |
物理彈性、阻尼參數 |
<self_collide> |
是否允許自碰撞 |
<max_contacts> |
最大接觸點數量 |
一般使用格式
<gazebo reference="link_name">
<!-- Gazebo 相關配置 -->
</gazebo>
或不指定 reference(應用到整個模型):
<gazebo>
<!-- 全局 Gazebo 配置 -->
</gazebo>
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
<xacro:include filename="$(find robot_description)/urdf/common_inert.urdf.xacro" />
<xacro:macro name="caster_xacro" params="caster_name xyz rpy radius caster_color rgba joint_name joint_type parent_name child_name joint_xyz joint_rpy">
<link name="${caster_name}">
<visual>
<origin xyz="${xyz}" rpy="${rpy}"/>
<geometry>
<sphere radius="${radius}"/>
</geometry>
<material name="${caster_color}">
<color rgba="${rgba}"/>
</material>
</visual>
<collision>
<origin xyz="${xyz}" rpy="${rpy}"/>
<geometry>
<sphere radius="${radius}"/>
</geometry>
<material name="${caster_color}">
<color rgba="${rgba}"/>
</material>
</collision>
<xacro:sphere_inertia m="0.05" r="${radius}"/>
</link>
<joint name="${joint_name}" type="${joint_type}">
<parent link="${parent_name}"/>
<child link="${child_name}"/>
<origin xyz="${joint_xyz}" rpy="${joint_rpy}"/>
<axis xyz="0 1 0"/>
</joint>
<gazebo reference="${caster_name}">
<!-- Gazebo 材質(對應 Gazebo 材質庫) -->
<material>Gazebo/${caster_color}</material>
<!-- 物理特性:摩擦、恢復系數等 -->
<mu1>0.2</mu1> <!-- 摩擦系數1 -->
<mu2>0.2</mu2> <!-- 摩擦系數2 -->
<kp>100000.0</kp> <!-- 彈性系數 -->
<kd>10.0</kd> <!-- 阻尼系數 -->
<maxVel>0.1</maxVel> <!-- 最大接觸速度 -->
<minDepth>0.001</minDepth> <!-- 最小接觸深度 -->
</gazebo>
</xacro:macro>
</robot>
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
<xacro:include filename="$(find robot_description)/urdf/common_inert.urdf.xacro" />
<xacro:macro name="wheel_xacro" params="wheel_name xyz rpy radius length wheel_color rgba joint_name joint_type parent_name child_name joint_xyz joint_rpy">
<link name="${wheel_name}">
<visual>
<origin xyz="${xyz}" rpy="${rpy}"/>
<geometry>
<cylinder radius="${radius}" length="${length}"/>
</geometry>
<material name="${wheel_color}">
<color rgba="${rgba}"/>
</material>
</visual>
<collision>
<origin xyz="${xyz}" rpy="${rpy}"/>
<geometry>
<cylinder radius="${radius}" length="${length}"/>
</geometry>
<material name="${wheel_color}">
<color rgba="${rgba}"/>
</material>
</collision>
<xacro:cylinder_inertia m="1.0" r="${radius}" h="${length}"/>
</link>
<joint name="${joint_name}" type="${joint_type}">
<parent link="${parent_name}"/>
<child link="${child_name}"/>
<origin xyz="${joint_xyz}" rpy="${joint_rpy}"/>
<axis xyz="0 1 0"/>
</joint>
<!-- 為 Gazebo 指定 link 的物理、視覺、摩擦等屬性 -->
<gazebo reference="${wheel_name}">
<!-- Gazebo 中的視覺材質 -->
<material>Gazebo/${wheel_color}</material>
<!-- 摩擦參數,決定輪胎打滑程度 -->
<mu1>20.0</mu1>
<mu2>20.0</mu2>
<!-- 彈性與阻尼系數 -->
<kp>100000.0</kp>
<kd>10.0</kd>
<!-- 避免輪子“穿透”地面 -->
<maxVel>0.1</maxVel>
<minDepth>0.001</minDepth>
</gazebo>
<gazebo>
</gazebo>
</xacro:macro>
</robot>
注意mybot傳入的顏色名稱首字母要大寫才會生效
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="mybot">
<!-- ========== Include 部分 ========== -->
<xacro:include filename="$(find robot_description)/urdf/base.urdf.xacro" />
<xacro:include filename="$(find robot_description)/urdf/sensor/camera.urdf.xacro" />
<xacro:include filename="$(find robot_description)/urdf/sensor/imu.urdf.xacro" />
<xacro:include filename="$(find robot_description)/urdf/sensor/laser.urdf.xacro" />
<xacro:include filename="$(find robot_description)/urdf/actuator/wheel.urdf.xacro" />
<xacro:include filename="$(find robot_description)/urdf/actuator/caster.urdf.xacro" />
<!-- ========== Base ========== -->
<xacro:base_xacro
base_name="base_link"
xyz="0 0 0"
rpy="0 0 0"
radius="0.1"
length="0.12"
base_color="White"
rgba="1 1 1 0.5" />
<!-- ========== IMU ========== -->
<xacro:imu_xacro
imu_name="imu_link"
xyz="0 0 0"
rpy="0 0 0"
size="0.02 0.02 0.02"
imu_color="Black"
rgba="0 0 0 0.5"
joint_name="imu_joint"
joint_type="fixed"
parent_name="base_link"
child_name="imu_link"
joint_xyz="0 0 0.02"
joint_rpy="0 0 0" />
<!-- ========== Camera ========== -->
<xacro:camera_xacro
camera_name="camera_link"
xyz="0 0 0"
rpy="0 0 0"
size="0.02 0.1 0.02"
camera_color="Black"
rgba="0 0 0 0.5"
joint_name="camera_joint"
joint_type="fixed"
parent_name="base_link"
child_name="camera_link"
joint_xyz="0.1 0 0.075"
joint_rpy="0 0 0" />
<!-- ========== Laser ========== -->
<xacro:laser_xacro
laser_name="laser"
parent_name="base_link"
joint1_name="laser_stand_joint"
joint1_type="fixed"
joint1_xyz="0 0 0.1"
joint1_rpy="0 0 0"
joint2_name="laser_mount_joint"
joint2_type="fixed"
joint2_xyz="0 0 0.05"
joint2_rpy="0 0 0"
cylinder_xyz="0 0 0"
cylinder_rpy="0 0 0"
cylinder_radius="0.01"
cylinder_length="0.1"
cylinder_color="Black"
cylinder_rgba="0 0 0 1.0"
sensor_xyz="0 0 0"
sensor_rpy="0 0 0"
sensor_radius="0.02"
sensor_length="0.02"
sensor_color="Black"
sensor_rgba="0 0 0 1.0" />
<!-- ========== Wheel ========== -->
<xacro:wheel_xacro
wheel_name="wheel_left"
xyz="0 0 0"
rpy="1.57079 0 0"
radius="0.032"
length="0.04"
wheel_color="Yellow"
rgba="1 1 0 0.8"
joint_name="wheel_left_joint"
joint_type="continuous"
parent_name="base_link"
child_name="wheel_left"
joint_xyz="0 0.1 -0.06"
joint_rpy="0 0 0" />
<xacro:wheel_xacro
wheel_name="wheel_right"
xyz="0 0 0"
rpy="1.57079 0 0"
radius="0.032"
length="0.04"
wheel_color="Yellow"
rgba="1 1 0 0.8"
joint_name="wheel_right_joint"
joint_type="continuous"
parent_name="base_link"
child_name="wheel_right"
joint_xyz="0 -0.1 -0.06"
joint_rpy="0 0 0" />
<!-- ========== Caster ========== -->
<xacro:caster_xacro
caster_name="caster_front"
xyz="0 0 0"
rpy="0 0 0"
radius="0.016"
caster_color="Yellow"
rgba="1 1 0 0.8"
joint_name="caster_front_joint"
joint_type="fixed"
parent_name="base_link"
child_name="caster_front"
joint_xyz="0.08 0 -0.076"
joint_rpy="0 0 0" />
<xacro:caster_xacro
caster_name="caster_back"
xyz="0 0 0"
rpy="0 0 0"
radius="0.016"
caster_color="Yellow"
rgba="1 1 0 0.8"
joint_name="caster_back_joint"
joint_type="fixed"
parent_name="base_link"
child_name="caster_back"
joint_xyz="-0.08 0 -0.076"
joint_rpy="0 0 0" />
</robot>
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
<xacro:include filename="$(find robot_description)/urdf/common_inert.urdf.xacro" />
<xacro:macro name="imu_xacro" params="imu_name xyz rpy size imu_color rgba joint_name joint_type parent_name child_name joint_xyz joint_rpy">
<link name="${imu_name}">
<visual>
<origin xyz="${xyz}" rpy="${rpy}"/>
<geometry>
<box size="${size}"/>
</geometry>
<material name="${imu_color}">
<color rgba="${rgba}"/>
</material>
</visual>
<collision>
<origin xyz="${xyz}" rpy="${rpy}"/>
<geometry>
<box size="${size}"/>
</geometry>
<material name="${imu_color}">
<color rgba="${rgba}"/>
</material>
</collision>
<xacro:box_inertia m="0.05" w="0.02" h="0.02" d="0.02"/>
</link>
<joint name="${joint_name}" type="${joint_type}">
<parent link="${parent_name}"/>
<child link="${child_name}"/>
<origin xyz="${joint_xyz}" rpy="${joint_rpy}"/>
</joint>
<gazebo reference="${imu_name}">
<material>Gazebo/${imu_color}</material>
</gazebo>
</xacro:macro>
</robot>
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
<xacro:include filename="$(find robot_description)/urdf/common_inert.urdf.xacro" />
<xacro:macro name="imu_xacro" params="imu_name xyz rpy size imu_color rgba joint_name joint_type parent_name child_name joint_xyz joint_rpy">
<link name="${imu_name}">
<visual>
<origin xyz="${xyz}" rpy="${rpy}"/>
<geometry>
<box size="${size}"/>
</geometry>
<material name="${imu_color}">
<color rgba="${rgba}"/>
</material>
</visual>
<collision>
<origin xyz="${xyz}" rpy="${rpy}"/>
<geometry>
<box size="${size}"/>
</geometry>
<material name="${imu_color}">
<color rgba="${rgba}"/>
</material>
</collision>
<xacro:box_inertia m="0.05" w="0.02" h="0.02" d="0.02"/>
</link>
<joint name="${joint_name}" type="${joint_type}">
<parent link="${parent_name}"/>
<child link="${child_name}"/>
<origin xyz="${joint_xyz}" rpy="${joint_rpy}"/>
</joint>
<gazebo reference="${imu_name}">
<material>Gazebo/${imu_color}</material>
</gazebo>
</xacro:macro>
</robot>
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
<xacro:include filename="$(find robot_description)/urdf/common_inert.urdf.xacro" />
<!-- 激光雷達組件宏 -->
<xacro:macro name="laser_xacro" params="
laser_name
parent_name
joint1_name joint1_type joint1_xyz joint1_rpy
joint2_name joint2_type joint2_xyz joint2_rpy
cylinder_xyz cylinder_rpy cylinder_radius cylinder_length cylinder_color cylinder_rgba
sensor_xyz sensor_rpy sensor_radius sensor_length sensor_color sensor_rgba">
<!-- 支撐桿 -->
<link name="${laser_name}_cylinder_link">
<visual>
<origin xyz="${cylinder_xyz}" rpy="${cylinder_rpy}"/>
<geometry>
<cylinder radius="${cylinder_radius}" length="${cylinder_length}"/>
</geometry>
<material name="${cylinder_color}">
<color rgba="${cylinder_rgba}"/>
</material>
</visual>
<collision>
<origin xyz="${cylinder_xyz}" rpy="${cylinder_rpy}"/>
<geometry>
<cylinder radius="${cylinder_radius}" length="${cylinder_length}"/>
</geometry>
<material name="${cylinder_color}">
<color rgba="${cylinder_rgba}"/>
</material>
</collision>
</link>
<!-- 激光雷達主體 -->
<link name="${laser_name}_sensor_link">
<visual>
<origin xyz="${sensor_xyz}" rpy="${sensor_rpy}"/>
<geometry>
<cylinder radius="${sensor_radius}" length="${sensor_length}"/>
</geometry>
<material name="${sensor_color}">
<color rgba="${sensor_rgba}"/>
</material>
</visual>
<collision>
<origin xyz="${sensor_xyz}" rpy="${sensor_rpy}"/>
<geometry>
<cylinder radius="${sensor_radius}" length="${sensor_length}"/>
</geometry>
<material name="${sensor_color}">
<color rgba="${sensor_rgba}"/>
</material>
</collision>
<xacro:cylinder_inertia m="0.1" r="${cylinder_radius}" h="${cylinder_length}"/>
<xacro:cylinder_inertia m="0.1" r="${sensor_radius}" h="${sensor_length}"/>
</link>
<!-- 關節1:底座連接支撐桿 -->
<joint name="${joint1_name}" type="${joint1_type}">
<parent link="${parent_name}"/>
<child link="${laser_name}_cylinder_link"/>
<origin xyz="${joint1_xyz}" rpy="${joint1_rpy}"/>
</joint>
<!-- 關節2:支撐桿連接激光雷達 -->
<joint name="${joint2_name}" type="${joint2_type}">
<parent link="${laser_name}_cylinder_link"/>
<child link="${laser_name}_sensor_link"/>
<origin xyz="${joint2_xyz}" rpy="${joint2_rpy}"/>
</joint>
<gazebo reference="${laser_name}">
<material>Gazebo/${laser_color}</material>
</gazebo>
</xacro:macro>
</robot>
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
<xacro:include filename="$(find robot_description)/urdf/common_inert.urdf.xacro" />
<xacro:macro name="base_xacro" params="base_name xyz rpy radius length base_color rgba">
<link name="${base_name}">
<visual>
<origin xyz="${xyz}" rpy="${rpy}"/>
<geometry>
<cylinder radius="${radius}" length="${length}"/>
</geometry>
<material name="${base_color}">
<color rgba="${rgba}"/>
</material>
</visual>
<collision>
<origin xyz="${xyz}" rpy="${rpy}"/>
<geometry>
<cylinder radius="${radius}" length="${length}"/>
</geometry>
<material name="${base_color}">
<color rgba="${rgba}"/>
</material>
</collision>
<xacro:cylinder_inertia m="1.0" r="${radius}" h="${length}"/>
</link>
<link name="base_footprint"> </link>
<joint name="joint_name" type="fixed">
<parent link="base_footprint"/>
<child link="${base_name}"/>
<origin xyz="0.0 0.0 ${length/2.0+0.032-0.01}" rpy="0.0 0.0 0.0"/>
</joint>
<gazebo reference="${base_name}">
<material>Gazebo/${base_color}</material>
</gazebo>
</xacro:macro>
</robot>
![image]()
差速驅動插件
該插件在Gazebo 的物理模型 和 ROS 的控制話題 之間建立了橋梁,讓兩輪差速小車在 Gazebo 里能被 ROS 控制移動
新建文件夾plugins,然后在下面新建插件xacro文件
<?xml version="1.0"?>
<!-- 定義一個 ROS xacro 文件,用于在 Gazebo 中加載差速驅動插件 -->
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
<!-- 定義一個宏:gazebo_control_plugin -->
<!-- 這個宏可以在主 URDF 文件中通過 <xacro:gazebo_control_plugin/> 調用 -->
<xacro:macro name="gazebo_control_plugin">
<!-- Gazebo 仿真相關的標簽 -->
<gazebo>
<!-- 插件定義 -->
<!-- libgazebo_ros_diff_drive.so 是 Gazebo 官方的 ROS 2 插件,用于實現差速驅動(左右輪控制) -->
<plugin name='diff_drive' filename='libgazebo_ros_diff_drive.so'>
<!-- ROS 2 相關配置 -->
<ros>
<!-- 命名空間(namespace):決定節點、話題等在 ROS 圖中的層級 -->
<!-- 默認為根命名空間“/” -->
<namespace>/</namespace>
<!-- 話題重映射 -->
<!-- 將 Gazebo 插件內部使用的 cmd_vel、odom 話題映射為 ROS 系統中對應的名稱 -->
<remapping>cmd_vel:=cmd_vel</remapping>
<remapping>odom:=odom</remapping>
</ros>
<!-- 更新頻率(Hz) -->
<!-- 插件內部更新控制與里程計發布的速率 -->
<update_rate>30</update_rate>
<!-- ========== 輪子設置 ========== -->
<!-- 左右輪的關節名(必須與 URDF 中 joint 名稱完全一致) -->
<left_joint>wheel_left_joint</left_joint>
<right_joint>wheel_right_joint</right_joint>
<!-- ========== 運動學參數 ========== -->
<!-- 輪間距(兩輪中心之間的距離,單位:米) -->
<wheel_separation>0.2</wheel_separation>
<!-- 輪子直徑(單位:米) -->
<wheel_diameter>0.064</wheel_diameter>
<!-- ========== 動力學限制 ========== -->
<!-- 最大扭矩(影響機器人加速能力,單位:N·m) -->
<max_wheel_torque>20</max_wheel_torque>
<!-- 最大加速度(單位:m/s2) -->
<max_wheel_acceleration>1.0</max_wheel_acceleration>
<!-- ========== 輸出與發布設置 ========== -->
<!-- 是否發布里程計數據 -->
<publish_odom>true</publish_odom>
<!-- 是否發布 TF(odom -> base_link 或 base_footprint) -->
<publish_odom_tf>true</publish_odom_tf>
<!-- 是否發布輪子 TF(wheel_link) -->
<publish_wheel_tf>true</publish_wheel_tf>
<!-- ========== 坐標系定義 ========== -->
<!-- 里程計參考坐標系 -->
<odometry_frame>odom</odometry_frame>
<!-- 機器人底盤坐標系(與 URDF 中定義一致) -->
<robot_base_frame>base_footprint</robot_base_frame>
</plugin>
</gazebo>
</xacro:macro>
</robot>
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="mybot">
<!-- ========== Include 部分 ========== -->
<xacro:include filename="$(find robot_description)/urdf/base.urdf.xacro" />
<xacro:include filename="$(find robot_description)/urdf/sensor/camera.urdf.xacro" />
<xacro:include filename="$(find robot_description)/urdf/sensor/imu.urdf.xacro" />
<xacro:include filename="$(find robot_description)/urdf/sensor/laser.urdf.xacro" />
<xacro:include filename="$(find robot_description)/urdf/actuator/wheel.urdf.xacro" />
<xacro:include filename="$(find robot_description)/urdf/actuator/caster.urdf.xacro" />
<xacro:include filename="$(find robot_description)/urdf/plugins/gazebo_control_plugin.xacro" />
<!-- ========== Base ========== -->
<xacro:base_xacro
base_name="base_link"
xyz="0 0 0"
rpy="0 0 0"
radius="0.1"
length="0.12"
base_color="White"
rgba="1 1 1 0.5" />
<!-- ========== IMU ========== -->
<xacro:imu_xacro
imu_name="imu_link"
xyz="0 0 0"
rpy="0 0 0"
size="0.02 0.02 0.02"
imu_color="Black"
rgba="0 0 0 0.5"
joint_name="imu_joint"
joint_type="fixed"
parent_name="base_link"
child_name="imu_link"
joint_xyz="0 0 0.02"
joint_rpy="0 0 0" />
<!-- ========== Camera ========== -->
<xacro:camera_xacro
camera_name="camera_link"
xyz="0 0 0"
rpy="0 0 0"
size="0.02 0.1 0.02"
camera_color="Black"
rgba="0 0 0 0.5"
joint_name="camera_joint"
joint_type="fixed"
parent_name="base_link"
child_name="camera_link"
joint_xyz="0.1 0 0.075"
joint_rpy="0 0 0" />
<!-- ========== Laser ========== -->
<xacro:laser_xacro
laser_name="laser"
parent_name="base_link"
joint1_name="laser_stand_joint"
joint1_type="fixed"
joint1_xyz="0 0 0.1"
joint1_rpy="0 0 0"
joint2_name="laser_mount_joint"
joint2_type="fixed"
joint2_xyz="0 0 0.05"
joint2_rpy="0 0 0"
cylinder_xyz="0 0 0"
cylinder_rpy="0 0 0"
cylinder_radius="0.01"
cylinder_length="0.1"
cylinder_color="Black"
cylinder_rgba="0 0 0 1.0"
sensor_xyz="0 0 0"
sensor_rpy="0 0 0"
sensor_radius="0.02"
sensor_length="0.02"
sensor_color="Black"
sensor_rgba="0 0 0 1.0" />
<!-- ========== Wheel ========== -->
<xacro:wheel_xacro
wheel_name="wheel_left"
xyz="0 0 0"
rpy="1.57079 0 0"
radius="0.032"
length="0.04"
wheel_color="Yellow"
rgba="1 1 0 0.8"
joint_name="wheel_left_joint"
joint_type="continuous"
parent_name="base_link"
child_name="wheel_left"
joint_xyz="0 0.1 -0.06"
joint_rpy="0 0 0" />
<xacro:wheel_xacro
wheel_name="wheel_right"
xyz="0 0 0"
rpy="1.57079 0 0"
radius="0.032"
length="0.04"
wheel_color="Yellow"
rgba="1 1 0 0.8"
joint_name="wheel_right_joint"
joint_type="continuous"
parent_name="base_link"
child_name="wheel_right"
joint_xyz="0 -0.1 -0.06"
joint_rpy="0 0 0" />
<!-- ========== Caster ========== -->
<xacro:caster_xacro
caster_name="caster_front"
xyz="0 0 0"
rpy="0 0 0"
radius="0.016"
caster_color="Yellow"
rgba="1 1 0 0.8"
joint_name="caster_front_joint"
joint_type="fixed"
parent_name="base_link"
child_name="caster_front"
joint_xyz="0.08 0 -0.076"
joint_rpy="0 0 0" />
<xacro:caster_xacro
caster_name="caster_back"
xyz="0 0 0"
rpy="0 0 0"
radius="0.016"
caster_color="Yellow"
rgba="1 1 0 0.8"
joint_name="caster_back_joint"
joint_type="fixed"
parent_name="base_link"
child_name="caster_back"
joint_xyz="-0.08 0 -0.076"
joint_rpy="0 0 0" />
<xacro:gazebo_control_plugin />
</robot>
控制小車進行一個移動
![image]()
![image]()
![image]()
![image]()
注意先通過launch啟動gazebo然后再使用命令啟動rviz2而不是通過之前編寫的launch去啟動,因為二者啟動得到的TF是各自獨立的,博主一開始沒有注意這個問題搞得得到的rviz2的TF中左右兩輪和虛擬地面之間的TF一直沒找到,折騰了好久才發現這個問題
添加三個傳感器的仿真插件
IMU是移動機器人、無人車、無人機中非常關鍵的一個傳感器,它的主要功能是:測量和估計機器人在三維空間中的姿態、角速度和線加速度。
需要修改一下相機的TF,傳統環境下的相機是Z軸向前的,即Z軸垂直于相機坐標系
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
<xacro:include filename="$(find robot_description)/urdf/common_inert.urdf.xacro" />
<xacro:macro name="camera_xacro" params="camera_name xyz rpy size camera_color rgba joint_name joint_type parent_name child_name joint_xyz joint_rpy">
<link name="${camera_name}">
<visual>
<origin xyz="${xyz}" rpy="${rpy}"/>
<geometry>
<box size="${size}"/>
</geometry>
<material name="${camera_color}">
<color rgba="${rgba}"/>
</material>
</visual>
<collision>
<origin xyz="${xyz}" rpy="${rpy}"/>
<geometry>
<box size="${size}"/>
</geometry>
<material name="${camera_color}">
<color rgba="${rgba}"/>
</material>
</collision>
</link>
<link name="camera_optical_link"></link>
<xacro:box_inertia m="0.1" w="0.02" h="0.1" d="0.02"/>
<joint name="${joint_name}" type="${joint_type}">
<parent link="${parent_name}"/>
<child link="${child_name}"/>
<origin xyz="${joint_xyz}" rpy="${joint_rpy}"/>
</joint>
<joint name="camera_optical_joint" type="fixed">
<parent link="${camera_name}" />
<child link="camera_optical_link" />
<origin xyz="0 0 0" rpy="${-pi/2} 0 ${-pi/2}" />
</joint>
<gazebo reference="${camera_name}">
<material>Gazebo/${camera_color}</material>
</gazebo>
</xacro:macro>
</robot>
![image]()
模擬真實環境下的相機坐標系
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
<!-- 定義一個宏:gazebo_sensor_plugin
用于在機器人URDF中插入激光雷達、IMU、深度相機的Gazebo仿真插件 -->
<xacro:macro name="gazebo_sensor_plugin">
<!-- ====================== 激光雷達(LaserScan)配置 ====================== -->
<gazebo reference="laser_sensor_link"> <!-- 將以下傳感器掛載到 laser_link 鏈接上 -->
<sensor name="laserscan" type="ray"> <!-- 定義一個射線型雷達傳感器 -->
<!-- 使用 Gazebo-ROS 插件,將雷達數據發布到 ROS 中 -->
<plugin name="laserscan" filename="libgazebo_ros_ray_sensor.so">
<ros>
<namespace>/</namespace> <!-- ROS命名空間設置為根目錄 -->
<remapping>~/out:=scan</remapping> <!-- 輸出話題重映射為 /scan -->
</ros>
<output_type>sensor_msgs/LaserScan</output_type> <!-- 輸出消息類型 -->
<frame_name>laser_sensor_link</frame_name> <!-- TF坐標系名稱 -->
</plugin>
<!-- 傳感器運行參數 -->
<always_on>true</always_on> <!-- 始終開啟 -->
<visualize>true</visualize> <!-- 在Gazebo界面中顯示掃描 -->
<update_rate>5</update_rate> <!-- 更新頻率 5Hz -->
<pose>0 0 0 0 0 0</pose> <!-- 相對于laser_link的位姿 -->
<!-- 激光掃描參數設置 -->
<ray>
<scan>
<horizontal>
<samples>360</samples> <!-- 水平掃描樣本數 -->
<resolution>1.000000</resolution> <!-- 角分辨率 -->
<min_angle>0.000000</min_angle> <!-- 最小角度(弧度) -->
<max_angle>6.280000</max_angle> <!-- 最大角度(2π,360°) -->
</horizontal>
</scan>
<!-- 測距范圍配置 -->
<range>
<min>0.120000</min> <!-- 最小測距0.12m -->
<max>8.0</max> <!-- 最大測距8m -->
<resolution>0.015000</resolution> <!-- 距離分辨率0.015m -->
</range>
<!-- 添加高斯噪聲以模擬真實環境測量誤差 -->
<noise>
<type>gaussian</type>
<mean>0.0</mean>
<stddev>0.01</stddev>
</noise>
</ray>
</sensor>
</gazebo>
<!-- ====================== IMU慣性測量單元配置 ====================== -->
<gazebo reference="imu_link"> <!-- 將IMU掛載到imu_link鏈接上 -->
<sensor name="imu_sensor" type="imu"> <!-- 傳感器類型為IMU -->
<!-- 加載Gazebo ROS IMU插件 -->
<plugin name="imu_plugin" filename="libgazebo_ros_imu_sensor.so">
<ros>
<namespace>/</namespace> <!-- ROS命名空間為根目錄 -->
<remapping>~/out:=imu</remapping> <!-- 輸出話題重映射為 /imu -->
</ros>
<initial_orientation_as_reference>false</initial_orientation_as_reference> <!-- 不將初始姿態作為基準 -->
</plugin>
<update_rate>100</update_rate> <!-- 100Hz高頻率輸出 -->
<always_on>true</always_on> <!-- 始終開啟 -->
<!-- IMU噪聲模型定義(角速度+線加速度) -->
<imu>
<!-- 角速度噪聲(x/y/z三軸) -->
<angular_velocity>
<x>
<noise type="gaussian">
<mean>0.0</mean>
<stddev>2e-4</stddev>
<bias_mean>0.0000075</bias_mean>
<bias_stddev>0.0000008</bias_stddev>
</noise>
</x>
<y>
<noise type="gaussian">
<mean>0.0</mean>
<stddev>2e-4</stddev>
<bias_mean>0.0000075</bias_mean>
<bias_stddev>0.0000008</bias_stddev>
</noise>
</y>
<z>
<noise type="gaussian">
<mean>0.0</mean>
<stddev>2e-4</stddev>
<bias_mean>0.0000075</bias_mean>
<bias_stddev>0.0000008</bias_stddev>
</noise>
</z>
</angular_velocity>
<!-- 線加速度噪聲(x/y/z三軸) -->
<linear_acceleration>
<x>
<noise type="gaussian">
<mean>0.0</mean>
<stddev>1.7e-2</stddev>
<bias_mean>0.1</bias_mean>
<bias_stddev>0.001</bias_stddev>
</noise>
</x>
<y>
<noise type="gaussian">
<mean>0.0</mean>
<stddev>1.7e-2</stddev>
<bias_mean>0.1</bias_mean>
<bias_stddev>0.001</bias_stddev>
</noise>
</y>
<z>
<noise type="gaussian">
<mean>0.0</mean>
<stddev>1.7e-2</stddev>
<bias_mean>0.1</bias_mean>
<bias_stddev>0.001</bias_stddev>
</noise>
</z>
</linear_acceleration>
</imu>
</sensor>
</gazebo>
<!-- ====================== 深度相機(Depth Camera)配置 ====================== -->
<gazebo reference="camera_link"> <!-- 掛載在camera_link鏈接 -->
<sensor type="depth" name="camera_sensor"> <!-- 定義一個深度相機傳感器 -->
<!-- 使用Gazebo-ROS相機插件 -->
<plugin name="depth_camera" filename="libgazebo_ros_camera.so">
<frame_name>camera_optical_link</frame_name> <!-- 相機坐標幀名稱 -->
</plugin>
<!-- 基本參數 -->
<always_on>true</always_on> <!-- 始終開啟 -->
<update_rate>10</update_rate> <!-- 10Hz幀率 -->
<!-- 相機內參設置 -->
<camera name="camera">
<horizontal_fov>1.5009831567</horizontal_fov> <!-- 水平視場角約85° -->
<image>
<width>800</width> <!-- 圖像寬度 -->
<height>600</height> <!-- 圖像高度 -->
<format>R8G8B8</format> <!-- RGB格式 -->
</image>
<!-- 畸變參數(全0表示理想無畸變相機) -->
<distortion>
<k1>0.0</k1>
<k2>0.0</k2>
<k3>0.0</k3>
<p1>0.0</p1>
<p2>0.0</p2>
<center>0.5 0.5</center> <!-- 光心位置(歸一化坐標) -->
</distortion>
</camera>
</sensor>
</gazebo>
</xacro:macro>
</robot>
不要忘了在mybot.urdf.xacro里面添加引用
![image]()
![image]()
![image]()
ros2_control
ros2_control 是 ROS 2 官方推薦的機器人控制框架,其核心思想是把控制算法(如差速控制器、關節位置控制器)與硬件接口(電機、執行器、Gazebo 模擬等)分離,底層為多個服務共同實現。
ros2_control本身就是真實環境/仿真環境和機器人行為交互之間的一個中間媒介,幫上層決策邏輯(比如根據環境避障、規劃路徑)和底層硬件(或仿真模型)高效對接,即控制算法-ros2_control-外部環境,這樣一個三層關系,將抽象算法轉為實際環境中的響應同時將實際的環境轉為算法所需的數據。
sudo apt install ros-$ROS_DISTRO-ros2-controllers
![image]()
ros2_control 的架構可概括為 “1 個中樞 + 3 類核心組件 + 標準化接口”,各部分各司其職、協同工作:
編寫<ros2_control>的xacro文件,通過 <ros2_control> 標簽關聯 ros2_control 的硬件組件和接口(狀態 / 指令接口),即關聯硬件和ros2_control 接口
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro">
<!-- 定義一個名為 mybot_ros2_control 的 xacro 宏 -->
<!-- 調用此宏即可為 mybot 加載 ros2_control 與 gazebo_ros2_control 插件 -->
<xacro:macro name="mybot_ros2_control">
<!-- ==========================
ROS2 Control 系統定義部分
========================== -->
<!-- 定義一個 ros2_control 系統接口,名字為 mybotGazeboSystem -->
<!-- type="system" 表示這是一個系統級控制器,而不是單個傳感器或執行器 -->
<ros2_control name="mybotGazeboSystem" type="system">
<!-- 指定使用的硬件接口插件 -->
<!-- gazebo_ros2_control/GazeboSystem 表示這是一個 Gazebo 模擬的硬件系統 -->
<hardware>
<plugin>gazebo_ros2_control/GazeboSystem</plugin>
</hardware>
<!-- ========== 左輪關節控制定義 ========== -->
<joint name="wheel_left_joint">
<!-- 定義速度控制接口(常用于差速驅動機器人) -->
<command_interface name="velocity">
<param name="min">-1</param> <!-- 最小速度(單位:rad/s) -->
<param name="max">1</param> <!-- 最大速度 -->
</command_interface>
<!-- 定義力矩(扭矩)控制接口 -->
<command_interface name="effort">
<param name="min">-0.1</param>
<param name="max">0.1</param>
</command_interface>
<!-- 定義狀態接口,用于反饋輪子的實際狀態 -->
<state_interface name="position" /> <!-- 輪子位置(角度) -->
<state_interface name="velocity" /> <!-- 輪子速度 -->
<state_interface name="effort" /> <!-- 輪子受力情況 -->
</joint>
<!-- ========== 右輪關節控制定義 ========== -->
<joint name="wheel_right_joint">
<command_interface name="velocity">
<param name="min">-1</param>
<param name="max">1</param>
</command_interface>
<command_interface name="effort">
<param name="min">-0.1</param>
<param name="max">0.1</param>
</command_interface>
<state_interface name="position" />
<state_interface name="velocity" />
<state_interface name="effort" />
</joint>
</ros2_control>
<!-- ==========================
Gazebo ros2_control 插件定義
========================== -->
<!-- 此插件將 ros2_control 框架與 Gazebo 仿真世界連接起來 -->
<gazebo>
<plugin filename="libgazebo_ros2_control.so" name="gazebo_ros2_control">
<!-- 指定控制器配置文件(yaml)路徑 -->
<!-- 該文件定義了哪些控制器(如 diff_drive_controller)會被加載 -->
<parameters>$(find robot_description)/config/mybot_ros2_controller.yaml</parameters>
<!-- ROS2 remapping,用于話題名映射 -->
<ros>
<!-- 將控制器的 /cmd_vel_unstamped 重映射到通用的 /cmd_vel -->
<remapping>/mybot_diff_drive_controller/cmd_vel_unstamped:=/cmd_vel</remapping>
<!-- 將控制器發布的 /odom 重映射為全局的 /odom -->
<remapping>/mybot_diff_drive_controller/odom:=/odom</remapping>
</ros>
</plugin>
</gazebo>
</xacro:macro>
</robot>
編寫配置文件,配置 Controller Manager、硬件組件、控制器、廣播器 的參數
# ==============================================================================
# 整體說明:ROS 2 ros2_control 控制器配置文件(適用于 mybot 差速驅動機器人)
# 核心功能:實現 "cmd_vel 速度指令 → 差速解算 → 關節力矩驅動 → odom/關節狀態反饋" 全鏈路
# 依賴組件:需配合 URDF/SDF 模型(關節名需與配置一致)、Gazebo 仿真(use_sim_time=true)
# ==============================================================================
# ------------------------------
# 1. 全局控制器管理器(核心統籌模塊)
# 作用:管理所有控制器的加載、啟動/停止,配置全局控制參數
# ------------------------------
controller_manager:
ros__parameters:
# 控制器更新頻率(100Hz):每 10ms 計算一次控制信號,頻率越高控制越平滑(需平衡資源占用)
update_rate: 100 # Hz
# 使用仿真時間(true=啟用):與 Gazebo 仿真配合必設,讀取 Gazebo 發布的 /clock 話題時間,避免時間同步錯誤
use_sim_time: true
# 關節狀態廣播器:負責讀取關節狀態,發布 /joint_states 話題(RViz2 可視化關節運動需依賴)
mybot_joint_state_broadcaster:
type: joint_state_broadcaster/JointStateBroadcaster # 官方狀態廣播器插件
use_sim_time: true # 廣播器也使用仿真時間,與全局一致
# 關節力控控制器:底層執行器,接收差速控制器指令,通過力矩驅動車輪關節
mybot_effort_controller:
type: effort_controllers/JointGroupEffortController # 官方關節組力控插件(控制力矩/力)
# 差速驅動控制器:核心解算模塊,接收 cmd_vel 指令,解算為車輪控制信號,同時發布 odom 里程計
mybot_diff_drive_controller:
type: diff_drive_controller/DiffDriveController # 官方差速驅動插件(適配兩輪差速機器人)
# ------------------------------
# 2. 關節力控控制器配置(底層驅動模塊)
# 作用:直接與車輪關節交互,接收上層差速控制器的力矩指令,驅動關節轉動并反饋狀態
# ------------------------------
mybot_effort_controller:
ros__parameters:
# 要控制的車輪關節名稱列表:必須與 URDF/SDF 模型中車輪關節名完全一致!
joints:
- wheel_left_joint # 左車輪關節名
- wheel_right_joint # 右車輪關節名(注意:原配置中關節名大小寫一致,需與模型匹配)
# 控制接口類型:effort=力矩/力控制(適合仿真或帶力矩傳感器的真實機器人)
command_interfaces:
- effort
# 狀態反饋接口:需要讀取的關節狀態(用于閉環控制和狀態發布)
state_interfaces:
- position # 關節位置(車輪轉動角度,單位:弧度)
- velocity # 關節速度(車輪轉動角速度,單位:rad/s)
- effort # 關節受力/力矩(反饋實際驅動扭矩)
# ------------------------------
# 3. 差速驅動控制器配置(核心邏輯模塊)
# 作用:1. 解算 cmd_vel 為車輪速度/力矩;2. 計算機器人里程計(odom);3. 發布 TF 變換
# ------------------------------
mybot_diff_drive_controller:
ros__parameters:
# 左右車輪關節名稱映射:指定哪幾個關節屬于左/右輪(與上面 joints 配置一致)
left_wheel_names: ["wheel_left_joint"] # 左車輪關節(單輪差速,列表形式支持多輪)
right_wheel_names: ["wheel_right_joint"] # 右車輪關節
# 機器人核心物理參數(必須與實際/仿真模型尺寸一致,否則運動解算會出錯!)
wheel_separation: 0.17 # 左右車輪中心間距(單位:米),差速轉向解算的關鍵參數
wheel_radius: 0.032 # 車輪半徑(單位:米),用于速度→車輪轉速、里程計位置計算
# 物理參數修正系數(默認1.0,用于校準實際與理論的偏差)
wheel_separation_multiplier: 1.0 # 輪距修正系數
left_wheel_radius_multiplier: 1.0 # 左輪半徑修正系數
right_wheel_radius_multiplier: 1.0 # 右輪半徑修正系數
# 里程計與狀態發布參數
publish_rate: 50.0 # /odom 里程計和關節狀態的發布頻率(50Hz,平衡實時性和資源)
odom_frame_id: odom # 里程計參考坐標系(與 /odom 話題的 frame_id 一致,固定為 "odom")
base_frame_id: base_footprint # 機器人底座坐標系(與 TF 樹中的 base_footprint 對應,RViz2 可視化參考)
# 里程計位置不確定性協方差(仿真場景用,表征定位誤差,實際機器人可根據傳感器精度調整)
pose_covariance_diagonal : [0.001, 0.001, 0.0, 0.0, 0.0, 0.01]
# 里程計速度不確定性協方差(同上,仿真場景用)
twist_covariance_diagonal: [0.001, 0.0, 0.0, 0.0, 0.0, 0.01]
# 控制模式配置
open_loop: true # 開環控制(true=啟用):無需車輪編碼器反饋,適合 Gazebo 仿真(無打滑誤差);真實機器人建議設 false(需編碼器閉環校正)
enable_odom_tf: true # 自動發布 TF 變換(odom → base_footprint):RViz2 可視化機器人位置必須啟用
# 安全與指令處理配置
cmd_vel_timeout: 0.5 # cmd_vel 指令超時時間(單位:秒):0.5秒內未收到新指令,自動停止車輪運動(避免機器人失控)
# publish_limited_velocity: true # (注釋禁用)發布限制后的速度(用于速度限流場景,需配合限流參數)
use_stamped_vel: false # 是否使用帶時間戳的 cmd_vel 指令(false=簡化模式,直接訂閱 /cmd_vel;true=訂閱 /cmd_vel_stamped)
# velocity_rolling_window_size: 10 # (注釋禁用)速度滾動窗口大小(用于計算平均速度,優化反饋平滑度)
差速控制是 “指揮官”:負責規劃機器人整體運動,告訴底層 “該跑多快、往哪轉”,力控是 “執行者”:負責落實指揮官的命令,告訴電機 “該用多大勁轉”,啟動順序:廣播器→力控→差速
| 對比維度 |
差速控制(diff_drive_controller) |
力控(effort_controllers) |
| 核心目標 |
解決 “機器人整體怎么運動”(路徑、速度、定位) |
解決 “執行器(車輪)怎么發力”(力矩、電流、驅動) |
| 控制對象 |
機器人整體(base_footprint 坐標系) |
單個 / 多個執行器(車輪關節) |
| 依賴關系 |
依賴底層力控控制器(或速度控制器)完成執行 |
不依賴上層控制器,只需要接收指令即可 |
| 在 ros2_control 中的角色 |
上層 “決策型控制器”(無直接硬件接口,需通過底層控制器交互) |
底層 “執行型控制器”(直接綁定硬件接口,與 URDF 關節接口匹配) |
| 關鍵參數 |
機器人物理尺寸(輪距、輪徑)、里程計配置、速度限制 |
PID 增益(力控精度)、力矩范圍(最大 / 最小發力) |
| 輸入 / 輸出 |
輸入:/cmd_vel(線速度 / 角速度);輸出:車輪目標指令(速度 / 力矩)+ /odom |
輸入:上層的車輪目標指令(力矩);輸出:車輪實際狀態(位置 / 速度 / 力) |
| 適用場景 |
所有差速輪移動機器人(小車、AGV 等) |
需要精準力矩控制的場景(仿真、帶力矩傳感器的真實電機、機械臂關節) |
<?xml version="1.0"?>
<robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="mybot">
<!-- ========== Include 部分 ========== -->
<xacro:include filename="$(find robot_description)/urdf/base.urdf.xacro" />
<xacro:include filename="$(find robot_description)/urdf/mybot.ros2_control.xacro" />
<xacro:include filename="$(find robot_description)/urdf/sensor/camera.urdf.xacro" />
<xacro:include filename="$(find robot_description)/urdf/sensor/imu.urdf.xacro" />
<xacro:include filename="$(find robot_description)/urdf/sensor/laser.urdf.xacro" />
<xacro:include filename="$(find robot_description)/urdf/actuator/wheel.urdf.xacro" />
<xacro:include filename="$(find robot_description)/urdf/actuator/caster.urdf.xacro" />
<!-- <xacro:include filename="$(find robot_description)/urdf/plugins/gazebo_control_plugin.xacro" /> -->
<xacro:include filename="$(find robot_description)/urdf/plugins/gazebo_sensor_plugin.xacro" />
<!-- ========== Base ========== -->
<xacro:base_xacro
base_name="base_link"
xyz="0 0 0"
rpy="0 0 0"
radius="0.1"
length="0.12"
base_color="White"
rgba="1 1 1 0.5" />
<!-- ========== IMU ========== -->
<xacro:imu_xacro
imu_name="imu_link"
xyz="0 0 0"
rpy="0 0 0"
size="0.02 0.02 0.02"
imu_color="Black"
rgba="0 0 0 0.5"
joint_name="imu_joint"
joint_type="fixed"
parent_name="base_link"
child_name="imu_link"
joint_xyz="0 0 0.02"
joint_rpy="0 0 0" />
<!-- ========== Camera ========== -->
<xacro:camera_xacro
camera_name="camera_link"
xyz="0 0 0"
rpy="0 0 0"
size="0.02 0.1 0.02"
camera_color="Black"
rgba="0 0 0 0.5"
joint_name="camera_joint"
joint_type="fixed"
parent_name="base_link"
child_name="camera_link"
joint_xyz="0.1 0 0.075"
joint_rpy="0 0 0"/>
<!-- ========== Laser ========== -->
<xacro:laser_xacro
laser_name="laser"
parent_name="base_link"
joint1_name="laser_stand_joint"
joint1_type="fixed"
joint1_xyz="0 0 0.1"
joint1_rpy="0 0 0"
joint2_name="laser_mount_joint"
joint2_type="fixed"
joint2_xyz="0 0 0.05"
joint2_rpy="0 0 0"
cylinder_xyz="0 0 0"
cylinder_rpy="0 0 0"
cylinder_radius="0.01"
cylinder_length="0.1"
cylinder_color="Black"
cylinder_rgba="0 0 0 1.0"
sensor_xyz="0 0 0"
sensor_rpy="0 0 0"
sensor_radius="0.02"
sensor_length="0.02"
sensor_color="Black"
sensor_rgba="0 0 0 1.0" />
<!-- ========== Wheel ========== -->
<xacro:wheel_xacro
wheel_name="wheel_left"
xyz="0 0 0"
rpy="1.57079 0 0"
radius="0.032"
length="0.04"
wheel_color="Yellow"
rgba="1 1 0 0.8"
joint_name="wheel_left_joint"
joint_type="continuous"
parent_name="base_link"
child_name="wheel_left"
joint_xyz="0 0.1 -0.06"
joint_rpy="0 0 0" />
<xacro:wheel_xacro
wheel_name="wheel_right"
xyz="0 0 0"
rpy="1.57079 0 0"
radius="0.032"
length="0.04"
wheel_color="Yellow"
rgba="1 1 0 0.8"
joint_name="wheel_right_joint"
joint_type="continuous"
parent_name="base_link"
child_name="wheel_right"
joint_xyz="0 -0.1 -0.06"
joint_rpy="0 0 0" />
<!-- ========== Caster ========== -->
<xacro:caster_xacro
caster_name="caster_front"
xyz="0 0 0"
rpy="0 0 0"
radius="0.016"
caster_color="Yellow"
rgba="1 1 0 0.8"
joint_name="caster_front_joint"
joint_type="fixed"
parent_name="base_link"
child_name="caster_front"
joint_xyz="0.08 0 -0.076"
joint_rpy="0 0 0" />
<xacro:caster_xacro
caster_name="caster_back"
xyz="0 0 0"
rpy="0 0 0"
radius="0.016"
caster_color="Yellow"
rgba="1 1 0 0.8"
joint_name="caster_back_joint"
joint_type="fixed"
parent_name="base_link"
child_name="caster_back"
joint_xyz="-0.08 0 -0.076"
joint_rpy="0 0 0" />
<!-- <xacro:gazebo_control_plugin /> -->
<xacro:gazebo_sensor_plugin />
<xacro:mybot_ros2_control />
</robot>
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument,IncludeLaunchDescription,ExecuteProcess,RegisterEventHandler
from launch.substitutions import Command, LaunchConfiguration
from launch.event_handlers import OnProcessExit
from launch_ros.actions import Node
from launch_ros.descriptions import ParameterValue
from launch.launch_description_sources import PythonLaunchDescriptionSource
from ament_index_python.packages import get_package_share_directory
import os
def generate_launch_description():
urdf_package_path = get_package_share_directory('robot_description')
default_xacro_path = os.path.join(urdf_package_path,'urdf','mybot.urdf.xacro')
default_gazebo_path = os.path.join(urdf_package_path,'world','custom_room.world')
action_declare_arg_mode_path=DeclareLaunchArgument('model',default_value=str(default_xacro_path))
command_result = Command(['xacro ',LaunchConfiguration('model')])
robot_description_value = ParameterValue(command_result,value_type=str)
robot_state_publisher = Node(
package='robot_state_publisher',
executable='robot_state_publisher',
parameters=[{'robot_description':robot_description_value}],
)
# 通過 IncludeLaunchDescription 包含另外一個 launch 文件
action_launch_gazebo = IncludeLaunchDescription(
PythonLaunchDescriptionSource([get_package_share_directory('gazebo_ros'),'/launch','/gazebo.launch.py']),
launch_arguments=[('world',default_gazebo_path),('verbose','true')],
)
action_spawn_entity = Node(
package='gazebo_ros',
executable='spawn_entity.py',
arguments=['-topic','/robot_description','-entity','mybot'],
)
# ExecuteProcess 是 ROS 2 Launch 系統中的一個 “命令行執行工具”,核心作用是 在 Launch 啟動流程中,直接調用操作系統的命令行指令
# 加載關節狀態廣播器(必須第一個激活,負責發布/joint_states話題)
action_load_joint_state_controller = ExecuteProcess(cmd='ros2 control load_controller mybot_joint_state_broadcaster --set-state active'.split(' '))
# 加載力控控制器(底層執行器,驅動車輪關節,依賴廣播器)
action_load_effort_controller =ExecuteProcess(cmd='ros2 control load_controller mybot_effort_controller --set-state active'.split(' '))
# 加載差速驅動控制器(上層決策器,解算cmd_vel,依賴力控控制器)
action_load_diff_drive_controller =ExecuteProcess(cmd='ros2 control load_controller mybot_diff_drive_controller --set-state active'.split(' '))
return LaunchDescription(
[action_declare_arg_mode_path,
robot_state_publisher,
action_launch_gazebo,
action_spawn_entity,
# 事件監聽器,當某個事件觸發后開始執行等待該事件觸發的事件
RegisterEventHandler(event_handler=OnProcessExit(target_action=action_spawn_entity,on_exit=[action_load_joint_state_controller],)),
RegisterEventHandler(event_handler=OnProcessExit(target_action=action_load_joint_state_controller,on_exit=[action_load_effort_controller],)),
RegisterEventHandler(event_handler=OnProcessExit(target_action=action_load_effort_controller,on_exit=[action_load_diff_drive_controller],)),
]
)