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;// RTUctx = 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 memoryfree(tab_rq_bits);free(tab_rq_bits);free(tab_rq_registers);free(tab_rp_registers);free(tab_rw_rq_registers);// Close the connectionmodbus_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.mzph.cn/news/663420.shtml

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

相关文章

【深度强化学习】Python:OpenAI Gym-CarRacing 自动驾驶 | 提供项目完整代码 | 车道检测功能 | 路径训练功能 | 车辆控制功能

💭 写在前面:本篇是关于 OpenAI Gym-CarRacing 自动驾驶项目的博客,面向掌握 Python 并有一定的深度强化学习基础的读者。GYM-Box2D CarRacing 是一种在 OpenAI Gym 平台上开发和比较强化学习算法的模拟环境。它是流行的 Box2D 物理引擎的一个版本,经过修改以支持模拟汽车…

一键部署幻兽帕鲁服务器免费一年方案

一、背景介绍 简单讲一下历程&#xff0c;幻兽帕鲁从在1月19日上线&#xff0c;24小时内在线人数峰值便突破200万&#xff0c;作为2024年第一款现象级游戏&#xff0c;《幻兽帕鲁》上线后&#xff0c;由于人数太多&#xff0c;频现服务器过载导致游戏卡顿掉线的情况。为了能够…

【GitHub项目推荐--大语言模型课程】【转载】

Large Language Model Course Large Language Model Course&#xff08;大型语言模型课程&#xff09;是一个开源项目&#xff0c;该课程分为三个部分&#xff1a; LLM 基础&#xff1a;涵盖了数学、Python 和神经网络的基础知识。 LLM 科学家&#xff1a;专注于学习如何使用…

【机器学习】基于K-近邻的车牌号识别

实验四: 基于K-近邻的车牌号识别 1 案例简介 ​ 图像的智能处理一直是人工智能领域广受关注的一类技术&#xff0c;代表性的如人脸识别与 CT 肿瘤识别&#xff0c;在人工智能落地的进程中发挥着重要作用。其中车牌号识别作为一个早期应用场景&#xff0c;已经融入日常生活中&…

LVGL部件7

一.图片部件 1.知识概览 2.函数接口 1.lv_img_set_pivot 在LVGL&#xff08;LittlevGL&#xff09;中&#xff0c;要设置图像对象的旋转中心点&#xff0c;可以使用 lv_img_set_pivot 函数。该函数的原型如下&#xff1a; void lv_img_set_pivot(lv_obj_t * img, lv_coord_…

Flask框架开发学习笔记《5》简易服务器代码

Flask框架开发学习笔记《5》 Flask是使用python的后端&#xff0c;由于小程序需要后端开发&#xff0c;遂学习一下后端开发。 简易服务器代码 接口解析那一块很关键&#xff0c;学后端服务器这一块&#xff0c;感觉主要就是学习相应地址的接口怎么处理。 然后写清楚每个地址…

在centos 7 中安装配置Jdk、Tomcat、及Tomcat自启动

目录 一、安装配置Jdk 1.创建目录并上传文件 2.解压JDK压缩包 3.配置JDK环境变量 4.设置环境变量生效 二、安装配置Tomcat 1.上传Tomcat并解压 2.启停Tomcat 3.修改tomcat-user.xml配置 4.配置远程访问Tomcat 5.远程项目发布 三.Tomcat自启动配置 1.配置Tomcat自启…

各大厂急招鸿蒙开发员,争抢鸿蒙工程师

余承东宣布鸿蒙原生应用全面启动&#xff0c;华为开始了全面抛弃安卓的进程。 多家互联网公司也发布了鸿蒙OS的App开发工程师的岗位&#xff0c;开启了抢人大战。 有的企业开出了近百万的年薪招聘鸿蒙OS工程师&#xff0c;而华为甚至为鸿蒙OS资深架构师开出了100万-160万元的…

Kotlin快速入门系列10

Kotlin的委托 委托模式是常见的设计模式之一。在委托模式中&#xff0c;有两个对象参与处理同一个请求&#xff0c;接受请求的对象将请求委托给另一个对象来处理。与Java一样&#xff0c;Kotlin也支持委托模式&#xff0c;通过关键字by。 类委托 类的委托即一个类中定义的方…

Mysql-ReadView + MVCC-RR 与 RC

实验准备 创建脚本 CREATE TABLE user (id int(11) NOT NULL AUTO_INCREMENT,name varchar(16) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,age int(11) NULL DEFAULT NULL,addr varchar(256) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL,PRIMARY …

Red Panda Dev C++项目的基本操作

最近在Red Panda Dev C上面编写程序时发现&#xff0c;很多小伙伴都会创建项目。今天我带大家看看如何使用一个项目。 一、项目的创建 创建项目 首先&#xff0c;我们需要创建一个项目。 1、打开Red Panda Dev C。 2、点击 “文件[F]” → “新建[N]” → “项目[P]...”&…

精选70套前端数据可视化大屏

分享70款还不错的前端数据可视化大屏源码 其中包含行业&#xff1a;智慧社区、智慧物业、政务系统、智慧交通、智慧工程、智慧医疗、智慧金融银行等&#xff0c;全网最新、最多&#xff0c;最全、最酷、最炫大数据可视化模板。 你可以点击在线预览查看该源码资源的最终展示效果…

从零搭建Vue3 + Typescript + Pinia + Vite + Tailwind CSS + Element Plus开发脚手架

项目代码以上传至码云&#xff0c;项目地址&#xff1a;https://gitee.com/breezefaith/vue-ts-scaffold 文章目录 前言脚手架技术栈简介vue3TypeScriptPiniaTailwind CSSElement Plusvite 详细步骤Node.js安装创建以 typescript 开发的vue3工程集成Pinia安装pinia修改main.ts创…

正点原子--STM32定时器学习笔记(1)(更新中....)

F1系列基本定时器&#xff08;TIM6 / TIM7&#xff09; 我们的目标是通过TIM6基本定时器定时500ms&#xff0c;让LED0每隔500ms闪一下&#xff01; 思路&#xff1a;使用定时器6&#xff0c;实现500ms产生一次定时器更新中断&#xff0c;在中断里执行“翻转LED0”。 定时器什…

docker容器之consul

一、consul解决了什么问题&#xff1f; 如果后端应用服务器集群节点数量很多&#xff0c;前端负载均衡器配置和管理会很麻烦的问题 &#xff08;负载均衡器的节点配置条目数量会很多&#xff0c;后端节点的网络位置发生了变化还需要修改所有调用这些后端节点的负载均衡器配置等…

Android 12.0 应用中监听系统收到的通知

Android 12.0 通知简介https://blog.csdn.net/Smile_729day/article/details/135502031?spm1001.2014.3001.5502 1. 需求 在系统内置应用中或者在第三方应用中,获取Android系统收到的通知的内容. 2. NotificationListenerService 接口 Android 系统预留了专门的API, 即 No…

Github 2F2【解决】经验帖-PPHub登入

最近在做项目时,Github总是出问题,这是一经验贴 Github 2F2登入问题【无法登入】PPhub 2F2是为了安全,更好的生态 启用 2FA 二十八 (28) 天后,要在使用 GitHub.com 时 2FA 检查 物理安全密钥、Windows Hello 或面容 ID/触控 ID、SMS、GitHub Mobile 都可以作为 2F2 的工…

jenkins 下载插件sentry-cli失败 证书过期

现状 npm set ENTRYCLI_CDNURLhttps://cdn.npm.taobao.org/dist/sentry-cli npm set sentrycli_cdnurlhttps://cdn.npm.taobao.org/dist/sentry-cli 原因是npm原域名停止解析&#xff0c;在访问上面sentry-cli的cdn资源的时候 证书过期无法下载。 解决&#xff1a; 替换证书过期…

【C语言】通讯录实现(下)

目录 1.进阶通讯录特点&#xff08;下&#xff09; 2.实现步骤 &#xff08;1&#xff09;保存增加的联系人数据到文件中 &#xff08;2&#xff09;加载保存的联系人数据 3.完整C语言通讯录代码 &#xff08;1&#xff09;contact.h (2)test.c (3)contact.c 4.结语 1.…

MongoDB从入门到实战之MongoDB简介

前言 相信很多同学对MongoDB这个非关系型数据库都应该挺熟悉的&#xff0c;在一些高性能、动态扩缩容、高可用、海量数据存储、数据价值较低、高扩展的业务场景下MongoDB可能是我们的首选&#xff0c;因为MongoDB通常能让我们以更低的成本解决问题&#xff08;包括学习、开发、…