├── README.txt ├── SerialDebuger_QCustomPlot.pro ├── SerialDebuger_QCustomPlot.pro.user ├── axis_64px_1200251_easyicon.net.ico ├── chart_curve_32px_506507_easyicon.net.ico ├── datafilter.cpp ├── datafilter.h ├── debug_32px_1103189_easyicon.net.ico ├── main.cpp ├── mainwindow.cpp ├── mainwindow.h ├── mainwindow.ui ├── mycombobox.cpp ├── mycombobox.h ├── pic.qrc ├── pic ├── ssCircle.png ├── ssCross.png ├── ssCrossCircle.png ├── ssCrossSquare.png ├── ssDiamond.png ├── ssDisc.png ├── ssNone.png ├── ssPeace.png ├── ssPlus.png ├── ssPlusCircle.png ├── ssPlusSquare.png ├── ssSquare.png ├── ssStar.png ├── ssTriangle.png └── ssTriangleInverted.png ├── plot.cpp ├── plot.h ├── plot.ui ├── qcustomplot.cpp ├── qcustomplot.h ├── res.qrc └── 测试帧.txt /README.txt: -------------------------------------------------------------------------------- 1 | 基于QCustomPlot,使用Qt编写的串口波形实时绘图工具。 2 | 3 | 4 | 基本功能齐全,包括: 5 | 自动扫描可用串口端口,同时显示端口硬件信息(无须再查看设备管理器,方便选择),波特率可手动输入。 6 | 发送/接收字节、速率统计、帧统计、误码统计。 7 | 接收区每2000字节强制换行,避免一行的内容太多,导致CPU占用过高从而卡顿,已测试过累计千万字节不卡顿。(仅仅是在接收显示里加换行,实际的真实数据不受影响) 8 | 支持中文收发,默认GB2312编码,如需Unicode/UTF8/UTF16需要更改代码。(单片机中文编码大多使用GB2312,占用空间小) 9 | 收/发区,无缝16进制转换,GB2312中文不乱码。输入框可作为转换框,无须在计算后重复输入。 10 | 11 | 增加帧捕获功能,用户可自定义修改两字节的帧头,帧协议兼容匿名四轴调试上位机。 12 | 增加捕获帧调试界面,方便调试。 13 | 14 | 波形绘图最多可显示20条曲线。曲线连接方式,颜色、散点形状可任意设置。可隐藏,可加粗显示。 15 | 曲线连接方式支持:点、线连接、左台阶、右台阶、居中台阶、纵值绘图。 16 | 波形散点形状支持:x形、+形、空心圆、实心圆、矩形、菱形、星形、正三角、倒三角、x框、+框、x圆、+圆、和平。 17 | 动态绘图支持: X轴自动跟随、Y轴范围自适应。 18 | 图页显示范围可在右下角手动输入调整。 19 | 支持滚轮Y轴缩放、左键拖拽、单击曲线加粗显示,不支持拖拽缩放。 20 | 21 | 22 | 使用说明,功能帧格式,见我的博客: 23 | https://blog.csdn.net/Mark_md/article/details/109995490 -------------------------------------------------------------------------------- /SerialDebuger_QCustomPlot.pro: -------------------------------------------------------------------------------- 1 | QT += core gui serialport 2 | 3 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets printsupport 4 | 5 | CONFIG += c++11 6 | 7 | # The following define makes your compiler emit warnings if you use 8 | # any Qt feature that has been marked deprecated (the exact warnings 9 | # depend on your compiler). Please consult the documentation of the 10 | # deprecated API in order to know how to port your code away from it. 11 | DEFINES += QT_DEPRECATED_WARNINGS 12 | 13 | RC_ICONS = debug_32px_1103189_easyicon.net.ico 14 | 15 | # You can also make your code fail to compile if it uses deprecated APIs. 16 | # In order to do so, uncomment the following line. 17 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 18 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 19 | 20 | SOURCES += \ 21 | main.cpp \ 22 | mainwindow.cpp \ 23 | mycombobox.cpp \ 24 | plot.cpp \ 25 | qcustomplot.cpp 26 | 27 | HEADERS += \ 28 | mainwindow.h \ 29 | mycombobox.h \ 30 | plot.h \ 31 | qcustomplot.h 32 | 33 | FORMS += \ 34 | mainwindow.ui \ 35 | plot.ui 36 | 37 | # Default rules for deployment. 38 | qnx: target.path = /tmp/$${TARGET}/bin 39 | else: unix:!android: target.path = /opt/$${TARGET}/bin 40 | !isEmpty(target.path): INSTALLS += target 41 | 42 | RESOURCES += \ 43 | res.qrc 44 | -------------------------------------------------------------------------------- /SerialDebuger_QCustomPlot.pro.user: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | EnvironmentId 7 | {a0493980-663c-4399-bed9-6237f8a1eee5} 8 | 9 | 10 | ProjectExplorer.Project.ActiveTarget 11 | 0 12 | 13 | 14 | ProjectExplorer.Project.EditorSettings 15 | 16 | true 17 | false 18 | true 19 | 20 | Cpp 21 | 22 | CppGlobal 23 | 24 | 25 | 26 | QmlJS 27 | 28 | QmlJSGlobal 29 | 30 | 31 | 2 32 | UTF-8 33 | false 34 | 4 35 | false 36 | 80 37 | true 38 | true 39 | 1 40 | true 41 | false 42 | 0 43 | true 44 | true 45 | 0 46 | 8 47 | true 48 | 1 49 | true 50 | true 51 | true 52 | false 53 | 54 | 55 | 56 | ProjectExplorer.Project.PluginSettings 57 | 58 | 59 | -fno-delayed-template-parsing 60 | 61 | true 62 | Builtin.Questionable 63 | 64 | true 65 | Builtin.DefaultTidyAndClazy 66 | 2 67 | 68 | 69 | 70 | true 71 | 72 | 73 | 74 | 75 | ProjectExplorer.Project.Target.0 76 | 77 | Desktop Qt 5.12.9 MinGW 64-bit 78 | Desktop Qt 5.12.9 MinGW 64-bit 79 | qt.qt5.5129.win64_mingw73_kit 80 | 1 81 | 0 82 | 0 83 | 84 | true 85 | 0 86 | D:\Qt\Qt_Code\PRJ\SerialDebuger_QCustomPlot\build-SerialDebuger_QCustomPlot-Desktop_Qt_5_12_9_MinGW_64_bit-Debug 87 | D:/Qt/Qt_Code/PRJ/SerialDebuger_QCustomPlot/build-SerialDebuger_QCustomPlot-Desktop_Qt_5_12_9_MinGW_64_bit-Debug 88 | 89 | 90 | true 91 | QtProjectManager.QMakeBuildStep 92 | 93 | false 94 | 95 | 96 | 97 | true 98 | Qt4ProjectManager.MakeStep 99 | 100 | false 101 | 102 | 103 | false 104 | 105 | 2 106 | Build 107 | Build 108 | ProjectExplorer.BuildSteps.Build 109 | 110 | 111 | 112 | true 113 | Qt4ProjectManager.MakeStep 114 | 115 | true 116 | clean 117 | 118 | false 119 | 120 | 1 121 | Clean 122 | Clean 123 | ProjectExplorer.BuildSteps.Clean 124 | 125 | 2 126 | false 127 | 128 | Debug 129 | Qt4ProjectManager.Qt4BuildConfiguration 130 | 2 131 | 2 132 | 2 133 | 134 | 135 | true 136 | 2 137 | D:\Qt\Qt_Code\PRJ\SerialDebuger_QCustomPlot\build-SerialDebuger_QCustomPlot-Desktop_Qt_5_12_9_MinGW_64_bit-Release 138 | D:/Qt/Qt_Code/PRJ/SerialDebuger_QCustomPlot/build-SerialDebuger_QCustomPlot-Desktop_Qt_5_12_9_MinGW_64_bit-Release 139 | 140 | 141 | true 142 | QtProjectManager.QMakeBuildStep 143 | 144 | false 145 | 146 | 147 | 148 | true 149 | Qt4ProjectManager.MakeStep 150 | 151 | false 152 | 153 | 154 | false 155 | 156 | 2 157 | Build 158 | Build 159 | ProjectExplorer.BuildSteps.Build 160 | 161 | 162 | 163 | true 164 | Qt4ProjectManager.MakeStep 165 | 166 | true 167 | clean 168 | 169 | false 170 | 171 | 1 172 | Clean 173 | Clean 174 | ProjectExplorer.BuildSteps.Clean 175 | 176 | 2 177 | false 178 | 179 | Release 180 | Qt4ProjectManager.Qt4BuildConfiguration 181 | 0 182 | 0 183 | 2 184 | 185 | 186 | true 187 | 0 188 | D:\Qt\Qt_Code\PRJ\SerialDebuger_QCustomPlot\build-SerialDebuger_QCustomPlot-Desktop_Qt_5_12_9_MinGW_64_bit-Profile 189 | D:/Qt/Qt_Code/PRJ/SerialDebuger_QCustomPlot/build-SerialDebuger_QCustomPlot-Desktop_Qt_5_12_9_MinGW_64_bit-Profile 190 | 191 | 192 | true 193 | QtProjectManager.QMakeBuildStep 194 | 195 | false 196 | 197 | 198 | 199 | true 200 | Qt4ProjectManager.MakeStep 201 | 202 | false 203 | 204 | 205 | false 206 | 207 | 2 208 | Build 209 | Build 210 | ProjectExplorer.BuildSteps.Build 211 | 212 | 213 | 214 | true 215 | Qt4ProjectManager.MakeStep 216 | 217 | true 218 | clean 219 | 220 | false 221 | 222 | 1 223 | Clean 224 | Clean 225 | ProjectExplorer.BuildSteps.Clean 226 | 227 | 2 228 | false 229 | 230 | Profile 231 | Qt4ProjectManager.Qt4BuildConfiguration 232 | 0 233 | 0 234 | 0 235 | 236 | 3 237 | 238 | 239 | 0 240 | Deploy 241 | Deploy 242 | ProjectExplorer.BuildSteps.Deploy 243 | 244 | 1 245 | 246 | false 247 | ProjectExplorer.DefaultDeployConfiguration 248 | 249 | 1 250 | 251 | 252 | dwarf 253 | 254 | cpu-cycles 255 | 256 | 257 | 250 258 | 259 | -e 260 | cpu-cycles 261 | --call-graph 262 | dwarf,4096 263 | -F 264 | 250 265 | 266 | -F 267 | true 268 | 4096 269 | false 270 | false 271 | 1000 272 | 273 | true 274 | 275 | false 276 | false 277 | false 278 | false 279 | true 280 | 0.01 281 | 10 282 | true 283 | kcachegrind 284 | 1 285 | 25 286 | 287 | 1 288 | true 289 | false 290 | true 291 | valgrind 292 | 293 | 0 294 | 1 295 | 2 296 | 3 297 | 4 298 | 5 299 | 6 300 | 7 301 | 8 302 | 9 303 | 10 304 | 11 305 | 12 306 | 13 307 | 14 308 | 309 | 2 310 | 311 | Qt4ProjectManager.Qt4RunConfiguration:D:/Qt/Qt_Code/PRJ/SerialDebuger_QCustomPlot/SerialDebuger_QCustomPlot/SerialDebuger_QCustomPlot.pro 312 | D:/Qt/Qt_Code/PRJ/SerialDebuger_QCustomPlot/SerialDebuger_QCustomPlot/SerialDebuger_QCustomPlot.pro 313 | 314 | false 315 | 316 | false 317 | true 318 | true 319 | false 320 | false 321 | true 322 | 323 | D:/Qt/Qt_Code/PRJ/SerialDebuger_QCustomPlot/build-SerialDebuger_QCustomPlot-Desktop_Qt_5_12_9_MinGW_64_bit-Release 324 | 325 | 1 326 | 327 | 328 | 329 | ProjectExplorer.Project.TargetCount 330 | 1 331 | 332 | 333 | ProjectExplorer.Project.Updater.FileVersion 334 | 22 335 | 336 | 337 | Version 338 | 22 339 | 340 | 341 | -------------------------------------------------------------------------------- /axis_64px_1200251_easyicon.net.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhiliangMa/Qt_SerialPlot/a3b4af3d2159b62c275c3d4b55dc4452f41ef1a0/axis_64px_1200251_easyicon.net.ico -------------------------------------------------------------------------------- /chart_curve_32px_506507_easyicon.net.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhiliangMa/Qt_SerialPlot/a3b4af3d2159b62c275c3d4b55dc4452f41ef1a0/chart_curve_32px_506507_easyicon.net.ico -------------------------------------------------------------------------------- /datafilter.cpp: -------------------------------------------------------------------------------- 1 | #include "datafilter.h" 2 | 3 | DataFilter::DataFilter() 4 | { 5 | // 要不要在析构中,初始化清0全局变量? 6 | } 7 | 8 | DataFilter::~DataFilter() 9 | { 10 | 11 | } 12 | 13 | QByteArray DataFilter::xFrameDataFilter(QByteArray *str) 14 | { 15 | int num = str->size(); 16 | if(num) 17 | { 18 | for(int i=0; iat(i); // 从接收缓存区读取一个字节 21 | 22 | if (f_h_flag == 1) // 有帧头,判断帧尾,接收消息 23 | { 24 | if (f_t1_flag == 1) //有帧头,有帧尾1 25 | { 26 | if (chrtmp[tnum] == Frame_Tail2) 27 | { 28 | // 用户处理代码 // 29 | // 将接收到符合帧定义的帧,原路发送回去 // 30 | /*int i = 0; 31 | for (i = 0; i < (tnum + 1); i++) 32 | { 33 | //serialPutchar(hs1, chrtmp[i]); // 通过串口发送字节 34 | }*/ 35 | QByteArray str1; 36 | for(int i=0; i<(tnum + 1); i++) 37 | { 38 | str1.append(chrtmp[i]); 39 | } 40 | return str1; 41 | 42 | /*// 16进制显示,并转换为大写 43 | QString str2 = str1.toHex().toUpper(); 44 | // 添加空格 45 | QString str3; 46 | for(int i = 0; itxtSend->insertPlainText(str3); 52 | // 移动光标到文本结尾 53 | ui->txtSend->moveCursor(QTextCursor::End);*/ 54 | 55 | 56 | // 处理完用户代码,重新接收计数 // 57 | tnum = 0; 58 | } 59 | else 60 | { 61 | f_t1_flag = 0; 62 | tnum ++; 63 | } 64 | } 65 | else // 有帧头,无帧尾1 66 | { 67 | if (chrtmp[tnum] == Frame_Tail1) 68 | { 69 | f_t1_flag = 1; 70 | tnum ++; 71 | } 72 | else // 接收消息包中间内容 73 | { 74 | tnum ++; 75 | } 76 | } 77 | } 78 | else // 没有接收到帧头 79 | { 80 | if (f_h1_flag == 1) //没有帧头,有帧头1。下一步判断是否为第2个字节 81 | { 82 | if (chrtmp[tnum] == Frame_Header2) // 如果为帧头的第2个字节,接收到帧头标志位标志位置1,tnum自增 83 | { 84 | f_h_flag = 1; 85 | tnum ++; 86 | } 87 | else 88 | { 89 | f_h1_flag = 0; 90 | tnum = 0; 91 | } 92 | } 93 | else //没有帧头,没有有帧头1。下一步判断,是否为帧头的第1个字节 94 | { 95 | if (chrtmp[tnum] == Frame_Header1) // 如果为帧头的第1个字节,标志位置1,tnum自增 96 | { 97 | f_h1_flag = 1; 98 | tnum ++; 99 | } 100 | else // 否则,标志位清0,tnum清0 101 | { 102 | tnum = 0; 103 | } 104 | } 105 | } 106 | 107 | // 大于MaxFrameLength个字节的帧不接收 108 | if (tnum > (MaxFrameLength - 1) ) 109 | { 110 | tnum = 0; 111 | f_h1_flag = 0; 112 | f_h_flag = 0; 113 | f_t1_flag = 0; 114 | continue; 115 | } 116 | } 117 | } 118 | } 119 | 120 | /* 121 | int main(void) 122 | { 123 | 124 | 125 | while (1) 126 | { 127 | snum = serialDataAvail(hs1); // 获取串口接收缓存区的可用字节数 128 | if(snum > 0) 129 | { 130 | chrtmp[tnum] = serialGetchar(hs1); // 从接收缓存区读取一个字节 131 | 132 | if (f_h_flag == 1) // 有帧头,判断帧尾,接收消息 133 | { 134 | if (f_t1_flag == 1) //有帧头,有帧尾1 135 | { 136 | if (chrtmp[tnum] == Frame_Tail2) 137 | { 138 | // 用户处理代码 // 139 | // 将接收到符合帧定义的帧,原路发送回去 // 140 | int i = 0; 141 | for (i = 0; i < (tnum + 1); i++) 142 | { 143 | serialPutchar(hs1, chrtmp[i]); // 通过串口发送字节 144 | } 145 | 146 | // 处理完用户代码,重新接收计数 // 147 | tnum = 0; 148 | } 149 | else 150 | { 151 | f_t1_flag = 0; 152 | tnum ++; 153 | } 154 | } 155 | else // 有帧头,无帧尾1 156 | { 157 | if (chrtmp[tnum] == Frame_Tail1) 158 | { 159 | f_t1_flag = 1; 160 | tnum ++; 161 | } 162 | else // 接收消息包中间内容 163 | { 164 | tnum ++; 165 | } 166 | } 167 | } 168 | else // 没有接收到帧头 169 | { 170 | if (f_h1_flag == 1) //没有帧头,有帧头1。下一步判断是否为第2个字节 171 | { 172 | if (chrtmp[tnum] == Frame_Header2) // 如果为帧头的第2个字节,接收到帧头标志位标志位置1,tnum自增 173 | { 174 | f_h_flag = 1; 175 | tnum ++; 176 | } 177 | else 178 | { 179 | f_h1_flag = 0; 180 | tnum = 0; 181 | } 182 | } 183 | else //没有帧头,没有有帧头1。下一步判断,是否为帧头的第1个字节 184 | { 185 | if (chrtmp[tnum] == Frame_Header1) // 如果为帧头的第1个字节,标志位置1,tnum自增 186 | { 187 | f_h1_flag = 1; 188 | tnum ++; 189 | } 190 | else // 否则,标志位清0,tnum清0 191 | { 192 | tnum = 0; 193 | } 194 | } 195 | } 196 | 197 | // 大于MaxFrameLength个字节的帧不接收 198 | if (tnum > (MaxFrameLength - 1) ) 199 | { 200 | tnum = 0; 201 | f_h1_flag = 0; 202 | f_h_flag = 0; 203 | f_t1_flag = 0; 204 | continue; 205 | } 206 | 207 | } 208 | } 209 | 210 | return 0; 211 | */ 212 | -------------------------------------------------------------------------------- /datafilter.h: -------------------------------------------------------------------------------- 1 | #ifndef DATAFILTER_H 2 | #define DATAFILTER_H 3 | 4 | #include 5 | 6 | // 最大帧长度 7 | #define MaxFrameLength 10 // 对最大帧长度加以限定,防止接收到过长的帧数据 8 | // 完整的帧头,2个字节 9 | #define Frame_Header1 0x3A // 串口接收消息包的帧头的第1个字节 10 | #define Frame_Header2 0x3B // 串口接收消息包的帧头的第2个字节 11 | // 完整的帧尾,2个字节 12 | #define Frame_Tail1 0x7E // 串口接收消息包的帧尾的第1个字节 13 | #define Frame_Tail2 0x7F // 串口接收消息包的帧尾的第2个字节 14 | 15 | 16 | class DataFilter 17 | { 18 | public: 19 | DataFilter(); 20 | ~DataFilter(); 21 | 22 | QByteArray xFrameDataFilter(QByteArray *str); 23 | 24 | private: 25 | int snum = 0; // 系统串口接收缓存区的可用字节数 26 | int tnum = 0; // 用户串口接收区的数目 27 | unsigned char chrtmp[100]; // 用户串口接收区,将缓存的数据放入这里处理 28 | int f_h1_flag = 0; // 接收到帧头的第一个字节标志位 29 | int f_h_flag = 0; // 接收到帧头标志位 30 | int f_t1_flag = 0; // 接收到帧尾的第一个字节标志位 31 | }; 32 | 33 | #endif // DATAFILTER_H 34 | -------------------------------------------------------------------------------- /debug_32px_1103189_easyicon.net.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhiliangMa/Qt_SerialPlot/a3b4af3d2159b62c275c3d4b55dc4452f41ef1a0/debug_32px_1103189_easyicon.net.ico -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | 3 | #include 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | QApplication a(argc, argv); 8 | MainWindow w; 9 | w.show(); 10 | return a.exec(); 11 | } 12 | -------------------------------------------------------------------------------- /mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include "ui_mainwindow.h" 3 | #include 4 | 5 | MainWindow::MainWindow(QWidget *parent) 6 | : QMainWindow(parent) 7 | , ui(new Ui::MainWindow) 8 | { 9 | ui->setupUi(this); 10 | setWindowTitle("Qt Serial Debugger"); 11 | 12 | // 状态栏 13 | QStatusBar *sBar = statusBar(); 14 | // 状态栏的收、发计数标签 15 | lblSendNum = new QLabel(this); 16 | lblRecvNum = new QLabel(this); 17 | lblSendRate = new QLabel(this); 18 | lblRecvRate = new QLabel(this); 19 | // 设置标签最小大小 20 | lblSendNum->setMinimumSize(100, 20); 21 | lblRecvNum->setMinimumSize(100, 20); 22 | lblSendRate->setMinimumSize(100, 20); 23 | lblRecvRate->setMinimumSize(100, 20); 24 | //statusBar()->showMessage("留言", 5000);// 留言显示,过期时间单位为ms,过期后不再有显示 25 | //statusBar()->setSizeGripEnabled(false); // 是否显示右下角拖放控制点,默认显示 26 | //statusBar()->setStyleSheet(QString("QStatusBar::item{border: 0px}")); // 设置不显示label的边框 27 | //lblSendNum->setAlignment(Qt::AlignHCenter);// 设置label属性 28 | //sBar->addPermanentWidget();//addSeparator();// 添加分割线,不能用 29 | // 状态栏显示计数值 30 | //lblSendNum->setText("S: 0"); 31 | //lblRecvNum->setText("R: 0"); 32 | setNumOnLabel(lblSendNum, "S: ", sendNum); 33 | setNumOnLabel(lblRecvNum, "R: ", recvNum); 34 | setNumOnLabel(lblSendRate, "Byte/s: ", 0); 35 | setNumOnLabel(lblRecvRate, "Byte/s: ", 0); 36 | // 从右往左依次添加 37 | sBar->addPermanentWidget(lblSendNum); 38 | sBar->addPermanentWidget(lblSendRate); 39 | sBar->addPermanentWidget(lblRecvNum); 40 | sBar->addPermanentWidget(lblRecvRate); 41 | 42 | // 状态栏添加超链接 43 | QLabel *lblLinkBlog = new QLabel(this); 44 | lblLinkBlog->setOpenExternalLinks(true); 45 | //lblLinkBlog->setText("博客");// 有下划线 46 | lblLinkBlog->setText(" 博客");// 无下划线 47 | QLabel *lblLinkSource = new QLabel(this); 48 | lblLinkSource->setOpenExternalLinks(true); 49 | //lblLinkSource->setText("源码下载"); 50 | lblLinkSource->setText(" 源码下载");// 无下划线 51 | lblLinkBlog->setMinimumSize(40, 20); 52 | lblLinkSource->setMinimumSize(60, 20); 53 | // 从左往右依次添加 54 | sBar->addWidget(lblLinkBlog); 55 | sBar->addWidget(lblLinkSource); 56 | 57 | lblRecvFrameNum = new QLabel(this); 58 | lblFrameRate = new QLabel(this); 59 | lblRecvFrameNum->setMinimumSize(100, 20); 60 | lblFrameRate->setMinimumSize(80, 20); 61 | setNumOnLabel(lblRecvFrameNum, "FNum: ", recvFrameNum); 62 | setNumOnLabel(lblFrameRate, "FPS: ", recvFrameRate); 63 | // 从右往左依次添加 64 | sBar->addPermanentWidget(lblRecvFrameNum); 65 | sBar->addPermanentWidget(lblFrameRate); 66 | 67 | // 定时发送-定时器 68 | timSend = new QTimer; 69 | timSend->setInterval(1000);// 设置默认定时时长1000ms 70 | connect(timSend, &QTimer::timeout, this, [=](){ 71 | on_btnSend_clicked(); 72 | }); 73 | 74 | // 发送速率、接收速率统计-定时器 75 | timRate = new QTimer; 76 | timRate->start(1000); 77 | connect(timRate, &QTimer::timeout, this, [=](){ 78 | dataRateCalculate(); 79 | }); 80 | 81 | // 新建一串口对象 82 | mySerialPort = new QSerialPort(this); 83 | 84 | // 串口接收,信号槽关联 85 | connect(mySerialPort, SIGNAL(readyRead()), this, SLOT(serialPortRead_Slot())); 86 | 87 | // 隐藏高级收码显示区域 88 | ui->widget_5->hide(); 89 | ui->frame_3->hide(); 90 | 91 | // 新建波形显示界面 92 | plot = new Plot; 93 | } 94 | 95 | MainWindow::~MainWindow() 96 | { 97 | delete plot; 98 | delete ui; 99 | } 100 | 101 | // 绘图事件 102 | void MainWindow::paintEvent(QPaintEvent *) 103 | { 104 | // 绘图 105 | // 实例化画家对象,this指定绘图设备 106 | QPainter painter(this); 107 | 108 | // 设置画笔颜色 109 | QPen pen(QColor(0,0,0)); 110 | // 设置画笔线宽(只对点线圆起作用,对文字不起作用) 111 | pen.setWidth(1); 112 | // 设置画笔线条风格,默认是SolidLine实线 113 | // DashLine虚线,DotLine点线,DashDotLine、DashDotDotLine点划线 114 | pen.setStyle(Qt::DashDotDotLine); 115 | // 让画家使用这个画笔 116 | painter.setPen(pen); 117 | 118 | //painter.drawLine(QPoint(ui->txtRec->x() + ui->txtRec->width(), ui->txtRec->y()), QPoint(this->width(), ui->txtRec->y())); 119 | //painter.drawLine(QPoint(ui->txtSend->x() + ui->txtSend->width(), ui->txtSend->y()), QPoint(this->width(), ui->txtSend->y())); 120 | painter.drawLine(QPoint(ui->statusbar->x(), ui->statusbar->y()-2), QPoint(this->width(), ui->statusbar->y()-2)); 121 | 122 | } 123 | 124 | // 串口接收显示,槽函数 125 | void MainWindow::serialPortRead_Slot() 126 | { 127 | /*QString recBuf; 128 | recBuf = QString(mySerialPort->readAll());*/ 129 | 130 | QByteArray recBuf; 131 | recBuf = mySerialPort->readAll(); 132 | 133 | /* 帧过滤部分代码 */ 134 | short wmValue[20] = {0}; 135 | xFrameDataFilter(&recBuf, wmValue); 136 | 137 | // 调试信息输出,显示缓冲区内容(16进制显示)及接收标志位 138 | if(!ui->widget_5->isHidden()){ 139 | QByteArray str1; 140 | //for(int i=0; i<(tnum + 1); i++) 141 | for(int i=0; itxtFrameTemp->setPlainText(str1.toHex().toUpper()); 146 | str1 = str1.toHex().toUpper(); 147 | QString str2; 148 | for(int i = 0; itxtFrameBuffer->setPlainText(str2); 154 | // 显示标志位 155 | ui->txtFrameTnum->setText(QString::number(tnum)); 156 | ui->txtFrameH1->setText(QString::number(f_h1_flag)); 157 | ui->txtFrameH->setText(QString::number(f_h_flag)); 158 | ui->txtFrameFun->setText(QString::number(f_fun_word)); 159 | ui->txtFrameLen->setText(QString::number(f_length)); 160 | ui->txtFrameErrorNum->setText(QString::number(recvErrorNum)); 161 | } 162 | 163 | // 接收字节计数 164 | recvNum += recBuf.size(); 165 | // 状态栏显示计数值 166 | setNumOnLabel(lblRecvNum, "R: ", recvNum); 167 | 168 | // 判断是否为16进制接收,将以后接收的数据全部转换为16进制显示(先前接收的部分在多选框槽函数中进行转换。最好多选框和接收区组成一个自定义控件,方便以后调用) 169 | if(ui->chkRec->checkState() == false){ 170 | // GB2312编码输入 171 | QString strb = QString::fromLocal8Bit(recBuf);//QString::fromUtf8(recBuf);//QString::fromLatin1(recBuf); 172 | // 在当前位置插入文本,不会发生换行。如果没有移动光标到文件结尾,会导致文件超出当前界面显示范围,界面也不会向下滚动。 173 | ui->txtRec->insertPlainText(strb); 174 | }else{ 175 | // 16进制显示,并转换为大写 176 | QString str1 = recBuf.toHex().toUpper();//.data(); 177 | // 添加空格 178 | QString str2; 179 | for(int i = 0; itxtRec->insertPlainText(str2); 185 | //ui->txtRec->insertPlainText(recBuf.toHex()); 186 | } 187 | 188 | // 移动光标到文本结尾 189 | ui->txtRec->moveCursor(QTextCursor::End); 190 | 191 | // 将文本追加到末尾显示,会导致插入的文本换行 192 | /*ui->txtRec->appendPlainText(recBuf);*/ 193 | 194 | /*// 在当前位置插入文本,不会发生换行。如果没有移动光标到文件结尾,会导致文件超出当前界面显示范围,界面也不会向下滚动。 195 | ui->txtRec->insertPlainText(recBuf); 196 | ui->txtRec->moveCursor(QTextCursor::End);*/ 197 | 198 | // 利用一个QString去获取消息框文本,再将新接收到的消息添加到QString尾部,但感觉效率会比当前位置插入低。也不会发生换行 199 | /*QString txtBuf; 200 | txtBuf = ui->txtRec->toPlainText(); 201 | txtBuf += recBuf; 202 | ui->txtRec->setPlainText(txtBuf); 203 | ui->txtRec->moveCursor(QTextCursor::End);*/ 204 | 205 | // 利用一个QString去缓存接收到的所有消息,效率会比上面高一点。但清空接收的时候,要将QString一并清空。 206 | /*static QString txtBuf; 207 | txtBuf += recBuf; 208 | ui->txtRec->setPlainText(txtBuf); 209 | ui->txtRec->moveCursor(QTextCursor::End);*/ 210 | } 211 | 212 | // 打开/关闭串口 槽函数 213 | void MainWindow::on_btnSwitch_clicked() 214 | { 215 | 216 | QSerialPort::BaudRate baudRate; 217 | QSerialPort::DataBits dataBits; 218 | QSerialPort::StopBits stopBits; 219 | QSerialPort::Parity checkBits; 220 | 221 | // 获取串口波特率 222 | baudRate = (QSerialPort::BaudRate)ui->cmbBaudRate->currentText().toUInt(); 223 | // 获取串口数据位 224 | dataBits = (QSerialPort::DataBits)ui->cmbData->currentText().toUInt(); 225 | // 获取串口停止位 226 | if(ui->cmbStop->currentText() == "1"){ 227 | stopBits = QSerialPort::OneStop; 228 | }else if(ui->cmbStop->currentText() == "1.5"){ 229 | stopBits = QSerialPort::OneAndHalfStop; 230 | }else if(ui->cmbStop->currentText() == "2"){ 231 | stopBits = QSerialPort::TwoStop; 232 | }else{ 233 | stopBits = QSerialPort::OneStop; 234 | } 235 | 236 | // 获取串口奇偶校验位 237 | if(ui->cmbCheck->currentText() == "无"){ 238 | checkBits = QSerialPort::NoParity; 239 | }else if(ui->cmbCheck->currentText() == "奇校验"){ 240 | checkBits = QSerialPort::OddParity; 241 | }else if(ui->cmbCheck->currentText() == "偶校验"){ 242 | checkBits = QSerialPort::EvenParity; 243 | }else{ 244 | checkBits = QSerialPort::NoParity; 245 | } 246 | 247 | // 想想用 substr strchr怎么从带有信息的字符串中提前串口号字符串 248 | // 初始化串口属性,设置 端口号、波特率、数据位、停止位、奇偶校验位数 249 | mySerialPort->setBaudRate(baudRate); 250 | mySerialPort->setDataBits(dataBits); 251 | mySerialPort->setStopBits(stopBits); 252 | mySerialPort->setParity(checkBits); 253 | //mySerialPort->setPortName(ui->cmbSerialPort->currentText());// 不匹配带有串口设备信息的文本 254 | // 匹配带有串口设备信息的文本 255 | QString spTxt = ui->cmbSerialPort->currentText(); 256 | spTxt = spTxt.section(':', 0, 0);//spTxt.mid(0, spTxt.indexOf(":")); 257 | //qDebug() << spTxt; 258 | mySerialPort->setPortName(spTxt); 259 | 260 | // 根据初始化好的串口属性,打开串口 261 | // 如果打开成功,反转打开按钮显示和功能。打开失败,无变化,并且弹出错误对话框。 262 | if(ui->btnSwitch->text() == "打开串口"){ 263 | if(mySerialPort->open(QIODevice::ReadWrite) == true){ 264 | //QMessageBox:: 265 | ui->btnSwitch->setText("关闭串口"); 266 | // 让端口号下拉框不可选,避免误操作(选择功能不可用,控件背景为灰色) 267 | ui->cmbSerialPort->setEnabled(false); 268 | ui->cmbBaudRate->setEnabled(false); 269 | ui->cmbStop->setEnabled(false); 270 | ui->cmbData->setEnabled(false); 271 | ui->cmbCheck->setEnabled(false); 272 | }else{ 273 | QMessageBox::critical(this, "错误提示", "串口打开失败!!!\r\n\r\n该串口可能被占用,请选择正确的串口\r\n或者波特率过高,超出硬件支持范围"); 274 | } 275 | }else{ 276 | mySerialPort->close(); 277 | ui->btnSwitch->setText("打开串口"); 278 | // 端口号下拉框恢复可选,避免误操作 279 | ui->cmbSerialPort->setEnabled(true); 280 | ui->cmbBaudRate->setEnabled(true); 281 | ui->cmbStop->setEnabled(true); 282 | ui->cmbData->setEnabled(true); 283 | ui->cmbCheck->setEnabled(true); 284 | } 285 | 286 | } 287 | 288 | // 发送按键槽函数 289 | // 如果勾选16进制发送,按照asc2的16进制发送 290 | void MainWindow::on_btnSend_clicked() 291 | { 292 | QByteArray sendData; 293 | // 判断是否为16进制发送,将发送区全部的asc2转换为16进制字符串显示,发送的时候转换为16进制发送 294 | if(ui->chkSend->checkState() == false){ 295 | // 字符串形式发送,GB2312编码用以兼容大多数单片机 296 | sendData = ui->txtSend->toPlainText().toLocal8Bit();// GB2312编码输出 297 | //sendData = ui->txtSend->toPlainText().toUtf8();// Unicode编码输出 298 | //sendData = ui->txtSend->toPlainText().toLatin1(); 299 | }else{ 300 | // 16进制发送,不要用.data(),.data()返回的是字符数组,0x00在ASC2中的意义为NUL,也就是'\0'结束符,所以遇到0x00就会终止 301 | //sendData = QByteArray::fromHex(ui->txtSend->toPlainText().toUtf8());// Unicode编码输出 302 | sendData = QByteArray::fromHex(ui->txtSend->toPlainText().toLocal8Bit());// GB2312编码输出 303 | } 304 | 305 | // 如发送成功,会返回发送的字节长度。失败,返回-1。 306 | int a = mySerialPort->write(sendData); 307 | // 发送字节计数并显示 308 | if(a > 0) 309 | { 310 | // 发送字节计数 311 | sendNum += a; 312 | // 状态栏显示计数值 313 | setNumOnLabel(lblSendNum, "S: ", sendNum); 314 | } 315 | 316 | } 317 | 318 | // 状态栏标签显示计数值 319 | void MainWindow::setNumOnLabel(QLabel *lbl, QString strS, long num) 320 | { 321 | // 标签显示 322 | QString strN; 323 | strN.sprintf("%ld", num); 324 | QString str = strS + strN; 325 | lbl->setText(str); 326 | } 327 | 328 | void MainWindow::on_btnClearRec_clicked() 329 | { 330 | ui->txtRec->clear(); 331 | // 清除发送、接收字节计数 332 | sendNum = 0; 333 | recvNum = 0; 334 | tSend = 0; 335 | tRecv = 0; 336 | // 状态栏显示计数值 337 | setNumOnLabel(lblSendNum, "S: ", sendNum); 338 | setNumOnLabel(lblRecvNum, "R: ", recvNum); 339 | // 清空帧数量 340 | recvFrameNum=0, recvFrameRate=0, recvErrorNum=0, tFrame=0; 341 | setNumOnLabel(lblRecvFrameNum, "FNum: ", recvFrameNum); 342 | ui->txtFrameErrorNum->setText(QString::number(recvErrorNum)); 343 | } 344 | 345 | void MainWindow::on_btnClearSend_clicked() 346 | { 347 | ui->txtSend->clear(); 348 | // 清除发送字节计数 349 | sendNum = 0; 350 | tSend = 0; 351 | // 状态栏显示计数值 352 | setNumOnLabel(lblSendNum, "S: ", sendNum); 353 | } 354 | 355 | // 先前接收的部分在多选框状态转换槽函数中进行转换。(最好多选框和接收区组成一个自定义控件,方便以后调用) 356 | void MainWindow::on_chkRec_stateChanged(int arg1) 357 | { 358 | // 获取文本字符串 359 | QString txtBuf = ui->txtRec->toPlainText(); 360 | 361 | // 获取多选框状态,未选为0,选中为2 362 | // 为0时,多选框未被勾选,接收区先前接收的16进制数据转换为asc2字符串格式 363 | if(arg1 == 0){ 364 | 365 | //QString str1 = QByteArray::fromHex(txtBuf.toUtf8()); 366 | //QString str1 = QByteArray::fromHex(txtBuf.toLocal8Bit()); 367 | //把gb2312编码转换成unicode 368 | QString str1 = QTextCodec::codecForName("GB2312")->toUnicode(QByteArray::fromHex(txtBuf.toLocal8Bit())); 369 | // 文本控件清屏,显示新文本 370 | ui->txtRec->clear(); 371 | ui->txtRec->insertPlainText(str1); 372 | // 移动光标到文本结尾 373 | ui->txtRec->moveCursor(QTextCursor::End); 374 | 375 | }else{// 不为0时,多选框被勾选,接收区先前接收asc2字符串转换为16进制显示 376 | 377 | //QString str1 = txtBuf.toUtf8().toHex().toUpper();// Unicode编码输出 378 | QString str1 = txtBuf.toLocal8Bit().toHex().toUpper();// GB2312编码输出 379 | // 添加空格 380 | QByteArray str2; 381 | for(int i = 0; itxtRec->clear(); 388 | ui->txtRec->insertPlainText(str2); 389 | // 移动光标到文本结尾 390 | ui->txtRec->moveCursor(QTextCursor::End); 391 | 392 | } 393 | } 394 | 395 | // 先前发送区的部分在多选框状态转换槽函数中进行转换。(最好多选框和发送区组成一个自定义控件,方便以后调用) 396 | void MainWindow::on_chkSend_stateChanged(int arg1) 397 | { 398 | // 获取文本字符串 399 | QString txtBuf = ui->txtSend->toPlainText(); 400 | 401 | // 获取多选框状态,未选为0,选中为2 402 | // 为0时,多选框未被勾选,将先前的发送区的16进制字符串转换为asc2字符串 403 | if(arg1 == 0){ 404 | 405 | //QByteArray str1 = QByteArray::fromHex(txtBuf.toUtf8());//仅能处理Unicode编码,因为QString就是Unicode 406 | //QString str1 = QString::fromLocal8Bit(txtBuf.toUtf8());//仅能处理GB2312编码,Unicode的数据无论如何都会乱码 407 | //把gb2312编码转换成unicode 408 | QString str1 = QTextCodec::codecForName("GB2312")->toUnicode(QByteArray::fromHex(txtBuf.toLocal8Bit())); 409 | // 文本控件清屏,显示新文本 410 | ui->txtSend->clear(); 411 | ui->txtSend->insertPlainText(str1); 412 | // 移动光标到文本结尾 413 | ui->txtSend->moveCursor(QTextCursor::End); 414 | 415 | }else{// 多选框被勾选,将先前的发送区的asc2字符串转换为16进制字符串 416 | 417 | //QByteArray str1 = txtBuf.toUtf8().toHex().toUpper();// Unicode编码输出 418 | QString str1 = txtBuf.toLocal8Bit().toHex().toUpper();// GB2312编码输出 419 | // 添加空格 420 | QString str2; 421 | for(int i = 0; itxtSend->clear(); 428 | ui->txtSend->insertPlainText(str2); 429 | // 移动光标到文本结尾 430 | ui->txtSend->moveCursor(QTextCursor::End); 431 | 432 | } 433 | } 434 | 435 | // 定时发送开关 选择复选框 436 | void MainWindow::on_chkTimSend_stateChanged(int arg1) 437 | { 438 | // 获取复选框状态,未选为0,选中为2 439 | if(arg1 == 0){ 440 | timSend->stop(); 441 | // 时间输入框恢复可选 442 | ui->txtSendMs->setEnabled(true); 443 | }else{ 444 | // 对输入的值做限幅,小于20ms会弹出对话框提示 445 | if(ui->txtSendMs->text().toInt() >= 20){ 446 | timSend->start(ui->txtSendMs->text().toInt());// 设置定时时长,重新计数 447 | // 让时间输入框不可选,避免误操作(输入功能不可用,控件背景为灰色) 448 | ui->txtSendMs->setEnabled(false); 449 | }else{ 450 | ui->chkTimSend->setCheckState(Qt::Unchecked); 451 | QMessageBox::critical(this, "错误提示", "定时发送的最小间隔为 20ms\r\n请确保输入的值 >=20"); 452 | } 453 | } 454 | } 455 | 456 | // 发送速率、接收速率统计-定时器 457 | void MainWindow::dataRateCalculate(void) 458 | { 459 | sendRate = sendNum - tSend; // * ui->cmbData->currentText().toUInt(); 460 | recvRate = recvNum - tRecv; // * ui->cmbData->currentText().toUInt(); 461 | recvFrameRate = recvFrameNum - tFrame; 462 | 463 | setNumOnLabel(lblSendRate, "Byte/s: ", sendRate); 464 | setNumOnLabel(lblRecvRate, "Byte/s: ", recvRate); 465 | setNumOnLabel(lblFrameRate, "FPS: ", recvFrameRate); 466 | tSend = sendNum; 467 | tRecv = recvNum; 468 | tFrame = recvFrameNum; 469 | } 470 | 471 | // "显示波形界面" 按键槽函数 472 | // 记得把plot在析构中释放掉,不然很容易下次运行崩溃 473 | void MainWindow::on_pushButton_clicked() 474 | { 475 | plot->show();// 显示波形绘图窗口 476 | } 477 | 478 | /* 479 | void MainWindow::xFrameDataFilter(QByteArray *str) 480 | { 481 | int num = str->size(); 482 | if(num) 483 | { 484 | 485 | } 486 | }*/ 487 | 488 | // 帧过滤 489 | // 适用于有帧头、功能字、有效字段长度、校验位的接收,无帧尾 490 | void MainWindow::xFrameDataFilter(QByteArray *str, short value[]) 491 | { 492 | int num = str->size(); 493 | if(num) 494 | { 495 | for(int i=0; iat(i); // 从接收缓存区读取一个字节 498 | if (f_h_flag == 1) // 有帧头。判断功能字、有效字段长度,接收消息 499 | { 500 | if (f_fun_word) // 有帧头,有功能字 501 | { 502 | if (f_length) // 有帧头,有功能字,有有效字节长度 503 | { 504 | if((tnum-4) widget_5->isHidden()){ 525 | QByteArray str1; 526 | for(int i=0; i<(tnum+1); i++) 527 | { 528 | str1.append(chrtmp[i]); 529 | } 530 | //ui->txtFrameEffective->appendPlainText(str1.toHex().toUpper()); 531 | str1 = str1.toHex().toUpper(); 532 | QString str2; 533 | for(int i = 0; itxtFrameEffective->appendPlainText(str2); 539 | } 540 | 541 | // 根据功能字进行功能解析,自动根据帧长度解析为对应的short值。 542 | if(f_fun_word == FunWord_WF) 543 | { 544 | for(int i=0; i<(f_length/2); i++) 545 | { 546 | value[i] = ((short)chrtmp[i*2+4] << 8) | chrtmp[i*2+4+1]; 547 | } 548 | } 549 | 550 | // 显示波形(在这里显示可以处理多帧粘包,避免多帧粘包只显示一帧的情况) 551 | // 将解析出的short数组,传入波形图,进行绘图 552 | if(!plot->isHidden()){ 553 | plot->ShowPlot_WaveForm(plot->pPlot1, value); 554 | } 555 | 556 | }else{ 557 | ++recvErrorNum;// 误码帧数量计数 558 | } 559 | 560 | // 清0重新接收 561 | tnum = 0; 562 | // 清空标志位 563 | f_h1_flag = 0; 564 | f_h_flag = 0; 565 | f_fun_word = 0; 566 | f_length = 0; 567 | 568 | } 569 | // 把上面下面的判断标志位 == 1去掉 570 | 571 | 572 | } // 有帧头,有功能字,判断是否是有效字节长度 573 | else 574 | { 575 | if(chrtmp[tnum] <= ValidByteLength) 576 | { 577 | f_length = chrtmp[tnum];// 记录当前帧的有效字节长度 578 | tnum ++; 579 | } 580 | else 581 | { 582 | // 清0重新接收 583 | tnum = 0; 584 | // 清空标志位 585 | f_h1_flag = 0; 586 | f_h_flag = 0; 587 | f_fun_word = 0; 588 | } 589 | } 590 | } 591 | else // 有帧头,无功能字,判断是否为有效功能字 592 | { 593 | if ((chrtmp[tnum] == FunWord_WF) || chrtmp[tnum] == FunWord_SM) 594 | { 595 | f_fun_word = chrtmp[tnum];//记录功能字 596 | tnum ++; 597 | } 598 | else 599 | { 600 | // 清0重新接收 601 | tnum = 0; 602 | // 清空标志位 603 | f_h1_flag = 0; 604 | f_h_flag = 0; 605 | } 606 | } 607 | } 608 | else // 没有接收到帧头 609 | { 610 | if (f_h1_flag == 1) //没有帧头,有帧头1。下一步判断是否为第2个字节 611 | { 612 | if (chrtmp[tnum] == Frame_Header2) // 如果为帧头的第2个字节,接收到帧头标志位标志位置1,tnum自增 613 | { 614 | f_h_flag = 1; 615 | tnum ++; 616 | } 617 | else 618 | { 619 | // 这里再添加一个判断,出现 3A 3A 3B xx的情况,如果没有这个判断会重新计数,导致丢帧 620 | if(chrtmp[tnum] == Frame_Header1){ 621 | f_h1_flag = 1; 622 | tnum = 1; 623 | }else{ 624 | // 重新计数,但如果出现 3A 3A 3B xx的情况,会导致丢帧,要加上上面的判断 625 | f_h1_flag = 0; 626 | tnum = 0; 627 | } 628 | } 629 | } 630 | else //没有帧头,没有有帧头1。下一步判断,是否为帧头的第1个字节 631 | { 632 | if (chrtmp[tnum] == Frame_Header1) // 如果为帧头的第1个字节,标志位置1,tnum自增 633 | { 634 | f_h1_flag = 1; 635 | tnum ++; 636 | } 637 | else // 否则,标志位清0,tnum清0 638 | { 639 | tnum = 0; 640 | } 641 | } 642 | } 643 | 644 | static int xx=0; 645 | // 判断多长的数据没有换行符,如果超过2000,会人为向数据接收区添加换行,来保证CPU占用率不会过高,不会导致卡顿 646 | // 但由于是先插入换行,后插入接收到的数据,所以每一箩数据并不是2000 647 | if(chrtmp[tnum] != 0x0A){ 648 | ++xx; 649 | if(xx > 2000){ 650 | ui->txtRec->appendPlainText(""); 651 | ui->txtRec->appendPlainText(""); 652 | xx=0; 653 | } 654 | }else{ 655 | xx=0; 656 | } 657 | 658 | // 大于MaxFrameLength个字节的帧不接收 659 | if (tnum > (MaxFrameLength - 1) ) 660 | { 661 | tnum = 0; 662 | f_h1_flag = 0; 663 | f_h_flag = 0; 664 | f_t1_flag = 0; 665 | //f_fun_word = 0; 666 | //f_length = 0; 667 | continue; 668 | } 669 | } 670 | } 671 | } 672 | 673 | /* 674 | // 适用于有帧头帧尾、无功能字和有效字段长度的接收 675 | void MainWindow::xFrameDataFilter(QByteArray *str) 676 | { 677 | int num = str->size(); 678 | if(num) 679 | { 680 | for(int i=0; iat(i); // 从接收缓存区读取一个字节 683 | if (f_h_flag == 1) // 有帧头,判断帧尾,接收消息 684 | { 685 | if (f_t1_flag == 1) //有帧头,有帧尾1 686 | { 687 | if (chrtmp[tnum] == Frame_Tail2) 688 | { 689 | tnum ++; 690 | 691 | // 接收到一帧有效帧 692 | // 用户处理代码 // 693 | // 根据接收到的数量,合成帧字段 694 | // 调试信息输出,显示有效帧的内容(16进制显示) 695 | if(!ui->widget_5->isHidden()){ 696 | QByteArray str1; 697 | for(int i=0; itxtFrameEffective->appendPlainText(str1.toHex().toUpper()); 702 | str1 = str1.toHex().toUpper(); 703 | QString str2; 704 | for(int i = 0; itxtFrameEffective->appendPlainText(str2); 710 | } 711 | 712 | // 处理完用户代码,重新接收计数 // 713 | tnum = 0; 714 | // 清空标志位,之前一直忘了 715 | f_h1_flag = 0; 716 | f_h_flag = 0; 717 | f_t1_flag = 0; 718 | 719 | // 将接收到符合帧定义的帧,原路发送回去 // 720 | //return str1; 721 | //ui->lineEdit->setText(str1.toHex().toUpper()); 722 | 723 | } 724 | else 725 | { 726 | f_t1_flag = 0; 727 | tnum ++; 728 | } 729 | } 730 | else // 有帧头,无帧尾1 731 | { 732 | if (chrtmp[tnum] == Frame_Tail1) 733 | { 734 | f_t1_flag = 1; 735 | tnum ++; 736 | } 737 | else // 接收消息包中间内容 738 | { 739 | tnum ++; 740 | } 741 | } 742 | } 743 | else // 没有接收到帧头 744 | { 745 | if (f_h1_flag == 1) //没有帧头,有帧头1。下一步判断是否为第2个字节 746 | { 747 | if (chrtmp[tnum] == Frame_Header2) // 如果为帧头的第2个字节,接收到帧头标志位标志位置1,tnum自增 748 | { 749 | f_h_flag = 1; 750 | tnum ++; 751 | } 752 | else 753 | { 754 | // 这里再添加一个判断,出现 3A 3A 3B xx的情况,如果没有这个判断会重新计数,导致丢帧 755 | if(chrtmp[tnum] == Frame_Header1){ 756 | f_h1_flag = 1; 757 | tnum = 1; 758 | }else{ 759 | // 重新计数,但如果出现 3A 3A 3B xx的情况,会导致丢帧,要加上上面的判断 760 | f_h1_flag = 0; 761 | tnum = 0; 762 | } 763 | } 764 | } 765 | else //没有帧头,没有有帧头1。下一步判断,是否为帧头的第1个字节 766 | { 767 | if (chrtmp[tnum] == Frame_Header1) // 如果为帧头的第1个字节,标志位置1,tnum自增 768 | { 769 | f_h1_flag = 1; 770 | tnum ++; 771 | } 772 | else // 否则,标志位清0,tnum清0 773 | { 774 | tnum = 0; 775 | } 776 | } 777 | } 778 | 779 | // 大于MaxFrameLength个字节的帧不接收 780 | if (tnum > (MaxFrameLength - 1) ) 781 | { 782 | tnum = 0; 783 | f_h1_flag = 0; 784 | f_h_flag = 0; 785 | f_t1_flag = 0; 786 | continue; 787 | } 788 | } 789 | } 790 | }*/ 791 | 792 | void MainWindow::on_btnFramaDebug_clicked() 793 | { 794 | if(ui->widget_5->isHidden()){ 795 | ui->widget_5->show(); 796 | ui->frame_3->show(); 797 | }else{ 798 | ui->widget_5->hide(); 799 | ui->frame_3->hide(); 800 | } 801 | } 802 | -------------------------------------------------------------------------------- /mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "plot.h" 12 | 13 | // 接收缓冲区大小,单位字节 14 | #define BufferSize 50 15 | // 最大帧长度,单位字节 16 | #define MaxFrameLength 40+5 // 对最大帧长度加以限定,防止接收到过长的帧数据 17 | // 完整的帧头,2个字节 18 | #define Frame_Header1 0x3A // 串口接收消息包的帧头的第1个字节 19 | #define Frame_Header2 0x3B // 串口接收消息包的帧头的第2个字节 20 | // 完整的帧尾,2个字节 21 | #define Frame_Tail1 0x7E // 串口接收消息包的帧尾的第1个字节 22 | #define Frame_Tail2 0x7F // 串口接收消息包的帧尾的第2个字节 23 | 24 | // 功能字1,0x01,自定义波形显示 25 | #define FunWord_WF 0x01 26 | // 功能字2,0x02,信息绘图页面显示,暂时未用 27 | #define FunWord_SM 0x02 28 | // 帧数据中包含有效字节的最大长度 29 | #define ValidByteLength 40 // 对最大帧长度加以限定,防止接收到过长的帧数据 30 | 31 | QT_BEGIN_NAMESPACE 32 | namespace Ui { class MainWindow; } 33 | QT_END_NAMESPACE 34 | 35 | class MainWindow : public QMainWindow 36 | { 37 | Q_OBJECT 38 | 39 | public: 40 | MainWindow(QWidget *parent = nullptr); 41 | ~MainWindow(); 42 | 43 | // 绘图事件 44 | void paintEvent(QPaintEvent *); 45 | 46 | private slots: 47 | void on_btnSwitch_clicked(); 48 | 49 | void serialPortRead_Slot(); 50 | 51 | void on_btnSend_clicked(); 52 | 53 | void on_btnClearRec_clicked(); 54 | 55 | void on_btnClearSend_clicked(); 56 | 57 | void on_chkRec_stateChanged(int arg1); 58 | 59 | void on_chkSend_stateChanged(int arg1); 60 | 61 | void on_chkTimSend_stateChanged(int arg1); 62 | 63 | void on_pushButton_clicked(); 64 | 65 | void on_btnFramaDebug_clicked(); 66 | 67 | void dataRateCalculate(void); 68 | 69 | private: 70 | Ui::MainWindow *ui; 71 | // 波形绘图窗口 72 | Plot *plot = NULL;// 必须初始化为空,否则后面NEW判断的时候会异常结束 73 | 74 | QSerialPort *mySerialPort; 75 | 76 | // 发送、接收字节计数 77 | long sendNum=0, recvNum=0, tSend=0, tRecv=0;// 发送/接收数量,历史发送/接收数量,Byte 78 | long sendRate=0, recvRate=0;// 发送/接收速率,Byte/s 79 | long recvFrameNum=0, recvFrameRate=0, recvErrorNum=0, tFrame=0;// 接收的有效帧数,帧速率,误码帧数量,历史帧数量 80 | QLabel *lblSendNum, *lblRecvNum, *lblSendRate, *lblRecvRate, *lblRecvFrameNum, *lblFrameRate; 81 | 82 | void setNumOnLabel(QLabel *lbl, QString strS, long num); 83 | 84 | /* 与帧过滤有关的标志位 */ 85 | //int snum = 0; // 系统串口接收缓存区的可用字节数 86 | int tnum = 0; // 用户串口接收缓存的指针位置 87 | unsigned char chrtmp[BufferSize]; // 用户串口接收缓存,将缓存的数据放入这里处理 88 | int f_h1_flag = 0; // 接收到帧头的第一个字节标志位 89 | int f_h_flag = 0; // 接收到帧头标志位 90 | int f_t1_flag = 0; // 接收到帧尾的第一个字节标志位 91 | // 即是标志位,也包含信息 92 | int f_fun_word = 0; // 功能字,限定为0x01、0x02 93 | int f_length = 0; // 帧数据中包含有效字节的长度 94 | void xFrameDataFilter(QByteArray *str, short value[]); 95 | //QByteArray xFrameDataFilter(QByteArray *str); 96 | 97 | // 定时发送-定时器 98 | QTimer *timSend; 99 | // 发送速率、接收速率统计-定时器 100 | QTimer *timRate; 101 | }; 102 | #endif // MAINWINDOW_H 103 | -------------------------------------------------------------------------------- /mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 800 10 | 720 11 | 12 | 13 | 14 | 15 | 240 16 | 0 17 | 18 | 19 | 20 | 21 | 16777215 22 | 16777215 23 | 24 | 25 | 26 | MainWindow 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 240 35 | 0 36 | 37 | 38 | 39 | 40 | 240 41 | 16777215 42 | 43 | 44 | 45 | Qt::NoFocus 46 | 47 | 48 | 49 | 50 | 51 | QFrame::Box 52 | 53 | 54 | QFrame::Sunken 55 | 56 | 57 | 58 | 59 | 60 | Qt::Vertical 61 | 62 | 63 | 64 | 20 65 | 40 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 显示波形界面 74 | 75 | 76 | 77 | 78 | 79 | 80 | 清除接收/统计 81 | 82 | 83 | 84 | 85 | 86 | 87 | 16进制接收 88 | 89 | 90 | 91 | 92 | 93 | 94 | 帧捕获调试 95 | 96 | 97 | 98 | 99 | 100 | 101 | 16进制发送 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 0 110 | 219 111 | 112 | 113 | 114 | 115 | 16777215 116 | 219 117 | 118 | 119 | 120 | QFrame::Box 121 | 122 | 123 | QFrame::Sunken 124 | 125 | 126 | 127 | 5 128 | 129 | 130 | 5 131 | 132 | 133 | 134 | 135 | 串口选择 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 波特率 149 | 150 | 151 | 152 | 153 | 154 | 155 | true 156 | 157 | 158 | 10 159 | 160 | 161 | 20 162 | 163 | 164 | 165 | 1200 166 | 167 | 168 | 169 | 170 | 2400 171 | 172 | 173 | 174 | 175 | 4800 176 | 177 | 178 | 179 | 180 | 9600 181 | 182 | 183 | 184 | 185 | 14400 186 | 187 | 188 | 189 | 190 | 19200 191 | 192 | 193 | 194 | 195 | 38400 196 | 197 | 198 | 199 | 200 | 43000 201 | 202 | 203 | 204 | 205 | 57600 206 | 207 | 208 | 209 | 210 | 76800 211 | 212 | 213 | 214 | 215 | 115200 216 | 217 | 218 | 219 | 220 | 128000 221 | 222 | 223 | 224 | 225 | 230400 226 | 227 | 228 | 229 | 230 | 256000 231 | 232 | 233 | 234 | 235 | 460800 236 | 237 | 238 | 239 | 240 | 921600 241 | 242 | 243 | 244 | 245 | 1382400 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 停止位 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 1 262 | 263 | 264 | 265 | 266 | 1.5 267 | 268 | 269 | 270 | 271 | 2 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 数据位 280 | 281 | 282 | 283 | 284 | 285 | 286 | 3 287 | 288 | 289 | 290 | 5 291 | 292 | 293 | 294 | 295 | 6 296 | 297 | 298 | 299 | 300 | 7 301 | 302 | 303 | 304 | 305 | 8 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 奇偶校验 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 奇校验 327 | 328 | 329 | 330 | 331 | 偶校验 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 串口操作 340 | 341 | 342 | 343 | 344 | 345 | 346 | 打开串口 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 清除发送 360 | 361 | 362 | 363 | 364 | 365 | 366 | Qt::Vertical 367 | 368 | 369 | 370 | 20 371 | 40 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 0 384 | 100 385 | 386 | 387 | 388 | 389 | 16777215 390 | 100 391 | 392 | 393 | 394 | 串口数据 - 发送区 395 | 396 | 397 | 398 | 0 399 | 400 | 401 | 0 402 | 403 | 404 | 405 | 406 | 407 | 0 408 | 0 409 | 410 | 411 | 412 | 413 | 16777215 414 | 16777215 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | true 426 | 427 | 428 | 429 | 523 430 | 151 431 | 432 | 433 | 434 | 435 | 16777215 436 | 171 437 | 438 | 439 | 440 | 441 | 442 | 0 443 | 0 444 | 531 445 | 61 446 | 447 | 448 | 449 | 帧过滤 缓冲区内容 450 | 451 | 452 | 453 | 454 | 0 455 | 20 456 | 531 457 | 41 458 | 459 | 460 | 461 | 462 | 439 463 | 0 464 | 465 | 466 | 467 | 468 | 16777215 469 | 41 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 0 478 | 70 479 | 531 480 | 91 481 | 482 | 483 | 484 | 有效帧内容 485 | 486 | 487 | 488 | 489 | 0 490 | 20 491 | 531 492 | 71 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 240 504 | 0 505 | 506 | 507 | 508 | 509 | 240 510 | 16777215 511 | 512 | 513 | 514 | QFrame::Box 515 | 516 | 517 | QFrame::Sunken 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 缓冲区指针位置 527 | 528 | 529 | 530 | 531 | 532 | 533 | 帧头首字节标志位 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 帧有效字节数 544 | 545 | 546 | 547 | 548 | 549 | 550 | 帧头次字节标志位 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 帧功能字 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 误码帧数量 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 240 588 | 100 589 | 590 | 591 | 592 | 593 | 240 594 | 100 595 | 596 | 597 | 598 | QFrame::Box 599 | 600 | 601 | QFrame::Sunken 602 | 603 | 604 | 605 | 606 | 607 | 608 | 0 609 | 60 610 | 611 | 612 | 613 | 614 | 125 615 | 16777215 616 | 617 | 618 | 619 | 发送 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 70 628 | 16777215 629 | 630 | 631 | 632 | 633 | 0 634 | 635 | 636 | 0 637 | 638 | 639 | 640 | 641 | 642 | 60 643 | 16777215 644 | 645 | 646 | 647 | 1000 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 20 656 | 16777215 657 | 658 | 659 | 660 | 661 | 12 662 | 663 | 664 | 665 | ms 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 100 677 | 16777215 678 | 679 | 680 | 681 | 定时发送 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 串口数据 - 接收区 692 | 693 | 694 | 695 | 0 696 | 697 | 698 | 0 699 | 700 | 701 | 702 | 703 | true 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | 0 716 | 0 717 | 800 718 | 23 719 | 720 | 721 | 722 | 723 | 724 | 725 | 0 726 | 22 727 | 728 | 729 | 730 | 731 | 16777215 732 | 22 733 | 734 | 735 | 736 | 737 | 738 | 739 | myComboBox 740 | QComboBox 741 |
mycombobox.h
742 |
743 |
744 | 745 | 746 |
747 | -------------------------------------------------------------------------------- /mycombobox.cpp: -------------------------------------------------------------------------------- 1 | #include "mycombobox.h" 2 | 3 | myComboBox::myComboBox(QWidget *parent) : QComboBox(parent) 4 | { 5 | // 扫描可用串口 6 | scanActivePort(); 7 | } 8 | 9 | // 扫描可用串口 10 | void myComboBox::scanActivePort() 11 | { 12 | // 先清空列表项,防止多次刷新后重叠 13 | clear(); 14 | 15 | // 串口端口号列表 16 | QStringList serialPortName; 17 | 18 | // 自动扫描当前可用串口,返回值追加到字符数组中 19 | foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts()){ 20 | 21 | //serialPortName << info.portName();// 不携带有串口设备信息的文本 22 | 23 | // 携带有串口设备信息的文本 24 | QString serialPortInfo = info.portName() + ": " + info.description();// 串口设备信息,芯片/驱动名称 25 | //QString serialPortInfo = info.portName() + ": " + info.manufacturer();// 串口设备制造商 26 | //QString serialPortInfo = info.portName() + ": " + info.serialNumber();// 串口设备的序列号,没什么用 27 | //QString serialPortInfo = info.portName() + ": " + info.systemLocation();// 串口设备的系统位置,没什么用 28 | serialPortName << serialPortInfo; 29 | } 30 | // 可用串口号,显示到串口选择下拉框中 31 | this->addItems(serialPortName); 32 | } 33 | 34 | // 重写鼠标点击事件 35 | void myComboBox::mousePressEvent(QMouseEvent *event) 36 | { 37 | if(event->button() == Qt::LeftButton) 38 | { 39 | // 扫描可用串口 40 | scanActivePort(); 41 | 42 | // 弹出下拉框 43 | showPopup(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /mycombobox.h: -------------------------------------------------------------------------------- 1 | #ifndef MYCOMBOBOX_H 2 | #define MYCOMBOBOX_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | class myComboBox : public QComboBox 10 | { 11 | Q_OBJECT 12 | public: 13 | explicit myComboBox(QWidget *parent = nullptr); 14 | 15 | // 重写鼠标点击事件 16 | void mousePressEvent(QMouseEvent *event); 17 | 18 | signals: 19 | 20 | private: 21 | void scanActivePort(); 22 | 23 | }; 24 | 25 | #endif // MYCOMBOBOX_H 26 | -------------------------------------------------------------------------------- /pic.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /pic/ssCircle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhiliangMa/Qt_SerialPlot/a3b4af3d2159b62c275c3d4b55dc4452f41ef1a0/pic/ssCircle.png -------------------------------------------------------------------------------- /pic/ssCross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhiliangMa/Qt_SerialPlot/a3b4af3d2159b62c275c3d4b55dc4452f41ef1a0/pic/ssCross.png -------------------------------------------------------------------------------- /pic/ssCrossCircle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhiliangMa/Qt_SerialPlot/a3b4af3d2159b62c275c3d4b55dc4452f41ef1a0/pic/ssCrossCircle.png -------------------------------------------------------------------------------- /pic/ssCrossSquare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhiliangMa/Qt_SerialPlot/a3b4af3d2159b62c275c3d4b55dc4452f41ef1a0/pic/ssCrossSquare.png -------------------------------------------------------------------------------- /pic/ssDiamond.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhiliangMa/Qt_SerialPlot/a3b4af3d2159b62c275c3d4b55dc4452f41ef1a0/pic/ssDiamond.png -------------------------------------------------------------------------------- /pic/ssDisc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhiliangMa/Qt_SerialPlot/a3b4af3d2159b62c275c3d4b55dc4452f41ef1a0/pic/ssDisc.png -------------------------------------------------------------------------------- /pic/ssNone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhiliangMa/Qt_SerialPlot/a3b4af3d2159b62c275c3d4b55dc4452f41ef1a0/pic/ssNone.png -------------------------------------------------------------------------------- /pic/ssPeace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhiliangMa/Qt_SerialPlot/a3b4af3d2159b62c275c3d4b55dc4452f41ef1a0/pic/ssPeace.png -------------------------------------------------------------------------------- /pic/ssPlus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhiliangMa/Qt_SerialPlot/a3b4af3d2159b62c275c3d4b55dc4452f41ef1a0/pic/ssPlus.png -------------------------------------------------------------------------------- /pic/ssPlusCircle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhiliangMa/Qt_SerialPlot/a3b4af3d2159b62c275c3d4b55dc4452f41ef1a0/pic/ssPlusCircle.png -------------------------------------------------------------------------------- /pic/ssPlusSquare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhiliangMa/Qt_SerialPlot/a3b4af3d2159b62c275c3d4b55dc4452f41ef1a0/pic/ssPlusSquare.png -------------------------------------------------------------------------------- /pic/ssSquare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhiliangMa/Qt_SerialPlot/a3b4af3d2159b62c275c3d4b55dc4452f41ef1a0/pic/ssSquare.png -------------------------------------------------------------------------------- /pic/ssStar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhiliangMa/Qt_SerialPlot/a3b4af3d2159b62c275c3d4b55dc4452f41ef1a0/pic/ssStar.png -------------------------------------------------------------------------------- /pic/ssTriangle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhiliangMa/Qt_SerialPlot/a3b4af3d2159b62c275c3d4b55dc4452f41ef1a0/pic/ssTriangle.png -------------------------------------------------------------------------------- /pic/ssTriangleInverted.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZhiliangMa/Qt_SerialPlot/a3b4af3d2159b62c275c3d4b55dc4452f41ef1a0/pic/ssTriangleInverted.png -------------------------------------------------------------------------------- /plot.cpp: -------------------------------------------------------------------------------- 1 | #include "plot.h" 2 | #include "ui_plot.h" 3 | #include 4 | 5 | Plot::Plot(QWidget *parent) : 6 | QMainWindow(parent), 7 | ui(new Ui::Plot) 8 | { 9 | ui->setupUi(this); 10 | setWindowTitle("Plot"); 11 | 12 | // 给widget绘图控件,设置个别名,方便书写 13 | pPlot1 = ui->winPlot; 14 | // 状态栏指针 15 | sBar = statusBar(); 16 | 17 | // 初始化图表1 18 | QPlot_init(pPlot1); 19 | // 绘图图表的设置控件初始化,主要用于关联控件的信号槽 20 | QPlot_widget_init(); 21 | 22 | // 创建定时器,用于定时生成曲线坐标点数据 23 | timer = new QTimer(this); 24 | timer->setInterval(10); 25 | connect(timer,SIGNAL(timeout()),this,SLOT(TimeData_Update())); 26 | //timer->start(10); 27 | 28 | // 关联控件初始化 29 | ui->txtPointOriginX->setEnabled(false); 30 | // 图表重绘后,刷新原点坐标和范围 31 | connect(pPlot1,SIGNAL(afterReplot()),this,SLOT(repPlotCoordinate())); 32 | } 33 | 34 | Plot::~Plot() 35 | { 36 | delete ui; 37 | } 38 | 39 | 40 | // 绘图图表初始化 41 | void Plot::QPlot_init(QCustomPlot *customPlot) 42 | { 43 | // 添加曲线名称 44 | QStringList lineNames;//设置图例的文本 45 | lineNames << "波形1" << "波形2" << "波形3" << "波形4" << "波形5" << "波形6" << "波形7" << "波形8" << "波形9" << "波形10"\ 46 | << "波形11" << "波形12" << "波形13" << "波形14" << "波形15" << "波形16" << "波形17" << "波形18" << "波形19" << "波形20"; 47 | // 曲线初始颜色 48 | QColor initColor[20] = {QColor(0,146,152), QColor(162,0,124), QColor(241,175,0), QColor(27,79,147), QColor(229,70,70),\ 49 | QColor(0,140,94), QColor(178,0,31), QColor(91,189,43), QColor(0,219,219), QColor(172,172,172),\ 50 | QColor(0,178,191), QColor(197,124,172), QColor(243,194,70), QColor(115,136,193), QColor(245,168,154),\ 51 | QColor(152,208,185), QColor(223,70,41), QColor(175,215,136), QColor(157,255,255), QColor(0,0,0)};//QColor(255,255,255)};//白色 52 | // 图表添加20条曲线,并设置初始颜色,和图例名称 53 | for(int i=0; i<20; i++){ 54 | pCurve[i] = customPlot->addGraph(); 55 | pCurve[i]->setPen(QPen(QColor(initColor[i]))); 56 | pCurve[i]->setName(lineNames.at(i)); 57 | } 58 | 59 | // 设置背景颜色 60 | customPlot->setBackground(QColor(255,255,255)); 61 | // 设置背景选择框颜色 62 | ui->btnColourBack->setStyleSheet(QString("border:0px solid;background-color: %1;").arg(QColor(255,255,255).name())); 63 | 64 | // 曲线选择框颜色,与曲线同步颜色。这样写太复杂了,用控件指针数组在下面写过了,记得要在addGraph()之后才有效。 65 | //ui->btnColourCurve1->setStyleSheet("border:0px solid;background-color:rgb(0,146,152)"); 66 | //ui->btnColourCurve1->setStyleSheet(QString("border:0px solid;background-color: %1;").arg(initColor[0].name())); 67 | //ui->btnColourCurve20->setStyleSheet(QString("border:0px solid;background-color: %1;").arg(pCurve[]->pen().color().name())); 68 | 69 | // 设置坐标轴名称 70 | customPlot->xAxis->setLabel("X"); 71 | customPlot->yAxis->setLabel("Y"); 72 | 73 | // 设置x,y坐标轴显示范围 74 | pointCountX = ui->txtPointCountX->text().toUInt(); 75 | pointCountY = ui->txtPointCountY->text().toUInt(); 76 | customPlot->xAxis->setRange(0,pointCountX); 77 | customPlot->yAxis->setRange(pointCountY/2*-1,pointCountY/2); 78 | 79 | //customPlot->axisRect()->setupFullAxesBox();//四边安装轴并显示 80 | //customPlot->xAxis->ticker()->setTickOrigin(1);//改变刻度原点为1 81 | //customPlot->xAxis->setNumberFormat("gbc");//g灵活的格式,b漂亮的指数形式,c乘号改成× 82 | //customPlot->xAxis->setNumberPrecision(1);//精度1 83 | customPlot->xAxis->ticker()->setTickCount(ui->txtMainScaleNumX->text().toUInt());//11个主刻度 84 | customPlot->yAxis->ticker()->setTickCount(ui->txtMainScaleNumY->text().toUInt());//11个主刻度 85 | customPlot->xAxis->ticker()->setTickStepStrategy(QCPAxisTicker::tssReadability);//可读性优于设置 86 | customPlot->yAxis->ticker()->setTickStepStrategy(QCPAxisTicker::tssReadability);//可读性优于设置 87 | 88 | // 显示图表的图例 89 | customPlot->legend->setVisible(true); 90 | 91 | // 设置波形曲线的复选框字体颜色 92 | //ui->chkVisibleCurve1->setStyleSheet("QCheckBox{color:rgb(255,0,0)}");//设定前景颜色,就是字体颜色 93 | 94 | // 允许用户用鼠标拖动轴范围,以鼠标为中心滚轮缩放,点击选择图形: 95 | customPlot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectPlottables); 96 | // 设置鼠标滚轮的缩放倍率,如果不设置默认为0.85,大于1反方向缩放 97 | //customPlot->axisRect()->setRangeZoomFactor(0.5); 98 | // 设置鼠标滚轮缩放的轴方向,仅设置垂直轴。垂直轴和水平轴全选使用:Qt::Vertical | Qt::Horizontal 99 | customPlot->axisRect()->setRangeZoom(Qt::Vertical); 100 | } 101 | 102 | // 绘图图表的设置控件初始化,主要用于关联控件的信号槽 103 | void Plot::QPlot_widget_init(void) 104 | { 105 | // 获取控件指针数组,方便设置时编码书写 106 | pChkVisibleCurve[0] = ui->chkVisibleCurve1; pBtnColourCurve[0] = ui->btnColourCurve1; pTxtValueCurve[0] = ui->txtValueCurve1; pRdoBoldCurve[0] = ui->rdoBoldCurve1; 107 | pChkVisibleCurve[1] = ui->chkVisibleCurve2; pBtnColourCurve[1] = ui->btnColourCurve2; pTxtValueCurve[1] = ui->txtValueCurve2; pRdoBoldCurve[1] = ui->rdoBoldCurve2; 108 | pChkVisibleCurve[2] = ui->chkVisibleCurve3; pBtnColourCurve[2] = ui->btnColourCurve3; pTxtValueCurve[2] = ui->txtValueCurve3; pRdoBoldCurve[2] = ui->rdoBoldCurve3; 109 | pChkVisibleCurve[3] = ui->chkVisibleCurve4; pBtnColourCurve[3] = ui->btnColourCurve4; pTxtValueCurve[3] = ui->txtValueCurve4; pRdoBoldCurve[3] = ui->rdoBoldCurve4; 110 | pChkVisibleCurve[4] = ui->chkVisibleCurve5; pBtnColourCurve[4] = ui->btnColourCurve5; pTxtValueCurve[4] = ui->txtValueCurve5; pRdoBoldCurve[4] = ui->rdoBoldCurve5; 111 | pChkVisibleCurve[5] = ui->chkVisibleCurve6; pBtnColourCurve[5] = ui->btnColourCurve6; pTxtValueCurve[5] = ui->txtValueCurve6; pRdoBoldCurve[5] = ui->rdoBoldCurve6; 112 | pChkVisibleCurve[6] = ui->chkVisibleCurve7; pBtnColourCurve[6] = ui->btnColourCurve7; pTxtValueCurve[6] = ui->txtValueCurve7; pRdoBoldCurve[6] = ui->rdoBoldCurve7; 113 | pChkVisibleCurve[7] = ui->chkVisibleCurve8; pBtnColourCurve[7] = ui->btnColourCurve8; pTxtValueCurve[7] = ui->txtValueCurve8; pRdoBoldCurve[7] = ui->rdoBoldCurve8; 114 | pChkVisibleCurve[8] = ui->chkVisibleCurve9; pBtnColourCurve[8] = ui->btnColourCurve9; pTxtValueCurve[8] = ui->txtValueCurve9; pRdoBoldCurve[8] = ui->rdoBoldCurve9; 115 | pChkVisibleCurve[9] = ui->chkVisibleCurve10; pBtnColourCurve[9] = ui->btnColourCurve10; pTxtValueCurve[9] = ui->txtValueCurve10; pRdoBoldCurve[9] = ui->rdoBoldCurve10; 116 | pChkVisibleCurve[10] = ui->chkVisibleCurve11; pBtnColourCurve[10] = ui->btnColourCurve11; pTxtValueCurve[10] = ui->txtValueCurve11; pRdoBoldCurve[10] = ui->rdoBoldCurve11; 117 | pChkVisibleCurve[11] = ui->chkVisibleCurve12; pBtnColourCurve[11] = ui->btnColourCurve12; pTxtValueCurve[11] = ui->txtValueCurve12; pRdoBoldCurve[11] = ui->rdoBoldCurve12; 118 | pChkVisibleCurve[12] = ui->chkVisibleCurve13; pBtnColourCurve[12] = ui->btnColourCurve13; pTxtValueCurve[12] = ui->txtValueCurve13; pRdoBoldCurve[12] = ui->rdoBoldCurve13; 119 | pChkVisibleCurve[13] = ui->chkVisibleCurve14; pBtnColourCurve[13] = ui->btnColourCurve14; pTxtValueCurve[13] = ui->txtValueCurve14; pRdoBoldCurve[13] = ui->rdoBoldCurve14; 120 | pChkVisibleCurve[14] = ui->chkVisibleCurve15; pBtnColourCurve[14] = ui->btnColourCurve15; pTxtValueCurve[14] = ui->txtValueCurve15; pRdoBoldCurve[14] = ui->rdoBoldCurve15; 121 | pChkVisibleCurve[15] = ui->chkVisibleCurve16; pBtnColourCurve[15] = ui->btnColourCurve16; pTxtValueCurve[15] = ui->txtValueCurve16; pRdoBoldCurve[15] = ui->rdoBoldCurve16; 122 | pChkVisibleCurve[16] = ui->chkVisibleCurve17; pBtnColourCurve[16] = ui->btnColourCurve17; pTxtValueCurve[16] = ui->txtValueCurve17; pRdoBoldCurve[16] = ui->rdoBoldCurve17; 123 | pChkVisibleCurve[17] = ui->chkVisibleCurve18; pBtnColourCurve[17] = ui->btnColourCurve18; pTxtValueCurve[17] = ui->txtValueCurve18; pRdoBoldCurve[17] = ui->rdoBoldCurve18; 124 | pChkVisibleCurve[18] = ui->chkVisibleCurve19; pBtnColourCurve[18] = ui->btnColourCurve19; pTxtValueCurve[18] = ui->txtValueCurve19; pRdoBoldCurve[18] = ui->rdoBoldCurve19; 125 | pChkVisibleCurve[19] = ui->chkVisibleCurve20; pBtnColourCurve[19] = ui->btnColourCurve20; pTxtValueCurve[19] = ui->txtValueCurve20; pRdoBoldCurve[19] = ui->rdoBoldCurve20; 126 | 127 | pCmbLineStyle[0] = ui->cmbLineStyle1; pCmbScatterStyle[0] = ui->cmbScatterStyle1; 128 | pCmbLineStyle[1] = ui->cmbLineStyle2; pCmbScatterStyle[1] = ui->cmbScatterStyle2; 129 | pCmbLineStyle[2] = ui->cmbLineStyle3; pCmbScatterStyle[2] = ui->cmbScatterStyle3; 130 | pCmbLineStyle[3] = ui->cmbLineStyle4; pCmbScatterStyle[3] = ui->cmbScatterStyle4; 131 | pCmbLineStyle[4] = ui->cmbLineStyle5; pCmbScatterStyle[4] = ui->cmbScatterStyle5; 132 | pCmbLineStyle[5] = ui->cmbLineStyle6; pCmbScatterStyle[5] = ui->cmbScatterStyle6; 133 | pCmbLineStyle[6] = ui->cmbLineStyle7; pCmbScatterStyle[6] = ui->cmbScatterStyle7; 134 | pCmbLineStyle[7] = ui->cmbLineStyle8; pCmbScatterStyle[7] = ui->cmbScatterStyle8; 135 | pCmbLineStyle[8] = ui->cmbLineStyle9; pCmbScatterStyle[8] = ui->cmbScatterStyle9; 136 | pCmbLineStyle[9] = ui->cmbLineStyle10; pCmbScatterStyle[9] = ui->cmbScatterStyle10; 137 | pCmbLineStyle[10] = ui->cmbLineStyle11; pCmbScatterStyle[10] = ui->cmbScatterStyle11; 138 | pCmbLineStyle[11] = ui->cmbLineStyle12; pCmbScatterStyle[11] = ui->cmbScatterStyle12; 139 | pCmbLineStyle[12] = ui->cmbLineStyle13; pCmbScatterStyle[12] = ui->cmbScatterStyle13; 140 | pCmbLineStyle[13] = ui->cmbLineStyle14; pCmbScatterStyle[13] = ui->cmbScatterStyle14; 141 | pCmbLineStyle[14] = ui->cmbLineStyle15; pCmbScatterStyle[14] = ui->cmbScatterStyle15; 142 | pCmbLineStyle[15] = ui->cmbLineStyle16; pCmbScatterStyle[15] = ui->cmbScatterStyle16; 143 | pCmbLineStyle[16] = ui->cmbLineStyle17; pCmbScatterStyle[16] = ui->cmbScatterStyle17; 144 | pCmbLineStyle[17] = ui->cmbLineStyle18; pCmbScatterStyle[17] = ui->cmbScatterStyle18; 145 | pCmbLineStyle[18] = ui->cmbLineStyle19; pCmbScatterStyle[18] = ui->cmbScatterStyle19; 146 | pCmbLineStyle[19] = ui->cmbLineStyle20; pCmbScatterStyle[19] = ui->cmbScatterStyle20; 147 | 148 | // 设置颜色选择框的初始背景颜色,与曲线同步颜色 149 | for(int i=0; i<20; i++){ 150 | pBtnColourCurve[i]->setStyleSheet(QString("border:0px solid;background-color: %1;").arg(QColor(pCurve[i]->pen().color()).name())); 151 | } 152 | 153 | // 可见性选择框关联 154 | for(int i=0; i<20; i++){ 155 | connect(pChkVisibleCurve[i], &QCheckBox::clicked, [=](){ 156 | curveSetVisible(pPlot1, pCurve[i], pChkVisibleCurve[i]->checkState()); 157 | }); 158 | } 159 | 160 | // 颜色选择框关联 161 | for(int i=0; i<20; i++){ 162 | connect(pBtnColourCurve[i], &QPushButton::clicked, [=](){ 163 | curveSetColor(pPlot1, pCurve[i], pBtnColourCurve[i]); 164 | }); 165 | } 166 | 167 | // 加粗显示多选框关联。尽量别用,会导致CPU使用率升高 168 | for(int i=0; i<20; i++){ 169 | connect(pRdoBoldCurve[i], &QRadioButton::clicked, [=](){ 170 | curveSetBold(pPlot1, pCurve[i], pRdoBoldCurve[i]->isChecked()); 171 | }); 172 | } 173 | 174 | // 曲线样式选择关联 175 | for(int i=0; i<20; i++){ 176 | connect(pCmbLineStyle[i], &QComboBox::currentTextChanged, [=](){ 177 | curveSetLineStyle(pPlot1, pCurve[i], pCmbLineStyle[i]->currentIndex()); 178 | }); 179 | } 180 | 181 | // 散点样式选择关联 182 | for(int i=0; i<20; i++){ 183 | connect(pCmbScatterStyle[i], &QComboBox::currentTextChanged, [=](){ 184 | curveSetScatterStyle(pPlot1, pCurve[i], pCmbScatterStyle[i]->currentIndex()+1); 185 | }); 186 | } 187 | 188 | //QIcon ssCircleIcon (":/pic/ssCircle.png"); 189 | //ui->cmbScatterStyle1->addItem(ssCircleIcon,"空心圆"); 190 | for(int i=0; i<20; i++){ 191 | pCmbScatterStyle[i]->setIconSize(QSize(25,17)); // 设置图片显示像素大小,不然会默认大小显示会模糊 192 | } 193 | 194 | } 195 | 196 | // 定时器溢出处理槽函数。用来生成曲线的坐标数据。 197 | void Plot::TimeData_Update(void) 198 | { 199 | // 生成坐标数据 200 | static float f; 201 | f += 0.01; 202 | //qDebug() << sin(f)*100; 203 | // 将坐标数据,传递给曲线 204 | ShowPlot_TimeDemo(pPlot1, sin(f)*100); 205 | } 206 | 207 | // 曲线更新绘图,定时器绘图演示 208 | void Plot::ShowPlot_TimeDemo(QCustomPlot *customPlot, double num) 209 | { 210 | cnt++; 211 | // 给曲线添加数据 212 | for(int i=0; i<10; i++){ 213 | pTxtValueCurve[i]->setText(QString::number(num-i*10,'g',8));// 显示曲线当前值 214 | pCurve[i]->addData(cnt, num-i*10); 215 | } 216 | for(int i=10; i<20; i++){ 217 | pTxtValueCurve[i]->setText(QString::number(num+(i-9)*10,'g',8));// 显示曲线当前值 218 | pCurve[i]->addData(cnt, num+(i-9)*10); 219 | } 220 | 221 | // 设置x坐标轴显示范围,使其自适应缩放x轴,x轴最大显示pointCountX个点。与chkTrackX复选框有关 222 | if(ui->chkTrackX->checkState()){ 223 | //customPlot->xAxis->setRange((pCurve[0]->dataCount()>pointCountX)?(pCurve[0]->dataCount()-pointCountX):0, pCurve[0]->dataCount()); 224 | setAutoTrackX(customPlot); 225 | } 226 | // 设置y坐标轴显示范围,使其自适应曲线缩放 227 | if(ui->chkAdjustY->checkState()){ 228 | setAutoTrackY(customPlot); 229 | } 230 | 231 | // 更新绘图,这种方式在高填充下太浪费资源。有另一种方式rpQueuedReplot,可避免重复绘图。 232 | // 最好的方法还是将数据填充、和更新绘图分隔开。将更新绘图单独用定时器更新。例程数据量较少没用单独定时器更新,实际工程中建议大家加上。 233 | //customPlot->replot(); 234 | customPlot->replot(QCustomPlot::rpQueuedReplot); 235 | 236 | static QTime time(QTime::currentTime()); 237 | double key = time.elapsed()/1000.0; // 开始到现在的时间,单位秒 238 | ////计算帧数 239 | static double lastFpsKey; 240 | static int frameCount; 241 | ++frameCount; 242 | if (key-lastFpsKey > 1) // 每1秒求一次平均值 243 | { 244 | //状态栏显示帧数和数据总数 245 | ui->statusbar->showMessage( 246 | QString("%1 FPS, Total Data points: %2") 247 | .arg(frameCount/(key-lastFpsKey), 0, 'f', 0) 248 | .arg(customPlot->graph(0)->data()->size()+customPlot->graph(1)->data()->size()) 249 | , 0); 250 | lastFpsKey = key; 251 | frameCount = 0; 252 | } 253 | 254 | } 255 | 256 | // 曲线更新绘图,波形数据绘图 257 | void Plot::ShowPlot_WaveForm(QCustomPlot *customPlot, short value[]) 258 | { 259 | cnt++; 260 | // 给曲线添加数据 261 | for(int i=0; i<20; i++){ 262 | //QString strNum = QString::number(num,'g',8);// double类型 263 | pTxtValueCurve[i]->setText(QString::number(value[i]));// 显示曲线当前值 264 | pCurve[i]->addData(cnt, value[i]);// 从原值获取数据 265 | //pCurve[i]->addData(cnt, pTxtValueCurve[i]->text().toShort());// 从输入框获取数据 266 | // 因为20条线重叠在一起,所以QCustomPlot输入为0时看起来像不显示,隐藏其他后观察单条曲线是可以看到显示的 267 | } 268 | 269 | // 设置x坐标轴显示范围,使其自适应缩放x轴,x轴最大显示pointCountX个点。与chkTrackX复选框有关 270 | if(ui->chkTrackX->checkState()){ 271 | //customPlot->xAxis->setRange((pCurve[0]->dataCount()>pointCountX)?(pCurve[0]->dataCount()-pointCountX):0, pCurve[0]->dataCount()); 272 | setAutoTrackX(customPlot); 273 | } 274 | // 设置y坐标轴显示范围,使其自适应曲线缩放 275 | if(ui->chkAdjustY->checkState()){ 276 | setAutoTrackY(customPlot); 277 | } 278 | 279 | // 更新绘图,这种方式在高填充下太浪费资源。有另一种方式rpQueuedReplot,可避免重复绘图。 280 | // 最好的方法还是将数据填充、和更新绘图分隔开。将更新绘图单独用定时器更新。例程数据量较少没用单独定时器更新,实际工程中建议大家加上。 281 | //customPlot->replot(); 282 | customPlot->replot(QCustomPlot::rpQueuedReplot); 283 | 284 | static QTime time(QTime::currentTime()); 285 | double key = time.elapsed()/1000.0; // 开始到现在的时间,单位秒 286 | ////计算帧数 287 | static double lastFpsKey; 288 | static int frameCount; 289 | ++frameCount; 290 | if (key-lastFpsKey > 1) // 每1秒求一次平均值 291 | { 292 | //状态栏显示帧数和数据总数 293 | ui->statusbar->showMessage( 294 | QString("%1 FPS, Total Data points: %2") 295 | .arg(frameCount/(key-lastFpsKey), 0, 'f', 0) 296 | .arg(customPlot->graph(0)->data()->size()+customPlot->graph(1)->data()->size()) 297 | , 0); 298 | lastFpsKey = key; 299 | frameCount = 0; 300 | } 301 | 302 | } 303 | 304 | /* 功能:隐藏/显示曲线n 305 | * QCustomPlot *pPlot:父控件,绘图图表 306 | * QCPGraph *pCurve:图表的曲线 307 | * int arg1:曲线的可见性,>0可见,0不可见 308 | * */ 309 | void Plot::curveSetVisible(QCustomPlot *pPlot, QCPGraph *pCurve, int arg1) 310 | { 311 | if(arg1){ 312 | pCurve->setVisible(true); 313 | }else{ 314 | pCurve->setVisible(false); 315 | } 316 | pPlot->replot(QCustomPlot::rpQueuedReplot); 317 | } 318 | 319 | /* 功能:弹出颜色对话框,设置曲线n的颜色 320 | * QCustomPlot *pPlot:父控件,绘图图表 321 | * QCPGraph *pCurve:图表的曲线 322 | * QPushButton *btn:曲线颜色选择框的按键,与曲线的颜色同步 323 | * */ 324 | void Plot::curveSetColor(QCustomPlot *pPlot, QCPGraph *pCurve, QPushButton *btn) 325 | { 326 | // 获取当前颜色 327 | QColor bgColor(0,0,0); 328 | //bgColor = btn->palette().color(QPalette::Background);// 由pushButton的背景色获得颜色 329 | bgColor = pCurve->pen().color();// 由curve曲线获得颜色 330 | // 以当前颜色打开调色板,父对象,标题,颜色对话框设置项(显示Alpha透明度通道) 331 | //QColor color = QColorDialog::getColor(bgColor); 332 | QColor color = QColorDialog::getColor(bgColor, this, 333 | tr("颜色对话框"), 334 | QColorDialog::ShowAlphaChannel); 335 | // 判断返回的颜色是否合法。若点击x关闭颜色对话框,会返回QColor(Invalid)无效值,直接使用会导致变为黑色。 336 | if(color.isValid()){ 337 | // 设置选择框颜色 338 | btn->setStyleSheet(QString("border:0px solid;background-color: %1;").arg(color.name())); 339 | // 设置曲线颜色 340 | QPen pen = pCurve->pen(); 341 | pen.setBrush(color); 342 | pCurve->setPen(pen); 343 | //pCurve->setPen(QPen(color)); 344 | } 345 | // 更新绘图 346 | pPlot->replot(QCustomPlot::rpQueuedReplot); 347 | } 348 | 349 | /* 功能:加粗显示曲线n 350 | * QCustomPlot *pPlot:父控件,绘图图表 351 | * QCPGraph *pCurve:图表的曲线 352 | * int arg1:曲线的粗细,>0粗,0细 353 | * */ 354 | void Plot::curveSetBold(QCustomPlot *pPlot, QCPGraph *pCurve, int arg1) 355 | { 356 | // 预先读取曲线的颜色 357 | QPen pen = pCurve->pen(); 358 | //pen.setBrush(pCurve->pen().color());// 由curve曲线获得颜色 359 | 360 | if(arg1){ 361 | pen.setWidth(3); 362 | pCurve->setPen(pen); 363 | }else{ 364 | pen.setWidth(1); 365 | pCurve->setPen(pen); 366 | } 367 | pPlot->replot(QCustomPlot::rpQueuedReplot); 368 | } 369 | 370 | /* 功能:选择曲线样式(线,点,积) 371 | * QCustomPlot *pPlot:父控件,绘图图表 372 | * QCPGraph *pCurve:图表的曲线 373 | * int arg1:曲线样式(线,点,积) 374 | * */ 375 | void Plot::curveSetLineStyle(QCustomPlot *pPlot, QCPGraph *pCurve, int arg1) 376 | { 377 | // 设置曲线样式 378 | //customPlot->graph(19)->setLineStyle(QCPGraph::lsLine); // 数据点通过直线连接 379 | //customPlot->graph(19)->setLineStyle((QCPGraph::LineStyle)i);//设置线性 380 | //pCurve->setLineStyle(QCPGraph::LineStyle(arg1)); 381 | pCurve->setLineStyle((QCPGraph::LineStyle)arg1); 382 | pPlot->replot(QCustomPlot::rpQueuedReplot); 383 | } 384 | 385 | /* 功能:选择散点样式(空心圆、实心圆、正三角、倒三角) 386 | * QCustomPlot *pPlot:父控件,绘图图表 387 | * QCPGraph *pCurve:图表的曲线 388 | * int arg1:散点样式(空心圆、实心圆、正三角、倒三角) 389 | * */ 390 | void Plot::curveSetScatterStyle(QCustomPlot *pPlot, QCPGraph *pCurve, int arg1) 391 | { 392 | // 设置散点样式 393 | //customPlot->graph(19)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, 5)); // 空心圆 394 | //pCurve->setScatterStyle(QCPScatterStyle::ScatterShape(arg1)); // 散点样式 395 | //pCurve->setScatterStyle((QCPScatterStyle::ScatterShape)arg1); // 散点样式 396 | if(arg1 <= 10){ 397 | pCurve->setScatterStyle(QCPScatterStyle((QCPScatterStyle::ScatterShape)arg1, 5)); // 散点样式 398 | }else{ // 后面的散点图形略复杂,太小会看不清 399 | pCurve->setScatterStyle(QCPScatterStyle((QCPScatterStyle::ScatterShape)arg1, 8)); // 散点样式 400 | } 401 | pPlot->replot(QCustomPlot::rpQueuedReplot); 402 | } 403 | 404 | // 图例显示与否 405 | void Plot::on_chkShowLegend_stateChanged(int arg1) 406 | { 407 | if(arg1){ 408 | // 显示图表的图例 409 | pPlot1->legend->setVisible(true); 410 | }else{ 411 | // 不显示图表的图例 412 | pPlot1->legend->setVisible(false); 413 | } 414 | pPlot1->replot(QCustomPlot::rpQueuedReplot); 415 | } 416 | 417 | // 绘图演示-曲线 418 | void Plot::on_chkDrawDemo_stateChanged(int arg1) 419 | { 420 | if(arg1){ 421 | timer->start(10); 422 | }else{ 423 | timer->stop(); 424 | } 425 | } 426 | 427 | // 设置曲线x轴自动跟随 428 | void Plot::setAutoTrackX(QCustomPlot *pPlot) 429 | { 430 | pointCountX = ui->txtPointCountX->text().toUInt(); 431 | if(pCurve[0]->dataCount() < pointCountX){ 432 | pPlot->xAxis->setRange(0,pointCountX); 433 | }else{ 434 | pPlot->xAxis->setRange((pCurve[0]->dataCount()>pointCountX)?(pCurve[0]->dataCount()-pointCountX):0, pCurve[0]->dataCount()); 435 | } 436 | } 437 | 438 | // 设置曲线x轴手动设置范围(依照右下角输入框) 439 | void Plot::setManualSettingX(QCustomPlot *pPlot) 440 | { 441 | pointOriginX = ui->txtPointOriginX->text().toInt(); 442 | pointCountX = ui->txtPointCountX->text().toUInt(); 443 | pPlot->xAxis->setRange(pointOriginX, pointOriginX+pointCountX); 444 | } 445 | 446 | // 设置Y轴自适应 447 | void Plot::setAutoTrackY(QCustomPlot *pPlot) 448 | { 449 | pPlot->graph(0)->rescaleValueAxis();// y轴自适应,可放大可缩小 450 | for(int i=1; i<20; i++){ 451 | pPlot->graph(i)->rescaleValueAxis(true);// y轴自适应,只能放大 452 | } 453 | } 454 | 455 | // 重新设置X轴显示的点数 456 | void Plot::on_txtPointCountX_returnPressed() 457 | { 458 | if(ui->chkTrackX->checkState()){ 459 | setAutoTrackX(pPlot1); 460 | }else{ 461 | setManualSettingX(pPlot1); 462 | } 463 | pPlot1->replot(QCustomPlot::rpQueuedReplot); 464 | } 465 | 466 | void Plot::on_txtPointCountY_returnPressed() 467 | { 468 | pointCountY = ui->txtPointCountY->text().toUInt(); 469 | pPlot1->yAxis->setRange(pointCountY/2*-1,pointCountY/2); 470 | ui->txtPointOriginY->setText(QString::number(pointCountY/2*-1)); 471 | pPlot1->replot(QCustomPlot::rpQueuedReplot); 472 | } 473 | 474 | void Plot::on_btnColourBack_clicked() 475 | { 476 | // 获取当前颜色 477 | QColor bgColor(0,0,0); 478 | bgColor = ui->btnColourBack->palette().color(QPalette::Background);// 由pushButton的背景色获得颜色 479 | // 以当前颜色打开调色板,父对象,标题,颜色对话框设置项(显示Alpha透明度通道) 480 | //QColor color = QColorDialog::getColor(bgColor); 481 | QColor color = QColorDialog::getColor(bgColor, this, 482 | tr("颜色对话框"), 483 | QColorDialog::ShowAlphaChannel); 484 | 485 | // 判断返回的颜色是否合法。若点击x关闭颜色对话框,会返回QColor(Invalid)无效值,直接使用会导致变为黑色。 486 | if(color.isValid()){ 487 | // 设置背景颜色 488 | pPlot1->setBackground(color); 489 | // 设置背景选择框颜色 490 | ui->btnColourBack->setStyleSheet(QString("border:0px solid;background-color: %1;").arg(color.name())); 491 | } 492 | // 更新绘图 493 | pPlot1->replot(QCustomPlot::rpQueuedReplot); 494 | } 495 | 496 | void Plot::on_txtPointOriginX_returnPressed() 497 | { 498 | setManualSettingX(pPlot1); 499 | pPlot1->replot(QCustomPlot::rpQueuedReplot); 500 | } 501 | 502 | void Plot::on_chkTrackX_stateChanged(int arg1) 503 | { 504 | if(arg1){ 505 | ui->txtPointOriginX->setEnabled(false); 506 | setAutoTrackX(pPlot1); 507 | pPlot1->replot(QCustomPlot::rpQueuedReplot); 508 | }else{ 509 | ui->txtPointOriginX->setEnabled(true); 510 | } 511 | } 512 | 513 | void Plot::on_chkAdjustY_stateChanged(int arg1) 514 | { 515 | if(arg1){ 516 | ui->txtPointOriginY->setEnabled(false); 517 | ui->txtPointCountY->setEnabled(false); 518 | setAutoTrackY(pPlot1); 519 | pPlot1->replot(QCustomPlot::rpQueuedReplot); 520 | }else{ 521 | ui->txtPointOriginY->setEnabled(true); 522 | ui->txtPointCountY->setEnabled(true); 523 | } 524 | } 525 | 526 | void Plot::on_txtPointOriginY_returnPressed() 527 | { 528 | pointOriginY = ui->txtPointOriginY->text().toInt(); 529 | pointCountY = ui->txtPointCountY->text().toUInt(); 530 | pPlot1->yAxis->setRange(pointOriginY, pointOriginY+pointCountY); 531 | qDebug() << pointOriginY << pointCountY; 532 | pPlot1->replot(QCustomPlot::rpQueuedReplot); 533 | } 534 | 535 | // 每次图表重绘后,都会更新当前显示的原点坐标与范围。与上次不同时才会更新显示,解决有曲线数据时无法输入y的参数的问题 536 | void Plot::repPlotCoordinate() 537 | { 538 | static int xOrigin, yOrigin, yCount; 539 | static int xOriginLast, yOriginLast, yCountLast; 540 | 541 | xOrigin = pPlot1->xAxis->range().lower; 542 | yOrigin = pPlot1->yAxis->range().lower; 543 | yCount = pPlot1->yAxis->range().size(); 544 | // 与上次不同时才会更新显示,解决有曲线数据时无法输入y的参数的问题 545 | if(xOriginLast != xOrigin){ 546 | ui->txtPointOriginX->setText(QString::number(xOrigin)); 547 | } 548 | if(yOriginLast != yOrigin){ 549 | ui->txtPointOriginY->setText(QString::number(yOrigin)); 550 | } 551 | if(yCountLast != yCount){ 552 | ui->txtPointCountY->setText(QString::number(yCount)); 553 | } 554 | // 记录历史值 555 | xOriginLast = xOrigin; 556 | yOriginLast = yOrigin; 557 | yCountLast = yCount; 558 | } 559 | 560 | // 清空绘图 561 | void Plot::on_btnClearGraphs_clicked() 562 | { 563 | //pPlot1->clearGraphs(); // 清除图表的所有数据和设置,需要重新设置才能重新绘图 564 | //pPlot1->clearPlottables(); // 清除图表中所有曲线,需要重新添加曲线才能绘图 565 | for(int i=0; i<20; i++){ 566 | pPlot1->graph(i)->data().data()->clear(); // 仅仅清除曲线的数据 567 | } 568 | cnt = 0; 569 | pPlot1->replot(QCustomPlot::rpQueuedReplot); 570 | } 571 | 572 | // 设置X轴主刻度个数 573 | void Plot::on_txtMainScaleNumX_returnPressed() 574 | { 575 | pPlot1->xAxis->ticker()->setTickCount(ui->txtMainScaleNumX->text().toUInt()); 576 | pPlot1->replot(QCustomPlot::rpQueuedReplot); 577 | } 578 | 579 | // 设置Y轴主刻度个数 580 | void Plot::on_txtMainScaleNumY_returnPressed() 581 | { 582 | pPlot1->yAxis->ticker()->setTickCount(ui->txtMainScaleNumY->text().toUInt()); 583 | pPlot1->replot(QCustomPlot::rpQueuedReplot); 584 | } 585 | -------------------------------------------------------------------------------- /plot.h: -------------------------------------------------------------------------------- 1 | #ifndef PLOT_H 2 | #define PLOT_H 3 | 4 | #include 5 | #include "qcustomplot.h" 6 | 7 | namespace Ui { 8 | class Plot; 9 | } 10 | 11 | class Plot : public QMainWindow 12 | { 13 | Q_OBJECT 14 | 15 | public: 16 | explicit Plot(QWidget *parent = nullptr); 17 | ~Plot(); 18 | // 绘图控件的指针 19 | QCustomPlot *pPlot1; 20 | 21 | void ShowPlot_TimeDemo(QCustomPlot *customPlot, double num); 22 | void ShowPlot_WaveForm(QCustomPlot *customPlot, short value[]); 23 | 24 | private slots: 25 | void TimeData_Update(void); 26 | 27 | void on_chkShowLegend_stateChanged(int arg1); 28 | 29 | void on_chkDrawDemo_stateChanged(int arg1); 30 | 31 | void on_txtPointCountX_returnPressed(); 32 | 33 | void on_txtPointCountY_returnPressed(); 34 | 35 | void on_btnColourBack_clicked(); 36 | 37 | void on_txtPointOriginX_returnPressed(); 38 | 39 | void on_chkTrackX_stateChanged(int arg1); 40 | 41 | void on_chkAdjustY_stateChanged(int arg1); 42 | 43 | void on_txtPointOriginY_returnPressed(); 44 | 45 | void repPlotCoordinate(); 46 | 47 | void on_btnClearGraphs_clicked(); 48 | 49 | void on_txtMainScaleNumX_returnPressed(); 50 | 51 | void on_txtMainScaleNumY_returnPressed(); 52 | 53 | private: 54 | Ui::Plot *ui; 55 | 56 | // 状态栏指针 57 | QStatusBar *sBar; 58 | // 定时器指针 59 | QTimer *timer; 60 | // 绘图控件中曲线的指针 61 | QCPGraph *pCurve[20]; 62 | // 绘图框X轴显示的坐标点数 63 | int pointOriginX=0; 64 | int pointOriginY=0; 65 | int pointCountX=0; 66 | int pointCountY=0; 67 | 68 | double cnt=0;// 当前绘图的X坐标 69 | 70 | // ui界面中,选择曲线可见性的checkBox的指针。方便用指针数组写代码,不然很占地方 71 | QCheckBox *pChkVisibleCurve[20]; 72 | // ui界面中,选择曲线颜色的pushButton的指针。方便用指针数组写代码,不然很占地方 73 | QPushButton *pBtnColourCurve[20]; 74 | // ui界面中,曲线当前值的lineEdit的指针。方便用指针数组写代码,不然很占地方 75 | QLineEdit *pTxtValueCurve[20]; 76 | // ui界面中,选择曲线粗细的radioButton的指针。方便用指针数组写代码,不然很占地方 77 | QRadioButton *pRdoBoldCurve[20]; 78 | // ui界面中,选择曲线样式的cmbLineStyle的指针。方便用指针数组写代码,不然很占地方 79 | QComboBox *pCmbLineStyle[20]; 80 | // ui界面中,选择散点样式的cmbScatterStyle的指针。方便用指针数组写代码,不然很占地方 81 | QComboBox *pCmbScatterStyle[20]; 82 | 83 | void QPlot_init(QCustomPlot *customPlot); 84 | void QPlot_widget_init(void); 85 | 86 | void curveSetVisible(QCustomPlot *pPlot, QCPGraph *pCurve, int arg1); 87 | void curveSetColor(QCustomPlot *pPlot, QCPGraph *pCurve, QPushButton *btn); 88 | void curveSetBold(QCustomPlot *pPlot, QCPGraph *pCurve, int arg1); 89 | void curveSetLineStyle(QCustomPlot *pPlot, QCPGraph *pCurve, int arg1); 90 | void curveSetScatterStyle(QCustomPlot *pPlot, QCPGraph *pCurve, int arg1); 91 | 92 | void setAutoTrackX(QCustomPlot *pPlot); 93 | void setManualSettingX(QCustomPlot *pPlot); 94 | void setAutoTrackY(QCustomPlot *pPlot); 95 | }; 96 | 97 | #endif // PLOT_H 98 | -------------------------------------------------------------------------------- /res.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | debug_32px_1103189_easyicon.net.ico 4 | chart_curve_32px_506507_easyicon.net.ico 5 | pic/ssCircle.png 6 | pic/ssDisc.png 7 | pic/ssSquare.png 8 | pic/ssDiamond.png 9 | pic/ssCross.png 10 | pic/ssPlus.png 11 | pic/ssStar.png 12 | pic/ssTriangle.png 13 | pic/ssTriangleInverted.png 14 | pic/ssNone.png 15 | pic/ssCrossSquare.png 16 | pic/ssPlusSquare.png 17 | pic/ssCrossCircle.png 18 | pic/ssPlusCircle.png 19 | pic/ssPeace.png 20 | axis_64px_1200251_easyicon.net.ico 21 | 22 | 23 | -------------------------------------------------------------------------------- /测试帧.txt: -------------------------------------------------------------------------------- 1 | 3A 3B 01 04 11 12 13 14 C4 2 | 4370 4884 3 | 4 | 3A 3B 01 04 FF 38 13 14 D8 5 | -200 4884 6 | 7 | 3A 3B 01 04 FF C8 13 14 68 8 | -56 4884 9 | 10 | 3A 3B 01 28 00 C8 01 90 02 58 03 20 03 E8 04 B0 05 78 06 40 07 08 07 D0 FF 38 FE 70 FD A8 FC E0 FC 18 FB 50 FA 88 F9 C0 F8 F8 F8 30 94 11 | 200 400 600 800 1000 1200 1400 1600 1800 2000 -200 -400 -600 -800 -1000-1200 -1400-1600 -1800 -2000 --------------------------------------------------------------------------------