前一节学习了launch的一些基本操作,接下来学习一下YAML,它负责这些控制器怎么工作
YAML是一种配置文件格式。ROS2常用它来写参数
最基础结构
节点名: ros__parameters: 参数名: 参数值例如我的remote_control.yaml
vt13_chassis_remote_node: ros__parameters: joy_topic: "/astra/remote/joy" publish_rate: 50.0意思是:给vt13_chassis_remote_node这个节点传入两个参数:
joy_topic="/astra/remote/joy"
publish_rate=50.0
注意两个下划线:ros__parameters
YAML基本语法:
字符串:
base_frame_id: "base_link"数字:
update_rate: 1000布尔值:
enable_odom_tf: true列表:
joints: - arm_j6_joint - arm_j5_joint - arm_j4_joint嵌套对象:
kinematics: wheels_radius: 0.076 base_frame_offset: x: 0.0 y: 0.0 theta: 0.0YAML 最重要的是缩进。缩进错了,配置含义就变了。
接下来来看我的enginner_real.yaml
controller_manager: ros__parameters: update_rate: 1000 joint_state_broadcaster: type: "joint_state_broadcaster/JointStateBroadcaster" chassis_controller: type: "mecanum_drive_controller/MecanumDriveController" arm_controller: type: "joint_trajectory_controller/JointTrajectoryController" gripper_controller: type: "forward_command_controller/ForwardCommandController" chassis_controller: ros__parameters: front_left_wheel_command_joint_name: "front_left_wheel_joint" front_right_wheel_command_joint_name: "front_right_wheel_joint" rear_right_wheel_command_joint_name: "rear_right_wheel_joint" rear_left_wheel_command_joint_name: "rear_left_wheel_joint" kinematics: wheels_radius: 0.076 sum_of_robot_center_projection_on_X_Y_axis: 0.51 base_frame_offset: x: 0.0 y: 0.0 theta: 0.0 base_frame_id: "base_link" odom_frame_id: "odom" enable_odom_tf: true use_stamped_vel: false reference_timeout: 0.5 arm_controller: ros__parameters: joints: - arm_j6_joint - arm_j5_joint - arm_j4_joint - arm_j3_joint - arm_j1_joint - arm_pitch1_joint command_interfaces: - position state_interfaces: - position - velocity allow_partial_joints_goal: true open_loop_control: false constraints: goal_time: 1.0 stopped_velocity_tolerance: 0.05 gripper_controller: ros__parameters: joints: - gripper_joint interface_name: position第一块:告诉controller_manager有哪些控制器。
controller_manager: ros__parameters: update_rate: 1000 joint_state_broadcaster: type: "joint_state_broadcaster/JointStateBroadcaster" chassis_controller: type: "mecanum_drive_controller/MecanumDriveController" arm_controller: type: "joint_trajectory_controller/JointTrajectoryController" gripper_controller: type: "forward_command_controller/ForwardCommandController"这部分不是控制参数,而是“注册控制器类型”。
可以理解成:
controller_manager,我有四个 controller: 1. joint_state_broadcaster 2. chassis_controller 3. arm_controller 4. gripper_controller 它们分别是什么类型,请按 type 加载。第二块:分别给每个控制器传参数。
chassis_controller: ros__parameters: ...arm_controller: ros__parameters: ...gripper_controller: ros__parameters: ...所以结构是:
controller_manager: ros__parameters: 控制器名字: type: "控制器类型" 控制器名字: ros__parameters: 这个控制器自己的参数这是ros2_control配置的核心模式。
YAML 只是声明,spawner 才是启动
在 YAML 里写:
chassis_controller: type: "mecanum_drive_controller/MecanumDriveController"只是告诉系统:
有一个 controller 叫 chassis_controller但它还没启动。
真正启动它的是
_spawner("chassis_controller")完整流程是:
1. launch 启动 ros2_control_node 2. ros2_control_node 读取 engineer_real.yaml 3. controller_manager 知道有哪些 controller 4. spawner 请求 controller_manager 启动 controller所以记住:
YAML 负责配置 spawner 负责启动joint_state_broadcaster 是什么
joint_state_broadcaster: type: "joint_state_broadcaster/JointStateBroadcaster"它的作用是读取所有关节状态,然后发布/joint_states。
比如:
arm_j6_joint 的 position arm_j6_joint 的 velocity front_left_wheel_joint 的 velocity这些状态会被 RViz、robot_state_publisher、上层控制逻辑使用。
它通常必须启动,否则机器人模型状态不会正常更新。
chassis_controller 是什么
底盘控制器:
chassis_controller: ros__parameters: front_left_wheel_command_joint_name: "front_left_wheel_joint" front_right_wheel_command_joint_name: "front_right_wheel_joint" rear_right_wheel_command_joint_name: "rear_right_wheel_joint" rear_left_wheel_command_joint_name: "rear_left_wheel_joint"这里告诉麦轮控制器:
四个轮子的 joint 分别叫什么这些名字必须和 xacro 里的关节名一致。
比如我的enginner_real.xacro是
<?xml version="1.0"?> <robot xmlns:xacro="http://www.ros.org/wiki/xacro" name="engineer"> <xacro:include filename="$(find tide_robot_description)/urdf/base/engineer_description_base.urdf.xacro"/> <ros2_control name="TideHardwareInterface" type="system"> <hardware> <plugin>tide_hw_interface/TideHardwareInterface</plugin> <param name="enable_virtual_control">false</param> <param name="need_calibration">false</param> <param name="transport">serial</param> <param name="serial_port">/dev/ttySTM32</param> <param name="serial_baudrate">115200</param> <param name="serial_command_period">0.02</param> </hardware> <joint name="front_left_wheel_joint"> <param name="can_bus">can0</param> <param name="tx_id">1</param> <param name="offset">0</param> <param name="motor_type">M3508</param> <command_interface name="velocity"/> <state_interface name="position"/> <state_interface name="velocity"/> <state_interface name="effort"/> </joint> <joint name="front_right_wheel_joint"> <param name="can_bus">can0</param> <param name="tx_id">2</param> <param name="offset">0</param> <param name="motor_type">M3508</param> <command_interface name="velocity"/> <state_interface name="position"/> <state_interface name="velocity"/> <state_interface name="effort"/> </joint> <joint name="rear_left_wheel_joint"> <param name="can_bus">can0</param> <param name="tx_id">3</param> <param name="offset">0</param> <param name="motor_type">M3508</param> <command_interface name="velocity"/> <state_interface name="position"/> <state_interface name="velocity"/> <state_interface name="effort"/> </joint> <joint name="rear_right_wheel_joint"> <param name="can_bus">can0</param> <param name="tx_id">4</param> <param name="offset">0</param> <param name="motor_type">M3508</param> <command_interface name="velocity"/> <state_interface name="position"/> <state_interface name="velocity"/> <state_interface name="effort"/> </joint> <joint name="arm_j6_joint"> <param name="can_bus">can1</param> <param name="tx_id">1</param> <param name="offset">0</param> <param name="motor_type">DM3510</param> <param name="kp">40.0</param> <param name="kd">2.0</param> <command_interface name="position"/> <state_interface name="position"/> <state_interface name="velocity"/> <state_interface name="effort"/> <state_interface name="temperature"/> </joint> <joint name="arm_j5_joint"> <param name="can_bus">can1</param> <param name="tx_id">3</param> <param name="offset">0</param> <param name="motor_type">DM3510</param> <param name="kp">40.0</param> <param name="kd">2.0</param> <command_interface name="position"/> <state_interface name="position"/> <state_interface name="velocity"/> <state_interface name="effort"/> <state_interface name="temperature"/> </joint> <joint name="arm_j4_joint"> <param name="can_bus">can1</param> <param name="tx_id">5</param> <param name="offset">0</param> <param name="motor_type">DM3510</param> <param name="kp">40.0</param> <param name="kd">2.0</param> <command_interface name="position"/> <state_interface name="position"/> <state_interface name="velocity"/> <state_interface name="effort"/> <state_interface name="temperature"/> </joint> <joint name="arm_j3_joint"> <param name="can_bus">can1</param> <param name="tx_id">7</param> <param name="offset">0</param> <param name="motor_type">DM</param> <param name="kp">30.0</param> <param name="kd">1.5</param> <command_interface name="position"/> <state_interface name="position"/> <state_interface name="velocity"/> <state_interface name="effort"/> <state_interface name="temperature"/> </joint> <joint name="arm_j1_joint"> <param name="can_bus">can1</param> <param name="tx_id">9</param> <param name="offset">0</param> <param name="motor_type">DM</param> <param name="kp">30.0</param> <param name="kd">1.5</param> <command_interface name="position"/> <state_interface name="position"/> <state_interface name="velocity"/> <state_interface name="effort"/> <state_interface name="temperature"/> </joint> <joint name="arm_pitch1_joint"> <param name="can_bus">can1</param> <param name="tx_id">19</param> <param name="offset">0</param> <param name="motor_type">DM</param> <param name="kp">25.0</param> <param name="kd">1.0</param> <command_interface name="position"/> <state_interface name="position"/> <state_interface name="velocity"/> <state_interface name="effort"/> <state_interface name="temperature"/> </joint> <joint name="gripper_joint"> <param name="can_bus">can1</param> <param name="tx_id">17</param> <param name="offset">0</param> <param name="motor_type">DM3510</param> <param name="kp">20.0</param> <param name="kd">0.8</param> <command_interface name="position"/> <state_interface name="position"/> <state_interface name="velocity"/> <state_interface name="effort"/> <state_interface name="temperature"/> </joint> </ros2_control> </robot>里有:
<joint name="front_left_wheel_joint">所以 YAML 里也必须写:
front_left_wheel_command_joint_name: "front_left_wheel_joint"名字错一个字符,controller 就找不到硬件接口。
底盘运动学参数
kinematics: wheels_radius: 0.076 sum_of_robot_center_projection_on_X_Y_axis: 0.51这两个参数用于麦轮运动学解算。
简单理解:
你给底盘一个 vx、vy、wz 控制器根据轮半径和车体尺寸 算出四个轮子的转速例如你发一个速度命令:
前进 1 m/smecanum_drive_controller会计算四个轮子应该转多快。
frame 参数是什么
base_frame_id: "base_link" odom_frame_id: "odom" enable_odom_tf: true这和 TF 坐标系有关。
简单理解:
odom 是世界/里程计坐标系 base_link 是机器人底盘坐标系如果:
enable_odom_tf: true控制器会发布:
odom -> base_link这表示机器人在世界里移动到了哪里。
reference_timeout 是什么
reference_timeout: 0.5意思是:
如果 0.5 秒没有收到新的速度指令,就认为控制输入超时这是安全参数。真机很重要。
比如遥控器断了,不能让机器人继续沿着上一条速度命令跑。
use_stamped_vel 是什么
use_stamped_vel: false如果是false,控制器通常接收普通速度消息:
geometry_msgs/msg/Twist如果是true,可能接收带时间戳的速度消息:
geometry_msgs/msg/TwistStamped我的遥控配置里底盘 topic 是:
vt13_chassis_remote_node: ros__parameters: joy_topic: "/astra/remote/joy" chassis_topic: "/chassis_controller/reference_unstamped" gripper_topic: "/gripper_controller/commands" motor_enable_topic: "/astra/motor_enable" # Axes come from STM32 AstraRemoteState.ch[] after normalization to [-1, 1]. # Default: ch1 forward/back, ch0 left/right, ch2 yaw. linear_x_axis: 1 linear_y_axis: 0 angular_z_axis: 2 linear_x_scale: 1.0 linear_y_scale: 1.0 angular_z_scale: 2.0 deadband: 0.05 # Button mapping from /astra/remote/joy: # 0..15 = key bits, 16 = mouse_l, 17 = mouse_r, # 18 = mode_sw 1, 19 = mode_sw 2, 20 = mode_sw 3, 21 = stop. enable_button_index: 19 disable_button_index: 21 gripper_open_button_index: 17 gripper_close_button_index: 16 gripper_open_position: 0.6 gripper_close_position: -0.6 publish_rate: 50.0 remote_timeout: 0.3chassis_topic: "/chassis_controller/reference_unstamped"这也说明现在走的是不带时间戳的速度命令。
arm_controller 是什么
arm_controller: ros__parameters: joints: - arm_j6_joint - arm_j5_joint - arm_j4_joint - arm_j3_joint - arm_j1_joint - arm_pitch1_joint这是机械臂轨迹控制器。
它控制这些关节的位置。
command_interfaces: - position意思是:
这个 controller 往硬件写 position 命令state_interfaces: - position - velocity意思是:
这个 controller 从硬件读取 position 和 velocity 状态这也必须和 xacro 匹配。
我的 xacro 里机械臂关节有:
<command_interface name="position"/> <state_interface name="position"/> <state_interface name="velocity"/>所以 YAML 里写position/velocity是合理的。
open_loop_control 是什么
真机配置:
open_loop_control: false仿真配置:
open_loop_control: true简单理解:
false:控制器更依赖真实反馈 true:控制器更相信自己发出去的命令真机有真实电机反馈,所以用:
open_loop_control: false仿真里经常简化处理,所以用:
open_loop_control: trueconstraints 是什么
constraints: goal_time: 1.0 stopped_velocity_tolerance: 0.05这是轨迹控制器的目标约束。
大致意思:
goal_time: 允许目标完成有 1 秒误差 stopped_velocity_tolerance: 认为停止时的速度容忍值如果机械臂执行轨迹时误差太大,controller 可能认为执行失败。
gripper_controller 是什么
gripper_controller: ros__parameters: joints: - gripper_joint interface_name: position这是一个简单的 forward command controller。
它不做复杂轨迹规划,只是把收到的命令直接转发给gripper_joint的position接口。
遥控节点会往这里发命令:
gripper_topic: "/gripper_controller/commands"所以链路是:
遥控器按钮 -> vt13_chassis_remote_node -> /gripper_controller/commands -> gripper_controller -> gripper_joint position怎么从零写一个 controller YAML
最小模板:
controller_manager: ros__parameters: update_rate: 100 my_controller: type: "some_controller_package/SomeController" my_controller: ros__parameters: some_param: 123对你这个工程,新增 controller 一般要三步。
第一步:在 YAML 里注册类型:
controller_manager: ros__parameters: my_controller: type: "xxx/XXXController"第二步:写它自己的参数:
my_controller: ros__parameters: joints: - some_joint interface_name: position第三步:在controller.py里启动它:
_spawner("my_controller")名字必须三处一致:
YAML controller_manager 里的名字 YAML 参数块的名字 controller.py spawner 的名字举个实际例子:如果你要新增一个 lift_controller
假设你以后有一个升降关节:
lift_joint它用 position 控制。
YAML 可以这样写:
controller_manager: ros__parameters: lift_controller: type: "forward_command_controller/ForwardCommandController" lift_controller: ros__parameters: joints: - lift_joint interface_name: position然后controller.py里加:
_spawner("lift_controller")但是还不够,xacro 里也必须有:
<joint name="lift_joint"> <command_interface name="position"/> <state_interface name="position"/> <state_interface name="velocity"/> </joint>所以新增一个可控关节时,通常要同时检查三处:
xacro:硬件有没有这个 joint 和 interface YAML:controller 有没有配置这个 joint controller.py:controller 有没有被 spawner 启动写这类代码时的顺序
以后不要上来就乱写 YAML。按这个顺序:
- 先看 xacro 里有哪些 joint。
- 看 joint 支持什么
command_interface。 - 选择合适的 controller 类型。
- 在 YAML 的
controller_manager里注册 controller。 - 给 controller 写参数。
- 在
controller.py里添加 spawner。 - 启动后用命令检查 controller 状态。
验证命令:
ros2 control list_controllers看接口:
ros2 control list_hardware_interfaces看参数:
ros2 param list /controller_manager看话题:
ros2 topic list最容易犯的错
- YAML 缩进错。
错误:
arm_controller: ros__parameters:正确:
arm_controller: ros__parameters:- controller 名字不一致。
错误:
arm_controller:但 spawner 写:
_spawner("arm_control")- joint 名字和 xacro 不一致。
错误:
front_left_wheel_joint_name: "front_left_joint"但 xacro 实际是:
front_left_wheel_joint- interface 不匹配。
YAML 写:
command_interfaces: - velocity但 xacro 只有:
<command_interface name="position"/>这样 controller 会启动失败。
控制器的type不是你随便起的名字,它来自 ROS 2 已安装的 controller 插件
可以理解成:
控制器名字: type: "提供这个控制器的包名/控制器插件类名"比如:
arm_controller: type: "joint_trajectory_controller/JointTrajectoryController"意思是:
我创建一个叫 arm_controller 的控制器, 它使用 joint_trajectory_controller 包里的 JointTrajectoryController 插件。怎么知道该用哪个类型
先不要从type开始想,应该从“我要控制什么”开始想。
| 你想做什么 | 常用 controller type |
|---|---|
发布所有关节状态/joint_states | joint_state_broadcaster/JointStateBroadcaster |
| 控制机械臂按轨迹运动 | joint_trajectory_controller/JointTrajectoryController |
| 直接给某个关节发位置/速度/力矩命令 | forward_command_controller/ForwardCommandController |
| 控制差速底盘 | diff_drive_controller/DiffDriveController |
| 控制麦轮底盘 | mecanum_drive_controller/MecanumDriveController |
类型从哪里查
第一种,最直接:启动后查当前系统支持哪些 controller type。
ros2 control list_controller_types如果 controller manager 已经运行,它会列出类似:
joint_state_broadcaster/JointStateBroadcaster joint_trajectory_controller/JointTrajectoryController forward_command_controller/ForwardCommandController mecanum_drive_controller/MecanumDriveController第二种,查有哪些 controller 包:
PowerShell 里可以用:
ros2 pkg list | Select-String "controller"你看到类似这些包名,就说明系统里装了对应 controller:
joint_state_broadcaster joint_trajectory_controller forward_command_controller mecanum_drive_controller controller_manager第三种,看你的package.xml。你工程里已经依赖了这些:
<depend>controller_manager</depend> <depend>ros2_control</depend> <depend>ros2_controllers</depend> <exec_depend>mecanum_drive_controller</exec_depend>这说明当前 bringup 包运行时需要ros2_control、ros2_controllers和mecanum_drive_controller。
以上是我最近在写一个项目刚好用到了这些,突发奇想写一些,可能又不全的地方,也可能有错误的地方,还希望各位大佬指正