🤖 roboto_origin_03 Wiki
首页 / 电机子模块 / 状态反馈与错误码解析

在电机闭环控制中,状态反馈是控制器感知物理世界的唯一通道,而错误码解析则是系统安全运行的最后防线。本文档深入剖析 roboto_motors SDK 中状态数据的流转路径、帧解析算法,以及跨品牌电机错误码的统一处理机制。理解这些机制将帮助你在多电机系统中构建可靠的故障诊断与状态监控逻辑。

Sources: motor_driver.hpp

状态数据的线程安全架构

MotorDriver 抽象基类为所有品牌电机定义了一套统一的状态存储接口。核心状态变量均以 std::atomic 包装,确保在多线程环境(CAN 接收线程 vs 主控制线程)下的无锁读写安全。

基类维护的五维状态向量包括:

状态变量 类型 语义 访问接口
motor_pos_ std::atomic<float> 当前转子位置 (rad) get_motor_pos()
motor_spd_ std::atomic<float> 当前转速 (rad/s) get_motor_spd()
motor_current_ std::atomic<float> 当前电流/力矩等效值 get_motor_current()
motor_temperature_ std::atomic<float> 绕组/电机温度 (°C) get_motor_temperature()
error_id_ std::atomic<uint8_t> 活动错误码 get_error_id()

此外,各品牌派生类扩展了私有状态字段,例如 DM 与 EVO 均增加了 mos_temperature_ 用于监控 MOS 管温度,LRO 则在其 CAN-FD 反馈帧中同时携带了 MOS 温度与绕组温度。

Sources: motor_driver.hpp, dm_motor_driver.hpp, evo_motor_driver.hpp, lro_motor_driver.hpp

接收回调中的帧解析与数值映射

状态反馈的生命周期始于 SocketCAN 接收线程中的回调函数。三个品牌实现了各自的 can_rx_cbk()canfd_rx_cbk(),负责将原始字节流解码为物理量。尽管帧格式各异,但它们共享同一套数值转换基础设施。

位宽与映射工具

utils.hpp 提供了帧解析的核心数学工具。bitmax<T>(n) 计算 n 位无符号整数的最大值 (1<<n)-1,用于确定量化范围的上界。range_map() 则执行线性映射,将无符号整数值映射到对称的物理量区间。

以 DM 电机为例,位置使用 16 位无符号整数编码,速度和力矩各使用 12 位无符号整数编码。在 can_rx_cbk() 中,解析逻辑首先提取原始整数值,再通过 range_map() 映射到由电机型号决定的极限参数区间:

pos_int = rx_frame.data[1] << 8 | rx_frame.data[2];
spd_int = rx_frame.data[3] << 4 | (rx_frame.data[4] & 0xF0) >> 4;
t_int   = (rx_frame.data[4] & 0x0F) << 8 | rx_frame.data[5];

motor_pos_ = range_map(pos_int, uint16_t(0), bitmax<uint16_t>(16),
                       -limit_param_.PosMax, limit_param_.PosMax) + motor_zero_offset_;
motor_spd_ = range_map(spd_int, uint16_t(0), bitmax<uint16_t>(12),
                       -limit_param_.SpdMax, limit_param_.SpdMax);
motor_current_ = range_map(t_int, uint16_t(0), bitmax<uint16_t>(12),
                           -limit_param_.TauMax, limit_param_.TauMax);

EVO 的 CAN 2.0 帧格式与 DM 完全一致,因此 can_rx_cbk() 的解析逻辑呈镜像结构。但在 CAN-FD 模式下,EVO 的帧布局发生变化:位置、速度、力矩依次占据前 5 字节,错误码从第 6-7 字节中提取,温度则通过 rx_frame.data[5] - 40.0f 进行偏移解码。

LRO 采用独特的反馈类型系统,其 canfd_rx_cbk()Byte0 的高 3 位表示反馈类型(Type 1 为常规状态反馈),低 5 位直接承载错误码。位置、速度、力矩的位宽分配与 DM/EVO 相同(16/12/12 位),但温度字段的解码公式为 static_cast<int>(rx_frame.data[6]) - 25

Sources: utils.hpp, dm_motor_driver.cpp, evo_motor_driver.cpp, lro_motor_driver.cpp

帧格式对比总览

graph TD
    A[CAN/CANFD 接收线程] --> B{品牌协议}
    B -->|DM CAN 2.0| C[Byte0高4位: 错误码<br/>Byte1-2: Pos(16bit)<br/>Byte3-4: Spd(12bit)<br/>Byte4-5: Torque(12bit)<br/>Byte6: MOS温度<br/>Byte7: 绕组温度]
    B -->|EVO CAN 2.0| D[Byte0: 电机ID高4位<br/>Byte1-2: Pos(16bit)<br/>Byte3-4: Spd(12bit)<br/>Byte4-5: Torque(12bit)<br/>Byte6: 错误码<br/>Byte7: 温度]
    B -->|EVO CAN-FD| E[Byte0-1: Pos(16bit)<br/>Byte2-3: Spd(12bit)<br/>Byte3-4: Torque(12bit)<br/>Byte5: 温度-40<br/>Byte6-7: 错误码]
    B -->|LRO CAN-FD| F[Byte0高3位: 反馈类型<br/>Byte0低5位: 错误码<br/>Byte1-2: Pos(16bit)<br/>Byte3-4: Spd(12bit)<br/>Byte4-5: Torque(12bit)<br/>Byte6: 温度-25<br/>Byte7: MOS温度]
    
    C --> G[MotorDriver 原子状态变量]
    D --> G
    E --> G
    F --> G

三品牌错误码体系详解

每个品牌在派生类头文件中定义了独立的错误码枚举。这些错误码在硬件层面由电机 MCU 生成,通过反馈帧的特定位字段传递至上位机。

达妙 DM 错误码

DM 电机的错误码位于 Byte0 的高 4 位(>> 4),取值范围 0x00-0x0F。当错误码大于 7 时,接收回调会触发 spdlog 错误日志。

枚举值 十六进制 含义
DM_DOWN 0x00 电机掉线/未使能
DM_UP 0x01 正常上电就绪
DM_OVER_VOLT 0x08 母线过压
DM_UNDER_VOLT 0x09 母线欠压
DM_OVER_CURRENT 0x0A 过流保护
DM_MOS_OVER_TEMP 0x0B MOS 过温
DM_COIL_OVER_TEMP 0x0C 线圈过温
DM_LOST_CONN 0x0D 通信丢失
DM_OVER_LOAD 0x0E 过载保护

DM 的 init_motor() 通过 switch(error_id_) 对全部上述错误码进行逐一匹配,若初始化时检测到非就绪错误,则直接返回对应错误码,阻止控制流程继续。

Sources: dm_motor_driver.hpp, dm_motor_driver.cpp, dm_motor_driver.cpp

EVO 错误码

EVO 在 CAN 2.0 模式下将错误码置于 Byte6,在 CAN-FD 模式下则从 Byte6-7 中提取 7 位错误码(((data[6]<<8)|data[7])>>1 & 0x7F)。这种位域差异意味着同一品牌在不同通信模式下的解析路径必须严格分离。

枚举值 十六进制 含义
EVO_NO_ERROR 0x00 无错误
EVO_OVER_VOLTAGE 0x01 过压
EVO_UNDER_VOLTAGE 0x02 欠压
EVO_OVER_CURRENT 0x03 过流
EVO_MOS_OVER_TEMP 0x09 MOS 过温
EVO_COIL_OVER_TEMP 0x0A 线圈过温
EVO_ENCODER_ERROR 0x0B 编码器故障
EVO_OVERLOAD 0x0F 过载
EVO_COMM_LOST 0x10 通信丢失
EVO_UNKNOWN_ERROR 0xFF 未知错误

EVO 的 clear_motor_error() 实现为发送 EVO_CMD_DISABLE(0xFD),即通过失能电机来复位错误状态,随后需重新调用 lock_motor() 使能。

Sources: evo_motor_driver.hpp, evo_motor_driver.cpp, evo_motor_driver.cpp, evo_motor_driver.cpp

LeadRobot (LRO) 错误码

LRO 的错误码嵌入在反馈帧 Byte0 的低 5 位(data[0] & 0x1F),与反馈类型(高 3 位)共享同一字节。这种紧凑的位域设计是 LRO 协议的核心特征。

枚举值 十六进制 含义
LRO_NO_ERROR 0x00 无错误
LRO_MOTOR_OVERHEAT 0x01 电机过热
LRO_OVER_CURRENT 0x02 过流
LRO_UNDER_VOLTAGE 0x03 欠压
LRO_ENCODER_ERROR 0x04 编码器错误
LRO_BRAKE_OVERVOLT 0x06 制动过压
LRO_DRV_ERROR 0x07 驱动器错误

LRO 的 clear_motor_error_lro() 采用两步策略:先发送 LRO_CMD_DISABLE 失能电机,延时后发送 LRO_CMD_ENABLE 重新使能。这与 DM 的单命令清错和 EVO 的单命令失能清错形成对比。

Sources: lro_motor_driver.hpp, lro_motor_driver.cpp, lro_motor_driver.cpp, lro_motor_driver.cpp

错误处理与状态监控机制

接收时实时日志告警

三个品牌的回调函数均实现了错误日志的即时上报。当解析出非零错误码时,会调用 logger_->error() 记录总线接口名、电机 ID 和错误码的十六进制值。这一设计确保了故障的第一时间可观测性,无需等待控制轮询。

if (error_id_ > 0) {
    logger_->error("can_interface: {0}\tmotor_id: {1}\terror_id: 0x{2:x}",
                   can_interface_, motor_id_, (uint32_t)error_id_);
}

初始化时的错误拦截

init_motor() 是错误码进入用户逻辑的关键闸口。所有品牌均在初始化序列的最后一步调用 refresh_motor_status() 主动查询电机状态,随后通过 switch-case 对已知错误码进行显式处理。若电机处于错误状态,初始化将返回具体的错误码而非布尔值,便于上层应用根据错误类型决定重试策略或停机流程。

通信超时检测

各派生类维护 response_count_(原子整型),每次发送命令时递增,在接收回调中归零。这一机制为上层提供了简单的心跳检测手段:若 get_response_count() 返回值持续上升,则意味着该电机在总线上已失联。结合 CommType 与总线接口名,用户可以在多总线、多电机拓扑中快速定位离线节点。

Sources: dm_motor_driver.cpp, evo_motor_driver.cpp, lro_motor_driver.cpp, motor_driver.hpp

主动状态刷新策略

refresh_motor_status() 提供了主动请求电机回传当前状态的接口,但三个品牌的实现策略存在显著差异,反映了各自硬件协议的设计哲学。

品牌 实现方式 命令特征
DM 发送专用查询帧 can_id=0x7FF Byte2=0xCC, Byte3=0x00,后接电机 ID
EVO 发送使能帧 can_id=motor_id_ 全 0xFF 填充,末尾 Byte7=EVO_CMD_ENABLE(0xFC)
LRO 调用 motor_mit_cmd(0,0,0,0,0) 发送零力矩 MIT 指令,触发 Type-1 反馈

DM 使用广播 ID 0x7FF 搭配电机 ID 作为数据载荷,属于主从查询模式;EVO 则直接对目标电机 ID 发送使能帧,利用其协议中使能命令自带状态回传特性;LRO 没有独立的状态查询命令,因此通过发送零指令 MIT 控制帧来被动获取反馈。理解这些差异对于设计高频率状态轮询循环至关重要——LRO 的刷新本质上是控制命令,频繁调用可能干扰实际控制输出。

Sources: dm_motor_driver.cpp, evo_motor_driver.cpp, lro_motor_driver.cpp

Python SDK 中的状态访问

通过 pybind11,C++ 基类的所有状态 getter 被无缝暴露至 Python。pybind_module.cpp 中依次绑定了 get_error_idget_motor_posget_motor_spdget_motor_currentget_motor_temperature,使得 Python 用户能够以零开销直接读取原子状态变量。

motor.refresh_motor_status()
print(f"Pos: {motor.get_motor_pos():.4f} rad")
print(f"Err: 0x{motor.get_error_id():02X}")

由于底层状态变量均为 std::atomic,Python GIL 的获取与释放不会与 CAN 接收线程产生数据竞争,但用户仍需注意 refresh_motor_status() 与 getter 调用之间的时间差——refresh 触发总线请求,而 getter 读取的是最近一次回调更新的缓存值。

Sources: pybind_module.cpp

错误清除与恢复流程

当硬件故障根因解除后,需要调用 clear_motor_error() 复位电机的保护状态。不同品牌的恢复语义如下:

sequenceDiagram
    participant App as 用户应用
    participant MD as MotorDriver
    participant Bus as CAN/CANFD 总线
    
    App->>MD: clear_motor_error()
    alt DM
        MD->>Bus: 发送清错命令 (0xFB)
    else EVO
        MD->>Bus: 发送失能命令 (0xFD)
        Note over MD,Bus: 需重新 lock_motor() 使能
    else LRO
        MD->>Bus: 发送失能命令 (0x07)
        MD->>MD: sleep(normal_sleep_time)
        MD->>Bus: 发送使能命令 (0x06)
    end
    Bus-->>MD: 状态反馈帧 (error_id=0)
    MD-->>App: 错误码清零

DM 的清错命令是独立操作码 0xFB,清错后电机保持使能状态;EVO 和 LRO 则通过失能电机实现错误复位,其中 LRO 自动完成失能-使能的序列,而 EVO 需要用户手动重新调用 lock_motor()。这些语义差异在多品牌混合同一总线的系统中尤为关键,错误的恢复流程可能导致电机意外进入自由态。

Sources: dm_motor_driver.cpp, evo_motor_driver.cpp, lro_motor_driver.cpp

阅读延伸与下一步

状态反馈与错误码解析是控制闭环的数据根基。若你已掌握本文内容,建议按以下路径继续深入: