Skip to content

Conversation

@k9ert
Copy link
Contributor

@k9ert k9ert commented Dec 20, 2025

The scripts/disco tool is a Python CLI for interacting with the STM32F469-Discovery board. Provides commands for flashing, serial console, OpenOCD control, memory inspection, fingerprint testing, and diagnostics. Includes test suite.

Optimized for AI agent usage. But works for humans alike.

Quick Start

scripts/disco ocd start       # Start OpenOCD
scripts/disco doctor          # Run full diagnostics
scripts/disco flash program firmware.bin  # Flash firmware
scripts/disco repl exec "print('hello')"  # Run Python code
Full command tree
disco
├── cables          # Detect connected USB cables
├── check           # Run full board diagnostics
├── doctor          # Automated diagnostics with logging
│
├── ocd             # OpenOCD server management
│   ├── start       # Start OpenOCD (background)
│   ├── stop        # Stop OpenOCD
│   ├── status      # Check if running
│   └── cmd         # Send raw OpenOCD command
│
├── cpu             # CPU control (requires ocd start)
│   ├── halt        # Halt CPU
│   ├── resume      # Resume execution
│   ├── reset       # Reset and halt
│   ├── step        # Single step
│   ├── pc          # Show program counter
│   ├── regs        # Show all registers
│   ├── stack       # Show stack (N words)
│   └── gdb         # Show/launch GDB
│
├── mem             # Memory inspection
│   ├── read        # Read memory words
│   ├── vectors     # Show vector table
│   ├── dump        # Dump N words from flash
│   └── save        # Save region to file
│
├── flash           # Flash programming
│   ├── program     # Program firmware
│   ├── erase       # Mass erase (dangerous!)
│   ├── verify      # Verify against file
│   ├── read        # Read flash to file
│   ├── info        # Show flash bank info
│   ├── analyze     # Analyze firmware layout
│   ├── identify    # Identify build type
│   └── fingerprint # Firmware fingerprints
│       ├── create  # Create new fingerprint
│       ├── update  # Update existing fingerprint
│       └── test    # Test against fingerprint
│
├── serial          # Serial/REPL communication
│   ├── list        # List serial devices
│   ├── repl        # Test REPL connection
│   ├── console     # Interactive console (screen)
│   ├── test        # Quick serial test
│   └── boot        # Reset and capture boot output
│
└── repl            # MicroPython REPL interaction
    ├── exec        # Execute Python code
    ├── info        # Show board info
    ├── modules     # List available modules
    ├── help        # Show MicroPython help
    ├── hello       # Display message on screen
    ├── import      # Import module, show output
    ├── reset       # Soft-reset (Ctrl-D)
    ├── ls          # List files
    ├── cat         # Print file contents
    ├── cp          # Copy file to/from board
    └── rm          # Remove file

Also included

  • CI workflow for building disco tool and test it
  • Debugging guide with troubleshooting for known issues
  • Fingerprint testing for firmware regression/CI validation

@tadeubas
Copy link
Member

Nice to know that it also work for humans, lol 😆

k9ert and others added 24 commits December 21, 2025 13:55
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add disco-tool-tests job to CI (pytest on Python 3.11)
- Move tests to tests/unit/ for CI, tests/integration/ for hardware
- Add scripts/requirements.txt and pytest.ini
- Update .gitignore (build, .direnv, .pytest_cache)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- create ocd_provider.py for centralized OCD instance w/ test injection
- create OCDMock that tracks halt/resume state
- add 29 tests for halt/resume balance
- fix 13 commands missing resume after halt:
  flash: identify/read/verify/erase
  mem: read/vectors/dump/save
  cpu: regs/pc/stack
  check/cables: add try/finally for error handling

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Move fingerprint create/update/test to flash_fingerprint.py
- Add detailed help text for fingerprint subcommand
- Rename ocd connect -> ocd start, ocd disconnect -> ocd stop
- Update quickstart with fingerprint tree, docs, and ocd renames
- Add session completion workflow to AGENTS.md

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Replace specter-diy references with f469-disco
- Note disco tool makes manual debugging rarely needed
- Add Known Issues section (QSPI shadow dirs, SPI flash hang)
- Update command examples to use ocd start

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Test 6 firmwares in fwbox using --static-only flag
- Run in CI without hardware
- Remove specter_releases (outdated fingerprints)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@k9ert k9ert marked this pull request as ready for review December 21, 2025 21:36
Explain flake.nix provides all build tools (arm-gcc, openocd, gdb, SDL2).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a comprehensive Python CLI tool (disco) for interacting with the STM32F469-Discovery board. The tool provides a unified interface for JTAG debugging, flash programming, serial communication, and MicroPython REPL interaction. It includes automated diagnostics, firmware fingerprinting for CI/regression testing, and extensive test coverage.

