diff --git a/LinkScope.pro b/LinkScope.pro index 1a358f1..16efb4f 100644 --- a/LinkScope.pro +++ b/LinkScope.pro @@ -1,5 +1,6 @@ QT += core gui QT += network +QT += serialport greaterThan(QT_MAJOR_VERSION, 4): QT += widgets @@ -10,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.0.2\\\" + APP_VERSION=\\\"1.1.0\\\" # You can also make your code fail to compile if it uses deprecated APIs. # In order to do so, uncomment the following line. @@ -25,7 +26,8 @@ SOURCES += \ listwindow.cpp \ main.cpp \ mainwindow.cpp \ - openocd.cpp + openocd.cpp \ + serialocd.cpp HEADERS += \ aboutwindow.h \ @@ -35,6 +37,7 @@ HEADERS += \ listwindow.h \ mainwindow.h \ openocd.h \ + serialocd.h \ vartype.h FORMS += \ diff --git a/LinkScope.pro.user b/LinkScope.pro.user index 42c9372..248518f 100644 --- a/LinkScope.pro.user +++ b/LinkScope.pro.user @@ -1,6 +1,6 @@ - + EnvironmentId diff --git a/README.md b/README.md index 5320b7c..26433fe 100644 --- a/README.md +++ b/README.md @@ -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) @@ -24,45 +30,55 @@ ## 使用方法 -1. 在下拉框中选择调试器和芯片类型,选择Axf文件路径,点击连接即可尝试连接芯片 +1. 若使用串口连接,需先将下位机程序移植到目标芯片中 [移植说明](lower/README.md) -2. 在表格最后一行变量名处填写变量名可以添加查看变量,选中变量名按Del键可以删除变量 +2. 点击设置符号文件,添加变量 -> 注:变量名不仅可以填入单个变量名,还可以填入合法的C语言表达式(GDB支持即可);复合类型不能修改和绘图,只能实时查看 + * 加载符号文件后即可在变量选择窗口添加需要查看的变量,可以直接添加到列表或添加到编辑框后进行手动编辑 -3. 在选择窗口中可以查看所选Axf文件包含的变量名,也可以一键添加到编辑框或变量列表中 + * 在主窗口表格最后一行变量名(编辑框)处手动填写也可以添加变量 + + * 在主窗口中选中变量名按Del键可删除变量 + + * 变量名不仅可以填入单个变量名,还可以填入合法的C语言表达式(GDB支持即可);复合类型不能修改和绘图,只能实时查看 + +3. 选择连接模式,连接芯片,连接后程序开始循环采样 + + * 调试器模式下,在下拉框中选择调试器和芯片类型,点击连接目标 + + * 串口模式下,点击刷新串口加载串口列表,选中所连接的串口,点击连接目标 4. 编辑`修改变量`列可以修改变量值,双击`图线颜色`列可以选择绘图颜色 -![基本操作](imgs/simp-oper.gif) - 5. 单击`变量名`列选中对应的变量,绘图窗口会加粗绘制波形,左下角会显示当前值和查看值(拖动鼠标进行查看) 6. 绘图界面说明可以在绘图窗口点击操作说明查看,滚轮配合`Ctrl`、`Shift`、`Alt`可以实现画面的缩放和移动 -![绘图操作](imgs/graph-oper.gif) +![操作演示](imgs/oper-sample.gif) --- -## 菜单项说明 - -* `显示绘图窗口`:手动关闭绘图窗口后可以通过这个菜单项重新打开绘图窗口并显示到前台 +## 主要菜单项说明 * `刷新连接配置`:连接配置文件位于`openocd/share/openocd/scripts`下的`target`和`interface`中,用户可按照OpenOCD语法编写配置脚本,放入对应目录下,然后点击该菜单项将配置文件加载到下拉选框中 -* `保存配置`:软件中所配置的调试器型号、芯片型号、Axf文件路径和各变量的配置都可以通过该菜单项保存到一个配置文件中 +* `保存配置`:软件中所配置的连接模式、调试器型号、芯片型号、符号文件路径和各变量的配置都可以通过该菜单项保存到一个配置文件中 * `导入配置`:将上述保存的配置文件重新载入软件中 * `导出数据`:将获取到的各变量采样数据导出到CSV表格文件 +* `显示绘图窗口`:手动关闭绘图窗口后可以通过这个菜单项重新打开并显示到前台 + +* `显示选择窗口`:手动关闭变量选择窗口后可以通过这个菜单项重新打开并显示到前台 + --- ## 使用注意事项 -* 若不指定Axf文件,无法使用变量名,只能通过绝对地址进行查看 +* 若不指定符号文件,无法使用变量名,只能通过绝对地址进行查看 -* 修改Axf路径后需要重新连接 +* 修改符号路径后需要重新连接 * 本程序不带下载功能,连接目标前请确认已为目标芯片下载过指定程序;若更换为不同类型的调试器,即使芯片程序没有变动,也应使用更换后的调试器再次下载程序 @@ -70,28 +86,38 @@ ## 已知问题及解决方法 -* 若程序发生错误闪退,可能在下次运行时无法成功连接目标,可以尝试手动查找`openocd.exe`进程并强制结束 +* 若程序在调试器连接模式下发生错误闪退,可能在下次运行时无法成功连接目标,可以尝试手动查找`openocd.exe`进程并强制结束 + +* 串口模式下从地址0读取单个字节结果恒为0,若要读取地址0可以使用大于1字节的类型 --- ## 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`目录复制到可执行文件同级目录下 diff --git a/aboutwindow.ui b/aboutwindow.ui index 6328433..32dcb37 100644 --- a/aboutwindow.ui +++ b/aboutwindow.ui @@ -91,7 +91,7 @@ - <html><head/><body><hr width="300"/><p align="center">版本号:V1.0.2</p><p align="center">更新时间:2022/1/29</p><hr width="300"/><p align="center">Developed by Skythinker</p></body></html> + <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> diff --git a/gdbprocess.cpp b/gdbprocess.cpp index 46aeb9b..3153081 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(); + process->waitForReadyRead(2); res+=process->readAllStandardOutput(); }while(!res.endsWith("(gdb) ")); return res; @@ -29,7 +29,7 @@ void GDBProcess::start() process->setProgram(QCoreApplication::applicationDirPath()+"/gdb/gdb.exe");//设置程序路径 process->setWorkingDirectory(QCoreApplication::applicationDirPath()+"/gdb");//设置工作路径 process->setNativeArguments("-q");//设置gdb在安静模式下打开 - process->start(); + process->start();//QProcess::Unbuffered|QProcess::ReadWrite); runCmd("set confirm off\r\n");//设置不要手动确认 runCmd("set print pretty on\r\n");//设置结构体规范打印 } @@ -130,7 +130,7 @@ bool GDBProcess::checkExpandableType(const QString &varFullName) return false; QString ptype=runCmd(QString("ptype %1\r\n").arg(varFullName));//再使用ptype指令判断是否是其他可展开类型 ptype.remove("type = "); - if(ptype.startsWith("struct")||ptype.startsWith("union")) + if(ptype.startsWith("struct")||ptype.startsWith("union")||ptype.startsWith("class")) return true; return false; } @@ -139,7 +139,7 @@ bool GDBProcess::checkExpandableType(const QString &varFullName) QStringList GDBProcess::getVarListFromRawOutput(const QString &rawVarList) { QStringList list; - QRegExp rootRx("\\s([^ (]*\\(\\*|)([^ )]+)(\\)\\(.*\\)|\\s:\\s\\d+|);");//正则匹配模板 + QRegExp rootRx("\\s([^ (]*\\(\\*|)([^ ):]+)(\\)\\(.*\\)|\\s:\\s\\d+|);");//正则匹配模板 rootRx.setMinimal(true);//非贪心匹配 int pos=0; while((pos=rootRx.indexIn(rawVarList,pos))!=-1) @@ -149,7 +149,8 @@ QStringList GDBProcess::getVarListFromRawOutput(const QString &rawVarList) name.remove('*'); if(name.contains('['))//手动剔除数组长度部分 name.remove(QRegExp("\\[\\d+\\]")); - list.append(name); + if(name!="const")//排除解析出来是保留字的情况 + list.append(name); pos+=rootRx.matchedLength(); } return list; diff --git a/gdbprocess.h b/gdbprocess.h index bf84547..c7f9beb 100644 --- a/gdbprocess.h +++ b/gdbprocess.h @@ -6,6 +6,8 @@ #include #include #include +#include +#include class GDBProcess : public QObject { diff --git a/helpwindow.ui b/helpwindow.ui index d29c91b..a9313e3 100644 --- a/helpwindow.ui +++ b/helpwindow.ui @@ -32,9 +32,9 @@ 0 - 0 + -735 523 - 906 + 1179 @@ -53,7 +53,7 @@ - <html><head/><body><p><span style=" font-weight:600;">LinkScope简介</span></p><p>本程序使用QT编写,基于OpenOCD和GDB,用于硬件设备的调试,可以实时查看并修改变量值,有波形绘制和数据导出功能 </p><p>程序支持OpenOCD支持的各种调试器及硬件芯片,如STLink、JLink、CMSIS-DAP等以及STM32全系列等</p><hr><p><span style=" font-weight:600;">使用方法</span></p><p>1.在下拉框中选择调试器和芯片类型,选择Axf文件路径,点击连接即可尝试连接芯片 </p><p>2.在表格最后一行变量名处填写变量名可以添加查看变量,选中变量名按Del键可以删除变量 </p><p>3.在选择窗口中可以查看所选Axf文件包含的变量名,可以一键添加到编辑框或变量列表</p><p>4.编辑【修改变量】列可以修改变量值,双击【图线颜色】列可以选择绘图颜色 </p><p>5.单击【变量名】列选中对应的变量,可以在绘图窗口查看历史数据,并会加粗绘制 </p><p>6.绘图界面说明请到绘图窗口点击操作说明 </p><p>7.点击菜单中的保存/导入配置可以将当前配置保存到INI文件或从文件中恢复配置,点击导出数据可以将获取到的采样数据导出到CSV表格文件</p><hr><p><span style=" font-weight:600;">注意事项 </span></p><p>1.修改Axf路径后需要重新连接 </p><p>2.在【变量名】列不仅能填写单个变量名,还可以填入任何合法的C语言表达式 </p><p>3.连接目标前请确认已使用该调试器为目标芯片下载过指定程序 </p><p>4.若程序闪退后发现下一次运行时无法连接目标,请尝试手动结束openocd.exe进程 </p><p>5.连接配置文件位于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>调试器连接模式理论上支持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> Qt::AutoText diff --git a/imgs/graph-oper.gif b/imgs/graph-oper.gif deleted file mode 100644 index 490c054..0000000 Binary files a/imgs/graph-oper.gif and /dev/null differ diff --git a/imgs/oper-sample.gif b/imgs/oper-sample.gif new file mode 100644 index 0000000..83e3b17 Binary files /dev/null and b/imgs/oper-sample.gif differ diff --git a/imgs/run-demo.png b/imgs/run-demo.png index 1a7fc49..0480dfb 100644 Binary files a/imgs/run-demo.png and b/imgs/run-demo.png differ diff --git a/imgs/simp-oper.gif b/imgs/simp-oper.gif deleted file mode 100644 index eac8df3..0000000 Binary files a/imgs/simp-oper.gif and /dev/null differ diff --git a/listwindow.cpp b/listwindow.cpp index 7ef8f3b..f2fad5a 100644 --- a/listwindow.cpp +++ b/listwindow.cpp @@ -47,6 +47,7 @@ void ListWindow::parseVarChildren(VarNode &node) if(node.parent==NULL)//传入的是根节点 { QString rawVarList=gdb->runCmd("info variables\r\n");//使用info variables指令列出axf中所有变量 + gdb->removeInnerSection(rawVarList,0);//移除内部直接嵌套的部分 QStringList varList=gdb->getVarListFromRawOutput(rawVarList);//解析出变量列表 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指令获取详细类型 detailRawVarType.remove("type = "); 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);//移除内部直接嵌套的部分 QStringList varList=gdb->getVarListFromRawOutput(detailRawVarType);//解析出变量列表 diff --git a/lower/README.md b/lower/README.md new file mode 100644 index 0000000..611a9f5 --- /dev/null +++ b/lower/README.md @@ -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); //进行解析 + } + //...其余代码不能有阻塞情况,需尽快执行完毕 +} + +``` diff --git a/lower/debug.c b/lower/debug.c new file mode 100644 index 0000000..8af96c3 --- /dev/null +++ b/lower/debug.c @@ -0,0 +1,144 @@ +#include + +#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)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;i0) //滹ݣеݹ + 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(); + } +} diff --git a/mainwindow.cpp b/mainwindow.cpp index 0da7d3e..ac0cccf 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -36,14 +36,14 @@ MainWindow::MainWindow(QWidget *parent) initTable(); openocd=new OpenOCD(); - connect(openocd,&OpenOCD::onErrorOccur,[=](const QString &info){//OpenOCD发生连接错误,断开连接并弹框 - setConnState(false); - QMessageBox::information(this,"连接错误",info); - }); + connect(openocd,&OpenOCD::onErrorOccur,this,&MainWindow::slotOnConnErrorOccur,Qt::QueuedConnection); + + serialocd=new SerialOCD(); + connect(serialocd,&SerialOCD::onErrorOccur,this,&MainWindow::slotOnConnErrorOccur,Qt::QueuedConnection); gdb=new GDBProcess();//创建并启动GDB gdb->setTempSymbolFileName("tmp");//设定临时符号文件名 - gdb->start(); + gdb->start();//启动gdb进程 loadConfFileList();//从openocd文件夹中读取配置文件列表 } @@ -54,7 +54,10 @@ MainWindow::~MainWindow() { gdb->disconnectFromRemote(); gdb->unloadSymbolFile(); - openocd->stop(); + if(ui->rb_openocd->isChecked()) + openocd->stop(); + else if(ui->rb_serialocd->isChecked()) + serialocd->stopConnect(); } gdb->stop();//结束gdb进程 delete ui; @@ -65,6 +68,7 @@ MainWindow::~MainWindow() delete watchTimer; delete tableTimer; delete graph; + delete listWindow; } //按键事件,监听DEL键按下,用于删除单个变量 @@ -85,6 +89,14 @@ void MainWindow::keyPressEvent(QKeyEvent *event) { varList.removeAt(index); redrawTable(); + + if(connected)//若正在连接状态则向gdb发送新的变量列表 + { + QStringList nameList; + for(int index=0;indexsetDisplayList(nameList); + } } } } @@ -195,6 +207,14 @@ void MainWindow::slotOnVarAdd2List(const QString &name) editItem->setText(name);//在编辑框中填入变量名,会直接添加到列表中 } +//发生连接错误时的槽函数 +void MainWindow::slotOnConnErrorOccur(const QString &info) +{ + if(connected) + setConnState(false); + QMessageBox::information(this,"连接错误",info); +} + //连接按钮点击,触发连接状态切换 void MainWindow::on_bt_conn_clicked() { @@ -216,9 +236,32 @@ void MainWindow::setConnState(bool connect) if(connect)//进行连接 { ui->bt_conn->setEnabled(false);//先禁用连接按钮,防止多次点击 - openocd->start(ui->cb_interface->currentText(),ui->cb_target->currentText(),3333);//运行openocd进程进行目标连接 - sleep(500);//等待500ms - if(openocd->isRunning())//若ocd成功启动 + + bool ocdStartSuccess=false; + + 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->connectToRemote("localhost:3333");//连接gdb到ocd @@ -236,6 +279,8 @@ void MainWindow::setConnState(bool connect) ui->bt_conn->setText("断开连接"); ui->bt_reset->setEnabled(true);//使能复位按钮 + ui->rb_openocd->setEnabled(false);//失能连接方式选择 + ui->rb_serialocd->setEnabled(false); connected=true;//更新连接标志 } @@ -247,9 +292,14 @@ void MainWindow::setConnState(bool connect) tableTimer->stop(); gdb->disconnectFromRemote();//断开gdb 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_reset->setEnabled(false);//禁用复位按钮 + ui->rb_openocd->setEnabled(true);//使能连接方式选择 + ui->rb_serialocd->setEnabled(true); connected=false;//更新连接标志 } } @@ -342,6 +392,8 @@ void MainWindow::saveToFile(const QString &filename) settings.setIniCodec("GBK"); 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("Target",ui->cb_target->currentText()); settings.setValue("AxfChosen",axfChosen); @@ -367,6 +419,8 @@ void MainWindow::loadFromFile(const QString &filename) settings.setIniCodec("GBK"); 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_target->setCurrentText(settings.value("Target").toString()); axfChosen=settings.value("AxfChosen",true).toBool(); @@ -653,3 +707,22 @@ void MainWindow::on_action_checkupdate_triggered() { 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()); +} diff --git a/mainwindow.h b/mainwindow.h index ad18e43..7c7f34d 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -26,6 +26,7 @@ #include #include #include +#include QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } @@ -42,12 +43,12 @@ public: void closeEvent(QCloseEvent *event); private slots: - //void slotOCDErrorReady(); void slotTableEdit(QModelIndex topleft, QModelIndex bottomright); void slotWatchTimerTrig(); void slotTableTimerTrig(); void slotOnVarAdd2Edit(const QString &name); void slotOnVarAdd2List(const QString &name); + void slotOnConnErrorOccur(const QString &info); void on_bt_conn_clicked(); void on_bt_set_axf_clicked(); void on_bt_reset_clicked(); @@ -64,12 +65,16 @@ private slots: 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(); private: Ui::MainWindow *ui; QProcess *ocdProcess; GDBProcess *gdb;//GDB进程控制 OpenOCD *openocd;//OpenOCD进程控制 + SerialOCD *serialocd;//串口ocd控制 bool connected=false;//标记当前是否已连接 QStandardItemModel *tableModel;//表格数据 QList varList;//变量列表 @@ -82,7 +87,6 @@ private: void checkUpdate(); void setStylesheet(); void setConnState(bool connect); - //void setOCDState(bool connect); void sleep(uint32_t ms); void loadConfFileList(); void initTable(); diff --git a/mainwindow.ui b/mainwindow.ui index 26a72b8..1f68f1a 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -14,69 +14,155 @@ MainWindow - + - - - 连接设置 - - - - - - 调试器 - - - - - - - - 0 - 0 - - - - - - - - 目标芯片 - - - - - - - - 0 - 0 - - - - - - - - 连接目标 - - - - - - - false - - - 复位并运行 - - - - - + + + + + + + + + + true + + + + + + + true + + + 调试器 + + + + + + 调试器类型 + + + + + + + + 0 + 0 + + + + + + + + 目标芯片 + + + + + + + true + + + + 0 + 0 + + + + + + + + + + + + + + + + + + + + + + false + + + + 0 + 0 + + + + 串口 + + + + + + 串口号 + + + + + + + + 0 + 0 + + + + + + + + 刷新串口 + + + + + + + + + + + + 操作 + + + + + + 连接目标 + + + + + + + false + + + 复位并运行 + + + + + + + diff --git a/serialocd.cpp b/serialocd.cpp new file mode 100644 index 0000000..dd00236 --- /dev/null +++ b/serialocd.cpp @@ -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> 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 &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;iwrite(output.toStdString().c_str(),output.length());//用当前socket发送 +} + +//获取当前串口列表 +QStringList SerialOCD::getSerialList() +{ + QStringList res; + foreach(const QSerialPortInfo &info,QSerialPortInfo::availablePorts()) //遍历并添加获取到的串口名称 + res<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;i0)//若缓冲区仍有数据说明后面还有数据帧,递归解析 + 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;iwrite(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); + } +} diff --git a/serialocd.h b/serialocd.h new file mode 100644 index 0000000..24d16e3 --- /dev/null +++ b/serialocd.h @@ -0,0 +1,58 @@ +#ifndef SERIALOCD_H +#define SERIALOCD_H + +#define DEBUG_FRAME_HEADER 0xDB + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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