diff --git a/LinkScope.pro b/LinkScope.pro index 16efb4f..b39b534 100644 --- a/LinkScope.pro +++ b/LinkScope.pro @@ -11,7 +11,7 @@ CONFIG += c++11 # 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 \ - APP_VERSION=\\\"1.1.0\\\" + APP_VERSION=\\\"1.2.0\\\" # You can also make your code fail to compile if it uses deprecated APIs. # In order to do so, uncomment the following line. @@ -24,6 +24,7 @@ SOURCES += \ graphwindow.cpp \ helpwindow.cpp \ listwindow.cpp \ + logwindow.cpp \ main.cpp \ mainwindow.cpp \ openocd.cpp \ @@ -35,6 +36,7 @@ HEADERS += \ graphwindow.h \ helpwindow.h \ listwindow.h \ + logwindow.h \ mainwindow.h \ openocd.h \ serialocd.h \ @@ -45,6 +47,7 @@ FORMS += \ graphwindow.ui \ helpwindow.ui \ listwindow.ui \ + logwindow.ui \ mainwindow.ui RC_ICONS = icon.ico diff --git a/LinkScope.pro.user b/LinkScope.pro.user index 248518f..e769906 100644 --- a/LinkScope.pro.user +++ b/LinkScope.pro.user @@ -1,6 +1,6 @@ - + EnvironmentId diff --git a/README.md b/README.md index 4c431e3..7ba365a 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,9 @@ * 变量值**波形实时绘制** - * 采样数据导出到CSV表格 + * **采样数据导出**到CSV表格 + + * 格式化**日志**输出 * 调试器模式理论上支持OpenOCD允许GDB连接的各种调试器及硬件芯片,如STLink、JLink、CMSIS-DAP等以及STM32全系列等 @@ -30,9 +32,13 @@ ## 使用方法 -1. 若使用串口连接,需先将下位机程序移植到目标芯片中 [移植说明](lower/README.md) +1. 若使用串口连接,需先将串口下位机程序移植到目标芯片中 [串口移植说明](lower/serial/README.md) -2. 点击设置符号文件,添加变量 +2. 若使用日志功能,需先将日志下位机程序移植到目标芯片中[日志移植说明](lower/log/README.md) + +> 注:串口与日志功能不冲突,可以同时使用 + +3. 点击设置符号文件,添加变量 * 加载符号文件后即可在变量选择窗口添加需要查看的变量,可以直接添加到列表或添加到编辑框后进行手动编辑 @@ -42,20 +48,22 @@ * 变量名不仅可以填入单个变量名,还可以填入合法的C语言表达式(GDB支持即可);复合类型不能修改和绘图,只能实时查看 -3. 选择连接模式,连接芯片,连接后程序开始循环采样 +4. 选择连接模式,连接芯片,连接后程序开始循环采样 * 调试器模式下,在下拉框中选择调试器和芯片类型,点击连接目标 * 串口模式下,点击刷新串口加载串口列表,选中所连接的串口,点击连接目标 -4. 编辑`修改变量`列可以修改变量值,双击`图线颜色`列可以选择绘图颜色 +5. 编辑`修改变量`列可以修改变量值,双击`图线颜色`列可以选择绘图颜色 -5. 单击`变量名`列选中对应的变量,绘图窗口会加粗绘制波形,左下角会显示当前值和查看值(拖动鼠标进行查看) +6. 单击`变量名`列选中对应的变量,绘图窗口会加粗绘制波形,左下角会显示当前值和查看值(拖动鼠标进行查看) -6. 绘图界面说明可以在绘图窗口点击操作说明查看,滚轮配合`Ctrl`、`Shift`、`Alt`可以实现画面的缩放和移动 +7. 绘图界面说明可以在绘图窗口点击操作说明查看,滚轮配合`Ctrl`、`Shift`、`Alt`可以实现画面的缩放和移动 ![操作演示](imgs/oper-sample.gif) +![日志输出](imgs/log-sample.gif) + --- ## 主要菜单项说明 @@ -82,6 +90,8 @@ * 本程序不带下载功能,连接目标前请确认已为目标芯片下载过指定程序;若更换为不同类型的调试器,即使芯片程序没有变动,也应使用更换后的调试器再次下载程序 +* 下位机程序应使用与上位机同时发行的版本,更新上位机软件后应同时更新下位机程序 + --- ## 已知问题及解决方法 @@ -92,6 +102,18 @@ --- +## 其他说明 + +* 关于采样速度 + + * 采样速度与CPU占用率、添加的变量数量、日志输出频率等因素相关,程序会以尽可能高的速度进行采样 + + * 简介中介绍的采样速度是在`i5-8265U`CPU接近空载时,添加单个变量并关闭日志的情况下测试得到的 + + * 调试器模式下获取单条日志用时约50ms,串口模式约90ms,获取过程中无法进行采样,若日志数量较多则会对采样速度造成较大影响 + +--- + ## TODO * 增加对不同文件中同名变量的区分 @@ -114,6 +136,8 @@ * 变量选择窗口另开有一个GDB进程用于解析符号文件,内部使用树形结构存储各级变量信息,在用户每展开一级树形图时使用`info variables`、`whatis`、`ptype`指令从GDB获取下一级变量类型信息并用正则表达式解析 +* 日志下位机程序会创建一个缓冲区队列,下位机会将日志输出到缓冲区中,程序会定时使用GDB的print指令将日志出队并解析显示出来 + --- ## 仓库文件说明 diff --git a/aboutwindow.ui b/aboutwindow.ui index 32dcb37..66f5b06 100644 --- a/aboutwindow.ui +++ b/aboutwindow.ui @@ -91,7 +91,7 @@ - <html><head/><body><hr width="300"/><p align="center">版本号:V1.1.0</p><p align="center">更新时间:2022/2/3</p><hr width="300"/><p align="center">Developed by Skythinker</p></body></html> + <html><head/><body><hr width="300"/><p align="center">版本号:V1.2.0</p><p align="center">更新时间:2022/2/18</p><hr width="300"/><p align="center">Developed by Skythinker</p></body></html> diff --git a/gdbprocess.cpp b/gdbprocess.cpp index ef36654..741818f 100644 --- a/gdbprocess.cpp +++ b/gdbprocess.cpp @@ -17,7 +17,7 @@ QString GDBProcess::runCmd(const QString &cmd) process->write(cmd.toStdString().c_str()); QString res=""; do{ - process->waitForReadyRead(0); + process->waitForReadyRead(1); res+=process->readAllStandardOutput(); }while(!res.endsWith("(gdb) ")); return res; @@ -112,6 +112,34 @@ bool GDBProcess::getDoubleFromDisplayValue(const QString &rawValue, double &resu return true; } +//将gdb所返回的无符号数组变量值转换为uint数组 +QList GDBProcess::getUintArrayFromDisplay(const QString &rawDisplay) +{ + QRegExp rx("\\{(.*)\\}"); + rx.indexIn(rawDisplay); + QString raw=rx.cap(1); + raw=raw.replace("\r\n ",""); + QStringList rawList=raw.split(", "); + QList numList; + for(int index=0;index"); + repeatRx.indexIn(rawList[index]); + int num=repeatRx.cap(1).toUInt(); + int times=repeatRx.cap(2).toUInt(); + for(int i=0;i getUintArrayFromDisplay(const QString &rawDisplay); void setVarValue(const QString &varFullName,double value); bool checkExpandableType(const QString &varFullName); QStringList getVarListFromRawOutput(const QString &rawVarList); diff --git a/helpwindow.ui b/helpwindow.ui index a9313e3..350afaf 100644 --- a/helpwindow.ui +++ b/helpwindow.ui @@ -32,9 +32,9 @@ 0 - -735 + -390 523 - 1179 + 1308 @@ -53,7 +53,7 @@ - <html><head/><body><p><span style=" font-weight:600;">LinkScope简介</span></p><p>本程序使用QT编写,用于硬件设备的调试,可直接驱动串口或各种调试器(基于OpenOCD支持),有以下几个主要功能</p><p>1.实时查看和修改变量值</p><p>2.变量值波形实时绘制</p><p>3.采样数据导出为CSV表格</p><p>调试器连接模式理论上支持OpenOCD允许GDB连接的各种调试器及硬件芯片,如STLink、JLink、CMSIS-DAP等以及STM32全系列等</p><hr/><p><span style=" font-weight:600;">使用方法</span></p><p>1.点击【设置符号文件】,添加变量</p><p>&gt; 加载符号文件后即可在变量选择窗口添加需要查看的变量,可以直接添加到列表或添加到编辑框后进行手动编辑</p><p>&gt; 在主窗口表格最后一行变量名(编辑框)处手动填写也可以添加变量</p><p>&gt; 在主窗口中选中变量名按Del键可删除变量</p><p>&gt; 变量名不仅可以填入单个变量名,还可以填入合法的C语言表达式(GDB支持即可);复合类型不能修改和绘图,只能实时查看</p><p>2.选择连接模式,连接芯片,连接后程序开始循环采样</p><p>&gt; 调试器模式下,在下拉框中选择调试器和芯片类型,点击连接目标</p><p>&gt; 串口模式下,点击刷新串口加载串口列表,选中所连接的串口,点击连接目标</p><p>3.编辑【修改变量】列可以修改变量值,双击【图线颜色】列可以选择绘图颜色 </p><p>4.单击【变量名】列选中对应的变量,可以在绘图窗口查看历史数据,并会加粗绘制 </p><p>5.绘图界面说明请到绘图窗口点击操作说明 </p><p>6.点击菜单中的保存/导入配置可以将当前配置保存到INI文件或从文件中恢复配置,点击导出数据可以将获取到的采样数据导出到CSV表格文件</p><hr/><p><span style=" font-weight:600;">注意事项 </span></p><p>1.修改符号文件路径后需要重新连接 </p><p>2.连接目标前请确认已使用该调试器为目标芯片下载过指定程序 </p><p>3.若程序闪退后发现下一次运行时无法连接目标,请尝试手动结束openocd.exe进程 </p><p>4.连接配置文件位于openocd/share/openocd/scripts下的target和interface中,用户可按照openocd语法编写配置脚本,放入对应目录下后点击“刷新连接配置”菜单项</p></body></html> + <html><head/><body><p><span style=" font-weight:600;">LinkScope简介</span></p><p>本程序使用QT编写,用于硬件设备的调试,可直接驱动串口或各种调试器(基于OpenOCD支持),有以下几个主要功能</p><p>1.实时查看和修改变量值</p><p>2.变量值波形实时绘制</p><p>3.采样数据导出为CSV表格</p><p>4.格式化日志输出</p><p>调试器连接模式理论上支持OpenOCD允许GDB连接的各种调试器及硬件芯片,如STLink、JLink、CMSIS-DAP等以及STM32全系列等</p><hr/><p><span style=" font-weight:600;">使用方法</span></p><p>1.点击【设置符号文件】选择编译器输出的符号文件,然后添加变量到列表中</p><p>&gt; 加载符号文件后即可在变量选择窗口添加需要查看的变量,可以直接添加到列表或添加到编辑框后进行手动编辑</p><p>&gt; 在主窗口表格最后一行变量名(编辑框)处手动填写也可以添加变量</p><p>&gt; 在主窗口中选中变量名按Del键可删除变量</p><p>&gt; 变量名不仅可以填入单个变量名,还可以填入合法的C语言表达式(GDB支持即可);复合类型不能修改和绘图,只能实时查看</p><p>2.选择连接模式,连接芯片,连接后程序开始循环采样,勾选【监视日志】后即可将下位机输出的日志实时显示出来</p><p>&gt; 调试器模式下,在下拉框中选择调试器和芯片类型,点击连接目标</p><p>&gt; 串口模式下,点击刷新串口加载串口列表,选中所连接的串口,点击连接目标</p><p>&gt; 串口功能和日志功能都需要修改下位机程序,可以在菜单中进入主页查看说明</p><p>3.编辑【修改变量】列可以修改变量值,双击【图线颜色】列可以选择绘图颜色 </p><p>4.单击【变量名】列选中对应的变量,可以在绘图窗口查看历史数据,并会加粗绘制 </p><p>5.绘图界面说明请到绘图窗口点击操作说明 </p><p>6.点击菜单中的保存/导入配置可以将当前配置保存到INI文件或从文件中恢复配置,点击导出数据可以将获取到的采样数据导出到CSV表格文件</p><hr/><p><span style=" font-weight:600;">注意事项 </span></p><p>1.修改符号文件路径后需要重新连接 </p><p>2.连接目标前请确认已使用该调试器为目标芯片下载过指定程序 </p><p>3.若程序闪退后发现下一次运行时无法连接目标,请尝试手动结束openocd.exe进程 </p><p>4.连接配置文件位于openocd/share/openocd/scripts下的target和interface中,用户可按照openocd语法编写配置脚本,放入对应目录下后点击“刷新连接配置”菜单项</p></body></html> Qt::AutoText diff --git a/imgs/log-sample.gif b/imgs/log-sample.gif new file mode 100644 index 0000000..e106c15 Binary files /dev/null and b/imgs/log-sample.gif differ diff --git a/imgs/run-demo.png b/imgs/run-demo.png index 0480dfb..9cfc0fe 100644 Binary files a/imgs/run-demo.png and b/imgs/run-demo.png differ diff --git a/logwindow.cpp b/logwindow.cpp new file mode 100644 index 0000000..80b15e4 --- /dev/null +++ b/logwindow.cpp @@ -0,0 +1,148 @@ +#include "logwindow.h" +#include "ui_logwindow.h" + +LogWindow::LogWindow(QWidget *parent) : + QDialog(parent), + ui(new Ui::LogWindow) +{ + ui->setupUi(this); + setWindowTitle("LinkScope - Log"); + + tableModel=new QStandardItemModel(this);//创建表格数据 + ui->tb_log->setModel(tableModel);//绑定表格数据 + ui->tb_log->horizontalHeader()->setMinimumSectionSize(150);//设置最小列宽 + ui->tb_log->horizontalHeader()->setSectionResizeMode(QHeaderView::Interactive);//列宽可由用户调整 + ui->tb_log->verticalHeader()->setSectionResizeMode(QHeaderView::Stretch);//行高自动拉伸 + ui->tb_log->verticalHeader()->hide();//隐藏垂直表头,否则滚动语句执行时间较长 + drawTableHeader();//设置表头 +} + +LogWindow::~LogWindow() +{ + delete ui; + delete tableModel; +} + +//添加一条日志 +void LogWindow::addLog(char attr, const QString &tag, const QString &msg, qulonglong time, const QString &func) +{ + //计算日志类型字符对应的颜色和属性名 + QColor textColor=Qt::black; + QString attrText="Unknown"; + switch(attr) + { + case 'i': + textColor=QColor("#218B47"); + attrText="Info"; + break; + case 'd': + textColor=QColor("#0046EB"); + attrText="Debug"; + break; + case 'w': + textColor=QColor("#FF8C00"); + attrText="Warn"; + break; + case 'e': + textColor=QColor("#EB005F"); + attrText="Error"; + break; + } + //创建各单元格数据 + QList items; + items<setForeground(QBrush(textColor));//设置文字颜色 + items[i]->setFlags(Qt::ItemIsEnabled);//设置为不可编辑 + } + //若当前表格滚动到底端则在插入新行后自动滚动到底端 + bool scrollAtEnd=ui->tb_log->verticalScrollBar()->value()==ui->tb_log->verticalScrollBar()->maximum(); + tableModel->insertRow(tableModel->rowCount(),items);//在表格末尾插入新行 + if(scrollAtEnd) + ui->tb_log->scrollToBottom(); +} + +//清空所有日志 +void LogWindow::clearLog() +{ + QByteArray horiHeaderState=ui->tb_log->horizontalHeader()->saveState();//记录列宽 + tableModel->clear(); + drawTableHeader(); + ui->tb_log->horizontalHeader()->restoreState(horiHeaderState);//恢复列宽 +} + +//绘制表头 +void LogWindow::drawTableHeader() +{ + tableModel->setColumnCount(5); + tableModel->setHeaderData(0,Qt::Horizontal,"类型"); + tableModel->setHeaderData(1,Qt::Horizontal,"TAG"); + tableModel->setHeaderData(2,Qt::Horizontal,"MSG"); + tableModel->setHeaderData(3,Qt::Horizontal,"时间戳"); + tableModel->setHeaderData(4,Qt::Horizontal,"函数名"); +} + +//将表格数据导出到CSV文件 +bool LogWindow::exportCSV(const QString &filename) +{ + QFile file(filename); + if(!file.open(QFile::WriteOnly)) + return false; + QTextStream outStream(&file);//使用流方式输出 + + outStream<<"Type,TAG,MSG,Timestamp,Function\n"; + + for(int row=0;rowrowCount();row++) + { + for(int col=0;colcolumnCount();col++) + outStream<item(row,col)->text()<<','; + outStream<<'\n'; + } + + return true; +} + +//清空日志按钮点击 +void LogWindow::on_bt_clear_clicked() +{ + clearLog(); +} + +//导出日志按钮点击 +void LogWindow::on_bt_export_clicked() +{ + //弹出文件选择框 + QFileDialog *fileDialog = new QFileDialog(this); + fileDialog->setWindowTitle(QStringLiteral("选中文件")); + fileDialog->setDirectory("."); + fileDialog->setNameFilter(tr("CSV File (*.csv)")); + fileDialog->setFileMode(QFileDialog::AnyFile); + fileDialog->setViewMode(QFileDialog::Detail); + if(fileDialog->exec())//等待用户选择文件 + { + QStringList fileList=fileDialog->selectedFiles(); + QString fileName=fileList.at(0); + if(!fileName.endsWith(".csv"))//若用户输入的文件名不以csv结尾则加上后缀 + fileName.append(".csv"); + QFileInfo file(fileName);//判断是否已经存在文件,若存在则弹出覆盖警告框 + if(file.exists()) + { + QMessageBox messageBox(QMessageBox::NoIcon,"警告", "文件已存在,是否覆盖?",QMessageBox::Yes | QMessageBox::No, NULL); + if(messageBox.exec()!=QMessageBox::Yes) + return; + } + if(!exportCSV(fileName))//将数据写入文件 + { + QMessageBox::information(this,"错误","文件打开失败,请检查文件是否被占用"); + } + } +} diff --git a/logwindow.h b/logwindow.h new file mode 100644 index 0000000..00fed4e --- /dev/null +++ b/logwindow.h @@ -0,0 +1,38 @@ +#ifndef LOGWINDOW_H +#define LOGWINDOW_H + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Ui { +class LogWindow; +} + +class LogWindow : public QDialog +{ + Q_OBJECT + +public: + explicit LogWindow(QWidget *parent = nullptr); + ~LogWindow(); + void addLog(char attr,const QString &tag,const QString &msg,qulonglong time,const QString &func); + void clearLog(); + +private slots: + void on_bt_clear_clicked(); + void on_bt_export_clicked(); + +private: + Ui::LogWindow *ui; + QStandardItemModel *tableModel;//表格数据 + void drawTableHeader(); + bool exportCSV(const QString &filename); +}; + +#endif // LOGWINDOW_H diff --git a/logwindow.ui b/logwindow.ui new file mode 100644 index 0000000..e349ff7 --- /dev/null +++ b/logwindow.ui @@ -0,0 +1,42 @@ + + + LogWindow + + + + 0 + 0 + 828 + 486 + + + + Dialog + + + + + + + + + + + 清空日志 + + + + + + + 导出日志 + + + + + + + + + + diff --git a/lower/log/README.md b/lower/log/README.md new file mode 100644 index 0000000..0706db8 --- /dev/null +++ b/lower/log/README.md @@ -0,0 +1,71 @@ +# 日志下位机程序说明 + +--- + +## 配置项 + +* **标准库函数配置项** + + * `#define LOG_MEMCPY(dst,src,len)`:内存拷贝函数,默认使用`string.h`中的`memcpy`函数 + + * `#define LOG_SPRINTF(buf,fmt,...)`:格式化字符串打印函数,默认使用`stdio.h`中的`sprintf`函数 + + * `#define LOG_STRLEN(str)`:字符串长度计算函数,默认使用`string.h`中的`strlen`函数 + + > 注:标准库函数配置项是为了平台不支持标准库函数的情况设计的,一般无需修改 + +* **缓冲区大小配置项** + + * `#define LOG_MAX_LEN`:单条日志数据最大长度,包含日志标签、日志内容、时间戳、函数名等数据,建议不小于100 + + * `#define LOG_MAX_QUEUE_SIZE`:缓冲区能存放的最大日志条数,需根据日志打印频率确定,默认值为10 + + > 注:缓冲区总占用内存大小为上述两项的乘积,若短时间写入多条日志可能导致缓冲区溢出,后输出的日志将被丢弃 + +* **其他配置项** + + * `#define LOG_GET_MS()`:获取时间戳接口,需返回系统启动以来的毫秒数 + + * `#define LOG_GET_FUNC_NAME()`:获取宏展开位置的函数名,一般由编译器提供`__FUNCTION__`宏实现,若平台不支持可替换为空字符串`("")` + + * `#define LOG_ENABLE`:日志输出使能项,若注释掉则所有输出语句将被替换为空语句 + +--- + +## 日志输出接口 + +* `#define LOG_INFO(tag,msg,...)`:输出信息日志,`tag`为日志标签,`msg`为日志内容,日志内容中可包含格式化占位符,与后面的可变参数一起进行格式化输出 + + > 例:`LOG_INFO("sys","code=%d",123);//输出的日志标签为"sys",日志内容为"code=123"` + +* `#define LOG_DEBUG(tag,msg,...)`:输出调试日志,用法与信息日志相同 + +* `#define LOG_WARN(tag,msg,...)`:输出警告日志,用法与信息日志相同 + +* `#define LOG_ERROR(tag,msg,...)`:输出错误日志,用法与信息日志相同 + +--- + +## 移植说明 + +1. 将`log.h`和`log.c`添加到工程目录中 + + > 注:若不想新增`log.c`文件或编译器不支持多个源文件,可以将其中的内容放入任何一个源文件中 + +2. 根据平台修改配置项 + + * 一般情况下只需修改时间戳配置即可,其余参数可以保持默认 + + * 若下位机内存不够,或希望输出更长的日志,或希望一次性写入更多条日志,可修改日志缓冲区大小配置 + + * 若编译器不支持`__FUNCTION__`宏,需要将函数名宏定义替换为空字符串 + + * 若编译器不支持标准库函数,需要自行实现各函数并替换到对应宏定义中 + +3. 在需要使用日志的文件中引用`log.h`,然后调用接口进行日志输出即可,上位机会定时查看并移除日志缓冲区中的数据 + +--- + +## 注意事项 + +* 日志输出频率不能持续过高,上位机读取速度约为10条/s,若长时间高于该频率输出日志会使缓冲区溢出而导致日志丢失 diff --git a/lower/log/log.c b/lower/log/log.c new file mode 100644 index 0000000..29b1f4e --- /dev/null +++ b/lower/log/log.c @@ -0,0 +1,6 @@ +#include "log.h" + +#ifdef LOG_ENABLE +//־ +LogQueue logQueue={LOG_MAX_QUEUE_SIZE}; +#endif diff --git a/lower/log/log.h b/lower/log/log.h new file mode 100644 index 0000000..d49e344 --- /dev/null +++ b/lower/log/log.h @@ -0,0 +1,67 @@ +#ifndef _LOG_H_ +#define _LOG_H_ + +/****************************/ + +//׼ͷļ +//ϣʹöӦעͷļ޸·ʵӦ +#include //ʹsprintf +#include //ʹmemcpystrlen +//׼⺯ֲ +#define LOG_MEMCPY(dst,src,len) memcpy(dst,src,len) //ڴ濽 +#define LOG_SPRINTF(buf,fmt,...) sprintf(buf,fmt,##__VA_ARGS__) //ʽַ +#define LOG_STRLEN(str) strlen(str) //ַ + +//־СܴСΪֵij˻ +#define LOG_MAX_LEN 100 //־С־ݡʱϢ鲻С100 +#define LOG_MAX_QUEUE_SIZE 10 //ɴ־ + +//ȡʱӿ(󾭹ĺ) +#define LOG_GET_MS() HAL_GetTick() //ʹSTM32-HAL + +//ȡںĺ(һɱṩ) +#define LOG_GET_FUNC_NAME() (__FUNCTION__) + +//Ƿ־(ע͵滻Ϊ) +#define LOG_ENABLE + +/****************************/ + +/**************־ӿڡ**************/ +//Ϣ־ +#define LOG_INFO(tag,msg,...) LOG_ADD_FORMAT('i',(tag),(msg),##__VA_ARGS__) +//־ +#define LOG_DEBUG(tag,msg,...) LOG_ADD_FORMAT('d',(tag),(msg),##__VA_ARGS__) +//־ +#define LOG_WARN(tag,msg,...) LOG_ADD_FORMAT('w',(tag),(msg),##__VA_ARGS__) +//־ +#define LOG_ERROR(tag,msg,...) LOG_ADD_FORMAT('e',(tag),(msg),##__VA_ARGS__) +/**************־ӿڡ**************/ + +//־ͣԶзʽ洢 +typedef struct{ + int maxSize; //󳤶ȣʼʱ븳ֵΪLOG_MAX_QUEUE_SIZE + char buf[LOG_MAX_QUEUE_SIZE][LOG_MAX_LEN]; //ʵʴ洢ռ + int lenBuf[LOG_MAX_QUEUE_SIZE]; //bufһһӦʾ洢־ + int size,startPos;//ǰгȺͶͷλ +}LogQueue; + +#ifdef LOG_ENABLE +//־дһ־ +#define LOG_ADD_FORMAT(attr,tag,msg,...) if(logQueue.sizeDEBUG_TXBUF_SIZE-3) //ƶȡֽʹ֡ͻС + byteNum=DEBUG_TXBUF_SIZE-3; uint32_t addr=0; //Ŀַ for(uint8_t i=0;i<4;i++) addr|=((uint32_t)DEBUG_QUEUE_AT(4+i))<<(i*8); diff --git a/mainwindow.cpp b/mainwindow.cpp index 8e21c17..fe2239e 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -10,20 +10,26 @@ MainWindow::MainWindow(QWidget *parent) setStylesheet();//设置全局样式表 - graph=new GraphWindow();//创建并显示绘图窗口 + graph=new GraphWindow();//创建绘图窗口 graph->setVarList(&varList); - graph->show(); + connect(ui->action_show_graph,&QAction::toggled,this,[=](bool checked){graph->setHidden(!checked);});//绑定菜单项到窗口开关 + connect(graph,&QDialog::rejected,this,[=]{ui->action_show_graph->setChecked(false);});//当窗口关闭时取消勾选菜单 - listWindow=new ListWindow(); - listWindow->show(); + listWindow=new ListWindow();//创建选择窗口 connect(listWindow,SIGNAL(add2Edit(const QString &)),this,SLOT(slotOnVarAdd2Edit(const QString &))); connect(listWindow,SIGNAL(add2List(const QString &)),this,SLOT(slotOnVarAdd2List(const QString &))); + connect(ui->action_show_selector,&QAction::toggled,this,[=](bool checked){listWindow->setHidden(!checked);}); + connect(listWindow,&QDialog::rejected,this,[=]{ui->action_show_selector->setChecked(false);}); + + logWindow=new LogWindow();//创建日志窗口 + connect(ui->action_show_log,&QAction::toggled,this,[=](bool checked){logWindow->setHidden(!checked);}); + connect(logWindow,&QDialog::rejected,this,[=]{ui->action_show_log->setChecked(false);}); stampTimer=new QElapsedTimer();//创建并运行时间戳定时器 stampTimer->start(); watchTimer=new QTimer(this);//创建watch定时器 - watchTimer->setInterval(10); + watchTimer->setInterval(0); watchTimer->stop(); connect(watchTimer,SIGNAL(timeout()),this,SLOT(slotWatchTimerTrig())); @@ -32,13 +38,18 @@ MainWindow::MainWindow(QWidget *parent) tableTimer->stop(); connect(tableTimer,SIGNAL(timeout()),this,SLOT(slotTableTimerTrig())); + logTimer=new QTimer(this);//创建日志监视定时器 + logTimer->setInterval(100); + logTimer->stop(); + connect(logTimer,SIGNAL(timeout()),this,SLOT(slotLogTimerTrig())); + tableModel=new QStandardItemModel(this);//创建并初始化表格 initTable(); - openocd=new OpenOCD(); + openocd=new OpenOCD();//创建OpenOCD对象并连接错误处理槽 connect(openocd,&OpenOCD::onErrorOccur,this,&MainWindow::slotOnConnErrorOccur,Qt::QueuedConnection); - serialocd=new SerialOCD(); + serialocd=new SerialOCD();//创建SerialOCD对象并连接错误处理槽 connect(serialocd,&SerialOCD::onErrorOccur,this,&MainWindow::slotOnConnErrorOccur,Qt::QueuedConnection); gdb=new GDBProcess();//创建并启动GDB @@ -46,6 +57,7 @@ MainWindow::MainWindow(QWidget *parent) gdb->start();//启动gdb进程 loadConfFileList();//从openocd文件夹中读取配置文件列表 + loadGlobalConf();//加载软件全局配置 } MainWindow::~MainWindow() @@ -67,8 +79,10 @@ MainWindow::~MainWindow() delete stampTimer; delete watchTimer; delete tableTimer; + delete logTimer; delete graph; delete listWindow; + delete logWindow; } //按键事件,监听DEL键按下,用于删除单个变量 @@ -91,24 +105,21 @@ void MainWindow::keyPressEvent(QKeyEvent *event) redrawTable(); if(connected)//若正在连接状态则向gdb发送新的变量列表 - { - QStringList nameList; - for(int index=0;indexsetDisplayList(nameList); - } + updateGDBList(); } } } } } -//窗口关闭事件,在主窗口关闭时自动关闭绘图窗口以退出程序 +//窗口关闭事件,处理软件退出 void MainWindow::closeEvent(QCloseEvent *event) { Q_UNUSED(event); - graph->close(); + saveGlobalConf();//保存软件全局设置 + graph->close();//关闭各窗口,退出软件 listWindow->close(); + logWindow->close(); } //表格被编辑,添加变量或修改变量值 @@ -130,12 +141,7 @@ void MainWindow::slotTableEdit(QModelIndex topleft, QModelIndex bottomright) redrawTable();//重绘表格 if(connected)//若正在连接状态则向gdb发送新的变量列表 - { - QStringList nameList; - for(int index=0;indexsetDisplayList(nameList); - } + updateGDBList(); } } else if(topleft.column()==2 && topleft.row()!=varList.size())//若编辑的是第二列,表示需进行变量值修改 @@ -191,6 +197,28 @@ void MainWindow::slotTableTimerTrig() } } +//日志监视定时器触发,获取并解析日志信息 +void MainWindow::slotLogTimerTrig() +{ + const QString cmd="print/u logQueue.size>0?(logQueue.size--,logQueue.startPos=(logQueue.startPos+1)%logQueue.maxSize,*logQueue.buf[logQueue.startPos]@logQueue.lenBuf[logQueue.startPos]):{0}\r\n"; + QString raw=gdb->runCmd(cmd);//命令GDB从下位机出队一条日志 + QList numList=gdb->getUintArrayFromDisplay(raw);//从日志中解析缓冲区数组内容 + if(numList.size()>1) + { + char attr=numList.at(0);//日志属性字符 + QStringList logList;//将缓冲区数组数据拆分成几个字符串 + logList<<""<<""<<""<<""; + for(int pos=1,strNum=0; posaddLog(attr,logList[0],logList[1],logList[2].toULongLong(),logList[3]);//向日志窗口添加一条日志 + } +} + //选择器窗口添加到编辑框槽函数 void MainWindow::slotOnVarAdd2Edit(const QString &name) { @@ -265,10 +293,7 @@ void MainWindow::setConnState(bool connect) { gdb->loadSymbolFile(ui->txt_axf_path->text());//设置gdb符号文件 gdb->connectToRemote("localhost:3333");//连接gdb到ocd - QStringList nameList; - for(int index=0;indexsetDisplayList(nameList); + updateGDBList();//向gdb发送变量列表 for(int i=0;istart();//开启定时查看变量值 tableTimer->start();//开启表格定时刷新 + if(ui->cb_log->isChecked())//开启日志监视定时器 + logTimer->start(); + logWindow->clearLog();//清空日志列表 + ui->bt_conn->setText("断开连接"); ui->bt_reset->setEnabled(true);//使能复位按钮 ui->rb_openocd->setEnabled(false);//失能连接方式选择 @@ -290,6 +319,8 @@ void MainWindow::setConnState(bool connect) { watchTimer->stop();//停止定时查看和定时刷新表格 tableTimer->stop(); + if(ui->cb_log->isChecked())//停止日志监视定时器 + logTimer->stop(); gdb->disconnectFromRemote();//断开gdb gdb->unloadSymbolFile();//卸载符号文件 if(ui->rb_openocd->isChecked())//根据连接方式选择结束ocd @@ -366,7 +397,7 @@ void MainWindow::redrawTable() tableModel->setHeaderData(0,Qt::Horizontal,"变量名");//设置表头 tableModel->setHeaderData(1,Qt::Horizontal,"当前值"); tableModel->setHeaderData(2,Qt::Horizontal,"修改变量"); - tableModel->setHeaderData(3,Qt::Horizontal,"使能绘图"); + tableModel->setHeaderData(3,Qt::Horizontal,"显示图像"); tableModel->setHeaderData(4,Qt::Horizontal,"图线颜色"); for(int i=0;itb_var->resizeColumnToContents(0);//根据第一列内容自动调整列宽 } +//更新GDB的display列表 +void MainWindow::updateGDBList() +{ + QStringList nameList; + for(int index=0;indexsetDisplayList(nameList); +} + //保存配置到指定路径的文件 void MainWindow::saveToFile(const QString &filename) { @@ -405,6 +445,7 @@ void MainWindow::saveToFile(const QString &filename) settings.setValue("Target",ui->cb_target->currentText()); settings.setValue("AxfChosen",axfChosen); settings.setValue("AxfPath",ui->txt_axf_path->text()); + settings.setValue("LogEnabled",ui->cb_log->isChecked()); settings.setValue("VarNum",varList.size()); settings.endGroup(); @@ -434,6 +475,7 @@ void MainWindow::loadFromFile(const QString &filename) ui->cb_target->setCurrentText(settings.value("Target").toString()); axfChosen=settings.value("AxfChosen",true).toBool(); ui->txt_axf_path->setText(settings.value("AxfPath").toString()); + ui->cb_log->setChecked(settings.value("LogEnabled",false).toBool()); int varNum=settings.value("VarNum").toInt(); settings.endGroup(); @@ -488,6 +530,28 @@ bool MainWindow::exportCSV(const QString &filename) return true; } +//读取全局配置 +void MainWindow::loadGlobalConf() +{ + QSettings settings("conf.ini",QSettings::IniFormat); + settings.setIniCodec("GBK"); + + ui->action_show_graph->setChecked(settings.value("GraphWindowOpen",true).toBool()); + ui->action_show_selector->setChecked(settings.value("ListWindowOpen",true).toBool()); + ui->action_show_log->setChecked(settings.value("LogWindowOpen",true).toBool()); +} + +//保存全局配置 +void MainWindow::saveGlobalConf() +{ + QSettings settings("conf.ini",QSettings::IniFormat); + settings.setIniCodec("GBK"); + + settings.setValue("GraphWindowOpen",!graph->isHidden()); + settings.setValue("ListWindowOpen",!listWindow->isHidden()); + settings.setValue("LogWindowOpen",!logWindow->isHidden()); +} + //复位按钮点击,向gdb发送复位指令 void MainWindow::on_bt_reset_clicked() { @@ -619,13 +683,6 @@ void MainWindow::on_action_help_triggered() HelpWindow().exec(); } -//显示绘图窗口菜单点击 -void MainWindow::on_action_show_graph_triggered() -{ - graph->show(); - graph->activateWindow(); -} - //刷新连接配置菜单点击 void MainWindow::on_action_refresh_conf_triggered() { @@ -646,13 +703,6 @@ void MainWindow::setStylesheet() qApp->setStyleSheet(QLatin1String(qss.readAll())); } -//显示选择窗口菜单点击 -void MainWindow::on_action_show_selector_triggered() -{ - listWindow->show(); - listWindow->activateWindow(); -} - //反馈菜单点击 void MainWindow::on_action_feedback_triggered() { @@ -735,3 +785,15 @@ void MainWindow::on_bt_refresh_serial_clicked() ui->cb_com->clear(); ui->cb_com->addItems(serialocd->getSerialList()); } + +//日志使能状态切换 +void MainWindow::on_cb_log_toggled(bool checked) +{ + if(connected) + { + if(checked) + logTimer->start(); + else + logTimer->stop(); + } +} diff --git a/mainwindow.h b/mainwindow.h index 7c7f34d..11ec28f 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -27,6 +27,7 @@ #include #include #include +#include QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } @@ -46,6 +47,7 @@ private slots: void slotTableEdit(QModelIndex topleft, QModelIndex bottomright); void slotWatchTimerTrig(); void slotTableTimerTrig(); + void slotLogTimerTrig(); void slotOnVarAdd2Edit(const QString &name); void slotOnVarAdd2List(const QString &name); void slotOnConnErrorOccur(const QString &info); @@ -59,15 +61,14 @@ private slots: void on_action_export_triggered(); void on_action_about_triggered(); void on_action_help_triggered(); - void on_action_show_graph_triggered(); void on_action_refresh_conf_triggered(); void on_action_homepage_triggered(); - void on_action_show_selector_triggered(); void on_action_feedback_triggered(); void on_action_checkupdate_triggered(); void on_rb_openocd_toggled(bool checked); void on_rb_serialocd_toggled(bool checked); void on_bt_refresh_serial_clicked(); + void on_cb_log_toggled(bool checked); private: Ui::MainWindow *ui; @@ -78,12 +79,13 @@ private: bool connected=false;//标记当前是否已连接 QStandardItemModel *tableModel;//表格数据 QList varList;//变量列表 - QTimer *watchTimer,*tableTimer;//定时器,用于查看变量值和刷新表格 + QTimer *watchTimer,*tableTimer,*logTimer;//定时器,用于查看变量值、刷新表格和监视日志 QElapsedTimer *stampTimer;//时间戳定时器指针 GraphWindow *graph;//绘图窗口指针 bool isWatchProcessing=false;//标记当前是否正在处理变量值查看 bool axfChosen=false;//是否已经选择了axf文件 ListWindow *listWindow; + LogWindow *logWindow; void checkUpdate(); void setStylesheet(); void setConnState(bool connect); @@ -91,8 +93,11 @@ private: void loadConfFileList(); void initTable(); void redrawTable(); + void updateGDBList(); void saveToFile(const QString &filename); void loadFromFile(const QString &filename); bool exportCSV(const QString &filename); + void loadGlobalConf(); + void saveGlobalConf(); }; #endif // MAINWINDOW_H diff --git a/mainwindow.ui b/mainwindow.ui index 1f68f1a..cb52dc2 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -159,6 +159,13 @@ + + + + 监视日志 + + + @@ -246,6 +253,7 @@ + @@ -278,8 +286,11 @@ + + true + - 显示绘图窗口 + 绘图窗口 @@ -296,8 +307,11 @@ + + true + - 显示选择窗口 + 选择窗口 @@ -310,6 +324,14 @@ 检查更新 + + + true + + + 日志窗口 + + diff --git a/qss/light-blue.qss b/qss/light-blue.qss index 06c0f27..cdd5ddf 100644 --- a/qss/light-blue.qss +++ b/qss/light-blue.qss @@ -137,7 +137,7 @@ QMenu{ QMenu:item{ background-color: white; - padding: 8px 15px; + padding: 8px 30px; } QMenu::item:selected{ diff --git a/serialocd.cpp b/serialocd.cpp index dd00236..a22af20 100644 --- a/serialocd.cpp +++ b/serialocd.cpp @@ -236,8 +236,6 @@ void SerialOCD::sendSerialReadMem(int addr, int len) { if(port->isOpen()) { - if(len>8)//最多一次性读取8个字节 - len=8; QByteArray arr; arr.resize(8); arr[0]=DEBUG_FRAME_HEADER;