libmodbus笔记

libmodbus笔记

@@ date : 2026.4.20

@@ ps : 一些开源工程libmodbus的源码学习笔记

网址:https://github.com/stephane/libmodbus

参考书籍:《modbus软件开发实战指南》

modbus初始化

这里主从都差不多。

主函数中首先确定使用的modbus类型,并根据类型确定使用的设备相关特征,对于modbus-tcp就是ip

c 复制代码
if (strcmp(argv[1], "tcp") == 0) {
    use_backend = TCP;
} else if (strcmp(argv[1], "tcppi") == 0) {
    use_backend = TCP_PI;
} else if (strcmp(argv[1], "rtu") == 0) {
    use_backend = RTU;
 ......   
switch (use_backend) {
    case TCP:
        ip_or_device = "127.0.0.1";
        break;
    case TCP_PI:
        ip_or_device = "::1";
        break;
    case RTU:
        ip_or_device = "/dev/ttyUSB1";
        break;
}

根据使用的ip_or_device创建modbus总线,并设置要访问的从机ID

c 复制代码
if (use_backend == TCP) {
        ctx = modbus_new_tcp(ip_or_device, 1502);
    } else if (use_backend == TCP_PI) {
        ctx = modbus_new_tcp_pi(ip_or_device, "1502");
    } else {
        ctx = modbus_new_rtu(ip_or_device, 115200, 'N', 8, 1);
    }
。。。。。
  if (use_backend == RTU) {
        modbus_set_slave(ctx, SERVER_ID);
    }

然后就是连接了。

其中有一个关键结构体就是函数modbus_new_rtu()创建的modbus_t *ctx,这个结构体中包含了所有需要使用到的东西,其modbus_backend_t是一堆函数指针,也就是完成各类modbus协议操作的函数。

c 复制代码
struct _modbus {
    /* Slave address */
    int slave;
    /* Socket or file descriptor */
    int s;
    int debug;
    int error_recovery;
    int quirks;
    struct timeval response_timeout;
    struct timeval byte_timeout;
    struct timeval indication_timeout;
    const modbus_backend_t *backend;
    void *backend_data;
};

typedef struct _modbus_backend {
    unsigned int backend_type;
    unsigned int header_length;
    unsigned int checksum_length;
    unsigned int max_adu_length;
    int (*set_slave)(modbus_t *ctx, int slave);
    int (*build_request_basis)(
        modbus_t *ctx, int function, int addr, int nb, uint8_t *req);
    int (*build_response_basis)(sft_t *sft, uint8_t *rsp);
    int (*prepare_response_tid)(const uint8_t *req, int *req_length);
    int (*send_msg_pre)(uint8_t *req, int req_length);
    ssize_t (*send)(modbus_t *ctx, const uint8_t *req, int req_length);
    int (*receive)(modbus_t *ctx, uint8_t *req);
    ssize_t (*recv)(modbus_t *ctx, uint8_t *rsp, int rsp_length);
    int (*check_integrity)(modbus_t *ctx, uint8_t *msg, const int msg_length);
    int (*pre_check_confirmation)(modbus_t *ctx,
                                  const uint8_t *req,
                                  const uint8_t *rsp,
                                  int rsp_length);
    int (*connect)(modbus_t *ctx);
    unsigned int (*is_connected)(modbus_t *ctx);
    void (*close)(modbus_t *ctx);
    int (*flush)(modbus_t *ctx);
    int (*select)(modbus_t *ctx, fd_set *rset, struct timeval *tv, int msg_length);
    void (*free)(modbus_t *ctx);
} modbus_backend_t;

寄存器读写

对于写数据,文件modbus.c中给出了各类寄存器的写函数,如下:

进入这些函数就会发现,所有的函数都调用了send_msg()函数,在这个函数中调用了上面backend中的各类接口函数。

对于MODBUS-RTU就是这些函数了:

复制代码
const modbus_backend_t _modbus_rtu_backend = {
    _MODBUS_BACKEND_TYPE_RTU,
    _MODBUS_RTU_HEADER_LENGTH,
    _MODBUS_RTU_CHECKSUM_LENGTH,
    MODBUS_RTU_MAX_ADU_LENGTH,
    _modbus_set_slave,
    _modbus_rtu_build_request_basis,
    _modbus_rtu_build_response_basis,
    _modbus_rtu_prepare_response_tid,
    _modbus_rtu_send_msg_pre,
    _modbus_rtu_send,
    _modbus_rtu_receive,
    _modbus_rtu_recv,
    _modbus_rtu_check_integrity,
    _modbus_rtu_pre_check_confirmation,
    _modbus_rtu_connect,
    _modbus_rtu_is_connected,
    _modbus_rtu_close,
    _modbus_rtu_flush,
    _modbus_rtu_select,
    _modbus_rtu_free
};

关键函数

来看一下modbus_new_rtu()函数中具体干了什么,其中只调用了一个函数_modbus_init_common,这个函数中对ctx中的一些基本成员进行初始化,剩下的都是直接对ctx结构体成员进行初始化,比如下面这些

c 复制代码
ctx_rtu->baud = baud;
    if (parity == 'N' || parity == 'E' || parity == 'O') {
        ctx_rtu->parity = parity;
    } else {
        modbus_free(ctx);
        errno = EINVAL;
        return NULL;
    }
    ctx_rtu->data_bit = data_bit;
    ctx_rtu->stop_bit = stop_bit;

#if HAVE_DECL_TIOCSRS485
    /* The RS232 mode has been set by default */
    ctx_rtu->serial_mode = MODBUS_RTU_RS232;
#endif
#if HAVE_DECL_TIOCM_RTS
    /* The RTS use has been set by default */
    ctx_rtu->rts = MODBUS_RTU_RTS_NONE;
    /* Calculate estimated time in micro second to send one byte */
    ctx_rtu->onebyte_time =
        1000000 * (1 + data_bit + (parity == 'N' ? 0 : 1) + stop_bit) / baud;
    /* The internal function is used by default to set RTS */
    ctx_rtu->set_rts = _modbus_rtu_ioctl_rts;
    /* The delay before and after transmission when toggling the RTS pin */
    ctx_rtu->rts_delay = ctx_rtu->onebyte_time;
#endif
    ctx_rtu->confirmation_to_ignore = FALSE;

最关键的一部就是对backend进行设置ctx->backend = &_modbus_rtu_backend;.

举个例子,当我们设置从设备ID时调用下面的函数,其实就是调用了backend中的set_slave函数指针指向的函数,该函数就是上面的_modbus_rtu_backend中的_modbus_set_slave函数。

c 复制代码
int modbus_set_slave(modbus_t *ctx, int slave)
{
    if (ctx == NULL) {
        errno = EINVAL;
        return -1;
    }

    return ctx->backend->set_slave(ctx, slave);
}

以从设备的接收为例,其使用下面这个函数进行接收:

c 复制代码
int modbus_receive(modbus_t *ctx, uint8_t *req)
{
    if (ctx == NULL) {
        errno = EINVAL;
        return -1;
    }

    return ctx->backend->receive(ctx, req);
}

可以看到,仍然使用的是backend中的receive函数,也就是下面这个函数,其真正的主体调用了_modbus_receive_msg函数

c 复制代码
static int _modbus_rtu_receive(modbus_t *ctx, uint8_t *req)
{
    int rc;
    modbus_rtu_t *ctx_rtu = ctx->backend_data;

    if (ctx_rtu->confirmation_to_ignore) {
        _modbus_receive_msg(ctx, req, MSG_CONFIRMATION);
        /* Ignore errors and reset the flag */
        ctx_rtu->confirmation_to_ignore = FALSE;
        rc = 0;
        if (ctx->debug) {
            printf("Confirmation to ignore\n");
        }
    } else {
        rc = _modbus_receive_msg(ctx, req, MSG_INDICATION);
        if (rc == 0) {
            /* The next expected message is a confirmation to ignore */
            ctx_rtu->confirmation_to_ignore = TRUE;
        }
    }
    return rc;
}
相关推荐
YaBingSec2 小时前
玄机靶场-第三届-长城杯-初赛-SnakeBackdoor WP
java·运维·笔记·tomcat·ssh
handler0111 小时前
从源码到二进制:深度拆解 Linux 下 C 程序的编译与链接全流程
linux·c语言·开发语言·c++·笔记·学习
寒秋花开曾相惜14 小时前
(学习笔记)4.1 Y86-64指令集体系结构(4.1.4 Y86-64异常&4.1.5 Y86-64程序)
开发语言·jvm·数据结构·笔记·学习
Theodore_102215 小时前
深度学习(15):倾斜数据集 & 精确率-召回率权衡
人工智能·笔记·深度学习·机器学习·知识图谱
不会聊天真君64715 小时前
JavaScript基础语法(Web前端开发笔记第三期)
前端·javascript·笔记
Fᴏʀ ʏ꯭ᴏ꯭ᴜ꯭.15 小时前
Redis主从复制配置全攻略
数据库·redis·笔记
海绵宝宝的月光宝盒16 小时前
2-非金属材料
经验分享·笔记·学习·其他·职场和发展·课程设计·制造
zzh08117 小时前
GluserFS笔记
笔记
三品吉他手会点灯19 小时前
C语言学习笔记 - 9.C概述 - 常见问题答疑
c语言·笔记·学习