MIT阻抗控制(MIT Impedance Control)是本SDK支持的最核心控制模式,它允许用户同时指定目标位置、目标速度、位置刚度、速度阻尼和前馈力矩五个自由度,使电机对外呈现出可编程的弹簧-阻尼-质量特性。本文档将系统阐述其控制学原理、跨品牌抽象接口设计、数值映射机制,以及DM、EVO、LeadRobot三大品牌电机在CAN/CAN-FD总线上的具体协议编码差异。阅读本文前,建议先熟悉MotorDriver抽象基类与接口设计和工厂模式与多品牌电机实例化中的对象创建与生命周期管理。
阻抗控制的核心原理
MIT阻抗控制的本质是一种外环混合力位控制。电机驱动器内部根据用户下发的五个参数实时计算输出力矩,其离散形式的动力学方程为:
$$\tau = K_p \cdot (p_{des} - p_{act}) + K_d \cdot (v_{des} - v_{act}) + \tau_{ff}$$
其中 $K_p$ 为位置刚度系数,$K_d$ 为速度阻尼系数,$\tau_{ff}$ 为前馈力矩。当实际位置偏离目标位置时,$K_p$ 项产生恢复力;当实际速度与目标速度不一致时,$K_d$ 项产生耗散力;$\tau_{ff}$ 则允许上层控制器直接注入已知动态补偿(如重力补偿或惯性前馈)。通过调节这五个参数,电机可以等效为从完全刚性跟踪($K_p$ 极大)到纯力矩控制($K_p=K_d=0$)之间的任意动态行为。在 MotorDriver 基类中,该模式被定义为枚举值 MIT = 1,与 POS、SPD 等模式并列。Sources: motor_driver.hpp
抽象接口与双重调用范式
SDK在 MotorDriver 基类中为MIT控制提供了两层接口。第一层是标量接口 motor_mit_cmd(float f_p, float f_v, float f_kp, float f_kd, float f_t),适用于单电机独立控制场景;第二层是指针接口 motor_mit_cmd(float* f_p, float* f_v, float* f_kp, float* f_kd, float* f_t),专为多电机批处理或推理引擎集成设计,允许以连续内存块的形式批量传入参数,具体硬件实现负责将这些数据提取并打包到总线特定的负载中。
从架构角度看,标量接口在所有驱动中均为纯虚函数强制实现,而指针接口仅在支持CAN-FD多电机同步帧的品牌(EVO、LeadRobot)中提供有效实现,DM系列则为空实现。这种设计使得上层控制算法可以在不感知品牌差异的情况下,通过同一套API完成从单电机调试到多机协同部署的平滑过渡。Sources: motor_driver.hpp
classDiagram
class MotorDriver {
+motor_mit_cmd(float f_p, float f_v, float f_kp, float f_kd, float f_t)*
+motor_mit_cmd(float* f_p, float* f_v, float* f_kp, float* f_kd, float* f_t)*
}
class DmMotorDriver {
+motor_mit_cmd(float, float, float, float, float)
+motor_mit_cmd(float*, float*, float*, float*, float*) {}
}
class EvoMotorDriver {
+motor_mit_cmd(float, float, float, float, float)
+motor_mit_cmd(float*, float*, float*, float*, float*)
}
class LroMotorDriver {
+motor_mit_cmd(float, float, float, float, float)
+motor_mit_cmd(float*, float*, float*, float*, float*)
}
MotorDriver <|-- DmMotorDriver
MotorDriver <|-- EvoMotorDriver
MotorDriver <|-- LroMotorDriver
参数处理流水线:从浮点到总线比特
无论哪个品牌,所有MIT命令在进入总线前都必须经过同一套三段式参数处理流水线:零偏补偿 → 限幅保护 → 线性量化。首先,驱动会从目标位置中减去 motor_zero_offset_,确保控制指令始终相对于用户设定的机械零位;随后,limit() 模板函数将五个参数裁剪到各型号电机的物理安全边界内,防止超限指令损坏硬件;最后,range_map() 将浮点量线性映射到协议规定的固定位宽无符号整数,例如16位位置量覆盖 $[-P_{max}, +P_{max}]$ 的完整行程。Sources: utils.hpp, dm_motor_driver.cpp
range_map 的实现采用简单的线性插值公式:
static_cast<U>(static_cast<double>((val - in_min)) * (out_max - out_min) / (in_max - in_min) + out_min);
而 bitmax<uint16_t>(n) 则用于快速计算 $2^n - 1$,作为量化输出的满量程值。这套流水线保证了不同品牌的电机在物理单位层面拥有一致的语义,同时在总线层面又严格遵循各自的位宽约束。Sources: utils.hpp
协议编码实现对比
尽管参数语义完全一致,但三大品牌电机在CAN/CAN-FD帧中的比特布局存在显著差异,这直接反映了各厂商硬件MCU的固件协议设计。
DM与EVO:经典MIT 8字节紧凑编码
DM和EVO电机共享同一套经典的MIT 8字节控制帧格式,使用标准CAN ID(电机ID本身)作为目标地址。在8字节数据段中,位置占16位,速度、$K_p$、$K_d$、力矩各占12位,通过字节级别的移位与掩码操作完成比特拼接。具体布局为:Byte0-1存储位置高8位与低8位;Byte2存储速度高8位;Byte3低4位存储速度低4位、高4位存储 $K_p$ 高4位;Byte4存储 $K_p$ 低8位;Byte5存储 $K_d$ 高8位;Byte6低4位存储 $K_d$ 低4位、高4位存储力矩高4位;Byte7存储力矩低8位。这种编码方式将一个64位空间切割为 $16+12+12+12+12=64$ 位,没有冗余,对标准CAN 2.0的8字节负载做到了极致利用。Sources: dm_motor_driver.cpp, evo_motor_driver.cpp
LeadRobot:64位整体打包编码
LeadRobot电机采用了一种结构化的64位打包方案,通过 uint64_t 一次性构造完整数据负载后再拆分为8字节。其比特分配为:模式标识占3位(MIT模式固定为 0x00),$K_p$ 占12位,$K_d$ 占9位(区别于DM/EVO的12位),位置占16位,速度占12位,力矩占12位,总计 $3+12+9+16+12+12=64$ 位。值得注意的是,LRO的 $K_d$ 位宽仅为9位,这意味着其阻尼系数精度低于DM和EVO,在需要精细调节阻尼的高频振动抑制场景中需格外关注量化误差。Sources: lro_motor_driver.cpp
下表汇总了三种品牌的MIT控制帧关键差异:
| 特性 | DM电机 | EVO电机 | LeadRobot电机 |
|---|---|---|---|
| 适用总线 | CAN / CAN-FD | CAN / CAN-FD | CAN-FD |
| 帧ID | motor_id_ |
motor_id_ |
motor_id_ |
| 位置位宽 | 16位 | 16位 | 16位 |
| 速度位宽 | 12位 | 12位 | 12位 |
| $K_p$ 位宽 | 12位 | 12位 | 12位 |
| $K_d$ 位宽 | 12位 | 12位 | 9位 |
| 力矩位宽 | 12位 | 12位 | 12位 |
| 编码风格 | 字节移位拼接 | 字节移位拼接 | uint64_t 整体打包 |
| 指针批处理 | 不支持(空实现) | 支持(CAN-FD 64B) | 支持(CAN-FD 64B) |
CAN-FD多电机批处理:One-to-Many 优化
当使用CAN-FD接口时,EVO和LeadRobot驱动充分利用了64字节数据段的优势,实现了单帧多电机同步控制。其工作原理是:在驱动器构造函数中,同一CAN接口上的所有 EvoMotorDriver 或 LroMotorDriver 实例会被注册到一个静态的 bus_registry_ 映射表中;当用户调用指针版本的 motor_mit_cmd 时,驱动器会锁定该注册表,遍历同接口上的所有电机实例,根据各自的 motor_index_ 将参数写入64字节帧的对应8字节槽位,最终通过一次 transmit() 调用完成最多8台电机的同步下发。
EVO的批处理帧使用固定CAN ID 0x20(EVOFD_MIT_ID),每个8字节槽位的编码方式与单电机模式完全相同;LRO则使用扩展帧ID 0x8080(带 CAN_EFF_FLAG 标志),每个槽位同样遵循其64位打包协议。这种机制将总线占用率降低了约8倍,对于高帧率(1kHz)的多自由度机器人控制至关重要。DM驱动由于仅支持标准CAN的8字节负载,其指针版本为空实现,调用时会静默返回。Sources: evo_motor_driver.cpp, lro_motor_driver.cpp
sequenceDiagram
participant User as 上层控制器
participant Registry as bus_registry_
participant CANFD as CAN-FD总线
User->>EvoMotorDriver_A: motor_mit_cmd(float* ...)
EvoMotorDriver_A->>Registry: lock_guard 遍历同接口电机
loop 最多8个电机槽位
Registry->>EvoMotorDriver_A: 写入 slot 0
Registry->>EvoMotorDriver_B: 写入 slot 1
Registry->>EvoMotorDriver_N: 写入 slot N
end
EvoMotorDriver_A->>CANFD: transmit(64B帧, ID=0x20)
控制模式状态机与初始化
MIT模式并非随时可用,其启用遵循严格的状态机。在 init_motor() 中,各品牌驱动的标准流程为:先发送 unlock_motor() 使电机进入被动响应状态,再调用 set_motor_control_mode(MIT) 将内部控制模式寄存器切换为MIT,随后发送 lock_motor() 使能功率级,最后通过 refresh_motor_status() 获取初始状态。这一顺序确保了电机在使能前已完成模式配置,避免在错误的控制律下突然上电。
更为精细的处理体现在 LroMotorDriver::set_motor_control_mode() 中:当从非MIT模式(如位置模式或速度模式)切换到MIT模式时,LRO驱动会自动发送一条全零MIT命令(pos=0, spd=0, kp=0, kd=0, torque=0)并等待一个控制周期。这一步是为了同步电机内部的状态变量,防止模式切换瞬间因参考值跳变而产生冲击转矩。Sources: dm_motor_driver.cpp, evo_motor_driver.cpp, lro_motor_driver.cpp
此外,所有驱动的标量 motor_mit_cmd 实现都内置了模式守卫:若当前 motor_control_mode_ 不是MIT,函数不会立即发送控制帧,而是先调用 set_motor_control_mode(MIT) 并返回,待下一控制周期才真正下发数据。这意味着首次调用MIT命令时会有一个周期的延迟,用户在设计高频控制环时需对此有所预期。Sources: dm_motor_driver.cpp, evo_motor_driver.cpp, lro_motor_driver.cpp
各品牌MIT参数限制速查
不同型号电机的物理极限直接决定了 limit() 的裁剪边界,进而影响控制器的可用参数空间。下表列出了当前SDK支持的各型号电机在MIT模式下的默认限制值(单位:rad、rad/s、N·m):
| 型号 | 位置上限 $P_{max}$ | 速度上限 $V_{max}$ | 力矩上限 $\tau_{max}$ | $K_p$ 上限 | $K_d$ 上限 |
|---|---|---|---|---|---|
| DM4340P_48V | 12.5 | 20 | 28 | 500 | 5 |
| DM10010L_48V | 12.5 | 25 | 200 | 500 | 5 |
| EVO431040 | 12.5 | 20 | 18 | 500 | 5 |
| EVO811825 | 12.5 | 10 | 50 | 250 | 50 |
| EVO811832 | 12.5 | 10 | 50 | 250 | 50 |
| LRO_5550 | 12.5 | 45 | 40 | 500 | 5 |
| LRO_6562 | 12.5 | 45 | 40 | 500 | 5 |
| LRO_8462 | 12.5 | 30 | 60 | 500 | 5 |
| LRO_10062 | 12.5 | 25 | 80 | 500 | 5 |
这些限制值在驱动器构造时从静态数组 *_limit_param 加载到实例的 limit_param_ 成员中。若应用需要突破默认限制(例如在减速比不同的关节上),可通过各品牌驱动提供的寄存器写入接口修改电机固件内部的映射极值,但这属于进阶操作,不在本文档讨论范围内。Sources: dm_motor_driver.cpp, evo_motor_driver.cpp, lro_motor_driver.cpp
使用示例
以下示例展示了如何通过Python SDK使单台DM电机工作在MIT阻抗模式下,并施加一个虚拟弹簧行为:
from motors_py import MotorDriver, MotorControlMode
# 创建DM4340电机实例
motor = MotorDriver.create_motor(
motor_id=1,
interface_type="can",
interface="can0",
motor_type="DM",
motor_model=0
)
motor.init_motor()
# MIT模式:目标位置=1.0 rad,目标速度=0,Kp=50,Kd=2,前馈力矩=0
motor.motor_mit_cmd(1.0, 0.0, 50.0, 2.0, 0.0)
在C++中,若使用EVO或LRO电机且需要同步控制多台电机,可调用指针重载版本。具体做法是将各关节的目标值填入连续数组,并确保同接口上的电机已通过工厂函数正确创建。需要注意的是,DM驱动不支持此批处理接口,若误用会在运行时静默无操作。
MIT模式 vs. 位置/速度模式
理解MIT模式与SDK中其他控制模式的本质差异,有助于在正确的场景下做出技术选型。位置模式(POS)在总线上仅传输目标位置和最大允许速度,电机内部自行完成PID伺服闭环,用户无法干预刚度与阻尼;速度模式(SPD)仅传输目标转速,同样由内部速度环独立完成。相比之下,MIT模式将外环阻抗特性完全开放给用户,电机内部不再运行固定的位置或速度PID,而是直接根据用户给定的 $K_p$、$K_d$ 和 $\tau_{ff}$ 计算电流指令。
这意味着MIT模式更适合以下场景:需要与环境进行力交互的柔顺操作、基于模型预测控制(MPC)或强化学习策略直接输出阻抗参数的应用、以及需要快速切换刚度特性的仿人机器人关节。而位置模式更适合传统的点对点轨迹跟踪任务。Sources: motor_driver.hpp
本文档详细拆解了MIT阻抗控制在SDK中的原理抽象与品牌级实现差异。若您已理解MIT的工作机制,下一步可查阅位置与速度控制模式以对比不同控制模式的适用边界,或参考状态反馈与错误码解析了解MIT控制下的电机 telemetry 数据解码方式。