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.
Files changed (31) hide show
  1. gd32_probe-1.0.0/PKG-INFO +74 -0
  2. gd32_probe-1.0.0/README.md +50 -0
  3. gd32_probe-1.0.0/pyproject.toml +40 -0
  4. gd32_probe-1.0.0/setup.cfg +4 -0
  5. gd32_probe-1.0.0/src/gd32_probe/__init__.py +0 -0
  6. gd32_probe-1.0.0/src/gd32_probe/__main__.py +9 -0
  7. gd32_probe-1.0.0/src/gd32_probe/app.py +356 -0
  8. gd32_probe-1.0.0/src/gd32_probe/core/__init__.py +0 -0
  9. gd32_probe-1.0.0/src/gd32_probe/core/ai_iface.py +129 -0
  10. gd32_probe-1.0.0/src/gd32_probe/core/dap_client.py +346 -0
  11. gd32_probe-1.0.0/src/gd32_probe/core/elf_parser.py +50 -0
  12. gd32_probe-1.0.0/src/gd32_probe/core/flash.py +82 -0
  13. gd32_probe-1.0.0/src/gd32_probe/core/hardfault.py +97 -0
  14. gd32_probe-1.0.0/src/gd32_probe/core/periph_defs.py +180 -0
  15. gd32_probe-1.0.0/src/gd32_probe/core/rtt_buf.py +44 -0
  16. gd32_probe-1.0.0/src/gd32_probe/core/swo_manager.py +67 -0
  17. gd32_probe-1.0.0/src/gd32_probe/views/__init__.py +23 -0
  18. gd32_probe-1.0.0/src/gd32_probe/views/breakpoint.py +52 -0
  19. gd32_probe-1.0.0/src/gd32_probe/views/cpu.py +57 -0
  20. gd32_probe-1.0.0/src/gd32_probe/views/flash_view.py +45 -0
  21. gd32_probe-1.0.0/src/gd32_probe/views/memory.py +58 -0
  22. gd32_probe-1.0.0/src/gd32_probe/views/register.py +86 -0
  23. gd32_probe-1.0.0/src/gd32_probe/views/rtt_log.py +45 -0
  24. gd32_probe-1.0.0/src/gd32_probe/views/swo.py +27 -0
  25. gd32_probe-1.0.0/src/gd32_probe/views/watch.py +54 -0
  26. gd32_probe-1.0.0/src/gd32_probe.egg-info/PKG-INFO +74 -0
  27. gd32_probe-1.0.0/src/gd32_probe.egg-info/SOURCES.txt +29 -0
  28. gd32_probe-1.0.0/src/gd32_probe.egg-info/dependency_links.txt +1 -0
  29. gd32_probe-1.0.0/src/gd32_probe.egg-info/entry_points.txt +2 -0
  30. gd32_probe-1.0.0/src/gd32_probe.egg-info/requires.txt +3 -0
  31. 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"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
File without changes
@@ -0,0 +1,9 @@
1
+ """gd32_probe 入口 — 可直接 python -m gd32_probe"""
2
+
3
+ from .app import GD32Debugger
4
+
5
+ def main():
6
+ GD32Debugger().run()
7
+
8
+ if __name__ == "__main__":
9
+ main()
@@ -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