Key changes:

  • New scripts/disco CLI tool with hierarchical command structure (ocd, cpu, mem, flash, serial, repl subcommands)
  • Comprehensive test suite with unit tests and integration tests for fingerprint validation
  • Automated diagnostics system (disco doctor) with markdown logging
  • Firmware fingerprinting for regression testing and CI validation

Reviewed changes

Copilot reviewed 57 out of 67 changed files in this pull request and generated 37 comments.

Show a summary per file
File Description
scripts/disco Main CLI entry point with venv auto-activation
scripts/disco_lib/ Core library modules (openocd, serial, repl, flash, diagnostics, cpu, memory)
scripts/disco_lib/commands/ Command implementations for all CLI subcommands
scripts/disco_lib/tests/unit/ Unit tests for core modules with mocking
scripts/disco_lib/tests/integration/ Integration tests for fingerprint validation
scripts/disco_lib/tests/mocks/ Mock objects for OpenOCD testing
tests/fwbox/ Firmware test box with fingerprint files for various builds
docs/debugging.md Comprehensive 1000+ line debugging guide
docs/architecture/firmware_layouts.md Documentation of firmware memory layouts
.github/workflows/ci.yml CI workflow for disco tool tests
Documentation updates Various doc files with URL updates (github.io domain change)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- Remove 10 unused imports across 8 files
- Remove unused variable (code_regions) and result assignments
- cables.py: enrich return dict with error/platform, catch specific exceptions
- diagnostics.py: _parse_mdw returns (words, skipped) tuple
- memory.py: read_vectors adds skipped key for unreadable values
- openocd.py: use non-blocking recv, add OPENOCD_LOG constant
- Add tests for skipped/unreadable memory values

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@k9ert
Copy link
Contributor Author

k9ert commented Dec 28, 2025

I've fixed all of copilot's issue with bc11187

Copy link
Contributor Author

@k9ert k9ert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This review has been created with the help of Claude cli (/review)

Code Review: PR #35 - feat: disco tool

Overview

This PR adds a Python CLI tool (scripts/disco) for interacting with the STM32F469-Discovery board. It provides commands for:

  • OpenOCD server management
  • CPU control (halt, resume, reset, step)
  • Memory inspection
  • Flash programming with smart verification
  • MicroPython REPL interaction
  • Automated board diagnostics

Size: +7088/-16 lines across 50 files

Code Quality: ✅ Good

Architecture

  • Well-structured: Clean separation between CLI (commands/), backend logic (flash.py, openocd.py, etc.), and tests
  • Click framework: Good use of Click with \b for help formatting
  • Context managers: Proper resource handling (with cpu_backend.halted())
  • Type hints: Used consistently throughout

Testing

  • Unit tests for flash analysis logic (test_flash.py)
  • Integration tests for fingerprint validation
  • Good test documentation explaining what each test validates

Documentation

  • Extensive docs/debugging.md (~1000 lines)
  • Firmware layouts documentation
  • Well-documented CLI commands with examples

Suggestions for Improvement

1. Bare exception handling

Some except Exception clauses could mask issues:

diagnostics.py:151, 317-318

except Exception:
    report.add("TARGET_NOT_RESPONDING", ...)

Consider catching specific exceptions or at minimum logging the actual error.

2. Platform portability concerns

Issue Location Impact
pkill -x openocd openocd.py:133 Linux/macOS only
/tmp/disco_log diagnostics.py:408 Linux/macOS only
usbmodem* pattern diagnostics.py:298 macOS-specific

The CI runs on ubuntu-latest but serial detection seems macOS-focused. Consider:

  • Using tempfile.gettempdir() for logs
  • Adding platform detection for serial patterns

3. Test cleanup safety

test_flash.py: Manual os.unlink() in try/finally - consider pytest tmp_path fixture:

def test_all_code(self, tmp_path):
    path = tmp_path / "test.bin"
    path.write_bytes(b"\xff" * 8192)
    regions = analyze_firmware(str(path))
    # No cleanup needed - pytest handles it

