当前位置: 首页 > article >正文

libmodbus主机通信主要函数分析

文章目录

  • 一、modbus_new_rtu
      • **1. 函数声明:**
      • **2. 参数检查:**
      • **3. 内存分配:**
      • **4. 设备字符串分配与初始化:**
      • **5. 配置 RTU 参数:**
      • **6. 其他 RTU 配置:**
      • **7. 返回 `modbus_t` 上下文:**
      • **总结:**
  • 二、modbus_set_slave
      • 函数声明:
      • 代码分析:
      • **总结:**
  • 三、modbus_connect
      • `modbus_rtu_connect` 函数
      • `modbus_rtu_is_connected` 函数
      • 总结:
  • 四、modbus_write_bit
      • 1. **`modbus_write_bit` 函数:**
      • 2. **`write_single` 函数:**
      • 3. **`send_msg` 函数:**
      • 4. **`_modbus_receive_msg` 和 `check_confirmation` 函数:**
      • 总结:


一、modbus_new_rtu

1. 函数声明:

modbus_t *modbus_new_rtu(const char *device, int baud, char parity, int data_bit, int stop_bit)
  • 返回类型modbus_t *,返回一个指向 modbus_t 类型的指针,表示新创建的 Modbus RTU 上下文。
  • 参数
    • device:设备名称(通常是串口设备路径,如 /dev/ttyS0)。
    • baud:波特率,表示通信速度(例如 9600,19200 等)。
    • parity:奇偶校验位(‘N’ 表示无校验,‘E’ 表示偶校验,‘O’ 表示奇校验)。
    • data_bit:数据位数,通常为 8 位。
    • stop_bit:停止位,通常为 1 或 2。

2. 参数检查:

if (device == NULL || *device == 0) {
    fprintf(stderr, "The device string is empty\n");
    errno = EINVAL;
    return NULL;
}
  • 如果设备字符串为空或无效(device 为 NULL 或空字符串),打印错误消息并返回 NULL,同时设置 errnoEINVAL(无效参数错误)。
if (baud == 0) {
    fprintf(stderr, "The baud rate value must not be zero\n");
    errno = EINVAL;
    return NULL;
}
  • 检查波特率是否为 0,如果是,则返回 NULL,并设置 errnoEINVAL

3. 内存分配:

ctx = (modbus_t *) malloc(sizeof(modbus_t));
if (ctx == NULL) {
    return NULL;
}
  • modbus_t 类型的结构体分配内存。modbus_t 是 Modbus 通信的上下文结构。
_modbus_init_common(ctx);
ctx->backend = &_modbus_rtu_backend;
  • 调用 _modbus_init_common 初始化 modbus_t 结构中的公共部分。
  • 设置 ctx->backend 为 RTU 后端的结构指针(_modbus_rtu_backend)。
ctx->backend_data = (modbus_rtu_t *) malloc(sizeof(modbus_rtu_t));
if (ctx->backend_data == NULL) {
    modbus_free(ctx);
    errno = ENOMEM;
    return NULL;
}
  • modbus_rtu_t 类型的结构体分配内存,modbus_rtu_t 是与 RTU 相关的具体实现数据结构。
  • 如果分配失败,则释放已经分配的 ctx 内存,并返回 NULL

4. 设备字符串分配与初始化:

ctx_rtu = (modbus_rtu_t *) ctx->backend_data;
ctx_rtu->device = (char *) malloc((strlen(device) + 1) * sizeof(char));
if (ctx_rtu->device == NULL) {
    modbus_free(ctx);
    errno = ENOMEM;
    return NULL;
}
  • 为设备名(如串口设备路径)分配内存,并检查是否分配成功。
#if defined(_WIN32)
strcpy_s(ctx_rtu->device, strlen(device) + 1, device);
#else
strcpy(ctx_rtu->device, device);
#endif
  • 根据平台(Windows 或其他平台),使用不同的方式复制设备名字符串。

5. 配置 RTU 参数:

