14. 点动与IO控制(综合示例)
本页把「连接 → 上电 → 点动 → 数字 IO」串成一个可独立编译运行的文件,适合作为最小闭环参考。各主题的细节说明见对应页面(本页末尾有链接)。
⚠️ 安全警告
本示例包含真实的运动控制(点动会直接驱动机器人)。执行前必须确认:
- 人员安全: 机器人工作空间内无人员或障碍物;
- 急停可用: 急停按钮处于可用状态,并在操作人员可触及范围内;
- 控制器状态: 控制器无未处理的安全报警,安全回路工作正常;
- 参数核对: 控制器 IP、端口、点动轴号与方向已按现场核对;
- 输出核对: 数字输出端口号与所接设备已核对,避免误触发。
建议先在无负载或仿真环境下验证,再连接真机。
功能
程序按以下顺序执行(每一步失败即安全退出并断开连接):
- 连接控制器(IP / 端口可用命令行参数覆盖),等待连接就绪;
- 切换示教模式,按当前伺服状态执行上电(停止 → 就绪 → 上电 → 轮询到运行状态 3);
- 设置全局速度;
- 读取 IO 板信息(板数、型号、各类型端口数量);
- 写数字输出端口并回读校验;
- 单轴点动:按 50ms 周期重发
robot_start_jogging(点动为保持式,必须在 200ms 内再次调用,否则控制器自动停止,见《基础连接与系统接口》§1.12),到时后调用robot_stop_jogging; - 数字输出复位、伺服下电、断开连接。
代码
cpp
/**
* 点动与IO控制(综合示例)——单文件最小闭环
* 流程:连接 → 示教模式 → 上电(轮询到状态 3)→ 设速度 → 读 IO 板
* → 写数字输出并回读 → 单轴点动(50ms 保持式重发)→ 停止
* → 数字输出复位 → 下电 → 断开
*
* 本文件不依赖任何其它源文件;编译方式见《环境搭建》各篇。
*/
#include <chrono>
#include <iostream>
#include <string>
#include <thread>
#include <vector>
#include "cpp_interface/nrc_api.h"
// ── 现场参数(运行前必须核对)──
const std::string robot_ip = "192.168.1.15"; // 控制器 IP
const std::string robot_port = "6001"; // 上位机 SDK 端口
const int jog_axis = 1; // 点动轴号
const bool jog_dir_positive = true; // 点动方向:true 正方向
const int jog_hold_ms = 1000; // 按住点动总时长(首次建议改为 200 验证方向)
const int jog_resend_ms = 50; // 点动重发周期(必须小于 200ms)
const int do_port = 1; // 数字输出端口(从 1 开始)
const int wait_ready_timeout_s = 90; // 等待上电完成总超时
const int poll_interval_ms = 500; // 伺服状态轮询间隔
/** 统一检查 SDK 接口返回值,仅 SUCCESS(0) 视为成功。 */
bool check_sdk_result(Result result, const char* operation)
{
if (result == SUCCESS)
{
return true;
}
std::cerr << operation << " 调用失败,错误码:" << static_cast<int>(result) << std::endl;
return false;
}
/** 等待伺服进入运行状态(3)。 */
bool wait_until_robot_ready(SOCKETFD fd)
{
const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(wait_ready_timeout_s);
int state = 0;
while (std::chrono::steady_clock::now() < deadline)
{
if (!check_sdk_result(get_servo_state(fd, state), "get_servo_state"))
{
return false;
}
if (state == 3)
{
std::cout << "伺服已进入运行状态(3)" << std::endl;
return true;
}
std::this_thread::sleep_for(std::chrono::milliseconds(poll_interval_ms));
}
std::cerr << "等待伺服上电超时,最后状态:" << state << std::endl;
return false;
}
/** 按当前伺服状态执行上电流程(停止/就绪/报警/已在运行四种分支)。 */
bool power_on(SOCKETFD fd)
{
int state = 0;
if (!check_sdk_result(get_servo_state(fd, state), "get_servo_state"))
{
return false; // 状态未知时不允许继续发送控制命令。
}
std::cout << "当前伺服状态:" << state << std::endl;
switch (state)
{
case 0: // 停止:先切换到就绪状态(1),再上电。
if (!check_sdk_result(set_servo_state(fd, 1), "set_servo_state")) return false;
if (!check_sdk_result(set_servo_poweron(fd), "set_servo_poweron")) return false;
break;
case 1: // 就绪:直接上电。
if (!check_sdk_result(set_servo_poweron(fd), "set_servo_poweron")) return false;
break;
case 2: // 报警:先清错,再重新切换到就绪状态并上电。
if (!check_sdk_result(clear_error(fd), "clear_error")) return false;
if (!check_sdk_result(set_servo_state(fd, 1), "set_servo_state")) return false;
if (!check_sdk_result(set_servo_poweron(fd), "set_servo_poweron")) return false;
break;
case 3: // 已在运行状态,无需重复上电。
return true;
default:
std::cerr << "未知伺服状态:" << state << std::endl;
return false;
}
return wait_until_robot_ready(fd);
}
int main(int argc, char** argv)
{
const std::string ip = (argc > 1) ? argv[1] : robot_ip;
const std::string port = (argc > 2) ? argv[2] : robot_port;
// 1. 连接控制器并等待就绪(get_connection_status 返回 SUCCESS 表示已就绪)。
SOCKETFD fd = connect_robot(ip, port);
if (fd <= 0)
{
std::cerr << "控制器连接失败:" << ip << ":" << port << std::endl;
return 1;
}
std::cout << "控制器连接成功,SDK 版本:" << get_library_version() << std::endl;
while (get_connection_status(fd) != SUCCESS)
{
std::this_thread::sleep_for(std::chrono::milliseconds(200));
}
// 2. 示教模式 + 上电(点动须在已上电状态下进行)。
if (!check_sdk_result(set_current_mode(fd, 0), "set_current_mode")) { disconnect_robot(fd); return 1; }
if (!power_on(fd)) { disconnect_robot(fd); return 1; }
// 3. 全局速度(范围 0 < speed <= 100,作用于当前模式)。
if (!check_sdk_result(set_speed(fd, 30), "set_speed")) { disconnect_robot(fd); return 1; }
// 4. 读取 IO 板信息。
IOtype io;
if (check_sdk_result(get_io_type(fd, io), "get_io_type"))
{
for (int i = 0; i < io.num; ++i)
{
std::cout << "IO 板 " << i + 1 << ":" << io.type[i]
<< ",DIN=" << io.io_port_sum[i][0]
<< " DOUT=" << io.io_port_sum[i][1]
<< " AIN=" << io.io_port_sum[i][2]
<< " AOUT=" << io.io_port_sum[i][3] << std::endl;
}
}
// 5. 写数字输出并回读校验(返回 SUCCESS 说明指令已发送,回读确认端口真实翻转)。
if (check_sdk_result(set_digital_output(fd, do_port, 1), "set_digital_output"))
{
std::vector<int> out;
if (check_sdk_result(get_digital_output(fd, out), "get_digital_output") &&
do_port <= static_cast<int>(out.size()))
{
std::cout << "DO 端口 " << do_port << " 回读值:" << out[do_port - 1] << std::endl;
}
}
// 6. 单轴点动:保持式——每 50ms 重发一次,停止时显式调用 robot_stop_jogging。
std::vector<double> before(7);
get_current_position(fd, 0, before);
std::cout << "点动前 J1:" << before[0] << ",开始点动(轴 " << jog_axis
<< ",共 " << jog_hold_ms << "ms)" << std::endl;
const auto jog_end = std::chrono::steady_clock::now() + std::chrono::milliseconds(jog_hold_ms);
while (std::chrono::steady_clock::now() < jog_end)
{
check_sdk_result(robot_start_jogging(fd, jog_axis, jog_dir_positive), "robot_start_jogging");
std::this_thread::sleep_for(std::chrono::milliseconds(jog_resend_ms));
}
check_sdk_result(robot_stop_jogging(fd, jog_axis), "robot_stop_jogging");
std::vector<double> after(7);
get_current_position(fd, 0, after);
std::cout << "点动后 J1:" << after[0] << ",点动结束" << std::endl;
// 7. 数字输出复位(可选)、下电并断开。
check_sdk_result(set_digital_output(fd, do_port, 0), "set_digital_output");
check_sdk_result(set_servo_poweroff(fd), "set_servo_poweroff"); // 如需保持上电,注释本行。
disconnect_robot(fd);
std::cout << "已断开连接" << std::endl;
return 0;
}运行说明
- 按《环境搭建》各篇配置好头文件与库(
nrc_host),把本文件保存为jog_io_demo.cpp编译运行;IP / 端口可直接用命令行参数覆盖:./jog_io_demo 192.168.1.15 6001 - Linux 下若链接报线程相关错误,编译命令请追加
-pthread; - 首次运行建议把
jog_hold_ms改为200,点动一小段距离确认轴号与方向无误后,再恢复正常时长。
关键点说明
- 上电状态机:
set_servo_poweron返回成功仅表示命令已发送,需轮询get_servo_state直到状态 3 才算就绪; - 点动为保持式:必须在 200ms 内再次调用
robot_start_jogging(重复调用幂等),否则控制器自动停止——本示例按 50ms 重发即满足该要求; - IO 回读校验:写数字输出后建议回读确认,避免只看接口返回值;
- 错误处理:所有 SDK 调用统一经
check_sdk_result检查,失败即进入退出路径并断开连接。