增加串口模式及下位机代码;提升GDB执行速度;增加对"class"的展开支持;变量名匹配排除':';修复选择窗口根节点展开出现成员变量名的问题;修复删除变量后GDB仍继续监视的问题;版本号更新至V1.1.0

This commit is contained in:
skythinker 2022-02-03 22:14:14 +08:00
parent 5a66c64a59
commit 0969c1d5ce
19 changed files with 904 additions and 110 deletions

View File

@ -1,5 +1,6 @@
QT += core gui QT += core gui
QT += network QT += network
QT += serialport
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
@ -10,7 +11,7 @@ CONFIG += c++11
# depend on your compiler). Please consult the documentation of the # depend on your compiler). Please consult the documentation of the
# deprecated API in order to know how to port your code away from it. # deprecated API in order to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS \ DEFINES += QT_DEPRECATED_WARNINGS \
APP_VERSION=\\\"1.0.2\\\" APP_VERSION=\\\"1.1.0\\\"
# You can also make your code fail to compile if it uses deprecated APIs. # You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line. # In order to do so, uncomment the following line.
@ -25,7 +26,8 @@ SOURCES += \
listwindow.cpp \ listwindow.cpp \
main.cpp \ main.cpp \
mainwindow.cpp \ mainwindow.cpp \
openocd.cpp openocd.cpp \
serialocd.cpp
HEADERS += \ HEADERS += \
aboutwindow.h \ aboutwindow.h \
@ -35,6 +37,7 @@ HEADERS += \
listwindow.h \ listwindow.h \
mainwindow.h \ mainwindow.h \
openocd.h \ openocd.h \
serialocd.h \
vartype.h vartype.h
FORMS += \ FORMS += \

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject> <!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 4.11.0, 2022-01-31T00:06:10. --> <!-- Written by QtCreator 4.11.0, 2022-02-03T22:09:38. -->
<qtcreator> <qtcreator>
<data> <data>
<variable>EnvironmentId</variable> <variable>EnvironmentId</variable>

View File

@ -4,19 +4,25 @@
## 项目简介 ## 项目简介
本程序使用QT编写基于OpenOCD和GDB用于硬件设备的调试有以下几个功能 本程序使用QT编写用于硬件设备的调试可直接驱动串口或各种调试器基于OpenOCD支持有以下几个主要特性
* **实时查看**和**修改**变量值 * 主要功能
* 变量值**波形实时绘制** * **实时查看**和**修改**变量值
* 采样数据导出到CSV表格 * 变量值**波形实时绘制**
* 采样频率约100Hz * 采样数据导出到CSV表格
程序理论上支持OpenOCD所支持的各种调试器及硬件芯片如STLink、JLink、CMSIS-DAP等以及STM32全系列等 * 调试器模式理论上支持OpenOCD允许GDB连接的各种调试器及硬件芯片如STLink、JLink、CMSIS-DAP等以及STM32全系列等
目前已测试STLink和CMSIS-DAP对STM32F103RCT6芯片的调试未发现问题 * 调试器模式下采样约100Hz串口模式下约70Hz
* 图形化变量选择器
> 目前进行了以下设备的测试
> 调试器STLink、CMSIS-DAP
> 目标芯片STM32F103RCT6、STM32F103C8T6
![运行演示](imgs/run-demo.png) ![运行演示](imgs/run-demo.png)
@ -24,45 +30,55 @@
## 使用方法 ## 使用方法
1. 在下拉框中选择调试器和芯片类型选择Axf文件路径点击连接即可尝试连接芯片 1. 若使用串口连接,需先将下位机程序移植到目标芯片中 [移植说明](lower/README.md)
2. 在表格最后一行变量名处填写变量名可以添加查看变量选中变量名按Del键可以删除变量 2. 点击设置符号文件,添加变量
> 注变量名不仅可以填入单个变量名还可以填入合法的C语言表达式GDB支持即可复合类型不能修改和绘图只能实时查看 * 加载符号文件后即可在变量选择窗口添加需要查看的变量,可以直接添加到列表或添加到编辑框后进行手动编辑
3. 在选择窗口中可以查看所选Axf文件包含的变量名,也可以一键添加到编辑框或变量列表中 * 在主窗口表格最后一行变量名(编辑框)处手动填写也可以添加变量
* 在主窗口中选中变量名按Del键可删除变量
* 变量名不仅可以填入单个变量名还可以填入合法的C语言表达式GDB支持即可复合类型不能修改和绘图只能实时查看
3. 选择连接模式,连接芯片,连接后程序开始循环采样
* 调试器模式下,在下拉框中选择调试器和芯片类型,点击连接目标
* 串口模式下,点击刷新串口加载串口列表,选中所连接的串口,点击连接目标
4. 编辑`修改变量`列可以修改变量值,双击`图线颜色`列可以选择绘图颜色 4. 编辑`修改变量`列可以修改变量值,双击`图线颜色`列可以选择绘图颜色
![基本操作](imgs/simp-oper.gif)
5. 单击`变量名`列选中对应的变量,绘图窗口会加粗绘制波形,左下角会显示当前值和查看值(拖动鼠标进行查看) 5. 单击`变量名`列选中对应的变量,绘图窗口会加粗绘制波形,左下角会显示当前值和查看值(拖动鼠标进行查看)
6. 绘图界面说明可以在绘图窗口点击操作说明查看,滚轮配合`Ctrl`、`Shift`、`Alt`可以实现画面的缩放和移动 6. 绘图界面说明可以在绘图窗口点击操作说明查看,滚轮配合`Ctrl`、`Shift`、`Alt`可以实现画面的缩放和移动
![绘图操作](imgs/graph-oper.gif) ![操作演示](imgs/oper-sample.gif)
--- ---
## 菜单项说明 ## 主要菜单项说明
* `显示绘图窗口`:手动关闭绘图窗口后可以通过这个菜单项重新打开绘图窗口并显示到前台
* `刷新连接配置`:连接配置文件位于`openocd/share/openocd/scripts`下的`target`和`interface`中用户可按照OpenOCD语法编写配置脚本放入对应目录下然后点击该菜单项将配置文件加载到下拉选框中 * `刷新连接配置`:连接配置文件位于`openocd/share/openocd/scripts`下的`target`和`interface`中用户可按照OpenOCD语法编写配置脚本放入对应目录下然后点击该菜单项将配置文件加载到下拉选框中
* `保存配置`:软件中所配置的调试器型号、芯片型号、Axf文件路径和各变量的配置都可以通过该菜单项保存到一个配置文件中 * `保存配置`:软件中所配置的连接模式、调试器型号、芯片型号、符号文件路径和各变量的配置都可以通过该菜单项保存到一个配置文件中
* `导入配置`:将上述保存的配置文件重新载入软件中 * `导入配置`:将上述保存的配置文件重新载入软件中
* `导出数据`将获取到的各变量采样数据导出到CSV表格文件 * `导出数据`将获取到的各变量采样数据导出到CSV表格文件
* `显示绘图窗口`:手动关闭绘图窗口后可以通过这个菜单项重新打开并显示到前台
* `显示选择窗口`:手动关闭变量选择窗口后可以通过这个菜单项重新打开并显示到前台
--- ---
## 使用注意事项 ## 使用注意事项
* 若不指定Axf文件,无法使用变量名,只能通过绝对地址进行查看 * 若不指定符号文件,无法使用变量名,只能通过绝对地址进行查看
* 修改Axf路径后需要重新连接 * 修改符号路径后需要重新连接
* 本程序不带下载功能,连接目标前请确认已为目标芯片下载过指定程序;若更换为不同类型的调试器,即使芯片程序没有变动,也应使用更换后的调试器再次下载程序 * 本程序不带下载功能,连接目标前请确认已为目标芯片下载过指定程序;若更换为不同类型的调试器,即使芯片程序没有变动,也应使用更换后的调试器再次下载程序
@ -70,28 +86,38 @@
## 已知问题及解决方法 ## 已知问题及解决方法
* 若程序发生错误闪退,可能在下次运行时无法成功连接目标,可以尝试手动查找`openocd.exe`进程并强制结束 * 若程序在调试器连接模式下发生错误闪退,可能在下次运行时无法成功连接目标,可以尝试手动查找`openocd.exe`进程并强制结束
* 串口模式下从地址0读取单个字节结果恒为0若要读取地址0可以使用大于1字节的类型
--- ---
## TODO ## TODO
* 将采样频率提升至约1kHz * 增加对不同文件中同名变量的区分
* 添加串口模式 * 提升采样频率
* 增加对本地程序的支持
--- ---
## 运行过程简介 ## 运行过程简介
* 连接目标时程序会在后台启动OpenOCD进程进行连接并命令GDB进程连接到OpenOCD * 调试器模式下,连接目标时程序会在后台启动OpenOCD进程进行连接并命令GDB进程连接到OpenOCD串口模式下OpenOCD被替换为一个Tcp服务器用于解析GDB指令并收发串口数据
* 运行过程中程序会不断模拟与GDB进程进行命令行交互在用户添加变量时使用`display expr`指令将变量添加到GDB的查看表中同时定时10ms发送`display`指令并进行正则解,更新用户界面 * 运行过程中程序会不断模拟与GDB进程进行命令行交互在用户添加变量时使用`display expr`指令将变量添加到GDB的查看表中同时定时10ms发送`display`指令并进行正则解,更新用户界面
* 程序开有一个微秒级定时器,每收到一个变量采样数据时,会从该定时器获取当前的时间戳并与数据一起记录下来,同时绘图窗口会不断对历史数据进行更新绘图 * 程序开有一个微秒级定时器,每收到一个变量采样数据时,会从该定时器获取当前的时间戳并与数据一起记录下来,同时绘图窗口会不断对历史数据进行更新绘图
* GDB不支持符号文件路径中含有非ASCII字符因此在连接GDB前会将指定符号文件复制到GDB目录下并用相对路径读取断开连接时删除临时文件
* 变量选择窗口另开有一个GDB进程用于解析符号文件内部使用树形结构存储各级变量信息在用户每展开一级树形图时使用`info variables`、`whatis`、`ptype`指令从GDB获取下一级变量类型信息并用正则表达式解析
--- ---
## 仓库文件说明 ## 仓库文件说明
* 编译程序后需要将`gdb`和`openocd`复制到可执行文件同级目录下 * `lower`目录下为下位机程序
* 编译QT程序后需要将`gdb`和`openocd`目录复制到可执行文件同级目录下