ctx_rtu->baud = baud;
  • 设置波特率。
if (parity == 'N' || parity == 'E' || parity == 'O') {
    ctx_rtu->parity = parity;
} else {
    modbus_free(ctx);
    errno = EINVAL;
    return NULL;
}
  • 检查传入的奇偶校验位是否合法。如果不合法(不是 'N''E''O'),则释放内存并返回 NULL
ctx_rtu->data_bit = data_bit;
ctx_rtu->stop_bit = stop_bit;
  • 设置数据位和停止位。

6. 其他 RTU 配置:

#if HAVE_DECL_TIOCSRS485
ctx_rtu->serial_mode = MODBUS_RTU_RS232;
#endif
  • 如果支持 RS485,设置串口模式。默认情况下使用 RS232 模式。
#if HAVE_DECL_TIOCM_RTS
ctx_rtu->rts = MODBUS_RTU_RTS_NONE;
ctx_rtu->onebyte_time =
    1000000 * (1 + data_bit + (parity == 'N' ? 0 : 1) + stop_bit) / baud;
ctx_rtu->set_rts = _modbus_rtu_ioctl_rts;
ctx_rtu->rts_delay = ctx_rtu->onebyte_time;
#endif
  • 配置 RTS(请求发送)信号的行为。
  • 计算每个字节传输所需的时间(以微秒为单位)。
  • 配置用于设置 RTS 的内部函数和延迟。
ctx_rtu->confirmation_to_ignore = FALSE;
  • 设置确认消息的忽略标志。

7. 返回 modbus_t 上下文:

return ctx;
  • 返回创建并初始化完成的 modbus_t 上下文指针。

总结:

该函数 modbus_new_rtu 创建并初始化了一个 Modbus RTU 的上下文结构,主要执行以下操作:

  1. 验证输入参数。
  2. 动态分配内存给 Modbus RTU 相关的结构体。
  3. 初始化 RTU 的设备名称、波特率、奇偶校验、数据位、停止位等参数。
  4. 配置 RS485 和 RTS 信号控制(如果适用)。
  5. 返回指向 modbus_t 结构的指针,以供后续 Modbus RTU 通信使用。

这种设计使得 modbus_new_rtu 函数能够创建一个专用于 Modbus RTU 的上下文对象,并将必要的硬件和通信参数封装到其中,为后续的 Modbus 通信操作做好准备。

二、modbus_set_slave

modbus_set_slave 函数的作用是设置 Modbus 通信中的从设备(Slave)的地址。它接收一个 modbus_t 类型的上下文指针(ctx)和一个整数类型的从设备地址(slave),然后调用具体 Modbus 后端的 set_slave 方法来设置从设备地址。下面我们逐步分析代码:

函数声明:

int modbus_set_slave(modbus_t *ctx, int slave)
  • 返回类型int,表示操作是否成功,通常 0 表示成功,-1 表示失败。
  • 参数
    • ctx:指向 Modbus 上下文的指针,表示当前的 Modbus 会话。
    • slave:要设置的从设备地址,通常是一个整数(例如 1 到 247),用于标识 Modbus 网络中的从设备。

代码分析:

if (ctx == NULL) {
    errno = EINVAL;
    return -1;
}
  • 检查 ctx 是否为 NULL
    • 如果 ctxNULL,说明传入的 Modbus 上下文无效,无法进行后续的操作。
    • 使用 errno = EINVAL 设置错误码为 EINVAL(无效的参数错误),并返回 -1 表示函数失败。
return ctx->backend->set_slave(ctx, slave);
  • 如果 ctx 不为 NULL,则调用 ctx->backend->set_slave 方法来设置从设备地址。
    • ctx->backend 是一个指向 Modbus 后端实现结构的指针。后端可能是 RTU 或 TCP 等不同的 Modbus 实现,它包含了不同的实现细节。
    • set_slave 是一个在后端实现中定义的函数,用来设置从设备的地址。通过 ctx->backend->set_slave 调用该函数,传入 ctxslave 参数,设置从设备地址。

