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

1、概述

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

(1)、串口参数的配置

  Modbus RTU通过串口来实现通讯,所以我们需要对串口相关的参数进行配置。对串口的配置主要是串口名、波特率、校验位、数据位和停止位等。对于这些参数我们让使用者可以根据需要选择。
  而串口号,我们希望软件可以自动搜索当前可用的串口列表。而且我们可以通过操作更新可用的串口列表。对串口的操作主要是串口的打开与关闭。

(2)、从站信息的配置

  我们实现Modbus RTU主站应用就是访问从站的数据,所以我们需要在主站应用中配置从站的信息。主要有站地址、数据类型、数据格式等,我们将其设置为可以选择。
读取从站的参数配置,主要是起始地址、读取的数量。写从站参数的配置,主要是起始地址、写入的数量以及写入的数值。

(3)、对从站的操作

  Modbus RTU主站对从站的操作无非是读从站数据和写从站数据,我们通过制定读写的寄存器类型、起始地址、数量等通过按钮操作来实现读写命令的发送。
  除了手动操作读写外,很多时候我们可能需要Modbus RTU主站自动周期性的读取从站的数据。所以我们让其可以选择以多长的周期自动循环读取。

(4)、对信息的显示

  接收信息的显示,作为一款工具软件, 我们当然希望看到我们发给从站的命令究竟有没有成功,最简单的和直观的办法就是将接收到的信息显示出来。对于Modbus RTU主站当然是显示对应的地址的值。
  同样的,我们有时候想要看到发送和接收到的原始报文,所以我们对发送和接收到的报文也作相应的显示。
  对于个别数据有时候我们还希望看到他的变化趋势,所以我们可以添加一个图形显示,用以显示我们制定的数据的变化趋势。
  运行状态的显示, 我们希望对操作的状态进行反馈以指示操作的动作是否执行,所以我们需要状态栏来实现这一需求。

2、界面设计

  根据上一节中分析的需求,我们先来设计软件的界面。我们在QT中基于QMainWindow类生成一个操作界面,包括菜单栏、工具栏和状态栏以满足需求中对状态显示及操作命令的要求。
  而在中间显示区域,我们将其划分为2列。在左边的一列从上到下设置:串口配置操作区域和读写从站的交互配置区域。在右侧的一列从上到下设置:动态曲线显示区域、收发消息显示区域以及直接输入报文发送命令的输入区域。具体的界面设置如下图所示:

  完成如上图的布局后,我们可以选择在属性中配置控件的参数,也可以在代码中添加相关的参数。在这里在代码中通过初始化形式完成参数的设置。完成整个布局后我们先试着运行程序,正常运行则出现如下的界面:

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

3、编码实现

  接下来这一小节,我们将来编码实现相应的功能。我们主要将功能分为串口操作功能、从站操作功能以及信息显示功能三个部分来实现。

3.1、串口操作功能

  对串口的操作首先就是对串口参数的设置。我们在代码中对界面上的串口号、波特率、数据位、校验位和停止位的ComboBox控件进行初始化。其中串口号通过自动搜索当前可用的串口来实现。具体的实现方式如下:

//搜索串口
void MainWindow::SearchSerialPorts()
{ui->comboBoxPort->clear();foreach(const QSerialPortInfo &info,QSerialPortInfo::availablePorts()){ui->comboBoxPort->addItem(info.portName());}
}

  对串口的操作主要是串口的打开和关闭,在这里因为是Modbus RTU主站应用,我们称之为连接和断开。建立或断开与从站的连接实际就是对串口的配置与操作,只是针对Modbus RTU作了一些封装,具体实现如下:

