Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d776979a5a |
+5
-2
@@ -69,12 +69,15 @@ DAPLinkFlash/
|
|||||||
"自动定位"可重新探测;也可手动浏览指定 `openocd.exe` 与 scripts 目录;
|
"自动定位"可重新探测;也可手动浏览指定 `openocd.exe` 与 scripts 目录;
|
||||||
2. **选芯片与烧录器**:芯片型号下拉自动带出 Flash 大小和 target;烧录器下拉
|
2. **选芯片与烧录器**:芯片型号下拉自动带出 Flash 大小和 target;烧录器下拉
|
||||||
自动带出 interface cfg;多探针时填序列号;
|
自动带出 interface cfg;多探针时填序列号;
|
||||||
|
**自定义 cfg**:把自己的 OpenOCD cfg 文件(如工程定制的 target 配置)放进
|
||||||
|
程序旁的 `cfgs/` 目录,"Target 配置"/"Interface 配置"下拉框即列出
|
||||||
|
`cfgs/<文件名>` 供选用;`cfgs/` 会随发布包分发;
|
||||||
3. **选固件**:点"浏览"选 BIN/HEX/ELF;BIN 确认基地址(完整镜像 `0x08000000`,
|
3. **选固件**:点"浏览"选 BIN/HEX/ELF;BIN 确认基地址(完整镜像 `0x08000000`,
|
||||||
单独 APP `0x08002000`);
|
单独 APP `0x08002000`);
|
||||||
4. **点"检测设备"**确认链路通 → **点"烧录固件"**;输出实时显示在右下日志区;
|
4. **点"检测设备"**确认链路通 → **点"烧录固件"**;输出实时显示在右下日志区;
|
||||||
5. **固件工具**(顶栏按钮弹出的独立窗口,非模态):
|
5. **固件工具**(顶栏按钮弹出的独立窗口,非模态):
|
||||||
- 补齐:选输入 BIN 后输出名自动带出(`*_pad.bin`),对齐默认 1K,点"执行补齐";
|
- 补齐:选输入 BIN 后输出名自动带出(`*_update.bin`,Y-Modem 升级包),对齐默认 1K,点"执行补齐";
|
||||||
- 拼接:选 Bootloader 与 APP 后输出名自动带出(`*_BL.bin`),地址按典型布局
|
- 拼接:选 Bootloader 与 APP 后输出名自动带出(`*_BL.bin`,整片镜像),地址按典型布局
|
||||||
预填,点"执行拼接"。
|
预填,点"执行拼接"。
|
||||||
|
|
||||||
典型布局:`0x08000000` Bootloader 8KB | `0x08002000` APP | `0x0800FFFC`
|
典型布局:`0x08000000` Bootloader 8KB | `0x08002000` APP | `0x0800FFFC`
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
#
|
||||||
|
# GigaDevice GD32E23x target
|
||||||
|
#
|
||||||
|
|
||||||
|
adapter driver cmsis-dap
|
||||||
|
|
||||||
|
# adapter driver jlink
|
||||||
|
|
||||||
|
source [find target/swj-dp.tcl]
|
||||||
|
source [find mem_helper.tcl]
|
||||||
|
|
||||||
|
if { [info exists CHIPNAME] } {
|
||||||
|
set _CHIPNAME $CHIPNAME
|
||||||
|
} else {
|
||||||
|
set _CHIPNAME gd32e23x
|
||||||
|
}
|
||||||
|
|
||||||
|
# Work-area is a space in RAM used for flash programming
|
||||||
|
# By default use 4kB (as found on some GD32E230s)
|
||||||
|
if { [info exists WORKAREASIZE] } {
|
||||||
|
set _WORKAREASIZE $WORKAREASIZE
|
||||||
|
} else {
|
||||||
|
set _WORKAREASIZE 0x1000
|
||||||
|
}
|
||||||
|
|
||||||
|
# Allow overriding the Flash bank size
|
||||||
|
if { [info exists FLASH_SIZE] } {
|
||||||
|
set _FLASH_SIZE $FLASH_SIZE
|
||||||
|
} else {
|
||||||
|
# autodetect size
|
||||||
|
set _FLASH_SIZE 0
|
||||||
|
}
|
||||||
|
|
||||||
|
#jtag scan chain
|
||||||
|
if { [info exists CPUTAPID] } {
|
||||||
|
set _CPUTAPID $CPUTAPID
|
||||||
|
} else {
|
||||||
|
# this is the SW-DP tap id not the jtag tap id
|
||||||
|
set _CPUTAPID 0x0BF11477
|
||||||
|
}
|
||||||
|
|
||||||
|
swj_newdap $_CHIPNAME cpu -irlen 4 -ircapture 0x1 -irmask 0xf -expected-id $_CPUTAPID
|
||||||
|
dap create $_CHIPNAME.dap -chain-position $_CHIPNAME.cpu
|
||||||
|
|
||||||
|
set _TARGETNAME $_CHIPNAME.cpu
|
||||||
|
target create $_TARGETNAME cortex_m -dap $_CHIPNAME.dap
|
||||||
|
|
||||||
|
$_TARGETNAME configure -work-area-phys 0x20000000 -work-area-size $_WORKAREASIZE -work-area-backup 0
|
||||||
|
|
||||||
|
# flash size will be probed
|
||||||
|
set _FLASHNAME $_CHIPNAME.flash
|
||||||
|
flash bank $_FLASHNAME gd32e23x 0x08000000 $_FLASH_SIZE 0 0 $_TARGETNAME
|
||||||
|
|
||||||
|
# SWD speed (may be updated to higher value in board config file)
|
||||||
|
adapter speed 1000
|
||||||
|
|
||||||
|
# use the hardware reset line (DAPLink RST pin -> GD32 NRST);
|
||||||
|
# connect_assert_srst: hold the chip in reset while connecting, so SWD pins
|
||||||
|
# (shared with LED in firmware) revert to SWD function before any access
|
||||||
|
adapter srst delay 100
|
||||||
|
reset_config srst_only srst_nogate connect_assert_srst
|
||||||
|
|
||||||
|
if {![using_hla]} {
|
||||||
|
# if srst is not fitted use SYSRESETREQ to
|
||||||
|
# perform a soft reset
|
||||||
|
cortex_m reset_config sysresetreq
|
||||||
|
}
|
||||||
|
|
||||||
|
$_TARGETNAME configure -event examine-end {
|
||||||
|
mmw 0x40021018 0x00400000 0
|
||||||
|
mmw 0x40015804 0x00000307 0
|
||||||
|
}
|
||||||
+78
-16
@@ -33,7 +33,7 @@ BOLD = ('Microsoft YaHei UI', 13, 'bold')
|
|||||||
TITLE_FONT = ('Microsoft YaHei UI', 18, 'bold')
|
TITLE_FONT = ('Microsoft YaHei UI', 18, 'bold')
|
||||||
|
|
||||||
APP_NAME = 'DAPLink Flash'
|
APP_NAME = 'DAPLink Flash'
|
||||||
APP_VERSION = '2.2.0'
|
APP_VERSION = '2.5.2'
|
||||||
REPO_URL = 'https://gitea.hulk.wang/hulk/gd32e230f8_firmware_merge_tool'
|
REPO_URL = 'https://gitea.hulk.wang/hulk/gd32e230f8_firmware_merge_tool'
|
||||||
|
|
||||||
OK_BLUE = '#3B8ED0'
|
OK_BLUE = '#3B8ED0'
|
||||||
@@ -200,18 +200,31 @@ class App(ctk.CTk):
|
|||||||
card.grid(row=0, column=0, sticky='ew')
|
card.grid(row=0, column=0, sticky='ew')
|
||||||
self._field(c, 'OpenOCD 路径', self.v['openocd'], self._browse_openocd)
|
self._field(c, 'OpenOCD 路径', self.v['openocd'], self._browse_openocd)
|
||||||
self._field(c, '脚本目录', self.v['scripts'], self._browse_scripts)
|
self._field(c, '脚本目录', self.v['scripts'], self._browse_scripts)
|
||||||
probe, _w = self._field_pair(
|
custom = ['cfgs/' + n for n in openocd_runner.list_custom_cfgs()]
|
||||||
|
probe, iface_cbo = self._field_pair(
|
||||||
c, '烧录器', self.v['probe'], 'Interface', self.v['interface'],
|
c, '烧录器', self.v['probe'], 'Interface', self.v['interface'],
|
||||||
cls1=(ctk.CTkComboBox, {'state': 'readonly',
|
cls1=(ctk.CTkComboBox, {'state': 'readonly',
|
||||||
'values': list(openocd_runner.PROBE_PROFILES)}),
|
'values': list(openocd_runner.PROBE_PROFILES)}),
|
||||||
cls2=(ctk.CTkComboBox,
|
cls2=(ctk.CTkComboBox, {'values': custom}))
|
||||||
{'values': sorted(set(openocd_runner.PROBE_PROFILES.values()) - {''})}))
|
|
||||||
probe.configure(command=lambda _=None: self._on_probe_change())
|
probe.configure(command=lambda _=None: self._on_probe_change())
|
||||||
|
self.probe_combo = probe
|
||||||
|
self.cbo_interface = iface_cbo
|
||||||
self._field_pair(c, '速度 (kHz)', self.v['speed'], '探针序列号',
|
self._field_pair(c, '速度 (kHz)', self.v['speed'], '探针序列号',
|
||||||
self.v['serial'], width2=150)
|
self.v['serial'], width2=150)
|
||||||
self._field_pair(
|
target_cbo, _extra = self._field_pair(
|
||||||
c, 'Target', self.v['target'], '额外命令', self.v['extra'],
|
c, 'Target', self.v['target'], '额外命令', self.v['extra'],
|
||||||
cls1=(ctk.CTkComboBox, {'values': ['target/gd32e23x.cfg']}))
|
cls1=(ctk.CTkComboBox, {'values': custom}))
|
||||||
|
self.cbo_target = target_cbo
|
||||||
|
row = ctk.CTkFrame(c, fg_color='transparent')
|
||||||
|
row.pack(fill='x', pady=(4, 0))
|
||||||
|
ctk.CTkButton(row, text='打开 cfgs 目录', width=110, height=24,
|
||||||
|
fg_color='transparent', border_width=1,
|
||||||
|
border_color=('gray55', 'gray35'),
|
||||||
|
text_color='gray60', hover_color=('gray82', 'gray22'),
|
||||||
|
command=self._open_cfgs_dir).pack(side='left')
|
||||||
|
ctk.CTkLabel(row, text='自定义 cfg 放 cfgs/(下拉自动列出); 换工具链后 Target/Interface 列表自动跟随其 scripts 目录',
|
||||||
|
text_color='gray50').pack(side='left', padx=(8, 0))
|
||||||
|
self._refresh_cfg_options()
|
||||||
|
|
||||||
card, c = self._card(col, '芯片与固件')
|
card, c = self._card(col, '芯片与固件')
|
||||||
card.grid(row=1, column=0, sticky='ew', pady=(10, 0))
|
card.grid(row=1, column=0, sticky='ew', pady=(10, 0))
|
||||||
@@ -228,13 +241,27 @@ class App(ctk.CTk):
|
|||||||
'单独烧 APP 0x08002000',
|
'单独烧 APP 0x08002000',
|
||||||
text_color='gray50', justify='left', anchor='w',
|
text_color='gray50', justify='left', anchor='w',
|
||||||
).pack(fill='x', pady=(2, 0))
|
).pack(fill='x', pady=(2, 0))
|
||||||
opts = ctk.CTkFrame(c, fg_color='transparent')
|
opts1 = ctk.CTkFrame(c, fg_color='transparent')
|
||||||
opts.pack(fill='x', pady=(6, 0))
|
opts1.pack(fill='x', pady=(6, 0))
|
||||||
for key, name in (('erase', '烧录前擦除'), ('verify', '烧录后校验'),
|
ctk.CTkCheckBox(opts1, text='烧录前硬件复位', variable=self.v['pre_reset'],
|
||||||
('reset', '烧录后复位运行')):
|
checkbox_width=18, checkbox_height=18).pack(
|
||||||
ctk.CTkCheckBox(opts, text=name, variable=self.v[key],
|
side='left', padx=(0, 14))
|
||||||
checkbox_width=18, checkbox_height=18).pack(
|
ctk.CTkCheckBox(opts1, text='烧录前擦除', variable=self.v['erase'],
|
||||||
side='left', padx=(0, 14))
|
checkbox_width=18, checkbox_height=18).pack(
|
||||||
|
side='left', padx=(0, 14))
|
||||||
|
ctk.CTkCheckBox(opts1, text='烧录后校验', variable=self.v['verify'],
|
||||||
|
checkbox_width=18, checkbox_height=18).pack(
|
||||||
|
side='left', padx=(0, 14))
|
||||||
|
opts2 = ctk.CTkFrame(c, fg_color='transparent')
|
||||||
|
opts2.pack(fill='x', pady=(4, 0))
|
||||||
|
ctk.CTkCheckBox(opts2, text='烧录后硬件复位', variable=self.v['post_reset'],
|
||||||
|
checkbox_width=18, checkbox_height=18).pack(
|
||||||
|
side='left', padx=(0, 14))
|
||||||
|
ctk.CTkCheckBox(opts2, text='烧录后复位运行', variable=self.v['reset'],
|
||||||
|
checkbox_width=18, checkbox_height=18).pack(
|
||||||
|
side='left', padx=(0, 14))
|
||||||
|
ctk.CTkLabel(opts2, text='硬件复位 = 复位前先释放 SRST 并等待 200ms',
|
||||||
|
text_color='gray50').pack(side='left')
|
||||||
|
|
||||||
card, c = self._card(col, '运行日志 — OpenOCD 输出与固件工具结果',
|
card, c = self._card(col, '运行日志 — OpenOCD 输出与固件工具结果',
|
||||||
button='清空', command=self._clear_log)
|
button='清空', command=self._clear_log)
|
||||||
@@ -376,11 +403,38 @@ class App(ctk.CTk):
|
|||||||
title='选择 OpenOCD', filetypes=[('OpenOCD', 'openocd.exe'), ('所有文件', '*.*')])
|
title='选择 OpenOCD', filetypes=[('OpenOCD', 'openocd.exe'), ('所有文件', '*.*')])
|
||||||
if path:
|
if path:
|
||||||
self.v['openocd'].set(path)
|
self.v['openocd'].set(path)
|
||||||
|
# 换工具链: scripts 目录与 cfg 下拉跟随当前版本
|
||||||
|
root = os.path.dirname(os.path.dirname(path))
|
||||||
|
scripts = openocd_runner._find_scripts(root)
|
||||||
|
if scripts:
|
||||||
|
self.v['scripts'].set(scripts)
|
||||||
|
self._refresh_cfg_options()
|
||||||
|
|
||||||
def _browse_scripts(self):
|
def _browse_scripts(self):
|
||||||
path = filedialog.askdirectory(title='选择 OpenOCD scripts 目录')
|
path = filedialog.askdirectory(title='选择 OpenOCD scripts 目录')
|
||||||
if path:
|
if path:
|
||||||
self.v['scripts'].set(path)
|
self.v['scripts'].set(path)
|
||||||
|
self._refresh_cfg_options()
|
||||||
|
|
||||||
|
def _refresh_cfg_options(self):
|
||||||
|
"""按当前 scripts 目录重建 Target/Interface 下拉列表(自定义 cfgs/ 优先在前)"""
|
||||||
|
custom = ['cfgs/' + n for n in openocd_runner.list_custom_cfgs()]
|
||||||
|
scripts = self.v['scripts'].get()
|
||||||
|
targets = openocd_runner.scan_cfgs(scripts, 'target')
|
||||||
|
interfaces = openocd_runner.scan_cfgs(scripts, 'interface')
|
||||||
|
cur_t, cur_i = self.v['target'].get(), self.v['interface'].get()
|
||||||
|
self.cbo_target.configure(values=custom + targets)
|
||||||
|
self.cbo_interface.configure(values=custom + interfaces)
|
||||||
|
self.v['target'].set(cur_t)
|
||||||
|
self.v['interface'].set(cur_i)
|
||||||
|
|
||||||
|
def _open_cfgs_dir(self):
|
||||||
|
"""创建并打开自定义 cfg 目录, 方便直接丢 cfg 文件进来"""
|
||||||
|
import subprocess
|
||||||
|
d = openocd_runner.custom_cfg_dir()
|
||||||
|
os.makedirs(d, exist_ok=True)
|
||||||
|
subprocess.Popen(['explorer', os.path.normpath(d)])
|
||||||
|
self._refresh_cfg_options()
|
||||||
|
|
||||||
def _relocate_toolchain(self):
|
def _relocate_toolchain(self):
|
||||||
exe, scripts, source = openocd_runner.locate_toolchain()
|
exe, scripts, source = openocd_runner.locate_toolchain()
|
||||||
@@ -390,6 +444,7 @@ class App(ctk.CTk):
|
|||||||
self.v_toolchain.set('工具链: ' + source)
|
self.v_toolchain.set('工具链: ' + source)
|
||||||
ver = openocd_runner.version(exe)
|
ver = openocd_runner.version(exe)
|
||||||
self._log('工具链定位: %s (%s)%s' % (exe, source, ' ' + ver if ver else ''))
|
self._log('工具链定位: %s (%s)%s' % (exe, source, ' ' + ver if ver else ''))
|
||||||
|
self._refresh_cfg_options()
|
||||||
|
|
||||||
def _mark_custom_toolchain(self, *_a):
|
def _mark_custom_toolchain(self, *_a):
|
||||||
if self._toolchain_source != '自定义':
|
if self._toolchain_source != '自定义':
|
||||||
@@ -409,7 +464,7 @@ class App(ctk.CTk):
|
|||||||
if path:
|
if path:
|
||||||
self.v['pad_in'].set(path)
|
self.v['pad_in'].set(path)
|
||||||
name, ext = os.path.splitext(path)
|
name, ext = os.path.splitext(path)
|
||||||
self.v['pad_out'].set(name + '_pad' + ext)
|
self.v['pad_out'].set(name + '_update' + ext)
|
||||||
|
|
||||||
def _browse_pad_out(self):
|
def _browse_pad_out(self):
|
||||||
path = filedialog.asksaveasfilename(
|
path = filedialog.asksaveasfilename(
|
||||||
@@ -436,7 +491,8 @@ class App(ctk.CTk):
|
|||||||
if self.v['app'].get():
|
if self.v['app'].get():
|
||||||
name, ext = os.path.splitext(os.path.basename(self.v['app'].get()))
|
name, ext = os.path.splitext(os.path.basename(self.v['app'].get()))
|
||||||
self.v['merge_out'].set(
|
self.v['merge_out'].set(
|
||||||
os.path.join(os.path.dirname(self.v['app'].get()), name + '_BL' + ext))
|
os.path.join(os.path.dirname(self.v['app'].get()),
|
||||||
|
name + '_BL' + ext))
|
||||||
|
|
||||||
def _browse_merge_out(self):
|
def _browse_merge_out(self):
|
||||||
path = filedialog.asksaveasfilename(
|
path = filedialog.asksaveasfilename(
|
||||||
@@ -450,6 +506,10 @@ class App(ctk.CTk):
|
|||||||
self.v['target'].set(openocd_runner.chip_target(self.v['chip'].get()))
|
self.v['target'].set(openocd_runner.chip_target(self.v['chip'].get()))
|
||||||
|
|
||||||
def _on_probe_change(self, *_a):
|
def _on_probe_change(self, *_a):
|
||||||
|
if self.v['probe'].get() == 'Custom':
|
||||||
|
# Custom: interface 留空(配合 cfgs/ 里自带 adapter driver 的 cfg)
|
||||||
|
self.v['interface'].set('')
|
||||||
|
return
|
||||||
cfg = openocd_runner.PROBE_PROFILES.get(self.v['probe'].get(), '')
|
cfg = openocd_runner.PROBE_PROFILES.get(self.v['probe'].get(), '')
|
||||||
if cfg:
|
if cfg:
|
||||||
self.v['interface'].set(cfg)
|
self.v['interface'].set(cfg)
|
||||||
@@ -511,7 +571,9 @@ class App(ctk.CTk):
|
|||||||
script = openocd_runner.script_flash(
|
script = openocd_runner.script_flash(
|
||||||
filepath, self.v['base'].get(),
|
filepath, self.v['base'].get(),
|
||||||
erase=self.v['erase'].get(), verify=self.v['verify'].get(),
|
erase=self.v['erase'].get(), verify=self.v['verify'].get(),
|
||||||
reset=self.v['reset'].get())
|
reset=self.v['reset'].get(),
|
||||||
|
pre_reset=self.v['pre_reset'].get(),
|
||||||
|
post_reset=self.v['post_reset'].get())
|
||||||
except ValueError as exc:
|
except ValueError as exc:
|
||||||
messagebox.showerror('参数错误', str(exc))
|
messagebox.showerror('参数错误', str(exc))
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -30,19 +30,21 @@ SETTINGS_PATH = os.path.join(BASE_DIR, SETTINGS_NAME)
|
|||||||
DEFAULTS = {
|
DEFAULTS = {
|
||||||
"openocd": "", # 空 = 启动时按 locate_toolchain() 自动定位
|
"openocd": "", # 空 = 启动时按 locate_toolchain() 自动定位
|
||||||
"scripts": "",
|
"scripts": "",
|
||||||
"speed": "4000",
|
"speed": "2000", # kHz; 空则用 cfg 内的值
|
||||||
"serial": "",
|
"serial": "",
|
||||||
"extra": "",
|
"extra": "",
|
||||||
"chip": "GD32E230F8",
|
"chip": "GD32E230F8",
|
||||||
"probe": "DAPLink / CMSIS-DAP",
|
"probe": "DAPLink / CMSIS-DAP",
|
||||||
"interface": "interface/cmsis-dap.cfg",
|
"interface": "interface/cmsis-dap.cfg", # 与 cfgs/ 定制 cfg 内重复声明 driver 无害
|
||||||
"target": "target/gd32e23x.cfg",
|
"target": "cfgs/openocd_gdlink_gd32e23x.cfg", # 随包分发的工程定制 cfg
|
||||||
"flash_size": "0x10000",
|
"flash_size": "0x10000",
|
||||||
"firmware": "",
|
"firmware": "",
|
||||||
"base": "0x08000000",
|
"base": "0x08000000",
|
||||||
"erase": True,
|
"erase": True,
|
||||||
"verify": True,
|
"verify": True,
|
||||||
"reset": True,
|
"reset": True,
|
||||||
|
"pre_reset": True,
|
||||||
|
"post_reset": True,
|
||||||
"pad_in": "",
|
"pad_in": "",
|
||||||
"pad_out": "",
|
"pad_out": "",
|
||||||
"pad_align": "1024",
|
"pad_align": "1024",
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ def run_pad(args):
|
|||||||
if pad_byte is None:
|
if pad_byte is None:
|
||||||
pad_byte = 0xFF
|
pad_byte = 0xFF
|
||||||
stem, ext = os.path.splitext(args.pad)
|
stem, ext = os.path.splitext(args.pad)
|
||||||
out = args.output or stem + '_pad' + ext
|
out = args.output or stem + '_update' + ext
|
||||||
_ensure_parent(out)
|
_ensure_parent(out)
|
||||||
original = os.path.getsize(args.pad)
|
original = os.path.getsize(args.pad)
|
||||||
padded = pad_bin(args.pad, out, align, pad_byte)
|
padded = pad_bin(args.pad, out, align, pad_byte)
|
||||||
@@ -162,6 +162,10 @@ def main():
|
|||||||
ap.add_argument('--no-erase', action='store_true', help='烧录前不擦除')
|
ap.add_argument('--no-erase', action='store_true', help='烧录前不擦除')
|
||||||
ap.add_argument('--no-verify', action='store_true', help='烧录后不校验')
|
ap.add_argument('--no-verify', action='store_true', help='烧录后不校验')
|
||||||
ap.add_argument('--no-reset', action='store_true', help='烧录后不复位运行')
|
ap.add_argument('--no-reset', action='store_true', help='烧录后不复位运行')
|
||||||
|
ap.add_argument('--no-pre-reset', action='store_true',
|
||||||
|
help='烧录前不做硬件复位(SRST 释放 + halt)')
|
||||||
|
ap.add_argument('--no-post-reset', action='store_true',
|
||||||
|
help='烧录后复位前不释放 SRST(仅软件复位)')
|
||||||
ap.add_argument('--speed', type=int, help='适配器速度 kHz(默认取配置 4000)')
|
ap.add_argument('--speed', type=int, help='适配器速度 kHz(默认取配置 4000)')
|
||||||
ap.add_argument('--serial', help='探针序列号(多探针时用)')
|
ap.add_argument('--serial', help='探针序列号(多探针时用)')
|
||||||
ap.add_argument('--interface', help='interface cfg(默认 interface/cmsis-dap.cfg)')
|
ap.add_argument('--interface', help='interface cfg(默认 interface/cmsis-dap.cfg)')
|
||||||
@@ -187,7 +191,9 @@ def main():
|
|||||||
sys.exit(run_openocd(args, '烧录固件', openocd_runner.script_flash(
|
sys.exit(run_openocd(args, '烧录固件', openocd_runner.script_flash(
|
||||||
args.flash, args.base,
|
args.flash, args.base,
|
||||||
erase=not args.no_erase, verify=not args.no_verify,
|
erase=not args.no_erase, verify=not args.no_verify,
|
||||||
reset=not args.no_reset)))
|
reset=not args.no_reset,
|
||||||
|
pre_reset=not args.no_pre_reset,
|
||||||
|
post_reset=not args.no_post_reset)))
|
||||||
if args.read:
|
if args.read:
|
||||||
sys.exit(run_openocd(args, '读取 Flash',
|
sys.exit(run_openocd(args, '读取 Flash',
|
||||||
openocd_runner.script_read(args.read, args.flash_size)))
|
openocd_runner.script_read(args.read, args.flash_size)))
|
||||||
|
|||||||
@@ -43,15 +43,54 @@ def _app_base():
|
|||||||
return os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
return os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
|
||||||
|
def _find_scripts(root):
|
||||||
|
"""在工具链根目录下找 scripts 目录, 兼容两种布局:
|
||||||
|
xpack 0.11(脚本在根: scripts/) 与 0.12(脚本在 openocd/scripts/)"""
|
||||||
|
for sub in ("openocd/scripts", "scripts"):
|
||||||
|
p = os.path.join(root, sub)
|
||||||
|
if os.path.isdir(p):
|
||||||
|
return p
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def bundled_toolchain():
|
def bundled_toolchain():
|
||||||
"""内置工具链路径, 不存在返回 None"""
|
"""内置工具链路径, 不存在返回 None"""
|
||||||
exe = os.path.join(_app_base(), "toolchain", "openocd", "bin", "openocd.exe")
|
root = os.path.join(_app_base(), "toolchain", "openocd")
|
||||||
|
exe = os.path.join(root, "bin", "openocd.exe")
|
||||||
if os.path.isfile(exe):
|
if os.path.isfile(exe):
|
||||||
scripts = os.path.join(_app_base(), "toolchain", "openocd", "openocd", "scripts")
|
scripts = _find_scripts(root)
|
||||||
return exe, scripts if os.path.isdir(scripts) else DEFAULT_SCRIPTS
|
return exe, (scripts or DEFAULT_SCRIPTS)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def custom_cfg_dir():
|
||||||
|
"""用户自定义 cfg 目录: 程序目录旁 cfgs/(exe 模式在 exe 旁, 源码模式在 FlashTool/)"""
|
||||||
|
return os.path.join(_app_base(), "cfgs")
|
||||||
|
|
||||||
|
|
||||||
|
def list_custom_cfgs():
|
||||||
|
"""cfgs/ 下的 *.cfg 文件名列表(供界面下拉选择), 目录不存在返回空表"""
|
||||||
|
d = custom_cfg_dir()
|
||||||
|
if not os.path.isdir(d):
|
||||||
|
return []
|
||||||
|
return sorted(f for f in os.listdir(d)
|
||||||
|
if f.lower().endswith(".cfg") and os.path.isfile(os.path.join(d, f)))
|
||||||
|
|
||||||
|
|
||||||
|
def scan_cfgs(scripts_dir, kind):
|
||||||
|
"""扫描 scripts 目录下 kind(target/interface) 子目录里的 cfg 文件名。
|
||||||
|
|
||||||
|
返回 ['target/gd32e23x.cfg', ...]; scripts 无效/子目录不存在返回空表。
|
||||||
|
"""
|
||||||
|
if not scripts_dir or not os.path.isdir(scripts_dir):
|
||||||
|
return []
|
||||||
|
d = os.path.join(scripts_dir, kind)
|
||||||
|
if not os.path.isdir(d):
|
||||||
|
return []
|
||||||
|
return sorted(kind + "/" + f for f in os.listdir(d)
|
||||||
|
if f.lower().endswith(".cfg") and os.path.isfile(os.path.join(d, f)))
|
||||||
|
|
||||||
|
|
||||||
def locate_toolchain():
|
def locate_toolchain():
|
||||||
"""返回 (openocd_exe, scripts_dir, source),source ∈ 内置/本机/PATH"""
|
"""返回 (openocd_exe, scripts_dir, source),source ∈ 内置/本机/PATH"""
|
||||||
bundled = bundled_toolchain()
|
bundled = bundled_toolchain()
|
||||||
@@ -83,6 +122,20 @@ def ensure_cfg_path(value, folder):
|
|||||||
return folder + "/" + value
|
return folder + "/" + value
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_cfg(value, folder):
|
||||||
|
"""界面里的 cfg 引用 → OpenOCD -f 参数。
|
||||||
|
|
||||||
|
cfgs/<name>.cfg 是自定义 cfg, 转成绝对路径(不依赖 cwd);
|
||||||
|
其余(裸名/带路径/绝对路径)按原逻辑处理。
|
||||||
|
"""
|
||||||
|
value = value.strip().replace("\\", "/")
|
||||||
|
if value.startswith("cfgs/"):
|
||||||
|
p = os.path.join(custom_cfg_dir(), os.path.basename(value))
|
||||||
|
if os.path.isfile(p):
|
||||||
|
return p
|
||||||
|
return ensure_cfg_path(value, folder)
|
||||||
|
|
||||||
|
|
||||||
def version(exe):
|
def version(exe):
|
||||||
"""openocd --version 首行(如 Open On-Chip Debugger 0.12.0), 失败返回 None"""
|
"""openocd --version 首行(如 Open On-Chip Debugger 0.12.0), 失败返回 None"""
|
||||||
try:
|
try:
|
||||||
@@ -99,12 +152,14 @@ def version(exe):
|
|||||||
# 烧录脚本构建(GUI 与 CLI 共用)
|
# 烧录脚本构建(GUI 与 CLI 共用)
|
||||||
# ============================================================
|
# ============================================================
|
||||||
def build_cmd(openocd, scripts, interface, target, speed="", serial="", extra="", script=""):
|
def build_cmd(openocd, scripts, interface, target, speed="", serial="", extra="", script=""):
|
||||||
"""拼 OpenOCD 命令行: -s 搜索目录 + interface/target cfg + -c 选项"""
|
"""拼 OpenOCD 命令行: -s 搜索目录 + interface/target cfg + -c 选项。
|
||||||
|
interface 留空时跳过(配合自带 adapter driver 的自定义 target cfg 使用)"""
|
||||||
cmd = [openocd.strip()]
|
cmd = [openocd.strip()]
|
||||||
if scripts and scripts.strip():
|
if scripts and scripts.strip():
|
||||||
cmd += ["-s", scripts.strip()]
|
cmd += ["-s", scripts.strip()]
|
||||||
cmd += ["-f", ensure_cfg_path(interface, "interface"),
|
if interface and interface.strip():
|
||||||
"-f", ensure_cfg_path(target, "target")]
|
cmd += ["-f", resolve_cfg(interface, "interface")]
|
||||||
|
cmd += ["-f", resolve_cfg(target, "target")]
|
||||||
if speed and speed.strip():
|
if speed and speed.strip():
|
||||||
cmd += ["-c", "adapter speed " + speed.strip()]
|
cmd += ["-c", "adapter speed " + speed.strip()]
|
||||||
if serial and serial.strip() and "cmsis-dap" in interface.lower():
|
if serial and serial.strip() and "cmsis-dap" in interface.lower():
|
||||||
@@ -117,21 +172,38 @@ def build_cmd(openocd, scripts, interface, target, speed="", serial="", extra=""
|
|||||||
|
|
||||||
|
|
||||||
def script_detect():
|
def script_detect():
|
||||||
return "init; reset halt; flash list; exit"
|
return "init; %s; flash list; exit" % CONNECT_SEQ
|
||||||
|
|
||||||
|
|
||||||
def script_erase(flash_size_text):
|
def script_erase(flash_size_text):
|
||||||
size = parse_int(flash_size_text)
|
size = parse_int(flash_size_text)
|
||||||
if size is None or size <= 0:
|
if size is None or size <= 0:
|
||||||
raise ValueError("Flash 大小无效: %s" % flash_size_text)
|
raise ValueError("Flash 大小无效: %s" % flash_size_text)
|
||||||
return ("init; reset halt; flash erase_address 0x%08X 0x%X; reset halt; exit"
|
return ("init; %s; flash erase_address 0x%08X 0x%X; reset halt; exit"
|
||||||
% (FLASH_BASE_DEFAULT, size))
|
% (CONNECT_SEQ, FLASH_BASE_DEFAULT, size))
|
||||||
|
|
||||||
|
|
||||||
def script_flash(firmware, base_text="", erase=True, verify=True, reset=True):
|
# 复刻 VSCode tasks.json 的复位序列(0.11 语法, 与内置 xpack 0.11.0-3 匹配):
|
||||||
"""HEX/ELF 用文件内地址(base_text 留空), BIN 必须给基地址"""
|
# init 连接(connect_assert_srst 由 cfg 声明)后显式释放 SRST 并等复位完成再 halt。
|
||||||
|
# 注意: 0.11 只认 "adapter deassert srst"; 0.12 的 "adapter srst deassert"
|
||||||
|
# 在 0.11 上是无效命令, 包 catch 会静默吞掉 → SRST 一直按住 → cannot read IDR。
|
||||||
|
# 全部操作(检测/烧录/擦除/读取/复位)都必须带本序列: 本目标必须按住复位连接,
|
||||||
|
# 否则 exam 失败(PARTNO 0x0), 后续任何访问都会 "Target not examined" 失败。
|
||||||
|
CONNECT_SEQ = "adapter deassert srst; sleep 200; catch {reset halt}; reset halt"
|
||||||
|
POST_RESET_PREFIX = "adapter deassert srst; sleep 200; "
|
||||||
|
|
||||||
|
|
||||||
|
def script_flash(firmware, base_text="", erase=True, verify=True, reset=True,
|
||||||
|
pre_reset=True, post_reset=True):
|
||||||
|
"""HEX/ELF 用文件内地址(base_text 留空), BIN 必须给基地址。
|
||||||
|
|
||||||
|
pre_reset - 烧录前硬件复位序列(SRST 释放 + halt), 复刻 VSCode 任务链
|
||||||
|
post_reset - 烧录后复位前先释放 SRST(硬件复位), 否则仅软件 reset
|
||||||
|
reset - 复位后运行(reset run); 不勾则复位后保持暂停(reset halt)
|
||||||
|
"""
|
||||||
ext = os.path.splitext(firmware)[1].lower()
|
ext = os.path.splitext(firmware)[1].lower()
|
||||||
script = "init; reset halt; "
|
script = "init; "
|
||||||
|
script += (CONNECT_SEQ if pre_reset else "reset halt") + "; "
|
||||||
addr = ""
|
addr = ""
|
||||||
if ext == ".bin":
|
if ext == ".bin":
|
||||||
base = parse_int(base_text)
|
base = parse_int(base_text)
|
||||||
@@ -143,7 +215,8 @@ def script_flash(firmware, base_text="", erase=True, verify=True, reset=True):
|
|||||||
else "flash write_image %s%s; ") % (quoted, addr)
|
else "flash write_image %s%s; ") % (quoted, addr)
|
||||||
if verify:
|
if verify:
|
||||||
script += "verify_image %s%s; " % (quoted, addr)
|
script += "verify_image %s%s; " % (quoted, addr)
|
||||||
script += ("reset run" if reset else "reset halt") + "; exit"
|
tail = "reset run" if reset else "reset halt"
|
||||||
|
script += (POST_RESET_PREFIX + tail if post_reset else tail) + "; exit"
|
||||||
return script
|
return script
|
||||||
|
|
||||||
|
|
||||||
@@ -151,12 +224,13 @@ def script_read(out_path, flash_size_text):
|
|||||||
size = parse_int(flash_size_text)
|
size = parse_int(flash_size_text)
|
||||||
if size is None or size <= 0:
|
if size is None or size <= 0:
|
||||||
raise ValueError("Flash 大小无效: %s" % flash_size_text)
|
raise ValueError("Flash 大小无效: %s" % flash_size_text)
|
||||||
return "init; reset halt; dump_image %s 0x%08X 0x%X; exit" % (
|
return "init; %s; dump_image %s 0x%08X 0x%X; exit" % (
|
||||||
tcl_quote(out_path), FLASH_BASE_DEFAULT, size)
|
CONNECT_SEQ, tcl_quote(out_path), FLASH_BASE_DEFAULT, size)
|
||||||
|
|
||||||
|
|
||||||
def script_reset():
|
def script_reset():
|
||||||
return "init; reset run; exit"
|
"""对齐 VSCode Reset 任务: 释放 SRST 后复位运行"""
|
||||||
|
return "init; adapter deassert srst; sleep 200; reset; exit"
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|||||||
@@ -15,8 +15,11 @@ zip 解压后根目录为 DAPLinkFlash/, 与 exe 运行目录结构一致:
|
|||||||
|
|
||||||
OpenOCD 来源(--openocd-src > 环境变量 OPENOCD_SRC > 自动探测):
|
OpenOCD 来源(--openocd-src > 环境变量 OPENOCD_SRC > 自动探测):
|
||||||
1. FlashTool/toolchain/openocd (随仓库自带, 若已 vendor)
|
1. FlashTool/toolchain/openocd (随仓库自带, 若已 vendor)
|
||||||
2. D:/toolchain/openocd (开发机本机安装)
|
2. D:/toolchain/xpack-openocd-0.11.0-3
|
||||||
目录布局: <src>/bin/openocd.exe + <src>/openocd/scripts/
|
3. D:/toolchain/openocd
|
||||||
|
整个工具链目录原样打包(不挑文件); scripts 布局自动兼容
|
||||||
|
xpack 0.11(scripts/) 与 0.12(openocd/scripts/)两种。
|
||||||
|
程序目录还会带一个空的 cfgs/ 目录(放自定义 OpenOCD cfg, 界面下拉可选)。
|
||||||
|
|
||||||
用法:
|
用法:
|
||||||
python package_release.py 版本号自动取最近 git tag(DAPLinkFlash-v*)
|
python package_release.py 版本号自动取最近 git tag(DAPLinkFlash-v*)
|
||||||
@@ -52,37 +55,52 @@ def detect_version():
|
|||||||
|
|
||||||
|
|
||||||
def resolve_openocd_src(explicit):
|
def resolve_openocd_src(explicit):
|
||||||
"""定位 OpenOCD 工具链源目录, 校验布局并返回路径"""
|
"""定位 OpenOCD 工具链源目录(bin/openocd.exe 必在, scripts 布局兼容两种)"""
|
||||||
candidates = [explicit, os.environ.get('OPENOCD_SRC'),
|
candidates = [explicit, os.environ.get('OPENOCD_SRC'),
|
||||||
os.path.join(BASE, 'toolchain', 'openocd'),
|
os.path.join(BASE, 'toolchain', 'openocd'),
|
||||||
|
r'D:/toolchain/xpack-openocd-0.11.0-3',
|
||||||
r'D:/toolchain/openocd']
|
r'D:/toolchain/openocd']
|
||||||
for cand in candidates:
|
for cand in candidates:
|
||||||
if not cand:
|
if not cand:
|
||||||
continue
|
continue
|
||||||
exe = os.path.join(cand, 'bin', 'openocd.exe')
|
if not os.path.isfile(os.path.join(cand, 'bin', 'openocd.exe')):
|
||||||
|
continue
|
||||||
scripts = os.path.join(cand, 'openocd', 'scripts')
|
scripts = os.path.join(cand, 'openocd', 'scripts')
|
||||||
if os.path.isfile(exe) and os.path.isdir(scripts):
|
if not os.path.isdir(scripts):
|
||||||
|
scripts = os.path.join(cand, 'scripts')
|
||||||
|
if os.path.isdir(scripts):
|
||||||
print(f'OpenOCD 工具链来源: {os.path.abspath(cand)}')
|
print(f'OpenOCD 工具链来源: {os.path.abspath(cand)}')
|
||||||
return os.path.abspath(cand)
|
return os.path.abspath(cand)
|
||||||
sys.exit('未找到 OpenOCD 工具链(需 bin/openocd.exe + openocd/scripts/):\n'
|
sys.exit('未找到 OpenOCD 工具链(需 bin/openocd.exe + scripts/):\n'
|
||||||
' python package_release.py <版本> --openocd-src <toolchain/openocd 目录>\n'
|
' python package_release.py <版本> --openocd-src <openocd 根目录>\n'
|
||||||
' 或设置环境变量 OPENOCD_SRC')
|
' 或设置环境变量 OPENOCD_SRC')
|
||||||
|
|
||||||
|
|
||||||
def copy_toolchain(openocd_src):
|
def copy_toolchain(openocd_src):
|
||||||
"""复制 bin(openocd.exe + 运行 DLL)与 openocd/scripts 到程序目录 toolchain/"""
|
"""整个工具链目录原样复制到程序目录 toolchain/openocd(不挑文件)"""
|
||||||
dst_root = os.path.join(APP_DIR, 'toolchain', 'openocd')
|
dst_root = os.path.join(APP_DIR, 'toolchain', 'openocd')
|
||||||
if os.path.isdir(dst_root):
|
if os.path.isdir(dst_root):
|
||||||
shutil.rmtree(dst_root)
|
shutil.rmtree(dst_root)
|
||||||
count = 0
|
shutil.copytree(openocd_src, dst_root)
|
||||||
for sub in ('bin', os.path.join('openocd', 'scripts')):
|
return sum(len(fs) for _r, _d, fs in os.walk(dst_root))
|
||||||
src = os.path.join(openocd_src, sub)
|
|
||||||
dst = os.path.join(dst_root, sub)
|
|
||||||
os.makedirs(os.path.dirname(dst), exist_ok=True)
|
CFGS_README = (
|
||||||
shutil.copytree(src, dst)
|
"把自定义 OpenOCD cfg 文件放到本目录(任意文件名.cfg)。\n"
|
||||||
for _root, _dirs, files in os.walk(dst):
|
"DAPLink Flash 的 \"Target 配置\"/\"Interface 配置\" 下拉框会自动列出\n"
|
||||||
count += len(files)
|
"cfgs/<文件名>, 选中即用; 选择会被记住。\n"
|
||||||
return count
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_cfgs_dir():
|
||||||
|
"""程序目录建 cfgs/ + 说明文件"""
|
||||||
|
d = os.path.join(APP_DIR, 'cfgs')
|
||||||
|
os.makedirs(d, exist_ok=True)
|
||||||
|
readme = os.path.join(d, '说明-把cfg放这里.txt')
|
||||||
|
if not os.path.exists(readme):
|
||||||
|
with open(readme, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(CFGS_README)
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@@ -103,7 +121,18 @@ def main():
|
|||||||
openocd_src = resolve_openocd_src(args.openocd_src)
|
openocd_src = resolve_openocd_src(args.openocd_src)
|
||||||
|
|
||||||
n_files = copy_toolchain(openocd_src)
|
n_files = copy_toolchain(openocd_src)
|
||||||
|
ensure_cfgs_dir()
|
||||||
shutil.copy2(readme, os.path.join(APP_DIR, 'README.md'))
|
shutil.copy2(readme, os.path.join(APP_DIR, 'README.md'))
|
||||||
|
# 用户自定义 cfg 目录(存在才打包): 界面 Target/Interface 下拉可选用
|
||||||
|
src_cfgs = os.path.join(BASE, 'cfgs')
|
||||||
|
n_cfgs = 0
|
||||||
|
if os.path.isdir(src_cfgs):
|
||||||
|
dst_cfgs = os.path.join(APP_DIR, 'cfgs')
|
||||||
|
if os.path.isdir(dst_cfgs):
|
||||||
|
shutil.rmtree(dst_cfgs)
|
||||||
|
shutil.copytree(src_cfgs, dst_cfgs)
|
||||||
|
n_cfgs = sum(len(fs) for _r, _d, fs in os.walk(dst_cfgs))
|
||||||
|
print(f'自定义 cfg: 复制 {n_cfgs} 个到发布包 cfgs/')
|
||||||
|
|
||||||
zip_name = f'{APP_NAME}-v{version}-win64.zip'
|
zip_name = f'{APP_NAME}-v{version}-win64.zip'
|
||||||
zip_path = os.path.join(RELEASE, zip_name)
|
zip_path = os.path.join(RELEASE, zip_name)
|
||||||
@@ -116,7 +145,9 @@ def main():
|
|||||||
z.write(full, os.path.join(APP_NAME, os.path.relpath(full, APP_DIR)))
|
z.write(full, os.path.join(APP_NAME, os.path.relpath(full, APP_DIR)))
|
||||||
|
|
||||||
zip_mb = os.path.getsize(zip_path) / 1024 / 1024
|
zip_mb = os.path.getsize(zip_path) / 1024 / 1024
|
||||||
print(f'发布文件夹: {APP_DIR} (exe + _internal + 内置工具链 {n_files} 个文件, 平铺即用)')
|
print(f'发布文件夹: {APP_DIR} (exe + _internal + 内置工具链 {n_files} 个文件'
|
||||||
|
+ (f' + 自定义 cfg {n_cfgs} 个' if n_cfgs else '')
|
||||||
|
+ ', 平铺即用)')
|
||||||
print(f'发布包: {zip_path} ({zip_mb:.1f} MB)')
|
print(f'发布包: {zip_path} ({zip_mb:.1f} MB)')
|
||||||
print('下一步上传: python publish_gitea.py', zip_name)
|
print('下一步上传: python publish_gitea.py', zip_name)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user