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

前置工作

        学了这么多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中打断点进行调试,一步一步理解本篇文章的代码逻辑。

写在最后

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【AutoCAD2023】删除验证组件+桌面应用程序+登陆组件方法

Autodesk删除验证组件桌面应用程序登陆组件方法&#xff1a; :: 建议在安装前找到官方安装包释放后的安装文件所在位置 例如&#xff1a;AutoCAD_2023_Simplified_Chinese_Win_64bit_dlm 删除验证组件Autodesk Genuine Service -> x64\AGS (必删) 删除桌面程序Autodesk Desk…

2023安防行业十件大事,一定有你关心的

2023年对我国安防行业来说&#xff0c;可以说是既充满希望又充满不确定性的一年。经历三年的市场低迷&#xff0c;2023年安防市场开始逐渐回暖&#xff0c;行业景气度缓慢上升。 那么&#xff0c;2023年我国安防行业都发生了哪些值得铭记的大事&#xff1f;哪些事件对安防产业…

浏览器内存泄漏排查指南

1、setTimeout执行原理 使用setInterval/setTimeOut遇到的坑 - 掘金 2、Chrome自带的Performance工具 当我们怀疑页面发生了内存泄漏的时候&#xff0c;可以先用Performance录制一段时间内页面的内存变化。 点击开始录制执行可能引起内存泄漏的操作点击停止录制 如果录制结束…

实现vue3响应式系统核心-shallowReactive

简介 今天来实现一下 shallowReactive 这个 API。 reactive函数是一个深响应&#xff0c;当你取出的值为对象类型&#xff0c;需要再次调用 reactive进行响应式处理。很明显我们目前的代码是一个浅响应&#xff0c;即 只代理了对象的第一层&#xff0c;也就是 shallowReactiv…

wespeaker项目grpc-java客户端开发

非常重要的原始参考资料&#xff1a; 链接: triton-inference-server/client github/grpc java ps&#xff1a; 使用grpc协议的其它项目python/go可以参考git hub目录client/tree/main/src/grpc_generated下的其它项目 其它链接&#xff1a; 想要系统了解triton-inference-ser…

#《AI中文版》V3 第 3 章 知情搜索

参考链接&#xff1a; [1] 开源内容&#xff1a;https://github.com/siyuxin/AI-3rd-edition-notes [2] Kimi Chat官网链接 正文笔记 P90 针对 大型问题。 知情搜索&#xff08;informed search&#xff0c;也称有信息搜索&#xff09;&#xff1a;利用启发式方法&#xff0c…

新版多功能去水印工具微信小程序源码下载+带流量主功能

新版多功能去水印工具微信小程序源码下载&#xff0c;带流量主功能。自带去水印接口的多功能小程序&#xff0c;支持各大平台短视频去水印。 支持保存封面、图集、标题等等&#xff1b;支持本地图片去水印&#xff1b;支持图片拼接&#xff1b;支持九宫格切图&#xff1b;支持…

如何实现任意设备远程SSH访问Deepin操作系统【内网穿透】

文章目录 推荐前言1. 开启SSH服务2. Deppin安装Cpolar3. 配置ssh公网地址4. 公网远程SSH连接5. 固定连接SSH公网地址6. SSH固定地址连接测试 推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击跳…

三、软硬件工作流程分析

现在的计算机主要是由两部分组成&#xff1a;软件系统和硬件系统。这里先捋清楚硬件和软件的关系&#xff0c;以及电脑 各个组成部分是如何配合工作的。 软件系统主要被分类为两大类&#xff1a; 系统软件&#xff1a;这包括操作系统&#xff0c;如Windows、Linux等。操作系统是…

果冻跳跃

欢迎来到程序小院 果冻跳跃 玩法&#xff1a;点击果冻跳跃&#xff0c;果冻会消失掉&#xff0c;果冻只能跳一个果冻的距离高度&#xff0c;共36关卡&#xff0c; 不同关卡有不同的跳板&#xff0c;快去闯关吧^^。开始游戏https://www.ormcc.com/play/gameStart/265 html <…

【全csdn最前沿LVGL9】按钮的使用(lv_button)、标签的使用(lv_label)

文章目录 前言一、按钮概述二、按钮的使用2.1 创建一个按钮2.2 按钮的样式 三、标签概述四、标签的使用4.1 创建一个标签4.2 样式4.3 设置文本4.4 长文本模式4.5 文本选择4.6 文本对齐4.7 非常长的文本4.8 字体设置字体支持的Unicode字符字体列表特殊的字体 总结 前言 欢迎来到…

CXO清单:低代码平台必备的16个基本功能:从需求到实现的全面指南

对于 CIO、CTO 和 CDO&#xff08;在此统称为 CXO&#xff09;来说&#xff0c;认识到快速变化的技术和竞争格局以及他们在组织中的角色变化至关重要。处理持续不断的软件开发请求、考虑不断变化的业务流程、提高客户和法规的透明度、提高企业数据安全性以及在短时间内扩展基础…

2024斋月大促跨境卖家准备指南

市场覆盖西欧、中东、东南亚、北非地区的跨境电商卖家注意了&#xff0c;2024年的斋月即将开启&#xff0c;较往年日期&#xff0c;今年提前了10天左右&#xff0c;斋月的第一天预测在3月11日星期一到来。 根据Google搜索数据可知&#xff0c;目前已经进入高频“斋月”搜索期&…

与数组相关经典面试题

&#x1d649;&#x1d65e;&#x1d658;&#x1d65a;!!&#x1f44f;&#x1f3fb;‧✧̣̥̇‧✦&#x1f44f;&#x1f3fb;‧✧̣̥̇‧✦ &#x1f44f;&#x1f3fb;‧✧̣̥̇:Solitary-walk ⸝⋆ ━━━┓ - 个性标签 - &#xff1a;来于“云”的“羽球人”。…

嵌入式学习第十六天

制作俄罗斯方块小游戏&#xff08;一&#xff09; 分析&#xff1a; printf函数高级用法 \033[&#xff1a;表示转义序列的开始 m&#xff1a;表示转义序列的结束 0&#xff1a;重置所有属性 1&#xff1a;设置粗体或高亮 30-37&#xff1a;设置字体色 30: 黑 31: 红 32:…

江科大stm32学习笔记10——对射式红外传感器

一、接线 上电之后可以看到对射式红外传感器亮两个灯&#xff0c;如果此时用挡光片挡住两个黑色方块中间的部分&#xff0c;则只亮一个灯。 二、代码 将4-1的工程文件夹复制粘贴一份&#xff0c;重命名为“5-1 对射式红外传感器计次”&#xff0c;打开keil&#xff0c;右键添…

C++/数据结构:二叉搜索树的实现与应用

目录 一、二叉搜索树简介 二、二叉搜索树的结构与实现 2.1二叉树的查找与插入 2.2二叉树的删除 2.3二叉搜索树的实现 2.3.1非递归实现 2.3.2递归实现 三、二叉搜索树的k模型和kv模型 一、二叉搜索树简介 二叉搜索树又称二叉排序树&#xff0c;它或者是一棵空树&#xff0…

LeetCode刷题记录:(5)前K个高频元素

LeetCode传送通道 很好的一道题&#xff01; 核心知识点&#xff1a; …①map频率统计&#xff1b; …②优先级队列(不用的话见解法二) //解法一 class Solution {public int[] topKFrequent(int[] nums, int k) {int[] result new int[k];//1.次数统计mapMap<Integer,Inte…

ElementUI Form:Input 输入框

ElementUI安装与使用指南 Input 输入框 点击下载learnelementuispringboot项目源码 效果图 el-input.vue &#xff08;Input 输入框&#xff09;页面效果图 项目里el-input.vue代码 <script> export default {name: el_input,data() {return {input: ,input1: ,i…

Mac内存清理的方法,Mac老用户都用这几种方法清理Mac内存

Mac磁盘空间又爆满了&#xff1f;系统运行又卡了&#xff1f;你的Mac需要清理内存啦&#xff01;如果你正在为“您的磁盘内存不足”的提示所困扰&#xff0c;或者你的Mac电脑突然运行缓慢和迟缓&#xff0c;那么你可能需要了解以下几种Mac释放内存的方法。 一、清理缓存 在配…