Skip to content

14. 点动与IO控制(综合示例) ​

本页把「连接 → 上电 → 点动 → 数字 IO」串成一个可独立编译运行的文件,适合作为最小闭环参考。各主题的细节说明见对应页面(本页末尾有链接)。

⚠️ 安全警告

本示例包含真实的运动控制(点动会直接驱动机器人)。执行前必须确认:

  • 人员安全: 机器人工作空间内无人员或障碍物;
  • 急停可用: 急停按钮处于可用状态,并在操作人员可触及范围内;
  • 控制器状态: 控制器无未处理的安全报警,安全回路工作正常;
  • 参数核对: 控制器 IP、端口、点动轴号与方向已按现场核对;
  • 输出核对: 数字输出端口号与所接设备已核对,避免误触发。

建议先在无负载或仿真环境下验证,再连接真机。

功能 ​

程序按以下顺序执行(每一步失败即安全退出并断开连接):

  1. 连接控制器(IP / 端口可用命令行参数覆盖),等待连接就绪;
  2. 切换示教模式,按当前伺服状态执行上电(停止 → 就绪 → 上电 → 轮询到运行状态 3);
  3. 设置全局速度;
  4. 读取 IO 板信息(板数、型号、各类型端口数量);
  5. 写数字输出端口并回读校验;
  6. 单轴点动:按 50ms 周期重发 robot_start_jogging(点动为保持式,必须在 200ms 内再次调用,否则控制器自动停止,见《基础连接与系统接口》§1.12),到时后调用 robot_stop_jogging;
  7. 数字输出复位、伺服下电、断开连接。

代码 ​

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 检查,失败即进入退出路径并断开连接。

下一步 ​

🤖 AI 助手 / 自动化工具:先读 《Agent 开发指引》 与 《生成前信息采集清单》;机器可读入口 /llms.txt;本页纯文本版:把网址中的 .html 换成 .md