View File

@ -91,7 +91,7 @@
</sizepolicy> </sizepolicy>
</property> </property>
<property name="text"> <property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;hr width=&quot;300&quot;/&gt;&lt;p align=&quot;center&quot;&gt;版本号V1.0.2&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;更新时间2022/1/29&lt;/p&gt;&lt;hr width=&quot;300&quot;/&gt;&lt;p align=&quot;center&quot;&gt;Developed by Skythinker&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;hr width=&quot;300&quot;/&gt;&lt;p align=&quot;center&quot;&gt;版本号V1.1.0&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;更新时间2022/2/3&lt;/p&gt;&lt;hr width=&quot;300&quot;/&gt;&lt;p align=&quot;center&quot;&gt;Developed by Skythinker&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
</widget> </widget>
</item> </item>

View File

@ -17,7 +17,7 @@ QString GDBProcess::runCmd(const QString &cmd)
process->write(cmd.toStdString().c_str()); process->write(cmd.toStdString().c_str());
QString res=""; QString res="";
do{ do{
process->waitForReadyRead(); process->waitForReadyRead(2);
res+=process->readAllStandardOutput(); res+=process->readAllStandardOutput();
}while(!res.endsWith("(gdb) ")); }while(!res.endsWith("(gdb) "));
return res; return res;
@ -29,7 +29,7 @@ void GDBProcess::start()
process->setProgram(QCoreApplication::applicationDirPath()+"/gdb/gdb.exe");//设置程序路径 process->setProgram(QCoreApplication::applicationDirPath()+"/gdb/gdb.exe");//设置程序路径
process->setWorkingDirectory(QCoreApplication::applicationDirPath()+"/gdb");//设置工作路径 process->setWorkingDirectory(QCoreApplication::applicationDirPath()+"/gdb");//设置工作路径
process->setNativeArguments("-q");//设置gdb在安静模式下打开 process->setNativeArguments("-q");//设置gdb在安静模式下打开
process->start(); process->start();//QProcess::Unbuffered|QProcess::ReadWrite);
runCmd("set confirm off\r\n");//设置不要手动确认 runCmd("set confirm off\r\n");//设置不要手动确认
runCmd("set print pretty on\r\n");//设置结构体规范打印 runCmd("set print pretty on\r\n");//设置结构体规范打印
} }
@ -130,7 +130,7 @@ bool GDBProcess::checkExpandableType(const QString &varFullName)
return false; return false;
QString ptype=runCmd(QString("ptype %1\r\n").arg(varFullName));//再使用ptype指令判断是否是其他可展开类型 QString ptype=runCmd(QString("ptype %1\r\n").arg(varFullName));//再使用ptype指令判断是否是其他可展开类型
ptype.remove("type = "); ptype.remove("type = ");
if(ptype.startsWith("struct")||ptype.startsWith("union")) if(ptype.startsWith("struct")||ptype.startsWith("union")||ptype.startsWith("class"))
return true; return true;
return false; return false;
} }
@ -139,7 +139,7 @@ bool GDBProcess::checkExpandableType(const QString &varFullName)
QStringList GDBProcess::getVarListFromRawOutput(const QString &rawVarList) QStringList GDBProcess::getVarListFromRawOutput(const QString &rawVarList)
{ {
QStringList list; QStringList list;
QRegExp rootRx("\\s([^ (]*\\(\\*|)([^ )]+)(\\)\\(.*\\)|\\s:\\s\\d+|);");//正则匹配模板 QRegExp rootRx("\\s([^ (]*\\(\\*|)([^ ):]+)(\\)\\(.*\\)|\\s:\\s\\d+|);");//正则匹配模板
rootRx.setMinimal(true);//非贪心匹配 rootRx.setMinimal(true);//非贪心匹配
int pos=0; int pos=0;
while((pos=rootRx.indexIn(rawVarList,pos))!=-1) while((pos=rootRx.indexIn(rawVarList,pos))!=-1)
@ -149,7 +149,8 @@ QStringList GDBProcess::getVarListFromRawOutput(const QString &rawVarList)
name.remove('*'); name.remove('*');
if(name.contains('['))//手动剔除数组长度部分 if(name.contains('['))//手动剔除数组长度部分
name.remove(QRegExp("\\[\\d+\\]")); name.remove(QRegExp("\\[\\d+\\]"));
list.append(name); if(name!="const")//排除解析出来是保留字的情况
list.append(name);
pos+=rootRx.matchedLength(); pos+=rootRx.matchedLength();
} }
return list; return list;

