feat: flash phase tracking, erase checkbox, flash_model config

Step 2 UI now shows real-time flash operation phases parsed from program_flash output:
- Erase: "Performing Erase Operation..." → sf erase → Erased: OK
- Program: "Performing Program Operation..." → write blocks 0%→100%
- Verify: "Performing Verify Operation..." → read+cmp 0%→100%

Configuration additions:
- erase_before_program checkbox: controls whether to pre-erase before programming
- flash_model: chip type for capacity reference (s25fl256s1 = 32 MiB)

subprocess_utils.py now emits 'phase' callbacks (erase/program/verify/done)
detected from program_flash output transitions.
This commit is contained in:
Jeremy Shen
2026-06-10 13:39:11 +08:00
parent 2c48224aea
commit ea3e3b8ac7
6 changed files with 101 additions and 17 deletions
+41 -4
View File
@@ -80,6 +80,7 @@ class MainWindow(ctk.CTk):
self._zynq_jtag_present: bool = False
self._zynq_jtag_info = None
self._uart_monitor: UartMonitor | None = None
self._flash_phase: str = ""
# StringVar references for config sync
self._ip_string_var: ctk.StringVar | None = None
@@ -186,6 +187,7 @@ class MainWindow(ctk.CTk):
self._config.bootloader_elf_path = files["bootloader_elf_path"]
self._config.bootloader_bin_path = files["bootloader_bin_path"]
self._config.fsbl_elf_path = files["fsbl_elf_path"]
self._config.erase_before_program = self._erase_cb_var.get()
self._config.firmware_bin_path = files["firmware_bin_path"]
def _save_config(self) -> None:
@@ -398,7 +400,7 @@ class MainWindow(ctk.CTk):
row=0, column=0, sticky="nsew",
padx=PADDING, pady=PADDING,
)
panel.grid_rowconfigure(6, weight=1)
panel.grid_rowconfigure(7, weight=1)
title = ctk.CTkLabel(
panel,
@@ -461,6 +463,27 @@ class MainWindow(ctk.CTk):
self._fsbl_selector.set_path(str(resolved))
self._fsbl_selector.grid(row=5, column=0, sticky="nsew", padx=PADDING, pady=PADDING_SMALL)
# Flash options
flash_frame = ctk.CTkFrame(panel)
flash_frame.grid(row=6, column=0, sticky="nsew", padx=PADDING, pady=PADDING_SMALL)
flash_frame.grid_columnconfigure(0, weight=1)
self._erase_cb_var = ctk.BooleanVar(value=self._config.erase_before_program)
self._erase_cb = ctk.CTkCheckBox(
flash_frame,
text="Erase flash before programming",
variable=self._erase_cb_var,
font=FONT_SMALL,
)
self._erase_cb.grid(row=0, column=0, sticky="w", padx=PADDING_SMALL)
flash_model_label = ctk.CTkLabel(
flash_frame,
text=f"Flash: {self._config.flash_model} (32 MiB)",
font=FONT_SMALL,
)
flash_model_label.grid(row=1, column=0, sticky="w", padx=PADDING_SMALL, pady=(2, 0))
# Firmware BIN path (TFTP upload)
self._firmware_bin_selector = FileSelector(
panel,
@@ -470,7 +493,7 @@ class MainWindow(ctk.CTk):
if self._config.firmware_bin_path:
resolved = self._config.resolve_path(self._config.firmware_bin_path)
self._firmware_bin_selector.set_path(str(resolved))
self._firmware_bin_selector.grid(row=6, column=0, sticky="nsew", padx=PADDING, pady=PADDING_SMALL)
self._firmware_bin_selector.grid(row=7, column=0, sticky="nsew", padx=PADDING, pady=PADDING_SMALL)
# Save button
save_btn = ctk.CTkButton(
@@ -479,7 +502,7 @@ class MainWindow(ctk.CTk):
font=FONT_SMALL,
command=self._save_config,
)
save_btn.grid(row=7, column=0, padx=PADDING, pady=PADDING)
save_btn.grid(row=8, column=0, padx=PADDING, pady=PADDING)
def _build_serial_panel(self, parent: ctk.CTkFrame) -> None:
"""Build the serial monitor panel."""
@@ -725,7 +748,21 @@ class MainWindow(ctk.CTk):
return False
def _flash_callback(self, status: str, message: str) -> None:
"""Callback for flash programming progress."""
"""Callback for flash programming progress — tracks phases."""
if status == "phase":
# Track current flash phase for UI
self._flash_phase = message # 'erase', 'program', 'verify', 'done'
phase_names = {"erase": "Erasing", "program": "Programming", "verify": "Verifying", "done": "Done"}
label = phase_names.get(message, message)
self._log_message(f"{label}")
return
if status == "progress":
pct = message.rstrip('%')
# Show progress within current phase
phase_label = {"erase": "Erase", "program": "Program", "verify": "Verify"}.get(
self._flash_phase, "Flash")
self._log_message(f" [{phase_label}] {pct}%")
return
self._log_message(f" [{status}] {message}")
def _jtag_callback(self, status: str, message: str) -> None: