软件设计开发笔记6:基于QT的Modbus RTU从站

  Modbus是一种常见的工业系统通讯协议。在我们的设计开发工作中经常使用到它。作为一种主从协议,在上一篇我们实现了Mobus RTU主站工具,接下来这一篇中我们将简单实现一个基于QT的Mobus RTU从站工具。

1、概述

  Modbus RTU从站应用很常见,有一些是通用的,有一些是专用的。而这里我们希望实现一个主要针对我们的产品调试的Modbus RTU从站工具。
  在开始软件设计之前,我们先来简略地分析一下,实现这样一个Modbus RTU从站工具包含的主要内容有哪些。我们认为软件需要如下几个方面的内容:

(1)、串口参数的配置

  Modbus RTU通过串口来实现通讯,所以我们需要对串口相关的参数进行配置。我们希望串口号能够自动搜索,而相应的配置参数我们可以选择。
  对串口的操作我们希望可以适应不同使用情况下的需求,让使用者可以自行选择串口名、波特率、校验位、数据位和停止位等,并控制串口的打开和关闭。

(2)、对数据的配置

  数据的修改及更新,因为我们是从站实例,所以我们需要设置从站的参数类型和数量。我们将实现Modbus协议规定的4中数据类型:线圈、状态、输入寄存器和保持寄存器。
  这四种数据其中线圈和状态是布尔量;输入寄存器和保持寄存器是16位整型量,这是指他的存储形式,事实上多个寄存器可以表示各种应用数据类型;线圈和保持寄存器支持读写;状态和输入寄存器位只读。

(3)、对主站的响应

  对读从站数据,主要是指Modbus的功能码0x01、0x02、0x03和0x04等的处理。收到这几个功能码后,我们根据报文中的首地址和数量返回给主站即可。
  对写从站数据,主要是指Modbus的功能码0x05、0x06、0x0F和0x10等的处理。对于这几个功能码,我们解析到这几个功能码后,根据指定的起始地址和数值对显影的变量值进行修改。

(4)、对信息的显示

  接收信息的显示,作为调试工具,我们肯定希望能够一目了然地看到接收到目标设备发送过来的消息,所以我们需要一个显示区域来对接收的区域进行显示。
  对数据的显示,我们在界面上设计各种数据类型的指示,线圈和状态使用CheckBox来显示其状态。当置位时现实为选中,复位时显示为不选中。而对于输入寄存器和保  持寄存器则可以直接显示它的数值。变量数据改变时则更新界面显示;同时界面状态改变时则更行变量数据。
  运行状态的显示, 我们希望对操作的状态进行反馈以指示操作的动作是否执行,所以我们需要状态栏来实现这一需求。

2、界面设计

  根据上一节中分析的需求,我们先来设计软件的界面。我们在QT中基于QMainWindow类生成一个操作界面,包括菜单栏、工具栏和状态栏以满足需求中对状态显示及操作命令的要求。
  我们将上部区域设置为串口参数及Modbus RTU从站参数的配置于操作区域。而下面我们以5列17行的形式展示各种数据类型的数据状态。具体的界面设置如下图所示:

  完成如上图的布局后,我们可以配置相应的参数,我们在程序初始化时配置好串口及Modbus RTU从站的默认参数。完成整个布局后我们先试着运行程序,正常运行则出现如下的界面:

  上图就是完成布局后的运行界面,不过我们还没有实现相应的编码,所以目前还不能实现我们第一节中所提出来的功能。

3、编码实现

  接下来这一小节,我们将来编码实现相应的功能。我们主要将功能分为参数设置与操作功能、数据的输入与发送功能以及数据的接收与显示功能三个部分来实现。

3.1、串口配置功能

  对于参数的配置除了串口号以外,我们可以在程序中默认设置相应的参数,如果有必要可以修改,如果默认参数能满足要求亦可直接使用。至于串口号默认的不一定时我们所希望的,根据硬件配置情况选择即可。参数选定后我们只需要点击“连接”按钮即可实现Modbus RTU从站的连接。而连接操作的具体实现方式如下:

void MainWindow::on_pushButtonConnect_clicked()
{bool intendToConnect = (modbusDevice->state() == QModbusDevice::UnconnectedState);statusBar()->clearMessage();if (intendToConnect){modbusDevice->setConnectionParameter(QModbusDevice::SerialPortNameParameter,ui->comboBoxPort->currentText());modbusDevice->setConnectionParameter(QModbusDevice::SerialParityParameter,QSerialPort::NoParity);modbusDevice->setConnectionParameter(QModbusDevice::SerialBaudRateParameter,ui->comboBoxBaud->currentText().toInt());modbusDevice->setConnectionParameter(QModbusDevice::SerialDataBitsParameter,QSerialPort::Data8);modbusDevice->setConnectionParameter(QModbusDevice::SerialStopBitsParameter,QSerialPort::OneStop);modbusDevice->setServerAddress(ui->spinBoxStation->text().toInt());if (!modbusDevice->connectDevice()){statusBar()->showMessage(tr("连接失败: ") + modbusDevice->errorString(), 5000);}else{ui->pushButtonConnect->setEnabled(false);ui->pushButtonDisconnect->setEnabled(true);}}
}

3.2、对界面操作的响应

  首先我们在界面上操作各个类型的变量时,我们希望变量中的数据会跟随我们的操作而改变,并能被从站访问到。对于4种类型的数据将采用类似的处理方式。
  对于线圈和状态量,当我们在界面上点击复选框改变选中状态时,会触发对应的槽函数,并肩对应的编号作为参数传递进去,槽函数会处理将Modbus RTU从站定义的变量状态改为和复选框的状态一致。具体实现如下:

void MainWindow::coilChanged(int id)
{if(!modbusDevice){return;}QAbstractButton *button = coilButtons.button(id);if (!modbusDevice->setData(QModbusDataUnit::Coils, quint16(id), button->isChecked())){statusBar()->showMessage(tr("Could not set data: ") + modbusDevice->errorString(), 5000);}
}void MainWindow::discreteInputChanged(int id)
{if(!modbusDevice){return;}QAbstractButton *button = discreteButtons.button(id);if (!modbusDevice->setData(QModbusDataUnit::DiscreteInputs, quint16(id), button->isChecked())){statusBar()->showMessage(tr("Could not set data: ") + modbusDevice->errorString(), 5000);}
}

  对于输入寄存器和保持寄存器在界面上使用的LineEdit来显示数据值,所以当我们改变LineEdit的内容时,让其触发槽函数。在槽函数中我们将Modbus RTU从站对应的寄存器的值改为和LineEdit的内容一样。具体实现如下:

void MainWindow::setInputRegister(const QString &value)
{if(!modbusDevice){return;}const QString objectName = QObject::sender()->objectName();if(inputRegisters.contains(objectName)){bool ok = true;const quint16 id = quint16(QObject::sender()->property("ID").toUInt());if (objectName.startsWith(QStringLiteral("inReg"))){if(!modbusDevice->setData(QModbusDataUnit::InputRegisters, id, value.toUShort(&ok, 16))){statusBar()->showMessage(tr("Could not set register: ") + modbusDevice->errorString(),5000);}}}
}void MainWindow::setHoldingRegister(const QString &value)
{if(!modbusDevice){return;}const QString objectName = QObject::sender()->objectName();if(holdingRegisters.contains(objectName)){bool ok = true;const quint16 id = quint16(QObject::sender()->property("ID").toUInt());qDebug()<<id;if (objectName.startsWith(QStringLiteral("holdReg"))){if(!modbusDevice->setData(QModbusDataUnit::HoldingRegisters, id, value.toUShort(&ok, 16))){statusBar()->showMessage(tr("Could not set register: ") + modbusDevice->errorString(),5000);}}}
}

  经过上面的处理之后,到我们在界面上操作时就会更新到Modbus RTU从站对应的数据变量。

3.3、对主站设置的响应

  当后台数据被修改时,需要修改显示数据。如主站修改了Coil或者HoldingRegister数据时,需要修改显示信息。而Modbus从站对象有3个信号,分别是:

void errorOccurred(QModbusDevice::Error error)
void stateChanged(QModbusDevice::State state)
void dataWritten(QModbusDataUnit::RegisterType table, int address, int size)

  当主站访问从站时,则会在不同状态下触发相应信号函数,我们需要实现这三个信号函数对应的槽函数就可以实现相应的操作。具体实现如下:

//根据从站的变化更新数据显示
void MainWindow::UpdateDisplayBySlave(QModbusDataUnit::RegisterType table, int address, int size)
{qDebug()<<"table:"<<table<<"address:"<<address<<"size:"<<size<<endl;for (int i = 0; i < size; ++i){quint16 value;QString text;switch (table){case QModbusDataUnit::Coils:{modbusDevice->data(QModbusDataUnit::Coils, quint16(address + i), &value);coilButtons.button(address + i)->setChecked(value);break;}case QModbusDataUnit::HoldingRegisters:{modbusDevice->data(QModbusDataUnit::HoldingRegisters, quint16(address + i), &value);qDebug()<<value;holdingRegisters.value(QStringLiteral("holdReg%1").arg(address + i))->setText(text.setNum(value, 16));break;}default:break;}}
}//设备错误处理
void MainWindow::HandleDeviceError(QModbusDevice::Error newError)
{if (newError == QModbusDevice::NoError || !modbusDevice)return;statusBar()->showMessage(modbusDevice->errorString(), 5000);
}//从站状态改变响应
void MainWindow::SlaveStateChanged(int state)
{bool connected = (state != QModbusDevice::UnconnectedState);ui->pushButtonConnect->setEnabled(!connected);ui->pushButtonDisconnect->setEnabled(connected);
}

4、测试验证

  完成了编码调试后,我们来对开发的这一工具进行一些测试。首先我们安装一个虚拟串口软件用以虚拟我们用于测试的串口。我们还需要一个Modbus的主站应用来测试从站,这里我们选择Modscan来实现这一过程。
  我们使用虚拟串口来模拟一对串口COM4和COM5。我们在ModScan中打开4个串口分别对应4中数据变量,数量都是16个,并将串口参数设置为和从站默认的一致,然后将其连接到COM5,如下图所示:

  同时设置好从站的参数,然后将其连接到串口COM4,具体如下图:

  这样从站和主站就连接上了。我们接下来在从站界面上操作,看看主站的数据变化。我们在从站界面上将线圈量的0、2、4、6、8、10、12、14等置位,并将状态量的1、3、5、7、9、11、13、15等置位,并在主站中查看,具体如下:

  上图中我们可以看到主站显示的数据与从站设置的数据是一致的,说明我们的设计结果正确。接下来我们在从站界面上的输入寄存器的值修改0x01~0x10,同时在主站将保持寄存器的第10个修改为0x04,具体如下:

  接下来我们在主站中修改保持寄存器的值,我们将保持寄存器的值修改为一系列不同的值,具体如下:

  我们可以看到从站的显示数据与主站的设置是一致的。到此我们就完成了对Modbus RTU从站的测试,结果与我们设计的一致。

源码:https://gitee.com/ErichMoonan/modbus-slave

欢迎关注:

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

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

相关文章

mysql 逻辑备份 bin-log日志恢复

一、逻辑备份 逻辑备份&#xff1a;备份的是建表&#xff0c;建库&#xff0c;插入数据等操作所执行SQL语句&#xff0c;适用于中小型数据库&#xff0c;效率相对较低&#xff0c;提供三种级别的备份&#xff0c;表级&#xff0c;库级和全库级。 本质&#xff1a;导出的是SQL语…

《视觉 SLAM 十四讲》第 7 讲 视觉里程计1 【如何根据图像 估计 相机运动】【特征点法】

github源码链接V2 文章目录 第 7 讲 视觉里程计17.1 特征点法7.1.1 特征点7.1.2 ORB 特征FAST 关键点 ⟹ \Longrightarrow ⟹ Oriented FASTBRIEF 描述子 7.1.3 特征匹配 7.2 实践 【Code】本讲 CMakeLists.txt 7.2.1 使用 OpenCV 进行 ORB 的特征匹配 【Code】7.2.2 手写 O…

CTF 全讲解:[SWPUCTF 2021 新生赛]Do_you_know_http

文章目录 参考环境题目hello.php雾现User-Agent伪造 User-AgentHackBarHackBar 插件的获取修改请求头信息 雾散 a.php雾现本地回环地址与客户端 IP 相关的 HTTP 请求头X-Forwarded-For 雾散 参考 项目描述搜索引擎Bing、GoogleAI 大模型文心一言、通义千问、讯飞星火认知大模型…

细粒度特征提取和定位用于目标检测:PPCNN

1、简介 近年来&#xff0c;深度卷积神经网络在计算机视觉上取得了优异的性能。深度卷积神经网络以精确地分类目标信息而闻名&#xff0c;并采用了简单的卷积体系结构来降低图层的复杂性。基于深度卷积神经网络概念设计的VGG网络。VGGNet在对大规模图像进行分类方面取得了巨大…

STM32--基于STM32的智能家居设计与实现

本文详细介绍基于STM32F103C8T6的智能家居设计与实现&#xff0c;详细设计资料见文末链接 一、功能模块介绍 智能家居系统系统图如下所示&#xff0c;主要包括温湿度传感器、OLED液晶显示&#xff0c;WIFI物联网模块、人体红外预警模块、烟雾传感器模块、蜂鸣器模块 &#…

linux,write:xxx has messages disabled 与 Ubuntu多用户同时登录的问题 ubuntu 20.04

write&#xff1a;xxx has messages disabled 问题 被这问题折磨了好久&#xff0c;搜都搜不到&#xff0c;还是灵机一动想到的。 很多 帖子说&#xff0c;要使用 mesg y用了还是没有用&#xff0c;后面我登录了很多用户&#xff0c;发现只有root用户可以给别的用户使用write…

教资成绩什么时候出来 2023教资笔试成绩查询时间介绍

上半年教资笔试成绩查询开放时期为2023年4月13日&#xff0c;面试成绩查询开放时间在6月14日。而下半年教资笔试成绩查询开放时间为2023年11月8日&#xff0c;2023下半年教资面试时间是2023年12月9日-10日。 值得一提的是如果考生对成绩有异议的话&#xff0c;还可以在成绩公布…

194、SpringBoot --- 下载和安装 Erlang 、 RabbitMQ

本节要点&#xff1a; 一些命令&#xff1a; 小黑窗输入&#xff1a; rabbitmq-plugins enable rabbitmq_management 启动控制台插件 rabbitmq-server 启动rabbitMQ服务器 管理员启动小黑窗&#xff1a; rabbitmq-service install 添加rabbitMQ为本地服务 启动浏览器访问 htt…

1.springcloudalibaba nacos2.2.3部署

前言 nacos是springcloudalibaba体系的注册中心&#xff0c;演示如何搭建最新稳定版本的linux搭建。 前置条件&#xff0c;安装好jdk1.8 一、二进制压缩包下载 1.1 下载压缩包 nacos下载 点击下载下载后得到二进制包如下 nacos-2.2.3.tar.gz二、安装步骤 2.1.解压二进制…

C++ - 可变模版参数 - emplace相关接口函数 - 移动构造函数 和 移动赋值运算符重载 的 默认成员函数

可变模版参数 我们先来了解一下&#xff0c;可变参数。可变参数就是在定义函数的时候&#xff0c;某一个参数位置使用 "..." 的方式来写的&#xff0c;在库当中有一个经典的函数系列就是用的 可变参数&#xff1a;printf&#xff08;&#xff09;系列就是用的可变参…

docker安装运行环境相关的容器

docker安装常用软件步骤 docker安装Tomcat:latest 2023-10-09 1&#xff09;搜索镜像 以Tomcat为例子&#xff0c;先去官网仓库搜索https://hub.docker.com/search?qtomcat 或者直接命令查询 docker search tomcat2&#xff09;拉取镜像 docker pull tomcat3&#xff09…

【git】git命令行

首先要了解git整个流程的一个分类&#xff1a; workspace&#xff1a;工作区staging area&#xff1a;暂存区/缓存区local repository&#xff1a;版本库或本地仓库remote repository&#xff1a;远程仓库 创建仓库 git clone gitgithub.comxxxxxxxxxxxx//拷贝一份远程仓库 …