Summary
When the Specter-DIY GUI is running on the touchscreen, the USB VCP REPL becomes inaccessible. mpremote cannot enter raw REPL mode, and even raw serial Ctrl-C interrupts are silently consumed. The REPL only works during a brief ~1-2 second boot window before the GUI's asyncio event loop takes over.
This blocks automated GUI testing and developer REPL access during normal operation.
Environment
- Board: STM32F469DISC (Specter-DIY Disco)
- Firmware:
make disco (GUI-enabled, no HIL)
- Boot:
boot/main/boot.py sets pyb.usb_mode("VCP+MSC")
- USB: Device enumerates as
16c0:27dd (CDC-ACM), creates /dev/ttyACM1
- MicroPython fork: f469-disco (custom)
Expected behavior
Per the MicroPython documentation and STM32 USB design, pyb.usb_mode("VCP+MSC") should provide:
- USB VCP (virtual COM port) for REPL — always available
- USB MSC (mass storage) for PYBFLASH — always available
These should coexist with the GUI (LVGL on LTDC/DSI touchscreen) since they use independent hardware peripherals.
Actual behavior
Boot sequence
- Device resets,
boot/main/boot.py runs pyb.usb_mode("VCP+MSC")
- USB enumerates as CDC-ACM (
/dev/ttyACM1 on Linux)
- Brief REPL window (~1-2s):
mpremote connect works, print('ok') succeeds
- GUI starts (
display.init() → SpecterGUI() → specter.start())
- REPL becomes permanently blocked:
mpremote fails with TransportError: could not enter raw repl
- Raw serial Ctrl-C (
\x03) is silently consumed — no response
- Raw serial Ctrl-D (
\x04) causes USB re-enumeration, device disappears
After GUI starts
$ mpremote connect /dev/ttyACM1 exec "print('ok')"
mpremote.transport.TransportError: could not enter raw repl
$ python3 -c "
import serial
s = serial.Serial('/dev/ttyACM1', 115200)
s.write(b'\x03') # Ctrl-C
"
# No response, no error — silently consumed
What works
make hil (HIL mode, no GUI): REPL works indefinitely
make disco during boot window: REPL works for ~1-2s before GUI starts
make disco with main.py patched to skip GUI: REPL works indefinitely
Root cause analysis
The GUI's asyncio event loop (asyncio.run()) monopolizes the MicroPython VM. When mpremote tries to enter raw REPL mode, it sends a soft reset (Ctrl-B) which requires the VM to be in the REPL idle state. But the VM is running the asyncio event loop and never returns to the REPL prompt.
Specifically:
boot/main/boot.py:53 — pyb.usb_mode("VCP+MSC") sets up USB VCP with dupterm slot 1 (REPL)
src/main.py:69 — specter.start() calls asyncio.run(self.setup()) which never returns
- The asyncio loop polls USB VCP for Specter host protocol data but doesn't service REPL dupterm
The USBHost.enable() call (in platform.set_usb_mode()) explicitly kills REPL:
# src/platform.py:337-338
os.dupterm(None, 0)
os.dupterm(None, 1)
But this only happens when the user enables USB communication in settings — the REPL should survive until then.
What we tried
1. make hil + HIL commands over ST-Link UART
Approach: Build with HIL_ENABLED=1, use TEST_UI: commands over pyb.UART("YB", 9600) via ST-Link VCP.
Result: ST-Link V2.1 on this board doesn't bridge the UART "YB" signal to its USB VCP. The ST-Link VCP only provides SWD debug, not UART passthrough. platform.stlk is a physical UART pin that would need a separate USB-serial adapter.
Verdict: Requires hardware change (external USB-serial adapter wired to UART YB pins).
2. make hil with GUI enabled (patched main.py)
Approach: Removed if platform.hil_test_mode: pass guard so GUI runs in HIL mode.
Result: HIL mode auto-enables USBHost early (specter.py:564-567), which calls platform.enable_usb() and kills REPL via dupterm(None, 0) and dupterm(None, 1). REPL is gone before GUI even starts.
Verdict: USBHost + REPL are mutually exclusive on the same USB VCP.
3. make disco (no HIL, standard boot)
Approach: Standard production build with GUI. USB set to VCP+MSC.
Result: REPL works during ~1-2s boot window, then blocked by GUI event loop. Ctrl-C silently consumed.
Verdict: asyncio event loop prevents REPL from regaining control.
4. Raw serial interrupt during GUI
Approach: Send Ctrl-C (\x03), Ctrl-D (\x04), Ctrl-B (\x02) via pyserial to /dev/ttyACM1.
Result: All commands silently consumed. Ctrl-D causes USB re-enumeration and device disappears. No way to break into REPL.
Verdict: No interrupt mechanism works once GUI is running.
5. Boot window race condition
Approach: Script to detect REPL during boot, upload DGP files before GUI starts.
Result: Boot window is ~1-2s and unreliable. Script timed out.
Verdict: Too fragile for automation.
Potential workarounds
A. LVGL timer-based REPL yield (recommended)
Add a mechanism where the LVGL update timer periodically yields to the REPL. MicroPython's dupterm can coexist with asyncio if the event loop checks for REPL input. This could be:
- A
dupterm callback that queues REPL input for processing
- A dedicated asyncio task that services dupterm alongside the GUI loop
- LVGL v6
lv_task_handler() is already called from a timer — add dupterm servicing there
B. GPIO-based GUI suspend
Add a physical button or GPIO pin that, when pressed, suspends the GUI event loop and returns to the REPL. Similar to how some MicroPython boards use a BOOT button to enter safe mode.
C. USB serial adapter for UART YB
Connect an external USB-serial adapter (e.g., FT232H) to the STM32's UART "YB" pins. This gives a dedicated REPL port that's independent of USB VCP. The ST-Link SWD connector has UART TX/RX pins that could be used.
Pros: Guaranteed REPL access regardless of GUI state
Cons: Requires hardware modification or additional cable
D. Separate USB configurations
Use pyb.usb_mode("VCP") without MSC, or configure USB descriptors to expose two separate interfaces (VCP for REPL + another VCP for Specter protocol). This would require custom USB descriptor changes in the MicroPython port.
Pros: Clean separation
Cons: Significant MicroPython port changes
E. UART-based REPL only, no USB REPL
Keep USB exclusively for Specter host protocol and MSC. Use the physical UART (wired to ST-Link or external adapter) for REPL. This is closest to the existing HIL design intent (see the commented-out code in platform.py:339-351).
Pros: No conflict, clean separation of concerns
Cons: Requires physical serial connection for development
Related code
| File |
Line(s) |
Relevance |
boot/main/boot.py |
53 |
pyb.usb_mode("VCP+MSC") |
src/main.py |
17-69 |
GUI setup and specter.start() |
src/platform.py |
320-356 |
set_usb_mode(), stlk UART, dupterm management |
src/specter.py |
555-621 |
HIL listener, USBHost auto-enable |
src/hil.py |
49-106 |
HIL command handler over UART |
f469-disco/micropython/ports/stm32/usb.c |
624-627 |
VCP dupterm slot assignment |
Impact
- Developer experience: Cannot use REPL while GUI is running. Must flash HIL firmware (no GUI) for any REPL-based development or testing.
- Automated testing: Cannot automate GUI tests via USB. HIL UART requires physical serial adapter.
- Debugging: Cannot inspect live GUI state via REPL. Must use debug traces or HIL commands.
Summary
When the Specter-DIY GUI is running on the touchscreen, the USB VCP REPL becomes inaccessible.
mpremotecannot enter raw REPL mode, and even raw serial Ctrl-C interrupts are silently consumed. The REPL only works during a brief ~1-2 second boot window before the GUI's asyncio event loop takes over.This blocks automated GUI testing and developer REPL access during normal operation.
Environment
make disco(GUI-enabled, no HIL)boot/main/boot.pysetspyb.usb_mode("VCP+MSC")16c0:27dd(CDC-ACM), creates/dev/ttyACM1Expected behavior
Per the MicroPython documentation and STM32 USB design,
pyb.usb_mode("VCP+MSC")should provide:These should coexist with the GUI (LVGL on LTDC/DSI touchscreen) since they use independent hardware peripherals.
Actual behavior
Boot sequence
boot/main/boot.pyrunspyb.usb_mode("VCP+MSC")/dev/ttyACM1on Linux)mpremote connectworks,print('ok')succeedsdisplay.init()→SpecterGUI()→specter.start())mpremotefails withTransportError: could not enter raw repl\x03) is silently consumed — no response\x04) causes USB re-enumeration, device disappearsAfter GUI starts
What works
make hil(HIL mode, no GUI): REPL works indefinitelymake discoduring boot window: REPL works for ~1-2s before GUI startsmake discowithmain.pypatched to skip GUI: REPL works indefinitelyRoot cause analysis
The GUI's asyncio event loop (
asyncio.run()) monopolizes the MicroPython VM. Whenmpremotetries to enter raw REPL mode, it sends asoft reset(Ctrl-B) which requires the VM to be in the REPL idle state. But the VM is running the asyncio event loop and never returns to the REPL prompt.Specifically:
boot/main/boot.py:53—pyb.usb_mode("VCP+MSC")sets up USB VCP with dupterm slot 1 (REPL)src/main.py:69—specter.start()callsasyncio.run(self.setup())which never returnsThe
USBHost.enable()call (inplatform.set_usb_mode()) explicitly kills REPL:But this only happens when the user enables USB communication in settings — the REPL should survive until then.
What we tried
1.
make hil+ HIL commands over ST-Link UARTApproach: Build with
HIL_ENABLED=1, useTEST_UI:commands overpyb.UART("YB", 9600)via ST-Link VCP.Result: ST-Link V2.1 on this board doesn't bridge the UART "YB" signal to its USB VCP. The ST-Link VCP only provides SWD debug, not UART passthrough.
platform.stlkis a physical UART pin that would need a separate USB-serial adapter.Verdict: Requires hardware change (external USB-serial adapter wired to UART YB pins).
2.
make hilwith GUI enabled (patched main.py)Approach: Removed
if platform.hil_test_mode: passguard so GUI runs in HIL mode.Result: HIL mode auto-enables USBHost early (
specter.py:564-567), which callsplatform.enable_usb()and kills REPL viadupterm(None, 0)anddupterm(None, 1). REPL is gone before GUI even starts.Verdict: USBHost + REPL are mutually exclusive on the same USB VCP.
3.
make disco(no HIL, standard boot)Approach: Standard production build with GUI. USB set to
VCP+MSC.Result: REPL works during ~1-2s boot window, then blocked by GUI event loop. Ctrl-C silently consumed.
Verdict: asyncio event loop prevents REPL from regaining control.
4. Raw serial interrupt during GUI
Approach: Send Ctrl-C (
\x03), Ctrl-D (\x04), Ctrl-B (\x02) viapyserialto/dev/ttyACM1.Result: All commands silently consumed. Ctrl-D causes USB re-enumeration and device disappears. No way to break into REPL.
Verdict: No interrupt mechanism works once GUI is running.
5. Boot window race condition
Approach: Script to detect REPL during boot, upload DGP files before GUI starts.
Result: Boot window is ~1-2s and unreliable. Script timed out.
Verdict: Too fragile for automation.
Potential workarounds
A. LVGL timer-based REPL yield (recommended)
Add a mechanism where the LVGL update timer periodically yields to the REPL. MicroPython's
duptermcan coexist with asyncio if the event loop checks for REPL input. This could be:duptermcallback that queues REPL input for processinglv_task_handler()is already called from a timer — add dupterm servicing thereB. GPIO-based GUI suspend
Add a physical button or GPIO pin that, when pressed, suspends the GUI event loop and returns to the REPL. Similar to how some MicroPython boards use a BOOT button to enter safe mode.
C. USB serial adapter for UART YB
Connect an external USB-serial adapter (e.g., FT232H) to the STM32's UART "YB" pins. This gives a dedicated REPL port that's independent of USB VCP. The ST-Link SWD connector has UART TX/RX pins that could be used.
Pros: Guaranteed REPL access regardless of GUI state
Cons: Requires hardware modification or additional cable
D. Separate USB configurations
Use
pyb.usb_mode("VCP")without MSC, or configure USB descriptors to expose two separate interfaces (VCP for REPL + another VCP for Specter protocol). This would require custom USB descriptor changes in the MicroPython port.Pros: Clean separation
Cons: Significant MicroPython port changes
E. UART-based REPL only, no USB REPL
Keep USB exclusively for Specter host protocol and MSC. Use the physical UART (wired to ST-Link or external adapter) for REPL. This is closest to the existing HIL design intent (see the commented-out code in
platform.py:339-351).Pros: No conflict, clean separation of concerns
Cons: Requires physical serial connection for development
Related code
boot/main/boot.pypyb.usb_mode("VCP+MSC")src/main.pyspecter.start()src/platform.pyset_usb_mode(),stlkUART, dupterm managementsrc/specter.pysrc/hil.pyf469-disco/micropython/ports/stm32/usb.cImpact