Modbus协议学习第六篇之基于libmodbus库的示例程序(可以联合Modbus模拟仿真软件进行调试)

news2024/11/26 10:49:06

前置工作

        学了这么多Modbus的知识,如果不进行实际的操作,总感觉懂的不透彻。基于此, 本篇博文就带各位读者来了解下如何通过编写程序来模拟与Modbus Slave仿真软件的通讯。当然了,这里有两个前提,如下:

        1.请确保读者跟随我的第五篇博文进行了同等的操作,编译生成了modbus库。第五篇博文地址:libmodbus库的编译

        2.使用VSPD创建一对串口:COM3和COM4。可参考:Modbus poll & slave仿真软件初体验

具体步骤

代码侧具体步骤

        1.启动Visual Studio,创建一个新的工程项目:【File】→【New】→【Project】。在弹出的新建对话框左边中选择【Visual C++】→【Win32 Console Application】项,输入你自定义的应用程序名,设置完成后单击【确定】按钮。继续点击下一步后,具体项目配置勾选如下图:

        2.工程创建完成后,找到第五篇博客中编译的libmodbus库文件,将之前生成的lib和dll文件以及几个必要的头文件都复制到本项目所在的目录,如图:

        3.来到VS的主界面,在项目下的文件夹【源文件】上右键,选择 【添加】→【现有项】,在弹出的对话框中,选中modbus.h和modbus.lib文件(按住ctrl键多选),再点击【添加】按钮将这两个文件添加进项目中,如下图所示:

        4.添加完之后,我们就可以使用libmodbus库中的各种接口函数了。接下来,我们在【源文件】文件夹上右键,【添加】→【新建项】,添加自己的Demo程序,暂且命名为SimRtuMaster.cpp吧,如下图:

        5.现在就可以在SimRtuMaster.cpp中编写程序了,在此将代码分享如下:

#include <stdio.h>
#ifndef _MSC_VER
#include <unistd.h>
#endif

#include <string.h>
#include <stdlib.h>
#include <errno.h>

#include "modbus.h"

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

int main(void) {
	modbus_t *ctx;
	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_rp_registers;
	uint16_t *tab_rw_rq_registers;

	// RTU
	ctx = modbus_new_rtu("COM3", 19200, 'N', 8, 1);		// 创建一个RTU类型的容器
	modbus_set_slave(ctx, SERVER_ID);					// 设置从端地址

	modbus_set_debug(ctx, TRUE);							// 设置debug模式

	if (modbus_connect(ctx) == -1)		// 建立连接
	{
		fprintf(stderr, "Connection failed: %s\n", modbus_strerror(errno));
		modbus_free(ctx);
		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]);			// 写线圈寄存器
			if (rc != 1)
			{
				printf("ERROR modbus_write_bit (%d) \n", rc);
				printf("Address = %d, value = %d\n", addr, tab_rq_bits[0]);
				nb_fail++;
			}
			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);
			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);
				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]);
			if (rc != 1) {
				printf("ERROR modbus_write_register (%d) \n", rc);
				printf("Addr = %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);
				if (rc != 1) {
					printf("ERROR modbus_write_registers (%d) \n", rc);
					printf("Address = %d\n", addr);
					nb_fail++;
				}
				else {
					// 读取后进行比较
					if (tab_rq_registers[0] != tab_rp_registers[0]) {
						printf("ERROR modbus_write_registers\n");
						printf("Addr = %d, Val = %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_registers(ctx, addr, nb, tab_rq_registers);
			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);
				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_rq_registers[i] != tab_rp_registers[i]) {
							printf("ERROR modbus_read_registers\n");
							printf("Addr = %d, Val = %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++;
						}
					}
				}
			}

			// 功能码23(0x17)读写多个寄存器的测试
			rc = modbus_write_and_read_registers(ctx,
				addr, nb, tab_rw_rq_registers,
				addr, nb, tab_rp_registers
				);
			if (rc != nb) {
				printf("ERROR modbus_write_and_read_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("Addr = %d, Val = %d (0x%X) != %d (0x%X)\n", addr, tab_rp_registers[i], tab_rw_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 READ\n");
							printf("Addr = %d, Val = %d (0x%X) != %d (0x%X)\n", addr, tab_rp_registers[i], tab_rw_rq_registers[i], tab_rp_registers[i], tab_rw_rq_registers[i]);
							nb_fail++;
						}
					}
				}
			}
		}

		printf("Test: ");
		if (nb_fail) {
			printf("%d Fails\n", nb_fail);
		}
		else
		{
			printf("Success\n");
		}
	}

	// Free the memory
	free(tab_rq_bits);
	free(tab_rq_bits);
	free(tab_rq_registers);
	free(tab_rp_registers);
	free(tab_rw_rq_registers);

	// Close the connection
	modbus_close(ctx);
	modbus_free(ctx);

	return 0;
}

模拟器侧具体步骤

        1.打开Modbus Slave仿真软件(上面的代码是用于模拟Master,所以不再需要打开Modbus poll),在导航栏点击【Connection】→【Connect】,进行连接配置,注意各项配置要与代码中一致,才能保证串口通信,具体配置如下图:

        2.连接设置完成之后,再在Modbus Slave主窗口中选择【File】→【New】,在下方新建的子窗口的空白处右键,弹出菜单,选择【Slave Definition】,按照下图进行配置:

        3.设置完成后,新建另一个窗口,使用相同的配置,只将Function改为03,现在便可以开始测试了。

        4.回到Visual Studio面板,在编译代码之前需要修改一个配置。在VS顶部导航栏选择【工具】→【选项】,然后在【调试】下找到【符号】,将Microsoft符号服务器的勾打上(需要联网),不然有可能会运行失败,如下图:

        5.右键该项目,点击生成,进行编译,编译完成后,点击启动,然后查看Modbus Slave中是否有变化,当然,你也可以在VS中打断点进行调试,一步一步理解本篇文章的代码逻辑。

写在最后

        本篇文章介绍了具体的调试过程,如果帮到了您,我将万分荣幸,还请不要吝啬您小小的点赞和收藏。当然,如果需要的话,可以将整个项目文件下载下去(下载地址),支持一下用爱发电的博主。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1426221.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

printf死翘翘

本来想把我的单片机玩一下&#xff0c;寄给在大学搞研究的一个朋友&#xff0c;但竟然挂在printf里面&#xff0c;大概知道是什么位置出问题&#xff0c;但是还想不清楚什么原因。 我先是在stc51单片机里面搞了串口&#xff0c;然后我想用串口重定向到printf做调试&#xff0c;…

element-ui icon 组件源码分享

今日简单分享 element-ui 源码中的 icon 组件&#xff0c;主要从以下两个方面来分享&#xff1a; 一、源码中 icon 设计思想是什么呢&#xff1f;主要从页面结构、数据、 icon 样式三个方面来分享。 1.1 源码中 icon 组件的页面结构&#xff0c;可以在 package 目录下找到 ico…

Android 高德地图切换图层

一、默认样式 Android 地图 SDK 提供了几种预置的地图图层&#xff0c;包括卫星图、白昼地图&#xff08;即最常见的黄白色地图&#xff09;、夜景地图、导航地图、路况图层。 findViewById<TextView>(R.id.normal).setOnClickListener {updateSelectedStatus(TYPE_NORMA…

LeetCode 每日一题Day 54 - 61

2859. 计算 K 置位下标对应元素的和 给你一个下标从 0 开始的整数数组 nums 和一个整数 k 。 请你用整数形式返回 nums 中的特定元素之 和 &#xff0c;这些特定元素满足&#xff1a;其对应下标的二进制表示中恰存在 k 个置位。 整数的二进制表示中的 1 就是这个整数的 置位…

Java 正则匹配sql

文章目录 正则匹配sql表名称insert intoupdate 正则表达式什么时候要加^$ 在线正则校验 正则匹配sql表名称 insert into insert into PING_TABLE (CODE, NAME) VALUES(0, 待提交),(1, 审核中),(2, 审核通过),(3, 已驳回); regex -> insert\sinto\s(\w)\s*\(?update upda…

xmind思维导图 for mac v24.01中文版

mac电脑上思维导图软件哪个好呢&#xff1f; xmind for mac一个功能强大、易于使用的思维导图软件&#xff0c;够帮助你更好地组织思维、管理信息、规划项目和解决问题&#xff0c;提高个人和团队的工作效率。 软件下载&#xff1a;xmind思维导图 for mac v24.01中文版 XMind f…

【issue-YOLO】自定义数据集训练YOLO-v7 Segmentation

1. 拉取代码创建环境 执行nvidia-smi验证cuda环境是否可用&#xff1b;拉取官方代码&#xff1b; clone官方代码仓库 git clone https://github.com/WongKinYiu/yolov7&#xff1b;从main分支切换到u7分支 cd yolov7 && git checkout 44f30af0daccb1a3baecc5d80eae229…

Spring Boot--07--@Repository 和@Mapper的区别

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 1.Repository的作用1.1 含义1.2 Repository与Service和Component有什么区别&#xff1f;1.3 使用场景单独使用Repository&#xff0c;需要配合使用MapperScannerCon…

Linux实验记录:使用Apache服务部署静态网站

前言&#xff1a; 本文是一篇关于Linux系统初学者的实验记录。 参考书籍&#xff1a;《Linux就该这么学》 实验环境&#xff1a; VmwareWorkStation 17——虚拟机软件 RedHatEnterpriseLinux[RHEL]8——红帽操作系统 目录 前言&#xff1a; 备注&#xff1a; 正文&…

Python算法题集_除自身以外数组的乘积

Python算法题集_除自身以外数组的乘积 题239&#xff1a;除自身以外数组的乘积1. 示例说明2. 题目解析- 题意分解- 优化思路- 测量工具 3. 代码展开1) 标准求解【暴力求解】2) 改进版一【字典改进乘积计算】3) 改进版二【字典改进乘积计算预计算数字乘积】4) 改进版三【前缀乘积…

Linux------进程优先级与进程切换

目录 一、进程优先级 二、优先级与权限的区别 三、优先级的查看 四、进程优先级修改 五、进程切换 六、linux2.6内核调度队列与调度原理 一、进程优先级 首先我们得知道一个进程总是需要排队的&#xff0c;他一会在运行队列中排队等待运行&#xff0c;一会在设备的等待队…

Python之数据分析

【案例】 某公司有2份数据文件&#xff0c;现在需要对其进行数据分析&#xff0c;计算每日的销售额并以柱状图表的形式进行展现。 数据如下&#xff1a; 一月份数据&#xff1a; 二月份数据&#xff1a; 需求分析 根据题目要求我们要得到每日销售额&#xff0c;分析文本数据可以…

微服务-微服务Alibaba-Nacos 源码分析 (源码流程图)

客户端流程 客户端心跳与实例往服务端注册

vue动态修改侧边菜单栏宽度

1.添加可修改宽度的dom元素 <div style"background: #f5f7fa;padding: 20px 10px;"><label>菜单宽度 </label><el-input v-model"sideWidth" placeholder"请输入宽度值" style"width: 100px"/> px<el-but…

2023爱分析·数据智能厂商全景报告|爱分析报告

利用多种数据智能技术实现数据驱动的分析与决策&#xff0c;已经成为当前企业数字化转型最重要的目标之一。随着数据来源日益丰富、数据体量快速增长&#xff0c;企业对数据的依赖和挖掘愈发深入&#xff0c;不仅带来数据应用场景、数据用户角色的复杂和多元&#xff0c;也使得…

TOP100 链表(上)

1.160. 相交链表 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点&#xff0c;返回 null 。 图示两个链表在节点 c1 开始相交&#xff1a; 题目数据 保证 整个链式结构中不存在环。 注意&#xff0…

机器学习基础、数学统计学概念、模型基础技术名词及相关代码个人举例

1.机器学习基础 &#xff08;1&#xff09;机器学习概述 机器学习是一种人工智能&#xff08;AI&#xff09;的分支&#xff0c;通过使用统计学和计算机科学的技术&#xff0c;使计算机能够从数据中学习并自动改进性能&#xff0c;而无需进行明确的编程。它涉及构建和训练机器…

助力水下潜行:浮力调节系统仿真

01.建设海洋强国 海洋蕴藏着丰富的资源&#xff0c;二十大报告强调&#xff0c;要“发展海洋经济&#xff0c;保护海洋生态环境&#xff0c;加快建设海洋强国”。建设海洋强国旨在通过科技创新驱动、合理开发利用海洋资源、强化海洋环境保护与生态修复、提升海洋经济质量等多个…

6个在线网页原型工具的推荐选择

即时设计 即时设计可以说为中国设计师提供了很大的帮助。作为最受欢迎的在线网页原型图设计协作工具之一&#xff0c;在线协作是其核心特征。在线协作工作允许整个团队同时编辑文件&#xff0c;并可以随时随地访问。 团队共享组件库&#xff0c;使成员可以自由上传、下载和使用…

Git解决分支合并冲突的问题:分支合并提交出现了不同分支同一个文件的修改的冲突解决

有些时候我们合并分支的时候&#xff0c;会出现冲突&#xff0c;原因就是我们修改了分支A 的第一行代码&#xff0c;我们还修改了主分支上面同一个文件中的第一行代码&#xff08;分支A已经提交过一次&#xff09;&#xff0c;此时我们在次合并的时候就会出现冲突&#xff0c;需…