11.3 libmodbus情景分析
以“modbus_write_bits”函数为例,分析下图的执行流程:
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 个寄存器 */
将会分配出如下结构体:
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协议,它必定执行如下操作:
①构造数据包
②通过串口发送数据包
③等待、读取回复
对于上述代码,数据包的内容如下:
1. 先构造包头
函数调用关系如下:
2. 再构造数据
代码如下:
3. 计算检验码
在发送数据包的函数里,先计算检验码,代码如下:
4. 发送数据包
前面构造好了数据包,发送就比较简单:调用write函数进行发送即可。代码如下:
11.3.3
从设备接收请求
从设备的程序一直在等待主机发来的消息,示例代码如下:
“modbus_receive”函数内部实现为:
①使用select机制,逐个读取字符
②根据读到的字符,分辨还需要读多少数据
1. 读取单个字符
函数调用关系如下:
2. 判断还需要读取多少数据
从设备读取主设备发来的请求包时,步骤为:
①先读取“功能码”
②再根据功能码判断后续要的包头数据还剩多少,读取包头
③最后根据包头数据解析要读多少数据,读取数据。
流程如下图所示:
确定第1个阶段数据长度的代码如下:
读到功能码后,根据功能码计算剩下的包头的数据:
读到完整的包头后,计算剩下的数据长度:
3. 判断数据完整性
就是根据校验码判断数据是否有错误,代码如下:
11.3.4
从设备回应
从设备接收到请求后,调用如下函数进行处理、回应:
在“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
代码如下:
2. 构造回复包
对于“写多个DO寄存器”的请求,它的回复包格式如下:
下面的代码,构造的回复包里含有上图1、2的信息(在发送回复包时才构造校验码):
3. 发送回复包
最后,从设备发送回复包:
全部0条评论
快来发表一下你的评论吧 !