使用 servo_move() 来进行跟踪运动
servo_move(SOCKETFD socketFd,ServoMovePara servoMove) 可以接收一组点,然后将接收到的点位根据 timeStamp 做平滑处理后下发到伺服运动。
⚠️ 安全警告
以下代码会导致机器人实际运动。执行前必须确认:
- 人员安全: 机器人工作空间内无人员或障碍物
- 急停可用: 急停按钮在可触及范围内
- 伺服使能: 确认伺服已正确使能
- 速度限制: 首次运行将速度设置为较低值(建议 ≤ 最大速度的 20%)
- 坐标系与单位: 关节运动单位为度(°),直线运动单位为毫米(mm)
建议先在仿真环境或空载低速条件下测试,确认无误后再用于实际生产。
cpp
struct ServoMovePara {
///< 传输参数
bool clearBuffer; ///< 是否清除之前发送的,未开始插补计算的点位
int targetMode; ///< 0-独立点 1-连续轨迹
int sendMode; ///< 0-一次传输完全部轨迹 1-一次传输部分点位
int runMode; ///< 0-接收完再运动 1-边接受边运动
int sum; ///< 总传输次数
int count; ///< 当前是第几次传输
///< 运动参数
int coord; ///< 0-关节 1-直角
int size; ///< 本次传输的点位数
std::vector<std::vector<double>> pos; ///< 二维数组,一维表示本次传输的点位数,二维长度为7,各个关节角度或笛卡尔坐标
std::vector<std::vector<double>> axisvel; ///< 二维数组,一维表示本次传输的点位数,二维长度为7,各个轴的速度
std::vector<std::vector<double>> axisacc; ///< 二维数组,一维表示本次传输的点位数,二维长度为7,各个轴的加速度
std::vector<double> timeStamp; ///< 长度为本次传输的点位数,表示到达该点位的时间,单位ms
};使用前提:需要作业文件配合
servo_move() 不能脱离作业文件单独使用:控制器侧必须有一个正在运行的作业,作业中写有外部点(伺服跟踪)指令,上位机经 7000 端口下发的点位才会被执行。
典型用法是在一段固定流程的作业中夹杂上位机实时生成的轨迹:作业负责流程骨架(回原点、取放料、IO 时序等固定动作),到需要随传感器 / 视觉结果变化的那一段,改由上位机实时生成点位并经 servo_move() 下发。