View File

@ -6,6 +6,8 @@
#include <qcoreapplication.h> #include <qcoreapplication.h>
#include <qfile.h> #include <qfile.h>
#include <qregexp.h> #include <qregexp.h>
#include <qdebug.h>
#include <QTime>
class GDBProcess : public QObject class GDBProcess : public QObject
{ {

View File

@ -32,9 +32,9 @@
<property name="geometry"> <property name="geometry">
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>-735</y>
<width>523</width> <width>523</width>
<height>906</height> <height>1179</height>
</rect> </rect>
</property> </property>
<property name="autoFillBackground"> <property name="autoFillBackground">
@ -53,7 +53,7 @@
</sizepolicy> </sizepolicy>
</property> </property>
<property name="text"> <property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;LinkScope简介&lt;/span&gt;&lt;/p&gt;&lt;p&gt;本程序使用QT编写基于OpenOCD和GDB用于硬件设备的调试可以实时查看并修改变量值有波形绘制和数据导出功能 &lt;/p&gt;&lt;p&gt;程序支持OpenOCD支持的各种调试器及硬件芯片如STLink、JLink、CMSIS-DAP等以及STM32全系列等&lt;/p&gt;&lt;hr&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;使用方法&lt;/span&gt;&lt;/p&gt;&lt;p&gt;1.在下拉框中选择调试器和芯片类型选择Axf文件路径点击连接即可尝试连接芯片 &lt;/p&gt;&lt;p&gt;2.在表格最后一行变量名处填写变量名可以添加查看变量选中变量名按Del键可以删除变量 &lt;/p&gt;&lt;p&gt;3.在选择窗口中可以查看所选Axf文件包含的变量名可以一键添加到编辑框或变量列表&lt;/p&gt;&lt;p&gt;4.编辑【修改变量】列可以修改变量值,双击【图线颜色】列可以选择绘图颜色 &lt;/p&gt;&lt;p&gt;5.单击【变量名】列选中对应的变量,可以在绘图窗口查看历史数据,并会加粗绘制 &lt;/p&gt;&lt;p&gt;6.绘图界面说明请到绘图窗口点击操作说明 &lt;/p&gt;&lt;p&gt;7.点击菜单中的保存/导入配置可以将当前配置保存到INI文件或从文件中恢复配置点击导出数据可以将获取到的采样数据导出到CSV表格文件&lt;/p&gt;&lt;hr&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;注意事项 &lt;/span&gt;&lt;/p&gt;&lt;p&gt;1.修改Axf路径后需要重新连接 &lt;/p&gt;&lt;p&gt;2.在【变量名】列不仅能填写单个变量名还可以填入任何合法的C语言表达式 &lt;/p&gt;&lt;p&gt;3.连接目标前请确认已使用该调试器为目标芯片下载过指定程序 &lt;/p&gt;&lt;p&gt;4.若程序闪退后发现下一次运行时无法连接目标请尝试手动结束openocd.exe进程 &lt;/p&gt;&lt;p&gt;5.连接配置文件位于openocd/share/openocd/scripts下的target和interface中用户可按照openocd语法编写配置脚本放入对应目录下后点击“刷新连接配置”菜单项&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;LinkScope简介&lt;/span&gt;&lt;/p&gt;&lt;p&gt;本程序使用QT编写用于硬件设备的调试可直接驱动串口或各种调试器基于OpenOCD支持有以下几个主要功能&lt;/p&gt;&lt;p&gt;1.实时查看和修改变量值&lt;/p&gt;&lt;p&gt;2.变量值波形实时绘制&lt;/p&gt;&lt;p&gt;3.采样数据导出为CSV表格&lt;/p&gt;&lt;p&gt;调试器连接模式理论上支持OpenOCD允许GDB连接的各种调试器及硬件芯片如STLink、JLink、CMSIS-DAP等以及STM32全系列等&lt;/p&gt;&lt;hr/&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;使用方法&lt;/span&gt;&lt;/p&gt;&lt;p&gt;1.点击【设置符号文件】,添加变量&lt;/p&gt;&lt;p&gt;&amp;gt; 加载符号文件后即可在变量选择窗口添加需要查看的变量,可以直接添加到列表或添加到编辑框后进行手动编辑&lt;/p&gt;&lt;p&gt;&amp;gt; 在主窗口表格最后一行变量名(编辑框)处手动填写也可以添加变量&lt;/p&gt;&lt;p&gt;&amp;gt; 在主窗口中选中变量名按Del键可删除变量&lt;/p&gt;&lt;p&gt;&amp;gt; 变量名不仅可以填入单个变量名还可以填入合法的C语言表达式GDB支持即可复合类型不能修改和绘图只能实时查看&lt;/p&gt;&lt;p&gt;2.选择连接模式,连接芯片,连接后程序开始循环采样&lt;/p&gt;&lt;p&gt;&amp;gt; 调试器模式下,在下拉框中选择调试器和芯片类型,点击连接目标&lt;/p&gt;&lt;p&gt;&amp;gt; 串口模式下,点击刷新串口加载串口列表,选中所连接的串口,点击连接目标&lt;/p&gt;&lt;p&gt;3.编辑【修改变量】列可以修改变量值,双击【图线颜色】列可以选择绘图颜色 &lt;/p&gt;&lt;p&gt;4.单击【变量名】列选中对应的变量,可以在绘图窗口查看历史数据,并会加粗绘制 &lt;/p&gt;&lt;p&gt;5.绘图界面说明请到绘图窗口点击操作说明 &lt;/p&gt;&lt;p&gt;6.点击菜单中的保存/导入配置可以将当前配置保存到INI文件或从文件中恢复配置点击导出数据可以将获取到的采样数据导出到CSV表格文件&lt;/p&gt;&lt;hr/&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;注意事项 &lt;/span&gt;&lt;/p&gt;&lt;p&gt;1.修改符号文件路径后需要重新连接 &lt;/p&gt;&lt;p&gt;2.连接目标前请确认已使用该调试器为目标芯片下载过指定程序 &lt;/p&gt;&lt;p&gt;3.若程序闪退后发现下一次运行时无法连接目标请尝试手动结束openocd.exe进程 &lt;/p&gt;&lt;p&gt;4.连接配置文件位于openocd/share/openocd/scripts下的target和interface中用户可按照openocd语法编写配置脚本放入对应目录下后点击“刷新连接配置”菜单项&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="textFormat"> <property name="textFormat">
<enum>Qt::AutoText</enum> <enum>Qt::AutoText</enum>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 MiB

BIN
imgs/oper-sample.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 MiB

View File

@ -47,6 +47,7 @@ void ListWindow::parseVarChildren(VarNode &node)
if(node.parent==NULL)//传入的是根节点 if(node.parent==NULL)//传入的是根节点
{ {
QString rawVarList=gdb->runCmd("info variables\r\n");//使用info variables指令列出axf中所有变量 QString rawVarList=gdb->runCmd("info variables\r\n");//使用info variables指令列出axf中所有变量
gdb->removeInnerSection(rawVarList,0);//移除内部直接嵌套的部分
QStringList varList=gdb->getVarListFromRawOutput(rawVarList);//解析出变量列表 QStringList varList=gdb->getVarListFromRawOutput(rawVarList);//解析出变量列表
foreach(QString name,varList)//依次添加子节点并更新可展开状态 foreach(QString name,varList)//依次添加子节点并更新可展开状态
{ {
@ -80,7 +81,7 @@ void ListWindow::parseVarChildren(VarNode &node)
QString detailRawVarType=gdb->runCmd(QString("ptype %1\r\n").arg(fullName));//用ptype指令获取详细类型 QString detailRawVarType=gdb->runCmd(QString("ptype %1\r\n").arg(fullName));//用ptype指令获取详细类型
detailRawVarType.remove("type = "); detailRawVarType.remove("type = ");
detailRawVarType.remove("\r\n(gdb) "); detailRawVarType.remove("\r\n(gdb) ");
if(detailRawVarType.startsWith("struct")||detailRawVarType.startsWith("union"))//判定为可展开类型 if(detailRawVarType.startsWith("struct")||detailRawVarType.startsWith("union")||detailRawVarType.startsWith("class"))//判定为可展开类型
{ {
gdb->removeInnerSection(detailRawVarType,detailRawVarType.indexOf('{')+1);//移除内部直接嵌套的部分 gdb->removeInnerSection(detailRawVarType,detailRawVarType.indexOf('{')+1);//移除内部直接嵌套的部分
QStringList varList=gdb->getVarListFromRawOutput(detailRawVarType);//解析出变量列表 QStringList varList=gdb->getVarListFromRawOutput(detailRawVarType);//解析出变量列表

111
lower/README.md Normal file
View File

@ -0,0 +1,111 @@
# 下位机程序说明
---
# 配置项
* **`#define DEBUG_SEND(buf,len)`**需配置为所用平台的串口发送语句将buf所指向的len个字节通过串口发出
* `#define DEBUG_RESET()`:需配置为所用平台的复位语句,上位机中点击复位并运行时会调用该语句
* `#define DEBUG_READ_ADDR_RANGE(addr)`读地址限制条件若请求的地址add不符合条件则返回0x00
* `#define DEBUG_WRITE_ADDR_RANGE(addr)`写地址限制条件若请求的地址addr不符合条件则不会写入
> 注:加粗项为必需配置,其余项若不需要对应功能可不定义
---
# 函数接口
* `void Debug_SerialRecv(uint8_t *buf,uint16_t len);`外部程序需要在收到串口数据时调用该函数进行解析buf为数据首地址len为字节数
> 注:程序使用循环队列作为接收数据缓存,调用该函数时无需保证一次性传入完整数据帧,但应保证一个数据帧接收结束时及时调用该函数
---
# 移植说明
1. 开启一个串口,将下位机程序添加到项目工程中
> 注串口需配置为波特率115200、8位数据位无校验位、1位停止位
2. 修改配置项
3. 在收到串口数据时调用`Debug_SerialRecv`函数
---
# 移植示例
## STM32 & HAL & 中断收发
```c
/****Debug.c****/
//...
//串口发送指令使用串口1
#define DEBUG_SEND(buf,len) HAL_UART_Transmit_IT(&huart1,(buf),(len))
//复位指令
#define DEBUG_RESET() { \
__set_FAULTMASK(1); \
NVIC_SystemReset(); \
}
//...
/****main.c****/
//...
int main(void)
{
//...(需先进行串口初始化)
__HAL_UART_ENABLE_IT(&huart1,UART_IT_RXNE); //使能串口RXNE中断
//...
while(1)
{
//...
}
}
//...
/****stm32f1xx_it.c****/
//...
void USART1_IRQHandler(void) //串口中断服务函数
{
if(__HAL_UART_GET_FLAG(&huart1,UART_FLAG_RXNE)!=RESET) //判定为RXNE中断
{
uint8_t ch=huart1.Instance->DR; //读出收到的字节
Debug_SerialRecv(&ch,1); //进行解析
}
}
//...
```
## Arduino 串口轮询方式
```c++
/****main.ino****/
//...
#define DEBUG_SEND(buf,len) Serial.write((buf),(len))
//...(下位机程序其他部分)
void setup()
{
//...
Serial.begin(115200); //初始化串口波特率为115200
//...
}
void loop()
{
while(Serial.available())
{
uint8_t ch=Serial.read(); //读出串口数据
Debug_SerialRecv(&ch,1); //进行解析
}
//...其余代码不能有阻塞情况,需尽快执行完毕
}
```

144
lower/debug.c Normal file
View File

@ -0,0 +1,144 @@
#include <stdint.h>
#include "usart.h" //本例使用STM32其他平台无需引用此头文件
/**************↓配置区↓**************/
//串口发送语句需实现将buf指向的len个字节通过串口发出
#define DEBUG_SEND(buf,len) HAL_UART_Transmit_IT(&huart1,(buf),(len))
//复位指令,若设备不支持或无需此功能可不定义
#define DEBUG_RESET() { \
__set_FAULTMASK(1); \
NVIC_SystemReset(); \
}
//读地址限制条件若请求的地址addr不符合条件则返回0x00无需限制可不定义
#define DEBUG_READ_ADDR_RANGE(addr) (addr>=0x20000000 && addr<=0x20001000)
//写地址限制条件若请求的地址addr不符合条件则不会写入无需限制可不定义
#define DEBUG_WRITE_ADDR_RANGE(addr) (addr>=0x20000000 && addr<=0x20001000)
/**************↑配置区↑**************/
//接收缓存区(循环队列)大小,不建议修改
#define DEBUG_RXBUF_SIZE 30
//发送缓存区大小,不建议修改
#define DEBUG_TXBUF_SIZE 30
//数据帧格式帧头1B+帧长1B+命令码1B+数据
//固定帧头
#define DEBUG_FRAME_HEADER 0xDB
//命令码枚举,用于标记数据帧类型
typedef enum{
SerialCMD_ReadMem, //读内存数据
SerialCMD_WriteMem, //写内存数据
SerialCMD_Reset //复位
}SerialCMD;
//串口发送缓存区
uint8_t debugTxBuf[DEBUG_TXBUF_SIZE];
//以下实现了一个循环队列用于串口接收,无溢出检查
struct{
uint8_t buf[DEBUG_RXBUF_SIZE]; //缓存区
uint16_t startPos,endPos; //队头队尾指针
}debugRxQueue={0};
//入队一个字符
#define DEBUG_QUEUE_PUSH(ch) { \
debugRxQueue.buf[debugRxQueue.endPos++]=(ch); \
if(debugRxQueue.endPos>=DEBUG_RXBUF_SIZE) \
debugRxQueue.endPos-=DEBUG_RXBUF_SIZE; \
}
//出队一个字符
#define DEBUG_QUEUE_POP() { \
debugRxQueue.startPos++; \
if(debugRxQueue.startPos>=DEBUG_RXBUF_SIZE) \
debugRxQueue.startPos-=DEBUG_RXBUF_SIZE; \
}
//获取队头字符
#define DEBUG_QUEUE_TOP() (debugRxQueue.buf[debugRxQueue.startPos])
//获取队列大小
#define DEBUG_QUEUE_SIZE() \
(debugRxQueue.startPos<=debugRxQueue.endPos? \
debugRxQueue.endPos-debugRxQueue.startPos: \
debugRxQueue.endPos+DEBUG_RXBUF_SIZE-debugRxQueue.startPos)
//获取队列第pos个元素
#define DEBUG_QUEUE_AT(pos) \
(debugRxQueue.startPos+(pos)<DEBUG_RXBUF_SIZE? \
debugRxQueue.buf[debugRxQueue.startPos+(pos)]: \
debugRxQueue.buf[debugRxQueue.startPos+(pos)-DEBUG_RXBUF_SIZE])
//函数声明
void Debug_SerialRecv(uint8_t *buf,uint16_t len);
void Debug_ParseBuffer(void);
//串口收到数据后传入本函数进行解析,需被外部调用
void Debug_SerialRecv(uint8_t *buf,uint16_t len)
{
for(uint16_t i=0;i<len;i++) //将收到的数据依次入队
DEBUG_QUEUE_PUSH(buf[i]);
Debug_ParseBuffer(); //进入解析
}
//解析串口数据
void Debug_ParseBuffer()
{
if(DEBUG_QUEUE_AT(0)==DEBUG_FRAME_HEADER) //第一个字节为帧头,可以继续解析
{
if(DEBUG_QUEUE_SIZE()>2 && DEBUG_QUEUE_SIZE()>=DEBUG_QUEUE_AT(1)) //帧长足够,可以解析
{
uint16_t frameLen=DEBUG_QUEUE_AT(1);//读出帧长
uint8_t cmd=DEBUG_QUEUE_AT(2); //读出命令码
if(cmd==SerialCMD_ReadMem) //若要读取内存数据
{
uint8_t byteNum=DEBUG_QUEUE_AT(3); //要读取的字节数
uint32_t addr=0; //计算目标地址
for(uint8_t i=0;i<4;i++)
addr|=((uint32_t)DEBUG_QUEUE_AT(4+i))<<(i*8);
debugTxBuf[0]=DEBUG_FRAME_HEADER; //构建发送数据帧
debugTxBuf[1]=byteNum+3;
debugTxBuf[2]=SerialCMD_ReadMem;
for(uint8_t i=0;i<byteNum;i++) //依次写入指定地址的数据
{
uint8_t byte=0;
#ifdef DEBUG_READ_ADDR_RANGE
if(DEBUG_READ_ADDR_RANGE((addr+i)))
#endif
byte=*(uint8_t*)(addr+i);
debugTxBuf[i+3]=byte;
}
DEBUG_SEND(debugTxBuf,byteNum+3); //串口发送
for(uint8_t i=0;i<frameLen;i++) //将本帧出队
DEBUG_QUEUE_POP();
}
else if(cmd==SerialCMD_WriteMem) //若要写入内存数据
{
uint8_t byteNum=frameLen-7; //要写入的字节数
uint32_t addr=0; //计算目标地址
for(uint8_t i=0;i<4;i++)
addr|=((uint32_t)DEBUG_QUEUE_AT(3+i))<<(i*8);
for(uint8_t i=0;i<byteNum;i++) //依次写入数据
{
#ifdef DEBUG_WRITE_ADDR_RANGE
if(DEBUG_WRITE_ADDR_RANGE((addr+i)))
#endif
*(uint8_t*)(addr+i)=DEBUG_QUEUE_AT(7+i);
}
for(uint8_t i=0;i<frameLen;i++) //将本帧出队
DEBUG_QUEUE_POP();
}
else if(cmd==SerialCMD_Reset) //若要复位
{
#ifdef DEBUG_RESET
DEBUG_RESET();
#endif
}
if(DEBUG_QUEUE_SIZE()>0) //若后面还有数据,进行递归解析
Debug_ParseBuffer();
}
}
else //数据帧错误
{
while(DEBUG_QUEUE_AT(0)!=DEBUG_FRAME_HEADER && DEBUG_QUEUE_SIZE()>0) //将错误数据出队
DEBUG_QUEUE_POP();
if(DEBUG_QUEUE_SIZE()>0) //若后面还有数据,继续解析
Debug_ParseBuffer();
}
}

View File

@ -36,14 +36,14 @@ MainWindow::MainWindow(QWidget *parent)
initTable(); initTable();
openocd=new OpenOCD(); openocd=new OpenOCD();
connect(openocd,&OpenOCD::onErrorOccur,[=](const QString &info){//OpenOCD发生连接错误断开连接并弹框 connect(openocd,&OpenOCD::onErrorOccur,this,&MainWindow::slotOnConnErrorOccur,Qt::QueuedConnection);
setConnState(false);
QMessageBox::information(this,"连接错误",info); serialocd=new SerialOCD();
}); connect(serialocd,&SerialOCD::onErrorOccur,this,&MainWindow::slotOnConnErrorOccur,Qt::QueuedConnection);
gdb=new GDBProcess();//创建并启动GDB gdb=new GDBProcess();//创建并启动GDB
gdb->setTempSymbolFileName("tmp");//设定临时符号文件名 gdb->setTempSymbolFileName("tmp");//设定临时符号文件名
gdb->start(); gdb->start();//启动gdb进程
loadConfFileList();//从openocd文件夹中读取配置文件列表 loadConfFileList();//从openocd文件夹中读取配置文件列表
} }
@ -54,7 +54,10 @@ MainWindow::~MainWindow()
{ {
gdb->disconnectFromRemote(); gdb->disconnectFromRemote();
gdb->unloadSymbolFile(); gdb->unloadSymbolFile();
openocd->stop(); if(ui->rb_openocd->isChecked())
openocd->stop();
else if(ui->rb_serialocd->isChecked())
serialocd->stopConnect();
} }
gdb->stop();//结束gdb进程 gdb->stop();//结束gdb进程
delete ui; delete ui;
@ -65,6 +68,7 @@ MainWindow::~MainWindow()
delete watchTimer; delete watchTimer;
delete tableTimer; delete tableTimer;
delete graph; delete graph;
delete listWindow;
} }
//按键事件监听DEL键按下用于删除单个变量 //按键事件监听DEL键按下用于删除单个变量
@ -85,6 +89,14 @@ void MainWindow::keyPressEvent(QKeyEvent *event)
{ {
varList.removeAt(index); varList.removeAt(index);
redrawTable(); redrawTable();
if(connected)//若正在连接状态则向gdb发送新的变量列表
{
QStringList nameList;
for(int index=0;index<varList.size();index++)
nameList<<varList.at(index).name;
gdb->setDisplayList(nameList);
}
} }
} }
} }
@ -195,6 +207,14 @@ void MainWindow::slotOnVarAdd2List(const QString &name)
editItem->setText(name);//在编辑框中填入变量名,会直接添加到列表中 editItem->setText(name);//在编辑框中填入变量名,会直接添加到列表中
} }
//发生连接错误时的槽函数
void MainWindow::slotOnConnErrorOccur(const QString &info)
{
if(connected)
setConnState(false);
QMessageBox::information(this,"连接错误",info);
}
//连接按钮点击,触发连接状态切换 //连接按钮点击,触发连接状态切换
void MainWindow::on_bt_conn_clicked() void MainWindow::on_bt_conn_clicked()
{ {
@ -216,9 +236,32 @@ void MainWindow::setConnState(bool connect)
if(connect)//进行连接 if(connect)//进行连接
{ {
ui->bt_conn->setEnabled(false);//先禁用连接按钮,防止多次点击 ui->bt_conn->setEnabled(false);//先禁用连接按钮,防止多次点击
openocd->start(ui->cb_interface->currentText(),ui->cb_target->currentText(),3333);//运行openocd进程进行目标连接
sleep(500);//等待500ms bool ocdStartSuccess=false;
if(openocd->isRunning())//若ocd成功启动
if(ui->rb_openocd->isChecked())//选择的是OpenOCD模式
{
openocd->start(ui->cb_interface->currentText(),ui->cb_target->currentText(),3333);//运行openocd进程进行目标连接
sleep(500);//等待500ms
if(openocd->isRunning())
ocdStartSuccess=true;
}
else if(ui->rb_serialocd->isChecked())//选择的是串口模式
{
if(!ui->cb_com->currentText().isEmpty())
{
serialocd->startConnect(ui->cb_com->currentText(),3333);//启动SerialOCD
sleep(500);
if(serialocd->isRunning())
ocdStartSuccess=true;
}
else
{
QMessageBox::information(this,"连接错误","请先选择串口号");
}
}
if(ocdStartSuccess)//若ocd成功启动
{ {
gdb->loadSymbolFile(ui->txt_axf_path->text());//设置gdb符号文件 gdb->loadSymbolFile(ui->txt_axf_path->text());//设置gdb符号文件
gdb->connectToRemote("localhost:3333");//连接gdb到ocd gdb->connectToRemote("localhost:3333");//连接gdb到ocd
@ -236,6 +279,8 @@ void MainWindow::setConnState(bool connect)
ui->bt_conn->setText("断开连接"); ui->bt_conn->setText("断开连接");
ui->bt_reset->setEnabled(true);//使能复位按钮 ui->bt_reset->setEnabled(true);//使能复位按钮
ui->rb_openocd->setEnabled(false);//失能连接方式选择
ui->rb_serialocd->setEnabled(false);
connected=true;//更新连接标志 connected=true;//更新连接标志
} }
@ -247,9 +292,14 @@ void MainWindow::setConnState(bool connect)
tableTimer->stop(); tableTimer->stop();
gdb->disconnectFromRemote();//断开gdb gdb->disconnectFromRemote();//断开gdb
gdb->unloadSymbolFile();//卸载符号文件 gdb->unloadSymbolFile();//卸载符号文件
openocd->stop(); if(ui->rb_openocd->isChecked())//根据连接方式选择结束ocd
openocd->stop();
else if(ui->rb_serialocd->isChecked())
serialocd->stopConnect();
ui->bt_conn->setText("连接目标"); ui->bt_conn->setText("连接目标");
ui->bt_reset->setEnabled(false);//禁用复位按钮 ui->bt_reset->setEnabled(false);//禁用复位按钮
ui->rb_openocd->setEnabled(true);//使能连接方式选择
ui->rb_serialocd->setEnabled(true);
connected=false;//更新连接标志 connected=false;//更新连接标志
} }
} }
@ -342,6 +392,8 @@ void MainWindow::saveToFile(const QString &filename)
settings.setIniCodec("GBK"); settings.setIniCodec("GBK");
settings.beginGroup("Global");//写入全局配置 settings.beginGroup("Global");//写入全局配置
settings.setValue("OpenocdMode",ui->rb_openocd->isChecked());
settings.setValue("SerialocdMode",ui->rb_serialocd->isChecked());
settings.setValue("Interface",ui->cb_interface->currentText()); settings.setValue("Interface",ui->cb_interface->currentText());
settings.setValue("Target",ui->cb_target->currentText()); settings.setValue("Target",ui->cb_target->currentText());
settings.setValue("AxfChosen",axfChosen); settings.setValue("AxfChosen",axfChosen);
@ -367,6 +419,8 @@ void MainWindow::loadFromFile(const QString &filename)
settings.setIniCodec("GBK"); settings.setIniCodec("GBK");
settings.beginGroup("Global");//读取全局配置 settings.beginGroup("Global");//读取全局配置
ui->rb_openocd->setChecked(settings.value("OpenocdMode",true).toBool());
ui->rb_serialocd->setChecked(settings.value("SerialocdMode",false).toBool());
ui->cb_interface->setCurrentText(settings.value("Interface").toString()); ui->cb_interface->setCurrentText(settings.value("Interface").toString());
ui->cb_target->setCurrentText(settings.value("Target").toString()); ui->cb_target->setCurrentText(settings.value("Target").toString());
axfChosen=settings.value("AxfChosen",true).toBool(); axfChosen=settings.value("AxfChosen",true).toBool();
@ -653,3 +707,22 @@ void MainWindow::on_action_checkupdate_triggered()
{ {
checkUpdate(); checkUpdate();
} }
//调试器模式选择切换
void MainWindow::on_rb_openocd_toggled(bool checked)
{
ui->box_openocd->setEnabled(checked);
}
//串口模式选择切换
void MainWindow::on_rb_serialocd_toggled(bool checked)
{
ui->box_serial->setEnabled(checked);
}
//刷新串口点击
void MainWindow::on_bt_refresh_serial_clicked()
{
ui->cb_com->clear();
ui->cb_com->addItems(serialocd->getSerialList());
}

View File

@ -26,6 +26,7 @@
#include <qnetworkreply.h> #include <qnetworkreply.h>
#include <gdbprocess.h> #include <gdbprocess.h>
#include <openocd.h> #include <openocd.h>
#include <serialocd.h>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; } namespace Ui { class MainWindow; }
@ -42,12 +43,12 @@ public:
void closeEvent(QCloseEvent *event); void closeEvent(QCloseEvent *event);
private slots: private slots:
//void slotOCDErrorReady();
void slotTableEdit(QModelIndex topleft, QModelIndex bottomright); void slotTableEdit(QModelIndex topleft, QModelIndex bottomright);
void slotWatchTimerTrig(); void slotWatchTimerTrig();
void slotTableTimerTrig(); void slotTableTimerTrig();
void slotOnVarAdd2Edit(const QString &name); void slotOnVarAdd2Edit(const QString &name);
void slotOnVarAdd2List(const QString &name); void slotOnVarAdd2List(const QString &name);
void slotOnConnErrorOccur(const QString &info);
void on_bt_conn_clicked(); void on_bt_conn_clicked();
void on_bt_set_axf_clicked(); void on_bt_set_axf_clicked();
void on_bt_reset_clicked(); void on_bt_reset_clicked();
@ -64,12 +65,16 @@ private slots:
void on_action_show_selector_triggered(); void on_action_show_selector_triggered();
void on_action_feedback_triggered(); void on_action_feedback_triggered();
void on_action_checkupdate_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();
private: private:
Ui::MainWindow *ui; Ui::MainWindow *ui;
QProcess *ocdProcess; QProcess *ocdProcess;
GDBProcess *gdb;//GDB进程控制 GDBProcess *gdb;//GDB进程控制
OpenOCD *openocd;//OpenOCD进程控制 OpenOCD *openocd;//OpenOCD进程控制
SerialOCD *serialocd;//串口ocd控制
bool connected=false;//标记当前是否已连接 bool connected=false;//标记当前是否已连接
QStandardItemModel *tableModel;//表格数据 QStandardItemModel *tableModel;//表格数据
QList<VarInfo> varList;//变量列表 QList<VarInfo> varList;//变量列表
@ -82,7 +87,6 @@ private:
void checkUpdate(); void checkUpdate();
void setStylesheet(); void setStylesheet();
void setConnState(bool connect); void setConnState(bool connect);
//void setOCDState(bool connect);
void sleep(uint32_t ms); void sleep(uint32_t ms);
void loadConfFileList(); void loadConfFileList();
void initTable(); void initTable();

