Skip to content

3.14 ModbusClient 主站能力

概述

Arm::modbusClient 对应机器人作为 Modbus 主站时的串口参数管理、 从站会话创建、寄存器读写与串口透传能力。

当前 C++17 公开能力分成两层:

  1. ModbusClient 接口模块:负责 GetSlave / GetParam / SetParam
  2. modbus_client::Slave 会话:负责具体读写、串口收发和按会话取参

推荐调用路径

cpp
Arm arm;  // 创建机器人会话对象
STATUS_CODE connectRet = arm.Connect("10.27.1.254", "");  // 连接控制器或路由地址
if (connectRet != STATUS_CODE::OK) {  // 判断连接是否失败
    return;  // 连接失败时退出当前流程
}  // 结束连接判断
auto [slave, slaveRet] = arm.modbusClient.GetSlave(  // 创建 Modbus 从站会话
    modbus_client::MODBUS_CHANNEL::CONTROLLER_485,  // 指定控制器 485 通道
    1,  // 指定从站 ID 为 1
    0  // 指定 masterId 为 0
);  // 结束从站会话创建调用
if (slaveRet != STATUS_CODE::OK || !slave.IsValid()) {  // 判断会话是否创建成功且有效
    return;  // 会话不可用时退出当前流程
}  // 结束会话判断
auto [values, readRet] = slave.ReadCoils(0, 2);  // 从地址 0 开始读取 2 个线圈值
if (readRet != STATUS_CODE::OK) {  // 判断读取是否失败
    return;  // 读取失败时退出当前流程
}  // 结束读取判断

接口总览

ModbusClient 接口模块

cpp
GetSlave(channel, slaveId, masterId) -> std::pair<modbus_client::Slave, STATUS_CODE>
GetParam(channel, masterId = 1) -> std::pair<modbus_client::SerialParams, STATUS_CODE>
SetParam(const modbus_client::SerialParams& params) -> std::pair<int32_t, STATUS_CODE>
方法输入输出关键行为
GetSlave通道、从站 ID、主站 IDstd::pair<modbus_client::Slave, STATUS_CODE>创建本地会话,后续读写时访问控制器
GetParam通道、主站 IDstd::pair<modbus_client::SerialParams, STATUS_CODE>TCP_TO_485 通道按 masterId 查询
SetParam串口参数std::pair<int32_t, STATUS_CODE>返回控制器确认的参数记录 id

modbus_client::Slave 会话

cpp
IsValid() const -> bool
ReadCoils(addr, num) -> std::pair<std::vector<int32_t>, STATUS_CODE>
WriteCoils(addr, values) -> STATUS_CODE
ReadHoldingRegs(addr, num) -> std::pair<std::vector<int32_t>, STATUS_CODE>
WriteHoldingRegs(addr, values) -> STATUS_CODE
ReadDiscreteInputs(addr, num) -> std::pair<std::vector<int32_t>, STATUS_CODE>
ReadInputRegs(addr, num) -> std::pair<std::vector<int32_t>, STATUS_CODE>
WriteInputRegs(addr, values) -> STATUS_CODE
GetParam() -> std::pair<modbus_client::SerialParams, STATUS_CODE>
SetParam(params) -> std::pair<int32_t, STATUS_CODE>
SerialSend(msg) -> STATUS_CODE
SerialReceive() -> std::pair<std::string, STATUS_CODE>
方法输入输出关键行为
IsValidbool只检查会话是否仍绑定有效 Arm
ReadCoils起始地址、数量std::pair<std::vector<int32_t>, STATUS_CODE>本地校验 1..120 和地址范围
WriteCoils起始地址、值列表STATUS_CODE本地校验写入数量 1..1024
ReadHoldingRegs起始地址、数量std::pair<std::vector<int32_t>, STATUS_CODE>行为同 ReadCoils
WriteHoldingRegs起始地址、值列表STATUS_CODE行为同 WriteCoils
ReadDiscreteInputs起始地址、数量std::pair<std::vector<int32_t>, STATUS_CODE>行为同 ReadCoils
ReadInputRegs起始地址、数量std::pair<std::vector<int32_t>, STATUS_CODE>行为同 ReadCoils
WriteInputRegs起始地址、值列表STATUS_CODE行为同 WriteCoils
GetParamstd::pair<modbus_client::SerialParams, STATUS_CODE>沿用当前 Slave 保存的通道 / 主站上下文
SetParam串口参数std::pair<int32_t, STATUS_CODE>通过当前会话调用设置接口
SerialSend文本串口消息STATUS_CODE走按通道的串口透传协议
SerialReceivestd::pair<std::string, STATUS_CODE>返回透传收到的原始字符串

前置条件与生命周期

规则
连接前提必须先 Arm::Connect() 成功,否则 ModbusClient 接口模块返回 NOT_CONNECTED
GetSlave创建本地 Slave ;远端从站状态由后续读写结果体现
会话失效Arm::Disconnect() 后,已有 modbus_client::Slave 全部失效
IsValid只判断当前会话是否仍与有效 Arm 绑定;远端在线状态由读写结果体现
与总线配置冲突调用方需要确认控制器上的总线配置是否冲突

参数校验与失败口径

场景返回
modbus_client::Slave 会话失效INVALID_SESSION
addr < 0INVALID_PARAMETER
num < 1num > 120INVALID_PARAMETER
addr + num - 1 > 65535INVALID_PARAMETER
values.size() < 1values.size() > 1024INVALID_PARAMETER
addr + values.size() - 1 > 65535INVALID_PARAMETER
控制器返回非预期 JSON 结构OTHER_ERR

额外说明:

  1. 读接口返回的 std::vector<int32_t> 是控制器返回的原始整型数组,线圈值按控制器结果呈现。
  2. 写接口校验地址范围和数量; values 内容语义交由控制器处理。
  3. SetParam() / GetParam() 将波特率、端口等业务参数交由控制器判断是否合法。

详细语义

GetSlave

签名

cpp
std::pair<modbus_client::Slave, STATUS_CODE> GetSlave(
    modbus_client::MODBUS_CHANNEL channel,
    int32_t slaveId,
    int32_t masterId
);
输入类型说明
channelmodbus_client::MODBUS_CHANNEL通道枚举
slaveIdint32_t从站 ID;创建时直接记录到会话
masterIdint32_t主站 ID;后续读写请求会原样带上
输出说明
modbus_client::Slave成功时返回可用会话;失败时返回默认构造的空会话
STATUS_CODEOK / NOT_CONNECTED

限制与行为

  • GetSlave() 创建本地会话;远端从站状态由后续读写结果体现。
  • slaveId / masterId 按控制器语义传入。
  • 成功返回的 Slave 依赖当前 Arm 生命周期,不能跨 Disconnect() 复用。

调用示例

cpp
auto [slave, ret] = arm.modbusClient.GetSlave(  // 创建 Modbus 从站会话
    modbus_client::MODBUS_CHANNEL::CONTROLLER_485,  // 指定控制器 485 通道
    7,  // 指定从站 ID 为 7
    0  // 指定 masterId 为 0
);  // 结束从站会话创建调用
if (ret != STATUS_CODE::OK || !slave.IsValid()) {  // 判断会话是否创建成功且仍然有效
    return;  // 会话不可用时退出当前流程
}  // 结束会话判断

GetParam

签名

cpp
std::pair<modbus_client::SerialParams, STATUS_CODE> GetParam(
    modbus_client::MODBUS_CHANNEL channel,
    int32_t masterId = 1
);
输入类型说明
channelmodbus_client::MODBUS_CHANNEL要查询的通道
masterIdint32_tCONTROLLER_TCP_TO_485 通道有实际意义;默认 1
输出说明
modbus_client::SerialParams成功时返回参数;失败时返回默认构造值
STATUS_CODEOK / NOT_CONNECTED / 其他控制器错误码

限制与行为

  • CONTROLLER_TCP_TO_485 通道向控制器发送的是 masterId
  • 其他串口通道向控制器发送的是通道枚举值, masterId 被忽略。
  • 这是对端协议既有口径,不是 SDK 自行定义的新规则。

调用示例

cpp
auto [params, ret] = arm.modbusClient.GetParam(  // 查询 Modbus 串口参数
    modbus_client::MODBUS_CHANNEL::CONTROLLER_TCP_TO_485,  // 指定 TCP 转 485 通道
    12  // 指定 masterId 为 12
);  // 结束串口参数查询调用
if (ret == STATUS_CODE::OK) {  // 判断参数查询是否成功
    std::cout << "ip=" << params.ip << "\n";  // 打印查询到的目标 IP
}  // 结束参数查询判断

SetParam

签名

cpp
std::pair<int32_t, STATUS_CODE> SetParam(const modbus_client::SerialParams& params);
输入类型说明
paramsmodbus_client::SerialParams串口参数对象
输出说明
int32_t成功时返回控制器确认的参数记录 id ;失败时返回 0
STATUS_CODEOK / NOT_CONNECTED / 其他控制器错误码

限制与行为

  • channelipportbauddataBitstopBitparitytimeout 都会被序列化后直接发给控制器。

调用示例

cpp
modbus_client::SerialParams params;  // 创建串口参数对象
params.channel = modbus_client::MODBUS_CHANNEL::CONTROLLER_485;  // 指定控制器 485 通道
params.ip = "10.27.1.99";  // 设置目标 IP
params.port = 1502;  // 设置目标端口
auto [id, ret] = arm.modbusClient.SetParam(params);  // 保存串口参数,会改变控制器配置
if (ret == STATUS_CODE::OK) {  // 判断参数保存是否成功
    std::cout << "saved_param_id=" << id << "\n";  // 打印控制器确认的参数记录 ID
}  // 结束参数保存判断

ReadCoils / ReadHoldingRegs / ReadDiscreteInputs / ReadInputRegs

这四个接口共用同一套本地校验规则和返回口径。

签名模式

cpp
std::pair<std::vector<int32_t>, STATUS_CODE> ReadXxx(
    int32_t addr,
    int32_t num
);
输入说明
addr起始地址,要求 addr >= 0
num读取数量,要求 1 <= num <= 120
输出说明
std::vector<int32_t>成功时返回整型数组;失败时返回空数组
STATUS_CODEOK / INVALID_SESSION / INVALID_PARAMETER / 其他控制器错误码

限制与行为

  • addr + num - 1 不能超过 65535
  • Slave 无效时直接返回 INVALID_SESSION
  • 控制器返回的不是整型数组时,SDK 返回 OTHER_ERR

调用示例

cpp
auto [coils, ret] = slave.ReadCoils(10, 3);  // 从地址 10 开始读取 3 个线圈值
if (ret == STATUS_CODE::OK) {  // 判断线圈读取是否成功
    std::cout << "coils=" << coils.size() << "\n";  // 打印读取到的线圈数量
}  // 结束线圈读取判断

WriteCoils / WriteHoldingRegs / WriteInputRegs

这些写接口只在目标 Modbus 区域不同,其余规则一致。

签名模式

cpp
STATUS_CODE WriteXxx(int32_t addr, const std::vector<int32_t>& values);
输入说明
addr起始地址,要求 addr >= 0
values写入值列表,要求数量在 1..1024
输出说明
STATUS_CODEOK / INVALID_SESSION / INVALID_PARAMETER / 其他控制器错误码

限制与行为

  • addr + values.size() - 1 不能超过 65535
  • SDK 校验地址和数量;每个元素的业务语义交由控制器处理。

调用示例

cpp
STATUS_CODE ret = slave.WriteHoldingRegs(20, {11, 12});  // 从地址 20 开始写入两个保持寄存器值
if (ret != STATUS_CODE::OK) {  // 判断保持寄存器写入是否失败
    return;  // 写入失败时退出当前流程
}  // 结束保持寄存器写入判断
// STATUS_CODE inputRet = slave.WriteInputRegs(20, {11, 12});  // 写输入寄存器会改变从站状态,确认后再执行

Slave::GetParam / Slave::SetParam

这两个接口复用 ModbusClient::GetParam / SetParam 语义, 区别只是会话上下文来自当前 Slave

  • Slave::GetParam() 使用创建会话时记录的 channel / masterId
  • Slave::SetParam(params) 通过当前有效会话发请求

如果 Slave 已失效,两者都返回 INVALID_SESSION

SerialSend / SerialReceive

签名

cpp
STATUS_CODE SerialSend(const std::string& msg);
std::pair<std::string, STATUS_CODE> SerialReceive();
接口输入输出关键行为
SerialSend原始字符串STATUS_CODE请求按当前 channel 发送,不带 slaveId / masterId
SerialReceivestd::pair<std::string, STATUS_CODE>成功返回原始字符串,失败返回空字符串

限制与行为

  • 透传协议是按通道工作的,所以 Slave 里保存的 slaveId 对串口透传没有作用。
  • SerialReceive() 成功时要求控制器返回的 data 是字符串; 否则返回 OTHER_ERR

常用类型

modbus_client::MODBUS_CHANNEL

枚举值说明
CONTROLLER_TCP_TO_4852控制器 TCP 转 485 通道
WRIST_485_03手腕 485_0 通道
WRIST_485_14手腕 485_1 通道
CONTROLLER_4855控制器 485 通道

modbus_client::MODBUS_PARITY

枚举值说明
NONE78ASCII N
ODD79ASCII O
EVEN69ASCII E

modbus_client::SerialParams

字段类型默认值说明
idint32_t1参数记录 ID
channelmodbus_client::MODBUS_CHANNELCONTROLLER_TCP_TO_485通道
ipstd::string空字符串目标 IP
portint32_t502端口
baudint32_t9600波特率
dataBitint32_t8数据位
stopBitint32_t1停止位
paritymodbus_client::MODBUS_PARITYNONE校验位
timeoutint32_t1000超时,毫秒

示例

C++17 最小示例

cpp
#include <iostream>  // 引入标准输出流,用于打印 Modbus 查询结果
#include "arm_api.h"  // 引入 Arm 主入口,连接后通过 arm.modbusClient 访问 Modbus 接口
int main()  // 示例程序入口
{  // 进入示例主函数
    Arm arm;  // 创建机器人会话对象
    STATUS_CODE connectRet = arm.Connect("10.27.1.254", "");  // 连接控制器或路由地址,示教器地址留空表示使用默认规则
    if (connectRet != STATUS_CODE::OK) {  // 判断连接是否失败
        return 1;  // 连接失败时直接退出
    }  // 结束连接结果判断
    auto [params, getRet] = arm.modbusClient.GetParam(  // 查询指定 Modbus 通道的串口参数
        modbus_client::MODBUS_CHANNEL::CONTROLLER_485,  // 指定控制器 485 通道
        0  // 指定 masterId,非 TCP_TO_485 通道通常会被控制器忽略
    );  // 结束串口参数查询调用
    if (getRet == STATUS_CODE::OK) {  // 只有查询成功时才打印串口参数
        std::cout << "modbus_ip=" << params.ip << "\n";  // 打印串口参数中的目标 IP
    }  // 结束串口参数查询判断
    auto [slave, slaveRet] = arm.modbusClient.GetSlave(  // 创建一个 Modbus 从站会话
        modbus_client::MODBUS_CHANNEL::CONTROLLER_485,  // 指定控制器 485 通道
        1,  // 指定从站 ID 为 1
        0  // 指定 masterId 为 0
    );  // 结束从站会话创建调用
    if (slaveRet != STATUS_CODE::OK || !slave.IsValid()) {  // 判断从站会话是否创建成功且有效
        return 1;  // 会话无效时返回错误码
    }  // 结束从站会话判断
    auto [coils, readRet] = slave.ReadCoils(0, 2);  // 从地址 0 开始读取 2 个线圈值
    if (readRet != STATUS_CODE::OK) {  // 判断线圈读取是否失败
        return 1;  // 读取失败时返回错误码
    }  // 结束线圈读取判断
    std::cout << "coil_count=" << coils.size() << "\n";  // 打印读取到的线圈数量
    return 0;  // 示例正常结束
}  // 结束示例主函数

C++17 场景化示例

下面几组片段按 “Modbus 参数接口、从站会话、读写寄存器、串口透传” 交叉覆盖本页 API。片段默认承接最小示例里已经连接成功的 arm 对象;写寄存器、改串口参数和串口透传会访问现场设备,确认后再执行。

查询和设置串口参数

cpp
auto [facadeParams, facadeGetRet] = arm.modbusClient.GetParam(  // 通过 ModbusClient 接口模块查询串口参数
    modbus_client::MODBUS_CHANNEL::CONTROLLER_485,  // 指定控制器 485 通道
    0  // 指定 masterId 为 0
);  // 结束参数查询调用
modbus_client::SerialParams params = facadeParams;  // 复制当前参数,便于展示 SetParam 调用形态
params.channel = modbus_client::MODBUS_CHANNEL::CONTROLLER_485;  // 指定要设置的通道
// auto [savedId, facadeSetRet] = arm.modbusClient.SetParam(params);  // 设置串口参数会改变控制器配置,确认后再执行

创建从站会话并按会话取参

cpp
auto [slave, slaveRet] = arm.modbusClient.GetSlave(  // 通过 ModbusClient 接口模块创建从站会话
    modbus_client::MODBUS_CHANNEL::CONTROLLER_485,  // 指定控制器 485 通道
    1,  // 指定从站 ID 为 1
    0  // 指定 masterId 为 0
);  // 结束从站会话创建调用
if (slaveRet != STATUS_CODE::OK || !slave.IsValid()) {  // 使用 Slave::IsValid 判断本地会话是否有效
    return;  // 会话无效时退出当前流程
}  // 结束从站会话判断
auto [slaveParams, slaveGetRet] = slave.GetParam();  // 使用当前 Slave 会话查询串口参数
// auto [slaveSavedId, slaveSetRet] = slave.SetParam(slaveParams);  // 通过 Slave 设置串口参数会改变控制器配置,确认后再执行

读取和写入 Modbus 数据区

cpp
auto [coils, coilsRet] = slave.ReadCoils(0, 2);  // 读取线圈区数据
auto [holdingRegs, holdingRet] = slave.ReadHoldingRegs(0, 2);  // 读取保持寄存器数据
auto [discreteInputs, discreteRet] = slave.ReadDiscreteInputs(0, 2);  // 读取离散输入数据
auto [inputRegs, inputRet] = slave.ReadInputRegs(0, 2);  // 读取输入寄存器数据
if (coilsRet == STATUS_CODE::OK || holdingRet == STATUS_CODE::OK || discreteRet == STATUS_CODE::OK || inputRet == STATUS_CODE::OK) {  // 判断至少一个读取是否成功
    std::cout << "coil_count=" << coils.size() << " holding_count=" << holdingRegs.size() << "\n";  // 打印读取结果摘要
}  // 结束 Modbus 读取判断
// STATUS_CODE writeCoilsRet = slave.WriteCoils(0, {1, 0});  // 写线圈会改变从站状态,确认后再执行
// STATUS_CODE writeHoldingRet = slave.WriteHoldingRegs(0, {100, 200});  // 写保持寄存器会改变从站状态,确认后再执行
// STATUS_CODE writeInputRet = slave.WriteInputRegs(0, {100, 200});  // 写输入寄存器会改变从站状态,确认后再执行

串口透传

cpp
// STATUS_CODE serialSendRet = slave.SerialSend("010300000002");  // 串口透传发送会访问现场总线,确认后再执行
// auto [serialText, serialReceiveRet] = slave.SerialReceive();  // 串口透传接收会等待现场数据,确认后再执行

示例代码

cpp17/modbus_basic/src/main.cpp
cpp
#include "query_master_params/run.h"
#include "operate_slave/run.h"
#include "set_master_params/run.h"

int main(void)
{
    // [ZH] 默认只调用一个门面方法;如需体验其他接口,请把下一行替换成下面任意一行。
    // [EN] The main function calls only one facade by default. Replace the next line with any line below to try other APIs.
    return RunModbusBasicQueryMasterParams();
    // return RunModbusBasicOperateSlave();
    // return RunModbusBasicSetMasterParams();
}
c99/modbus_basic/src/main.cpp
cpp
#include <stdio.h>
#include <string.h>

extern "C" {
#include "c_arm_api.h"
}

int main(void)
{
    // [ZH] 本示例直接在源码中写死连接地址,不解析命令行参数。
    // [EN] This example hard-codes the connection addresses in the source code and does not parse command-line arguments.
    // [ZH] 创建并连接 SDK 句柄。
    // [EN] Create the SDK handle and connect to the robot.
    ArmHandle* handle = Arm_Create();
    if (handle == NULL) {
        printf("[c99_modbus] 创建句柄失败 / Failed to create the handle\n");
        return 1;
    }
    int ret = Arm_Connect(handle, "10.27.1.2", "10.27.1.102");
    if (ret != 0) {
        printf("[c99_modbus] 连接失败 / Connect failed, 状态码 / Status code: %d\n", ret);
        Arm_Destroy(handle);
        return 1;
    }
    printf("[c99_modbus] 机器人连接成功 / Robot connected successfully\n");

    // [ZH] 准备主站串口参数并读取当前值。
    // [EN] Prepare master serial params and read the current values.
    ArmSerialParams masterParams = {0};
    int32_t paramId = 0;
    snprintf(masterParams.ip, sizeof(masterParams.ip), "127.0.0.1");
    masterParams.channel = ARM_MODBUS_CHANNEL_CONTROLLER_485;
    masterParams.id = 1;
    masterParams.port = 502;
    masterParams.baud = 9600;
    masterParams.dataBit = 8;
    masterParams.stopBit = 1;
    masterParams.parity = ARM_MODBUS_PARITY_NONE;
    masterParams.timeout = 1000;
    ret = Arm_Modbus_GetParam(handle, ARM_MODBUS_CHANNEL_CONTROLLER_485, 0, &masterParams);
    printf("[c99_modbus] GetParam 状态码 / GetParam status code: %d, channel=%d, baud=%d\n", ret, masterParams.channel, masterParams.baud);
    ret = Arm_Modbus_SetParam(handle, &masterParams, &paramId);
    printf("[c99_modbus] SetParam 状态码 / SetParam status code: %d, id=%d\n", ret, paramId);

    // [ZH] 获取从站句柄并顺序执行全部从站接口。
    // [EN] Acquire the slave handle and execute all slave APIs in sequence.
    ArmModbusSlaveHandle* slave = NULL;
    ret = Arm_Modbus_GetSlave(handle, ARM_MODBUS_CHANNEL_CONTROLLER_485, 1, 0, &slave);
    printf("[c99_modbus] GetSlave 状态码 / GetSlave status code: %d\n", ret);
    if (slave != NULL) {
        int valid = Arm_ModbusSlave_IsValid(slave);
        printf("[c99_modbus] Slave_IsValid / Slave_IsValid: %d\n", valid);
        int32_t values[8] = {0};
        size_t valueCount = 0U;
        ret = Arm_ModbusSlave_ReadCoils(slave, 0, 2, values, 8U, &valueCount);
        printf("[c99_modbus] ReadCoils 状态码 / ReadCoils status code: %d, count=%zu\n", ret, valueCount);
        int32_t writeCoils[2] = {1, 0};
        ret = Arm_ModbusSlave_WriteCoils(slave, 0, writeCoils, 2U);
        printf("[c99_modbus] WriteCoils 状态码 / WriteCoils status code: %d\n", ret);
        ret = Arm_ModbusSlave_ReadHoldingRegs(slave, 0, 2, values, 8U, &valueCount);
        printf("[c99_modbus] ReadHoldingRegs 状态码 / ReadHoldingRegs status code: %d, count=%zu\n", ret, valueCount);
        int32_t holdingRegs[2] = {1, 2};
        ret = Arm_ModbusSlave_WriteHoldingRegs(slave, 0, holdingRegs, 2U);
        printf("[c99_modbus] WriteHoldingRegs 状态码 / WriteHoldingRegs status code: %d\n", ret);
        ret = Arm_ModbusSlave_ReadDiscreteInputs(slave, 0, 2, values, 8U, &valueCount);
        printf("[c99_modbus] ReadDiscreteInputs 状态码 / ReadDiscreteInputs status code: %d, count=%zu\n", ret, valueCount);
        ret = Arm_ModbusSlave_ReadInputRegs(slave, 0, 2, values, 8U, &valueCount);
        printf("[c99_modbus] ReadInputRegs 状态码 / ReadInputRegs status code: %d, count=%zu\n", ret, valueCount);
        int32_t inputRegs[2] = {3, 4};
        ret = Arm_ModbusSlave_WriteInputRegs(slave, 0, inputRegs, 2U);
        printf("[c99_modbus] WriteInputRegs 状态码 / WriteInputRegs status code: %d\n", ret);
        ret = Arm_ModbusSlave_SetParam(slave, &masterParams, &paramId);
        printf("[c99_modbus] Slave_SetParam 状态码 / Slave_SetParam status code: %d, id=%d\n", ret, paramId);
        ret = Arm_ModbusSlave_GetParam(slave, &masterParams);
        printf("[c99_modbus] Slave_GetParam 状态码 / Slave_GetParam status code: %d, baud=%d\n", ret, masterParams.baud);
        ret = Arm_ModbusSlave_SerialSend(slave, "hello from c99 example");
        printf("[c99_modbus] SerialSend 状态码 / SerialSend status code: %d\n", ret);
        char recvBuf[256] = {0};
        ret = Arm_ModbusSlave_SerialReceive(slave, recvBuf, sizeof(recvBuf));
        printf("[c99_modbus] SerialReceive 状态码 / SerialReceive status code: %d, 内容 / Text: %s\n", ret, recvBuf);
        Arm_ModbusSlave_Destroy(slave);
        printf("[c99_modbus] 从站句柄已释放 / Slave handle destroyed\n");
    }

    // [ZH] 断开连接并销毁句柄。
    // [EN] Disconnect and destroy the handle.
    Arm_Disconnect(handle);
    Arm_Destroy(handle);
    printf("[c99_modbus] 示例结束 / Example finished\n");
    return 0;
}