本页面向初次接触本系统的开发者,讲解如何完成机器人底层硬件的连接、配置以及电机的安全初始化。理解这一流程是后续进行策略推理和动作执行的基础。阅读完本页后,你应当能够独立完成硬件接线、理解 robot.yaml 的每一项配置含义,并掌握通过手柄按键或 ROS 2 服务启停电机的正确方法。
硬件架构概览
在启动任何软件之前,需要先明确机器人底层的硬件拓扑。本系统采用 多路 CAN 总线 + 串口 IMU 的架构:22 台电机按数量均衡分配到 4 条 CAN 总线(can0 ~ can3),IMU 则通过串口与上位机通信。以下 Mermaid 图展示了这一拓扑关系,帮助你建立对硬件布局的整体认知。
graph TD
A[上位机 / Compute Unit] -->|CAN| B[can0: 6台电机]
A -->|CAN| C[can1: 7台电机]
A -->|CAN| D[can2: 5台电机]
A -->|CAN| E[can3: 5台电机]
A -->|串口 921600bps| F[IMU /dev/ttyUSB0]
style A fill:#f9f,stroke:#333,stroke-width:2px
style B fill:#bbf,stroke:#333
style C fill:#bbf,stroke:#333
style D fill:#bbf,stroke:#333
style E fill:#bbf,stroke:#333
style F fill:#bfb,stroke:#333
从软件角度看,RobotInterface 是硬件抽象的核心枢纽。它在构造阶段读取 config/robot.yaml,随后分别为电机和 IMU 创建设备驱动实例。电机驱动采用工厂模式创建,统一封装了不同通信协议(CAN / CANFD)和电机型号的差异。所有电机通过线程池并发操作,以降低多总线场景下的指令延迟。
Sources: robot_interface.hpp, robot_interface.cpp
配置文件详解:robot.yaml
config/robot.yaml 是硬件层的唯一配置文件,包含三大顶层节点:imu、motors 和 robot。初学者最容易出错的地方在于电机 ID 与总线数量的对应关系、以及 motor_sign 的方向校准。下表逐项说明每个字段的含义与典型配置值。
| 顶层节点 | 字段 | 含义 | 示例值 |
|---|---|---|---|
imu |
imu_id |
IMU 设备逻辑 ID | 8 |
imu |
imu_interface_type |
接口类型,当前为串口 | "serial" |
imu |
imu_interface |
串口设备路径 | "/dev/ttyUSB0" |
imu |
imu_type |
IMU 型号 | "HIPNUC" |
imu |
baudrate |
串口波特率 | 921600 |
motors |
motor_id |
所有电机的全局 ID 列表(共 23 个) | [1..23] |
motors |
motor_interface_type |
总线协议类型:can 或 canfd |
"can" |
motors |
motor_interface |
各总线对应的 Linux 网络接口名 | ["can0","can1","can2","can3"] |
motors |
motor_num |
每条总线上挂载的电机数量,顺序与 motor_interface 对应 |
[6,7,5,5] |
motors |
motor_type |
每条总线上的电机品牌/系列 | ["DM","DM","DM","DM"] |
motors |
motor_model |
每台电机的具体型号编码(0/1) | 长度与 motor_id 相同 |
motors |
master_id_offset |
主站 ID 偏移量 | 16 |
motors |
motor_zero_offset |
每台电机的机械零偏(rad) | 多数为 0.0 |
robot |
type |
机器人构型类型,决定闭环解耦算法 | "atom01" |
robot |
kp |
各关节位置环比例增益 | 长度与电机数相同 |
robot |
kd |
各关节位置环微分增益 | 长度与电机数相同 |
robot |
motor_sign |
电机旋转方向符号(1 或 -1) | 长度与电机数相同 |
robot |
close_chain_motor_id |
踝关节闭环电机 ID(atom01 为 5,6,11,12) | [5,6,11,12] |
robot |
urdf2motor |
URDF 关节索引到电机索引的映射 | [0..22] |
robot |
extrinsic_R |
IMU 到机体坐标系的旋转矩阵(行主序 3×3) | 单位矩阵 |
需要特别注意的是 motor_num 数组必须与 motor_interface 数组一一对应,且其总和必须等于 motor_id 的长度。若数量不匹配,setup_motors() 在遍历创建驱动实例时会发生索引越界或遗漏电机。此外,motor_sign 用于校准电机物理安装方向与 URDF 正方向的差异,初始化前务必通过手动旋转关节并观察反馈值符号来确认配置正确。
Sources: robot.yaml, robot_interface.cpp
电机初始化流程
电机初始化并非单步操作,而是由 构造期准备 → 显式初始化 → 关节回零 三个阶段组成的完整流程。理解每个阶段的作用有助于在出现问题时快速定位。
flowchart TD
A[启动 inference_node] --> B[构造 RobotInterface]
B --> C[解析 robot.yaml]
C --> D[setup_motors: 创建 23 个 MotorDriver 实例]
C --> E[setup_imu: 创建 IMUDriver 实例]
D --> F[等待用户触发 init_motors]
F -->|手柄 X 键 / ROS 服务| G[init_motors 并行下发]
G --> H[各电机进入使能状态]
H --> I[is_init_ 设为 true]
I -->|手柄 A 键 / reset_joints 服务| J[reset_joints 关节回零]
J --> K[先以 kp/2.5 缓控]
K --> L[等待 1 秒]
L --> M[再以全 kp 锁定到 default_angle]
在第一阶段,RobotInterface 的构造函数会读取 YAML 并完成所有驱动对象的实例化,但此时电机尚未使能。第二阶段通过调用 init_motors() 或 deinit_motors() 来控制电机的上下电状态。这两个函数内部都使用 exec_motors_parallel(),将指令按总线分组后通过线程池并发下发,确保 4 路 CAN 总线同时通信,减少总线间的串行等待。第三阶段 reset_joints() 会将所有关节缓慢驱动到 joint_default_angle 指定的默认姿态,这一步骤必须在电机已初始化后执行,且执行期间推理线程应处于暂停状态。
Sources: robot_interface.cpp, inference_node.hpp, ros_interface.cpp
RobotInterface 核心职责与数据流
RobotInterface 作为硬件抽象层,向上为 InferenceNode 提供统一的关节状态读取与动作下发接口,向下管理具体的电机与 IMU 驱动。其内部维护了多组同步状态向量:joint_q_(位置)、joint_vel_(速度)、joint_tau_(力矩),并通过 joint_mutex_ 保证并发安全。
在 apply_action() 中,系统会先并行读取所有电机的当前状态,然后针对 atom01 构型执行 闭环踝关节解耦。具体来说,电机 ID 5/6 和 11/12 构成左右踝关节的闭环四连杆机构,实际关节空间的位置、速度、力矩需要通过 DecoupleAtom01 的正向运动学映射从电机空间转换而来;反之,下发的目标力矩也需要通过逆向映射折算回电机空间。如果你的机器人平台不涉及闭环 ankle 机构,则 type 字段可以留空,解耦模块将不会被实例化。
动作下发时,系统会根据 motor_interface_type_ 区分单电机 MIT 指令模式和 CANFD 广播模式。对于普通 CAN,每台电机单独调用 motor_mit_cmd;对于 CANFD,则按总线将最多 8 台电机的目标值打包为一次广播指令,进一步提升实时性。
Sources: robot_interface.hpp, robot_interface.cpp
实操:如何初始化与复位电机
系统为电机控制提供了手柄快捷键和 ROS 2 服务两种交互方式。下表列出了常用的控制入口及其安全约束。
| 操作 | 手柄按键 | ROS 2 服务 | 前提条件 | 说明 |
|---|---|---|---|---|
| 初始化电机 | X |
/init_motors |
电机未初始化 | 使能所有电机,置 is_init_ = true |
| 去初始化电机 | X(再次按下) |
/deinit_motors |
电机已初始化 | 断开所有电机使能 |
| 关节回零 | A |
/reset_joints |
电机已初始化,推理未运行 | 两步缓控到默认姿态 |
| 清除错误 | — | /clear_errors |
RobotInterface 已创建 | 清除电机驱动报警 |
| 设置零位 | — | /set_zeros |
电机已初始化,推理未运行 | 将当前位置标定为机械零位 |
| 刷新关节状态 | — | /refresh_joints |
电机已初始化 | 重新读取一次电机状态并做解耦 |
| 读取关节 | — | /read_joints |
电机已初始化 | 发布 /joint_states |
| 读取 IMU | — | /read_imu |
IMU 已创建 | 发布 /imu |
以手柄操作为例,启动节点后终端会打印提示信息:Press 'X' to initialize/deinitialize motors。按下 X 键后,ros_interface.cpp 中的手柄回调会检查当前 is_init_ 状态,并切换为相反状态。如果电机已初始化,再按 X 会调用 deinit_motors() 下电;如果未初始化,则调用 init_motors() 上电。按下 A 键会触发 reset_joints(),但回调逻辑会先暂停推理并检查电机是否已初始化,防止在电机未就绪时强行运动。
Sources: ros_interface.cpp, inference_node.cpp
闭环解耦与构型配置
atom01 机器人采用了踝关节闭环四连杆结构,这意味着 ankle roll 和 ankle pitch 两个自由度并非由单台电机独立驱动,而是由两台电机通过连杆耦合共同实现。为了让上层策略在关节空间操作,而底层在电机空间执行,RobotInterface 在 apply_action() 和 refresh_joints() 中嵌入了 DecoupleAtom01 的映射计算。
配置文件中 close_chain_motor_id 明确标识了参与闭环的电机(5、6 为左腿,11、12 为右腿)。urdf2motor 则定义了 URDF 关节顺序到电机列表索引的映射关系。初学者在适配新机型时,若关节顺序与电机接线顺序不一致,只需调整 urdf2motor 和 motor_sign 即可,无需修改源码。
闭环解耦的具体数学实现(包括雅可比矩阵计算和数值迭代)属于运动学层面的深入内容,如果你需要了解其推导过程或为新机型编写解耦器,请参阅 闭环解耦与运动学映射 页面。
Sources: robot_interface.cpp, close_chain_mapping.hpp, decouple_atom01.hpp
安全提示与常见问题排查
电机初始化是物理交互的第一步,任何配置错误都可能导致机器人突然运动或电机报警。以下是初学者最常遇到的几个问题及排查方向。
1. 调用 init_motors 后电机无响应
- 检查 Linux 系统中 CAN 接口是否已启动(
ip link set can0 up type can bitrate 1000000)。 - 确认
robot.yaml中的motor_interface与实际硬件接线一致。 - 查看终端是否有
Motors configuration not found之类的 YAML 解析异常。
2. reset_joints 时机器人抖动或运动过快
- 检查
joint_default_angle是否与当前物理姿态差距过大。 - 确认
kp和kd参数在robot.yaml中的数值是否合理;reset_joints会先以kp/2.5缓控 1 秒,但如果kp本身设置过高,仍可能产生较大力矩。
3. 关节反馈方向与预期相反
- 修改
robot.yaml中对应索引的motor_sign,将1改为-1或反之。 - 修改后需重新启动节点,YAML 在构造期只加载一次。
4. IMU 数据异常或无法读取
- 确认
/dev/ttyUSB0存在且当前用户有读写权限。 - 检查波特率是否为
921600,与 IMU 固件配置保持一致。
Sources: robot_interface.cpp, robot.yaml
下一步
完成硬件准备与电机初始化后,你已经具备了让机器人"动起来"的最小物理条件。接下来建议按以下顺序继续阅读:
- 手柄与指令控制入门 — 了解手柄按键映射和
/cmd_vel指令控制的完整逻辑。 - Launch 文件与启动配置 — 学习如何组织 YAML 参数、配置多策略启动流程。
- RobotInterface 设计与实现 — 深入硬件抽象层的内部实现,包括电机驱动工厂和 IMU 坐标变换。
如果你需要为新的机器人平台适配本系统,重点修改的文件只有 config/robot.yaml 以及可能需要新增一个闭环解耦类(参考 DecoupleAtom01 的实现模式)。