Visual Studio 上基于libmodbus库的modbus RTU模式的通讯模拟

Visual Studio 上基于libmodbus库的modbus RTU模式的通讯模拟

   本文主要为《Modbus 软件开发实战指南》的代码。看这本书的缘由是,我在做libmodbus+qt上位机,参考了网上的一些资料,照着来了一遍,总是编译不过,就在博客中搜索,意外中看到了这本书,下载pdf居然也要充钱收费,于是借了书,针对其中有用的部分,码字分享之,希望对各位有用。

libmodbus库的编译

libmodbus的库在VS平台上的编译,与在Msys上的编译略有不同。编译的步骤如下:

  1. 在github上下载libmodbus源代码,下载网址:https://github.com/stephane/libmodbus.
  2. 解压完成后,在src/win32文件目录下,双击configure.js,生成了config.h文件和modbus-version.h文件。
  3. 打开项目文件modbus-9.sln,在弹出的文件升级对话框中,点击确定。
  4. 将源文件夹下的modbus-version.h替换成新生成的modbus-version.h。
  5. 右键点击项目,选择属性,打开属性对话框,选择该VS版本下的SDK,并去除掉Version选项的定义。
  6. 编译通过。生成modbus.dll和modbus.lib文件。

RTU Master端的编写

新建项目工程,将之前生成的dll文件、lib文件以及libmodbus库里的头文件,复制到项目文件所在的目录下,并通过添加现有文件到项目中,至此libmodbus中的各种接口函数可以应用了。
1.TestRtuMaster.cpp

#include <stdio.h>
#ifdef _MSC_VER
#ifndef _UNISTD_H     /*unistd.h头文件的替代*/
#define _UNISTD_H
#include <io.h>
#include <process.h>
#endif                
#endif
#include <string.h>
#include<stdlib.h>
#include <errno.h>     //查看错误代码的头文件

#include "modbus.h"   //引用libmodbus数据库


#define LOOP   1           //宏定义循环次数
#define SERVER_ID   17     //从机端设备地址
#define  ADDRESS_START  0  //测试寄存器的起始地址
#define  ADDRESS_END  99   //测试寄存器的结束地址