总结:

  • 功能modbus_set_slave 函数的主要作用是设置 Modbus 协议中的从设备地址,通常用于后续的通信。
  • 步骤
    1. 函数首先检查传入的 ctx 是否为 NULL,如果是,则返回错误。
    2. 如果 ctx 是有效的,调用 Modbus 后端的 set_slave 函数来设置从设备地址。
  • 扩展:具体的 set_slave 实现会依赖于 Modbus 后端类型(例如 RTU 或 TCP)。后端的 set_slave 函数通常负责存储或处理从设备地址的设置。

三、modbus_connect

modbus_rtu_connect 函数

static int _modbus_rtu_connect(modbus_t *ctx)
{
    struct termios tios;
    int flags;
    speed_t speed;
    modbus_rtu_t *ctx_rtu = ctx->backend_data;
  • 变量定义
    • struct termios tios:用于保存和设置串行端口的配置参数。
    • int flags:用于保存打开串行端口时的标志。
    • speed_t speed:用于保存串行通信的波特率。
    • modbus_rtu_t *ctx_rtu:指向 Modbus RTU 后端数据结构的指针,包含与 RTU 设备通信相关的参数。
if (ctx->debug) {
    printf("Opening %s at %d bauds (%c, %d, %d)\n",
           ctx_rtu->device,
           ctx_rtu->baud,
           ctx_rtu->parity,
           ctx_rtu->data_bit,
           ctx_rtu->stop_bit);
}
  • 如果 ctx->debugtrue,打印串口打开的调试信息,包括设备名、波特率、校验类型、数据位数和停止位。
flags = O_RDWR | O_NOCTTY | O_NDELAY | O_EXCL;
#ifdef O_CLOEXEC
flags |= O_CLOEXEC;
#endif
  • 串口配置标志
    • O_RDWR:以读写模式打开文件。
    • O_NOCTTY:不将程序设置为控制终端。
    • O_NDELAY:打开时不等待连接建立,允许继续执行。
    • O_EXCL:以排他模式打开设备,确保不会被其他进程使用。
    • O_CLOEXEC:在执行 exec 时关闭文件描述符(在支持的平台上)。
ctx->s = open(ctx_rtu->device, flags);
if (ctx->s < 0) {
    if (ctx->debug) {
        fprintf(stderr,
                "ERROR Can't open the device %s (%s)\n",
                ctx_rtu->device,
                strerror(errno));
    }
    return -1;
}
  • 打开串口设备:使用 open 系统调用打开设备文件。如果打开失败,输出错误信息并返回 -1
tcgetattr(ctx->s, &ctx_rtu->old_tios);
memset(&tios, 0, sizeof(struct termios));
  • 保存原始的串口配置:使用 tcgetattr 获取当前串口的设置,保存在 ctx_rtu->old_tios 中。
  • 清空新的串口配置:将 tios 结构体初始化为全零,准备设置新的串口参数。
if (9600 == B9600) {
    speed = ctx_rtu->baud;
} else {
    speed = _get_termios_speed(ctx_rtu->baud, ctx->debug);
}
  • 设置波特率:根据 ctx_rtu->baud 设置串口的波特率,如果是在 9600 波特率的情况下,直接使用 B9600,否则调用 _get_termios_speed 函数获取对应的波特率常量。
if ((cfsetispeed(&tios, speed) < 0) || (cfsetospeed(&tios, speed) < 0)) {
    close(ctx->s);
    ctx->s = -1;
    return -1;
}
  • 设置输入和输出的波特率。如果设置失败,关闭串口并返回 -1
tios.c_cflag |= (CREAD | CLOCAL);
tios.c_cflag &= ~CSIZE;
  • 设置控制标志:
    • CREAD:使能接收功能。
    • CLOCAL:本地连接,不让系统将串口设置为控制终端。
    • CSIZE:掩码,表示数据位数。
switch (ctx_rtu->data_bit) {
case 5:
    tios.c_cflag |= CS5;
    break;
case 6:
    tios.c_cflag |= CS6;
    break;
case 7:
    tios.c_cflag |= CS7;
    break;
case 8:
default:
    tios.c_cflag |= CS8;
    break;
}
  • 设置数据位(5、6、7 或 8 位),根据 ctx_rtu->data_bit 选择合适的设置。
if (ctx_rtu->stop_bit == 1)
    tios.c_cflag &= ~CSTOPB;
else /* 2 */
    tios.c_cflag |= CSTOPB;
  • 设置停止位(1 或 2 位)。
if (ctx_rtu->parity == 'N') {
    tios.c_cflag &= ~PARENB;
} else if (ctx_rtu->parity == 'E') {
    tios.c_cflag |= PARENB;
    tios.c_cflag &= ~PARODD;
} else {
    tios.c_cflag |= PARENB;
    tios.c_cflag |= PARODD;
}
  • 设置校验位:
    • 'N':无校验,PARENB 被清除。
    • 'E':偶校验,PARENB 设置,PARODD 清除。
    • 'O':奇校验,PARENBPARODD 都设置。
tios.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
  • 设置输入模式:禁用规范输入模式(ICANON)、回显(ECHO)、和信号处理(ISIG)。
if (ctx_rtu->parity == 'N') {
    tios.c_iflag &= ~INPCK;
} else {
    tios.c_iflag |= INPCK;
}
  • 设置输入校验:
    • 无校验时,INPCK 清除。
    • 有校验时,INPCK 设置。
tios.c_iflag &= ~(IXON | IXOFF | IXANY);
tios.c_oflag &= ~OPOST;
  • 禁用软件流控制(IXON, IXOFF, IXANY)并设置原始输出模式(OPOST)。
tios.c_cc[VMIN] = 0;
tios.c_cc[VTIME] = 0;
  • 设置最小读取字符数为 0,并禁用读取超时(VMINVTIME)。
if (tcsetattr(ctx->s, TCSANOW, &tios) < 0) {
    close(ctx->s);
    ctx->s = -1;
    return -1;
}
  • 使用 tcsetattr 设置串口配置。如果设置失败,关闭串口并返回 -1
return 0;
}
  • 如果一切设置成功,返回 0,表示连接成功。