View File

@ -14,69 +14,155 @@
<string>MainWindow</string> <string>MainWindow</string>
</property> </property>
<widget class="QWidget" name="centralwidget"> <widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout_2">
<item> <item>
<widget class="QTableView" name="tb_var"/> <widget class="QTableView" name="tb_var"/>
</item> </item>
<item> <item>
<widget class="QGroupBox" name="groupBox"> <layout class="QGridLayout" name="gridLayout">
<property name="title"> <item row="0" column="0">
<string>连接设置</string> <layout class="QHBoxLayout" name="horizontalLayout_4">
</property> <item>
<layout class="QHBoxLayout" name="horizontalLayout"> <widget class="QRadioButton" name="rb_openocd">
<item> <property name="text">
<widget class="QLabel" name="label"> <string/>
<property name="text"> </property>
<string>调试器</string> <property name="checked">
</property> <bool>true</bool>
</widget> </property>
</item> </widget>
<item> </item>
<widget class="QComboBox" name="cb_interface"> <item>
<property name="sizePolicy"> <widget class="QGroupBox" name="box_openocd">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed"> <property name="enabled">
<horstretch>0</horstretch> <bool>true</bool>
<verstretch>0</verstretch> </property>
</sizepolicy> <property name="title">
</property> <string>调试器</string>
</widget> </property>
</item> <layout class="QHBoxLayout" name="horizontalLayout">
<item> <item>
<widget class="QLabel" name="label_2"> <widget class="QLabel" name="label">
<property name="text"> <property name="text">
<string>目标芯片</string> <string>调试器类型</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QComboBox" name="cb_target"> <widget class="QComboBox" name="cb_interface">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed"> <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QPushButton" name="bt_conn"> <widget class="QLabel" name="label_2">
<property name="text"> <property name="text">
<string>连接目标</string> <string>目标芯片</string>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QPushButton" name="bt_reset"> <widget class="QComboBox" name="cb_target">
<property name="enabled"> <property name="enabled">
<bool>false</bool> <bool>true</bool>
</property> </property>
<property name="text"> <property name="sizePolicy">
<string>复位并运行</string> <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
</property> <horstretch>0</horstretch>
</widget> <verstretch>0</verstretch>
</item> </sizepolicy>
</layout> </property>
</widget> </widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QRadioButton" name="rb_serialocd">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="box_serial">
<property name="enabled">
<bool>false</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>串口</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="label_4">
<property name="text">
<string>串口号</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="cb_com">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="bt_refresh_serial">
<property name="text">
<string>刷新串口</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item row="0" column="1" rowspan="2">
<widget class="QGroupBox" name="groupBox_4">
<property name="title">
<string>操作</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QPushButton" name="bt_conn">
<property name="text">
<string>连接目标</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="bt_reset">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>复位并运行</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item> </item>
<item> <item>
<widget class="QGroupBox" name="groupBox_2"> <widget class="QGroupBox" name="groupBox_2">

