libmodbus 源码分析(1)基本框架、关键数据结构、接口

在之前的文章:

《 libmodbus协议栈1——Linux下详细移植步骤(配置、生成) 》

《 libmodbus协议栈2—— Linux下 modbus RTU master 开发案例 》

《 libmodbus协议栈3—— Linux下 modbus RTU 从机 开发案例 》

《 libmodbus协议栈4—— 总结 》

  我们是从 应用的角度了解到了 libmodbus 的使用方法,经过最近一段时间的测试,我发现该库文件 还是比较稳定的,尤其是其提供了 故障恢复 功能,大大的提高了其稳定性。下面我们就从源码的角度,分析一下 libmodbus的基本原理,这里我们以最常用的modbus rtu 来分析,也就是 用于串口。

一、关键数据结构、接口函数

1、modbus_t 

 该类型是libmodbus的最基本的 数据结构,它是一个结构体,源码定义如下:

struct _modbus {
    /* Slave address */
    int slave;
    /* Socket or file descriptor */
    int s;
    int debug;
    int error_recovery;
    struct timeval response_timeout;
    struct timeval byte_timeout;
    const modbus_backend_t *backend;
    void *backend_data;
};

  其中:

/* 
 * 设备地址,如果我们使用libmodbus作为主机,则slave是 我们想要访问的从机地址 
 *           如果我们使用libmodbus作为从机(服务器),则slave是 本机地址
 */
int slave;

/* 
 * 文件描述符,如果是modbus rtu 则是串口的 文件描述符,如果是modbus tcp 则是 socket 文件描述符
 */
int s;

/* 
 * debug 功能使能标志
 */
int debug;

/* 
 * 错误恢复标志,这个很重要,是保证 libmodbus运行稳定的 关键,设置相应的 错误 异常恢复功能。
 */
int error_recovery;

/* 
 * 响应超时 参数
 */
struct timeval response_timeout;

/* 
 * 数据帧 字节 延时,这个是modbus 协议中定义的参数,如果那个 3.5 字节长度时间
 */   
struct timeval byte_timeout;

/* 
 * 这个有多种翻译解释,我把它称作 回调函数 集,作者将代码做了很好的封装接口,modbus rtu 和 
 * modbus tcp 的访问接口基本相同,但是在初始化时,赋值不同的函数指针。 
 */
const modbus_backend_t *backend;

/* 
 * 回调 参数,这个一般存放 通信参数,比如modbus rtu 下,就是串口的通信参数。
 */ 
void *backend_data;

2、modbus_backend_t

   该数据结构 为modbus 的 回调函数集 结构体,源码如下:

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);
    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 rtu 和  modbus tcp 的访问接口是相同,但是在初始化时,赋值不同协议的函数指针,就是modbus 的各种 操作接口函数。

3、modbus_mapping_t 

   该结构是用于libmodbus 作为从机使用时,modbus 各区 寄存器 集合的指针,源码如下:

typedef struct {
    int nb_bits;
    int start_bits;
    int nb_input_bits;
    int start_input_bits;
    int nb_input_registers;
    int start_input_registers;
    int nb_registers;
    int start_registers;
    uint8_t *tab_bits;
    uint8_t *tab_input_bits;
    uint16_t *tab_input_registers;
    uint16_t *tab_registers;
} modbus_mapping_t;

  从上面的定义,也可以看出,就是对 0x、1x、3x、4x 共 4个区寄存器参数的定义。

4、modbus_new_tcp 、 modbus_new_rtu 

  这两个函数的功能是一样的,申请并初始化一个 modbus_t *ctx 结构指针,后面所有的modbus操作,都是基于这个ctx,所不同的是modbus_new_tcp 用于 modbus tcp, 而modbus_new_rtu 用于modbus rtu,具体的功能就是 申请 modbus_t 结构指针,然后 赋值 不同的  回调函数。

示例代码:

if (use_backend == TCP) {
        ctx = modbus_new_tcp("127.0.0.1", 1502);
} else if (use_backend == TCP_PI) {
        ctx = modbus_new_tcp_pi("::1", "1502");
} else {
        ctx = modbus_new_rtu("/dev/ttyUSB1", 115200, 'N', 8, 1);
}

5、modbus_set_error_recovery

    该函数 用于设定 libmodbus 运行过程中,发生错误时的 如何恢复,该函数非常重要,因为程序的运行 少不了出现各种异常,异常不怕,只要 有应对,程序还是稳定的,示例代码如下:

modbus_set_error_recovery(ctx,
                          MODBUS_ERROR_RECOVERY_LINK |
                          MODBUS_ERROR_RECOVERY_PROTOCOL);

  代码中,MODBUS_ERROR_RECOVERY_LINK 表示 断开连接后,自动恢复。

  MODBUS_ERROR_RECOVERY_PROTOCOL 表示协议层面 出现故障时,自动恢复,比如从设备超时等。

6、modbus_set_slave

   设定 modbus 设备地址,如果我们的设备是主机,则该函数 设定要读的从机地址,反之,如果我们的设备是从机,则该函数设定本机地址,示例代码如下:

 modbus_set_slave(ctx, SERVER_ID);

7、modbus_connect 

   该函数字面意思上可以看出来,是 实现 “连接”功能,只有调用该函数成功后,才能进行通信,前面的函数都是申请、 初始化各种参数,对硬件设备(串口、网口)并没有实际的 动作,该函数就是对底层硬件接口的动作,在modbus rtu模式下,就是设置并打开串口, 而 在modbus tcp模式下则是 创建socket连接。

8、modbus_read_xxxx 

     该函数 是libmodbus 用于主机时,读 各区(0x、1x、3x、4x) 的接口函数。需要说明的是,这个函数不仅仅 包含“读”功能,它包含了“读”功能的所有步骤:

 ① 创建并打包 读命令。

 ② 发送 步骤①中的读指令。

 ③ 接收从机返回数据。

 ④ 对返回数据 进行各种校验,比如设备地址,地址、数量、CRC校验等。

 ⑤ 将正确的返回 有效数据 存放到 我们指定的 缓存中。

9、modbus_write_xxxx

  当libmodbus 用作主机时,写 0x、4x区 的接口函数,类似于 modbus_read_xxxx 包含了所有的功能。

10、modbus_receive

   libmodbus 无论是作为主机还是从机使用,都会使用该函数,也就是接收功能,该函数 在linux下采用select 机制,所不同的是,当libmodbus 作为 主机时,select 中有 超时,不会一直阻塞,而作为从机时,select中的超时为 0, 也就是会阻塞,这也是必然的,而且该函数 还不是 一下子接收完毕,往往是分三步接收,这一点在后面的文章会详细介绍。

11、modbus_mapping_new 、 modbus_mapping_new_start_address

   这两个函数功能 本质上是一样的,前者也是通过调用后者来实现的的。当libmodbus作为 从机(服务器)使用时,通过该函数申请并初始化4个区寄存器 缓存。

12、modbus_reply

   从函数字面意思上看,当libmodbus 作为 从机(服务器)使用时,用于对主机访问的 回复。该函数的的实现代码还是比较多的,毕竟要 相应 主机的一切需求,不仅正确的,还有非法的需求。一般只要我们 调用 modbus_receive 接收数据完成后,直接调用 该函数,libmodbus就会自动的 组建打包 对应的 数据包,然后返回给 主机。

13、modbus_close 

  关闭libmodbus。

14、modbus_mapping_free

    当libmodbus用作 从机(服务器)时,释放 4个区寄存器的缓存。

15、modbus_free 

    释放 libmodbus 的资源,也就是 最开始 初始化 申请的  modbus_t *ctx;

 

 

你可能感兴趣的:(Linux,嵌入式)