modbus_rtu_is_connected 函数

static unsigned int _modbus_rtu_is_connected(modbus_t *ctx)
{
#if defined(_WIN32)
    modbus_rtu_t *ctx_rtu = ctx->backend_data;

    return ctx_rtu->w_ser.fd != INVALID_HANDLE_VALUE;
#else
    return ctx->s >= 0;
#endif
}
  • 功能:检查 RTU 连接是否成功。
    • 在 Windows 上,检查文件描述符是否有效(fd != INVALID_HANDLE_VALUE)。
    • 在其他系统(如 Linux),检查串口文件描述符 ctx->s 是否大于或等于 0(表示打开成功)。

总结:

  • modbus_rtu_connect 函数设置并打开 Modbus RTU 的串口设备,配置波特率、数据位、停止位、校验等参数,确保通信能够正常进行。
  • modbus_rtu_is_connected 函数用于检查 RTU 连接是否成功。

四、modbus_write_bit

modbus_write_bit 函数用于将单个线圈(coil)位(0 或 1)写入 Modbus 从设备。我们一步步分析这个函数以及它的调用关系:

1. modbus_write_bit 函数:

  • 函数的三个参数:

    • ctx:指向 modbus_t 结构体的指针,表示 Modbus 上下文(包括连接等信息)。
    • addr:目标线圈的地址(即要写入的寄存器地址)。
    • status:要写入的状态,0 或 1。
  • 函数内部:

    • 首先检查 ctx 是否为 NULL。如果是 NULL,则设置 errnoEINVAL(无效参数),并返回 -1,表示出错。
    • 否则,调用 write_single 函数来实际执行写入操作。