int main(void)
{
	modbus_t *ctx;   //modbus_t结构体指针变量
	int rc;
	int nb_fail;   //表示什么
	int nb_loop;
	int addr;
	int nb;      //寄存器的个数
	uint8_t *tab_rq_bits;
	uint8_t *tab_rp_bits;
	uint16_t *tab_rq_registers;
	uint16_t *tab_rw_rq_registers;
	uint16_t *tab_rp_registers;

	/*RTU*/
	ctx = modbus_new_rtu("COM3", 19200, 'N', 8, 1); //生成并初始化一个modbus结构体在串行线路中使用RTU模式通讯,N表示无奇偶位校验
	modbus_set_slave(ctx, SERVER_ID);  //设置从机端地址

	modbus_set_debug(ctx, TRUE); //启动调试模式,标志位设置为TRUE,表示会在stdout和stderr(标准输出设备)上显示很多冗长的信息,用于显示modbus消息的字节

	if (modbus_connect(ctx) == -1)  //建立modbus连接,若连接成功,该函数返回0,否则,返回-1,并将errno设置为底层系统调用的定义值
	{
		fprintf(stderr, "Connection failed :%s\n", modbus_strerror(errno));  //通过调用errno,捕获程序失败的原因
		modbus_free(ctx);  //释放已分配的modbus结构体
		return -1;
	}

	nb = ADDRESS_END - ADDRESS_START;  //计算需测试的寄存器个数

	//申请内存,用于保存发送以及接收的数据
	tab_rq_bits = (uint8_t *)malloc(nb * sizeof(uint8_t));
	memset(tab_rq_bits, 0, nb * sizeof(uint8_t));

	tab_rp_bits = (uint8_t *)malloc(nb * sizeof(uint8_t));
	memset(tab_rp_bits, 0, nb * sizeof(uint8_t));

	tab_rq_registers = (uint16_t *)malloc(nb * sizeof(uint16_t));
	memset(tab_rq_registers, 0, nb * sizeof(uint16_t));

	tab_rp_registers = (uint16_t *)malloc(nb * sizeof(uint16_t));
	memset(tab_rp_registers, 0, nb * sizeof(uint16_t));

	tab_rw_rq_registers = (uint16_t *)malloc(nb * sizeof(uint16_t));
	memset(tab_rw_rq_registers, 0, nb * sizeof(uint16_t));

	nb_loop = nb_fail = 0;
	while (nb_loop++ < LOOP)
	{
		//从起始地址开始顺序测试
		for (addr = ADDRESS_START; addr < ADDRESS_END; addr++)
		{
			int i;
			//生成随机数
			for (i = 0; i < nb; i++)
			{
				tab_rq_registers[i] = (uint16_t)(65535.0 * rand() / (RAND_MAX + 1.0));
				tab_rw_rq_registers[i] = ~tab_rq_registers[i];
				tab_rq_bits[i] = tab_rq_registers[i] % 2;

			}
			nb = ADDRESS_END - addr;

			rc = modbus_write_bit(ctx, addr, tab_rq_bits[0]);  //这个函数的第三个参数是status值,不对应,我有点不太理解 
			if (rc != 1)
			{
				printf("ERROR modbus _write_bit(%d)\n", rc);
				printf("Address = %d, value = %d\n", addr, tab_rq_bits[0]);
				nb_fail++;   //nb_fail==nb_loop
			}
			else
			{
				//写入之后,读取并进行比较
				rc = modbus_read_bits(ctx, addr, 1, tab_rp_bits);
				if (rc != 1 || tab_rq_bits[0] != tab_rp_bits[0])
				{
					printf("ERROR modbus_read_bits single(%d)\n", rc);
					printf("address =%d\n", addr);
					nb_fail++;
				}

			}
			/*测试线圈寄存器的批量读写*/
			rc = modbus_write_bits(ctx, addr, nb, tab_rq_bits);  //对应0x0F功能
			if (rc != nb)
			{
				printf("ERROR modbus_write_bits(%d)\n", rc);
				printf("Address=%d,nb=%d\n", addr, nb);
				nb_fail++;
			}
			else
			{
				//写入之后,再读取并比较
				rc = modbus_read_bits(ctx, addr, nb, tab_rp_bits);  //对应0x01功能
				if (rc != nb)
				{
					printf("ERROR modbus_read_bits\n");
					printf("Address=%d,nb=%d\n", addr, nb);
					nb_fail++;
				}
				else
				{
					//进行比较
					for (i = 0; i < nb; i++)
					{
						if (tab_rp_bits[i] != tab_rq_bits[i])
						{
							printf("ERROR modbus_read_bits\n");
							printf("Addr=%d,Val=%d(0x%X)!=%d(0x%X)\n", addr, tab_rq_bits[i], tab_rq_bits[i], tab_rp_bits[i], tab_rp_bits[i]);
							nb_fail++;
						}
					}
				}
			}

			/*测试保持寄存器的单个读写*/
			rc = modbus_write_register(ctx, addr, tab_rq_registers[0]);  //写单个寄存器,对应功能0x06
			if (rc != 1)
			{
				printf("ERROR modbus_write_register(%d)\n", rc);
				printf("Address=%d,Val=%d(0x%X)\n", addr, tab_rq_registers[0], tab_rq_registers[0]);
				nb_fail++;
			}
			else
			{
				//写入后再进行读取
				rc = modbus_read_registers(ctx, addr, 1, tab_rp_registers); //读取保持寄存器,对应功能0x03
				if (rc != 1)
				{
					printf("ERROR modbus_read_registers(%d)\n", rc);
					printf("Address = %d\n", addr);
					nb_fail++;
				}
				else
				{
					//读取后进行比较
					if (tab_rq_registers[0] != tab_rp_registers[0])
					{
						printf("ERROR modbus_read_registers\n");
						printf("Address=%d,Val=%d(0x%X)!=%d(0x%X)\n", addr, tab_rq_registers[0], tab_rq_registers[0], tab_rp_registers[0], tab_rp_registers[0]);
						nb_fail++;
					}
				}

			}

			/*测试多个寄存器的批量读写*/
			rc = modbus_write_registers(ctx, addr, nb, tab_rq_registers);  //写多个寄存器,对应功能0x16
			if (rc != nb)
			{
				printf("ERROR modbus_write_registers(%d)\n", rc);
				printf("Address=%d,nb=%d\n", addr, nb);
				nb_fail++;
			}
			else
			{
				rc = modbus_read_registers(ctx, addr, nb, tab_rp_registers); //读取保持寄存器,对应功能0x03
				if (rc != nb)
				{
					printf("ERROR modbus_read_registers(%d)\n", rc);
					printf("Address=%d\n", addr, nb);
					nb_fail++;
				}
				else
				{
					for (i = 0; i < nb; i++)
					{
						//进行比较
						if (tab_rq_registers[i] != tab_rp_registers[i])
						{
							printf("ERROR modbus_read_registers\n");
							printf("Address=%d,value%d(0x%X)!=%d(0x%X)\n,addr,tab_rq_registers[i],tab_rq_registers[i],tab_rp_registers[i],tab_rp_registers[i]");
							nb_fail++;
						}
					}
				}

				/*读写多个寄存器的测试*/
				rc = modbus_write_and_read_registers(ctx, addr, nb, tab_rw_rq_registers, addr, nb, tab_rp_registers);//读写多个寄存器,对应功能ox17
				if (rc != nb)
				{
					printf("ERROR modbus_read_and_write_registers(%d)\n", rc);
					printf("Address=%d,nb=%d\n", addr, nb);
					nb_fail++;
				}
				else
				{
					//读取并比较
					for (i = 0; i < nb; i++)
					{
						if (tab_rp_registers[i] != tab_rw_rq_registers[i])
						{
							printf("ERROR modbus_read_and_write_registers READ\n");
							printf("Address=%d,value%d(0x%X)!=%d(0x%X)\n", addr, tab_rp_registers[i], tab_rq_registers[i], tab_rp_registers[i], tab_rw_rq_registers[i]);
							nb_fail++;
						}
					}

					rc = modbus_read_registers(ctx, addr, nb, tab_rp_registers);
					if (rc != nb)
					{
						printf("ERROR modbus_read_registers(%d)\n", rc);
						printf("Address=%d,nb=%d\n", addr, nb);
						nb_fail++;
					}
					else
					{
						for (i = 0; i < nb; i++)
						{
							if (tab_rw_rq_registers[i] != tab_rp_registers[i])
							{
								printf("ERROR modbus_read_and_write_registers\n");
								printf("Address=%d,Val %d(0x%X)!=%d(0x%X)\n", addr, tab_rw_rq_registers[i], tab_rw_rq_registers[i], tab_rp_registers[i], tab_rp_registers[i]);
								nb_fail++;
							}
						}
					}
				}
			}
			printf("Test:");
			if (nb_fail)
				printf("%d FAIL\n", nb_fail);
			else
				printf("SUCCESS\n");
		}

		/*释放内存空间*/
		free(tab_rq_bits);
		free(tab_rp_bits);
		free(tab_rq_registers);
		free(tab_rp_registers);
		free(tab_rw_rq_registers);

		/*关闭连接*/
		modbus_close(ctx);
		modbus_free(ctx);

		return 0;
	}
}


