libmodbus情景分析

描述

11.3 libmodbus情景分析

以“modbus_write_bits”函数为例,分析下图的执行流程:

LibModbus

11.3.1

初始化

1. 主设备初始化

主设备程序先调用“modbus_new_rtu”函数,仅仅是分配一个modbus结构体,在里面记录要使用的串口设备、参数:

左右滑动查看完整内容

 

modbus_t *
modbus_new_rtu(const char *device, int baud, char parity, int data_bit, int stop_bi
t);

 

再调用“modbus_set_slave”,是设置“要访问哪个从设备”。每当访问不同地址的设备之前,都应该调用这个函数。

最后调用“modbus_connect”函数,这个函数只是打开串口、设置串口参数,并没有跟从设备进行数据交互。

2. 从设备初始化

从设备的初始化,跟主设备类似,不过多了使用“modbus_mapping_new_start_address”函数创建寄存器 buffer。

modbus_mapping_t结构体如下定义:

左右滑动查看完整内容

 

typedef struct _modbus_mapping_t {
 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;

 

它被用来描述DI、DO、AI、AO四类寄存器。以DO寄存器为例,这个结构体里有3个成员:

①nb_bits:DO寄存器个数。

②start_bits:DO寄存器起始寄存器地址。

③tab_bits:指向一个“uint8_t”类型的数组,里面每个数组项表示一个DO寄存器,这个数组大小为nb_bits。数组中第0项,对应第“start_bits”个DO寄存器。

“modbus_mapping_new_start_address”函数原型

如下:

左右滑动查看完整内容

 

/* Allocates 4 arrays to store bits, input bits, registers and inputs
 registers. The pointers are stored in modbus_mapping structure.
 The modbus_mapping_new_start_address() function shall return the new allocated
 structure if successful. Otherwise it shall return NULL and set errno to
 ENOMEM. */
modbus_mapping_t *modbus_mapping_new_start_address(unsigned int start_bits,
 unsigned int nb_bits,
 unsigned int start_input_bits,
 unsigned int nb_input_bits,
 unsigned int start_registers,
 unsigned int nb_registers,
 unsigned int start_input_registers,
 unsigned int nb_input_registers);

 

假设从设备执行了如下代码:

左右滑动查看完整内容

 

modbus_mapping_t *mb_mapping;
mb_mapping = modbus_mapping_new_start_address(0,
 4, /* DO, 4 个寄存器 */
 0,
 3, /* DI, 3 个寄存器 */
 0, 
 2, /* AO, 2 个寄存器 */
 0,
 1; /* AI, 1 个寄存器 */

 

将会分配出如下结构体:

LibModbus

modbus传输的本质,就是读写上图中4个数组。

11.3.2

主设备发送请求

主设备调用“modbus_write_bits”函数,想写若干个DO寄存器,比如:

左右滑动查看完整内容

 

01 uint8_t buf[2] = {1, 0};
02 int rc = modbus_write_bits(ctx, 0, 2, buf);

 

根据Modbus RTU协议,它必定执行如下操作:

①构造数据包

②通过串口发送数据包

③等待、读取回复

对于上述代码,数据包的内容如下:

LibModbus

1. 先构造包头

函数调用关系如下:

LibModbus

2. 再构造数据

代码如下:

LibModbus

3. 计算检验码

在发送数据包的函数里,先计算检验码,代码如下:

LibModbus

4. 发送数据包

前面构造好了数据包,发送就比较简单:调用write函数进行发送即可。代码如下:

LibModbus

11.3.3

从设备接收请求

从设备的程序一直在等待主机发来的消息,示例代码如下:

LibModbus

“modbus_receive”函数内部实现为:

①使用select机制,逐个读取字符

②根据读到的字符,分辨还需要读多少数据

1. 读取单个字符

函数调用关系如下:

LibModbus

2. 判断还需要读取多少数据

从设备读取主设备发来的请求包时,步骤为:

①先读取“功能码”

②再根据功能码判断后续要的包头数据还剩多少,读取包头

③最后根据包头数据解析要读多少数据,读取数据。

流程如下图所示:

LibModbus

确定第1个阶段数据长度的代码如下:

LibModbus

读到功能码后,根据功能码计算剩下的包头的数据:

LibModbus

读到完整的包头后,计算剩下的数据长度:

LibModbus

3. 判断数据完整性

就是根据校验码判断数据是否有错误,代码如下:

LibModbus

11.3.4

从设备回应

从设备接收到请求后,调用如下函数进行处理、回应:

LibModbus

在“modbus_reply”函数内部,它会:

①对于写请求:把请求包中的数据解析出入,填入 mb_mapping中对应的寄存器buffer;

②对于读请求:从mb_mapping中对应的寄存器buffer取出数据;

③构造回复包,发送给主设备。

本情景分析中,主设备调用“modbus_write_bits”函数,想写两个DO寄存器,比如:

左右滑动查看完整内容

 

01 uint8_t buf[2] = {1, 0};
02 int rc = modbus_write_bits(ctx, 0, 2, buf);

 

从设备使用“modbus_reply”函数处理。

1. 根据请求包设置寄存器buffer

代码如下:

LibModbus

2. 构造回复包

对于“写多个DO寄存器”的请求,它的回复包格式如下:

LibModbus

下面的代码,构造的回复包里含有上图1、2的信息(在发送回复包时才构造校验码):

LibModbus

3. 发送回复包

最后,从设备发送回复包:

LibModbus

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分