0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看威廉希尔官方网站 视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

基于QT的Modbus RTU主站上位工具设计

CHANBAEK 来源:木南创智 作者:尹家军 2022-12-13 14:39 次阅读

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 entries
    quint16 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);
        else
            delete reply; // broadcast replies return immediately
    }
    else
    {
        statusBar()->showMessage(tr("Read error: ") + modbusDevice->errorString(), 5000);
    }

}

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

//写数据请求
void MainWindow::WriteRequest(QList 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; isize(); i++)
    {
        writeUnit.setValue(i, values.at(i));
    }

    //serverEdit 发生给slave的ID
    if (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 list[8];
    QVector 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的从站interwetten与威廉的赔率体系 软件。当然有实际的从站和硬件的串行端口更好,在这里我们先用软件模拟。具体的配置如下图所示:

  而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

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • MODBUS
    +关注

    关注

    28

    文章

    1783

    浏览量

    76897
  • 通讯协议
    +关注

    关注

    10

    文章

    273

    浏览量

    20340
  • Qt
    Qt
    +关注

    关注

    1

    文章

    301

    浏览量

    37865
  • RTU
    RTU
    +关注

    关注

    0

    文章

    408

    浏览量

    28653
收藏 人收藏

    评论

    相关推荐

    请教 labview 做一个modbus RTU

    最近在用LabVIE想做一个modbus RTu站,我的步骤是1、新建工程,2、新建一个 IO server,选择modbus (串口,)3、新建一个VI现在有几疑问,1、我通过这样
    发表于 11-17 17:09

    【NanoPi M2试用体验】之Modbus-RTU站开发

    NanoPi M2试用体验之Modbus-RTU站开发本菜鸟之前的几篇使用报告介绍了基于NanoPi M2无线网卡设置、NFS文件系统搭建、Mysql安装、Mysql可视化配置,以及基于QT
    发表于 05-26 09:20

    Modbus TCP转Modbus RTU的实现

    使用ZLSN2040、NETCOM2040实现Modbus TCP到Modbus RTU的转化。1.Modbus TCP与Modbus
    发表于 08-10 10:04

    Modbus站问题

    ModbusRTU两站之间如何交互数据? 工业控制中,一般都是主从通讯方式居多,有时也会碰到两个站之间通讯。例如某工作站上位机(站)需要监控一个plc末端设备,常规来说plc应作
    发表于 11-24 16:36

    请问在STM32上跑modbus rtu站应该怎么做

    最近一个项目中需要用到485通信,下面的期间是modbus rtu协议,我是小白没搞过modbus,有没有移植过的大神给小弟指点迷津啊,要在STM32上跑modbus
    发表于 01-11 09:04

    如何快速实现Modbus RTUModbus TCP协议转换?

    Modbus协议是工业现场串口设备之间常用的连接方式,其中最常见的就是Modbus RTUModbus TCP两种。许多工厂需要将现场各种不同型号设备的数据都能够通过一个
    发表于 08-18 18:36

    上位MODBUS RTU多从站通讯的VB程序

    上位MODBUS RTU多从站通讯的VB程序,实现上位机与下位机之间的数据传输。
    发表于 10-12 16:05 77次下载

    浅谈ModBus RTUModBus TCP

    ModBus RTUModBus TCP作为ModBus协议的两个主要变体,传统上,ModBus RT
    的头像 发表于 12-30 14:36 2328次阅读

    C#上位机:Modbus RTU通讯实例

    本文是对前文的补充,主要是针对上位机的串口通讯在Modbus RTU协议方面的运用。在前文中有详细代码描述了如何搭建一个串口通讯上 位机模板,在本文中,给出一些在前文中代码基础上的Modbu
    发表于 05-09 14:38 6次下载
    C#<b class='flag-5'>上位</b>机:<b class='flag-5'>Modbus</b> <b class='flag-5'>RTU</b>通讯实例

    EtherNet/IP转Modbus-RTU站协议网关(JM-EIP-RTU

    RTU网络之间无缝内部连接。 广泛应用:应用于Modbus RTU接口的变频器、上位机、仪表、马保等等。例如在房山某污水处理厂的PLC控制系统项目中,罗克韦尔PLC作为EtherNe
    的头像 发表于 08-26 14:43 298次阅读
    EtherNet/IP转<b class='flag-5'>Modbus-RTU</b><b class='flag-5'>主</b>站协议网关(JM-EIP-<b class='flag-5'>RTU</b>)

    EtherNet/IP转Modbus-RTU站网关(EtherNet/IP转Modbus-RTU

    RTU网络之间无缝内部连接。 广泛应用:应用于Modbus RTU接口的变频器、上位机、仪表、马保等等。例如在房山某污水处理厂的PLC控制系统项目中,罗克韦尔PLC作为EtherNe
    的头像 发表于 09-04 10:55 270次阅读
    EtherNet/IP转<b class='flag-5'>Modbus-RTU</b><b class='flag-5'>主</b>站网关(EtherNet/IP转<b class='flag-5'>Modbus-RTU</b>)

    Modbus RTU转CC-Link协议网关(CC-Link转Modbus RTU

    远创智控YC-CCLK-RTU型网关实现了CC-Link从站和Modbus RTU站(从站)。网关作为CC-Link从站接入到CC-Link网络中,比如连接到三菱PLC。CCLK-
    的头像 发表于 09-07 14:59 432次阅读
    <b class='flag-5'>Modbus</b> <b class='flag-5'>RTU</b>转CC-Link协议网关(CC-Link转<b class='flag-5'>Modbus</b> <b class='flag-5'>RTU</b>)

    CC-Link IEFB站转Modbus RTU协议网关(YC-CCLKIEM-RTU

    一,设备主要功能 远创智控YC-CCLKIEM-RTU型网关在CC-Link IEFB总线侧实现站功能,在Modbus RTU串口侧实现从站功能。可将CC-Link IEFB协议的设
    的头像 发表于 09-09 15:58 235次阅读
    CC-Link IEFB<b class='flag-5'>主</b>站转<b class='flag-5'>Modbus</b> <b class='flag-5'>RTU</b>协议网关(YC-CCLKIEM-<b class='flag-5'>RTU</b>)

    EtherCAT站转Modbus RTU网关(EtherCAT转Modbus RTU

    一,设备主要功能 远创智控YC-ECTM-RTU型网关将EtherCAT协议的设备接入到Modbus RTU网络中;可将EtherCAT站接入Mo
    的头像 发表于 09-09 16:32 267次阅读
    EtherCAT<b class='flag-5'>主</b>站转<b class='flag-5'>Modbus</b> <b class='flag-5'>RTU</b>网关(EtherCAT转<b class='flag-5'>Modbus</b> <b class='flag-5'>RTU</b>)

    Modbus RTU转CC-link协议网关(Modbus RTU转CC-link)

    一,设备主要功能 捷米特JM-CCLK-RTU网关实现CC-Link从站和Modbus RTU站(从站)。即将CC-Link作为CC-Link从站接入到
    的头像 发表于 09-10 10:28 632次阅读
    <b class='flag-5'>Modbus</b> <b class='flag-5'>RTU</b>转CC-link协议网关(<b class='flag-5'>Modbus</b> <b class='flag-5'>RTU</b>转CC-link)