4. Minor: _is_blank_chunk only checks 0x00

Erased flash is typically 0xFF. The code intentionally treats 0x00 as "blank" for filesystem preservation detection - consider adding a comment explaining this design choice in the function docstring.


Potential Issues/Risks

Low Risk

  • Timeout behavior: 10 consecutive 2s timeouts (20s total) before giving up in openocd.py:70 - slow failures could be very slow

Security

  • pkill -x openocd could affect other users' processes on shared systems (unlikely to be a concern for embedded dev boards)

Edge Cases

  • What happens if OpenOCD start fails but a stale process exists?
  • Serial device detection may miss non-standard USB CDC implementations

Summary

Aspect Rating Notes
Code quality ⭐⭐⭐⭐ Clean, well-organized
Documentation ⭐⭐⭐⭐⭐ Excellent, thorough
Test coverage ⭐⭐⭐ Good for core logic, could expand
Portability ⭐⭐⭐ macOS-focused serial detection
Error handling ⭐⭐⭐ Some bare except clauses

Recommendation: ✅ Approve with minor suggestions

The PR is well-implemented and provides valuable tooling. The suggestions above are improvements, not blockers. The AI agent optimization focus is clear and the documentation is excellent.

- Replace bare except with specific exceptions (OSError, ClickException, SerialException)
- Use tempfile.gettempdir() for platform-portable log paths
- Add platform detection for USB CDC (macOS: usbmodem*, Linux: ttyACM*)
- Use pytest tmp_path fixture in test_flash.py
- Document _is_blank_chunk 0x00 design choice

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@k9ert
Copy link
Contributor Author

k9ert commented Dec 28, 2025

I've fixed all claude recommendations in 5e27501

- test_cpu.py: 32 tests for halted() ctx mgr, read_pc/sp parsing
- test_memory.py: 27 tests for read_vectors, unreadable memory
- test_ocd_provider.py: 19 tests for singleton/injection
- test_flash.py: +27 error path tests (version tags, flash addr)
- test_serial.py: +12 edge case tests
- test_diagnostics.py: +13 tests (check_target, cpu_stuck)
- test_halt_resume.py: remove stale "Currently FAILS" comments

Total: 169 → 267 tests

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@k9ert
Copy link
Contributor Author

k9ert commented Dec 28, 2025

7c19ea2 added a lot more tests and also reviewed and improved existing tests.

Add commands to manage STM32F469 Read-out Protection (RDP):
- disco flash rdp: Show current RDP level (0/1/2)
- disco flash unlock: Remove RDP1 protection (causes mass erase)
- disco flash lock: Enable RDP1 protection

Backend functions: read_option_bytes, parse_rdp_level, unlock_rdp, lock_rdp
Unit tests: 19 new tests for RDP parsing and commands

Safety: Refuses RDP2 operations (permanent), confirms destructive actions

Closes: fw-0ol, fw-p27

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@k9ert
Copy link
Contributor Author

k9ert commented Dec 28, 2025

5e57039 added a lock and unlock command and a rdp command to get the current status of the read-protection.

Details
(.venv) ➜  scripts git:(kn/disco_tool) ✗ ./disco flash --help
Usage: disco flash [OPTIONS] COMMAND [ARGS]...

  Flash programming commands.

  Program and erase the STM32F469 internal flash via JTAG/OpenOCD. The flash
  is 2MB organized as dual-bank with mixed sector sizes.

  Flash Layout (STM32F469, 2MB dual-bank):
    Bank 1 (0x08000000 - 0x080FFFFF):
      Sectors 0-3:   16KB each  (0x08000000 - 0x0800FFFF)
      Sector 4:      64KB       (0x08010000 - 0x0801FFFF)
      Sectors 5-11: 128KB each  (0x08020000 - 0x080FFFFF)

    Bank 2 (0x08100000 - 0x081FFFFF):     Same layout as Bank 1

  Filesystem Preservation:
    MicroPython firmware files typically have zeros between code regions
    to preserve the internal filesystem during updates. Zero regions
    won't overwrite existing flash data when programmed.
    Use 'disco flash analyze <file>' to see the firmware layout.

  Programming Addresses:
    0x08000000  Full image (bootloader + firmware, preserves filesystem)
    0x08020000  Main firmware only (default for 'program' command)

  Examples:
    disco flash analyze firmware.bin             # Show firmware layout
    disco flash program firmware.bin             # Flash to 0x08020000
    disco flash program full.bin --addr 0x08000000
    disco flash erase                            # Requires confirmation

  Safety:
    - 'program' verifies after writing by default (--no-verify to skip)
    - 'program' resets the board after flashing (--no-reset to skip)
    - 'erase' requires explicit confirmation

Options:
  --help  Show this message and exit.

Commands:
  analyze      Analyze firmware file layout.
  erase        Mass erase flash (DANGEROUS).
  fingerprint  Firmware fingerprint commands.
  identify     Identify firmware build type (debug vs production).
  info         Show flash bank info.
  lock         Enable flash read protection (RDP Level 0 -> 1).
  program      Program firmware to flash.
  rdp          Show current read protection (RDP) level.
  read         Read flash contents to file.
  unlock       Remove flash read protection (RDP Level 1 -> 0).
  verify       Verify flash contents agai

@tadeubas
Copy link
Member

Hey @k9ert I was trying to test the PR and followed instructions of docs/build.md but got several issues.

I've also had to add pytest>=9.0.2 to requirements.txt in order to execute pytest scripts/disco_lib/tests/unit -v or pytest scripts/disco_lib/tests/integration -v.

I tried to execute doctor but got this msg:

>scripts/disco ocd start
Starting OpenOCD...
OpenOCD connected successfully
Target info:
pc (/32): 0x08047bb2

----------------------------------

>scripts/disco doctor
=== Board Diagnostic Report ===

Connection:
  ✓ OpenOCD
  ✓ Target
  ⚠ USB CDC

CPU:
  ✓ PC=0x08047bb2

Issues:
  [warn] USB_OTG_MISSING: No USB CDC device (check cable)

1 warnings

Then I've unplugged and plugged again, and tried few times, got two diff errors:

>scripts/disco doctor
=== Board Diagnostic Report ===

Connection:
  ✓ OpenOCD
  ✗ Target
  ⚠ USB CDC

Issues:
  [error] TARGET_NOT_RESPONDING: JTAG connected but MCU not responding
  [warn] USB_OTG_MISSING: No USB CDC device (check cable)

1 errors, 1 warnings

-------------------------------------

>scripts/disco doctor
=== Board Diagnostic Report ===

Connection:
  ✓ OpenOCD
  ✓ Target
  ⚠ USB CDC

CPU:
  ✗ STUCK at 0x08047bb2

Issues:
  [error] CPU_STUCK: PC doesn't change (infinite loop/fault)
  [warn] FPU_DISABLED: FPU not fully enabled
  [warn] USB_OTG_MISSING: No USB CDC device (check cable)

1 errors, 2 warnings

The board is connected with the USB mini and micro on the PC (with power jumper on USB), I've flashed it with the st-flash write bin/upy-f469disco.bin 0x8000000 after using make mpy-cross than make disco

@tadeubas
Copy link
Member

tadeubas commented Dec 29, 2025

I got this msg (tried to change power jumper to st-link and connecting mini and micro USB cables, but same result):

>scripts/disco cables
=== Cable Detection ===

MicroUSB (ST-LINK connector):
  JTAG: connected
  Serial (VCP): not detected

MiniUSB (USB OTG connector):
  USB: detected but no CDC - firmware issue?

Tip: Connect miniUSB cable for REPL access

@tadeubas
Copy link
Member

tadeubas commented Jan 3, 2026

Hey @k9ert, I noticed that tty device names differ between macOS and Linux. For example, on Linux ttyACM0 / serial/by-id/usb-STMicroelectronics corresponds to the ST-Link mini USB cable, while ttyACM1 / serial/by-id/usb-Micropython is the micro-USB connection. The current code only checks for tty.usbmodem*, which works on macOS but misses on Linux.

/dev/serial/by-id/usb-MicroPython_Pyboard_Virtual_Comm_Port_in_FS_Mode_3471355E3031-if01  /dev/ttyACM0
/dev/serial/by-id/usb-STMicroelectronics_STM32_STLink_066EFF313358353143103737-if02       /dev/ttyACM1

@tadeubas
Copy link
Member

tadeubas commented Jan 5, 2026

I fixed some Linux-specific issues in this PR: k9ert#1

However, driver behavior differs between macOS and Linux. On Linux, once OpenOCD starts, it holds the USB endpoint long enough to disrupt the CDC interface, causing repl_test serial read/write to stall. Increasing the timeout (up to 30.0) did not resolve the issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants