Browse Source

Modbus程序提交

master
kai__wei 5 days ago
parent
commit
c672e47374
16 changed files with 3744 additions and 0 deletions
  1. +38
    -0
      Modbus/ModbusMatser.pro
  2. +319
    -0
      Modbus/ModbusMatser.pro.user
  3. +103
    -0
      Modbus/include/mainwindow.h
  4. +716
    -0
      Modbus/include/mainwindow.ui
  5. +178
    -0
      Modbus/include/modbus_master.h
  6. +117
    -0
      Modbus/include/serial_communication.h
  7. +73
    -0
      Modbus/include/timeout_handler.h
  8. +11
    -0
      Modbus/src/main.cpp
  9. +613
    -0
      Modbus/src/mainwindow.cpp
  10. +547
    -0
      Modbus/src/modbus_master.cpp
  11. +371
    -0
      Modbus/src/serial_communication.cpp
  12. +118
    -0
      Modbus/src/timeout_handler.cpp
  13. +38
    -0
      ModbusMatser.pro
  14. +165
    -0
      UnitTest/tst_test_modbus.cpp
  15. +18
    -0
      UnitTest/untitled.pro
  16. +319
    -0
      UnitTest/untitled.pro.user

+ 38
- 0
Modbus/ModbusMatser.pro View File

@@ -0,0 +1,38 @@
QT += core gui
QT += serialport
QT += core
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

CONFIG += c++11

# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS

# You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0

SOURCES += \
main.cpp \
mainwindow.cpp \
modbus_master.cpp \
serial_communication.cpp \
timeout_handler.cpp

HEADERS += \
mainwindow.h \
modbus_master.h \
serial_communication.h \
timeout_handler.h

FORMS += \
mainwindow.ui

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

+ 319
- 0
Modbus/ModbusMatser.pro.user View File

@@ -0,0 +1,319 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 4.11.1, 2025-08-04T10:01:37. -->
<qtcreator>
<data>
<variable>EnvironmentId</variable>
<value type="QByteArray">{54b998bd-0b43-4b57-8d79-23e6237f0a57}</value>
</data>
<data>
<variable>ProjectExplorer.Project.ActiveTarget</variable>
<value type="int">0</value>
</data>
<data>
<variable>ProjectExplorer.Project.EditorSettings</variable>
<valuemap type="QVariantMap">
<value type="bool" key="EditorConfiguration.AutoIndent">true</value>
<value type="bool" key="EditorConfiguration.AutoSpacesForTabs">false</value>
<value type="bool" key="EditorConfiguration.CamelCaseNavigation">true</value>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.0">
<value type="QString" key="language">Cpp</value>
<valuemap type="QVariantMap" key="value">
<value type="QByteArray" key="CurrentPreferences">CppGlobal</value>
</valuemap>
</valuemap>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.1">
<value type="QString" key="language">QmlJS</value>
<valuemap type="QVariantMap" key="value">
<value type="QByteArray" key="CurrentPreferences">QmlJSGlobal</value>
</valuemap>
</valuemap>
<value type="int" key="EditorConfiguration.CodeStyle.Count">2</value>
<value type="QByteArray" key="EditorConfiguration.Codec">UTF-8</value>
<value type="bool" key="EditorConfiguration.ConstrainTooltips">false</value>
<value type="int" key="EditorConfiguration.IndentSize">4</value>
<value type="bool" key="EditorConfiguration.KeyboardTooltips">false</value>
<value type="int" key="EditorConfiguration.MarginColumn">80</value>
<value type="bool" key="EditorConfiguration.MouseHiding">true</value>
<value type="bool" key="EditorConfiguration.MouseNavigation">true</value>
<value type="int" key="EditorConfiguration.PaddingMode">1</value>
<value type="bool" key="EditorConfiguration.ScrollWheelZooming">true</value>
<value type="bool" key="EditorConfiguration.ShowMargin">false</value>
<value type="int" key="EditorConfiguration.SmartBackspaceBehavior">0</value>
<value type="bool" key="EditorConfiguration.SmartSelectionChanging">true</value>
<value type="bool" key="EditorConfiguration.SpacesForTabs">true</value>
<value type="int" key="EditorConfiguration.TabKeyBehavior">0</value>
<value type="int" key="EditorConfiguration.TabSize">8</value>
<value type="bool" key="EditorConfiguration.UseGlobal">true</value>
<value type="int" key="EditorConfiguration.Utf8BomBehavior">1</value>
<value type="bool" key="EditorConfiguration.addFinalNewLine">true</value>
<value type="bool" key="EditorConfiguration.cleanIndentation">true</value>
<value type="bool" key="EditorConfiguration.cleanWhitespace">true</value>
<value type="bool" key="EditorConfiguration.inEntireDocument">false</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.PluginSettings</variable>
<valuemap type="QVariantMap">
<valuelist type="QVariantList" key="ClangCodeModel.CustomCommandLineKey">
<value type="QString">-fno-delayed-template-parsing</value>
</valuelist>
<value type="bool" key="ClangCodeModel.UseGlobalConfig">true</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.Target.0</variable>
<valuemap type="QVariantMap">
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Desktop Qt 5.14.2 MinGW 64-bit</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Desktop Qt 5.14.2 MinGW 64-bit</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">qt.qt5.5142.win64_mingw73_kit</value>
<value type="int" key="ProjectExplorer.Target.ActiveBuildConfiguration">0</value>
<value type="int" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
<value type="int" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0">
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">D:/QT/QTProject/build-ModbusMatser-Desktop_Qt_5_14_2_MinGW_64_bit-Debug</value>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">QtProjectManager.QMakeBuildStep</value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.LinkQmlDebuggingLibrary">true</value>
<value type="QString" key="QtProjectManager.QMakeBuildStep.QMakeArguments"></value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.QMakeForced">false</value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.SeparateDebugInfo">false</value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.UseQtQuickCompiler">false</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.1">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
<valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.BuildTargets"/>
<value type="bool" key="Qt4ProjectManager.MakeStep.Clean">false</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments"></value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeCommand"></value>
<value type="bool" key="Qt4ProjectManager.MakeStep.OverrideMakeflags">false</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">2</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
<valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.BuildTargets"/>
<value type="bool" key="Qt4ProjectManager.MakeStep.Clean">true</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments">clean</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeCommand"></value>
<value type="bool" key="Qt4ProjectManager.MakeStep.OverrideMakeflags">false</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Debug</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4BuildConfiguration</value>
<value type="int" key="Qt4ProjectManager.Qt4BuildConfiguration.BuildConfiguration">2</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.1">
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">D:/QT/QTProject/build-ModbusMatser-Desktop_Qt_5_14_2_MinGW_64_bit-Release</value>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">QtProjectManager.QMakeBuildStep</value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.LinkQmlDebuggingLibrary">false</value>
<value type="QString" key="QtProjectManager.QMakeBuildStep.QMakeArguments"></value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.QMakeForced">false</value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.SeparateDebugInfo">false</value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.UseQtQuickCompiler">true</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.1">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
<valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.BuildTargets"/>
<value type="bool" key="Qt4ProjectManager.MakeStep.Clean">false</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments"></value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeCommand"></value>
<value type="bool" key="Qt4ProjectManager.MakeStep.OverrideMakeflags">false</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">2</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
<valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.BuildTargets"/>
<value type="bool" key="Qt4ProjectManager.MakeStep.Clean">true</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments">clean</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeCommand"></value>
<value type="bool" key="Qt4ProjectManager.MakeStep.OverrideMakeflags">false</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Release</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4BuildConfiguration</value>
<value type="int" key="Qt4ProjectManager.Qt4BuildConfiguration.BuildConfiguration">0</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.2">
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">D:/QT/QTProject/build-ModbusMatser-Desktop_Qt_5_14_2_MinGW_64_bit-Profile</value>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">QtProjectManager.QMakeBuildStep</value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.LinkQmlDebuggingLibrary">true</value>
<value type="QString" key="QtProjectManager.QMakeBuildStep.QMakeArguments"></value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.QMakeForced">false</value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.SeparateDebugInfo">true</value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.UseQtQuickCompiler">true</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.1">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
<valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.BuildTargets"/>
<value type="bool" key="Qt4ProjectManager.MakeStep.Clean">false</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments"></value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeCommand"></value>
<value type="bool" key="Qt4ProjectManager.MakeStep.OverrideMakeflags">false</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">2</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
<valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.BuildTargets"/>
<value type="bool" key="Qt4ProjectManager.MakeStep.Clean">true</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments">clean</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeCommand"></value>
<value type="bool" key="Qt4ProjectManager.MakeStep.OverrideMakeflags">false</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Profile</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4BuildConfiguration</value>
<value type="int" key="Qt4ProjectManager.Qt4BuildConfiguration.BuildConfiguration">0</value>
</valuemap>
<value type="int" key="ProjectExplorer.Target.BuildConfigurationCount">3</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
</valuemap>
<value type="int" key="ProjectExplorer.Target.DeployConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.PluginSettings"/>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
<value type="QString" key="Analyzer.Perf.CallgraphMode">dwarf</value>
<valuelist type="QVariantList" key="Analyzer.Perf.Events">
<value type="QString">cpu-cycles</value>
</valuelist>
<valuelist type="QVariantList" key="Analyzer.Perf.ExtraArguments"/>
<value type="int" key="Analyzer.Perf.Frequency">250</value>
<valuelist type="QVariantList" key="Analyzer.Perf.RecordArguments">
<value type="QString">-e</value>
<value type="QString">cpu-cycles</value>
<value type="QString">--call-graph</value>
<value type="QString">dwarf,4096</value>
<value type="QString">-F</value>
<value type="QString">250</value>
</valuelist>
<value type="QString" key="Analyzer.Perf.SampleMode">-F</value>
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
<value type="int" key="Analyzer.Perf.StackSize">4096</value>
<value type="bool" key="Analyzer.QmlProfiler.AggregateTraces">false</value>
<value type="bool" key="Analyzer.QmlProfiler.FlushEnabled">false</value>
<value type="uint" key="Analyzer.QmlProfiler.FlushInterval">1000</value>
<value type="QString" key="Analyzer.QmlProfiler.LastTraceFile"></value>
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
<valuelist type="QVariantList" key="Analyzer.Valgrind.AddedSuppressionFiles"/>
<value type="bool" key="Analyzer.Valgrind.Callgrind.CollectBusEvents">false</value>
<value type="bool" key="Analyzer.Valgrind.Callgrind.CollectSystime">false</value>
<value type="bool" key="Analyzer.Valgrind.Callgrind.EnableBranchSim">false</value>
<value type="bool" key="Analyzer.Valgrind.Callgrind.EnableCacheSim">false</value>
<value type="bool" key="Analyzer.Valgrind.Callgrind.EnableEventToolTips">true</value>
<value type="double" key="Analyzer.Valgrind.Callgrind.MinimumCostRatio">0.01</value>
<value type="double" key="Analyzer.Valgrind.Callgrind.VisualisationMinimumCostRatio">10</value>
<value type="bool" key="Analyzer.Valgrind.FilterExternalIssues">true</value>
<value type="QString" key="Analyzer.Valgrind.KCachegrindExecutable">kcachegrind</value>
<value type="int" key="Analyzer.Valgrind.LeakCheckOnFinish">1</value>
<value type="int" key="Analyzer.Valgrind.NumCallers">25</value>
<valuelist type="QVariantList" key="Analyzer.Valgrind.RemovedSuppressionFiles"/>
<value type="int" key="Analyzer.Valgrind.SelfModifyingCodeDetection">1</value>
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
<value type="bool" key="Analyzer.Valgrind.ShowReachable">false</value>
<value type="bool" key="Analyzer.Valgrind.TrackOrigins">true</value>
<value type="QString" key="Analyzer.Valgrind.ValgrindExecutable">valgrind</value>
<valuelist type="QVariantList" key="Analyzer.Valgrind.VisibleErrorKinds">
<value type="int">0</value>
<value type="int">1</value>
<value type="int">2</value>
<value type="int">3</value>
<value type="int">4</value>
<value type="int">5</value>
<value type="int">6</value>
<value type="int">7</value>
<value type="int">8</value>
<value type="int">9</value>
<value type="int">10</value>
<value type="int">11</value>
<value type="int">12</value>
<value type="int">13</value>
<value type="int">14</value>
</valuelist>
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4RunConfiguration:D:/QT/QTProject/ModbusMatser/ModbusMatser.pro</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey">D:/QT/QTProject/ModbusMatser/ModbusMatser.pro</value>
<value type="QString" key="RunConfiguration.Arguments"></value>
<value type="bool" key="RunConfiguration.Arguments.multi">false</value>
<value type="QString" key="RunConfiguration.OverrideDebuggerStartup"></value>
<value type="bool" key="RunConfiguration.UseCppDebugger">false</value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseLibrarySearchPath">true</value>
<value type="bool" key="RunConfiguration.UseMultiProcess">false</value>
<value type="bool" key="RunConfiguration.UseQmlDebugger">false</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
<value type="QString" key="RunConfiguration.WorkingDirectory"></value>
<value type="QString" key="RunConfiguration.WorkingDirectory.default">D:/QT/QTProject/build-ModbusMatser-Desktop_Qt_5_14_2_MinGW_64_bit-Debug</value>
</valuemap>
<value type="int" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.TargetCount</variable>
<value type="int">1</value>
</data>
<data>
<variable>ProjectExplorer.Project.Updater.FileVersion</variable>
<value type="int">22</value>
</data>
<data>
<variable>Version</variable>
<value type="int">22</value>
</data>
</qtcreator>

+ 103
- 0
Modbus/include/mainwindow.h View File

