- 选型指南
- 概述
- 入门指南
- 机械臂参数
- 示教器使用
-
二次开发
-
API(C++)
- 快速开始
- 接口说明
- 7000端口接口说明
- 使用示例
- 返回值说明
-
API(Python)
- 快速开始
- 接口说明
- 7000端口接口说明
- 使用示例
- 返回值说明
- ROS2开发
-
API(C++)
- 相关下载
Modbus主站通信综合示例
1. Modbus 主站通信综合示例(因时电爪 Inspire EG2-4C2)
本节为 Modbus 主站通信综合示例(因时电爪 Inspire EG2-4C2,通过 RS485 串口以 Modbus RTU 协议控制),完整源码见 examples/cpp/ex_inspire_claw.cpp。完整代码如下:
C++
/**
* ex_inspire_claw.cpp
* @brief 因时电爪(Inspire EG2-4C2,RS485 / Modbus RTU)控制测试示例
* @attention
* - 确保控制器网络通信正常,夹爪通过 RS485 串口连接控制器
* - 主站参数(从站 ID、串口号、波特率等)请在下方"RTU 主站配置区"自行修改
* - 开度范围 0~1000:0=闭合完,1000=张开完;写目标开度后夹爪立即动作,
* 需轮询实际开度寄存器等待到位
* - 夹爪会真实动作:默认测试序列首位置 0 为全闭合,运行前请确保夹爪空闲、
* 指间无物体
* @note 运行步骤
* 编译: cd examples/cpp/build && cmake .. && make
* 运行: ./ex_inspire_claw
*/
#include <chrono>
#include <cstdlib>
#include <iostream>
#include <string>
#include <thread>
#include <vector>
#include "tl_api.h"
#ifdef _WIN32
#include <windows.h>
#endif
// ============================================================
// 控制器连接配置(按实际修改)
// ============================================================
static std::string ROBOT_IP = "192.168.1.13"; // 机械臂控制器 IP(可用 -ip 参数覆盖)
const char* ROBOT_PORT = "6001"; // 控制器端口
// ============================================================
// RTU 主站配置区(请根据实际接线自行修改)
// ============================================================
constexpr int RTU_MASTER_ID = 1; // 控制器侧主站编号
constexpr int RTU_SLAVE_ID = 1; // 夹爪从站 ID
constexpr int RTU_PORT = 2; // 控制器串口号(按接线修改)
constexpr int RTU_BAUDRATE = 115200; // 波特率(EG2 默认 115200)
const std::string RTU_CHECK_BIT = "None"; // 校验位: "None"/"Even"/"Odd"
constexpr int RTU_DATA_BIT = 8; // 数据位
constexpr int RTU_STOP_BIT = 1; // 停止位
// ============================================================
// EG2-4C2 夹爪保持寄存器(控制器侧地址,EG2-4C2 固件实测)
// 注意: 通用手册(EG2-4XX V1.0.6)编号为 1010/1011/1012/1061/1064/1065,
// 本机 EG2-4C2 实测目标开度寄存器为地址 10(写 500 即张开到半开),
// 其余寄存器按同段顺延,若读数异常请按夹爪实际固件调整
// 功能码: 03H 读 / 06H 写单个 / 10H 写多个,CRC16-REV=0xA001
// ============================================================
constexpr int REG_OPENLEN_SET = 10; // 目标开度 0~1000(0=闭合完 1000=张开完),写后立即动作
constexpr int REG_SPEED_SET = 11; // 速度 10~1000,写后不动作
constexpr int REG_FORCE_SET = 12; // 抓力 100~1000,写后不动作
constexpr int REG_OPENLEN_ACT = 61; // 实际开度 0~1000(只读)
constexpr int REG_ERRORCODE = 64; // 错误码(只读,位0堵转 位1过温 位2过流 位3运行故障 位4内部通信故障)
constexpr int REG_STATUS = 65; // 状态码(只读,1张开到位 2闭合到位 3停止 4夹取中 5张开中 6夹取到位)
// 其它只读寄存器按需扩展: 60 实际抓力 / 62 电流 / 63 温度
constexpr int CLAW_SPEED = 300; // 夹爪动作速度
constexpr int CLAW_FORCE = 300; // 抓力阈值
// 测试开度序列: 0=闭合完, 1000=张开完;首位置 0 为全闭合,运行前确保指间无物体
const std::vector<int> CLAW_TEST_POS = {0, 1000};
// ============================================================
// 工具函数
// ============================================================
// @brief 打印操作结果
void print_result(const std::string& msg, tl::Result ret)
{
if (ret == tl::Result::SUCCESS) {
std::cout << " [成功] " << msg << std::endl;
} else {
std::cout << " [失败] " << msg << ",错误码: " << static_cast<int>(ret) << std::endl;
}
}
// @brief 打印分隔标题
void print_separator(const std::string& title)
{
std::cout << "\n========== " << title << " ==========" << std::endl;
}
// @brief 带重试的读取:最多尝试 3 次,每次间隔 500ms
tl::Result read_with_retry(tl::SOCKETFD sock, int id, int addr, int qty,
std::vector<int>& data, const std::string& desc)
{
tl::Result ret = tl::Result::SUCCESS;
for (int attempt = 1; attempt <= 3; attempt++) {
data.clear();
ret = tl::modbus_read_holding_registers(sock, id, addr, qty, data);
if (ret == tl::Result::SUCCESS) {
std::cout << " [成功] " << desc << "(第" << attempt << "次)" << std::endl;
return ret;
}
std::cout << " [重试] " << desc << " 第" << attempt << "次失败,500ms后重试..."
<< std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
std::cout << " [失败] " << desc << ",错误码: " << static_cast<int>(ret)
<< "(重试3次均失败)" << std::endl;
return ret;
}
// @brief 轮询实际开度寄存器(REG_OPENLEN_ACT)直到接近目标或超时
// @param last 输出: 最后一次成功读到的实际开度(无成功读数时为 -1)
// @return true 到位 / false 超时
bool wait_openlen_reach(tl::SOCKETFD sock, int master_id, int target, int& last,
int timeout_ms = 10000)
{
auto deadline = std::chrono::steady_clock::now()
+ std::chrono::milliseconds(timeout_ms);
last = -1;
bool read_ok = false;
while (std::chrono::steady_clock::now() < deadline) {
std::vector<int> data;
auto ret = tl::modbus_read_holding_registers(sock, master_id, REG_OPENLEN_ACT, 1, data);
if (ret == tl::Result::SUCCESS && !data.empty()) {
read_ok = true;
last = data[0];
std::cout << " 当前开度: " << last << "(目标 " << target << ")\r" << std::flush;
if (std::abs(last - target) <= 5) {
std::cout << std::endl;
return true;
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(200));
}
std::cout << std::endl;
if (!read_ok) last = -1;
return false;
}
// ============================================================
// 主函数
// ============================================================
int main(int argc, char* argv[]) {
#ifdef _WIN32
SetConsoleOutputCP(CP_UTF8);
#endif
// 解析命令行参数: ./ex_xxx [-ip 192.168.1.13],默认 192.168.1.13
for (int i = 1; i + 1 < argc; ++i) {
if (std::string(argv[i]) == "-ip" || std::string(argv[i]) == "--ip") {
ROBOT_IP = argv[i + 1];
break;
}
}
// ---- 获取 SDK 版本 ----
std::string version = tl::get_library_version();
std::cout << "[信息] SDK 版本: " << version << std::endl;
// ---- 连接控制器 ----
print_separator("连接控制器");
tl::SOCKETFD sock = tl::connect_robot(ROBOT_IP, ROBOT_PORT);
if (sock < 0) {
std::cerr << "[ERROR] 连接控制器失败" << std::endl;
return -1;
}
std::cout << "[信息] 连接成功 (socket = " << sock << ")" << std::endl;
tl::Result ret;
// ---- 配置 RTU 主站 ----
print_separator("配置 RTU 主站");
{
tl::ModbusMasterParameter param;
param.type = "RTU";
param.startAddress = true; // 起始地址 0
param.RTU.slaveId = RTU_SLAVE_ID;
param.RTU.port = RTU_PORT;
param.RTU.baudrate = RTU_BAUDRATE;
param.RTU.checkBit = RTU_CHECK_BIT;
param.RTU.dataBit = RTU_DATA_BIT;
param.RTU.stopBit = RTU_STOP_BIT;
ret = tl::modbus_set_master_parameter(sock, RTU_MASTER_ID, param);
print_result("tl::modbus_set_master_parameter(id=1, RTU)", ret);
if (ret != tl::Result::SUCCESS) {
tl::disconnect_robot(sock);
return -1;
}
std::cout << " 从站ID: " << param.RTU.slaveId
<< ", 串口: " << param.RTU.port
<< ", 波特率: " << param.RTU.baudrate << std::endl;
std::cout << " 校验: " << param.RTU.checkBit
<< ", 数据位: " << param.RTU.dataBit
<< ", 停止位: " << param.RTU.stopBit << std::endl;
}
// ---- 打开 Modbus 主站 ----
ret = tl::modbus_open_master(sock, RTU_MASTER_ID);
print_result("tl::modbus_open_master(id=1)", ret);
if (ret != tl::Result::SUCCESS) {
tl::disconnect_robot(sock);
return -1;
}
std::this_thread::sleep_for(std::chrono::milliseconds(500));
// ---- 03H 读当前开度 ----
print_separator("读取夹爪当前开度");
{
std::vector<int> data;
ret = read_with_retry(sock, RTU_MASTER_ID, REG_OPENLEN_ACT, 1, data, "读实际开度");
if (ret != tl::Result::SUCCESS) {
std::cout << " [提示] 读取实际开度失败(地址 " << REG_OPENLEN_ACT
<< " 为同段顺延假设,若持续失败请调整 REG_OPENLEN_ACT)" << std::endl;
} else {
std::cout << " 当前开度: " << data[0] << "(0=闭合完, 1000=张开完)" << std::endl;
}
}
// ---- 06H 设置速度与抓力(地址 11/12 为同段顺延,失败不阻塞动作) ----
print_separator("设置速度与抓力");
ret = tl::modbus_write_signal_holding_registers(sock, RTU_MASTER_ID, REG_SPEED_SET, CLAW_SPEED);
print_result("写速度 (addr=" + std::to_string(REG_SPEED_SET) + ", val="
+ std::to_string(CLAW_SPEED) + ")", ret);
if (ret != tl::Result::SUCCESS) {
std::cout << " [提示] 速度寄存器可能不在此地址,跳过(不影响开度动作)" << std::endl;
}
ret = tl::modbus_write_signal_holding_registers(sock, RTU_MASTER_ID, REG_FORCE_SET, CLAW_FORCE);
print_result("写抓力 (addr=" + std::to_string(REG_FORCE_SET) + ", val="
+ std::to_string(CLAW_FORCE) + ")", ret);
if (ret != tl::Result::SUCCESS) {
std::cout << " [提示] 抓力寄存器可能不在此地址,跳过(不影响开度动作)" << std::endl;
}
// ---- 06H 写目标开度(addr=10 为实测地址),并轮询实际开度到位 ----
for (size_t i = 0; i < CLAW_TEST_POS.size(); i++) {
int target = CLAW_TEST_POS[i];
std::string title = "测试位置 " + std::to_string(i + 1) + "/"
+ std::to_string(CLAW_TEST_POS.size())
+ ": 目标开度=" + std::to_string(target);
print_separator(title);
ret = tl::modbus_write_signal_holding_registers(sock, RTU_MASTER_ID, REG_OPENLEN_SET, target);
print_result("tl::modbus_write_signal_holding_registers(addr="
+ std::to_string(REG_OPENLEN_SET) + ", val=" + std::to_string(target) + ")", ret);
if (ret != tl::Result::SUCCESS) {
tl::disconnect_robot(sock);
return -1;
}
int last = -1;
bool reached = wait_openlen_reach(sock, RTU_MASTER_ID, target, last);
if (!reached) {
std::cout << " [提示] 未能确认到位(实际开度地址 " << REG_OPENLEN_ACT
<< " 为同段顺延假设,若读数异常请调整 REG_OPENLEN_ACT;开度命令已下发)"
<< std::endl;
} else {
std::cout << " [到位] 开度 " << last << " 到达目标 " << target << std::endl;
}
std::this_thread::sleep_for(std::chrono::milliseconds(300));
}
// ---- 03H 读状态码与错误码 ----
print_separator("读取夹爪状态");
{
std::vector<int> data;
ret = read_with_retry(sock, RTU_MASTER_ID, REG_STATUS, 1, data, "读状态码");
if (ret == tl::Result::SUCCESS) {
int status = data[0];
const char* desc = "未知";
switch (status) {
case 1: desc = "张开到位"; break;
case 2: desc = "闭合到位"; break;
case 3: desc = "停止"; break;
case 4: desc = "夹取中"; break;
case 5: desc = "张开中"; break;
case 6: desc = "夹取到位"; break;
default: break;
}
std::cout << " 状态码: " << status << "(" << desc << ")" << std::endl;
}
}
{
std::vector<int> data;
ret = read_with_retry(sock, RTU_MASTER_ID, REG_ERRORCODE, 1, data, "读错误码");
if (ret != tl::Result::SUCCESS) {
std::cout << " [提示] 读取错误码失败(地址 " << REG_ERRORCODE
<< " 为同段顺延假设)" << std::endl;
tl::disconnect_robot(sock);
return 0;
}
int err = data[0];
if (err == 0) {
std::cout << " 错误码: 0(无错误)" << std::endl;
} else {
static const std::vector<std::pair<int, const char*>> bits = {
{0, "堵转"}, {1, "过温"}, {2, "过流"}, {3, "运行故障"}, {4, "内部通信故障"}};
std::string faults;
for (const auto& b : bits) {
if (err & (1 << b.first)) {
if (!faults.empty()) faults += ", ";
faults += b.second;
}
}
std::cout << " 错误码: " << err << "(" << faults << ")" << std::endl;
}
}
// ---- 断开连接 ----
print_separator("断开连接");
tl::disconnect_robot(sock);
std::cout << "[信息] 已断开连接" << std::endl;
std::cout << "\n[信息] 因时夹爪测试示例运行完毕" << std::endl;
return 0;
}
