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;
}
相关推荐
吃着火锅x唱着歌7 小时前
Effective C++ 学习笔记 条款37 绝不重新定义继承而来的缺省参数值
c++·笔记·学习
风曦Kisaki8 小时前
# Linux笔记:操作系统优化与资源管理
linux·运维·服务器·笔记
阿米亚波8 小时前
【C++】流式数据输入处理(不完全整理)
开发语言·c++·笔记
zhangrelay10 小时前
笔记本轻量高品质延寿工具完整分系统清单
运维·笔记·学习
巴巴媛66610 小时前
STM32学习笔记【38.DHT11实验】
笔记·stm32·学习
caimouse10 小时前
MM 学习笔记 01:miarm.h 核心头文件分析
笔记·学习·reactos
疯狂打码的少年13 小时前
【软件工程】软件测试阶段:单元测试与集成测试(高频对比考点)
笔记·单元测试·集成测试·软件工程
小秋求学记.13 小时前
第三天笔记
笔记
迷途呀14 小时前
CSDN Markdown 编辑器完全指南:从快捷键到高级图表
笔记·其他
栀栀栀栀栀栀14 小时前
刷刷刷 2026/7/16
笔记