位置控制模式(POS)与速度控制模式(SPD)是伺服电机最常用的两种底层控制范式:前者让电机跟踪目标角度,后者让电机维持目标转速。本文聚焦 MotorDriver 抽象层对这两种模式的统一接口定义,并深入解析达妙 DM、EVO 与 LeadRobot(LRO)三大品牌电机在协议帧结构、数值编码与模式切换机制上的差异,帮助开发者在不触及 MIT 阻抗控制细节的前提下,正确使用 SDK 的位置与速度指令。
控制模式抽象定义
MotorDriver 基类通过枚举 MotorControlMode_e 将控制模式划分为四类:NONE = 0、MIT = 1、POS = 2、SPD = 3。其中 POS 与 SPD 分别对应位置伺服环和速度伺服环。基类为两种模式各暴露一条纯虚命令接口:motor_pos_cmd(float pos, float spd, bool ignore_limit) 负责下发目标位置与最大允许速度,motor_spd_cmd(float spd) 负责下发目标转速。这两个接口的调用语义在所有子类中保持一致:传入的 pos 与 spd 均以 弧度(rad) 和 弧度每秒(rad/s) 为单位,驱动内部会自动扣除 motor_zero_offset_ 零点偏移并做限幅保护。
classDiagram
class MotorDriver {
+enum MotorControlMode_e
+motor_pos_cmd(pos, spd, ignore_limit)*
+motor_spd_cmd(spd)*
+set_motor_control_mode(mode)*
+get_motor_pos() float
+get_motor_spd() float
}
class DmMotorDriver {
+motor_pos_cmd(pos, spd, ignore_limit)
+motor_spd_cmd(spd)
}
class EvoMotorDriver {
+motor_pos_cmd(pos, spd, ignore_limit)
+motor_spd_cmd(spd)
}
class LroMotorDriver {
+motor_pos_cmd(pos, spd, ignore_limit)
+motor_spd_cmd(spd)
}
MotorDriver <|-- DmMotorDriver
MotorDriver <|-- EvoMotorDriver
MotorDriver <|-- LroMotorDriver
Sources: motor_driver.hpp, motor_driver.hpp
位置控制模式详解
位置控制模式的核心语义是“以不超过给定速度上限的前提下,运动到目标角度”。三个品牌的驱动实现均遵循 motor_pos_cmd → motor_zero_offset_ 扣除 → limit() 限幅 → 总线发送的流水线,但在 CAN 帧 ID、数据长度与数值编码上存在显著差异。
DM 电机 使用 0x100 + motor_id_ 作为位置指令专用 CAN ID,帧长固定 8 字节,前 4 字节为 float 目标位置,后 4 字节为 float 速度上限,均按小端字节序直接拷贝内存。该设计将位置环与速度环的通信 ID 空间完全分离,POS 指令不会与 MIT 指令(使用 motor_id_)冲突。
EVO 电机 复用 motor_id_ 作为 CAN ID,帧长同样为 8 字节,前 4 字节为 float 目标位置,后 4 字节为 float 速度上限。由于 ID 与 MIT 模式相同,EVO 依靠电机内部的模式寄存器状态来区分当前应解析为位置指令还是阻抗控制指令。
LRO 电机 采用紧凑的位域打包协议,帧长 8 字节,CAN ID 为 motor_id_。最高 3 位为模式标识 0x01(LRO_MODE_POS),紧接着的 32 位为 IEEE-754 单精度浮点位置,但单位在总线侧转换为 度(deg) 而非弧度;中间 15 位为速度限制,单位为 RPM × 10;低 14 位包含 12 位电流限制与 2 位应答标志。这意味着调用 motor_pos_cmd(3.14, 1.0, false) 时,LRO 驱动会先扣除零点偏移,将弧度转为角度,再与速度限制、电流限制一起打包成单个 uint64_t 写入帧数据区。
| 品牌 | CAN ID | 帧长 | 位置编码 | 速度编码 | 备注 |
|---|---|---|---|---|---|
| DM | 0x100 + motor_id |
8 B | float32 (rad) | float32 (rad/s) | ID 空间与 MIT 分离 |
| EVO | motor_id |
8 B | float32 (rad) | float32 (rad/s) | ID 复用,依赖内部模式寄存器 |
| LRO | motor_id |
8 B | float32 (deg) | uint15 (RPM×10) | 位域打包,含电流限制与应答位 |
Sources: dm_motor_driver.cpp, evo_motor_driver.cpp, lro_motor_driver.cpp
速度控制模式详解
速度控制模式让电机以指定转速持续旋转,接口更简洁,仅需一个 spd 参数。三种电机在速度指令的编码策略上延续了位置模式的风格差异。
DM 电机 使用专用 CAN ID 0x200 + motor_id_,帧长 4 字节,直接发送一个 float32 转速值,单位仍为 rad/s。union32_t 被用于安全地进行 float 到字节数组的转换,避免未定义行为。
EVO 电机 复用 motor_id_ 作为 CAN ID;在 CAN 2.0 下帧长为 4 字节,在 CAN-FD 下帧长为 8 字节(有效载荷仍为前 4 字节的 float32,后 4 字节未使用)。与 DM 不同,EVO 的速度指令与位置指令共享同一 ID,完全依赖电机内部当前所处的控制模式来决定如何解析。
LRO 电机 速度帧长 7 字节,CAN ID 为 motor_id_。首字节高 3 位为模式标识 0x02(LRO_MODE_SPD),低 5 位包含 2 位应答标志;随后 4 字节为 float32 转速,但单位转换为 RPM;最后 2 字节为 16 位电流限制(默认 65535)。与位置模式类似,LRO 的速度指令也需要在驱动层完成 rad/s → RPM 的线性换算。
| 品牌 | CAN ID | 帧长 | 速度编码 | 额外字段 |
|---|---|---|---|---|
| DM | 0x200 + motor_id |
4 B | float32 (rad/s) | 无 |
| EVO | motor_id |
4/8 B | float32 (rad/s) | 无 |
| LRO | motor_id |
7 B | float32 (RPM) | 模式位 + 电流限制 uint16 |
Sources: dm_motor_driver.cpp, evo_motor_driver.cpp, lro_motor_driver.cpp
模式切换机制
三个品牌的驱动均实现了**惰性模式切换(Lazy Mode Switch)**策略,这是保证控制循环安全性的关键设计。以 motor_pos_cmd 为例,驱动首先检查当前内部状态 motor_control_mode_ 是否已为 POS;若不是,则调用 set_motor_control_mode(POS) 并立即返回,本周期不会发送运动指令。这意味着用户在切换控制模式后的第一次调用仅完成模式注册,第二次调用才开始真正下发目标位置或速度。
flowchart TD
A[调用 motor_pos_cmd] --> B{motor_control_mode_ == POS?}
B -->|否| C[set_motor_control_mode(POS)]
C --> D[return 本周期不发送运动指令]
B -->|是| E[扣除零点偏移 & 限幅]
E --> F[构造 CAN/CANFD 帧]
F --> G[transmit 到总线]
这种设计避免了在电机尚未完成模式切换时误发不兼容的控制帧,但要求调用者在控制循环中至少预留一个周期的切换缓冲。DM 通过向寄存器 DM_CTRL_MODE(RID = 10)写入模式值来完成硬件切换;EVO 在经典 CAN 下向寄存器 11 写入 0x02,而在 CAN-FD 下仅更新内部状态;LRO 则完全依赖内部状态更新,其硬件模式由每帧首字节的位域模式位实时指示,无需额外寄存器写入。LRO 在切回 MIT 模式时还有特殊保护:会先发送一条零值 MIT 指令,防止阻抗环从非零状态突兀切入导致抖动。
Sources: dm_motor_driver.cpp, evo_motor_driver.cpp, lro_motor_driver.cpp, lro_motor_driver.cpp
数值限幅与零点偏移
在位置与速度控制路径中,所有品牌均通过 utils.hpp 提供的模板函数进行数值保护。limit(val, min, max) 基于 std::clamp 实现饱和限幅;range_map(val, in_min, in_max, out_min, out_max) 则负责将物理量线性映射到无符号整型位域。motor_zero_offset_ 在每一条位置相关指令(POS 与 MIT)中都会被扣除,确保用户层始终以安装后的机械零点为参考。速度指令由于只涉及相对运动,不扣除零点偏移。
Sources: utils.hpp, utils.hpp, dm_motor_driver.cpp, evo_motor_driver.cpp, lro_motor_driver.cpp
状态反馈与闭环控制
在 POS 与 SPD 模式下,电机仍会周期性返回实时状态。DM 与 EVO 的反馈帧结构一致:16 位位置、12 位速度、12 位力矩,通过 range_map 逆映射回物理量;LRO 则采用 type-1 反馈(LRO_FB_TYPE1),在 8 字节中打包位置、速度、电流、温度、MOS 温度、母线电压与母线电流。无论电机处于 POS、SPD 还是 MIT 模式,用户均可通过 refresh_motor_status() 主动请求一次状态更新,或依赖总线上的周期性回传。
Sources: dm_motor_driver.cpp, evo_motor_driver.cpp, lro_motor_driver.cpp
Python 接口
通过 pybind11 暴露的 Python SDK 中,motor_pos_cmd 与 motor_spd_cmd 的签名与 C++ 层完全一致。MotorControlMode 枚举同样向 Python 暴露了 POS 与 SPD 两种状态,便于脚本层在切换模式时做断言检查。
from motors_py import MotorDriver, MotorControlMode
motor = MotorDriver.create_motor(
motor_id=1, interface_type="canfd", interface="can0",
motor_type="DM", motor_model=0
)
# 位置控制
motor.set_motor_control_mode(MotorControlMode.POS)
motor.motor_pos_cmd(pos=1.57, spd=3.0, ignore_limit=False)
# 速度控制
motor.set_motor_control_mode(MotorControlMode.SPD)
motor.motor_spd_cmd(spd=1.0)
Sources: pybind_module.cpp, pybind_module.cpp
与 MIT 模式的边界
POS 与 SPD 模式将位置环或速度环完全托管给电机内部的伺服控制器,用户无需指定 Kp、Kd 或前馈力矩。这与 MIT 阻抗控制原理与实现 中需要显式传入刚度与阻尼参数的阻抗控制形成鲜明对比。若应用场景需要同时调节刚性、阻尼和力前馈,应使用 MIT 模式而非本文所述的 POS/SPD 模式。
相关阅读与下一步
- 若需要了解
MotorDriver基类的完整接口契约,请参阅 MotorDriver 抽象基类与接口设计。 - 若需要排查电机在位置或速度模式下的异常报警,请参阅 状态反馈与错误码解析。
- 若需要理解工厂函数如何根据
motor_type字符串实例化对应驱动,请参阅 工厂模式与多品牌电机实例化。