其中有几个需要补充的说明和几个函数用法介绍如下:

  • unistd.h头文件无法使用,采用了它的替换方式;
  • include <errno.h>:是查看错误代码的头文件;
  • modbus_new_rtu():用来生成并初始化一个modbus_t结构体,在串行线路中使用RTU模式通讯,N表示无奇偶位校验;
  • modbus_set_slave():设置从机端的ID;
  • modbus_set_debug():启动调试模式,标志位设置为TRUE,表示会在stdout和stderr(标准输出设备)上显示很多冗长的信息,用于显示modbus消息的字节;
  • modbus_connect():建立modbus连接,若连接成功,该函数返回0,否则,返回-1,并将errno设置为底层系统调用的定义值;
  • modbus_strerror():通过调用errno,可捕获程序失败的原因;
  • modbus_free():释放已分配的modbus_t结构体;
  • modbus_write_bit():线圈的单个写操作
    int modbus_write_bit(modbus_t *ctx,int addr,int status),其中status为True或false,对应Modbus功能码0x05;
  • modbus_read_bits():线圈的读操作
    int modbus_read_bits(modbus_t *ctx,int addr,int nb,uint8_t *dest),表示将nb位(线圈)的状态读取到地址为addr的设备上,并存储到dest数组中去,设置为TRUE或FALSE的无符号字节。对应Modbus功能码0x01;
  • modbus_write_bits(): 多个线圈的批量写操作
    int modbus_write_bits(modbus_t *ctx,int addr,int nb,const uint8_t *src),表示从设备地址addr处的src数组写入nb位(线圈)的状态,其中src的数组要包含设置有TRUE或FALSE的字节,对应Modbus功能码0x0F;
  • modbus_write_register(): 单个保持寄存器的写操作
    int modbus_write_register(modbus_t *ctx,int addr,int value),用于将保持寄存器的值写在地址为addr的设备上,对应Modbus功能码为0x06;
  • modbus_write_registers():多个保持寄存器的写操作
    int modbus_write_registers(modbus_t *ctx,int addr,int nb,const uint16_t *src);表示将src数组中的内容写到地址为addr的nb个寄存器中,对应Modbus功能码为0x16;
  • modbus_read_registers():读取保持寄存器
    int modbus_read_registers(modbus_t *ctx,int addr, int nb, uint16_t *dest),表示读取地址设备在addr处开始的nb位(保持寄存器)的状态,读取结果以uint16位的形式存储在dest数组中,对应Modbus功能码0x03;
  • modbus_write_and_read_registers:多个寄存器的读和写
    int modbus_write_and_read_registers(modbus_t *ctx, int write_addr, int write_nb, const uint16_t *src, int read_addr, int read_nb, const uint16_t *dest),表示将src数组中的内容写到远程设备write_addr地址处的一组write_nb个寄存器,然后读取read_addr处的一组read_nb个寄存器内容并保存到dest数组,对应Modbus功能码0x17。

RTU-Master的代码流程小结

1.初始化并生成modbus_t 结构体;
2.设置从机端的ID;
3.启动调试模式;
4.建立modbus连接;
5.申请内存;
6.寄存器/线圈的赋值;
7.线圈寄存器的单个读写/批量读写/保持寄存器的单个读写/批量读写/读取多个寄存器;
8.释放内存;
9.关闭modbus连接;
10.释放modbus结构体

RTU-Slave端的编写

同上
1.TestRtuSlave.cpp:

#include <stdio.h>
#ifndef _MSC_VER
#endif
#include <stdlib.h>
#include <errno.h>
#include "modbus.h"
#define SERVER_ID 17

int main(void)
{
	modbus_t *ctx;
	modbus_mapping_t *mb_mapping;

	/*RTU*/
	ctx = modbus_new_rtu("COM2", 19200, 'N', 8, 1);  //创建一个RTU的容器
	modbus_set_slave(ctx, SERVER_ID);
	modbus_set_debug(ctx, TRUE);
	if (modbus_connect(ctx) == -1)
	{
		fprintf(stderr, "Connection failed:%s\n", modbus_strerror(errno));
		modbus_free(ctx);
		return -1;
	}

	

	mb_mapping = modbus_mapping_new(500, 500, 500, 500);
	if (mb_mapping == NULL)
	{
		fprintf(stderr, "Error mapping: %s\n", modbus_strerror(errno));
		modbus_free(ctx);
		return -1;
	}

	for (;;)
	{
		uint8_t query[MODBUS_TCP_MAX_ADU_LENGTH];
		int rc;

		rc = modbus_receive(ctx, query);
		if (rc > 0)
		{
			modbus_reply(ctx, query, rc, mb_mapping);
		}
		else
		{
			printf("Connection Closed\n");
		}
	}
	printf("Quit the loop :%s\n", modbus_strerror(errno));

	modbus_mapping_free(mb_mapping);
	modbus_close(ctx);
	modbus_free(ctx);
	return 0;
}

补充函数说明

  • modbus_mapping_new()
    modbus_mapping_t modbus_mapping_new(int nb_bits,int nb_input_bits,int nb_registers,int nb_registers);
    modbus_mapping_new()函数需要分配4个数组来存储位、输入位、寄存器和输入寄存器。若不需要为特定类型的数据分配数组,可在参数中传递0值,关联指针则为NULL。
    该函数返回一个modbus_mapping_t指针。
  • modbus_receive()
    int modbus_receive(modbus_t *ctx,uint8_t *req);
    modbus_receive()函数是Modbus从机端/服务器使用此功能接收和分析主机端/客户机发送的指示请求。
  • modbus_reply()
    *int modbus_reply(modbus_t *ctx,const uint8_t *req,int req_length,modbus_mapping_t *mb_mapping);
    modbus_reply()函数是对接收到的请求发送响应,先是分析参数中请求req,然后利用ctx来构建和发送响应。此功能是为Modbus 服务器设计的。
  • modbus_mapping_free()
    void modbus_mapping_free(modbus_mapping_t *mb_mapping);
    modbus_mapping_free()函数将释放mb_mapping_t结构中的四个数组,并最终释放mb_mapping所引用的mb_mapping_t。

RTU-Slave的代码流程小结:

1.初始化并生成modbus_t 结构体;
2.设置从机端的ID;
3.起点调试模式;
4.建立modbus连接;
5.modbus_mapping_new()初始化寄存器,返回一个modbus_mapping_t 指针;
6.调用modbus_receive()函数判断串口的接收数据,负责接收和分析;
7.调用modbus_reply()函数,对接收到的请求指示发送响应(回复);
8.释放modbus_mapping_t 结构体;
9.关闭modbus连接;
10.释放modbus_t 结构体。

创建虚拟串口

下载Configure Virtual Serial Port Driver,新建虚拟端口COM2和COM3,可在设备管理器中查询是否建立。
新建虚拟串口
设备管理器中查询
分别启动两个项目,则可完成modbus RTU模式的通讯。
modbus RTU通讯执行命令窗口
至此,Modbus RTU模式的通讯测试完成,希望对你有用,谢谢~
参考1:libmodbus手册翻译
参考2:《Modbus 软件开发实战指南》
参考3:modbus之libmodbus

评论 22
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值