在之前的文章:
《 libmodbus协议栈1——Linux下详细移植步骤(配置、生成) 》
《 libmodbus协议栈2—— Linux下 modbus RTU master 开发案例 》
《 libmodbus协议栈3—— Linux下 modbus RTU 从机 开发案例 》
《 libmodbus协议栈4—— 总结 》
我们是从 应用的角度了解到了 libmodbus 的使用方法,经过最近一段时间的测试,我发现该库文件 还是比较稳定的,尤其是其提供了 故障恢复 功能,大大的提高了其稳定性。下面我们就从源码的角度,分析一下 libmodbus的基本原理,这里我们以最常用的modbus rtu 来分析,也就是 用于串口。
该类型是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;
该数据结构 为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 的各种 操作接口函数。
该结构是用于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个区寄存器参数的定义。
这两个函数的功能是一样的,申请并初始化一个 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);
}
该函数 用于设定 libmodbus 运行过程中,发生错误时的 如何恢复,该函数非常重要,因为程序的运行 少不了出现各种异常,异常不怕,只要 有应对,程序还是稳定的,示例代码如下:
modbus_set_error_recovery(ctx,
MODBUS_ERROR_RECOVERY_LINK |
MODBUS_ERROR_RECOVERY_PROTOCOL);
代码中,MODBUS_ERROR_RECOVERY_LINK 表示 断开连接后,自动恢复。
MODBUS_ERROR_RECOVERY_PROTOCOL 表示协议层面 出现故障时,自动恢复,比如从设备超时等。
设定 modbus 设备地址,如果我们的设备是主机,则该函数 设定要读的从机地址,反之,如果我们的设备是从机,则该函数设定本机地址,示例代码如下:
modbus_set_slave(ctx, SERVER_ID);
该函数字面意思上可以看出来,是 实现 “连接”功能,只有调用该函数成功后,才能进行通信,前面的函数都是申请、 初始化各种参数,对硬件设备(串口、网口)并没有实际的 动作,该函数就是对底层硬件接口的动作,在modbus rtu模式下,就是设置并打开串口, 而 在modbus tcp模式下则是 创建socket连接。
该函数 是libmodbus 用于主机时,读 各区(0x、1x、3x、4x) 的接口函数。需要说明的是,这个函数不仅仅 包含“读”功能,它包含了“读”功能的所有步骤:
① 创建并打包 读命令。
② 发送 步骤①中的读指令。
③ 接收从机返回数据。
④ 对返回数据 进行各种校验,比如设备地址,地址、数量、CRC校验等。
⑤ 将正确的返回 有效数据 存放到 我们指定的 缓存中。
当libmodbus 用作主机时,写 0x、4x区 的接口函数,类似于 modbus_read_xxxx 包含了所有的功能。
libmodbus 无论是作为主机还是从机使用,都会使用该函数,也就是接收功能,该函数 在linux下采用select 机制,所不同的是,当libmodbus 作为 主机时,select 中有 超时,不会一直阻塞,而作为从机时,select中的超时为 0, 也就是会阻塞,这也是必然的,而且该函数 还不是 一下子接收完毕,往往是分三步接收,这一点在后面的文章会详细介绍。
这两个函数功能 本质上是一样的,前者也是通过调用后者来实现的的。当libmodbus作为 从机(服务器)使用时,通过该函数申请并初始化4个区寄存器 缓存。
从函数字面意思上看,当libmodbus 作为 从机(服务器)使用时,用于对主机访问的 回复。该函数的的实现代码还是比较多的,毕竟要 相应 主机的一切需求,不仅正确的,还有非法的需求。一般只要我们 调用 modbus_receive 接收数据完成后,直接调用 该函数,libmodbus就会自动的 组建打包 对应的 数据包,然后返回给 主机。
关闭libmodbus。
当libmodbus用作 从机(服务器)时,释放 4个区寄存器的缓存。
释放 libmodbus 的资源,也就是 最开始 初始化 申请的 modbus_t *ctx;