🤖 roboto_origin_03 Wiki
首页 / 电机子模块 / EVO电机驱动详解

本文档深入讲解 EvoMotorDriver 的实现原理与使用要点,涵盖其双总线兼容架构、MIT 阻抗控制数据打包协议、寄存器配置体系,以及 CAN-FD 场景下的批量一帧多机控制机制。阅读本文前,建议先掌握 MotorDriver 抽象基类与接口设计 中的通用状态机与回调模型,以便理解本文中 lock_motorunlock_motorinit_motor 等生命周期方法的设计上下文。

Sources: evo_motor_driver.hpp, motor_driver.hpp

架构定位与双总线兼容

EvoMotorDriver 继承自 MotorDriver 抽象基类,是 SDK 中唯二同时支持 CAN 2.0 与 CAN-FD 的驱动实现(另一为 DM 系列)。构造函数通过 interface_type 参数("can" / "canfd" / "ethercanfd")在运行时决定通信后端:CAN 2.0 场景绑定 MotorsCAN 单例,CAN-FD 场景则绑定 MotorsCANFD 单例,并将实例指针注册到以 can_interface_ 为键的全局静态 bus_registry_ 中。该注册表在析构时自动清理,保证多电机共享同一总线时的生命周期安全。

classDiagram
    class MotorDriver {
        <<abstract>>
        +lock_motor()
        +unlock_motor()
        +init_motor()
        +motor_mit_cmd(f_p, f_v, f_kp, f_kd, f_t)
        +motor_pos_cmd(pos, spd, ignore_limit)
        +motor_spd_cmd(spd)
        +set_motor_control_mode(mode)
    }
    class EvoMotorDriver {
        -EVO_Motor_Model motor_model_
        -EVO_Limit_Param limit_param_
        -uint8_t motor_index_
        -can_rx_cbk(rx_frame)
        -canfd_rx_cbk(rx_frame)
        -write_register_evo(rid, value)
        +motor_mit_cmd(float*)
    }
    MotorsCAN --> EvoMotorDriver : CAN 2.0
    MotorsCANFD --> EvoMotorDriver : CAN-FD
    MotorDriver <|-- EvoMotorDriver

在 CAN-FD 模式下,电机 ID 必须落在 1~8 范围内,驱动内部将其映射为 motor_index_ = motor_id - 1,该索引直接决定批量 MIT 控制帧中的 8 字节槽位位置。若 ID 超出此范围,则强制归零,可能导致批量命令不匹配。

Sources: evo_motor_driver.cpp, evo_motor_driver.cpp

电机型号与运行限幅

EVO 系列目前内置三款电机的硬编码限幅参数,以全局数组 evo_limit_param 维护。驱动在构造时根据 motor_model_ 索引拷贝对应结构到成员 limit_param_,后续所有控制量饱和、反馈量反归一化均以此为准。

型号枚举 位置限幅 PosMax (rad) 速度限幅 SpdMax (rad/s) 力矩限幅 TauMax (N·m) 外环 Kp 上限 OKpMax 外环 Kd 上限 OKdMax
EVO431040 12.5 20.0 18.0 500.0 5.0
EVO811825 12.5 10.0 50.0 250.0 50.0
EVO811832 12.5 10.0 50.0 250.0 50.0

这些参数不仅用于软件层面的 limit() 截断,更关键的是作为 range_map() 的物理量程边界——MIT 控制指令需要将浮点物理量映射到固定位宽的整型域,反馈解析时则反向映射。若实际电机型号与构造时传入的枚举不一致,会导致反馈值比例错误,表现为位置或力矩读数偏差。

Sources: evo_motor_driver.cpp, evo_motor_driver.hpp

数据打包协议:MIT 8 字节帧

EVO 的 MIT(阻抗控制)协议将 5 个浮点控制量 压缩进 8 字节 payload,这是整个驱动中位运算最密集的部分。打包规则如下:

字节布局采用紧凑拼接,无字节对齐填充:

data[0] = p[15:8]           // 位置高 8 位
data[1] = p[7:0]            // 位置低 8 位
data[2] = v[11:4]           // 速度高 8 位
data[3] = v[3:0] | kp[11:8] // 速度低 4 位 + Kp 高 4 位
data[4] = kp[7:0]           // Kp 低 8 位
data[5] = kd[11:4]          // Kd 高 8 位
data[6] = kd[3:0] | t[11:8] // Kd 低 4 位 + 力矩高 4 位
data[7] = t[7:0]            // 力矩低 8 位

代码实现中,motor_mit_cmd(float, float, float, float, float) 先通过 limit() 将物理量钳位到安全区间,再经 range_map() 完成量程转换,最后按上述位域写入 data[]。注意位置命令会自动扣除构造时传入的 motor_zero_offset_,实现软件零偏补偿。

Sources: evo_motor_driver.cpp

状态反馈解析

接收回调同样存在 CAN 与 CAN-FD 两条路径。can_rx_cbk 针对标准 CAN 2.0,canfd_rx_cbk 针对 CAN-FD;后者若检测到帧未携带 CANFD_FDF 标志,会降级转调到前者的解析逻辑,保证协议兼容性。

反馈帧结构:

字段 CAN 2.0 数据源 CAN-FD 数据源 位宽 物理映射
位置 data[1..2] data[0..1] 16 bit range_map[-PosMax, PosMax]
速度 data[3..4] 高12bit data[2..3] 高12bit 12 bit range_map[-SpdMax, SpdMax]
力矩/电流 data[4] 低4bit + data[5] data[3] 低4bit + data[4] 12 bit range_map[-TauMax, TauMax]
错误码 data[6] (data[6..7] >> 1) & 0x7F 8/7 bit 直接映射 EVOError
温度 data[7] data[5] - 40.0f 8 bit CAN: 原始值;CAN-FD: 摄氏度偏移

其中 CAN-FD 的温度采用 data[5] - 40.0f 的偏移编码,与 CAN 2.0 的原始值表示不同,这是双总线切换时最容易被忽略的差异点。错误码 error_id_ 在解析后若大于 0,驱动会通过 spdlog 自动输出 ERROR 级日志,包含总线名与电机 ID,便于现场快速定位故障节点。

Sources: evo_motor_driver.cpp

错误码体系

EVO 驱动定义了独立的错误枚举 EVOError,在 init_motor() 返回时用于向调用方传递上电自检结果。

错误码 含义 触发场景
0x00 无错误 正常运行
0x01 过压 (OVER_VOLTAGE) 母线电压超过保护阈值
0x02 欠压 (UNDER_VOLTAGE) 母线电压低于保护阈值
0x03 过流 (OVER_CURRENT) 相电流超过锁定值
0x09 MOS 过温 (MOS_OVER_TEMP) 驱动板功率管温度超限
0x0A 线圈过温 (COIL_OVER_TEMP) 电机绕组温度超限
0x0B 编码器错误 (ENCODER_ERROR) 位置传感器通信或校验失败
0x0F 过载 (OVERLOAD) 机械负载持续超限
0x10 通信丢失 (COMM_LOST) 上位机超时未收到有效帧
0xFF 未知错误 (UNKNOWN_ERROR) 未定义故障码

init_motor() 的标准流程为:先 unlock_motor() 进入就绪态,再设置 MIT 模式,随后 lock_motor() 使能,最后 refresh_motor_status() 拉取实时状态并返回错误码。任何非零返回值都意味着当前电机不宜直接投入闭环控制。

Sources: evo_motor_driver.hpp, evo_motor_driver.cpp

寄存器配置与 Flash 操作

EVO 系列采用寄存器地址模型,通过 0x600 + motor_id_ 的扩展标识符进行参数读写。核心寄存器枚举 EVO_REG 覆盖了电流环 PI、死区、运动限幅、CAN 映射量程以及安全阈值等配置项。

寄存器写入采用统一的 8 字节协议模板:

data[0] = EVO_CMD_START_FLASH (0x67)
data[1] = rid                 // 寄存器地址
data[2..5] = value (float/int32 LE)
data[6] = EVO_CMD_WRITE_FLASH (0x15)
data[7] = EVO_CMD_END_FLASH   (0x76)

驱动为此提供两个重载的私有方法 write_register_evo(uint8_t rid, float value)write_register_evo(uint8_t index, int32_t value),分别对应浮点型和整型寄存器。save_register_evo() 则将当前内存配置固化到 Flash,模板中 data[6] = EVO_CMD_SAVE_FLASH (0x00)。不过当前版本中 write_motor_flash() 公共接口直接返回 true,实际持久化操作建议通过 set_motor_zero() 或显式寄存器调用完成。

Sources: evo_motor_driver.hpp, evo_motor_driver.cpp

批量 MIT 控制:CAN-FD 一帧多机

