MotorDriver 是本 SDK 的唯一电机抽象入口。它的设计目标是在统一语义层上屏蔽达妙 DM、EVO、LeadRobot 等不同品牌电机的协议差异与硬件特性,使上层控制算法只需面向同一组接口编程,而无需关注底层总线格式或寄存器语义。本章聚焦该抽象基类的接口划分、状态模型、线程安全设计及其与通信层的解耦关系,帮助你建立对“多品牌统一驱动”架构的系统性认知。
Sources: motor_driver.hpp
设计哲学与分层定位
整个驱动栈采用**“抽象基类 + 协议适配层 + 品牌特化实现”**的三层结构。MotorDriver 处于最顶层,只声明电机作为“执行器”应有的通用行为契约,不持有任何直接操作硬件的文件描述符或套接字。它将总线访问委托给独立的协议抽象(MotorsCAN / MotorsCANFD),而品牌相关的帧解析、量纲转换、限幅逻辑则下沉到 DmMotorDriver、EvoMotorDriver、LroMotorDriver 等子类。这种分层使得新增一个电机品牌时,上层控制代码与 Python 绑定层均无需修改,仅需提供一份新的子类实现即可。
Sources: motor_driver.hpp, can_iso.hpp, canfd_iso.hpp
类继承体系与模块关系
下图展示了 MotorDriver 在代码库中的核心位置:它向下被三个品牌子类继承,向右依赖协议抽象接口,向上通过 pybind11 暴露给 Python 生态。
classDiagram
class MotorDriver {
<<abstract>>
+create_motor()$ shared_ptr~MotorDriver~
+lock_motor()*
+unlock_motor()*
+init_motor()* uint8_t
+deinit_motor()*
+set_motor_zero()* bool
+write_motor_flash()* bool
+get_motor_param()* void
+motor_pos_cmd()* void
+motor_spd_cmd()* void
+motor_mit_cmd()* void
+set_motor_control_mode()* void
+refresh_motor_status()* void
+clear_motor_error()* void
+set_motor_id()* void
+reset_motor_id()* void
+get_motor_id() uint8_t
+get_motor_pos() float
+get_motor_spd() float
+get_motor_current() float
+get_motor_temperature() float
+get_error_id() uint8_t
+get_response_count()* int
#logger_: shared_ptr~spdlog~
#motor_id_: uint16_t
#motor_control_mode_: uint8_t
#motor_zero_offset_: double
#motor_pos_: atomic~float~
#motor_spd_: atomic~float~
#motor_current_: atomic~float~
#motor_temperature_: atomic~float~
#error_id_: atomic~uint8_t~
#comm_type_: CommType
#can_interface_: string
}
class DmMotorDriver {
+can_rx_cbk()
+canfd_rx_cbk()
-can_: shared_ptr~MotorsCAN~
-canfd_: shared_ptr~MotorsCANFD~
}
class EvoMotorDriver {
+can_rx_cbk()
+canfd_rx_cbk()
-can_: shared_ptr~MotorsCAN~
-canfd_: shared_ptr~MotorsCANFD~
}
class LroMotorDriver {
+canfd_rx_cbk()
-canfd_: shared_ptr~MotorsCANFD~
}
MotorDriver <|-- DmMotorDriver
MotorDriver <|-- EvoMotorDriver
MotorDriver <|-- LroMotorDriver
class MotorsCAN {
<<abstract>>
+transmit()*
+add_can_callback()*
+remove_can_callback()*
}
class MotorsCANFD {
<<abstract>>
+transmit()*
+add_canfd_callback()*
+remove_canfd_callback()*
}
DmMotorDriver ..> MotorsCAN : uses
DmMotorDriver ..> MotorsCANFD : uses
EvoMotorDriver ..> MotorsCAN : uses
EvoMotorDriver ..> MotorsCANFD : uses
LroMotorDriver ..> MotorsCANFD : uses
Sources: motor_driver.hpp, dm_motor_driver.hpp, evo_motor_driver.hpp, lro_motor_driver.hpp
接口全景:四大职能域
MotorDriver 的公共接口可按职责划分为四大域。下表列出了每个域的核心方法及其契约性质(纯虚或已有默认实现):
| 职能域 | 纯虚接口 | 带默认实现的接口 | 设计意图 |
|---|---|---|---|
| 生命周期 | init_motor, deinit_motor, lock_motor, unlock_motor |
— | 上电初始化、使能/失能电机,各品牌握手流程不同,必须特化 |
| 运动控制 | motor_pos_cmd, motor_spd_cmd, motor_mit_cmd(标量/指针双载) |
— | 统一位置、速度、MIT 阻抗三种控制语义,帧格式由子类封装 |
| 配置管理 | set_motor_zero, write_motor_flash, get_motor_param, set_motor_id, reset_motor_id |
get_motor_id, get_can_name |
零点标定、参数固化、ID 烧录;部分品牌存在空实现或不支持项 |
| 状态观测 | refresh_motor_status, clear_motor_error, get_response_count |
get_motor_pos, get_motor_spd, get_motor_current, get_motor_temperature, get_error_id, get_motor_control_mode |
实时刷新与读取解耦:先调用 refresh 触发总线查询,再通过无锁 getter 读本地缓存 |
Sources: motor_driver.hpp
生命周期与设备管理接口
init_motor()、deinit_motor()、lock_motor()、unlock_motor() 构成了电机设备状态机的最小闭环。它们被声明为纯虚函数,意味着任何新增品牌都必须给出明确的使能与失能语义。例如 DM 电机通过发送 0xFF..FC 与 0xFF..FD 控制帧完成使能/失能,而 EVO 与 LRO 则使用各自的命令码;这些差异被完全封装在子类实现中,基类仅保留统一的调用签名。
Sources: motor_driver.hpp, dm_motor_driver.cpp
控制指令接口:标量与指针双载
运动控制接口包含三种模式:位置伺服(motor_pos_cmd)、速度伺服(motor_spd_cmd)、MIT 阻抗控制(motor_mit_cmd)。其中 MIT 接口提供了两套重载:一套接收五个标量参数(f_p, f_v, f_kp, f_kd, f_t),适合单电机调用;另一套接收五个指针(float*),面向批处理或多电机推理引擎的连续内存布局。需要特别指出的是,基类在接口层面为批处理预留了统一契约,但具体子类是否实现该指针重载取决于品牌协议能力——例如 DmMotorDriver 当前为空实现,而 EvoMotorDriver 与 LroMotorDriver 则提供了实际实现。
Sources: motor_driver.hpp, dm_motor_driver.hpp, evo_motor_driver.hpp
控制模式枚举
基类通过嵌套枚举 MotorControlMode_e 定义了四种控制模式:NONE、MIT、POS、SPD。该枚举在 C++ 与 Python 层保持一致,用于在运行时标识电机当前所处的控制律。子类在 set_motor_control_mode 的实现中,通常会依据该枚举值切换内部状态机,并在后续控制指令发送时选择对应的帧格式与寄存器映射。
Sources: motor_driver.hpp, pybind_module.cpp
配置与持久化接口
set_motor_zero()、write_motor_flash()、set_motor_id() 和 reset_motor_id() 这组接口负责硬件级配置。基类将它们定义为纯虚函数,确保每个子类必须显式声明对“零点标定”“参数固化”“ID 修改”的支持策略。实际代码中,部分品牌的某些功能可能表现为空操作(如 DM 的 set_motor_id 与 reset_motor_id 当前为空实现),但这仍属于一种明确的“不支持”声明,避免了上层代码因接口缺失而产生条件编译或动态类型转换的复杂度。
Sources: motor_driver.hpp, dm_motor_driver.hpp
状态反馈与观测接口
为了兼顾实时性与线程安全,基类将“总线查询”与“内存读取”拆分为两个步骤。refresh_motor_status() 负责向物理电机发送查询指令并解析回帧,更新内部原子状态;而 get_motor_pos()、get_motor_spd()、get_motor_current()、get_motor_temperature()、get_error_id() 等 getter 则直接返回本地缓存,不涉及总线 IO。这种**“显式刷新 + 无锁读取”**的设计使得高频控制循环可以在必要时精确控制查询时机,避免隐式阻塞带来的抖动。
Sources: motor_driver.hpp
内部状态模型与线程安全
基类在 protected 区维护了一套原子状态变量:motor_pos_、motor_spd_、motor_current_、motor_temperature_ 以及 error_id_。这些变量使用 std::atomic 封装,确保在 SocketCAN 接收线程(回调中更新)与主控制线程(getter 中读取)之间建立无锁的 happen-before 关系。非频繁变更的成员如 motor_id_、can_interface_、motor_zero_offset_ 则保持普通类型,由构造函数一次性写定。logger_ 作为 spdlog 共享指针被所有子实例共享,统一输出到标准错误并可通过 setup_logger 自定义。
Sources: motor_driver.hpp, utils.hpp
通信层解耦设计
MotorDriver 本身不感知 Linux SocketCAN 或 EtherCAT 的存在。它通过组合方式持有 std::shared_ptr<MotorsCAN> 或 std::shared_ptr<MotorsCANFD>,并注册品牌私有的接收回调(如 can_rx_cbk、canfd_rx_cbk)。MotorsCAN 与 MotorsCANFD 同样是抽象基类,向下再对接 MotorsSocketCAN、MotorsSocketCANFD 等具体后端。这种**“双重抽象”**结构使得电机驱动与总线后端可以独立演进:更换 CAN 后端(例如从 SocketCAN 切到 EtherCAT 桥接)时,驱动代码完全无需改动。
Sources: can_iso.hpp, canfd_iso.hpp, dm_motor_driver.hpp
工厂方法的入口角色
MotorDriver 声明了一个静态工厂方法 create_motor(),其实现位于 src/motor_driver.cpp 中。该方法依据 motor_type 字符串(如 "DM"、"EVO"、"LRO")和 motor_model 整型枚举,在运行时构造对应子类的 shared_ptr 并返回基类指针。工厂的存在使得用户代码可以做到**“面向接口编程,延迟绑定实现”**,而 Python 层也直接复用了这一入口。关于工厂内部的参数映射与扩展机制,详见后续章节。
Sources: motor_driver.hpp, motor_driver.cpp
Python 抽象层暴露
pybind_module.cpp 将 MotorDriver 以 shared_ptr 持有者的形式导出为 Python 类。所有纯虚接口在 Python 侧均表现为普通成员方法,由 pybind11 的虚函数派发机制自动路由到实际子类实现。这意味着 Python 用户同样遵循“基类引用 + 工厂构造”的范式,与 C++ 层保持语义同构。Python 绑定的完整技术细节可参阅语言绑定章节。
Sources: pybind_module.cpp
本章小结
MotorDriver 抽象基类通过纯虚接口定义通用契约、原子变量保证线程安全、组合协议抽象实现总线解耦,以及静态工厂提供统一入口,构建了一个可扩展的多品牌电机驱动骨架。理解这一层的设计意图后,后续阅读可按以下路径深入:
- 若关注运行时对象创建与多态路由,继续阅读 工厂模式与多品牌电机实例化。
- 若关注 CAN/CAN-FD 协议抽象与无锁发送队列,继续阅读 CAN/CANFD协议抽象接口 与 SocketCAN单例与无锁发送队列。
- 若关注具体品牌的帧格式与寄存器语义,可分别阅读 达妙DM电机驱动详解、EVO电机驱动详解 与 LeadRobot电机驱动详解。
- 若关注 MIT 阻抗控制参数含义与实现,继续阅读 MIT阻抗控制原理与实现。