gd32-probe 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gd32_probe-1.0.0/PKG-INFO +74 -0
- gd32_probe-1.0.0/README.md +50 -0
- gd32_probe-1.0.0/pyproject.toml +40 -0
- gd32_probe-1.0.0/setup.cfg +4 -0
- gd32_probe-1.0.0/src/gd32_probe/__init__.py +0 -0
- gd32_probe-1.0.0/src/gd32_probe/__main__.py +9 -0
- gd32_probe-1.0.0/src/gd32_probe/app.py +356 -0
- gd32_probe-1.0.0/src/gd32_probe/core/__init__.py +0 -0
- gd32_probe-1.0.0/src/gd32_probe/core/ai_iface.py +129 -0
- gd32_probe-1.0.0/src/gd32_probe/core/dap_client.py +346 -0
- gd32_probe-1.0.0/src/gd32_probe/core/elf_parser.py +50 -0
- gd32_probe-1.0.0/src/gd32_probe/core/flash.py +82 -0
- gd32_probe-1.0.0/src/gd32_probe/core/hardfault.py +97 -0
- gd32_probe-1.0.0/src/gd32_probe/core/periph_defs.py +180 -0
- gd32_probe-1.0.0/src/gd32_probe/core/rtt_buf.py +44 -0
- gd32_probe-1.0.0/src/gd32_probe/core/swo_manager.py +67 -0
- gd32_probe-1.0.0/src/gd32_probe/views/__init__.py +23 -0
- gd32_probe-1.0.0/src/gd32_probe/views/breakpoint.py +52 -0
- gd32_probe-1.0.0/src/gd32_probe/views/cpu.py +57 -0
- gd32_probe-1.0.0/src/gd32_probe/views/flash_view.py +45 -0
- gd32_probe-1.0.0/src/gd32_probe/views/memory.py +58 -0
- gd32_probe-1.0.0/src/gd32_probe/views/register.py +86 -0
- gd32_probe-1.0.0/src/gd32_probe/views/rtt_log.py +45 -0
- gd32_probe-1.0.0/src/gd32_probe/views/swo.py +27 -0
- gd32_probe-1.0.0/src/gd32_probe/views/watch.py +54 -0
- gd32_probe-1.0.0/src/gd32_probe.egg-info/PKG-INFO +74 -0
- gd32_probe-1.0.0/src/gd32_probe.egg-info/SOURCES.txt +29 -0
- gd32_probe-1.0.0/src/gd32_probe.egg-info/dependency_links.txt +1 -0
- gd32_probe-1.0.0/src/gd32_probe.egg-info/entry_points.txt +2 -0
- gd32_probe-1.0.0/src/gd32_probe.egg-info/requires.txt +3 -0
- gd32_probe-1.0.0/src/gd32_probe.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gd32-probe
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: GD32F303 专业调试终端 PRO — 人机+AI 共用,对标 Keil 仿真
|
|
5
|
+
Author-email: chenyuewei <228558959@qq.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: gd32,cortex-m,debugger,dap-link,embedded,tui
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Software Development :: Debuggers
|
|
18
|
+
Classifier: Topic :: Software Development :: Embedded Systems
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
Requires-Dist: textual>=0.40.0
|
|
22
|
+
Requires-Dist: pyocd>=0.36.0
|
|
23
|
+
Requires-Dist: pyelftools>=0.30
|
|
24
|
+
|
|
25
|
+
# gd32-probe — GD32F303 调试终端 PRO
|
|
26
|
+
|
|
27
|
+
人机 + AI 智能体共用调试控制台,对标 Keil 仿真功能。
|
|
28
|
+
|
|
29
|
+
## 功能
|
|
30
|
+
|
|
31
|
+
- 📋 **RTT 日志** — 高速双向日志,不占用串口
|
|
32
|
+
- 📊 **外设寄存器** — 7 类外设 + SCB,位域解码,变更高亮
|
|
33
|
+
- 💾 **内存浏览器** — Hex/ASCII 双栏,地址跳转
|
|
34
|
+
- 👁 **变量监视** — ELF 符号自动解析,实时刷新
|
|
35
|
+
- 🧠 **CPU 核心** — 暂停/运行/单步/复位,HardFault 栈解析
|
|
36
|
+
- ● **硬件断点/观察点** — 读写监控
|
|
37
|
+
- 📡 **SWO 高速日志** — 串行线输出
|
|
38
|
+
- 🔥 **烧录** — ELF/HEX/BIN 一键烧录
|
|
39
|
+
- 🤖 **AI 指令接口** — JSON 文件驱动,Agent 可自动调用
|
|
40
|
+
|
|
41
|
+
## 安装
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install gd32-probe
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## 启动
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
gd32-debug
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## 依赖
|
|
54
|
+
|
|
55
|
+
- Python >= 3.10
|
|
56
|
+
- DAPLink / CMSIS-DAP 调试器
|
|
57
|
+
- GD32F303 目标板
|
|
58
|
+
|
|
59
|
+
## 命令速查
|
|
60
|
+
|
|
61
|
+
| 命令 | 说明 |
|
|
62
|
+
|------|------|
|
|
63
|
+
| `reg can0` | CAN0 寄存器 + 位域 |
|
|
64
|
+
| `peek 0x20004000 16` | 读内存 |
|
|
65
|
+
| `var g_freq_hz` | 读 ELF 变量 |
|
|
66
|
+
| `bp add 0x0800A145` | 硬件断点 |
|
|
67
|
+
| `flash` | 烧录 ELF |
|
|
68
|
+
| `Ctrl+R` | 复位 MCU |
|
|
69
|
+
| `F5` | 运行 |
|
|
70
|
+
| `F10` | 单步 |
|
|
71
|
+
|
|
72
|
+
## License
|
|
73
|
+
|
|
74
|
+
MIT
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# gd32-probe — GD32F303 调试终端 PRO
|
|
2
|
+
|
|
3
|
+
人机 + AI 智能体共用调试控制台,对标 Keil 仿真功能。
|
|
4
|
+
|
|
5
|
+
## 功能
|
|
6
|
+
|
|
7
|
+
- 📋 **RTT 日志** — 高速双向日志,不占用串口
|
|
8
|
+
- 📊 **外设寄存器** — 7 类外设 + SCB,位域解码,变更高亮
|
|
9
|
+
- 💾 **内存浏览器** — Hex/ASCII 双栏,地址跳转
|
|
10
|
+
- 👁 **变量监视** — ELF 符号自动解析,实时刷新
|
|
11
|
+
- 🧠 **CPU 核心** — 暂停/运行/单步/复位,HardFault 栈解析
|
|
12
|
+
- ● **硬件断点/观察点** — 读写监控
|
|
13
|
+
- 📡 **SWO 高速日志** — 串行线输出
|
|
14
|
+
- 🔥 **烧录** — ELF/HEX/BIN 一键烧录
|
|
15
|
+
- 🤖 **AI 指令接口** — JSON 文件驱动,Agent 可自动调用
|
|
16
|
+
|
|
17
|
+
## 安装
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install gd32-probe
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 启动
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
gd32-debug
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## 依赖
|
|
30
|
+
|
|
31
|
+
- Python >= 3.10
|
|
32
|
+
- DAPLink / CMSIS-DAP 调试器
|
|
33
|
+
- GD32F303 目标板
|
|
34
|
+
|
|
35
|
+
## 命令速查
|
|
36
|
+
|
|
37
|
+
| 命令 | 说明 |
|
|
38
|
+
|------|------|
|
|
39
|
+
| `reg can0` | CAN0 寄存器 + 位域 |
|
|
40
|
+
| `peek 0x20004000 16` | 读内存 |
|
|
41
|
+
| `var g_freq_hz` | 读 ELF 变量 |
|
|
42
|
+
| `bp add 0x0800A145` | 硬件断点 |
|
|
43
|
+
| `flash` | 烧录 ELF |
|
|
44
|
+
| `Ctrl+R` | 复位 MCU |
|
|
45
|
+
| `F5` | 运行 |
|
|
46
|
+
| `F10` | 单步 |
|
|
47
|
+
|
|
48
|
+
## License
|
|
49
|
+
|
|
50
|
+
MIT
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=75", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "gd32-probe"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "GD32F303 专业调试终端 PRO — 人机+AI 共用,对标 Keil 仿真"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
authors = [{name = "chenyuewei", email = "228558959@qq.com"}]
|
|
12
|
+
keywords = ["gd32", "cortex-m", "debugger", "dap-link", "embedded", "tui"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 4 - Beta",
|
|
15
|
+
"Environment :: Console",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Operating System :: Microsoft :: Windows",
|
|
19
|
+
"Programming Language :: Python :: 3.10",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Programming Language :: Python :: 3.13",
|
|
23
|
+
"Topic :: Software Development :: Debuggers",
|
|
24
|
+
"Topic :: Software Development :: Embedded Systems",
|
|
25
|
+
]
|
|
26
|
+
requires-python = ">=3.10"
|
|
27
|
+
dependencies = [
|
|
28
|
+
"textual>=0.40.0",
|
|
29
|
+
"pyocd>=0.36.0",
|
|
30
|
+
"pyelftools>=0.30",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.scripts]
|
|
34
|
+
gd32-debug = "gd32_probe.app:main"
|
|
35
|
+
|
|
36
|
+
[tool.setuptools.package-dir]
|
|
37
|
+
"" = "src"
|
|
38
|
+
|
|
39
|
+
[tool.setuptools.packages.find]
|
|
40
|
+
where = ["src"]
|
|
File without changes
|
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
"""gd32_probe.app — GD32F303 调试终端 PRO 主应用"""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from textual import work, on
|
|
7
|
+
from textual.app import App, ComposeResult
|
|
8
|
+
from textual.containers import Horizontal, Vertical, Container
|
|
9
|
+
from textual.widgets import Header, Footer, Static, Input, Label, Button
|
|
10
|
+
from textual.reactive import reactive
|
|
11
|
+
from textual.binding import Binding
|
|
12
|
+
|
|
13
|
+
from .core import dap_client, elf_parser, rtt_buf, ai_iface, swo_manager
|
|
14
|
+
from .views.rtt_log import RTTLogTab
|
|
15
|
+
from .views.register import RegTab
|
|
16
|
+
from .views.memory import MemTab
|
|
17
|
+
from .views.watch import WatchTab
|
|
18
|
+
from .views.cpu import CpuTab
|
|
19
|
+
from .views.swo import SwoTab
|
|
20
|
+
from .views.flash_view import FlashTab
|
|
21
|
+
from .views.breakpoint import BpTab
|
|
22
|
+
from .views import classify
|
|
23
|
+
|
|
24
|
+
ELF_PATH = Path("F:/workspace/ultrasonic_psfb_re/build_gcc_app/ultrasonic_psfb_re.elf")
|
|
25
|
+
POLL_MS = 150
|
|
26
|
+
|
|
27
|
+
TABS = [
|
|
28
|
+
("📋 RTT", RTTLogTab),
|
|
29
|
+
("📊 寄存器", RegTab),
|
|
30
|
+
("💾 内存", MemTab),
|
|
31
|
+
("👁 监视", WatchTab),
|
|
32
|
+
("🧠 CPU", CpuTab),
|
|
33
|
+
("● 断点", BpTab),
|
|
34
|
+
("📡 SWO", SwoTab),
|
|
35
|
+
("🔥 烧录", FlashTab),
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
class StatusBar(Static):
|
|
39
|
+
status = reactive("● 断开")
|
|
40
|
+
def render(self):
|
|
41
|
+
c = "green" if "连接" in self.status else "red"
|
|
42
|
+
return f"[{c}]{self.status}[/]"
|
|
43
|
+
|
|
44
|
+
class GD32Debugger(App):
|
|
45
|
+
CSS = """
|
|
46
|
+
#top-bar { height: 1; padding: 0 1; background: $surface; }
|
|
47
|
+
#tab-bar { height: 1; background: $surface-darken-2; }
|
|
48
|
+
#tab-bar Button { min-width: 8; margin: 0; }
|
|
49
|
+
#tab-bar Button.active { background: $accent; color: $text; }
|
|
50
|
+
#content-area { height: 1fr; }
|
|
51
|
+
#rtt-log, #swo-log, #bp-view, #flash-log { height: 1fr; background: $surface-darken-1; }
|
|
52
|
+
#reg-tree { width: 30; height: 1fr; border: solid $secondary; }
|
|
53
|
+
#reg-detail { height: 1fr; border: solid $secondary; background: $surface-darken-1; }
|
|
54
|
+
#mem-view { height: 1fr; background: $surface-darken-1; }
|
|
55
|
+
#watch-view { height: 1fr; background: $surface-darken-1; }
|
|
56
|
+
#cpu-view { height: 1fr; background: $surface-darken-1; }
|
|
57
|
+
#mem-bar, #cpu-bar, #swo-bar, #flash-bar, #bp-bar, #rtt-ctrl { height: 3; }
|
|
58
|
+
#mem-addr, #bp-addr, #flash-path { width: 40; }
|
|
59
|
+
#cmd-panel { height: 3; border: solid $accent; background: $surface; }
|
|
60
|
+
#cmd-input { margin: 0 1; }
|
|
61
|
+
#cmd-label { margin: 0 1; color: $text-muted; }
|
|
62
|
+
#flash-progress { height: 1; color: $text-muted; }
|
|
63
|
+
StatusBar { width: 42; }
|
|
64
|
+
.hidden { display: none; }
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
BINDINGS = [
|
|
68
|
+
Binding("ctrl+c", "quit", "退出"),
|
|
69
|
+
Binding("ctrl+r", "reset_mcu", "复位MCU"),
|
|
70
|
+
Binding("f5", "resume_mcu", "运行"),
|
|
71
|
+
Binding("f10", "step_mcu", "单步"),
|
|
72
|
+
Binding("ctrl+1", "tab(0)", "RTT"),
|
|
73
|
+
Binding("ctrl+2", "tab(1)", "寄存器"),
|
|
74
|
+
Binding("ctrl+3", "tab(2)", "内存"),
|
|
75
|
+
Binding("ctrl+4", "tab(3)", "监视"),
|
|
76
|
+
Binding("ctrl+5", "tab(4)", "CPU"),
|
|
77
|
+
Binding("ctrl+6", "tab(5)", "断点"),
|
|
78
|
+
Binding("ctrl+7", "tab(6)", "SWO"),
|
|
79
|
+
Binding("ctrl+8", "tab(7)", "烧录"),
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
_tick = 0; _log_wr = 0; _log_rd = 0; _log_first = True; _log_fail = 0
|
|
83
|
+
_active_tab = 0
|
|
84
|
+
|
|
85
|
+
def compose(self) -> ComposeResult:
|
|
86
|
+
yield Header(show_clock=True)
|
|
87
|
+
yield Horizontal(
|
|
88
|
+
Static(f" 🖥 GD32F303 调试终端 PRO | {ELF_PATH.name if ELF_PATH.exists() else '无ELF'}"),
|
|
89
|
+
StatusBar(id="status"), id="top-bar")
|
|
90
|
+
|
|
91
|
+
with Horizontal(id="tab-bar"):
|
|
92
|
+
for label, _ in TABS:
|
|
93
|
+
yield Button(label, classes="active" if label == TABS[0][0] else "")
|
|
94
|
+
|
|
95
|
+
yield Container(id="content-area")
|
|
96
|
+
|
|
97
|
+
yield Vertical(
|
|
98
|
+
Label("> reg|peek|var|bp|wp|flash|hardfault|send|help Ctrl+R:复位 F5:运行 F10:单步", id="cmd-label"),
|
|
99
|
+
Input(id="cmd-input", placeholder="reg can0 / peek 0x20004000 / bp add 0x0800xxxx / flash ..."),
|
|
100
|
+
id="cmd-panel")
|
|
101
|
+
yield Footer()
|
|
102
|
+
|
|
103
|
+
def on_mount(self):
|
|
104
|
+
self.content = self.query_one("#content-area", Container)
|
|
105
|
+
self.inp = self.query_one("#cmd-input", Input)
|
|
106
|
+
self.st = self.query_one(StatusBar)
|
|
107
|
+
self.tab_btns = list(self.query("#tab-bar Button"))
|
|
108
|
+
|
|
109
|
+
# 创建所有标签页
|
|
110
|
+
self.tab_widgets = []
|
|
111
|
+
for i, (label, cls) in enumerate(TABS):
|
|
112
|
+
w = cls()
|
|
113
|
+
w.id = f"tab-{i}"
|
|
114
|
+
self.content.mount(w)
|
|
115
|
+
self.tab_widgets.append(w)
|
|
116
|
+
if i != 0:
|
|
117
|
+
w.add_class("hidden")
|
|
118
|
+
|
|
119
|
+
self.rtt_tab = self.tab_widgets[0]
|
|
120
|
+
self.poll_loop()
|
|
121
|
+
|
|
122
|
+
def switch_tab(self, idx: int):
|
|
123
|
+
if idx < 0 or idx >= len(self.tab_widgets):
|
|
124
|
+
return
|
|
125
|
+
for i, w in enumerate(self.tab_widgets):
|
|
126
|
+
if i == idx:
|
|
127
|
+
w.remove_class("hidden")
|
|
128
|
+
else:
|
|
129
|
+
w.add_class("hidden")
|
|
130
|
+
for i, btn in enumerate(self.tab_btns):
|
|
131
|
+
if i == idx:
|
|
132
|
+
btn.add_class("active")
|
|
133
|
+
else:
|
|
134
|
+
btn.remove_class("active")
|
|
135
|
+
self._active_tab = idx
|
|
136
|
+
|
|
137
|
+
def action_tab(self, idx_str: str):
|
|
138
|
+
self.switch_tab(int(idx_str))
|
|
139
|
+
|
|
140
|
+
@on(Button.Pressed, "#tab-bar Button")
|
|
141
|
+
def on_tab_button(self, event: Button.Pressed):
|
|
142
|
+
for i, btn in enumerate(self.tab_btns):
|
|
143
|
+
if btn is event.button:
|
|
144
|
+
self.switch_tab(i)
|
|
145
|
+
return
|
|
146
|
+
|
|
147
|
+
@on(Button.Pressed, "#rtt-reset")
|
|
148
|
+
def on_rtt_reset(self):
|
|
149
|
+
dap_client.reset("software")
|
|
150
|
+
self._log_first = True
|
|
151
|
+
self.rtt_tab.write_line("[yellow]🔄 MCU 已复位[/]")
|
|
152
|
+
|
|
153
|
+
@on(Button.Pressed, "#rtt-clear")
|
|
154
|
+
def on_rtt_clear(self):
|
|
155
|
+
self.rtt_tab.rlog.clear()
|
|
156
|
+
|
|
157
|
+
def on_input_submitted(self, event: Input.Submitted):
|
|
158
|
+
cmd = event.value.strip()
|
|
159
|
+
if not cmd:
|
|
160
|
+
return
|
|
161
|
+
self.inp.value = ""
|
|
162
|
+
parts = cmd.split(maxsplit=1)
|
|
163
|
+
op = parts[0].lower()
|
|
164
|
+
arg = parts[1] if len(parts) > 1 else ""
|
|
165
|
+
|
|
166
|
+
if op == "reg":
|
|
167
|
+
self._cmd_reg(arg)
|
|
168
|
+
elif op == "peek":
|
|
169
|
+
self._cmd_peek(arg)
|
|
170
|
+
elif op == "var":
|
|
171
|
+
self._cmd_var(arg)
|
|
172
|
+
elif op == "bp":
|
|
173
|
+
self._cmd_bp(arg)
|
|
174
|
+
elif op == "wp":
|
|
175
|
+
self._cmd_wp(arg)
|
|
176
|
+
elif op == "flash":
|
|
177
|
+
path = Path(arg) if arg else ELF_PATH
|
|
178
|
+
self._cmd_flash(path)
|
|
179
|
+
elif op == "hardfault":
|
|
180
|
+
from .core import hardfault
|
|
181
|
+
r = hardfault.analyze()
|
|
182
|
+
if r:
|
|
183
|
+
self.rtt_tab.write_line(hardfault.format_report(r))
|
|
184
|
+
else:
|
|
185
|
+
self.rtt_tab.write_line("[dim]无 HardFault[/]")
|
|
186
|
+
elif op == "send":
|
|
187
|
+
ok, err = rtt_buf.write_cmd(arg)
|
|
188
|
+
self.rtt_tab.write_line(f"[bold magenta]> {arg}[/]" if ok else f"[red]发送失败: {err}[/]")
|
|
189
|
+
elif op == "help":
|
|
190
|
+
self.rtt_tab.write_line(
|
|
191
|
+
"[bold]命令:[/]\n"
|
|
192
|
+
" reg <外设> | peek <addr> | var <name> | bp add/del <addr>\n"
|
|
193
|
+
" wp add/del <addr> | flash [path] | hardfault | send <text>\n"
|
|
194
|
+
" Ctrl+R:复位 F5:运行 F10:单步 Ctrl+1~8:切标签")
|
|
195
|
+
else:
|
|
196
|
+
ok, err = rtt_buf.write_cmd(cmd)
|
|
197
|
+
if ok:
|
|
198
|
+
self.rtt_tab.write_line(f"[bold magenta]> {cmd}[/]")
|
|
199
|
+
else:
|
|
200
|
+
self.rtt_tab.write_line(f"[red]发送失败: {err}[/]")
|
|
201
|
+
|
|
202
|
+
def _cmd_reg(self, arg):
|
|
203
|
+
from .core.periph_defs import PERIPHERALS
|
|
204
|
+
peri_name = arg.upper()
|
|
205
|
+
if peri_name == "SCB":
|
|
206
|
+
self.switch_tab(1)
|
|
207
|
+
self.tab_widgets[1].show_scb()
|
|
208
|
+
return
|
|
209
|
+
for p in PERIPHERALS:
|
|
210
|
+
if p.name == peri_name:
|
|
211
|
+
self.switch_tab(1)
|
|
212
|
+
self.tab_widgets[1].show_periph(p)
|
|
213
|
+
return
|
|
214
|
+
self.rtt_tab.write_line(f"[red]未知外设: {peri_name}[/]")
|
|
215
|
+
|
|
216
|
+
def _cmd_peek(self, arg):
|
|
217
|
+
try:
|
|
218
|
+
p2 = arg.split()
|
|
219
|
+
addr = int(p2[0], 16)
|
|
220
|
+
length = int(p2[1]) if len(p2) > 1 else 4
|
|
221
|
+
except (ValueError, IndexError):
|
|
222
|
+
self.rtt_tab.write_line("[red]用法: peek 0x40006400 [len][/]")
|
|
223
|
+
return
|
|
224
|
+
raw = dap_client.read_mem(addr, length)
|
|
225
|
+
if raw is None:
|
|
226
|
+
self.rtt_tab.write_line(f"[red]读取 0x{addr:08X} 失败[/]")
|
|
227
|
+
else:
|
|
228
|
+
self.rtt_tab.write_line(f"0x{addr:08X} ({length}B): {raw.hex(' ')}")
|
|
229
|
+
if length <= 4:
|
|
230
|
+
val = int.from_bytes(raw, 'little')
|
|
231
|
+
self.rtt_tab.write_line(f" = [bold]{val}[/] (0x{val:X})")
|
|
232
|
+
|
|
233
|
+
def _cmd_var(self, arg):
|
|
234
|
+
name = arg.strip()
|
|
235
|
+
sym = elf_parser.elf_symbols.get(name)
|
|
236
|
+
if not sym:
|
|
237
|
+
self.rtt_tab.write_line(f"[red]未找到: {name}[/]")
|
|
238
|
+
return
|
|
239
|
+
v = dap_client.read_u32(sym.addr)
|
|
240
|
+
self.rtt_tab.write_line(f"[cyan]{name}[/] @ 0x{sym.addr:08X} = 0x{v:08X} ({v})" if v is not None else "?")
|
|
241
|
+
|
|
242
|
+
def _cmd_bp(self, arg):
|
|
243
|
+
parts = arg.split()
|
|
244
|
+
if len(parts) < 2:
|
|
245
|
+
self.rtt_tab.write_line("[red]用法: bp add|del <addr>[/]")
|
|
246
|
+
return
|
|
247
|
+
action, addr_str = parts[0], parts[1]
|
|
248
|
+
try: addr = int(addr_str, 16)
|
|
249
|
+
except ValueError: self.rtt_tab.write_line("[red]地址格式错误[/]"); return
|
|
250
|
+
if action == "add": dap_client.add_breakpoint(addr)
|
|
251
|
+
elif action == "del": dap_client.remove_breakpoint(addr)
|
|
252
|
+
|
|
253
|
+
def _cmd_wp(self, arg):
|
|
254
|
+
parts = arg.split()
|
|
255
|
+
if len(parts) < 2:
|
|
256
|
+
self.rtt_tab.write_line("[red]用法: wp add|del <addr> [size] [r|w|rw][/]")
|
|
257
|
+
return
|
|
258
|
+
action, addr_str = parts[0], parts[1]
|
|
259
|
+
size = int(parts[2]) if len(parts) > 2 else 4
|
|
260
|
+
rw = parts[3] if len(parts) > 3 else "w"
|
|
261
|
+
try: addr = int(addr_str, 16)
|
|
262
|
+
except ValueError: self.rtt_tab.write_line("[red]地址错误[/]"); return
|
|
263
|
+
if action == "add": dap_client.add_watchpoint(addr, size, rw)
|
|
264
|
+
elif action == "del": dap_client.remove_watchpoint(addr)
|
|
265
|
+
|
|
266
|
+
def _cmd_flash(self, path: Path):
|
|
267
|
+
if not path.exists():
|
|
268
|
+
self.rtt_tab.write_line(f"[red]文件不存在: {path}[/]")
|
|
269
|
+
return
|
|
270
|
+
self.switch_tab(7)
|
|
271
|
+
ftab = self.tab_widgets[7]
|
|
272
|
+
ftab.fpath.value = str(path)
|
|
273
|
+
from .core import flash
|
|
274
|
+
def prog(pct, msg):
|
|
275
|
+
ftab.fprog.update(f"进度: {pct}% — {msg}")
|
|
276
|
+
ok, written = flash.program_file(path, progress_cb=prog)
|
|
277
|
+
if ok:
|
|
278
|
+
self.rtt_tab.write_line(f"[green]✓ 烧录完成 {written}B[/]")
|
|
279
|
+
ftab.fprog.update("进度: 完成 ✓")
|
|
280
|
+
dap_client.reset("software")
|
|
281
|
+
self._log_first = True
|
|
282
|
+
else:
|
|
283
|
+
self.rtt_tab.write_line("[red]✗ 烧录失败[/]")
|
|
284
|
+
|
|
285
|
+
@work(exclusive=True)
|
|
286
|
+
async def poll_loop(self):
|
|
287
|
+
while True:
|
|
288
|
+
self._tick += 1
|
|
289
|
+
|
|
290
|
+
wr, rd, data = rtt_buf.read_log()
|
|
291
|
+
if wr == 0 or not data:
|
|
292
|
+
self._log_fail += 1
|
|
293
|
+
if self._log_fail == 1:
|
|
294
|
+
self.st.status = "● 断开"
|
|
295
|
+
else:
|
|
296
|
+
if self._log_fail > 0:
|
|
297
|
+
dap_client.log("[green]✓ 已恢复连接[/]")
|
|
298
|
+
self._log_fail = 0
|
|
299
|
+
self.st.status = f"● 已连接 (wr={wr} rd={rd})"
|
|
300
|
+
|
|
301
|
+
if self._log_first:
|
|
302
|
+
self._log_first = False
|
|
303
|
+
self._log_rd = rd
|
|
304
|
+
|
|
305
|
+
if wr == self._log_rd:
|
|
306
|
+
new = b""
|
|
307
|
+
elif wr > self._log_rd:
|
|
308
|
+
new = data[self._log_rd:wr]
|
|
309
|
+
else:
|
|
310
|
+
new = data[self._log_rd:] + data[:wr]
|
|
311
|
+
|
|
312
|
+
if new:
|
|
313
|
+
text = bytes(new).decode("utf-8", errors="replace")
|
|
314
|
+
for line in text.split("\n"):
|
|
315
|
+
line = line.strip("\r")
|
|
316
|
+
if line:
|
|
317
|
+
self.rtt_tab.write_log(line)
|
|
318
|
+
if wr != self._log_rd:
|
|
319
|
+
rtt_buf.write_log_rd(wr)
|
|
320
|
+
self._log_rd = wr
|
|
321
|
+
|
|
322
|
+
if self._tick % 3 == 0 and self._log_fail == 0:
|
|
323
|
+
if self._active_tab == 3: # Watch
|
|
324
|
+
self.tab_widgets[3].refresh_data()
|
|
325
|
+
if self._active_tab == 4: # CPU
|
|
326
|
+
self.tab_widgets[4].refresh_data()
|
|
327
|
+
|
|
328
|
+
if self._tick % 3 == 0:
|
|
329
|
+
result = ai_iface.check()
|
|
330
|
+
if result:
|
|
331
|
+
dap_client.log(f"[dim]AI: {result.get('id','?')} ✓[/]")
|
|
332
|
+
|
|
333
|
+
await asyncio.sleep(POLL_MS / 1000.0)
|
|
334
|
+
|
|
335
|
+
def action_reset_mcu(self):
|
|
336
|
+
dap_client.reset("software")
|
|
337
|
+
self._log_first = True
|
|
338
|
+
|
|
339
|
+
def action_resume_mcu(self):
|
|
340
|
+
dap_client.resume()
|
|
341
|
+
|
|
342
|
+
def action_step_mcu(self):
|
|
343
|
+
dap_client.step()
|
|
344
|
+
if self._active_tab == 4:
|
|
345
|
+
self.tab_widgets[4].refresh_data()
|
|
346
|
+
|
|
347
|
+
def on_unmount(self):
|
|
348
|
+
swo_manager.stop()
|
|
349
|
+
dap_client.disconnect()
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def main():
|
|
353
|
+
GD32Debugger().run()
|
|
354
|
+
|
|
355
|
+
if __name__ == "__main__":
|
|
356
|
+
main()
|
|
File without changes
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""gd32_probe.core.ai_iface — AI 智能体指令接口"""
|
|
2
|
+
|
|
3
|
+
import json, os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Optional
|
|
6
|
+
from . import dap_client, periph_defs
|
|
7
|
+
|
|
8
|
+
CMD_FILE = Path("F:/workspace/_temp/ramlog_ai_cmd.json")
|
|
9
|
+
RES_FILE = Path("F:/workspace/_temp/ramlog_ai_res.json")
|
|
10
|
+
|
|
11
|
+
def process(cmd: dict) -> dict:
|
|
12
|
+
c = cmd.get("cmd", "")
|
|
13
|
+
rid = cmd.get("id", "?")
|
|
14
|
+
try:
|
|
15
|
+
if c == "reg":
|
|
16
|
+
peri_name = cmd.get("periph", "").upper()
|
|
17
|
+
peri = next((p for p in periph_defs.PERIPHERALS if p.name == peri_name), None)
|
|
18
|
+
if not peri:
|
|
19
|
+
return {"id": rid, "error": f"未知外设: {peri_name}",
|
|
20
|
+
"available": [p.name for p in periph_defs.PERIPHERALS]}
|
|
21
|
+
vals = {}
|
|
22
|
+
for r in peri.registers:
|
|
23
|
+
v = dap_client.read_u32(peri.base + r.offset)
|
|
24
|
+
if v is not None:
|
|
25
|
+
vals[r.name] = v
|
|
26
|
+
return {"id": rid, "cmd": "reg", "periph": peri_name, "regs": vals}
|
|
27
|
+
|
|
28
|
+
elif c == "peek":
|
|
29
|
+
addr = int(cmd["addr"], 16) if isinstance(cmd["addr"], str) else cmd["addr"]
|
|
30
|
+
length = cmd.get("len", 4)
|
|
31
|
+
raw = dap_client.read_mem(addr, length)
|
|
32
|
+
if raw is None:
|
|
33
|
+
return {"id": rid, "error": "读取失败"}
|
|
34
|
+
return {"id": rid, "addr": f"0x{addr:08X}", "hex": raw.hex(),
|
|
35
|
+
"value": f"0x{int.from_bytes(raw,'little'):0{length*2}X}" if length <= 4 else None}
|
|
36
|
+
|
|
37
|
+
elif c == "poke":
|
|
38
|
+
addr = int(cmd["addr"], 16) if isinstance(cmd["addr"], str) else cmd["addr"]
|
|
39
|
+
val = int(cmd["value"], 16) if isinstance(cmd["value"], str) else cmd["value"]
|
|
40
|
+
size = cmd.get("size", 4)
|
|
41
|
+
data = val.to_bytes(size, 'little')
|
|
42
|
+
ok = dap_client.write_mem(addr, data)
|
|
43
|
+
return {"id": rid, "ok": ok}
|
|
44
|
+
|
|
45
|
+
elif c == "halt":
|
|
46
|
+
dap_client.halt()
|
|
47
|
+
return {"id": rid, "halted": True}
|
|
48
|
+
|
|
49
|
+
elif c == "resume":
|
|
50
|
+
dap_client.resume()
|
|
51
|
+
return {"id": rid, "resumed": True}
|
|
52
|
+
|
|
53
|
+
elif c == "reset":
|
|
54
|
+
mode = cmd.get("mode", "hardware")
|
|
55
|
+
dap_client.reset(mode)
|
|
56
|
+
return {"id": rid, "reset": mode}
|
|
57
|
+
|
|
58
|
+
elif c == "bp":
|
|
59
|
+
sub = cmd.get("action", "add")
|
|
60
|
+
addr = int(cmd["addr"], 16) if isinstance(cmd["addr"], str) else cmd["addr"]
|
|
61
|
+
if sub == "add":
|
|
62
|
+
ok = dap_client.add_breakpoint(addr)
|
|
63
|
+
return {"id": rid, "bp_added": ok, "addr": f"0x{addr:08X}"}
|
|
64
|
+
elif sub == "remove":
|
|
65
|
+
ok = dap_client.remove_breakpoint(addr)
|
|
66
|
+
return {"id": rid, "bp_removed": ok}
|
|
67
|
+
|
|
68
|
+
elif c == "wp":
|
|
69
|
+
sub = cmd.get("action", "add")
|
|
70
|
+
addr = int(cmd["addr"], 16) if isinstance(cmd["addr"], str) else cmd["addr"]
|
|
71
|
+
size = cmd.get("size", 4)
|
|
72
|
+
rw = cmd.get("mode", "w")
|
|
73
|
+
if sub == "add":
|
|
74
|
+
ok = dap_client.add_watchpoint(addr, size, rw)
|
|
75
|
+
return {"id": rid, "wp_added": ok}
|
|
76
|
+
elif sub == "remove":
|
|
77
|
+
ok = dap_client.remove_watchpoint(addr)
|
|
78
|
+
return {"id": rid, "wp_removed": ok}
|
|
79
|
+
|
|
80
|
+
elif c == "flash":
|
|
81
|
+
path_str = cmd.get("file", "")
|
|
82
|
+
path = Path(path_str)
|
|
83
|
+
if not path.exists():
|
|
84
|
+
return {"id": rid, "error": f"文件不存在: {path_str}"}
|
|
85
|
+
from . import flash
|
|
86
|
+
ok, written = flash.program_file(path)
|
|
87
|
+
return {"id": rid, "flashed": ok, "bytes": written}
|
|
88
|
+
|
|
89
|
+
elif c == "hardfault":
|
|
90
|
+
from . import hardfault
|
|
91
|
+
r = hardfault.analyze()
|
|
92
|
+
return {"id": rid, "hardfault": r}
|
|
93
|
+
|
|
94
|
+
elif c == "var":
|
|
95
|
+
name = cmd.get("name", "")
|
|
96
|
+
from . import elf_parser
|
|
97
|
+
addr = elf_parser.get_var_addr(name)
|
|
98
|
+
if not addr:
|
|
99
|
+
return {"id": rid, "error": f"变量未找到: {name}"}
|
|
100
|
+
v = dap_client.read_u32(addr)
|
|
101
|
+
return {"id": rid, "var": name, "addr": f"0x{addr:08X}",
|
|
102
|
+
"value": f"0x{v:08X}" if v is not None else "?"}
|
|
103
|
+
|
|
104
|
+
elif c == "send":
|
|
105
|
+
text = cmd.get("text", "")
|
|
106
|
+
from . import rtt_buf
|
|
107
|
+
ok, err = rtt_buf.write_cmd(text)
|
|
108
|
+
return {"id": rid, "ok": ok, "error": err if not ok else None}
|
|
109
|
+
|
|
110
|
+
return {"id": rid, "error": f"未知指令: {c}",
|
|
111
|
+
"available": ["reg","peek","poke","halt","resume","reset",
|
|
112
|
+
"bp","wp","flash","hardfault","var","send"]}
|
|
113
|
+
except Exception as e:
|
|
114
|
+
return {"id": rid, "error": str(e)}
|
|
115
|
+
|
|
116
|
+
def check() -> Optional[dict]:
|
|
117
|
+
if not CMD_FILE.exists():
|
|
118
|
+
return None
|
|
119
|
+
try:
|
|
120
|
+
raw = CMD_FILE.read_text("utf-8").strip()
|
|
121
|
+
if not raw:
|
|
122
|
+
return None
|
|
123
|
+
obj = json.loads(raw)
|
|
124
|
+
CMD_FILE.unlink()
|
|
125
|
+
result = process(obj)
|
|
126
|
+
RES_FILE.write_text(json.dumps(result, ensure_ascii=False, indent=2), "utf-8")
|
|
127
|
+
return result
|
|
128
|
+
except Exception:
|
|
129
|
+
return None
|