EVO 驱动在 CAN-FD 场景下支持高效的一帧多机批量控制,这是其区别于 CAN 2.0 实现的核心性能特性。motor_mit_cmd(float* f_p, float* f_v, float* f_kp, float* f_kd, float* f_t) 重载会构造一个 64 字节 payload 的 CAN-FD 帧,CAN ID 固定为 EVOFD_MIT_ID (0x20),每个电机占用 8 字节槽位,槽位索引等于 motor_index_

sequenceDiagram
    participant Host as 上位机
    participant Bus as CAN-FD 总线
    participant M1 as EVO #1 (slot 0)
    participant M2 as EVO #2 (slot 1)
    participant M8 as EVO #8 (slot 7)

    Host->>Bus: 发送 64B 帧 (ID=0x20)
    Note over Bus: slot0: M1 MIT 数据<br/>slot1: M2 MIT 数据<br/>...<br/>slot7: M8 MIT 数据
    Bus->>M1: 提取 data[0..7]
    Bus->>M2: 提取 data[8..15]
    Bus->>M8: 提取 data[56..63]

实现上,方法先以全 0x7F/0xFF 填充全部 8 个槽位作为默认值,随后加锁遍历 bus_registry_[can_interface_],对注册到该总线的每个 EvoMotorDriver 实例,依据其 motor_index_ 和各自的 limit_param_ 计算并覆写对应槽位。这意味着同一总线上不同型号的 EVO 电机可以共存于同一批量帧,各自的物理限幅独立生效。若某槽位无对应电机实例,则保留默认值,电机端应忽略或保持上一帧状态。

该接口仅在 comm_type_ == CANFD 时启用;若为 CAN 2.0,会降级到单电机 motor_mit_cmd(float, ...),确保 API 语义一致。

Sources: evo_motor_driver.cpp

位置与速度模式

除 MIT 阻抗控制外,EVO 驱动同样支持独立的位置模式(POS)与速度模式(SPD)。两种模式均通过向电机基础 ID 发送 IEEE-754 float 原始字节实现。

set_motor_control_mode 在 CAN 2.0 下会向寄存器 11 写入 0x02,随后更新成员变量 motor_control_mode_;CAN-FD 路径目前也执行相同操作。

Sources: evo_motor_driver.cpp, evo_motor_driver.cpp

零位标定与 ID 管理

set_motor_zero() 是 EVO 系列 commissioning 阶段最常用的操作之一。其实现流程为:发送 EVO_CMD_SET_ZERO (0xFE),等待 500 ms,刷新状态并读取当前位置,随后 unlock_motor()。若读取到的位置绝对值大于 judgment_accuracy_threshold (1e-2),则判定标定失败并返回 false,否则返回 true。该阈值来自基类静态常量,适用于全系列电机。

电机 ID 修改通过向寄存器 EVO_REG_MOTOR_ID (36) 写入新 ID 完成。set_motor_id(old_id, new_id)reset_motor_id() 均调用 write_register_evo 实现,但修改后不会自动固化到 Flash,如需掉电保存,需额外调用保存流程。reset_motor_id() 将 ID 恢复为出厂默认值 1

Sources: evo_motor_driver.cpp, evo_motor_driver.cpp

工厂创建与 Python 暴露

用户代码通常不直接调用 EvoMotorDriver 构造函数,而是通过基类工厂方法统一实例化:

auto motor = MotorDriver::create_motor(
    motor_id,           // uint16_t
    "canfd",            // interface_type
    "can0",             // can_interface
    "EVO",              // motor_type
    EVO_Motor_Model::EVO431040,  // motor_model
    0,                  // master_id_offset (EVO 未使用)
    0.0                 // motor_zero_offset
);

Python 层通过 pybind11 暴露相同语义,create_motor 静态方法在 MotorDriver 基类上绑定,调用端无需感知具体子类。EVO 的枚举模型在 Python 中通过整型传递即可。

Sources: motor_driver.cpp, pybind_module.cpp

与其他驱动的关键差异总结

维度 EVO DM LeadRobot
总线支持 CAN 2.0 + CAN-FD CAN-FD 为主 CAN 2.0
批量 MIT CAN-FD 64B 一帧 8 机 单帧单电机 不支持
温度编码 CAN-FD 带 -40°C 偏移 模型相关 原始值
零位命令 0xFE 独立命令 Flash 写寄存器 独立协议
ID 范围约束 CAN-FD 要求 1~8 较宽 较宽
错误码体系 独立 EVOError 枚举 独立 DMError 独立 LROError

Sources: evo_motor_driver.hpp

下一步阅读建议

若你已理解 EVO 驱动的通信协议与生命周期管理,建议继续深入以下关联主题: