ROS2 URDF建模
参考博客:https://blog.csdn.net/qq_51482778/article/details/137111113
新建工程drdf
进入src文件中,建立一个新的功能包:robot_car
ros2 pkg create robot_car --build-type ament_python
建立并编写URDF文件
cd robot_car && mkdir urdf
cd urdf
touch fishbot_base.urdf
完成后的robot_car文件结构如下:
robot_car ├── package.xml ├── resource │ └── robot_car ├── robot_car │ └── __init__.py ├── setup.cfg ├── setup.py ├── test │ ├── test_copyright.py │ ├── test_flake8.py │ └── test_pep257.py └── urdf └── fishbot_base.urdf
|
编辑fishbot_base.urdf
添加下面代码
<?xml version="1.0"?>
<robot name="fishbot">
<!-- base link -->
<link name="base_link">
<visual>
<origin xyz="0 0 0.0" rpy="0 0 0"/>
<geometry>
<cylinder length="0.12" radius="0.10"/>
</geometry>
</visual>
</link>
<!-- laser link -->
<link name="laser_link">
<visual>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<cylinder length="0.02" radius="0.02"/>
</geometry>
<material name="black">
<color rgba="0.0 0.0 0.0 0.8" />
</material>
</visual>
</link>
<!-- laser joint -->
<joint name="laser_joint" type="fixed">
<parent link="base_link" />
<child link="laser_link" />
<origin xyz="0 0 0.075" />
</joint>
</robot>
建立并编写launch文件
在目录src/robot_car下创建launch文件夹并在其下新建display_rviz2.launch.py文件
mkdir launch
touch display_rviz2.launch.py
完成后的robot_car文件结构如下:
robot_car ├── launch │ └── display_rviz2.launch.py ├── package.xml ├── resource │ └── robot_car ├── robot_car │ └── __init__.py ├── setup.cfg ├── setup.py ├── test │ ├── test_copyright.py │ ├── test_flake8.py │ └── test_pep257.py └── urdf └── fishbot_base.urdf |
编辑launch文件
import os
from launch import LaunchDescription
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node
from launch_ros.substitutions import FindPackageShare
def generate_launch_description():
package_name = 'robot_car'
urdf_name = "fishbot_base.urdf"
ld = LaunchDescription()
pkg_share = FindPackageShare(package=package_name).find(package_name)
urdf_model_path = os.path.join(pkg_share, f'urdf/{urdf_name}')
robot_state_publisher_node = Node(
package='robot_state_publisher',
executable='robot_state_publisher',
arguments=[urdf_model_path]
)
joint_state_publisher_node = Node(
package='joint_state_publisher_gui',
executable='joint_state_publisher_gui',
name='joint_state_publisher_gui',
arguments=[urdf_model_path]
)
rviz2_node = Node(
package='rviz2',
executable='rviz2',
name='rviz2',
output='screen',
)
ld.add_action(robot_state_publisher_node)
ld.add_action(joint_state_publisher_node)
ld.add_action(rviz2_node)
return ld
1、generate_launch_description() 函数是入口点,用于生成 LaunchDescription 对象,描述了启动的节点及其参数。
2、FindPackageShare 类用于查找指定包的共享目录,以便获取该包中的资源文件。在这里,它用于获取机器人描述包(fishbot_description)的共享目录路径。
3、LaunchDescription 对象用于存储要启动的节点及其参数。
其中涉及三个节点
1、joint_state_publisher_gui 负责发布机器人关节数据信息,通过joint_states话题发布
2、robot_state_publisher_node负责发布机器人模型信息robot_description,并将joint_states数据转换tf信息发布
3、rviz2_node负责显示机器人的信息
attention
这里我们用到了joint_state_publisher_gui和robot_state_publisher两个包,如果你的系统没有安装这两个包,可以手动安装:
sudo apt install ros-$ROS_DISTRO-joint-state-publisher-gui ros-$ROS_DISTRO-robot-state-publisher
或者
apt install ros-humble-joint-state-publisher-gui
apt install ros-humble-robot-state-publisher
joint_state_publisher_gui,还有一个兄弟叫做joint_state_publisher 两者区别在于joint_state_publisher_gui运行起来会跳出一个界面,通过界面可以操作URDF中能动的关节 |
修改setup.py
导入头文件
from glob import glob
import os
更改data_file列表:
data_files 列表包含了要安装到系统中的其他文件和目录,以及它们的安装路径。在这里需要包含了 resource_index/packages、package.xml、launch 目录下的所有 .launch.py 文件、urdf 目录下的所有文件等。
from setuptools import setup
from glob import glob
import os
package_name = 'robot_car'
setup(
name=package_name,
version='0.0.0',
packages=[package_name],
data_files=[
('share/ament_index/resource_index/packages',
['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
(os.path.join('share', package_name, 'launch'), glob('launch/*.launch.py')),
(os.path.join('share', package_name, 'urdf'), glob('urdf/**')),
],
install_requires=['setuptools'],
zip_safe=True,
maintainer='grb',
maintainer_email='grb@todo.todo',
description='TODO: Package description',
license='TODO: License declaration',
tests_require=['pytest'],
entry_points={
'console_scripts': [
],
},
)
编译测试
回到工作空间,编译
colcon build
或者
colcon build --packages-select robot_car

运行测试
source install/setup.bash
ros2 launch robot_car display_rviz2.launch.py
运行起来之后,需要做一些配置
1、设置Global Options -> Fixed Frame -> base_link
2、设置Grid->Reference Frame-> base_link
3、添加RobotModel模块->Description Topic -> /robot_description
4、可以通过添加tf查看link之间的坐标关系

然后放大中间的那一个小红点,可以看到URDF所描述的机器人了

博主自己整理的差速轮小车:带两个主动轮、一个万向轮、一个激光雷达、一个IMU,文件名:robot_car.urdf
<?xml version="1.0" ?>
<robot name="robot_car">
<!-- Robot Footprint -->
<!-- 虚拟根坐标系 base_footprint -->
<link name="base_footprint"/>
<!-- 固定连接 base_joint-->
<joint name="base_joint" type="fixed">
<parent link="base_footprint"/>
<!-- 机器人实际基座 base_link-->
<child link="base_link"/>
<origin xyz="0.0 0.0 0.076" rpy="0 0 0"/>
</joint>
<!-- 机器人基座(base_link) -->
<link name="base_link">
<visual>
<!-- 原点位于基座坐标系后方 x = -0.18, y = 187 处(xyz参数) -->
<origin xyz="-0.18 0 0.187" rpy="0 0 0" />
<geometry>
<!-- 长方体(长0.565m × 宽0.44m × 高0.222m) -->
<box size="0.565 0.44 0.222"/>
</geometry>
<material name="white">
<!-- 纯白色(RGBA: 1,1,1,1) -->
<color rgba="1 1 1 0.8"/>
</material>
</visual>
<!-- 碰撞属性:collision -->
<collision>
<!-- origin,表示碰撞体的中心位姿 -->
<origin xyz="-0.18 0 0.187" rpy="0 0 0"/>
<!-- geometry,用于表示用于碰撞检测的几何形状 -->
<geometry>
<box size="0.565 0.44 0.222"/>
</geometry>
<!-- material,可选的,描述碰撞几何体的材料(这个设置可以在gazebo仿真时通过view选项看到碰撞包围体的形状) -->
<material name="blue">
<color rgba="1 1 1 0.8" />
</material>
</collision>
<!-- 惯性属性:Inertial -->
<inertial>
<!-- 质量:22.54千克 -->
<mass value="22.54"/>
<!-- 惯性矩阵:ixx = iyy = 0.0122666 kg·m²(绕X/Y轴的转动惯量) -->
<!-- 惯性矩阵:izz = 0.02 kg·m²(绕Z轴的转动惯量) -->
<inertia ixx="0.0122666" ixy="0" ixz="0" iyy="0.0122666" iyz="0" izz="0.02"/>
</inertial>
</link>
<!-- 左轮结构 left_wheel_joint + left_wheel_link -->
<link name="left_wheel_link">
<visual>
<origin xyz="0 0 0" rpy="0 0 0" />
<geometry>
<!-- 轮子参数:圆柱体(半径0.076m/(152mm/2),厚度0.046m) -->
<cylinder radius="0.076" length = "0.046"/>
</geometry>
<material name="black">
<!-- 颜色:纯黑色 -->
<color rgba="0 0 0 1"/>
</material>
</visual>
<!-- 碰撞属性:collision -->
<collision>
<!-- origin,表示碰撞体的中心位姿 -->
<origin xyz="0 0 0" rpy="0 0 0"/>
<!-- geometry,用于表示用于碰撞检测的几何形状 -->
<geometry>
<cylinder radius="0.076" length = "0.046"/>
</geometry>
<material name="black">
<color rgba="0 0 0 1" />
</material>
</collision>
<inertial>
<!-- 质量:0.2千克 -->
<mass value="0.2"/>
<inertia ixx="0.000190416666667" ixy="0" ixz="0" iyy="0.0001904" iyz="0" izz="0.00036"/>
</inertial>
</link>
<!-- 关节类型:连续旋转关节 continuous -->
<joint name="left_wheel_joint" type="continuous">
<parent link="base_link"/>
<child link="left_wheel_link"/>
<!-- 安装位置:基座坐标系右侧(Y轴+0.2495m方向) -->
<origin xyz="0 0.248 0.076" rpy="-1.57 0 0"/>
<!-- 旋转轴:Y轴(axis xyz="0 1 0") -->
<axis xyz="0 0 1"/>
</joint>
<!-- 右轮结构(right_wheel_joint + right_wheel_link) -->
<link name="right_wheel_link">
<visual>
<origin xyz="0 0 0" rpy="0 0 0" />
<geometry>
<cylinder radius="0.076" length = "0.046"/>
</geometry>
<material name="black">
<color rgba="0 0 0 1"/>
</material>
</visual>
<!-- 碰撞属性:collision -->
<collision>
<!-- origin,表示碰撞体的中心位姿 -->
<origin xyz="0 0 0" rpy="0 0 0"/>
<!-- geometry,用于表示用于碰撞检测的几何形状 -->
<geometry>
<cylinder radius="0.076" length = "0.046"/>
</geometry>
<material name="black">
<color rgba="0 0 0 1" />
</material>
</collision>
<inertial>
<!-- 质量:0.2千克 -->
<mass value="0.2"/>
<inertia ixx="0.000190416666667" ixy="0" ixz="0" iyy="0.0001904" iyz="0" izz="0.00036"/>
</inertial>
</link>
<joint name="right_wheel_joint" type="continuous">
<parent link="base_link"/>
<child link="right_wheel_link"/>
<!-- 结构与左轮对称,安装位置在Y轴-0.2495m方向 -->
<origin xyz="0 -0.248 0.076" rpy="-1.57 0 0"/>
<axis xyz="0 0 1"/>
</joint>
<!-- 前轮结构(front_wheel_joint + front_wheel_link) -->
<link name="front_wheel_link">
<visual>
<origin xyz="0 0 0" rpy="0 0 0" />
<geometry>
<cylinder radius="0.076" length = "0.046"/>
</geometry>
<material name="black">
<color rgba="0 0 0 1"/>
</material>
</visual>
<collision>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<cylinder radius="0.076" length = "0.046"/>
</geometry>
<material name="black">
<color rgba="0 0 0 1"/>
</material>
</collision>
<inertial>
<mass value="0.02"/>
<inertia ixx="0.000190416666667" ixy="0" ixz="0" iyy="0.0001904" iyz="0" izz="0.00036"/>
</inertial>
</link>
<joint name="front_wheel_joint" type="continuous">
<parent link="base_link"/>
<child link="front_wheel_link"/>
<!-- 安装位置:基座坐标系前方(X轴+0.23m方向) -->
<origin xyz="-0.3 0 0.076" rpy="-1.57 0 0"/>
<axis xyz="0 0 1"/>
</joint>
<!-- 激光雷达传感器(laser_link) -->
<link name="laser_link">
<visual>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<!-- 高度:0.02米(2厘米)半径:0.02米(2厘米) -->
<cylinder length="0.02" radius="0.02"/>
</geometry>
<material name="black">
<!-- 黑色,透明度:0.5(半透明效果) -->
<color rgba="0.0 0.0 0.0 0.5" />
</material>
</visual>
<collision>
<origin xyz="0 0 0" rpy="0 0 0"/>
<geometry>
<cylinder length="0.02" radius="0.02"/>
</geometry>
<material name="black">
<color rgba="0.0 0.0 0.0 0.5" />
</material>
</collision>
<inertial>
<mass value="0.1"/>
<inertia ixx="0.000190416666667" ixy="0" ixz="0" iyy="0.0001904" iyz="0" izz="0.00036"/>
</inertial>
</link>
<!-- 关节类型:fixed(固定连接) -->
<joint name="laser_joint" type="fixed">
<parent link="base_link" />
<child link="laser_link" />
<!-- 将激光雷达安装在基座上方*米处 0.308 = 0.222+0.076+0.01 -->
<origin xyz="0.076 0 0.308" />
</joint>
<!-- 惯导 IMU -->
<link name="imu_link">
<visual>
<origin xyz="0 0 0.0" rpy="0 0 0"/>
<geometry>
<box size="0.02 0.02 0.02"/>
</geometry>
</visual>
<collision>
<origin xyz="0 0 0.0" rpy="0 0 0"/>
<geometry>
<box size="0.02 0.02 0.02"/>
</geometry>
</collision>
<inertial>
<mass value="0.1"/>
<inertia ixx="0.000190416666667" ixy="0" ixz="0" iyy="0.0001904" iyz="0" izz="0.00036"/>
</inertial>
</link>
<joint name="imu_joint" type="fixed">
<parent link="base_link" />
<child link="imu_link" />
<origin xyz="0 0 0.187" />
</joint>
<!-- gazebo 前轮 -->
<gazebo reference="front_wheel_link">
<!-- 材质定义 Gazebo/Black:Gazebo内置的预定义材质,使部件在仿真中显示为黑色。 -->
<material>Gazebo/Black</material>
</gazebo>
<!-- 物理属性 -->
<gazebo reference="front_wheel_link">
<!--沿接触面法线方向(通常为垂直方向)的摩擦系数 -->
<mu1 value="0.0"/>
<!-- 沿接触面切线方向(水平方向)的摩擦系数。 -->
<mu2 value="0.0"/>
<!-- 接触刚度(Stiffness),值越大,碰撞时形变越小,物体越“硬” -->
<kp value="1000000.0" />
<!-- 阻尼系数(Damping),值越大,碰撞能量耗散越快,模拟“弹性”效果 -->
<kd value="10.0" />
<!-- 若取消注释,fdir1会定义摩擦力的主方向(此处为Z轴方向),通常用于各向异性摩擦(如轮胎的纵向/横向摩擦差异) -->
<!-- <fdir1 value="0 0 1"/> -->
</gazebo>
<!-- ROS参数定义 -->
<gazebo>
<!-- 差速驱动插件 (diff_drive) -->
<!-- 实现两轮差速驱动机器人的运动学仿真,将ROS的cmd_vel指令转换为车轮运动,并发布里程计(odometry)数据 -->
<plugin name='diff_drive' filename='libgazebo_ros_diff_drive.so'>
<!-- ROS集成 -->
<ros>
<!-- ROS命名空间(此处为根命名空间) -->
<namespace>/</namespace>
<!-- remapping 将插件的默认话题重映射为ROS标准话题: -->
<!-- 接收速度指令的话题。 -->
<remapping>cmd_vel:=cmd_vel</remapping>
<!-- 发布里程计数据的话题 -->
<remapping>odom:=odom</remapping>
</ros>
<update_rate>30</update_rate>
<!-- wheels -->
<!-- <left_joint>left_wheel_joint</left_joint> -->
<!-- <right_joint>right_wheel_joint</right_joint> -->
<left_joint>left_wheel_joint</left_joint>
<right_joint>right_wheel_joint</right_joint>
<!-- 运动学参数 kinematics -->
<!-- 两轮间距(单位:米),影响转弯半径计算 -->
<wheel_separation>0.2</wheel_separation>
<!-- 车轮直径(单位:米),用于计算线速度。 -->
<wheel_diameter>0.065</wheel_diameter>
<!-- 物理限制 limits -->
<!-- 车轮最大扭矩(N·m),限制电机输出。 -->
<max_wheel_torque>20</max_wheel_torque>
<!-- 车轮最大加速度(rad/s²),控制加速平滑性。 -->
<max_wheel_acceleration>1.0</max_wheel_acceleration>
<!-- 输出配置 output -->
<!-- 发布里程计数据(/odom话题) -->
<publish_odom>true</publish_odom>
<!-- 发布里程计的TF变换(odom → base_footprint) -->
<publish_odom_tf>true</publish_odom_tf>
<!-- 不发布车轮单独的TF -->
<publish_wheel_tf>false</publish_wheel_tf>
<!-- 里程计参考坐标系 -->
<odometry_frame>odom</odometry_frame>
<!-- 机器人基座坐标系 -->
<robot_base_frame>base_footprint</robot_base_frame>
</plugin>
<!-- 关节状态发布插件 fishbot_joint_state -->
<plugin name="fishbot_joint_state" filename="libgazebo_ros_joint_state_publisher.so">
<ros>
<!-- 将插件默认输出重映射为ROS标准话题/joint_states -->
<remapping>~/out:=joint_states</remapping>
</ros>
<!-- 以30Hz频率发布关节状态 -->
<update_rate>30</update_rate>
<!-- 指定需要监控的关节名称(此处为左右车轮关节) -->
<joint_name>right_wheel_joint</joint_name>
<joint_name>left_wheel_joint</joint_name>
</plugin>
</gazebo>
<!-- IMU 传感器基础配置 -->
<gazebo reference="imu_link">
<sensor name="imu_sensor" type="imu">
<plugin filename="libgazebo_ros_imu_sensor.so" name="imu_plugin">
<ros>
<namespace>/</namespace>
<!-- 将插件默认输出话题重映射为/imu -->
<remapping>~/out:=imu</remapping>
</ros>
<!-- 禁用初始方向作为参考,直接输出世界坐标系下的数据。 -->
<initial_orientation_as_reference>false</initial_orientation_as_reference>
</plugin>
<!-- 传感器持续运行,无需外部触发 -->
<always_on>true</always_on>
<!-- 以100Hz频率更新数据(高频率适合动态仿真) -->
<update_rate>100</update_rate>
<!-- 在Gazebo界面中显示传感器可视化模型(如坐标轴)。 -->
<visualize>true</visualize>
<imu>
<!-- 角速度(陀螺仪)噪声 -->
<angular_velocity>
<x>
<noise type="gaussian">
<!-- 模拟随机噪声(均值0,标准差2e-4 rad/s) -->
<mean>0.0</mean>
<stddev>2e-4</stddev>
<!-- 模拟传感器零偏(均值7.5e-6 rad/s,标准差8e-7 rad/s),反映真实硬件的长期漂移 -->
<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>
<!-- 线加速度(加速度计)噪声 -->
<linear_acceleration>
<x>
<noise type="gaussian">
<!-- 模拟随机噪声(均值0,标准差1.7e-2 m/s²) -->
<mean>0.0</mean>
<stddev>1.7e-2</stddev>
<!-- 模拟加速度计零偏(均值0.1 m/s²,标准差0.001 m/s²),反映真实硬件的静态误差。 -->
<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>
<!-- 激光雷达材质(laser_link) -->
<gazebo reference="laser_link">
<material>Gazebo/Black</material>
</gazebo>
<!-- 激光雷达基础配置 -->
<gazebo reference="laser_link">
<sensor name="laser_sensor" type="ray">
<!-- 传感器持续运行,无需外部触发。 -->
<always_on>true</always_on>
<!-- 在Gazebo界面中显示激光束的可视化效果(如扇形扫描区域)。 -->
<visualize>true</visualize>
<!-- 以5Hz频率更新数据(适用于低速场景或计算资源受限环境)。 -->
<update_rate>5</update_rate>
<!-- 传感器相对于laser_link的位姿(x=0, y=0, z=0.145,无旋转),表示传感器安装在链接上方0.145米处。 -->
<pose>0 0 0.145 0 0 0</pose>
<ray>
<!-- 水平扫描(Horizontal Scan) -->
<scan>
<horizontal>
<!-- 单次扫描的射线数量(360条射线)。 -->
<samples>360</samples>
<!-- 每条射线之间的角度间隔(1度) -->
<resolution>1.000000</resolution>
<!-- 扫描角度范围(0到6.28弧度,即0°到360°),实现全向扫描。 -->
<min_angle>0.000000</min_angle>
<max_angle>6.280000</max_angle>
</horizontal>
</scan>
<range>
<!-- 最小测量距离(0.12米),小于此距离的障碍物无法被检测。 -->
<min>0.120000</min>
<!-- 最大测量距离(3.5米),超出此距离的障碍物无法被检测。 -->
<max>3.5</max>
<!-- 距离测量精度(0.015米),影响数据离散化程度。 -->
<resolution>0.015000</resolution>
</range>
<!-- 模拟随机噪声(均值0,标准差0.01米),反映真实激光雷达的测量误差 -->
<noise>
<type>gaussian</type>
<mean>0.0</mean>
<stddev>0.01</stddev>
</noise>
</ray>
<plugin name="laserscan" filename="libgazebo_ros_ray_sensor.so">
<ros>
<!-- <namespace>/tb3</namespace> -->
<!-- 将插件默认输出话题重映射为/scan -->
<remapping>~/out:=scan</remapping>
</ros>
<!-- 数据格式为sensor_msgs/LaserScan(ROS标准激光雷达消息类型) -->
<output_type>sensor_msgs/LaserScan</output_type>
<!-- 数据坐标系为laser_link,确保与TF树一致 -->
<frame_name>laser_link</frame_name>
</plugin>
</sensor>
</gazebo>
</robot>
效果如下:
