码上通QT实战17--监控页面09-通信过程对接

1、前言

1、Modbus通信过程概述

Modbus是一种串行通信协议,广泛应用于工业自动化领域。通信过程基于主从架构,主设备(Master)发起请求,从设备(Slave)响应。协议支持RTU、ASCII和TCP三种传输模式。

Modbus RTU/ASCII通信流程

主设备发送请求帧

主设备构造包含从设备地址、功能码、数据字段和校验码的请求帧。RTU模式使用CRC校验,ASCII模式使用LRC校验。帧格式如下:

  • RTU:[地址][功能码][数据][CRC低字节][CRC高字节]

  • ASCII::[地址][功能码][数据][LRC][CR][LF]

从设备接收并处理请求

从设备检测地址匹配后,解析功能码和数据字段。若请求合法,执行相应操作(如读取寄存器或写入数据),否则返回异常响应。

从设备返回响应帧

正常响应帧包含从设备地址、功能码、数据字段和校验码。异常响应帧将功能码最高位置1,并附加异常码。例如:

  • 正常响应:[地址][功能码][数据长度][数据][校验]

  • 异常响应:[地址][功能码+0x80][异常码][校验]

Modbus TCP通信流程

建立TCP连接

主设备与从设备通过IP地址和端口(默认502)建立TCP连接。无需地址字段,改用MBAP头(Modbus Application Protocol Header)。

请求与响应结构

MBAP头包含事务标识符、协议标识符(固定0)、长度字段和单元标识符(从设备地址)。数据部分与RTU模式类似:

  • 请求:[MBAP][功能码][数据]

  • 响应:[MBAP][功能码][数据][MBAP][功能码+0x80][异常码]

典型功能码示例

  • 0x01:读取线圈状态(离散量输出)

  • 0x03:读取保持寄存器(模拟量输入)

  • 0x06:写入单个保持寄存器

  • 0x10:写入多个保持寄存器

离散量与模拟量的定义

离散量(Digital Quantity):以不连续的数值或状态表示,通常是有限个数的离散值。例如二进制信号(0和1)、数字计数器等。离散量在计算机系统和数字电路中广泛应用,简单通俗地说,离散量就是指只有开或关两种情况,比如灯的开关可用1表示开,0表示关,象这种情况是指开关量,也叫离散量,只有0或1,只有真或假,这种环境 。

模拟量(Analog Quantity):以连续变化的数值表示,可以在一定范围内取无限多个值。例如温度、压力、电压等物理量的连续变化。模拟量常见于传感器信号和传统控制系统。比如描述设备的转速,产量多少,他是一个变化的数字,而不是只有0和1这两种情况的,这是模拟量。

2、支持modbus协议的仪器设备

3、Modbus Slave

modbus Slave 是 Modbus 协议中的从设备(服务器端),负责响应主设备(客户端)的请求,提供数据访问或执行控制操作。它广泛应用于工业自动化、SCADA 系统和物联网设备中。

Modbus Slave 的功能

  • 数据存储:保存寄存器数据(如线圈、输入寄存器、保持寄存器等)。
  • 协议支持:支持 Modbus RTU、ASCII 和 TCP/IP 通信模式。
  • 响应请求:解析主设备的读写指令并返回相应数据。

Modbus Slave:用于测试和调试的模拟工具。也就是说,这个软件是用来模拟上面那些湿度,温度,灯泡硬件设备的,因为当前没有真实的硬家伙,可以使用软件来模拟设备,效果是一样的,效果相当的奈撕,耐死。

1)安装软件

2)创建设备

3)连接串口

4)串口调试工具

ComMonitor 是一款用于串口通信调试的工具,支持实时监控、发送和接收串口数据。常用于嵌入式开发、硬件调试、工业控制等领域,帮助开发者分析串口通信协议或排查通信问题。通过合理配置和功能选择,ComMonitor 能有效提升串口调试效率。


主要功能

实时数据监控

  • 显示串口接收和发送的原始数据(十六进制、ASCII 或混合格式)。

  • 支持时间戳记录,便于分析数据时序。

数据发送与接收

  • 支持手动输入或从文件加载数据并发送。

  • 自动记录接收到的数据,可保存为文本或二进制文件。

参数配置

  • 可设置波特率、数据位、停止位、校验位等串口参数。

  • 支持自定义帧头、帧尾过滤,方便协议解析。

高级功能

  • 数据统计(如接收字节数、错误计数)。

  • 支持多串口同时监控(部分版本)。

更详细的modbus功能码,发送指令和响应指令的格式要求,请自行百度或AI工具

2、直接干

1、修改配置文件

2、创建异步任务

QtConcurrent 是 Qt 提供的一个高级并发编程模块,用于简化多线程任务的开发。它基于线程池实现,无需直接管理线程,适合处理可并行化的任务(如数据过滤、映射、归约等)。该模块属于 QtConcurrent 命名空间,需在项目中引入 QT += concurrent

通过 QtConcurrent::run() 在后台线程中执行函数,返回 QFuture 对象用于监控结果:

注意事项

  • 线程安全 :确保共享数据通过互斥锁(如 QMutex)保护,或使用无状态函数。
  • 返回值处理QFutureresult() 会阻塞调用线程,建议结合信号槽异步获取结果。
  • 性能权衡 :线程池默认大小与 CPU 核心数相关,可通过 QThreadPool::globalInstance()->setMaxThreadCount() 调整。
cpp 复制代码
QFuture<void> future = QtConcurrent::run([]() {
    // 耗时操作
});
future.waitForFinished(); // 阻塞等待完成

QFuture 是 Qt 框架中用于表示异步计算结果的类,属于 QtConcurrent 模块的一部分。它允许开发者启动异步任务(如多线程操作),并通过信号槽机制或轮询方式获取任务结果,常用于非阻塞式编程。

核心功能

  • 异步任务管理 :与 QtConcurrent::run()QThreadPool 结合使用,执行耗时操作(如文件读写、网络请求)。

  • 结果获取 :通过 result()waitForFinished() 获取计算结果,支持泛型类型(如 QFuture<int>)。

  • 进度与状态监控 :提供 isFinished()isCanceled() 等方法,并可连接 progressValueChanged 信号更新 UI。

3、发送功能指令

cpp 复制代码
#include "mainwindow.h"
#include "systemutils.h"
#include "ui_mainwindow.h"
#include <QMessageBox>
#include <QMouseEvent>
#include <QThread>
#include <QtConcurrent/QtConcurrentRun>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    // 1.设置窗口无边框和窗口透明
    this->setWindowFlags(Qt::FramelessWindowHint);
    this->setAttribute(Qt::WA_TranslucentBackground);


    // 设置相关阴影效果
    SystemUtils * utils=new SystemUtils();
    utils->SetDropShadowEffect(ui->wdg_root,Qt::gray,10);

    //设置关闭,最小,最大的图标
    // 设置相关字体图标
    QFont font=QFont(QString("zx_icons"),9);
    // 关闭
    ui->pb_close->setFont(font);
    ui->pb_close->setText(QChar(0xe653));
    // 最大化
    ui->pb_max->setFont(font);
    ui->pb_max->setText(QChar(0xe694));
    // 最小化
    ui->pb_min->setFont(font);
    ui->pb_min->setText(QChar(0xe7e6));
    //设置第二行消息的图标
    ui->lbl_message_icon->setFont(font);
    ui->lbl_message_icon->setText(QChar(0xe7ff));

    //设置标题字体
    QFont font2=QFont(QString("钉钉进步体"),14);
    ui->lbl_title->setFont(font2);

    //设置"实时监控"按钮的信号和槽函数
    connect(ui->nb_monitor,SIGNAL(onClicked(int)),SLOT(onNavClicked(int)));
    //设置"趋势图表"导航按钮的信号和槽函数
    connect(ui->nb_trend,SIGNAL(onClicked(int)),SLOT(onNavClicked(int)));
    //设置"异常报警"导航按钮的信号和槽函数
    connect(ui->nb_alarm,SIGNAL(onClicked(int)),SLOT(onNavClicked(int)));
    //设置"系统设置"导航按钮的信号和槽函数
    connect(ui->nb_settings,SIGNAL(onClicked(int)),SLOT(onNavClicked(int)));

    //绑定监控页面Monitor的信号和槽函数关联,即monitor页面中的信号函数onConnect由本页面的槽函数onConnect响应处理
    connect(ui->Monitor,SIGNAL(onConnect(QString,QString,QString,QString,QString)),SLOT(onConnect(QString,QString,QString,QString,QString)));

    //创建线程及对象,处理串口
    thread=new QThread();
    worker=new QWorker();
    //1、本页面的信号函数onOpen由worker对象的槽函数openPort响应处理
    connect(this,SIGNAL(onOpen(QString,QString,QString,QString,QString)),worker,SLOT(openPort(QString,QString,QString,QString,QString)));
    //2、worker对象中的信号函数openCompleted由本页面的onSetopenState槽函数响应处理
    connect(worker,SIGNAL(openCompleted(bool)),this,SLOT(onSetopenState(bool)));
    //3、本页面的信号函数onSend由worker对象的槽函数sendData响应处理
    connect(this,SIGNAL(onSend(QByteArray,int)),worker,SLOT(sendData(QByteArray,int)));
    //4、worker对象中的信号函数sendCompleted由本页面的槽函数onSendCompleted响应处理
    connect(worker,SIGNAL(sendCompleted(QByteArray,int)),this,SLOT(onSendCompleted(QByteArray,int)));

    worker->moveToThread(thread);//把对象worker放在多线程中
    thread->start();//启动线程
    //f1=QtConcurrent::run(this,&MainWindow::onMonitor);//这是qt5.X的写法,这个写法在6.x中不支持
    //实例化异步任务
    f1 = QtConcurrent::run([this]() {
        this->onMonitor(); // 替换为你的成员函数名
    });
}

MainWindow::~MainWindow()
{
    delete ui;
}

//监控事件
void MainWindow::onMonitor()
{
    while(this->isMonitor){
        // 从设备中获取到相关数据
        QByteArray bytes;
        bytes.resize(6);
        bytes[0]=0x01;//设备地址
        bytes[1]=0x03;//功能码
        bytes[2]=0x00;//开始地址
        bytes[3]=0x00;
        bytes[4]=0x00;//请求数量
        bytes[5]=0x03;
        // CRC16算法
        auto crc=this->CRC16(bytes);
        uint8_t hi=uint8_t(crc>>8);
        uint8_t lo=uint8_t(crc);
        bytes.resize(8);
        bytes[6]=lo;//crc校验码
        bytes[7]=hi;
        // 需要给QWorker对象发送一个信号
        emit onSend(bytes,1);//发送信号
        QThread::msleep(500);
    }
}

//鼠标按下事件
void MainWindow::mousePressEvent(QMouseEvent *event){
    if (event->button() == Qt::LeftButton) {  // 如果按下左边按钮
        m_drag = true;//表示要移动
        // 获取当前光标的位置
        m_dragPos = event->pos();
        // 当前鼠标点相对于桌面屏幕左上角的坐标(0,0),全局坐标;
        m_resizeDownPos = event->globalPosition().toPoint();
        // 获取当前窗口的相关参数,包括位置,大小等等各种参数
        m_mouseDownRect = this->rect();
    }
}

//鼠标移动事件
void MainWindow::mouseMoveEvent(QMouseEvent *event){
    // 如果是鼠标在拖动时,当前窗口是全屏,不做任何处理
    if (isFullScreen()) {
        return;
    }
    if (m_move) {
        move(event->globalPosition().toPoint() - m_dragPos);
        return;
    }

    setCursor(Qt::ArrowCursor);
    if (m_drag && (event->buttons() & Qt::LeftButton)) {
        m_move = true;
        move(event->globalPosition().toPoint() - m_dragPos);
    }
}

//鼠标释放事件
void MainWindow::mouseReleaseEvent(QMouseEvent *event){
    Q_UNUSED(event)
    m_drag = false;
    if (m_move) {
        m_move = false;
    }
    setCursor(Qt::ArrowCursor);
}

//关闭事件
void MainWindow::on_pb_close_clicked()
{
    QMessageBox::StandardButton ret=QMessageBox::question(this,tr("关闭系统运行"),tr("您确定要退出系统吗?"),QMessageBox::Yes | QMessageBox::No,QMessageBox::Yes );
    if(ret== QMessageBox::Yes){
        this->isMonitor=false;
        this->close();
    }

}
//最大化
void MainWindow::on_pb_max_clicked()
{
    this->showFullScreen();  // 全屏展示
}
//最小化
void MainWindow::on_pb_min_clicked()
{
    this->showMinimized();
}

//窗体双击事件
void MainWindow::mouseDoubleClickEvent(QMouseEvent *event)
{
    Q_UNUSED(event); // 标记参数未使用,消除警告
    if (isFullScreen()) {
        showNormal();  // 如果是全屏,就恢复到非全屏
    } else {
        showFullScreen();  // 否则就变成全屏的
    }
}

//导航按钮的点击响应事件
void MainWindow::onNavClicked(int index)
{
    ui->sw_pages->setCurrentIndex(index);
}

//连接串口设备
void MainWindow::onConnect(QString port, QString baud, QString parity, QString data, QString stop)
{
    //执行串口对象的连接动作,动作必须在后台线程中处理
    //触发信号,让qworker这个对象来接收信号
    emit onOpen(port,baud,parity,data,stop);
}

//串口连接状态
void MainWindow::onSetopenState(bool state)
{
    //接收到worker对象中的打开状态信号
    ui->Monitor->setOpenState(state);

}

//计算CRC校验码
uint16_t MainWindow::CRC16(QByteArray bytes)
{
    int len=bytes.size();
    uint16_t wcrc=0XFFFF;//预置16位crc寄存器,初值全部为1
    uint8_t temp;//定义中间变量
    int i=0,j=0;//定义计数

    for(i=0;i<len;i++)//循环计算每个数据
    {
        temp=bytes.at(i);
        wcrc^=temp;
        for(j=0;j<8;j++){
            //判断右移出的是不是1,如果是1则与多项式进行异或。
            if(wcrc&0X0001){
                wcrc>>=1;//先将数据右移一位
                wcrc^=0XA001;//与上面的多项式进行异或
            }
            else//如果不是1,则直接移出
                wcrc>>=1;//直接移出
        }
    }
    temp=wcrc;//crc的值
    return wcrc;
}

4、发送接收流程

5、运行测试

6、小结

本节内容较多较复杂,重点是modbus功能码,另外是工具软件的使用,还有QT的异步,多线程知识,下节解析报文,并显示到界面上。

复制代码
原创不易,打字不易,截图不易,撸码不易,整理不易,走过路过,不要错过,欢迎点赞,收藏,转载,复制,抄袭,留言,灌水,请动动你的金手指,祝您早日实现财务自由。
相关推荐
运维行者_2 小时前
远程办公场景 NFA:从网络嗅探与局域网流量监控软件排查团队网络卡顿问题
运维·服务器·开发语言·网络·自动化·php
北京耐用通信2 小时前
协议转换“黑科技”:耐达讯自动化CANopen转Profibus 网关破解电机控制通信难题
网络·人工智能·科技·物联网·自动化·信息与通信
咕噜企业分发小米2 小时前
如何通过腾讯云防护直播云服务器?
服务器·网络·腾讯云
捷米研发三部2 小时前
EtherNet/IP转CC-Link IEFB协议转换网关实现三菱 PLC与编码器通讯在印刷机械的应用案例
网络
YYYing.3 小时前
【计算机网络 | 第三篇】MAC地址与IP地址
网络·tcp/ip·计算机网络
嘿嘿嘿x33 小时前
网络通信基础知识
网络
这儿有一堆花3 小时前
深入解析 VPC:云端网络架构的核心基石
网络·架构
8K超高清3 小时前
2026科技风口:有哪些前沿场景即将落地?
网络·人工智能·科技·数码相机·计算机视觉
我想吃余3 小时前
【网络篇】网络基础概念
网络