本文面向已具备 C++17 基础和 CMake 项目经验的开发者,提供从工程集成到完成首条电机控制指令的完整路径。阅读完本页,你将能够在自己的 C++ 项目中实例化电机、切换控制模式、发送指令并读取实时反馈,而无需深入协议或驱动实现细节。
Sources: motor_driver.hpp, motor_driver.cpp
前置条件
在开始编码前,请确保已完成以下两步:
- 硬件与接口:CAN 总线接口已正确配置。若尚未完成,请参考 CAN总线接口配置。
- 编译与安装:
roboto_motors库已编译并通过make install或 Debian 包安装到系统。若尚未完成,请参考 编译依赖与安装。
本页示例假设你使用标准 CAN 接口 can0,并链接已安装的 roboto_motors 库。
Sources: CMakeLists.txt, roboto_motorsConfig.cmake
工程集成
roboto_motors 对外暴露单一公共头文件 motor_driver.hpp,并通过 CMake 导出 roboto_motors::roboto_motors 接口目标。在你的 CMakeLists.txt 中只需三行即可集成:
find_package(roboto_motors REQUIRED)
add_executable(my_robot_app main.cpp)
target_link_libraries(my_robot_app PRIVATE roboto_motors::roboto_motors)
该接口目标会自动拉取头文件路径、静态库(motors、dm_motors、evo_motors、lro_motors、motors_can、motors_canfd)以及第三方依赖(fmt、spdlog、Eigen3)。若你是在 ROS 2 工作空间内通过 colcon build 编译,则通过 ament_target_dependencies 或直接用 target_link_libraries 链接即可,因为 ament_cmake 会同步导出库和包含目录。
Sources: CMakeLists.txt, roboto_motorsConfig.cmake
核心工作流
使用 C++ SDK 的典型流程遵循“创建 → 初始化 → 控制循环 → 反初始化”四段式结构。下图展示了单电机标准控制循环的数据流与调用顺序:
flowchart TD
A[1. create_motor<br/>工厂方法实例化] --> B[2. init_motor<br/>解锁并设置模式]
B --> C{控制循环<br/>1kHz 典型频率}
C --> D[set_motor_control_mode<br/>MIT / POS / SPD]
D --> E[发送指令<br/>motor_mit_cmd / motor_pos_cmd / motor_spd_cmd]
E --> F[refresh_motor_status<br/>请求最新反馈]
F --> G[读取状态<br/>get_motor_pos / get_motor_spd / get_motor_current]
G --> C
C --> H[3. deinit_motor<br/>失能并释放资源]
H --> I[电机实例析构<br/>自动注销 CAN 回调]
注意 create_motor 返回 std::shared_ptr<MotorDriver>,因此建议用 auto 或智能指针接收,避免所有权管理错误。进入控制循环前必须先调用 init_motor(),它会执行电机解锁、模式设置、状态刷新并返回错误码;退出前必须调用 deinit_motor(),否则电机可能停留在带电状态。
Sources: motor_driver.hpp, motor_driver.hpp, dm_motor_driver.cpp
创建电机实例
库内部采用工厂模式屏蔽不同品牌电机的构造差异。你只需提供统一参数,即可在运行时生成 DmMotorDriver、EvoMotorDriver 或 LroMotorDriver 实例。
auto motor = MotorDriver::create_motor(
0x01, // motor_id: CAN 节点 ID
"can", // interface_type: "can" | "canfd" | "ethercanfd"
"can0", // interface: SocketCAN 接口名
"DM", // motor_type: "DM" | "EVO" | "LRO"
0, // motor_model: 型号枚举值
0, // master_id_offset: 主站偏移(仅 DM 常用)
0.0 // motor_zero_offset: 零位偏移,单位 rad
);
下表汇总了当前支持电机的型号枚举与常用参数。motor_model 传入的是对应枚举的整数值。
| 品牌 | motor_type | 型号枚举 | 默认值索引 | 支持接口 | 可用控制模式 |
|---|---|---|---|---|---|
| DM(达妙) | "DM" |
DM4340P_48V = 0, DM10010L_48V = 1 |
0 |
CAN / CAN-FD | MIT, 位置, 速度 |
| EVO | "EVO" |
EVO431040 = 0, EVO811825 = 1, EVO811832 = 2 |
0 |
CAN / CAN-FD | MIT |
| LeadRobot | "LRO" |
LRO_5550 = 0, LRO_6562 = 1, LRO_8462 = 2, LRO_10062 = 3 |
0 |
CAN-FD | MIT, 位置, 速度, 电流 |
工厂方法会根据 motor_type 字符串分发到具体实现类,并在构造函数中自动注册 SocketCAN 接收回调。若传入不支持的类型,将抛出 std::runtime_error。
Sources: motor_driver.cpp, dm_motor_driver.hpp, evo_motor_driver.hpp, lro_motor_driver.hpp
电机生命周期管理
电机实例创建后处于“未激活”状态,必须通过生命周期方法切换硬件运行状态。
// 初始化:解锁 → 设置默认模式 → 上锁使能 → 读取状态
uint8_t err = motor->init_motor();
if (err != 0) {
// 根据品牌错误码判断故障类型
std::cerr << "Init failed, error code: " << (int)err << std::endl;
}
// ... 控制循环 ...
// 反初始化:发送失能命令,电机进入自由状态
motor->deinit_motor();
init_motor() 的返回值由各品牌驱动自行定义。例如 DM 电机返回 DMError 枚举,若返回 0x01(DM_UP)表示正常上线,返回 0x0D(DM_LOST_CONN)则表示通信未建立。建议在生产代码中对返回值做分支处理。deinit_motor() 无返回值,但会触发失能帧,释放电机力矩。
Sources: motor_driver.hpp, dm_motor_driver.hpp
控制模式与指令发送
MotorDriver 抽象基类定义了三种控制模式,对应 MotorControlMode_e 枚举:MIT = 1、POS = 2、SPD = 3。在发送任何运动指令前,必须先调用 set_motor_control_mode() 显式切换模式,否则底层驱动可能忽略指令或按旧模式解析数据。
MIT 阻抗控制
MIT 模式同时指定目标位置、速度、刚度、阻尼和前馈力矩,适用于腿式机器人、机械臂力控等场景。
motor->set_motor_control_mode(MotorDriver::MIT);
motor->motor_mit_cmd(
0.0f, // f_p: 目标位置 (rad)
0.0f, // f_v: 目标速度 (rad/s)
10.0f, // f_kp: 位置刚度 / 比例增益
1.0f, // f_kd: 速度阻尼 / 微分增益
0.0f // f_t: 前馈力矩 (Nm)
);
| 参数 | 类型 | 单位 | 说明 |
|---|---|---|---|
f_p |
float | rad | 目标关节位置 |
f_v |
float | rad/s | 目标关节速度 |
f_kp |
float | — | 外环位置刚度增益,越大越“硬” |
f_kd |
float | — | 外环速度阻尼增益,越大越“粘滞” |
f_t |
float | Nm | 前馈力矩,用于补偿重力或接触力 |
位置控制
位置模式让电机以指定速度逼近目标位置,内部由驱动器完成轨迹规划或伺服跟踪。
motor->set_motor_control_mode(MotorDriver::POS);
motor->motor_pos_cmd(1.57f, 3.14f, false); // 目标 90°,最大速度 ~180°/s
| 参数 | 类型 | 单位 | 说明 |
|---|---|---|---|
pos |
float | rad | 目标位置 |
spd |
float | rad/s | 运动速度上限 |
ignore_limit |
bool | — | 是否忽略内部位置限位,默认 false |
速度控制
速度模式使电机持续旋转,适用于轮子、传送带等无需位置闭环的场景。
motor->set_motor_control_mode(MotorDriver::SPD);
motor->motor_spd_cmd(3.14f); // ~180°/s
此外,基类还提供指针重载版本的 motor_mit_cmd(float*, float*, float*, float*, float*),供批量多电机控制或推理引擎直接传递连续内存块。若你仅需控制单电机,使用标量版本即可。
Sources: motor_driver.hpp, motor_driver.hpp
状态反馈读取
控制循环中,应在发送指令后调用 refresh_motor_status() 触发一次状态查询,随后通过原子 getter 读取最新数据。所有反馈变量均为 std::atomic 类型,可直接在多线程环境中安全读取。
motor->refresh_motor_status();
float pos = motor->get_motor_pos(); // rad
float spd = motor->get_motor_spd(); // rad/s
float cur = motor->get_motor_current(); // A
float temp = motor->get_motor_temperature(); // °C
uint8_t err = motor->get_error_id(); // 品牌相关错误码
| 方法 | 返回值 | 单位 | 数据源说明 |
|---|---|---|---|
get_motor_pos() |
float | rad | 编码器反馈经齿轮比和零位偏移换算后的关节位置 |
get_motor_spd() |
float | rad/s | 关节速度,部分电机由驱动器微分输出 |
get_motor_current() |
float | A | 相电流或母线电流(品牌定义不同) |
get_motor_temperature() |
float | °C | 绕组或功率板温度 |
get_error_id() |
uint8_t | — | 实时错误标志位,非零时需关注 |
get_response_count() |
int | — | 累计接收到的 CAN 回复帧数,可用于诊断丢包 |
refresh_motor_status() 的具体实现因品牌而异:部分电机发送显式查询帧,部分则依赖周期性广播帧更新内部缓存。在高频控制循环中(如 1kHz),应确认目标电机的总线负载能力,避免过度查询。
Sources: motor_driver.hpp
错误处理与状态恢复
当 get_error_id() 返回非零值,或 init_motor() 异常返回时,应优先排查硬件过压、过流、过温或通信断开等根本原因。排除故障后,调用 clear_motor_error() 向电机 MCU 发送错误清除指令,使其从保护态恢复到待机态。
if (motor->get_error_id() != 0) {
motor->clear_motor_error();
Timer::sleep_for(10); // 等待电机处理,单位 ms
// 重新初始化
motor->init_motor();
}
注意:clear_motor_error() 只重置软件故障标志,无法修复物理损坏。若错误反复出现,应检查供电、接线及机械负载。utils.hpp 中提供的 Timer::sleep_for() 可作为跨平台延时工具使用。
Sources: motor_driver.hpp, utils.hpp
完整最小示例
下面是一个从实例化到控制再到安全关闭的完整 main.cpp,可直接作为模板复制到你的工程中:
#include "motor_driver.hpp"
#include <iostream>
#include <thread>
int main() {
try {
// 1. 创建 DM 电机实例
auto motor = MotorDriver::create_motor(
0x01, "can", "can0", "DM", 0, 0, 0.0);
// 2. 初始化并使能
uint8_t err = motor->init_motor();
if (err != 0) {
std::cerr << "Motor init error: " << (int)err << std::endl;
return 1;
}
// 3. MIT 控制循环(示例:1kHz,持续 2 秒)
motor->set_motor_control_mode(MotorDriver::MIT);
auto start = std::chrono::steady_clock::now();
while (true) {
auto now = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - start).count();
if (elapsed > 2000) break;
// 目标:回到零位,中等刚度
motor->motor_mit_cmd(0.0f, 0.0f, 10.0f, 1.0f, 0.0f);
// 读取反馈
motor->refresh_motor_status();
std::cout << "pos: " << motor->get_motor_pos()
<< " spd: " << motor->get_motor_spd()
<< " err: " << (int)motor->get_error_id()
<< std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
// 4. 安全关闭
motor->deinit_motor();
} catch (const std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
return 1;
}
return 0;
}
Sources: README_CN.md, motor_driver.hpp
下一步
掌握上述基础后,你可以根据实际电机品牌和控制需求,深入阅读以下页面:
- 若需理解工厂方法背后的多态设计与扩展机制,请参阅 工厂模式与多品牌电机实例化。
- 若使用 DM(达妙)电机并需调参或解析寄存器,请参阅 达妙DM电机驱动详解。
- 若对 MIT 模式的数学原理和参数整定有疑问,请参阅 MIT阻抗控制原理与实现。
- 若需将本库集成到 ROS 2 节点或自定义 CMake 工程中,请参阅 CMake构建系统与依赖管理。