285
serialocd.cpp Normal file
View File

@ -0,0 +1,285 @@
#include "serialocd.h"
SerialOCD::SerialOCD(QObject *parent) : QThread(parent)
{
}
//启动OCD由主线程调用
void SerialOCD::startConnect(const QString &serial, int port)
{
serialName=serial;//保存串口号和端口号后启动子线程
listenPort=port;
start();//启动线程
}
//停止OCD(由主线程调用)
void SerialOCD::stopConnect()
{
emit onStopConnect();//发送停止信号
QTimer *exitTimer=new QTimer();//定时500ms退出子线程保证事件循环关闭tcp和串口
exitTimer->singleShot(500,this,&SerialOCD::quit);
connect(exitTimer,&QTimer::timeout,exitTimer,&QTimer::deleteLater);
}
//子线程函数
void SerialOCD::run()
{
if(startSerial(serialName) && startServer(listenPort))//串口和tcp都连接成功才进入事件循环
exec();//进行事件循环
}
//启动tcp服务器返回是否成功
bool SerialOCD::startServer(int port)
{
server=new QTcpServer();//创建服务器对象
if(server->listen(QHostAddress::Any,port))
{
connect(server,&QTcpServer::newConnection,[=]{//连接槽函数,在有新连接时进入
socket=server->nextPendingConnection();//获取套接字
connect(socket,SIGNAL(readyRead()),this,SLOT(slotSocketReadyRead()),Qt::DirectConnection);
});
//退出时关闭服务器
connect(this,&SerialOCD::onStopConnect,server,&QTcpServer::close,Qt::QueuedConnection);
connect(this,&SerialOCD::onStopConnect,server,&QTcpServer::deleteLater,Qt::QueuedConnection);
}
else
{
emit onErrorOccur(QString("端口%1监听失败").arg(port));
server->deleteLater();
return false;
}
return true;
}
//停止tcp服务器
void SerialOCD::stopServer()
{
if(server)
{
if(server->isListening())
server->close();//关闭服务器
server->deleteLater();//销毁服务器对象
}
}
//tcp接收槽函数
void SerialOCD::slotSocketReadyRead()
{
//固定指令回复表,若匹配则直接回复
const QList<QList<QString>> fixReplyTable={
{"qSupported","PacketSize=30"},
{"H","OK"},
{"?","S05"},
{"qC","QC0"},
{"qAttached","1"},
{"g","00000000"},
{"p","00"},
{"qOffsets","TextSeg=0"},
{"qTStatus","T1"},
{"qSymbol","OK"},
{"m0,1","00"}
};
QString input=socket->readAll();//获取接收数据
QRegExp inputRx("\\$([^#]+)#");//正则表达式截取指令部分
inputRx.indexIn(input);
input=inputRx.cap(1);
if(input.isEmpty())//若截取失败则直接退出
return;
for(int i=0;i<fixReplyTable.length();i++)//依次在回复表中匹配
{
const QList<QString> &pair=fixReplyTable.at(i);
if(input.startsWith(pair.at(0)))//若匹配成功则直接返回表中字符串
{
sendToClient(pair.at(1));
return;
}
}
if(input.startsWith("m"))//读取内存指令
{
QRegExp rx("m([0-9a-f]+),([0-9a-f]+)");//正则截取地址和字节数量
rx.indexIn(input);
sendSerialReadMem(rx.cap(1).toUInt(NULL,16),rx.cap(2).toUInt(NULL,16));//发送串口指令
}
else if(input.startsWith("M"))//写入内存指令
{
QRegExp rx("M([0-9a-f]+),[0-9a-f]+:([0-9a-f]+)");//正则截取地址和要写入的内容
rx.indexIn(input);
QString rawData=rx.cap(2);
QByteArray data;//将截取到的内容转为qbytearray
for(int i=0;i<rawData.length()/2;i++)
data.append(rawData.mid(i*2,2).toUInt(NULL,16));
sendSerialWriteMem(rx.cap(1).toUInt(NULL,16),data);//发送串口指令
sendToClient("OK");//向gdb回复OK
}
else if(input.startsWith("qRcmd,7265736574"))
{
sendSerialReset();//发送串口指令
sendToClient("OK");//向gdb回复OK
}
else//其他未知指令,回复空字符串
{
sendToClient("");
}
}
//向tcp客户端gdb发送字符串
void SerialOCD::sendToClient(const QString &data)
{
QByteArray array=data.toLatin1();
int sum=0;//进行校验和计算
for(int i=0;i<array.length();i++)
sum+=array.at(i);
sum%=0x100;
QString output=QString("+$%1#%2").arg(data).arg(sum,2,16,QLatin1Char('0'));//拼接指令字符串
socket->write(output.toStdString().c_str(),output.length());//用当前socket发送
}
//获取当前串口列表
QStringList SerialOCD::getSerialList()
{
QStringList res;
foreach(const QSerialPortInfo &info,QSerialPortInfo::availablePorts()) //遍历并添加获取到的串口名称
res<<info.portName();
return res;
}
//启动串口连接,返回是否连接成功
bool SerialOCD::startSerial(const QString &name)
{
waitReadMemTimer=new QTimer();//创建读内存超时定时器
waitReadMemTimer->setSingleShot(true);//仅运行一次
waitReadMemTimer->setInterval(1000);//超时设定为1s
connect(waitReadMemTimer,&QTimer::timeout,[=]{
sendToClient("00000000");
emit onErrorOccur("内存数据读取超时");
});
port=new QSerialPort();//创建串口对象
port->setPortName(name);//设定端口号
if(port->open(QIODevice::ReadWrite))//打开串口
{
port->setBaudRate(QSerialPort::Baud115200,QSerialPort::AllDirections); //波特率115200
port->setDataBits(QSerialPort::Data8); //8数据位
port->setFlowControl(QSerialPort::NoFlowControl);
port->setParity(QSerialPort::NoParity); //无校验位
port->setStopBits(QSerialPort::OneStop); //1停止位
connect(port,SIGNAL(readyRead()),this,SLOT(slotSerialReadyRead()),Qt::DirectConnection);
//退出时断开串口
connect(this,&SerialOCD::onStopConnect,port,&QSerialPort::close,Qt::QueuedConnection);
connect(this,&SerialOCD::onStopConnect,port,&QSerialPort::deleteLater,Qt::QueuedConnection);
}
else
{
emit onErrorOccur("打开串口失败");
waitReadMemTimer->deleteLater();
port->deleteLater();
return false;
}
return true;
}
//断开串口
void SerialOCD::stopSerial()
{
waitReadMemTimer->deleteLater();
port->clear();//清空串口数据
port->close();//断开串口
port->deleteLater();//销毁串口对象
}
//串口接收槽函数
void SerialOCD::slotSerialReadyRead()
{
serialBuf.append(port->readAll());//读出数据追加到缓存区内
parseSerial();//解析缓存区数据
}
//解析串口缓存区数据
void SerialOCD::parseSerial()
{
if(serialBuf[0]==(char)DEBUG_FRAME_HEADER)//第一个字节是帧头,可以进入解析
{
if(serialBuf.size()>2 && serialBuf.size()>=serialBuf[1])//缓存区内数据长度足够
{
char cmd=serialBuf[2];//命令码
int frameLen=serialBuf[1];//帧长
if(cmd==SerialCMD_ReadMem)//返回的是下位机读取的内存数据
{
waitReadMemTimer->stop();
QString res="";
int dataLen=frameLen-3;
for(int i=0;i<dataLen;i++)//拼接结果字符串返回给gdb客户端
res+=QString("%1").arg((unsigned char)serialBuf[i+3],2,16,QLatin1Char('0'));
sendToClient(res);
}
serialBuf.remove(0,frameLen);//从缓存区中移除当前帧
if(serialBuf.size()>0)//若缓冲区仍有数据说明后面还有数据帧,递归解析
parseSerial();
}
}
else//帧错误或不完整
{
while(serialBuf[0]!=(char)DEBUG_FRAME_HEADER && serialBuf.size()>0)//去除错误数据
serialBuf.remove(0,1);
if(serialBuf.size()>0)//若缓冲区仍有数据说明后面还有数据帧,递归解析
parseSerial();
}
}
//发送串口指令,请求下位机发送指定地址处指定长度的内存数据
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;
arr[1]=8;
arr[2]=SerialCMD_ReadMem;
arr[3]=len;
for(int i=0;i<4;i++)//将地址拆分为四个字节
arr[i+4]=(addr>>(i*8))&0xFF;
port->write(arr);//串口发送
waitReadMemTimer->start();
}
}
//发送串口指令,向下位机指定地址处写入指定的内存数据
void SerialOCD::sendSerialWriteMem(int addr, const QByteArray &data)
{
if(port->isOpen())
{
QByteArray arr;
int frameLen=7+data.length();//计算总帧长
arr.resize(frameLen);
arr[0]=DEBUG_FRAME_HEADER;
arr[1]=frameLen;
arr[2]=SerialCMD_WriteMem;
for(int i=0;i<4;i++)//将地址分为四个字节
arr[i+3]=(addr>>(i*8))&0xFF;
for(int i=0;i<data.length();i++)//依次写入数据
arr[i+7]=data.at(i);
port->write(arr);
}
}
//发送串口复位指令
void SerialOCD::sendSerialReset()
{
if(port->isOpen())
{
QByteArray arr;
arr.resize(3);
arr[0]=DEBUG_FRAME_HEADER;
arr[1]=3;
arr[2]=SerialCMD_Reset;
port->write(arr);
}
}