@@ -0,0 +1,103 @@
/***********************************************************************
* Copyright (C) 2025-, XINJE Co., Ltd.
*
* File Name: mainwindow.h
* Description: Modbus主站的人机交互界面头文件,包含其成员变量与成员函数声明
* Others:
* Version: v1.0
* Author: weikai XINJE
* Date: 2025-7-30
***********************************************************************/

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QTimer>
#include <QDateTime>
#include "serial_communication.h"
#include "modbus_master.h"

namespace Ui
{
class MainWindow;
}

const int DEFAULT_TIMEOUT = 1000; //默认超时时间
const int DEFAULT_MAXRETRISE = 3; //默认最大重发次数

/********************************************************************
* Iterates over the contents of a MainWindow.
*人机交互界面:
*通过SerialCommunicator实例serialComm实现串口通信
*通过ModbusRTUMaster实例modbusMaster实现Modbus请求帧生成与响应帧解析
*通过QTimer实例serialPortTimer实现串口下拉框定时刷新
***********************************************************************/
class MainWindow : public QMainWindow
{
Q_OBJECT

public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();


private slots:
// UI按钮槽函数
void onConnectButtonClicked();//连接按钮
void onDisconnectButtonClicked();//断开连接按钮
void onReadButtonClicked();//读取按钮
void onWriteButtonClicked();//写入按钮
void onClearLogButtonClicked();//清空日志框
void onExportHistoryButtonClicked();//导出通信历史

// 串口通信信号处理槽
/***********************************************************************
* @brief : 串口接收数据到达信号的处理槽函数
* @param : data 串口接收到的数据字节流
***********************************************************************/
void onSerialDataReceived(const QString &data);

/***********************************************************************
* @brief : 串口接收状态变化信号的处理槽函数
* @param : 串口status状态变化的描述字符串
***********************************************************************/
void onSerialStatusChanged(const QString &status);

/***********************************************************************
* @brief : 串口接收错误发生信号的处理槽函数
* @param : error 串口错误的描述字符串
***********************************************************************/
void onSerialErrorOccurred(const QString &error);

//断开连接信号处理槽
void onConnectionDisconnected();

// 定时刷新槽函数
void onRefreshSerialPorts();

private:
//界面初始化
void init();

/***********************************************************************
* @brief : 日志输出函数
* @param : message 日志字符串
***********************************************************************/
void logToBox(const QString &message);

/***********************************************************************
* @brief : 刷新串口下拉框
***********************************************************************/
void refreshSerialPorts();

Ui::MainWindow *ui_;
SerialCommunicator *serialComm_; // 串口通信实例
ModbusRTUMaster *modbusMaster_;//Modbus协议实例
QTimer *serialPortTimer_;//用于定时刷新可用串口下拉栏的定时器
};

#endif // MAINWINDOW_H




+ 716
- 0
Modbus/include/mainwindow.ui View File

@@ -0,0 +1,716 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>815</width>
<height>606</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<widget class="QGroupBox" name="groupBox_3">
<property name="geometry">
<rect>
<x>10</x>
<y>240</y>
<width>381</width>
<height>311</height>
</rect>
</property>
<property name="font">
<font>
<family>楷体</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="title">
<string/>
</property>
<widget class="QLabel" name="label">
<property name="geometry">
<rect>
<x>10</x>
<y>120</y>
<width>91</width>
<height>31</height>
</rect>
</property>
<property name="text">
<string>起始地址:</string>
</property>
</widget>
<widget class="QLabel" name="label_10">
<property name="geometry">
<rect>
<x>190</x>
<y>110</y>
<width>91</width>
<height>41</height>
</rect>
</property>
<property name="font">
<font>
<family>楷体</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="text">
<string>读取数量:</string>
</property>
</widget>
<widget class="QLabel" name="label_4">
<property name="geometry">
<rect>
<x>10</x>
<y>70</y>
<width>91</width>
<height>20</height>
</rect>
</property>
<property name="font">
<font>
<family>楷体</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="text">
<string>从站地址:</string>
</property>
</widget>
<widget class="QLabel" name="label_8">
<property name="geometry">
<rect>
<x>200</x>
<y>60</y>
<width>81</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<family>楷体</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="text">
<string>功能码:</string>
</property>
</widget>
<widget class="QComboBox" name="functionComboBox">
<property name="geometry">
<rect>
<x>270</x>
<y>60</y>
<width>101</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<family>楷体</family>
<pointsize>11</pointsize>
</font>
</property>
<item>
<property name="text">
<string>01读线圈</string>
</property>
</item>
<item>
<property name="text">
<string>03读寄存器</string>
</property>
</item>
<item>
<property name="text">
<string>0F写线圈</string>
</property>
</item>
<item>
<property name="text">
<string>10写寄存器</string>
</property>
</item>
</widget>
<widget class="QTextEdit" name="writeTextEdit">
<property name="geometry">
<rect>
<x>0</x>
<y>260</y>
<width>381</width>
<height>41</height>
</rect>
</property>
</widget>
<widget class="QPushButton" name="writeButton">
<property name="geometry">
<rect>
<x>260</x>
<y>180</y>
<width>93</width>
<height>41</height>
</rect>
</property>
<property name="text">
<string>写入</string>
</property>
</widget>
<widget class="QPushButton" name="readButton">
<property name="geometry">
<rect>
<x>90</x>
<y>180</y>
<width>93</width>
<height>41</height>
</rect>
</property>
<property name="text">
<string>读取</string>
</property>
</widget>
<widget class="QSpinBox" name="numberSpinBox">
<property name="geometry">
<rect>
<x>280</x>
<y>120</y>
<width>90</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<family>黑体</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="maximum">
<number>1000</number>
</property>
</widget>
<widget class="QSpinBox" name="startAddrSpinBox">
<property name="geometry">
<rect>
<x>100</x>
<y>120</y>
<width>91</width>
<height>31</height>
</rect>
</property>
<property name="maximum">
<number>20000</number>
</property>
</widget>
<widget class="QSpinBox" name="stationAddrSpinBox">
<property name="geometry">
<rect>
<x>100</x>
<y>60</y>
<width>91</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<family>黑体</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="maximum">
<number>1000</number>
</property>
</widget>
<widget class="QLabel" name="label_13">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>101</width>
<height>31</height>
</rect>
</property>
<property name="text">
<string>Modbus配置</string>
</property>
</widget>
<widget class="QLabel" name="label_14">
<property name="geometry">
<rect>
<x>0</x>
<y>230</y>
<width>91</width>
<height>31</height>
</rect>
</property>
<property name="text">
<string>写入数据</string>
</property>
</widget>
</widget>
<widget class="QGroupBox" name="groupBox_4">
<property name="geometry">
<rect>
<x>400</x>
<y>310</y>
<width>401</width>
<height>241</height>
</rect>
</property>
<property name="font">
<font>
<family>楷体</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="title">
<string/>
</property>
<widget class="QTextEdit" name="logTextEdit">
<property name="geometry">
<rect>
<x>0</x>
<y>30</y>
<width>391</width>
<height>201</height>
</rect>
</property>
<property name="font">
<font>
<family>微软雅黑 Light</family>
<pointsize>10</pointsize>
</font>
</property>
</widget>
<widget class="QPushButton" name="clearLogButton">
<property name="geometry">
<rect>
<x>300</x>
<y>0</y>
<width>93</width>
<height>31</height>
</rect>
</property>
<property name="text">
<string>清空</string>
</property>
</widget>
<widget class="QLabel" name="label_12">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>81</width>
<height>21</height>
</rect>
</property>
<property name="text">
<string>日志框</string>
</property>
</widget>
</widget>
<widget class="QGroupBox" name="groupBox">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>381</width>
<height>231</height>
</rect>
</property>
<property name="font">
<font>
<family>楷体</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="title">
<string>通信配置</string>
</property>
<widget class="QComboBox" name="verifyComboBox">
<property name="geometry">
<rect>
<x>120</x>
<y>70</y>
<width>70</width>
<height>30</height>
</rect>
</property>
</widget>
<widget class="QComboBox" name="dataComboBox">
<property name="geometry">
<rect>
<x>280</x>
<y>70</y>
<width>70</width>
<height>30</height>
</rect>
</property>
<property name="font">
<font>
<family>黑体</family>
<pointsize>10</pointsize>
</font>
</property>
</widget>
<widget class="QComboBox" name="stopComboBox">
<property name="geometry">
<rect>
<x>280</x>
<y>110</y>
<width>70</width>
<height>30</height>
</rect>
</property>
<property name="font">
<font>
<family>黑体</family>
<pointsize>10</pointsize>
</font>
</property>
</widget>
<widget class="QLabel" name="label_5">
<property name="geometry">
<rect>
<x>40</x>
<y>120</y>
<width>85</width>
<height>23</height>
</rect>
</property>
<property name="font">
<font>
<family>楷体</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="text">
<string>波特率:</string>
</property>
</widget>
<widget class="QComboBox" name="baudComboBox">
<property name="geometry">
<rect>
<x>120</x>
<y>110</y>
<width>70</width>
<height>30</height>
</rect>
</property>
<property name="font">
<font>
<family>黑体</family>
<pointsize>10</pointsize>
</font>
</property>
<item>
<property name="text">
<string>4800</string>
</property>
</item>
<item>
<property name="text">
<string>9600</string>
</property>
</item>
<item>
<property name="text">
<string>19200</string>
</property>
</item>
<item>
<property name="text">
<string>38400</string>
</property>
</item>
<item>
<property name="text">
<string>115200</string>
</property>
</item>
</widget>
<widget class="QPushButton" name="disconnectButton">
<property name="geometry">
<rect>
<x>260</x>
<y>190</y>
<width>93</width>
<height>31</height>
</rect>
</property>
<property name="text">
<string>断开连接</string>
</property>
</widget>
<widget class="QPushButton" name="connectButton">
<property name="geometry">
<rect>
<x>110</x>
<y>190</y>
<width>93</width>
<height>31</height>
</rect>
</property>
<property name="text">
<string>连接</string>
</property>
</widget>
<widget class="QComboBox" name="serialPortCombox">
<property name="geometry">
<rect>
<x>120</x>
<y>30</y>
<width>231</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<family>楷体</family>
<pointsize>10</pointsize>
</font>
</property>
</widget>
<widget class="QLabel" name="label_2">
<property name="geometry">
<rect>
<x>30</x>
<y>30</y>
<width>85</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<family>楷体</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="text">
<string>通信接口:</string>
</property>
</widget>
<widget class="QLabel" name="label_6">
<property name="geometry">
<rect>
<x>210</x>
<y>70</y>
<width>68</width>
<height>23</height>
</rect>
</property>
<property name="font">
<font>
<family>楷体</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="text">
<string>数据位:</string>
</property>
</widget>
<widget class="QLabel" name="label_3">
<property name="geometry">
<rect>
<x>30</x>
<y>80</y>
<width>85</width>
<height>23</height>
</rect>
</property>
<property name="font">
<font>
<family>楷体</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="text">
<string>奇偶校验:</string>
</property>
</widget>
<widget class="QLabel" name="label_7">
<property name="geometry">
<rect>
<x>210</x>
<y>110</y>
<width>68</width>
<height>23</height>
</rect>
</property>
<property name="font">
<font>
<family>楷体</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="text">
<string>停止位:</string>
</property>
</widget>
<widget class="QLineEdit" name="timeoutEdit">
<property name="geometry">
<rect>
<x>120</x>
<y>150</y>
<width>71</width>
<height>21</height>
</rect>
</property>
<property name="font">
<font>
<family>黑体</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="maxLength">
<number>5000</number>
</property>
</widget>
<widget class="QLineEdit" name="reissueEdit">
<property name="geometry">
<rect>
<x>280</x>
<y>150</y>
<width>71</width>
<height>21</height>
</rect>
</property>
<property name="font">
<font>
<family>黑体</family>
<pointsize>9</pointsize>
</font>
</property>
<property name="maxLength">
<number>10</number>
</property>
</widget>
<widget class="QLabel" name="label_9">
<property name="geometry">
<rect>
<x>0</x>
<y>150</y>
<width>121</width>
<height>23</height>
</rect>
</property>
<property name="font">
<font>
<family>楷体</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="text">
<string>超时时间(ms):</string>
</property>
</widget>
<widget class="QLabel" name="label_11">
<property name="geometry">
<rect>
<x>190</x>
<y>150</y>
<width>101</width>
<height>23</height>
</rect>
</property>
<property name="font">
<font>
<family>楷体</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="text">
<string>重发次数:</string>
</property>
</widget>
</widget>
<widget class="QGroupBox" name="groupBox_2">
<property name="geometry">
<rect>
<x>400</x>
<y>0</y>
<width>401</width>
<height>301</height>
</rect>
</property>
<property name="title">
<string/>
</property>
<widget class="QListWidget" name="historyListWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>40</y>
<width>391</width>
<height>261</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>10</pointsize>
</font>
</property>
</widget>
<widget class="QLabel" name="recv_label">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>81</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<family>楷体</family>
<pointsize>12</pointsize>
</font>
</property>
<property name="text">
<string>通信历史</string>
</property>
</widget>
<widget class="QPushButton" name="exportHistoryButton">
<property name="geometry">
<rect>
<x>300</x>
<y>10</y>
<width>93</width>
<height>31</height>
</rect>
</property>
<property name="text">
<string>导出历史</string>
</property>
</widget>
</widget>
<zorder>groupBox</zorder>
<zorder>groupBox_3</zorder>
<zorder>groupBox_4</zorder>
<zorder>groupBox_2</zorder>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>815</width>
<height>26</height>
</rect>
</property>
<widget class="QMenu" name="menu">
<property name="title">
<string>主站界面</string>
</property>
</widget>
<addaction name="menu"/>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>

+ 178
- 0
Modbus/include/modbus_master.h View File

@@ -0,0 +1,178 @@
/***********************************************************************
* Copyright (C) 2025-, XINJE Co., Ltd.
*
* File Name: modbus_master.h
* Description: Modbus主站的ModbusRTUMaster头文件,主要负责四种请求帧的生成:(01)(03)(0F)(10)、响应帧的解析。
* Others:
* Version: v1.0
* Author: weikai XINJE
* Date: 2025-7-30
***********************************************************************/



#ifndef MODBUS_RTU_MASTER_H
#define MODBUS_RTU_MASTER_H

#include <QObject>
#include <QByteArray>
#include <QVector>
#include <QHash>

const int TYPE_REGISTERS = 1;
const int TYPE_COILS = 2;

/**********************************************************************
* Iterates over the contents of a ModbusRTUMaster.
*ModbusRTUMaster:
*提供Modbus RTU协议的主站功能,支持生成各种Modbus RTU请求帧,
*以及解析从站返回的响应帧。所有协议相关参数均作为类成员变量,可通过setter方法进行设置。
***********************************************************************/
class ModbusRTUMaster : public QObject
{
Q_OBJECT
public:
//构造函数
explicit ModbusRTUMaster(QObject *parent = nullptr);

//功能码枚举 - 定义Modbus协议支持的功能码
enum FunctionCode
{
READ_COILS = 0x01, // 读线圈状态
READ_HOLDING_REGISTERS = 0x03, // 读保持寄存器
WRITE_MULTIPLE_COILS = 0x0F, // 写多个线圈
WRITE_MULTIPLE_REGISTERS = 0x10 // 写多个保持寄存器
};

//错误码枚举 - 定义Modbus协议可能返回的错误码
enum ErrorCode
{
NO_ERROR = 0x00, // 无错误
ILLEGAL_FUNCTION = 0x01, // 非法功能 - 从站不支持该功能码
ILLEGAL_DATA_ADDRESS = 0x02, // 非法数据地址 - 从站不支持该地址
ILLEGAL_DATA_VALUE = 0x03, // 非法数据值 - 数据值超出范围
SERVER_DEVICE_FAILURE = 0x04,// 从站设备故障
ACKNOWLEDGE = 0x05, // 确认 - 请求已接收但未处理
SERVER_DEVICE_BUSY = 0x06, // 从站设备忙 - 无法处理请求
MEMORY_PARITY_ERROR = 0x08 // 内存奇偶性错误
};


//设置从站地址
void setSlaveAddr(quint8 slaveAddr) { slaveAddr_ = slaveAddr; }

//设置功能码
void setFuncCode(FunctionCode funcCode) { funcCode_ = funcCode; }

//设置起始地址
void setStartAddr(quint16 startAddr) { startAddr_ = startAddr; }

//设置读取数量
void setReadCount(quint16 readCount) { readCount_ = readCount; }

//设置线圈数据
void setCoils(const QVector<bool>& coils) { coils_ = coils; }

//设置寄存器数据
void setRegisters(const QVector<quint16>& registers) { registers_ = registers; }

//提取错误码对应描述
QString getErrorDescription(int errorCode);

//获取起始地址
quint16 getStartAddr() const;

//生成读线圈请求帧 (功能码01)
QByteArray createReadCoilsFrame();

//生成读保持寄存器请求帧 (功能码03)
QByteArray createReadHoldingRegistersFrame();

//生成写多个线圈请求帧 (功能码0F)
QByteArray createWriteMultipleCoilsFrame();

//生成写多个保持寄存器请求帧 (功能码10)
QByteArray createWriteMultipleRegistersFrame();

/***********************************************************************
*@brief: 处理接收到的数据流并解析完整帧
*@param: data 接收到的原始数据
*@param: slaveAddr 输出参数,从响应中解析出的从站地址
*@param: funcCode 输出参数,从响应中解析出的功能码
*@param: parsedData 输出参数,解析后的数据
*@param: errorCode 输出参数,错误码(NoError表示成功)
*@param: extractedFrame 输出参数,提取到的完整帧
*@return:是否成功解析
*@note: 提取到帧后会进行解析
***********************************************************************/
bool processReceivedData(const QByteArray& data, quint8& slaveAddr, quint8& funcCode,
QVector<quint16>& parsedData, quint8& errorCode,QByteArray& extractedFrame,bool& isComFrame);
public:
/***********************************************************************
*@brief: 创建读请求帧
*@param: frame 输出参数 请求帧
*@param: type 请求类型
***********************************************************************/
void buildReadFrame(QByteArray& frame,FunctionCode funcCode);

/***********************************************************************
*@brief: 创建部分写请求帧
*@param: frame 输出参数 请求帧
*@param: type 请求类型
***********************************************************************/
void buildWriteFrame(QByteArray& frame,FunctionCode funcCode);

/***********************************************************************
*@brief: 解析读线圈响应数据
*@param: responseData 响应中的数据部分
*@param: coils 输出参数(bool类型的数组),解析出的线圈状态
*@return:是否成功解析
***********************************************************************/
bool parseReadCoilsResponse(const QByteArray& responseData, QVector<bool>& coils);

/***********************************************************************
*@brief: 解析读保持寄存器响应数据
*@param: responseData 响应中的数据部分
*@param: registers 输出参数(quint16类型的数组),解析出的寄存器值
*@return:是否成功解析
***********************************************************************/
bool parseReadHoldingRegistersResponse(const QByteArray& responseData, QVector<quint16>& registers);

/***********************************************************************
*@brief: 解析单帧响应
*@param: response 从站返回的响应数据
*@param: slaveAddr 输出参数,从响应中解析出的从站地址
*@param: funcCode 输出参数,从响应中解析出的功能码
*@param: data 输出参数,解析后的数据
*@param: errorCode 输出参数,错误码(NoError表示成功)
*@return:是否成功解析
***********************************************************************/
bool parseResponse(const QByteArray& response, quint8& slaveAddr, quint8& funcCode,
QVector<quint16>& data, quint8& errorCode);

/***********************************************************************
*@brief: 从缓冲区提取完整帧
*@param: frame 输出参数,提取的完整帧
*@return:是否成功提取到一帧
***********************************************************************/
bool extractFrame(QByteArray& frame,bool& isComFrame);

//计算CRC16校验值
quint16 calculateCRC(const QByteArray& data);

//验证帧的CRC16校验
bool verifyCRC(const QByteArray& frame);

//Modbus RTU协议参数
quint8 slaveAddr_; // 从站地址
FunctionCode funcCode_; // 功能码
quint16 startAddr_; // 起始地址
quint16 readCount_; // 读取数量
QVector<bool> coils_; // 线圈数据(写入)
QVector<quint16> registers_; // 寄存器数据(写入)
//其他
QHash<int,QString> errMessage; //错误码描述映射表
QByteArray buffer_; // 数据缓冲区
};

#endif // MODBUS_RTU_MASTER_H

+ 117
- 0
Modbus/include/serial_communication.h View File

@@ -0,0 +1,117 @@
/***********************************************************************
* Copyright (C) 2025-, XINJE Co., Ltd.
*
* File Name: serial_communication.h
* Description: Modbus主站的串口通信头文件
* Others:
* Version: v1.0
* Author: weikai XINJE
* Date: 2025-7-30
***********************************************************************/

#ifndef SERIALCOMMUNICATOR_H
#define SERIALCOMMUNICATOR_H

#include <QObject>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QByteArray>
#include <QString>
#include"timeout_handler.h"

/**********************************************************************
* Iterates over the contents of a SerialCommunicator.
*SerialCommunicator
*提供串口通信功能,连接,断开连接,发送数据,接收数据处理等
*调用TimeoutHandler实例进行超时处理
***********************************************************************/
class SerialCommunicator : public QObject
{
Q_OBJECT
public:
// 构造函数
explicit SerialCommunicator(QObject *parent = nullptr);
~SerialCommunicator();

// 参数设置接口
void setPortName(const QString &portName);
void setBaudRate(int baudRate);
void setDataBits(QSerialPort::DataBits dataBits);
void setStopBits(QSerialPort::StopBits stopBits);
void setParity(QSerialPort::Parity parity);

//设置是否可以开始心跳
void setIsCanHeart(bool flag) {isCanHeartbeat_ = flag;}
void setHeartbeatDFrame(const QByteArray& frame){ heartbeatFrame_ = frame; }

// 初始化函数(设置默认参数)
void init();

// 连接/断开接口
bool connectDevice();
void disconnectDevice();

// 设置超时参数
bool setTimeoutSettings(int timeoutMs, int maxRetries);
//获取
void getTimeoutSettings(int& timeoutMs,int& maxRetries);
// 发送数据接口
bool sendData(const QByteArray &data);

// 状态查询接口
bool isConnected() const;
//获取可用端口列表
QStringList getAvailablePorts();

signals:
// 数据接收信号(发送处理后的十六进制字符串)
void dataReceived(const QString &hexData);
// 状态通知信号
void statusChanged(const QString &status);
// 错误通知信号
void errorOccurred(const QString &errorMsg);
// 连接断开信号
void connectionDisconnected();

private:
// 内部数据接收处理
void onReadyRead();
// 处理超时信号
void onTimeoutOccurred(int currentRetry);
// 处理最大重试次数达到
void onMaxRetriesReached();
//处理串口错误
void onSerialError(QSerialPort::SerialPortError error);

//发送心跳槽函数
void onSendHeartbeat();
//心跳超时槽函数
void onHeartbeatTimeout();
//启动心跳机制
void startHeartbeat();
//停止心跳机制
void stopHeartbeat();

QSerialPort *serialPort_;// 串口对象
// 串口参数
QString portName_;//串口名
int baudRate_;//波特率
QSerialPort::DataBits dataBits_;//数据位
QSerialPort::StopBits stopBits_;//停止位
QSerialPort::Parity parity_;//奇偶校验

bool connected_;// 连接状态

TimeoutHandler timeoutHandler_; // 超时处理器
QByteArray pendingData_; // 等待响应的数据(用于重发)

//断线重连
QTimer* heartbeatTimer_;//定期发送心跳帧
QTimer* heartbeatTimeoutTimer_;//心跳响应计时器
int heartbeatInterval_;//心跳间隔
int heartbeatTimeout_;//心跳响应时间
QByteArray heartbeatFrame_;//心跳帧
bool isCanHeartbeat_;//是否可以开始心跳
};

#endif // SERIALCOMMUNICATOR_H

+ 73
- 0
Modbus/include/timeout_handler.h View File

@@ -0,0 +1,73 @@
/***********************************************************************
* Copyright (C) 2025-, XINJE Co., Ltd.
*
* File Name: timeout_handler.h
* Description: Modbus主站的超时处理头文件
* Others:
* Version: v1.0
* Author: weikai XINJE
* Date: 2025-7-30
***********************************************************************/

#ifndef TIMEOUT_HANDLER_H
#define TIMEOUT_HANDLER_H
#include <QObject>
#include <QTimer>

/**********************************************************************
* Iterates over the contents of a TimeoutHandler.
*TimeoutHandler
*提供超时处理功能,若超过时间未收到响应,则触发超时重发机制
*被SerialCommunicator调用使用
***********************************************************************/
class TimeoutHandler : public QObject
{
Q_OBJECT
public:
explicit TimeoutHandler(QObject *parent = nullptr);

// 获取当前重试次数
int getCurrentRetryCount() const
{
return currentRetryCount_;
}

// 设置超时时间(毫秒)
void setTimeoutInterval(int msec);

// 获取当前超时时间(毫秒)
int getTimeoutInterval() const;

// 设置最大重试次数
void setRetryCount(int count);

// 获取当前设置的重试次数
int getRetryCount() const;

// 启动超时计时(若已在运行则重启)
void start();

// 停止超时计时并重置重试计数
void stop();

// 判断是否正在计时中
bool isRunning() const;

signals:
// 超时信号(参数为当前重试次数)
void timeoutOccurred(int retryCount);

// 达到最大重试次数信号
void maxRetriesReached();

private:
// 处理定时器超时
void onTimerTimeout();

QTimer *timer_; // 定时器
int timeoutInterval_; // 超时时间(毫秒)
int maxRetryCount_; // 最大重试次数
int currentRetryCount_; // 当前重试次数
};

#endif // TIMEOUT_HANDLER_H

+ 11
- 0
Modbus/src/main.cpp View File

@@ -0,0 +1,11 @@
#include "mainwindow.h"

#include <QApplication>

int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}

+ 613
- 0
Modbus/src/mainwindow.cpp View File

@@ -0,0 +1,613 @@
/***********************************************************************
* Copyright (C) 2025-, XINJE Co., Ltd.
*
* File Name: // mainwindow.cpp
* Description: // ModbusRTU主站上位机人机交互界面源文件
* //调用SerialCommunicator实现串口通信,调用ModbusRTUMaster实现帧的生成与解析。
* Others: //
* Version: // v1.0
* Author: // weikai,XINJE
* Date: // 2025-7-30
***********************************************************************/

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QStatusBar>
#include <QTextCursor>
#include <QMessageBox>
#include <QDebug>
#include <QFileDialog>
#include <QFile>

MainWindow::MainWindow(QWidget *parent)
:QMainWindow(parent),
ui_(new Ui::MainWindow),
serialComm_(new SerialCommunicator(this)),
modbusMaster_(new ModbusRTUMaster(this)),
serialPortTimer_(new QTimer(this))
{
ui_->setupUi(this);
statusBar()->showMessage("就绪");

// 添加UI控件显式关联
connect(ui_->connectButton, &QPushButton::clicked,this, &MainWindow::onConnectButtonClicked);//连接按钮关联槽函数
connect(ui_->disconnectButton, &QPushButton::clicked,this, &MainWindow::onDisconnectButtonClicked);//断开连接按钮关联槽函数
connect(ui_->readButton, &QPushButton::clicked,this, &MainWindow::onReadButtonClicked);//读取按钮关联槽函数
connect(ui_->writeButton, &QPushButton::clicked,this, &MainWindow::onWriteButtonClicked);//写入按钮关联槽函数
connect(ui_->clearLogButton, &QPushButton::clicked,this, &MainWindow::onClearLogButtonClicked);//清空日志按钮关联槽函数
connect(ui_->exportHistoryButton, &QPushButton::clicked,this, &MainWindow::onExportHistoryButtonClicked);//导出通信历史按钮


// 连接串口通信信号
connect(serialComm_, &SerialCommunicator::dataReceived,this, &MainWindow::onSerialDataReceived);//串口接收到数据关联槽函数
connect(serialComm_, &SerialCommunicator::statusChanged,this, &MainWindow::onSerialStatusChanged);//状态变化信号关联槽函数
connect(serialComm_, &SerialCommunicator::errorOccurred,this, &MainWindow::onSerialErrorOccurred);//错误发生信号关联槽函数
connect(serialComm_, &SerialCommunicator::connectionDisconnected,this, &MainWindow::onConnectionDisconnected);//连接断开信号关联槽函数

//设置定时器,每2秒刷新一次串口列表
connect(serialPortTimer_, &QTimer::timeout, this, &MainWindow::onRefreshSerialPorts);//定时信号关联刷新串口槽函数
serialPortTimer_->start(2000); // 每2000毫秒(2秒)检查一次

init();//初始化

refreshSerialPorts();//刷新串口列表

logToBox("程序启动,就绪");// 启动日志
}

/***********************************************************************
* 析构函数
***********************************************************************/
MainWindow::~MainWindow()
{
delete ui_;
}

/***********************************************************************
* 初始化各个组件,如数据位,停止位等
***********************************************************************/
void MainWindow::init()
{
// 初始化日志框组件
ui_->logTextEdit->setReadOnly(true);//设置只读
ui_->logTextEdit->setLineWrapMode(QTextEdit::WidgetWidth);//设置自动换行
ui_->logTextEdit->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);//设置垂直滚动条常显

// 初始化数据位
ui_->dataComboBox->addItem("7", QSerialPort::Data7);
ui_->dataComboBox->addItem("8", QSerialPort::Data8);
ui_->dataComboBox->setCurrentIndex(1);

//初始化停止位
ui_->stopComboBox->addItem("1", QSerialPort::OneStop);
ui_->stopComboBox->addItem("1.5", QSerialPort::OneAndHalfStop);
ui_->stopComboBox->addItem("2", QSerialPort::TwoStop);
ui_->stopComboBox->setCurrentIndex(0);

//初始化校验位下拉框
ui_->verifyComboBox->addItem("无", QSerialPort::NoParity);
ui_->verifyComboBox->addItem("奇校验", QSerialPort::OddParity);
ui_->verifyComboBox->addItem("偶校验", QSerialPort::EvenParity);
ui_->verifyComboBox->setCurrentIndex(0);

//初始化波特率
ui_->baudComboBox->setCurrentIndex(1);

//创建心跳帧
modbusMaster_->setSlaveAddr(1);
modbusMaster_->setFuncCode(ModbusRTUMaster::READ_COILS);
modbusMaster_->setStartAddr(256);
modbusMaster_->setReadCount(1);
QByteArray frame = modbusMaster_ ->createReadCoilsFrame();
//qDebug() << "文件:" << __FILE__ << "行:" << __LINE__ << "心跳帧frame:"<< frame;
serialComm_->setHeartbeatDFrame(frame);
}

/***********************************************************************
* 导出历史记录按钮,点击触发,打开文件保存对话框,打开文件并写入。
***********************************************************************/
void MainWindow::onExportHistoryButtonClicked()
{
// 默认文件名:当前日期时间(格式为yyyyMMdd_HHmmss)加上"_history.txt"组成
QString defaultFileName = QDateTime::currentDateTime().toString("yyyyMMdd_HHmmss") + "_history.txt";

// 打开文件保存对话框,让用户选择保存路径和文件名
QString fileName = QFileDialog::getSaveFileName(
this,
"导出历史记录",
defaultFileName,
"文本文件 (*.txt);"
);

// 判断用户是否取消了文件选择
if (fileName.isEmpty())
{
//将导出取消输出至状态栏与日志框
statusBar()->showMessage("导出取消", 2000);
logToBox("导出历史记录取消");
return;
}

// 根据用户选择的文件名创建QFile对象,用于文件操作
QFile file(fileName);

//文件打开失败
if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
{
statusBar()->showMessage("无法打开文件进行写入", 2000); // 将具体错误信息记录到日志框
logToBox("错误: 无法打开文件 " + fileName);
return;
}

// 创建文本流对象并关联
QTextStream out(&file);

// 遍历历史记录列表中的所有项,将内容写入文件
for (int i = 0; i < ui_->historyListWidget->count(); ++i)
{
out << ui_->historyListWidget->item(i)->text() << "\n\n";
}

file.close();

statusBar()->showMessage("历史记录已导出到 " + fileName, 3000);
logToBox("历史记录已导出到 " + fileName);
}

/***********************************************************************
* 连接按钮,点击触发。获取界面通信配置参数,与对应串口建立连接
***********************************************************************/
void MainWindow::onConnectButtonClicked()
{
// 获取UI参数:串口名、波特率、数据位、停止位、校验选项
QString currentText = ui_->serialPortCombox->currentText();
QString portName = currentText.split("(").first();

int baudRate = ui_->baudComboBox->currentText().toUInt();
QSerialPort::DataBits dataBits = static_cast<QSerialPort::DataBits>(ui_->dataComboBox->currentData().toUInt());
QSerialPort::StopBits stopBits = static_cast<QSerialPort::StopBits>(ui_->stopComboBox->currentData().toUInt());
QSerialPort::Parity parity = static_cast<QSerialPort::Parity>(ui_->verifyComboBox->currentData().toUInt());

// 向serialComm设置串口参数
serialComm_->setPortName(portName);
serialComm_->setBaudRate(baudRate);
serialComm_->setDataBits(dataBits);
serialComm_->setStopBits(stopBits);
serialComm_->setParity(parity);

//设置超时时间与最大重发次数
int timeout=DEFAULT_TIMEOUT,maxRetries=DEFAULT_MAXRETRISE;
if(!ui_->timeoutEdit->text().isEmpty())
{
bool ok;
timeout = ui_->timeoutEdit->text().toUInt(&ok);
if(!ok)
{
logToBox("超时时间输入有误,请检查!");
}
}
if(!ui_->reissueEdit->text().isEmpty())
{
bool ok;
maxRetries = ui_->reissueEdit->text().toUInt(&ok);
if(!ok)
{
logToBox("最大重发次数输入有误,请检查!");
}
}

if(!serialComm_->setTimeoutSettings(timeout,maxRetries))
{
QMessageBox::warning(
this,
"超时参数错误!",
"请重新输入,超时时间500~5000ms,最大重发次数1~5次",
QMessageBox::Ok
);
return;
}
// 尝试连接
if (serialComm_->connectDevice())//连接成功
{
ui_->connectButton->setEnabled(false);
ui_->disconnectButton->setEnabled(true);

ui_->timeoutEdit->setText(QString::number(timeout));
ui_->reissueEdit->setText(QString::number(maxRetries));
}
else
{
logToBox(QString(portName + "串口连接失败\n"));
}
}

/***********************************************************************
* 断开按钮,点击触发。更新连接与断开连接按钮状态,并调用serialComm断开连接函数
***********************************************************************/
void MainWindow::onDisconnectButtonClicked()
{
serialComm_->disconnectDevice();
ui_->connectButton->setEnabled(true);
ui_->disconnectButton->setEnabled(false);
}

/***********************************************************************
* 清空日志按钮,点击触发。清空日志框
***********************************************************************/
void MainWindow::onClearLogButtonClicked()
{
ui_->logTextEdit->clear();
logToBox("日志已清空");
}

/***********************************************************************
* 读取按钮,点击触发。触发后读取界面参数,根据功能码选择请求帧类型,创建帧并发送
***********************************************************************/
void MainWindow::onReadButtonClicked()
{
if (!serialComm_->isConnected())
{
statusBar()->showMessage("请先连接设备", 2000);
return;
}

// 从UI获取参数
int slaveAddress = ui_->stationAddrSpinBox->value();
QString funcText = ui_->functionComboBox->currentText();
int startAddress = ui_->startAddrSpinBox->value();
int readNum = ui_->numberSpinBox->value();

// 设置Modbus参数
modbusMaster_->setSlaveAddr(slaveAddress);
modbusMaster_->setStartAddr(startAddress);

QByteArray frame;
// 根据功能码生成请求帧
if ("01读线圈" == funcText)
{
modbusMaster_->setFuncCode(ModbusRTUMaster::READ_COILS);
modbusMaster_->setReadCount(readNum);
frame = modbusMaster_->createReadCoilsFrame();
}
else if ("03读寄存器" == funcText)
{
modbusMaster_->setFuncCode(ModbusRTUMaster::READ_HOLDING_REGISTERS);
modbusMaster_->setReadCount(readNum);
frame = modbusMaster_->createReadHoldingRegistersFrame();
}
else
{
statusBar()->showMessage("请选择读取功能码", 2000);
return;
}

// 发送帧
if (!frame.isEmpty())
{
serialComm_->sendData(frame);
logToBox("发送读取请求完成!");
QString timeStamp = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss");
ui_->historyListWidget->addItem("[" + timeStamp + "]" + "发送: " + frame.toHex(' ').toUpper());
}
else
{
statusBar()->showMessage("生成请求帧失败", 2000);
}

//读取请求发送成功弹窗
QMessageBox::information(
this,
"读取请求",
"读取请求发送完成!",
QMessageBox::Ok
);
}

/***********************************************************************
* 写入按钮,点击触发。触发后读取界面参数,根据功能码选择请求帧类型,创建帧并发送
***********************************************************************/
void MainWindow::onWriteButtonClicked()
{
if (!serialComm_->isConnected())
{
statusBar()->showMessage("请先连接设备", 2000);
return;
}

// 从UI获取参数
int slaveAddress = ui_->stationAddrSpinBox->value();
QString funcText = ui_->functionComboBox->currentText();
int startAddress = ui_->startAddrSpinBox->value();
QString dataText = ui_->writeTextEdit->toPlainText().trimmed();

if (dataText.isEmpty())
{
statusBar()->showMessage("请输入写入数据", 2000);
return;
}

// 设置Modbus参数
modbusMaster_->setSlaveAddr(slaveAddress);
modbusMaster_->setStartAddr(startAddress);

QByteArray frame;
// 根据功能码生成请求帧
if ("0F写线圈" == funcText)
{
// 解析线圈数据(二进制字符串)
QVector<bool> coils;
for (QChar c : dataText)
{
if ('0' == c)
{
coils.append(false);
}
else if ('1' == c)
{
coils.append(true);
}
else
{
statusBar()->showMessage("写线圈需输入二进制数据(仅0/1),请重新输入", 2000);
return;
}
}
modbusMaster_->setFuncCode(ModbusRTUMaster::WRITE_MULTIPLE_COILS);
modbusMaster_->setCoils(coils);
frame = modbusMaster_->createWriteMultipleCoilsFrame();
}
else if ("10写寄存器" == funcText)
{
// 解析寄存器数据(逗号分隔的整数)
QStringList dataList = dataText.split(',');
QVector<quint16> registers;
for (const QString &d : dataList)
{
bool flag;
quint16 data = d.toUInt(&flag);;
if (!flag)
{
statusBar()->showMessage("写线圈需输入整数,请重新输入)", 2000);
return;
}
registers.append(data);
}
modbusMaster_->setFuncCode(ModbusRTUMaster::WRITE_MULTIPLE_REGISTERS);
modbusMaster_->setRegisters(registers);
frame = modbusMaster_->createWriteMultipleRegistersFrame();
}
else
{
statusBar()->showMessage("请选择写入功能码", 2000);
return;
}

// 发送帧
if (!frame.isEmpty())
{
serialComm_->sendData(frame);
// 将帧转换为带空格分隔的十六进制大写字符串
QString hexStr = frame.toHex(' ').toUpper();

logToBox("发送请求帧");
QString timeStamp = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss");
ui_->historyListWidget->addItem("[" + timeStamp + "]" +"发送: " + hexStr);

QMessageBox::information(
this,
"写入请求",
"写入请求发送完成!",
QMessageBox::Ok
);
}
else
{
statusBar()->showMessage("生成请求帧失败", 2000);
}
}

/***********************************************************************
* 收到串口刷新定时器信号的槽函数,调用刷新串口函数
***********************************************************************/
void MainWindow::onRefreshSerialPorts()
{
refreshSerialPorts();
}

/***********************************************************************
* 收到连接已断开信号的槽函数,更新按钮状态并弹出警告弹窗告知用户。
***********************************************************************/
void MainWindow::onConnectionDisconnected()
{
// 更新UI按钮状态
ui_->connectButton->setEnabled(true);
ui_->disconnectButton->setEnabled(false);

logToBox("连接已断开");

QMessageBox::warning(
this,
"连接断开",
"连接已断开!",
QMessageBox::Ok
);
}

/***********************************************************************
* 收到串口状态变化信号的槽函数,并将状态变化输出至底部状态栏与日志框。
***********************************************************************/
void MainWindow::onSerialStatusChanged(const QString &status)
{
statusBar()->showMessage(status, 3000);
logToBox(status);
}

/***********************************************************************
* 收到串口错误信号的槽函数,并将错误输出至底部状态栏与日志框。
***********************************************************************/
void MainWindow::onSerialErrorOccurred(const QString &error)
{
statusBar()->showMessage(error, 3000);
logToBox("错误: " + error);
}

/***********************************************************************
* 收到数据信号触发
* 处理串口接收数据的逻辑,提取完整帧后解析并展示,data为串口收到的原始数据。
***********************************************************************/
void MainWindow::onSerialDataReceived(const QString &data)
{

QByteArray response = QByteArray::fromHex(data.toUtf8());
quint8 slaveAddr, funcCode, errorCode;
QVector<quint16> parsedData;//解析后的数据
QByteArray extractedFrame; // 提取的完整帧
bool isComFrame = true;//是否解析到完整帧

// 将数据传递到下层处理解析
if (modbusMaster_->processReceivedData(response, slaveAddr, funcCode, parsedData, errorCode, extractedFrame,isComFrame))
{
serialComm_->setIsCanHeart(true);
//解析成功,获取到帧的各部分,根据格式显示在日志框
QString hexData = extractedFrame.toHex(' ').toUpper().trimmed();
QString timeStamp = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss");
QString display = QString("[%1]接收原始数据: %2\n").arg(timeStamp).arg(hexData);
display += QString("解析结果:\n从站地址: " + QString::number(slaveAddr) + "\n功能码: 0x" + QString::number(funcCode,16).rightJustified(2,'0'));

//对解析成功后的数据进行打印
if (ModbusRTUMaster::NO_ERROR == errorCode)
{
int startAddr = modbusMaster_->getStartAddr();
switch (funcCode)
{
case ModbusRTUMaster::READ_COILS:
{
display += " 类型: 读线圈 (01)\n数据: ";
QString datas;
for (int i = 0; i < parsedData.size(); ++i)
{
if(0 == i%10)
{
datas += "\n";
}
datas += QString("线圈%1=%2 ").arg(i + startAddr).arg(parsedData[i] ? "ON" : "OFF");
}
display += datas;
break;
}
case ModbusRTUMaster::READ_HOLDING_REGISTERS:
{
display += " 类型: 读保持寄存器 (03)\n数据: ";
QString datas;
for (int i = 0; i < parsedData.size(); ++i)
{
if(0 == i%10)
{
datas += "\n";
}
datas += QString("寄存器%1=%2 ").arg(i + startAddr).arg(parsedData[i]);
}
display += datas;
break;
}
case ModbusRTUMaster::WRITE_MULTIPLE_COILS:
{
display += QString(" 类型: 写多个线圈 (0F)\n起始地址: %1\n写入线圈数量: %2")
.arg(parsedData[0])
.arg(parsedData[1]);
break;
}
case ModbusRTUMaster::WRITE_MULTIPLE_REGISTERS:
{
display += QString(" 类型: 写多个寄存器 (10)\n起始地址: %1\n写入寄存器数量: %2")
.arg(parsedData[0])
.arg(parsedData[1]);
break;
}
default:
display += "\n类型: 不支持的功能码";
break;
}
logToBox("接收并解析响应成功");
}
else
{
display += QString(" 类型: 错误响应\n错误码: 0x%1 错误描述: %2")
.arg(errorCode, 2, 16, QChar('0'))
.arg(modbusMaster_->getErrorDescription(errorCode));
logToBox(QString("接收错误响应: 错误码0x" + QString::number(errorCode,16).rightJustified(2,'0')));
}
ui_->historyListWidget->addItem(display); // 添加解析结果
ui_->historyListWidget->addItem("****************************************************************************************************");
}
else
{
if(isComFrame)
{
QString hexData = response.toHex(' ').toUpper().trimmed();
QString timeStamp = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss");
QString display = QString("[%1]\n接收原始数据:%2\n").arg(timeStamp).arg(hexData);
display += "解析失败";
ui_->historyListWidget->addItem(display); // 添加未解析的帧信息
}
}
}

/***********************************************************************
* 刷新串口列表
***********************************************************************/
void MainWindow::refreshSerialPorts()
{
// 获取当前已有的端口
QStringList oldPorts;
for (int i = 0; i < ui_->serialPortCombox->count(); ++i)
{
QString port = ui_->serialPortCombox->itemText(i);
if (port != "未检测到串口")
{
oldPorts << port;
}
}

// 获取新端口列表
QStringList newPorts = serialComm_->getAvailablePorts();

// 处理新增端口
for (const QString& port : newPorts)
{
if (!oldPorts.contains(port))
{
ui_->serialPortCombox->addItem(port);
logToBox("新增串口: " + port);
}
}

// 处理移除端口
for (const QString& port : oldPorts)
{
if (!newPorts.contains(port))
{
int index = ui_->serialPortCombox->findText(port);
if (index != -1)
{
ui_->serialPortCombox->removeItem(index);
logToBox("移除串口: " + port);
}
}
}
}

/***********************************************************************
* 日志输出函数。将message输出到日志框
***********************************************************************/
void MainWindow::logToBox(const QString &message)
{
QString timeStamp = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss");
QString logEntry = QString("[%1] %2\n").arg(timeStamp).arg(message);
ui_->logTextEdit->insertPlainText(logEntry);

// 自动滚动到底部
QTextCursor cursor = ui_->logTextEdit->textCursor();
cursor.movePosition(QTextCursor::End);
ui_->logTextEdit->setTextCursor(cursor);
}

+ 547
- 0
Modbus/src/modbus_master.cpp View File

@@ -0,0 +1,547 @@
/***********************************************************************
* Copyright (C) 2025-, XINJE Co., Ltd.
*
* File Name: // modbus_master.cpp
* Description: // ModbusRTU协议源文件,负责请求帧的生成,响应帧的解析
* Others: //
* Version: // v1.0
* Author: // weikai,XINJE
* Date: // 2025-7-30
***********************************************************************/
#include <QDebug>
#include "modbus_master.h"


ModbusRTUMaster::ModbusRTUMaster(QObject *parent)
: QObject(parent),
slaveAddr_(0),
funcCode_(READ_COILS),
startAddr_(0),
readCount_(0)
{
errMessage.insert(NO_ERROR,"无错误");
errMessage.insert(ILLEGAL_FUNCTION,"非法功能-从站不支持该功能码");
errMessage.insert(ILLEGAL_DATA_ADDRESS,"非法数据地址-从站不支持该地址");
errMessage.insert(ILLEGAL_DATA_VALUE,"非法数据值-数据值超出范围");
errMessage.insert(SERVER_DEVICE_FAILURE,"从站设备故障");
errMessage.insert(ACKNOWLEDGE,"确认-请求已接收但未处理");
errMessage.insert(SERVER_DEVICE_BUSY,"从站设备忙");
errMessage.insert(MEMORY_PARITY_ERROR,"内存奇偶校验错误");
}

/*************************************************************************************
* 获取起始地址
**************************************************************************************/
quint16 ModbusRTUMaster::getStartAddr() const
{
return startAddr_;
}

/*************************************************************************************
*获取错误码描述
**************************************************************************************/
QString ModbusRTUMaster::getErrorDescription(int errorCode)
{
QString resMessage;
if(errMessage.contains(errorCode))
{
resMessage = errMessage[errorCode];
}
else
{
resMessage = "未知错误";
}
return resMessage;
}

/*************************************************************************************
* 生成读请求帧 (功能码03)
*帧格式: [从站地址(1字节)] [功能码0x03(1字节)] [起始地址(2字节)] [读取数量(2字节)][CRC(2字节)]
**************************************************************************************/
void ModbusRTUMaster::buildReadFrame(QByteArray& frame,FunctionCode funcCode)
{
// 从站地址
frame.append(slaveAddr_);

// 功能码
frame.append(funcCode);

// 起始地址
frame.append((startAddr_ >> 8) & 0xFF);
frame.append(startAddr_ & 0xFF);

//读取数量
frame.append((readCount_ >> 8) & 0xFF);
frame.append(readCount_ & 0xFF);

// 计算并添加CRC校验 (2字节)
quint16 crc = calculateCRC(frame);
frame.append(crc & 0xFF); // CRC低字节在前
frame.append((crc >> 8) & 0xFF); // CRC高字节在后

}

/***********************************************************************
* 生成部分写请求帧 (功能码03)
*帧格式: [从站地址(1字节)] [功能码0x03(1字节)] [起始地址(2字节)]
***********************************************************************/
void ModbusRTUMaster::buildWriteFrame(QByteArray& frame,FunctionCode funcCode)
{

// 从站地址
frame.append(slaveAddr_);

// 功能码
frame.append(funcCode);

// 起始地址
frame.append((startAddr_ >> 8) & 0xFF);
frame.append(startAddr_ & 0xFF);
}

/***********************************************************************
* 生成读线圈请求帧 (功能码01)
* 帧格式: [从站地址(1字节)] [功能码0x01(1字节)] [起始地址(2字节)]
* [线圈数量(2字节)] [CRC校验(2字节)]
***********************************************************************/
QByteArray ModbusRTUMaster::createReadCoilsFrame()
{
QByteArray frame;
buildReadFrame(frame,READ_COILS);
return frame;
}

/***********************************************************************
* 生成读保持寄存器请求帧 (功能码03)
*帧格式: [从站地址(1字节)] [功能码0x03(1字节)] [起始地址(2字节)]
* [寄存器数量(2字节)] [CRC校验(2字节)]
***********************************************************************/
QByteArray ModbusRTUMaster::createReadHoldingRegistersFrame()
{
QByteArray frame;
buildReadFrame(frame,READ_HOLDING_REGISTERS);
return frame;
}

/***********************************************************************
* 生成写多个线圈请求帧 (功能码0F)
*帧格式: [从站地址(1字节)] [功能码0x0F(1字节)] [起始地址(2字节)]
* [线圈数量(2字节)] [字节数(1字节)] [线圈数据(n字节)] [CRC校验(2字节)]
***********************************************************************/
QByteArray ModbusRTUMaster::createWriteMultipleCoilsFrame()
{
// 检查线圈数据是否为空,为空则返回空
if (coils_.isEmpty())
{
return QByteArray();
}

QByteArray frame;
buildWriteFrame(frame,WRITE_MULTIPLE_COILS);

//线圈数量
quint16 coilCount = coils_.size();
frame.append((coilCount >> 8) & 0xFF);
frame.append(coilCount & 0xFF);

//字节数
quint8 byteCount = (coilCount + 7) / 8;
frame.append(byteCount);

//线圈数据
QByteArray coilData;
for (int currByte = 0; currByte < byteCount; ++currByte)
{
//当前字节
quint8 byte = 0;
for (int currBit = 0; currBit < 8; ++currBit)
{
//当前比特位
int index = currByte * 8 + currBit;
if (index < coilCount && coils_[index])
{
//当前比特位按位或
int mask = 1 << currBit;
byte |= mask;
}
}
coilData.append(byte);
}
frame.append(coilData);

// 计算并添加CRC校验 (2字节)
quint16 crc = calculateCRC(frame);
frame.append(crc & 0xFF);
frame.append((crc >> 8) & 0xFF);
return frame;
}

/***********************************************************************
*生成写多个保持寄存器请求帧 (功能码10)
*帧格式: [从站地址(1字节)] [功能码0x10(1字节)] [起始地址(2字节)]
* [寄存器数量(2字节)] [字节数(1字节)] [寄存器数据(2n字节)] [CRC校验(2字节)]
***********************************************************************/
QByteArray ModbusRTUMaster::createWriteMultipleRegistersFrame()
{
// 检查寄存器数据是否为空,为空则返回空
if (registers_.isEmpty())
{
return QByteArray();
}

QByteArray frame;
buildWriteFrame(frame,WRITE_MULTIPLE_REGISTERS);

//寄存器数量
quint16 regCount = registers_.size();
frame.append((regCount >> 8) & 0xFF);
frame.append(regCount & 0xFF);

// 字节数
quint8 byteCount = regCount * 2;
frame.append(byteCount);

// 寄存器数据
for (quint16 reg : registers_)
{
frame.append((reg >> 8) & 0xFF);
frame.append(reg & 0xFF);
}

// 计算并添加CRC校验
quint16 crc = calculateCRC(frame);
frame.append(crc & 0xFF);
frame.append((crc >> 8) & 0xFF);
return frame;
}

/***********************************************************************
*处理接收到的数据流
***********************************************************************/
bool ModbusRTUMaster::processReceivedData(const QByteArray& data, quint8& slaveAddr, quint8& funcCode,
QVector<quint16>& parsedData, quint8& errorCode, QByteArray& extractedFrame,bool& isComFrame)
{
buffer_.append(data);
//qDebug() <<"文件:"<<__FILE__ << "行数:" << __LINE__ << "内容:" << "接收数据追加到缓冲区,当前缓冲区大小:" << buffer_.size();

QByteArray frame;
bool frameExtracted = false;

// 对完整帧做处理
if (extractFrame(frame,isComFrame))
{
//qDebug() <<"文件:"<<__FILE__ << "行数:" << __LINE__ << " 完整帧: " << frame;
extractedFrame = frame; // 保存当前提取的完整帧
if (parseResponse(frame, slaveAddr, funcCode, parsedData, errorCode))
{
frameExtracted = true;
//qDebug() <<"文件:"<<__FILE__ << " 行数:" << __LINE__ << ": 帧解析成功,功能码:" << QString::number(funcCode, 16);
}
else
{
qDebug() <<"文件:"<<__FILE__ << "行数:" << __LINE__ << "内容:" << "帧解析失败";
}
}

return frameExtracted; // 返回是否提取并解析成功
}
/********************************************************************
* 从缓冲区中提取一个完整的Modbus RTU帧
********************************************************************/
bool ModbusRTUMaster::extractFrame(QByteArray& frame,bool& isComFrame)
{
// 检查缓冲区是否至少包含最小帧长度(异常帧5字节)
if (buffer_.size() < 5)
{
isComFrame = false;
return false;
}

// 遍历缓冲区,尝试提取有效帧
for (int i = 0; i <= buffer_.size() - 5; ++i)
{
// 确保有足够的字节读取功能码
if (i + 1 >= buffer_.size())
{
break;
}

// 获取功能码
quint8 funcCode = static_cast<quint8>(buffer_[i + 1]);
int expectedLen = 0; // 预期帧长度

// 判断是否为异常响应帧
if (funcCode & 0x80)
{
expectedLen = 5; //异常响应帧5字节
}
else
{
// 根据功能码计算预期帧长度
switch (funcCode)
{
case READ_COILS:
case READ_HOLDING_REGISTERS:
{
// 确保有足够的字节读取byteCount
if (i + 3 > buffer_.size())
{
return false; // 数据不足
}
int byteCount = static_cast<quint8>(buffer_[i + 2]);
expectedLen = 3 + byteCount + 2; // 地址(1) + 功能码(1) + 字节数(1) + 数据 + CRC(2)
break;
}
case WRITE_MULTIPLE_COILS:
case WRITE_MULTIPLE_REGISTERS:
{
expectedLen = 8; // 固定长度:地址(1) + 功能码(1) + 地址(2) + 数量(2) + CRC(2)
break;
}
default:
{
// 未知功能码,移除无效字节并继续
buffer_.remove(0, i + 1);
i = -1; // 重置索引
continue;
}
}
}

// 检查缓冲区是否包含完整帧
if (i + expectedLen > buffer_.size())
{
isComFrame = false;
return false; // 帧不完整
}

// 提取可能有效的帧并验证CRC
frame = buffer_.mid(i, expectedLen);
if (verifyCRC(frame))
{
//qDebug() <<"文件:"<<__FILE__ << "行数:" << __LINE__ << "内容:" << "提取到的完整帧:" << frame.toHex();
buffer_.remove(0, i + expectedLen); // 移除已处理的帧
return true; // 提取到完整帧
}
else
{
// CRC校验失败,移除无效字节
buffer_.remove(0, i + 1);
i = -1; // 重置索引
}
}
return false;
}

