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;
}
相关推荐
じ☆冷颜〃1 天前
实分析与测度论、复分析、傅里叶分析、泛函分析、凸分析概述.
笔记·学习·数学建模·拓扑学·傅立叶分析
kobesdu1 天前
【ROS2实战笔记-19】ROS2 生命周期节点的启动顺序、状态转换陷阱与热备方案
java·前端·笔记·机器人·ros·ros2
谙弆悕博士1 天前
快速学C语言——第16章:预处理
c语言·开发语言·chrome·笔记·创业创新·预处理·业界资讯
handler011 天前
UDP协议与网络通信知识点
c语言·网络·c++·笔记·网络协议·udp
sheeta19981 天前
LeetCode 每日一题笔记 日期:2026.05.13 题目:1674. 使数组互补的最少操作次数
笔记·算法·leetcode
叁散1 天前
实验项目1 LTE通信原理与应用
笔记·其他
AOwhisky1 天前
Docker 学习笔记:镜像分发、容器运行与资源限制
笔记·学习·docker
TANGLONG2221 天前
【C++】继承详解——基类/派生类、作用域、默认函数、菱形继承(超详细)
java·c语言·c++·经验分享·笔记·ajax
木木_王1 天前
嵌入式学习 | STM32裸板驱动开发(Day01)入门学习笔记(超详细完整版|点灯实验 + 库函数代码 + 原理全解)
linux·驱动开发·笔记·stm32·学习
largecode1 天前
能不能让座机号码显示“XX公司”那样的认证名称?申请号码认证方法
经验分享·笔记·音视频·课程设计·oneapi·segmentfault·微信开放平台