58
serialocd.h Normal file
View File

@ -0,0 +1,58 @@
#ifndef SERIALOCD_H
#define SERIALOCD_H
#define DEBUG_FRAME_HEADER 0xDB
#include <QObject>
#include <qtcpserver.h>
#include <qtcpsocket.h>
#include <QRegExp>
#include <qserialport.h>
#include <qserialportinfo.h>
#include <qthread.h>
#include <QTime>
#include <qtimer.h>
class SerialOCD : public QThread
{
Q_OBJECT
public:
explicit SerialOCD(QObject *parent = nullptr);
QStringList getSerialList();
void startConnect(const QString &serial,int port);
void stopConnect();
signals:
void onErrorOccur(const QString &info);
void onStopConnect();
private slots:
void slotSerialReadyRead();
void slotSocketReadyRead();
private:
enum SerialCMD{
SerialCMD_ReadMem,
SerialCMD_WriteMem,
SerialCMD_Reset
};
QTcpServer *server;
QTcpSocket *socket;
QSerialPort *port;
QString serialName;
int listenPort;
QByteArray serialBuf;
QTimer *waitReadMemTimer;
void run();
bool startServer(int port);
void stopServer();
void sendToClient(const QString &data);
bool startSerial(const QString &name);
void stopSerial();
void parseSerial();
void sendSerialReadMem(int addr,int len);
void sendSerialWriteMem(int addr,const QByteArray &data);
void sendSerialReset();
};
#endif // SERIALOCD_H