//串口连接
void MainWindow::on_actionConnect_triggered()
{if (!modbusDevice)return;modbusDevice->setConnectionParameter(QModbusDevice::SerialPortNameParameter,ui->comboBoxPort->currentText());modbusDevice->setConnectionParameter(QModbusDevice::SerialBaudRateParameter,ui->comboBoxBaud->currentText().toInt());switch(ui->comboBoxParity->currentIndex())                   //设置奇偶校验{case 0: modbusDevice->setConnectionParameter(QModbusDevice::SerialParityParameter,QSerialPort::NoParity);break;default: break;}switch(ui->comboBoxData->currentIndex())                   //设置数据位数{case 1:modbusDevice->setConnectionParameter(QModbusDevice::SerialDataBitsParameter,QSerialPort::Data8);break;default: break;}switch(ui->comboBoxStop->currentIndex())                     //设置停止位{case 1: modbusDevice->setConnectionParameter(QModbusDevice::SerialStopBitsParameter,QSerialPort::OneStop);break;case 2: modbusDevice->setConnectionParameter(QModbusDevice::SerialStopBitsParameter,QSerialPort::TwoStop);break;default: break;}modbusDevice->setTimeout(1000);modbusDevice->setNumberOfRetries(3);if (modbusDevice->connectDevice()){//开启自动读取if(ui->checkBoxAuto->isChecked()){connect(pollTimer,&QTimer::timeout, this, &MainWindow::ReadRequest);pollTimer->setInterval(ui->spinBoxInterval->value());pollTimer->start();}//连接槽函数//QObject::connect(serialPort, &QSerialPort::readyRead, this, &MainWindow::ReadSerialData);// 设置控件可否使用ui->actionConnect->setEnabled(false);ui->actionDisconnect->setEnabled(true);ui->actionRefresh->setEnabled(false);}else    //打开失败提示{QMessageBox::information(this,tr("错误"),tr("连接从站失败!"),QMessageBox::Ok);}
}

3.2、从站操作功能

  在前面一节中我们已经设计过,对从站的操作包括手动按钮读取从站数据、手动按钮写入从站数据以及自动周期读取从站数据。
  手动读取从站数据是指点击按钮时触发一次读从站的操作,而从站的地址、读取的寄存器类型、读取的寄存器起始地址和寄存器的数量均根据界面上相应的设置确定。具体的实现如下:

//读数据请求
void MainWindow::ReadRequest()
{if (!modbusDevice){QMessageBox::information(NULL,  "Title",  "尚未连接从站设备");return;}QModbusDataUnit::RegisterType type;switch(ui->comboBoxDataType->currentIndex()){case 0:type=QModbusDataUnit::Coils;break;case 1:type=QModbusDataUnit::DiscreteInputs;break;case 2:type=QModbusDataUnit::InputRegisters;break;case 3:type=QModbusDataUnit::HoldingRegisters;break;default:type=QModbusDataUnit::Invalid;}int startAddress = ui->spinBoxStartRead->value();Q_ASSERT(startAddress >= 0 && startAddress < 10);// do not go beyond 10 entriesquint16 numberOfEntries = qMin(quint16(ui->spinBoxNumberRead->value()), quint16(10 - startAddress));QModbusDataUnit readUnit=QModbusDataUnit(type, startAddress, numberOfEntries);statusBar()->clearMessage();if (auto *reply = modbusDevice->sendReadRequest(readUnit, ui->spinBoxStation->value())){if (!reply->isFinished())connect(reply, &QModbusReply::finished, this, &MainWindow::ReadSerialData);elsedelete reply; // broadcast replies return immediately}else{statusBar()->showMessage(tr("Read error: ") + modbusDevice->errorString(), 5000);}}

  手动写从站操作是指点击按钮触发一次写从站操作,而从站的地址、写入的寄存器类型、写入的寄存器起始地址、写入的寄存器的数量以及写入的值均根据界面上相应的设置确定。而寄存器的值得输入以“,”分割,具体的实现如下:

//写数据请求
void MainWindow::WriteRequest(QList<quint16> values)
{if (!modbusDevice){QMessageBox::information(NULL,  "Title",  "尚未连接从站设备");return;}QModbusDataUnit::RegisterType type;switch(ui->comboBoxDataType->currentIndex()){case 0:type=QModbusDataUnit::Coils;break;case 1:type=QModbusDataUnit::DiscreteInputs;break;case 2:type=QModbusDataUnit::InputRegisters;break;case 3:type=QModbusDataUnit::HoldingRegisters;break;default:type=QModbusDataUnit::Invalid;}int startAddress = ui->spinBoxStartWrite->value();Q_ASSERT(startAddress >= 0 && startAddress < 10);QModbusDataUnit writeUnit = QModbusDataUnit(type,startAddress, values.size());for(int i=0; i<values.size(); i++){writeUnit.setValue(i, values.at(i));}//serverEdit 发生给slave的IDif (auto *reply = modbusDevice->sendWriteRequest(writeUnit,ui->spinBoxStation->value())){if (!reply->isFinished()){connect(reply, &QModbusReply::finished, this, [this, reply]() {if (reply->error() == QModbusDevice::ProtocolError) {qDebug() << QString("Write response error: %1 (Mobus exception: 0x%2)").arg(reply->errorString()).arg(reply->rawResult().exceptionCode(), -1, 16);} else if (reply->error() != QModbusDevice::NoError) {qDebug() << QString("Write response error: %1 (code: 0x%2)").arg(reply->errorString()).arg(reply->error(), -1, 16);}reply->deleteLater();});}else{reply->deleteLater();}}else{qDebug() << QString(("Write error: ") + modbusDevice->errorString());}
}

  对于自动周期性读取从站数据我们通过一个计时器周期性操作,而从站的地址、读取的寄存器类型、读取的寄存器起始地址、寄存器的数量以及间隔时间通过界面设置。而其操作与手动按钮触发一样。

3.3、信息显示功能

  对于信息的显示我们主要考虑3个方面的内容。一是读取回来的从站数据结果显示;二是上下行报文的监视;三是操作过程及状态的显示。
  首先是对读取回来的从站数据进行显示,在这里我们将读取的寄存器地址及其对应的数据显示在消息框中。同时我们将部分数据在图形显示中以曲线的形式展示出来。

//曲线显示
void MainWindow::ChartDisplay()
{QColor acolor[8]={Qt::red,Qt::blue,Qt::green,Qt::cyan,Qt::yellow,Qt::magenta,Qt::black,Qt::darkRed};QStringList name={"抛物线","正弦值","正弦值","固定值","固定值","固定值","固定值","固定值"};QVector<QPointF> list[8];QVector<QPointF> newlist[8];for(int j=0;j<8;j++){list[j] = lineSeries[j]->pointsVector();//获取现在图中列表if (list[j].size() < 200){//保持原来newlist[j] = list[j];}else{//错位移动for(int i =1 ; i< list[j].size();i++){newlist[j].append(QPointF(i-1,list[j].at(i).y()));}}newlist[j].append(QPointF(newlist[j].size(),values[j]));//最后补上新的数据lineSeries[j]->replace(newlist[j]);//替换更新lineSeries[j]->setName(name[j]);//设置曲线名称lineSeries[j]->setPen(acolor[j]);//设置曲线颜色lineSeries[j]->setUseOpenGL(true);//openGl 加速//mChart->setTitle("Pressure Data");//设置图标标题mChart->removeSeries(lineSeries[j]);mChart->addSeries(lineSeries[j]);mChart->createDefaultAxes();//设置坐标轴}ui->graphicsView->setChart(mChart);
}

  其次对于上下行报文我们也将其显示到消息显示框中。在QT对Modbus协议进行封装后,我们没有办法直接获取上下行的报文,我们可以开启日志答应功能,再从其中截取相应的报文。

QLoggingCategory::setFilterRules(QStringLiteral("qt.modbus* = true"));

  而操作过程及状态显示则比较简单,我们在状态栏显示相应的操作过程和操作的状态。

4、小结

  完成了编码调试后,我们尚需要对这一工具进行一些测试。首先我们安装一个虚拟串口软件用以虚拟我们用于测试的串口,并找到一款Modbus RTU的从站模拟软件。当然有实际的从站和硬件的串行端口更好,在这里我们先用软件模拟。具体的配置如下图所示:

  而Modbus RTU从站我们使用MThings来模拟,当然也可以使用其它Modbus RTU从站模拟软件。我们模拟10个保持寄存器和10个线圈,之所以这么设置是因为这两种数据类型支持读写,方便我们测试。具体的配置如下图所示:

  现在将我们设计的Modbus RTU主站运行起来,并使用它去访问我们刚才配置的Modbus RTU从站。首先我们实验读从站数据操作。测试的结果如下图所示:

  这里我们读取从站从地址0开始的10个保持寄存器,并将值显示在消息框和图形中。我们模拟了2路正弦信号、1路抛物线信号和5路固定值信号。接下来我们测试一下写操作。测试的结果如下图所示:

  这里对从站的从地址3开始的3个保持寄存器的值进行修改。设定的值分别是123、456和789,操作完成后我们查看从站的结果如下:

  上图中与我们设定值的完全符合,说明我们的写从站操作时正确的。到这里我们基于QT的Modbus RTU主站就基本实现了。当然,我们还可以根据需要修改或添加一些功能以适应不同的应用需求。我们已经将代码发布到Gitee,欢迎下载和交流。
下载地址:https://gitee.com/ErichMoonan/ModbusMaster

欢迎关注:

软件设计开发笔记3:基于QT的Modbus RTU主站相关推荐

  1. 软件设计开发笔记1:基于状态机的程序设计

      在编码实现的过程中,我们会经常使用到条件判断结构,而且使用起来很方便.但是在需要转移的状态比较多,或是条件比较复杂时,我们就可能需要很长的条件判断结构来处理.不过,过于复杂的条件判断结构会给代码的 ...

  2. 软件设计心情笔记(一)目的与手段都很重要

    忽然发现自己很久没有写技术博文了,上一篇还是在两周前. 今天下午和51CTO的博客管理员同学聊了聊,慢慢地感觉到那种大型技术博客网站是个好东西.要感谢51CTO和图灵社区这样的讨论园地,使我认识了很多 ...

  3. 微课竞赛系统的设计与实现所需工作条件_启升微课丨从软件设计开发着手准备医疗器械软件描述文档...

    启升微课--第四课! 大家好!今天是启升微课的第四课,本微课将跟大家分享的是"从软件设计开发着手准备软件描述文档". 首先,我们要清楚一个问题:有哪些医疗器械需要准备软件描述文档? ...

  4. 【QT学习笔记】基于QT的天气预报

    [QT学习笔记]基于QT的天气预报 前言 那就开始吧! 先看一下效果 颜面最重要,画个UI 构造实现 怎么开始? 开始解析数据 关于城市切换 ok 最后源码献上 感谢 前言 学习qt已经有一段时间了, ...

  5. 自己拿项目,软件设计开发,释放你的力量

    自己拿项目,软件设计开发,释放你的力量,链接地址 http://un.zhubajie.com/r/?u=4674706&l=http://u.zhubajie.com/user/buyer ...

  6. 软件设计开发思想总结

    起源:这段关于软件设计开发的想法源于两年前和同学刘某某一起逛资源论坛时,无心一句玩笑话而来,昨晚突然回忆起这个片段决定把这个细节通过文字记录下来,希望能够在今后软件开发的道路上陪伴我一起成长. 详细描 ...

  7. 分享一款Unity3d软件设计开发实例

    分享一款Unity3d软件设计开发实例 1 概述 软件制作过程中感觉有些像在做游戏,本软件为个人兴趣,还在测试制作阶段,仅用于学习交流.通过导入实际测量点云数据和抽样钻孔数据信息,采用DTM三角网三维 ...

  8. 软件开发、软件设计培训笔记

    一.代码质量的提高培训笔记(2013.11.29): 部分内容摘自网络: 1.      struct:注意成员排列顺序. 2.      #pragma pack(1) :指定按1字节对齐. 3.  ...

  9. Android导航软件设计开发,基于Android平台的手机导航系统的设计与实现

    摘要: 互联网和移动智能技术的快速发展,平板电脑,智能手机,已经走进我们生活的方方面面,购物.外卖.租房.工作.旅游,医疗各个行业.我们无时无刻不在享受互联网和移动终端带来的便利.GPS全球卫星定位带 ...

最新文章

  1. 2022-2028年中国网络出版产业投资分析及前景预测报告
  2. Python常用函数--文档字符串DocStrings
  3. java中有关覆写的总结
  4. 最常用的Python爬虫和数据分析常用第三方库,收藏吧
  5. Tomcat容器入门介绍
  6. phpcms 指定id范围 调用_Elasticsearch v7 中Java High-Level REST Client同步和异步调用
  7. Keras-数据增广
  8. SEO的有利因素跟不利因素
  9. c语言中 的优先级几级,C语言中的操作符优先级的详细介绍
  10. Panabit安装(二、Panabit安装)
  11. 用ShopEx网上开店之安装Zend插件
  12. 【转】Pro Android学习笔记(一):Android 平台 2013.6.4
  13. 云访问安全代理(CASB)行业调研报告 - 市场现状分析与发展前景预测(2021-2027年)
  14. 仿iOS Segmented Control样式
  15. 微信开发者工具关联gitee账户
  16. GnuTLS recv error (-54): Error in the pull function(解决方法)
  17. mysql版本查询命令
  18. 服务器虚拟机迁移的6个步骤,vmware 虚拟机迁移
  19. 【软考系统架构设计师】复盘架构设计师真题知识点第二十五章---企业信息化战略
  20. c语言中有符号数的补码,[分享]带符号数的表示-----补码

热门文章

  1. 持久畅快的使用体验 华为麦芒7与年轻人加速前行
  2. 服务器打印的信息怎么看到,怎么查看打印机服务器的ip地址
  3. Base64转Binary
  4. Eclipse代码智能提示设置
  5. 利用Python实现几种常见排序算法
  6. redis缓存雪崩、穿透、击穿概念、布隆过滤器小结及解决办法
  7. 腾讯企业邮箱官网是哪个?腾讯企业邮箱怎么注册?
  8. [ERP]呆滞库存产生的原因是什么?该如何预防?
  9. 优信发布2018年Q4财报:营收11.367亿元 同比增61.6%
  10. 死神(日期暴力破解问题)