/********************************************************************
* 解析提取到的完整帧
********************************************************************/
bool ModbusRTUMaster::parseResponse(const QByteArray& response, quint8& slaveAddr, quint8& funcCode,
QVector<quint16>& data, quint8& errorCode)
{
// 响应数据至少需要包含:地址(1字节) + 功能码(1字节) + 数据(至少1字节) + CRC(2字节),共5字节
if (response.size() < 5)
{
return false;
}

// 验证响应数据的CRC校验是否正确
if (!verifyCRC(response))
{
return false;
}

// 提取从机地址
slaveAddr = static_cast<quint8>(response[0]);
// 提取功能码
funcCode = static_cast<quint8>(response[1]);

// 判断是否为错误响应
if (funcCode & 0x80)
{
// 错误响应格式:地址(1) + 错误功能码(1) + 错误码(1) + CRC(2),共5字节
// 提取错误码
errorCode = static_cast<quint8>(response[2]);
return true;
}

// 正常响应
errorCode = NO_ERROR;

// 提取数据部分(去除地址、功能码和CRC)
QByteArray responseData = response.mid(2, response.size() - 4);

// 根据功能码解析对应的数据
switch (funcCode)
{
case READ_COILS:
{
QVector<bool> coils;
// 解析读线圈响应数据
if (!parseReadCoilsResponse(responseData, coils))
{
return false;
}
// 将布尔线圈状态转换为16位整数
data.clear();
for (bool coil : coils)
{
int bit = 0;
if(coil) { bit=1; }
data.append(bit);
}
break;
}
case READ_HOLDING_REGISTERS:
{
// 解析读保持寄存器响应数据
if (!parseReadHoldingRegistersResponse(responseData, data))
{
return false;
}
break;
}
case WRITE_MULTIPLE_COILS: // 写多个线圈功能码
case WRITE_MULTIPLE_REGISTERS: // 写多个寄存器功能码
{
// 写多个线圈/寄存器的响应数据固定为4字节:起始地址(2字节) + 数量(2字节)
if (responseData.size() != 4)
{
return false;
}
// 解析起始地址
quint16 startAddr = (static_cast<quint8>(responseData[0]) << 8) | static_cast<quint8>(responseData[1]);
// 解析写入数量
quint16 count = (static_cast<quint8>(responseData[2]) << 8) | static_cast<quint8>(responseData[3]);
// 存入起始地址和数量
data.clear();
data.append(startAddr);
data.append(count);
break;
}
default: // 不支持的功能码
return false;
}

return true;
}

/********************************************************************
* 解析读线圈响应,[字节数][数据]
********************************************************************/
bool ModbusRTUMaster::parseReadCoilsResponse(const QByteArray& responseData, QVector<bool>& coils)
{
// 数据为空,解析失败
if (responseData.isEmpty())
{
return false;
}

quint8 byteCount = static_cast<quint8>(responseData[0]);
// 验证数据长度是否与字节计数匹配(字节计数 + 自身1字节)
if (responseData.size() != byteCount + 1)
{
return false;
}

coils.clear();
int parseCoils = readCount_;//需要解析的线圈数量
// 遍历每个字节
for (int currByte = 0; currByte < byteCount && parseCoils > 0; ++currByte)
{
// 获取当前字节的数值
quint8 byte = static_cast<quint8>(responseData[currByte + 1]);
// 计算当前字节需要解析的位数(不超过8位,且不超过剩余需要读取的线圈数)
int parseBits = qMin(8, parseCoils);
// 遍历每个位
for (int currBit = 0; currBit < parseBits; ++currBit)
{
//查看该位是0 or 1并添加
int mask = 1 << currBit;
bool bitIsSet = ((byte & mask) != 0);
coils.append(bitIsSet);
}
// 更新剩余需要读取的线圈数
parseCoils -= parseBits;
}
return true;
}

/********************************************************************
* 解析读寄存器响应,[字节数][数据]
********************************************************************/
bool ModbusRTUMaster::parseReadHoldingRegistersResponse(const QByteArray& responseData, QVector<quint16>& registers)
{
if (responseData.isEmpty())
{
qDebug() <<"文件:"<<__FILE__ << "行数:" << __LINE__ << "内容:" << "响应数据为空";
return false;
}

//字节数
quint8 byteCount = static_cast<quint8>(responseData[0]);
if (byteCount != readCount_ * 2 || responseData.size() != byteCount + 1)
{
return false;
}

//解析寄存器数据
registers.clear();
int regCount = byteCount / 2;
for (int i = 0; i < regCount; ++i)
{
if(2 + i * 2 < responseData.size())
{
//1寄存器数据 2字节
quint16 reg = static_cast<quint8>(responseData[1 + i * 2]) << 8 | static_cast<quint8>(responseData[2 + i * 2]);
registers.append(reg);
}
}

return true;
}

/********************************************************************
* 计算CRC校验值
********************************************************************/
quint16 ModbusRTUMaster::calculateCRC(const QByteArray &data)
{
quint16 crc = 0xFFFF;
for (int i = 0; i < data.size(); ++i)
{
crc ^= (quint8)data[i];
for (int j = 0; j < 8; ++j)
{
bool carry = crc & 0x0001;
crc >>= 1;
if (carry)
{
crc ^= 0xA001;
}
}
}
return crc;
}

/********************************************************************
* 验证响应帧校验码
********************************************************************/
bool ModbusRTUMaster::verifyCRC(const QByteArray& frame)
{
// 帧至少需要包含2字节CRC
if (frame.size() < 2)
{
return false;
}

// 提取帧中的CRC值(小端)
quint16 receivedCRC = (static_cast<quint8>(frame[frame.size() - 1]) << 8) |
static_cast<quint8>(frame[frame.size() - 2]);

// 计算数据部分的CRC
QByteArray data = frame.left(frame.size() - 2);
quint16 calculatedCRC = calculateCRC(data);

// 比较接收的CRC和计算的CRC
return (receivedCRC == calculatedCRC);
}

+ 371
- 0
Modbus/src/serial_communication.cpp View File

@@ -0,0 +1,371 @@
/***********************************************************************
* Copyright (C) 2025-, XINJE Co., Ltd.
*
* File Name: // serial_communication.cpp
* Description: // 主站串口通信模块源文件,负责串口的连接管理,数据收发管理
* //调用超时处理TimeoutHandler实现超时重传
* Others: //
* Version: // v1.0
* Author: // weikai,XINJE
* Date: // 2025-7-30
***********************************************************************/

#include "serial_communication.h"
#include <QDebug>

SerialCommunicator::SerialCommunicator(QObject *parent)
: QObject(parent),
serialPort_(new QSerialPort(this)),
connected_(false),
timeoutHandler_(this),
heartbeatTimer_(new QTimer(this)),
heartbeatTimeoutTimer_(new QTimer(this))
{
// 连接串口接收信号到内部处理槽
connect(serialPort_, &QSerialPort::readyRead, this, &SerialCommunicator::onReadyRead);//串口收到数据
//连接串口错误信号
connect(serialPort_, &QSerialPort::errorOccurred, this, &SerialCommunicator::onSerialError);//串口错误

// 连接超时处理器的信号
connect(&timeoutHandler_, &TimeoutHandler::timeoutOccurred,this, &SerialCommunicator::onTimeoutOccurred);//定时到达,触发超时重传
connect(&timeoutHandler_, &TimeoutHandler::maxRetriesReached,this, &SerialCommunicator::onMaxRetriesReached);//到达最大重发次数

//连接心跳
connect(heartbeatTimer_, &QTimer::timeout ,this, &SerialCommunicator::onSendHeartbeat);//心跳触发
connect(heartbeatTimeoutTimer_, &QTimer::timeout, this, &SerialCommunicator::onHeartbeatTimeout);//心跳超时
//初始化成员变量
init();
}

SerialCommunicator::~SerialCommunicator()
{
if (connected_)
{
serialPort_->close();
}
}

// 参数设置实现
void SerialCommunicator::setPortName(const QString &portName)
{
portName_ = portName;
}

void SerialCommunicator::setBaudRate(int baudRate)
{
baudRate_ = baudRate;
}

void SerialCommunicator::setDataBits(QSerialPort::DataBits dataBits)
{
dataBits_ = dataBits;
}

void SerialCommunicator::setStopBits(QSerialPort::StopBits stopBits)
{
stopBits_ = stopBits;
}

void SerialCommunicator::setParity(QSerialPort::Parity parity)
{
parity_ = parity;
}

/********************************************************************
* 初始化
********************************************************************/
void SerialCommunicator::init()
{
//串口参数
portName_ = "";
baudRate_ = 9600;
dataBits_ = QSerialPort::Data8;
stopBits_ = QSerialPort::OneStop;
parity_ = QSerialPort::NoParity;
connected_ = false;

//心跳参数
heartbeatInterval_ = 3000;
heartbeatTimeout_ = 1000;
isCanHeartbeat_ = true;
}

/********************************************************************
* 连接设备
********************************************************************/
bool SerialCommunicator::connectDevice()
{
if (connected_)
{
emit statusChanged("已处于连接状态");
return true;
}

// 检查端口名是否有效
if (portName_.isEmpty())
{
emit errorOccurred("未设置端口名");
return false;
}

// 配置串口参数
serialPort_->setPortName(portName_);
serialPort_->setBaudRate(baudRate_);
serialPort_->setDataBits(dataBits_);
serialPort_->setStopBits(stopBits_);
serialPort_->setParity(parity_);
serialPort_->setFlowControl(QSerialPort::NoFlowControl); // 默认无流控

// 尝试打开串口
if (serialPort_->open(QIODevice::ReadWrite))
{
connected_ = true;
emit statusChanged("连接成功: " + portName_);
qDebug()<<__FILE__<<__LINE__<<"心跳机制启动";
//启动心跳机制
startHeartbeat();
return true;
}
else
{
emit errorOccurred("连接失败: " + serialPort_->errorString());
return false;
}
}

/********************************************************************
* 断开连接
********************************************************************/
void SerialCommunicator::disconnectDevice()
{
if (!connected_) return;

serialPort_->close();
connected_ = false;
//停止心跳机制
stopHeartbeat();
emit connectionDisconnected();
}

/********************************************************************
* 启动心跳机制
********************************************************************/
void SerialCommunicator::startHeartbeat()
{
if(connected_)
{
//启动心跳
heartbeatTimer_->start(heartbeatInterval_);
//qDebug() << "文件:" << __FILE__ << "行:" << __LINE__ << "心跳机制启动" << heartbeatInterval_ << "ms";
}
}

/********************************************************************
* 停止心跳机制
********************************************************************/
void SerialCommunicator::stopHeartbeat()
{
heartbeatTimer_->stop();
heartbeatTimeoutTimer_->stop();
//qDebug() << "文件:" << __FILE__ << "行:" << __LINE__ << "心跳机制终止" << heartbeatInterval_ << "ms";
}

/********************************************************************
* 心跳触发,发送心跳帧
********************************************************************/
void SerialCommunicator::onSendHeartbeat()
{
if(false == isCanHeartbeat_)
return;

if(!connected_)
{
qDebug() << "文件:" << __FILE__ << "行:" << __LINE__ << "未连接,停止发送心跳帧";
return;
}

qint64 success = serialPort_->write(heartbeatFrame_);
//qDebug() << "文件:" << __FILE__ << "行:" << __LINE__ << "心跳帧: " <<heartbeatFrame_.toHex(' ').toUpper();
if(-1 == success)
{
qDebug() << "文件:" << __FILE__ << "行:" << __LINE__ << "心跳帧发送失败";
}
else
{
//发送成功,启动响应超时定时器
heartbeatTimeoutTimer_->start(heartbeatTimeout_);
qDebug() << "文件:" << __FILE__ << "行:" << __LINE__ << "心跳帧发送成功";
}

}

/********************************************************************
* 心跳超时触发,断开连接
********************************************************************/
void SerialCommunicator::onHeartbeatTimeout()
{
//qDebug() << "文件:" << __FILE__ << "行:" << __LINE__ << "心跳超时" << heartbeatTimeout_;
disconnectDevice();
}

/********************************************************************
* 设置超时参数
********************************************************************/
bool SerialCommunicator::setTimeoutSettings(int timeoutMs, int maxRetries)
{
if(timeoutMs < 500 || timeoutMs > 5000 || maxRetries < 1 || maxRetries >5)
{
return false;
}
timeoutHandler_.setTimeoutInterval(timeoutMs);
timeoutHandler_.setRetryCount(maxRetries);
return true;
}

/********************************************************************
* 获取超时参数
********************************************************************/
void SerialCommunicator::getTimeoutSettings(int &timeoutMs,int &maxRetries)
{
timeoutMs=timeoutHandler_.getTimeoutInterval();
maxRetries=timeoutHandler_.getRetryCount();
}

/********************************************************************
* 发送数据,启动计时
********************************************************************/
bool SerialCommunicator::sendData(const QByteArray &data)
{
if (!connected_)
{
emit errorOccurred("发送失败: 未连接设备");
return false;
}
if (data.isEmpty())
{
emit errorOccurred("发送失败: 数据为空");
return false;
}
isCanHeartbeat_ = false;
// 停止并重置超时处理器
timeoutHandler_.stop();

// 保存数据用于可能的重发
pendingData_ = data;

// 发送数据
qint64 bytesWritten = serialPort_->write(data);

if (-1 == bytesWritten)
{
emit errorOccurred("发送失败: " + serialPort_->errorString());
return false;
}
else
{
// 启动超时计时
timeoutHandler_.start();
return true;
}
}

/********************************************************************
* 检查是否连接
********************************************************************/
bool SerialCommunicator::isConnected() const
{
return connected_;
}

/********************************************************************
* 获取可用串口
********************************************************************/
QStringList SerialCommunicator::getAvailablePorts()
{
QStringList portList;
for (const QSerialPortInfo &info : QSerialPortInfo::availablePorts())
{
QString portDescription = info.portName() + " (" + info.description() + ")";
portList << portDescription;
}
return portList;
}

/********************************************************************
* 串口接收到数据,停止计时,并发送信号
********************************************************************/
void SerialCommunicator::onReadyRead()
{
//qDebug() <<"文件:"<<__FILE__ << "行数:" << __LINE__ << " 收到响应";
if (!connected_) return;

QByteArray data = serialPort_->readAll();
if (data.isEmpty())
{
emit statusChanged("接收为空数据");
return;
}

//如果心跳在计时,停止
if(heartbeatTimeoutTimer_->isActive())
{
heartbeatTimeoutTimer_->stop();
}
// 停止超时计时器(收到响应)
if (timeoutHandler_.isRunning())
{
timeoutHandler_.stop();
}

//处于心跳阶段,数据直接丢弃
if(true == isCanHeartbeat_)
{
data.clear();
}
// 转换为带空格的十六进制字符串
QString hexData = data.toHex(' ').toUpper();
// 发射数据接收信号
emit dataReceived(hexData.trimmed());
}

// 处理超时信号
void SerialCommunicator::onTimeoutOccurred(int currentRetry)
{
if (!connected_)
{
qDebug() <<"文件:"<<__FILE__ << "行数:" << __LINE__ << "设备未连接,取消重试";
return;
}

// 重发数据
qint64 bytesWritten = serialPort_->write(pendingData_);

if (-1 == bytesWritten)
{
emit errorOccurred("重发失败: " + serialPort_->errorString());
timeoutHandler_.stop(); // 停止重试
}
else
{
emit statusChanged(QString("超时重发 (%1/%2)").arg(currentRetry)
.arg(timeoutHandler_.getRetryCount()));
}
}

// 处理最大重试次数达到
void SerialCommunicator::onMaxRetriesReached()
{
qDebug() <<"文件:"<<__FILE__ << "行数:" << __LINE__ << "达到最大重试次数";
emit statusChanged("通信超时,请检查连接后再试!");
disconnectDevice();
}

// 处理串口错误(如设备拔出)
void SerialCommunicator::onSerialError(QSerialPort::SerialPortError error)
{
if (QSerialPort::ResourceError == error && connected_)
{
qDebug() <<"文件:"<<__FILE__ << "行数:" << __LINE__ << "串口错误: 设备可能已被拔出或不可用";
disconnectDevice(); // 调用断开连接,触发 connectionDisconnected 信号
}
}

+ 118
- 0
Modbus/src/timeout_handler.cpp View File

@@ -0,0 +1,118 @@
/***********************************************************************
* Copyright (C) 2025-, XINJE Co., Ltd.
*
* File Name: // serial_communication.cpp
* Description: // 主站超时处理模块源文件
* Others: //
* Version: // v1.0
* Author: // weikai,XINJE
* Date: // 2025-7-30
***********************************************************************/

#include "timeout_handler.h"
#include "mainwindow.h"
#include<QDebug>

TimeoutHandler::TimeoutHandler(QObject *parent)
: QObject(parent),
timer_(new QTimer(this)),
timeoutInterval_(DEFAULT_TIMEOUT),
maxRetryCount_(DEFAULT_MAXRETRISE),
currentRetryCount_(0)
{
// 配置定时器为单次触发
timer_->setSingleShot(true);
// 连接定时器超时信号到内部处理函数
connect(timer_, &QTimer::timeout, this, &TimeoutHandler::onTimerTimeout);
}

/********************************************************************
* 设置超时时间
********************************************************************/
void TimeoutHandler::setTimeoutInterval(int msec)
{
timeoutInterval_ = msec;
}

/********************************************************************
* 获取超时时间
********************************************************************/
int TimeoutHandler::getTimeoutInterval() const
{
return timeoutInterval_;
}

/********************************************************************
* 设置最大重发次数
********************************************************************/
void TimeoutHandler::setRetryCount(int count)
{
maxRetryCount_ = count;
}

/********************************************************************
* 获取设置的最大重发次数
********************************************************************/
int TimeoutHandler::getRetryCount() const
{
return maxRetryCount_;
}

/********************************************************************
* 启动超时计时
********************************************************************/
void TimeoutHandler::start()
{
// 若已在运行,先停止
if (timer_->isActive())
{
timer_->stop();
}
// 启动定时器
timer_->start(timeoutInterval_);
}

/********************************************************************
* 停止超时计时,重置重发次数
********************************************************************/
void TimeoutHandler::stop()
{
if (timer_->isActive())
{
timer_->stop();
}
// 重置重试计数
currentRetryCount_ = 0;
}

/********************************************************************
* 判断是否在计时中
********************************************************************/
bool TimeoutHandler::isRunning() const
{
return timer_->isActive();
}

/********************************************************************
* 超时信号触发的槽函数,发送超时发生给串口通信模块,启动重发
********************************************************************/
void TimeoutHandler::onTimerTimeout()
{
// 增加重发计数
currentRetryCount_++;

// 检查是否达到最大重发次数
if (currentRetryCount_ <= maxRetryCount_)
{
// 发送超时信号,显示从 1 开始的计数
emit timeoutOccurred(currentRetryCount_);
// 继续下一次重发
timer_->start(timeoutInterval_);
}
else
{
// 达到最大重发次数,发送信号并重置计数
emit maxRetriesReached();
currentRetryCount_ = 0;
}
}

+ 38
- 0
ModbusMatser.pro View File

@@ -0,0 +1,38 @@
QT += core gui
QT += serialport
QT += core
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

CONFIG += c++11

# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS

# You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0

SOURCES += \
main.cpp \
mainwindow.cpp \
modbus_master.cpp \
serial_communication.cpp \
timeout_handler.cpp

HEADERS += \
mainwindow.h \
modbus_master.h \
serial_communication.h \
timeout_handler.h

FORMS += \
mainwindow.ui

# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

+ 165
- 0
UnitTest/tst_test_modbus.cpp View File

@@ -0,0 +1,165 @@
#include <QtTest>
#include "../ModbusMatser/modbus_master.h"

// 单元测试类,用于测试 ModbusRTUMaster 类的解析函数
class Test_Modbus : public QObject
{
Q_OBJECT

public:
Test_Modbus();
~Test_Modbus();

private slots:
// 测试用例声明
void test_parseResponse(); // 测试 parseResponse 函数
void test_parseReadCoilsResponse(); // 测试 parseReadCoilsResponse 函数
void test_parseReadHoldingRegistersResponse(); // 测试 parseReadHoldingRegistersResponse 函数
};

// 构造函数
Test_Modbus::Test_Modbus()
{
}

// 析构函数
Test_Modbus::~Test_Modbus()
{
}

// 测试 parseResponse 函数
void Test_Modbus::test_parseResponse()
{
ModbusRTUMaster modbus;
quint8 slaveAddr, funcCode, errorCode;
QVector<quint16> parsedData;

// 测试用例 1: 正常读保持寄存器响应(功能码03,2个寄存器)
// 帧格式: [从站地址:01] [功能码:03] [字节数:04] [数据:0001 0003] [CRC]
QByteArray response = QByteArray::fromHex("01030400010003EBF2");
modbus.setRegCount(2); // 设置期望的寄存器数量
QVERIFY(modbus.parseResponse(response, slaveAddr, funcCode, parsedData, errorCode));
QCOMPARE(slaveAddr, static_cast<quint8>(0x01)); // 验证从站地址
QCOMPARE(funcCode, static_cast<quint8>(0x03)); // 验证功能码
QCOMPARE(errorCode, ModbusRTUMaster::NO_ERROR); // 验证无错误
QCOMPARE(parsedData.size(), 2); // 验证数据长度
QCOMPARE(parsedData[0], static_cast<quint16>(0x0001)); // 验证寄存器1
QCOMPARE(parsedData[1], static_cast<quint16>(0x0003)); // 验证寄存器3

// 测试用例 2: 正常读线圈响应(功能码01,8个线圈)
// 帧格式: [从站地址:01] [功能码:01] [字节数:01] [数据:FF] [CRC]
modbus.setCoilCount(8); // 设置期望的线圈数量
response = QByteArray::fromHex("010101FF11C8");
QVERIFY(modbus.parseResponse(response, slaveAddr, funcCode, parsedData, errorCode));
QCOMPARE(slaveAddr, static_cast<quint8>(0x01)); // 验证从站地址
QCOMPARE(funcCode, static_cast<quint8>(0x01)); // 验证功能码
QCOMPARE(errorCode, ModbusRTUMaster::NO_ERROR); // 验证无错误
QCOMPARE(parsedData.size(), 8); // 验证数据长度
for (int i = 0; i < 8; ++i)
{
QCOMPARE(parsedData[i], static_cast<quint16>(1)); // 验证所有线圈为1
}

// 测试用例 3: 错误响应(功能码83,非法功能)
// 帧格式: [从站地址:01] [功能码:83] [错误码:01] [CRC]
parsedData.clear();
response = QByteArray::fromHex("01830180F0");
QVERIFY(modbus.parseResponse(response, slaveAddr, funcCode, parsedData, errorCode));
QCOMPARE(slaveAddr, static_cast<quint8>(0x01)); // 验证从站地址
QCOMPARE(funcCode, static_cast<quint8>(0x83)); // 验证功能码
QCOMPARE(errorCode, static_cast<quint8>(0x01)); // 验证错误码
QCOMPARE(parsedData.size(), 0); // 验证无数据

// 测试用例 4: 数据帧过短(无效帧)
response = QByteArray::fromHex("0103"); // 只有2字节,少于最小长度
QVERIFY(!modbus.parseResponse(response, slaveAddr, funcCode, parsedData, errorCode));

// 测试用例 5: CRC错误
// 帧格式: [从站地址:01] [功能码:03] [字节数:04] [数据:0001 0004] [错误CRC]
response = QByteArray::fromHex("01030400010004AB30");
QVERIFY(!modbus.parseResponse(response, slaveAddr, funcCode, parsedData, errorCode));

// 测试用例 6: 不支持的功能码
// 帧格式: [从站地址:01] [功能码:05] [数据] [CRC]
response = QByteArray::fromHex("01050400010002D6E9");
QVERIFY(!modbus.parseResponse(response, slaveAddr, funcCode, parsedData, errorCode));
}

// 测试 parseReadCoilsResponse 函数
void Test_Modbus::test_parseReadCoilsResponse()
{
ModbusRTUMaster modbus;
QVector<bool> coils;

// 测试用例 1: 正常读线圈响应(8个线圈全1)
// 数据格式: [字节数:01] [数据:FF]
modbus.setCoilCount(8);
QByteArray responseData = QByteArray::fromHex("01FF");
QVERIFY(modbus.parseReadCoilsResponse(responseData, coils));
QCOMPARE(coils.size(), 8); // 验证线圈数量
for (int i = 0; i < 8; ++i)
{
QCOMPARE(coils[i], true); // 验证每个线圈状态为1
}

// 测试用例 2: 正常读线圈响应(5个线圈,前5位为1)
// 数据格式: [字节数:01] [数据:1F]
modbus.setCoilCount(5);
responseData = QByteArray::fromHex("011F");
QVERIFY(modbus.parseReadCoilsResponse(responseData, coils));
QCOMPARE(coils.size(), 5); // 验证线圈数量
for (int i = 0; i < 5; ++i)
{
QCOMPARE(coils[i], true); // 验证每个线圈状态为1
}

// 测试用例 3: 字节数不匹配
// 数据格式: [字节数:02] [数据:03](数据长度与字节数声明不符)
responseData = QByteArray::fromHex("0203");
QVERIFY(!modbus.parseReadCoilsResponse(responseData, coils));

// 测试用例 4: 空数据
responseData = QByteArray();
QVERIFY(!modbus.parseReadCoilsResponse(responseData, coils));
}

// 测试 parseReadHoldingRegistersResponse 函数
void Test_Modbus::test_parseReadHoldingRegistersResponse()
{
ModbusRTUMaster modbus;
QVector<quint16> registers;

// 测试用例 1: 正常读保持寄存器响应(2个寄存器)
// 数据格式: [字节数:04] [数据:0001 0002]
modbus.setRegCount(2);
QByteArray responseData = QByteArray::fromHex("0400010002");
QVERIFY(modbus.parseReadHoldingRegistersResponse(responseData, registers));
QCOMPARE(registers.size(), 2); // 验证寄存器数量
QCOMPARE(registers[0], static_cast<quint16>(0x0001)); // 验证寄存器1
QCOMPARE(registers[1], static_cast<quint16>(0x0002)); // 验证寄存器2

// 测试用例 2: 读取10个寄存器
// 数据格式: [字节数:14] [数据:0001 0002 ... 000A]
modbus.setRegCount(10);
responseData = QByteArray::fromHex("14000100020003000400050006000700080009000A");
QVERIFY(modbus.parseReadHoldingRegistersResponse(responseData, registers));
QCOMPARE(registers.size(), 10); // 验证寄存器数量
for (int i = 0; i < 10; ++i)
{
QCOMPARE(registers[i], static_cast<quint16>(i + 1)); // 验证每个寄存器值
}

// 测试用例 3: 字节数不匹配
// 数据格式: [字节数:02] [数据:0001](数据长度与字节数声明不符)
responseData = QByteArray::fromHex("020001");
QVERIFY(!modbus.parseReadHoldingRegistersResponse(responseData, registers));

// 测试用例 4: 空数据
responseData = QByteArray();
QVERIFY(!modbus.parseReadHoldingRegistersResponse(responseData, registers));
}


QTEST_APPLESS_MAIN(Test_Modbus)

#include "tst_test_modbus.moc"

+ 18
- 0
UnitTest/untitled.pro View File

@@ -0,0 +1,18 @@
QT += testlib widgets serialport
QT += testlib
QT -= gui



TEMPLATE = app

SOURCES += tst_test_modbus.cpp \
../ModbusMatser/modbus_master.cpp

HEADERS += \
../ModbusMatser/modbus_master.h

TARGET = modbus_master
CONFIG += console
CONFIG -= app_bundle


+ 319
- 0
UnitTest/untitled.pro.user View File

@@ -0,0 +1,319 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 4.11.1, 2025-08-04T10:01:37. -->
<qtcreator>
<data>
<variable>EnvironmentId</variable>
<value type="QByteArray">{54b998bd-0b43-4b57-8d79-23e6237f0a57}</value>
</data>
<data>
<variable>ProjectExplorer.Project.ActiveTarget</variable>
<value type="int">0</value>
</data>
<data>
<variable>ProjectExplorer.Project.EditorSettings</variable>
<valuemap type="QVariantMap">
<value type="bool" key="EditorConfiguration.AutoIndent">true</value>
<value type="bool" key="EditorConfiguration.AutoSpacesForTabs">false</value>
<value type="bool" key="EditorConfiguration.CamelCaseNavigation">true</value>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.0">
<value type="QString" key="language">Cpp</value>
<valuemap type="QVariantMap" key="value">
<value type="QByteArray" key="CurrentPreferences">CppGlobal</value>
</valuemap>
</valuemap>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.1">
<value type="QString" key="language">QmlJS</value>
<valuemap type="QVariantMap" key="value">
<value type="QByteArray" key="CurrentPreferences">QmlJSGlobal</value>
</valuemap>
</valuemap>
<value type="int" key="EditorConfiguration.CodeStyle.Count">2</value>
<value type="QByteArray" key="EditorConfiguration.Codec">UTF-8</value>
<value type="bool" key="EditorConfiguration.ConstrainTooltips">false</value>
<value type="int" key="EditorConfiguration.IndentSize">4</value>
<value type="bool" key="EditorConfiguration.KeyboardTooltips">false</value>
<value type="int" key="EditorConfiguration.MarginColumn">80</value>
<value type="bool" key="EditorConfiguration.MouseHiding">true</value>
<value type="bool" key="EditorConfiguration.MouseNavigation">true</value>
<value type="int" key="EditorConfiguration.PaddingMode">1</value>
<value type="bool" key="EditorConfiguration.ScrollWheelZooming">true</value>
<value type="bool" key="EditorConfiguration.ShowMargin">false</value>
<value type="int" key="EditorConfiguration.SmartBackspaceBehavior">0</value>
<value type="bool" key="EditorConfiguration.SmartSelectionChanging">true</value>
<value type="bool" key="EditorConfiguration.SpacesForTabs">true</value>
<value type="int" key="EditorConfiguration.TabKeyBehavior">0</value>
<value type="int" key="EditorConfiguration.TabSize">8</value>
<value type="bool" key="EditorConfiguration.UseGlobal">true</value>
<value type="int" key="EditorConfiguration.Utf8BomBehavior">1</value>
<value type="bool" key="EditorConfiguration.addFinalNewLine">true</value>
<value type="bool" key="EditorConfiguration.cleanIndentation">true</value>
<value type="bool" key="EditorConfiguration.cleanWhitespace">true</value>
<value type="bool" key="EditorConfiguration.inEntireDocument">false</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.PluginSettings</variable>
<valuemap type="QVariantMap">
<valuelist type="QVariantList" key="ClangCodeModel.CustomCommandLineKey">
<value type="QString">-fno-delayed-template-parsing</value>
</valuelist>
<value type="bool" key="ClangCodeModel.UseGlobalConfig">true</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.Target.0</variable>
<valuemap type="QVariantMap">
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Desktop Qt 5.14.2 MinGW 64-bit</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Desktop Qt 5.14.2 MinGW 64-bit</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">qt.qt5.5142.win64_mingw73_kit</value>
<value type="int" key="ProjectExplorer.Target.ActiveBuildConfiguration">0</value>
<value type="int" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
<value type="int" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0">
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">D:/QT/QTProject/build-untitled-Desktop_Qt_5_14_2_MinGW_64_bit-Debug</value>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">QtProjectManager.QMakeBuildStep</value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.LinkQmlDebuggingLibrary">true</value>
<value type="QString" key="QtProjectManager.QMakeBuildStep.QMakeArguments"></value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.QMakeForced">false</value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.SeparateDebugInfo">false</value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.UseQtQuickCompiler">false</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.1">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
<valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.BuildTargets"/>
<value type="bool" key="Qt4ProjectManager.MakeStep.Clean">false</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments"></value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeCommand"></value>
<value type="bool" key="Qt4ProjectManager.MakeStep.OverrideMakeflags">false</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">2</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
<valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.BuildTargets"/>
<value type="bool" key="Qt4ProjectManager.MakeStep.Clean">true</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments">clean</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeCommand"></value>
<value type="bool" key="Qt4ProjectManager.MakeStep.OverrideMakeflags">false</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Debug</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4BuildConfiguration</value>
<value type="int" key="Qt4ProjectManager.Qt4BuildConfiguration.BuildConfiguration">2</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.1">
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">D:/QT/QTProject/build-untitled-Desktop_Qt_5_14_2_MinGW_64_bit-Release</value>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">QtProjectManager.QMakeBuildStep</value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.LinkQmlDebuggingLibrary">false</value>
<value type="QString" key="QtProjectManager.QMakeBuildStep.QMakeArguments"></value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.QMakeForced">false</value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.SeparateDebugInfo">false</value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.UseQtQuickCompiler">true</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.1">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
<valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.BuildTargets"/>
<value type="bool" key="Qt4ProjectManager.MakeStep.Clean">false</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments"></value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeCommand"></value>
<value type="bool" key="Qt4ProjectManager.MakeStep.OverrideMakeflags">false</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">2</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
<valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.BuildTargets"/>
<value type="bool" key="Qt4ProjectManager.MakeStep.Clean">true</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments">clean</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeCommand"></value>
<value type="bool" key="Qt4ProjectManager.MakeStep.OverrideMakeflags">false</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Release</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4BuildConfiguration</value>
<value type="int" key="Qt4ProjectManager.Qt4BuildConfiguration.BuildConfiguration">0</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.2">
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">D:/QT/QTProject/build-untitled-Desktop_Qt_5_14_2_MinGW_64_bit-Profile</value>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">QtProjectManager.QMakeBuildStep</value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.LinkQmlDebuggingLibrary">true</value>
<value type="QString" key="QtProjectManager.QMakeBuildStep.QMakeArguments"></value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.QMakeForced">false</value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.SeparateDebugInfo">true</value>
<value type="bool" key="QtProjectManager.QMakeBuildStep.UseQtQuickCompiler">true</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.1">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
<valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.BuildTargets"/>
<value type="bool" key="Qt4ProjectManager.MakeStep.Clean">false</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments"></value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeCommand"></value>
<value type="bool" key="Qt4ProjectManager.MakeStep.OverrideMakeflags">false</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">2</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.MakeStep</value>
<valuelist type="QVariantList" key="Qt4ProjectManager.MakeStep.BuildTargets"/>
<value type="bool" key="Qt4ProjectManager.MakeStep.Clean">true</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeArguments">clean</value>
<value type="QString" key="Qt4ProjectManager.MakeStep.MakeCommand"></value>
<value type="bool" key="Qt4ProjectManager.MakeStep.OverrideMakeflags">false</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Profile</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4BuildConfiguration</value>
<value type="int" key="Qt4ProjectManager.Qt4BuildConfiguration.BuildConfiguration">0</value>
</valuemap>
<value type="int" key="ProjectExplorer.Target.BuildConfigurationCount">3</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<value type="int" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
</valuemap>
<value type="int" key="ProjectExplorer.Target.DeployConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.PluginSettings"/>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
<value type="QString" key="Analyzer.Perf.CallgraphMode">dwarf</value>
<valuelist type="QVariantList" key="Analyzer.Perf.Events">
<value type="QString">cpu-cycles</value>
</valuelist>
<valuelist type="QVariantList" key="Analyzer.Perf.ExtraArguments"/>
<value type="int" key="Analyzer.Perf.Frequency">250</value>
<valuelist type="QVariantList" key="Analyzer.Perf.RecordArguments">
<value type="QString">-e</value>
<value type="QString">cpu-cycles</value>
<value type="QString">--call-graph</value>
<value type="QString">dwarf,4096</value>
<value type="QString">-F</value>
<value type="QString">250</value>
</valuelist>
<value type="QString" key="Analyzer.Perf.SampleMode">-F</value>
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
<value type="int" key="Analyzer.Perf.StackSize">4096</value>
<value type="bool" key="Analyzer.QmlProfiler.AggregateTraces">false</value>
<value type="bool" key="Analyzer.QmlProfiler.FlushEnabled">false</value>
<value type="uint" key="Analyzer.QmlProfiler.FlushInterval">1000</value>
<value type="QString" key="Analyzer.QmlProfiler.LastTraceFile"></value>
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
<valuelist type="QVariantList" key="Analyzer.Valgrind.AddedSuppressionFiles"/>
<value type="bool" key="Analyzer.Valgrind.Callgrind.CollectBusEvents">false</value>
<value type="bool" key="Analyzer.Valgrind.Callgrind.CollectSystime">false</value>
<value type="bool" key="Analyzer.Valgrind.Callgrind.EnableBranchSim">false</value>
<value type="bool" key="Analyzer.Valgrind.Callgrind.EnableCacheSim">false</value>
<value type="bool" key="Analyzer.Valgrind.Callgrind.EnableEventToolTips">true</value>
<value type="double" key="Analyzer.Valgrind.Callgrind.MinimumCostRatio">0.01</value>
<value type="double" key="Analyzer.Valgrind.Callgrind.VisualisationMinimumCostRatio">10</value>
<value type="bool" key="Analyzer.Valgrind.FilterExternalIssues">true</value>
<value type="QString" key="Analyzer.Valgrind.KCachegrindExecutable">kcachegrind</value>
<value type="int" key="Analyzer.Valgrind.LeakCheckOnFinish">1</value>
<value type="int" key="Analyzer.Valgrind.NumCallers">25</value>
<valuelist type="QVariantList" key="Analyzer.Valgrind.RemovedSuppressionFiles"/>
<value type="int" key="Analyzer.Valgrind.SelfModifyingCodeDetection">1</value>
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
<value type="bool" key="Analyzer.Valgrind.ShowReachable">false</value>
<value type="bool" key="Analyzer.Valgrind.TrackOrigins">true</value>
<value type="QString" key="Analyzer.Valgrind.ValgrindExecutable">valgrind</value>
<valuelist type="QVariantList" key="Analyzer.Valgrind.VisibleErrorKinds">
<value type="int">0</value>
<value type="int">1</value>
<value type="int">2</value>
<value type="int">3</value>
<value type="int">4</value>
<value type="int">5</value>
<value type="int">6</value>
<value type="int">7</value>
<value type="int">8</value>
<value type="int">9</value>
<value type="int">10</value>
<value type="int">11</value>
<value type="int">12</value>
<value type="int">13</value>
<value type="int">14</value>
</valuelist>
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">Qt4ProjectManager.Qt4RunConfiguration:D:/QT/QTProject/untitled/untitled.pro</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey">D:/QT/QTProject/untitled/untitled.pro</value>
<value type="QString" key="RunConfiguration.Arguments"></value>
<value type="bool" key="RunConfiguration.Arguments.multi">false</value>
<value type="QString" key="RunConfiguration.OverrideDebuggerStartup"></value>
<value type="bool" key="RunConfiguration.UseCppDebugger">false</value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseLibrarySearchPath">true</value>
<value type="bool" key="RunConfiguration.UseMultiProcess">false</value>
<value type="bool" key="RunConfiguration.UseQmlDebugger">false</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
<value type="QString" key="RunConfiguration.WorkingDirectory"></value>
<value type="QString" key="RunConfiguration.WorkingDirectory.default">D:/QT/QTProject/build-untitled-Desktop_Qt_5_14_2_MinGW_64_bit-Debug</value>
</valuemap>
<value type="int" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.TargetCount</variable>
<value type="int">1</value>
</data>
<data>
<variable>ProjectExplorer.Project.Updater.FileVersion</variable>
<value type="int">22</value>
</data>
<data>
<variable>Version</variable>
<value type="int">22</value>
</data>
</qtcreator>

Loading…
Cancel
Save