2. write_single 函数:

  • write_single 是实现写操作的核心函数,它接受以下参数:

    • ctx:Modbus 上下文。
    • function:Modbus 功能码,这里是写单个线圈的功能码 MODBUS_FC_WRITE_SINGLE_COIL
    • addr:寄存器地址。
    • value:要写入的值,这里是 status ? 0xFF00 : 0。如果 status 为 1,则写入 0xFF00,否则写入 0x00
  • 函数内部:

    • 调用 ctx->backend->build_request_basis 构建请求报文,req 数组存储构建的请求消息,req_length 存储消息长度。
    • 调用 send_msg 函数发送请求消息,并返回响应。如果发送成功,继续接收并处理响应消息。
    • 如果接收到的响应消息有效,则调用 check_confirmation 验证确认消息。
    • 最终返回执行结果。

3. send_msg 函数:

  • send_msg 负责将构建的消息通过串口或其他传输方式发送出去。该函数执行如下步骤:
    • 先通过 ctx->backend->send_msg_pre 对消息进行预处理(这可能与 Modbus 的协议实现或底层操作系统的设置有关)。
    • 如果启用了调试(ctx->debug),则打印发送的消息。
    • 然后,进入一个循环,调用 ctx->backend->send 发送消息。如果发送失败,根据错误类型决定是否进行错误恢复操作(如重连或清理)。
    • 如果消息发送成功,并且实际发送的字节数与期望的字节数不匹配,则设置 errnoEMBBADDATA,表示数据错误。

4. _modbus_receive_msgcheck_confirmation 函数:

  • 如果消息发送成功,接下来需要接收响应。_modbus_receive_msg 用于接收来自从设备的响应。
  • 接收到响应后,check_confirmation 函数用于检查收到的响应是否有效,并与请求进行比较,确保通信正确。

总结:

  • modbus_write_bit 函数调用了 write_single,后者负责构建请求并发送给从设备。
  • 在发送消息后,系统会等待并验证从设备的确认响应。
  • 如果发生错误(如设备断开连接),send_msg 会尝试进行重连或错误恢复。

整个流程实现了对 Modbus 从设备的单个线圈写操作。通过发送和接收 Modbus 消息,确保请求成功执行。


http://www.kler.cn/a/463261.html

相关文章:

  • 力扣【SQL连续问题】
  • 二、CSS基础
  • 金融租赁系统的创新与发展推动行业效率提升
  • JS基础 -- 数组 (对象 / 数组 / 类数组 / 对象数组)的遍历
  • 【C语言的小角落】--- 深度理解取余/取模运算
  • 【Rust练习】26.Package and Crate
  • 2021年国家公考《申论》题(地市级)
  • [工业 4.0] 机器学习如何推动智能制造升级
  • 【从零开始入门unity游戏开发之——C#篇40】C#特性(Attributes)和自定义特性
  • HarmonyOS Next ArkUI ListListItem笔记
  • 【SQL server】教材数据库(5)
  • github
  • 在 Alpine Linux 下通过 Docker 部署 Nginx 服务器
  • 【Pytorch实用教程】深入了解 torchvision.models.resnet18 新旧版本的区别
  • 智能边缘计算×软硬件一体化:开启全场景效能革命新征程(独立开发者作品)
  • 【置顶】测试学习笔记整理
  • SUBSTRING_INDEX()在MySQL中的用法
  • Vue 3.0 中 template 多个根元素警告问题
  • springboot522基于Spring Boot的律师事务所案件管理系统的设计与开发(论文+源码)_kaic
  • BGP(Border Gateway Protocol,边界网关协议)
  • 改进爬山算法之五:自适应爬山法(Adaptive Hill Climbing,AHC)
  • c#String和StringBuilder
  • Coding Our First Neurons
  • SpringMVC的工作流程
  • 数据结构————概述
  • Gitee在项目中的运用全解析