本文档聚焦Roboto平台电机驱动栈的全链路架构与实现细节,覆盖从物理层USB2CAN适配器固件到应用层推理节点的高频控制闭环。核心目标是为具备嵌入式与ROS2基础的开发者提供一套可复现、可调试、可扩展的电机通信知识体系,内容涵盖CAN/CAN-FD协议抽象、SocketCAN实时收发线程、多品牌电机驱动(DM/EVO/LRO)的MIT阻抗控制模式,以及推理节点中的多总线并行调度策略。阅读前建议已完成部署架构与实时内核配置与IMU驱动与传感器集成章节的环境准备。
Sources: readme_cn.md, robot_interface.cpp
系统架构概览
电机驱动栈采用四层硬件抽象设计,确保协议层与驱动层解耦,使同一套上层控制逻辑可透明切换CAN、CAN-FD乃至EtherCAT后端。物理通信依赖自研的roboto_usb2can适配器,该设备在Linux下通过原生gs_usb驱动枚举为SocketCAN接口(如can0),从而允许用户空间程序以标准Berkeley Socket API收发帧。
flowchart TB
subgraph APP["应用层"]
A1["推理节点 inference_node"]
A2["Python SDK / 标定脚本"]
end
subgraph DRV["驱动层"]
D1["DmMotorDriver"]
D2["EvoMotorDriver"]
D3["LroMotorDriver"]
D4["MotorDriver 抽象工厂"]
end
subgraph PROTO["协议层"]
P1["MotorsCAN / MotorsCANFD"]
P2["MotorsSocketCAN"]
P3["MotorsSocketCANFD"]
end
subgraph HW["硬件层"]
H1["主控板 USB Host"]
H2["roboto_usb2can 适配器"]
H3["CAN 总线"]
H4["关节电机"]
end
A1 --> D4
A2 --> D4
D4 --> D1 & D2 & D3
D1 & D2 & D3 --> P1 & P3
P1 --> P2
P3 --> P2 & P3
P2 --> H1
P3 --> H1
H1 --> H2 --> H3 --> H4
Sources: motor_driver.hpp, can_iso.hpp
USB2CAN适配器固件
roboto_usb2can是运行于STM32G431CBT6的Zephyr RTOS固件,实现gs_usb协议以兼容candleLight生态。其核心价值在于:Linux系统无需额外驱动,插入即可生成can0/can1等标准SocketCAN网络接口;Windows系统则通过内嵌的Microsoft OS 2.0描述符实现WinUSB自动绑定,真正做到跨平台即插即用。
固件内置了多级CAN总线故障保护机制。can_state_change_callback持续监听CAN控制器状态机,当检测到错误帧洪水(每秒超过50帧)或持续ERROR_PASSIVE(累计超过10次)时,主动调用can_stop强制进入Bus-Off,防止故障节点拖垮整网。状态变化通过板载三色LED直观呈现:蓝灯指示USB就绪/错误,黄灯指示CAN活跃/警告/总线关闭,绿灯则作为数据收发活动指示灯。
Sources: main.c, roboto_usb2can.h
CAN协议层:SocketCAN实时收发
协议层以MotorsCAN和MotorsCANFD为抽象基类,当前提供基于Linux SocketCAN的具体实现。MotorsSocketCAN采用单例模式管理每个网络接口的生命周期,确保同一进程中对can0的多次引用共享同一个套接字与收发线程,避免资源冗余。
为达到足控所需的千赫兹级通信频率,收发线程均配置了硬实时属性。接收线程与发送线程均通过pthread_setschedparam设定SCHED_FIFO调度策略,优先级固定为80;同时依据接口名末位数字自动绑定到不同CPU核心(如can0绑定到倒数第1核,can1绑定到倒数第2核),以最小化缓存失效与上下文切换开销。发送侧使用boost::lockfree::queue实现无锁队列,容量4096帧,配合条件变量实现低延迟唤醒与流量反压。
sequenceDiagram
participant M as 电机驱动实例
participant TX as Sender线程<br/>(SCHED_FIFO, CPU N)
participant Q as Lock-free TX Queue
participant SOC as SocketCAN
participant CAN as 物理CAN总线
M->>Q: bounded_push(frame)
M->>TX: notify_one()
TX->>Q: pop(frame)
TX->>SOC: write(sockfd, frame)
SOC->>CAN: 电平信号
loop MAX_RETRY_COUNT=3
alt write失败
TX->>TX: sleep 1ms
TX->>SOC: 重试write
end
end
Sources: socket_can.hpp, socket_can.cpp
电机驱动层:抽象工厂与品牌实现
MotorDriver作为抽象基类定义了统一的控制接口,包括MIT阻抗控制、位置模式、速度模式、零位标定与错误清除。具体品牌驱动通过create_motor工厂方法按需实例化。当前支持DM(达妙)、EVO以及LRO(LeadRobot)三大系列,其中DM与EVO为Atom01主力关节电机。
| 品牌 | 支持接口 | 控制模式 | 典型型号 |
|---|---|---|---|
| DM | CAN / CAN-FD | MIT, POS, SPD | DM4340P-48V, DM10010L-48V |
| EVO | CAN / CAN-FD | MIT, POS, SPD | EVO431040, EVO811825, EVO811832 |
| LRO | CAN | MIT, POS, SPD, CUR | — |
DM与EVO驱动在CAN 2.0模式下共享高度相似的帧打包范式。以MIT控制命令为例,目标位置、速度、Kp、Kd、力矩五个浮点量首先被range_map映射到固定位宽的无符号整数(位置16位,其余12位),再按大端序与位域交错填入8字节数据段。回传帧的解析则是逆过程:从can_frame.data中提取位域后逆映射为物理量,并叠加motor_zero_offset补偿机械安装零偏。
Sources: motor_driver.cpp, dm_motor_driver.cpp, evo_motor_driver.cpp
MIT阻抗控制与数据解析
MIT模式(又称阻抗控制模式)是足式机器人最常用的高带宽控制接口。其数学本质是在电机端实现一个虚拟弹簧阻尼系统,输出力矩由以下公式决定:
τ = Kp × (pos_target − pos_actual) + Kd × (vel_target − vel_actual) + τ_feedforward
在总线层面,DM与EVO电机要求发送方将五个参数压缩到单个8字节CAN帧内。以DM电机为例,motor_mit_cmd先将浮点参数裁剪到型号对应的物理极限(如DM4340P的位置极限±12.5 rad),再映射到定点数:位置占16位,速度、Kp、Kd、力矩各占12位。最终帧结构为:[15:0]p, [15:4]v, [3:0]v+[11:8]kp, [7:0]kp, [11:4]kd, [3:0]kd+[11:8]t, [7:0]t。
回传帧以主站ID(motor_id + master_id_offset)作为can_id。can_rx_cbk回调从中解析位置(16位)、速度(12位)、力矩(12位),并通过range_map恢复为浮点物理量。错误码位于data[0]高4位,当值大于7时触发日志报错。
Sources: dm_motor_driver.cpp, utils.hpp
CAN-FD批模式与多总线并行
在推理节点的高频闭环中,逐电机单帧发送会成为显著瓶颈。EVO驱动在CAN-FD模式下支持单帧多机批写:利用CAN-FD最大64字节的有效载荷,将最多8台电机的MIT数据按8字节/槽位紧凑排列,通过EVOFD_MIT_ID(0x20)统一广播。EvoMotorDriver::motor_mit_cmd(float*, ...)会查询静态bus_registry_,按motor_index_将各电机的目标值写入对应槽位,随后一次性发出。
RobotInterface::apply_action进一步在总线维度并行化:通过线程池为每条CAN/CAN-FD总线分配独立任务,各任务填充该总线下的电机数组后调用批写接口。这种“总线级并行 + 帧级批处理”的双重优化,使得23关节的全局控制周期可稳定运行在1kHz量级。
flowchart LR
subgraph BUS0["can0 (6 motors)"]
B0_M1["motor_id=1 slot=0"]
B0_M2["motor_id=2 slot=1"]
end
subgraph BUS1["can1 (7 motors)"]
B1_M1["motor_id=1 slot=0"]
B1_M2["motor_id=2 slot=1"]
end
A["推理输出 action[23]"] --> T["ThreadPool 4 tasks"]
T --> BUS0 & BUS1 & BUS2 & BUS3
BUS0 --> F0["CAN-FD Frame 64B<br/>EVOFD_MIT_ID=0x20"]
BUS1 --> F1["CAN-FD Frame 64B<br/>EVOFD_MIT_ID=0x20"]
Sources: evo_motor_driver.cpp, robot_interface.cpp
配置文件与电机拓扑
电机拓扑在robot.yaml中声明,字段语义如下:
| 字段 | 类型 | 说明 |
|---|---|---|
motor_id |
int[] | 各电机的CAN节点ID,顺序决定内部数组索引 |
motor_interface_type |
string | 全局接口类型:can / canfd |
motor_interface |
string[] | 总线接口名列表,如["can0", "can1"] |
motor_num |
int[] | 每条总线上的电机数量,与motor_interface一一对应 |
motor_type |
string[] | 每条总线的电机品牌,如["DM", "EVO"] |
motor_model |
int[] | 各电机型号枚举值,与motor_id一一对应 |
master_id_offset |
int | 主站接收ID偏移量(DM系列常用16) |
motor_zero_offset |
double[] | 机械零位偏移量(rad),与motor_id一一对应 |
RobotInterface::setup_motors按上述配置逐条总线、逐个电机调用MotorDriver::create_motor,并通过motor_sign数组在应用层处理关节方向反转,无需修改底层驱动。close_chain_motor_id则用于标记闭链传动关节(如脚踝),这些关节在apply_action中会经过ankle_decouple_进行力/位解耦计算。
Sources: robot.yaml, robot_interface.cpp
Python SDK快速上手
roboto_motors通过pybind11暴露Python接口,编译后生成motors_py模块。以下示例演示单台DM电机的创建、初始化、MIT控制与状态回读:
import motors_py
import time
motor = motors_py.MotorDriver.create_motor(
motor_id=1,
interface_type="can",
interface="can0",
motor_type="DM",
motor_model=0, # DM4340P_48V
master_id_offset=16,
)
motor.init_motor() # 解锁 → 设模式 → 锁定 → 刷新状态
motor.motor_mit_cmd(-0.5, 0.0, 5.0, 1.0, 0.0)
time.sleep(0.1)
print(f"位置: {motor.get_motor_pos():.4f} rad")
print(f"错误码: {motor.get_error_id()}")
motor.deinit_motor()
Sources: motors_py_example.py, pybind_module.cpp
故障排查与最佳实践
| 现象 | 根因分析 | 排查动作 |
|---|---|---|
Unable to detect CAN interface |
USB2CAN未插入或接口未up | 检查ip link show can0,确认gs_usb已加载 |
Motor id X offline |
回包计数超过阈值,总线断连 | 检查终端电阻、波特率一致性、电机供电 |
| 电机高频抖动 | MIT参数Kp/Kd过大或方向符号错误 | 对比robot.yaml中motor_sign与URDF关节方向 |
Failed to set realtime priority |
用户缺少CAP_SYS_NICE |
参考部署架构与实时内核配置配置limits |
| 错误帧洪水导致Bus-Off | 线缆干扰或GND不稳 | 检查屏蔽层接地,缩短Stub线长度 |
调试时建议首先使用roboto_usb2can_tool.py上位机扫描总线,确认电机原始回包正常后,再切入ROS2推理节点。若需修改电机ID或零位,可调用set_motor_id与set_motor_zero接口,后者会主动回读位置并校验绝对值是否小于judgment_accuracy_threshold(默认0.01 rad)。
Sources: socket_can.cpp, motor_driver.hpp
延伸阅读与下一步
完成电机驱动与CAN总线通信的部署后,你已具备让关节响应控制指令的基础能力。接下来建议阅读推理节点与ONNX策略部署,了解如何将训练好的策略模型接入本文档所述的电机栈,实现从Sim到Real的完整闭环。如需扩展新品牌电机,可参考drivers/dm或drivers/evo目录,继承MotorDriver并实现对应的帧打包/解包逻辑即可。