Skip to content

Conversation

@andrewleech
Copy link

Note: this is stacked on top of #36 so includes that commit too.

Add --build flag for local MicroPython firmware building

Overview

This PR adds a --build flag to mpflash that integrates with mpbuild to build MicroPython firmware locally. This solves a critical compatibility issue where pyOCD SWD/JTAG programming requires .hex/.bin/.elf files, but only .dfu files are available from micropython.org downloads.

Problem Statement

When using --method pyocd for SWD/JTAG programming, users encounter errors like:

❌ Flash programming failed: unknown file format 'dfu'

This happens because:

  • pyOCD needs .hex, .bin, or .elf firmware files
  • micropython.org only provides .dfu files for most STM32 boards
  • No local build option existed in mpflash

Solution

The --build flag enables local firmware building that generates all formats needed by different flash methods:

mpflash flash --build --method pyocd --board NUCLEO_H563ZI

Key Features

🏗️ Complete Build Integration

  • Seamless mpbuild integration with Docker containerized builds
  • Automatic MicroPython repository detection and management
  • Generates all firmware formats: .dfu, .hex, .bin, .elf

Smart Caching System

  • Intelligent build caching (5-30 minute builds cached indefinitely)
  • Cache invalidation based on board and version
  • Avoids rebuilding identical firmware

🔄 Database Integration

  • Built firmware automatically imported to mpflash database
  • Works seamlessly with existing firmware selection logic
  • Method-aware format selection (pyOCD prefers .hex, DFU prefers .dfu)

🛡️ Robust Error Handling

  • Python 3.10+ requirement detection with clear error messages
  • Docker availability validation
  • Graceful degradation on unsupported systems

🎯 Backward Compatibility

  • Zero breaking changes to existing functionality
  • Works with all existing flash methods
  • Optional feature - existing workflows unchanged

Technical Implementation

Core Architecture

class BuildManager:
    """Manages MicroPython firmware builds with caching."""

    def get_or_build(self, board: str, version: str) -> List[Path]:
        # Check cache first
        if cached := self._find_cached(board, version):
            return cached
        # Build using mpbuild
        return self._build_firmware(board, version)

CLI Integration

The --build flag is added to the flash command:

@click.option(
    "--build",
    default=False,
    is_flag=True,
    help="Build MicroPython firmware locally using mpbuild before flashing. Generates all formats (.dfu, .hex, .bin, .elf). Requires Docker.",
)

Dependencies

  • mpbuild: Added as optional dependency (uv sync --extra build)
  • Python 3.10+: Required due to mpbuild's union type syntax
  • Docker: Required for containerized MicroPython builds

Usage Examples

Basic Local Build

# Build and flash latest firmware
mpflash flash --build --board NUCLEO_H563ZI --method pyocd

# Build specific version
mpflash flash --build --board RPI_PICO --version v1.26.0

pyOCD SWD/JTAG Programming

# Now works! Generates .hex files for pyOCD
mpflash flash --build --method pyocd --board STM32F4DISC

Force Rebuild

# Bypass cache and rebuild
mpflash flash --build --force --board NUCLEO_H563ZI

Error Handling

Python Version Check

❌ Build functionality not available: mpbuild requires Python 3.10 or newer (current: Python 3.9).
The --build flag is not available on this Python version.

Docker Requirement

❌ Build functionality not available: Docker is not installed or not running.
Building requires Docker to be available.

Missing mpbuild

❌ Build functionality not available: mpbuild is not installed. Install with: uv sync --extra build
Note: mpbuild requires Docker to build MicroPython firmware.

Files Changed

  • mpflash/build.py (NEW, 410 lines): Complete build management system
  • mpflash/cli_flash.py (+40 lines): CLI integration and build workflow
  • pyproject.toml (+3 lines): Optional mpbuild dependency
  • uv.lock (+66 lines): Dependency lockfile updates

Testing

The implementation includes comprehensive error handling and has been tested with:

  • ✅ Python 3.9 (clear error message about version requirement)
  • ✅ Python 3.12 + Docker (successful build integration)
  • ✅ Missing dependencies (helpful installation guidance)
  • ✅ Method-aware firmware selection (pyOCD gets .hex, DFU gets .dfu)

Benefits

  1. Solves pyOCD Compatibility: No more "unknown file format 'dfu'" errors
  2. Local Development: Build custom firmware with local changes
  3. All Flash Methods: Works with pyOCD, DFU, UF2, esptool
  4. Performance: Smart caching avoids redundant builds
  5. User Experience: Single command from problem to solution

Migration Path

This is a purely additive feature:

  • Existing users: No changes required, everything works as before
  • New capability: Add --build flag when needed
  • Optional dependency: Only install mpbuild if you need local builds

Future Enhancements

  • Build configuration options (debug/release, specific features)
  • Build artifact management and cleanup
  • Integration with Git worktrees and custom MicroPython forks
  • Parallel builds for multiple boards

Before this PR: pyOCD fails with DFU files ❌
After this PR: --build generates all formats ✅

This addresses a fundamental compatibility gap and provides a foundation for advanced local development workflows.

pi-anl added 3 commits August 19, 2025 15:28
…ction

## Major Features Added

### pyOCD Integration
- Add SWD/JTAG programming as alternative to serial bootloader methods
- Support for debug probe discovery and management
- Automated target chip selection using dynamic detection
- Optional pyOCD dependency via `pyocd` extra

### Dynamic Target Detection
- Replace hardcoded target mappings with dynamic API-based detection
- Parse MCU info from `sys.implementation._machine` strings
- Fuzzy matching algorithm for target selection
- Direct probe-based target detection with fallback to fuzzy matching
- Extensible architecture for future OpenOCD/J-Link support

### CLI Integration
- Add `--method pyocd` option for explicit SWD/JTAG programming
- Add `--probe-id` option for specific debug probe selection
- Maintain existing serial bootloader behavior as default
- Clean integration with existing flash method selection

### Architecture Improvements
- Abstract debug probe layer for extensibility
- Target detector abstraction with registry system
- Proper error handling and fallback mechanisms
- Performance optimized with caching and lazy loading

## Technical Details

### Files Added
- `mpflash/flash/debug_probe.py` - Debug probe abstraction layer
- `mpflash/flash/pyocd_probe.py` - pyOCD-specific probe implementation
- `mpflash/flash/pyocd_flash.py` - pyOCD flash programming interface
- `mpflash/flash/pyocd_targets.py` - Target detection wrapper functions
- `mpflash/flash/dynamic_targets.py` - Dynamic target detection engine
- `mpflash/cli_pyocd.py` - pyOCD-specific CLI commands (future)

### Files Modified
- `mpflash/common.py` - Add FlashMethod enum for different programming methods
- `mpflash/flash/__init__.py` - Integrate pyOCD into flash method selection
- `mpflash/cli_flash.py` - Add CLI options for pyOCD method and probe selection
- `pyproject.toml` - Add optional pyOCD dependency
- `mpflash/cli_download.py` - Fix unused pytest import

### Key Benefits
- **No hardware requirements change** - existing serial methods remain default
- **Automated target selection** - no manual target configuration needed
- **Extensible design** - easy to add OpenOCD, J-Link, etc. in future
- **Performance optimized** - direct API calls instead of subprocess shells
- **Maintainable** - eliminates hardcoded target mappings

## Usage

```bash
# Existing behavior unchanged (serial bootloader methods)
mpflash flash

# Explicit pyOCD SWD/JTAG programming
mpflash flash --method pyocd

# Specific debug probe selection
mpflash flash --method pyocd --probe-id stlink

# Install with pyOCD support
uv sync --extra pyocd
```

## Breaking Changes
None - all existing functionality preserved with same default behavior.
Integrate mpbuild for building MicroPython firmware locally.

- Add BuildManager class with caching for 5-30 minute builds
- Implement firmware import to mpflash database
- Add --build CLI flag with comprehensive error handling
- Support Python 3.10+ requirement with clear messaging
When no --board is specified, use --serial parameter for board detection
instead of scanning all ports. This ensures specific serial devices are
targeted even during auto-detection.

- Use params.serial instead of params.ports in connected_ports_boards_variants()
- Only fall back to params.ports when --serial is '*' (scan all)
- Prevents unnecessary port scanning when specific device is requested
@andrewleech andrewleech changed the title Mpbuild mpbuild integration Aug 19, 2025
@andrewleech
Copy link
Author

Again, I haven't code reviewed this at all yet, so feel free to not look at it at all either until I do so and un-draft the PR!

@Josverl
Copy link
Owner

Josverl commented Aug 19, 2025

Thanks for making a start on this, I'll be patient 😁

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.

3 participants