在示教器上启动运行该作业文件后,再从上位机调用 servo_move() 下发点位。
参数取值(以头文件注释为准)
| 参数 | 取值 | 说明 |
|---|---|---|
targetMode | 0 / 1 | 0-独立点,1-连续轨迹 |
sendMode | 0 / 1 | 0-一次传输完全部轨迹点位,1-一次传输部分点位 |
runMode | 0 / 1 | 0-接收完再运动,1-边接收边运动 |
coord | 0 / 1 | 0-关节,1-直角 |
clearBuffer | true / false | 是否清除之前发送的、未开始插补计算的点位 |
下面两个示例都用 targetMode = 0(独立点)、sendMode = 0(一次传输完本帧全部点位);若要让点与点之间按连续轨迹衔接,把 targetMode 置 1。
示例一:逐帧持续发送点位来进行遥操作
cpp
#include <iostream>
#include <vector>
#include <chrono>
#include "cpp_interface/nrc_api.h"
int main() {
ServoMovePara param;
// 目标点位数据
double axis1_pos = 1;
double axis2_pos = 2;
// 查询运动前坐标
std::vector<double> pos(7);
get_current_position(fd, 0, pos);
std::cout << "关节运动前坐标:" << pos[0] << " " << pos[1] << " " << pos[2] << " "
<< pos[3] << " " << pos[4] << " " << pos[5] << " " << pos[6] << std::endl;
for (int k = 0; k < 4000; k++) {
// 生成目标点位数据
for (int i = 0; i < 2; i++) {
param.pos.push_back({pos[0], pos[1], pos[2], pos[3], pos[4], pos[5], pos[6]}); //目标点位数据
std::cout << "关节角=" << param.pos[i][6] << std::endl;
if (k < 100) {
pos[5] += 0.05;
} else if (k >= 100 && k < 200) {
pos[5] -= 0.05;
} else if (k >= 200 && k < 300) {
pos[5] += 0.05;
} else {
pos[5] -= 0.05;
}
std::cout << "k=" << k << std::endl;
}
std::cout << "大小=" << param.pos.size() << std::endl;
// 设置每个轴的速度
for (int i = 0; i < param.pos.size(); i++) {
param.axisvel.push_back({1, 1, 1, 1, 1, 50, 1}); //到达目标点位的每个轴的速度,单位度每秒
}
// 设置每个轴的加速度
for (int i = 0; i < param.pos.size(); i++) {
param.axisacc.push_back({1, 1, 1, 1, 1, 50, 1}); //到达目标点位的每个轴的加速度
}
// 设置时间戳
double times = 5; //运行到该点位时,相对于起始点的时间
for (int i = 0; i < param.pos.size(); i++) {
param.timeStamp.push_back(times);
times += 5; //假设后续每一个点位都相较于上一个点多5ms,最终timeStamp数组是{5,10,15,20...}
}
// 设置参数
param.clearBuffer = true; // 清除上一帧未开始插补计算的点位
param.targetMode = 0; // 0-独立点,1-连续轨迹
param.sendMode = 0; // 0-一次传输完全部轨迹点位,1-一次传输部分点位
param.runMode = 0; // 0-接收完再运动,1-边接收边运动
param.coord = 0; // 关节坐标
param.sum = 100000; // 因为需要持续发送点位,我们假设总的发送次数很大
param.count = 100000; // 因为需要持续发送点位,我们假设总的发送次数很大
param.extMove = 0;
param.size = param.pos.size();
// 调用运动接口
auto t_start = std::chrono::high_resolution_clock::now();
std::cout << "servo_move return: " << servo_move(fd, param) << std::endl;
auto t_stop = std::chrono::high_resolution_clock::now();
auto t_duration = std::chrono::duration<double>(t_stop - t_start);
std::cout << "t_duration: " << t_duration.count() << std::endl;
// 延时补偿
if (t_duration.count() < times / 1000 + 0.01) {
std::this_thread::sleep_for(std::chrono::duration<double>(times / 1000 + 0.01 - t_duration.count()));
}
// 清空参数
param.pos.clear();
param.axisvel.clear();
param.axisacc.clear();
param.timeStamp.clear();
}
return 0;
}示例二:使用独立点位轨迹,,一次性传输完全部轨迹点位(没有平滑)
cpp
#include <iostream>
#include <vector>
#include <chrono>
#include "cpp_interface/nrc_api.h"
int main() {
ServoMovePara param;
// 目标点位数据
double axis1_pos = 1;
double axis2_pos = 2;
// 查询运动前坐标
std::vector<double> pos(7);
get_current_position(fd, 0, pos);
std::cout << "关节运动前坐标:"
<< pos[0] << " " << pos[1] << " " << pos[2] << " "
<< pos[3] << " " << pos[4] << " " << pos[5] << " "
<< pos[6] << std::endl;
for (int k = 0; k < 4000; k++) {
// 生成目标点位数据
for (int i = 0; i < 2; i++) {
param.pos.push_back({pos[0], pos[1], pos[2], pos[3], pos[4], pos[5], pos[6]}); //目标点位数据
std::cout << "关节角=" << param.pos[i][6] << std::endl;
if (k < 100) {
pos[5] += 0.05;
} else if (k >= 100 && k < 200) {
pos[5] -= 0.05;
} else if (k >= 200 && k < 300) {
pos[5] += 0.05;
} else {
pos[5] -= 0.05;
}
std::cout << "k=" << k << std::endl;
}
std::cout << "大小=" << param.pos.size() << std::endl;
// 设置每个轴的速度
for (int i = 0; i < param.pos.size(); i++) {
param.axisvel.push_back({1, 1, 1, 1, 1, 50, 1}); //到达目标点位的每个轴的速度,单位度每秒
}
// 设置每个轴的加速度
for (int i = 0; i < param.pos.size(); i++) {
param.axisacc.push_back({1, 1, 1, 1, 1, 50, 1}); //到达目标点位的每个轴的加速度
}
// 设置时间戳
double times = 5; //运行到该点位时,相对于起始点的时间
for (int i = 0; i < param.pos.size(); i++) {
param.timeStamp.push_back(times);
times += 5; //假设后续每一个点位都相较于上一个点多5ms,最终timeStamp数组是{500,550,600,650...}
}
// 设置参数
param.robotNum = 1;
param.clearBuffer = true; // 清除上一帧未开始插补计算的点位
param.targetMode = 0; // 0-独立点,1-连续轨迹
param.sendMode = 0; // 0-一次传输完全部轨迹点位,1-一次传输部分点位
param.runMode = 0; // 0-接收完再运动,1-边接收边运动
param.coord = 0; // 关节坐标
param.size = param.pos.size();
// 调用运动接口
std::cout << "servo_move return: " << servo_move(fd, param) << std::endl;
// 延时5ms
std::this_thread::sleep_for(std::chrono::milliseconds(5));
// 清空参数
param.pos.clear();
param.axisvel.clear();
param.axisacc.clear();
param.timeStamp.clear();
}
return 0;
}