From cddecebf06d8f471fde166355385d912d7184e40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20NEDJAR?= Date: Mon, 13 Apr 2026 05:27:08 +0200 Subject: [PATCH 1/6] tooling: Add make daplink-firmware and make daplink-deploy targets. --- .devcontainer/Dockerfile | 4 +- CONTRIBUTING.md | 32 +++++++++-- Makefile | 55 +++++++++++++++++- env.mk | 12 +++- scripts/deploy_usb.py | 118 +++++++++++++++++++++++++-------------- 5 files changed, 171 insertions(+), 50 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index ca3a52eb..d83205db 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,11 +1,13 @@ FROM mcr.microsoft.com/devcontainers/python:3.10 -# System packages for MicroPython firmware build and board communication +# System packages for MicroPython and DAPLink firmware builds, board communication RUN apt-get update && apt-get install -y --no-install-recommends \ gcc-arm-none-eabi \ libnewlib-arm-none-eabi \ openocd \ udev \ + ccache \ + ninja-build \ && rm -rf /var/lib/apt/lists/* # udev rules for STeaMi board (DAPLink / STM32) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1b4593d9..d2bd9b27 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -119,7 +119,8 @@ For local development (without dev container): * Python 3.10+ * Node.js 22+ (for husky, commitlint, lint-staged, semantic-release) -* `arm-none-eabi-gcc` toolchain (for `make micropython-firmware`) +* `arm-none-eabi-gcc` toolchain (for `make micropython-firmware` and `make daplink-firmware`) +* `ccache` and `ninja-build` (for `make daplink-firmware`) * `pyocd` (for `make micropython-deploy`, installed via `pip install -e ".[flash]"`) * OpenOCD (optional, for `make micropython-deploy-openocd`) * `mpremote` (installed via `pip install -e ".[test]"`) @@ -129,7 +130,7 @@ Then run `make setup` to install all dependencies and git hooks. This creates a ## Dev Container -A dev container is available for VS Code (local Docker only, not GitHub Codespaces). It includes all prerequisites out of the box: Python 3.10, Node.js 22, ruff, pytest, mpremote, pyOCD, arm-none-eabi-gcc, OpenOCD, and the GitHub CLI. +A dev container is available for VS Code (local Docker only, not GitHub Codespaces). It includes all prerequisites out of the box: Python 3.10, Node.js 22, ruff, pytest, mpremote, pyOCD, arm-none-eabi-gcc, OpenOCD, ccache, ninja-build, and the GitHub CLI. 1. Open the repository in VS Code 2. When prompted, click **Reopen in Container** (or use the command palette: *Dev Containers: Reopen in Container*) @@ -191,9 +192,11 @@ make bump PART=major # major: v1.1.0 → v2.0.0 The STeaMi board has two distinct firmwares: - **MicroPython firmware** — runs on the STM32WB55 main MCU and exposes the drivers from this repository -- **DAPLink firmware** — runs on the STM32F103 interface chip and provides the I2C bridge, mass-storage, and CMSIS-DAP debug interface (build targets planned in #377) +- **DAPLink firmware** — runs on the STM32F103 interface chip and provides the I2C bridge, mass-storage, and CMSIS-DAP debug interface -This section covers the **MicroPython firmware** only. The drivers in this repository are "frozen" into it. The Makefile automates cloning, building, and flashing: +The drivers in this repository are "frozen" into the **MicroPython firmware**. The Makefile automates cloning, building, and flashing both firmwares. + +### MicroPython firmware ```bash make micropython-firmware # Clone micropython-steami (if needed), link local drivers, build @@ -215,6 +218,27 @@ Use `make micropython-firmware` for normal rebuilds from the existing local clon All these tools are included in the dev container. For local development, see the [Prerequisites](#prerequisites) section. +### DAPLink firmware + +DAPLink is the firmware running on the STM32F103 interface chip. It provides the USB mass-storage, CMSIS-DAP debug interface, and the I2C bridge used by `daplink_bridge` / `daplink_flash` / `steami_config`. + +DAPLink consists of **two parts**: + +- **Bootloader** (first stage, flashed at `0x08000000`) — installed once at the factory, rarely updated. It provides the MAINTENANCE mode used to update the interface firmware. Updating the bootloader requires an external SWD probe and is not covered by these targets. +- **Interface firmware** (second stage, flashed at `0x08002000`) — the part that contains the I2C bridge, mass-storage, debug interface, and is updated routinely. This is what the `daplink-*` Makefile targets manage. + +```bash +make daplink-firmware # Clone steamicc/DAPLink and build stm32f103xb_steami32_if +make daplink-update # Refresh the DAPLink clone +make daplink-deploy # Flash DAPLink interface firmware (default: usb mass-storage) +make daplink-deploy-usb # Flash DAPLink interface firmware via MAINTENANCE volume +make daplink-clean # Clean DAPLink build artifacts +``` + +The DAPLink source is cloned from [steamicc/DAPLink](https://github.com/steamicc/DAPLink) into `.build/DAPLink/` (gitignored). A Python virtualenv is created automatically inside the clone for the progen build tool. + +**Maintenance mode:** to flash the DAPLink interface firmware, the board must be in maintenance mode. Power on the board with the RESET button held until a `MAINTENANCE` USB volume appears (instead of the usual `STeaMi` volume). The `make daplink-deploy-usb` target then copies the firmware to that volume and the board reboots automatically with the new interface firmware. + ## Notes * Keep implementations simple and readable diff --git a/Makefile b/Makefile index f374819c..246ceaa2 100644 --- a/Makefile +++ b/Makefile @@ -136,7 +136,9 @@ micropython-deploy-openocd: $(MPY_DIR) ## Flash MicroPython firmware via OpenOCD .PHONY: micropython-deploy-usb micropython-deploy-usb: $(MPY_DIR) ## Flash MicroPython firmware via DAPLink USB mass-storage - @$(PYTHON) scripts/deploy_usb.py $(STM32_DIR)/build-$(BOARD)/firmware.bin + @$(PYTHON) scripts/deploy_usb.py \ + --build-target micropython-firmware \ + $(STM32_DIR)/build-$(BOARD)/firmware.bin # --- Deprecated targets (ambiguous since DAPLink build is also planned) --- # Replaced by explicit micropython-* / daplink-* targets to avoid confusion @@ -191,6 +193,57 @@ micropython-clean: ## Clean MicroPython firmware build artifacts $(MAKE) -C $(STM32_DIR) BOARD=$(BOARD) clean; \ fi +# --- DAPLink firmware --- +# These targets manage the DAPLink **interface firmware** only (the second +# stage of DAPLink, flashed at 0x08002000). The bootloader (first stage, +# flashed at 0x08000000) is installed once at the factory and is not +# managed here. A future `daplink-deploy-bootloader` target could be added +# if needed, but it requires an external SWD probe and is rarely necessary. + +$(DAPLINK_DIR): + @echo "Cloning DAPLink into $(CURDIR)/$(DAPLINK_DIR)..." + @mkdir -p $(dir $(CURDIR)/$(DAPLINK_DIR)) + git clone --branch $(DAPLINK_BRANCH) $(DAPLINK_REPO) $(CURDIR)/$(DAPLINK_DIR) + +.PHONY: daplink-firmware +daplink-firmware: $(DAPLINK_DIR) ## Build DAPLink interface firmware for the STeaMi STM32F103 + @set -e + @if [ ! -d "$(DAPLINK_DIR)/venv" ]; then \ + echo "Setting up DAPLink Python virtualenv..."; \ + $(PYTHON) -m venv $(DAPLINK_DIR)/venv; \ + $(DAPLINK_DIR)/venv/bin/pip install -r $(DAPLINK_DIR)/requirements.txt; \ + fi + @echo "Building DAPLink target $(DAPLINK_TARGET)..." + cd $(CURDIR)/$(DAPLINK_DIR) && \ + ./venv/bin/python tools/progen_compile.py -t make_gcc_arm $(DAPLINK_TARGET) + @echo "DAPLink firmware ready: $(DAPLINK_BUILD_DIR)/$(DAPLINK_TARGET)_crc.bin" + +.PHONY: daplink-update +daplink-update: $(DAPLINK_DIR) ## Update the DAPLink clone + @set -e + @echo "Updating DAPLink..." + git -C $(CURDIR)/$(DAPLINK_DIR) fetch origin + git -C $(CURDIR)/$(DAPLINK_DIR) checkout $(DAPLINK_BRANCH) + git -C $(CURDIR)/$(DAPLINK_DIR) pull --ff-only + +.PHONY: daplink-deploy +daplink-deploy: daplink-deploy-usb ## Flash DAPLink interface firmware (default: usb mass-storage) + +.PHONY: daplink-deploy-usb +daplink-deploy-usb: $(DAPLINK_DIR) ## Flash DAPLink interface firmware via MAINTENANCE USB mass-storage + @echo "Note: the board must be in MAINTENANCE mode." + @echo "Power on the board with the RESET button held until the MAINTENANCE volume appears." + @echo "" + @$(PYTHON) scripts/deploy_usb.py --label MAINTENANCE \ + --build-target daplink-firmware \ + $(DAPLINK_BUILD_DIR)/$(DAPLINK_TARGET)_crc.bin + +.PHONY: daplink-clean +daplink-clean: ## Clean DAPLink firmware build artifacts + @if [ -d "$(DAPLINK_DIR)" ]; then \ + rm -rf $(DAPLINK_DIR)/projectfiles; \ + fi + # --- Hardware --- .PHONY: repl diff --git a/env.mk b/env.mk index 54f24d91..dc1aee5d 100644 --- a/env.mk +++ b/env.mk @@ -1,10 +1,18 @@ export PATH := $(CURDIR)/node_modules/.bin:$(PATH) PORT ?= /dev/ttyACM0 -# Firmware build configuration +BUILD_DIR ?= .build + +# MicroPython firmware build configuration MICROPYTHON_REPO ?= https://github.com/steamicc/micropython-steami.git MICROPYTHON_BRANCH ?= stm32-steami-rev1d-final BOARD ?= STEAM32_WB55RG -BUILD_DIR ?= .build MPY_DIR ?= $(BUILD_DIR)/micropython-steami STM32_DIR ?= $(MPY_DIR)/ports/stm32 + +# DAPLink firmware build configuration +DAPLINK_REPO ?= https://github.com/steamicc/DAPLink.git +DAPLINK_BRANCH ?= release_letssteam +DAPLINK_DIR ?= $(BUILD_DIR)/DAPLink +DAPLINK_TARGET ?= stm32f103xb_steami32_if +DAPLINK_BUILD_DIR ?= $(DAPLINK_DIR)/projectfiles/make_gcc_arm/$(DAPLINK_TARGET)/build diff --git a/scripts/deploy_usb.py b/scripts/deploy_usb.py index 93a2afda..d6aafe02 100644 --- a/scripts/deploy_usb.py +++ b/scripts/deploy_usb.py @@ -1,30 +1,34 @@ -"""Deploy MicroPython firmware to a STeaMi board via DAPLink USB mass-storage. +"""Deploy a firmware binary to a STeaMi board via DAPLink USB mass-storage. -Detects the STeaMi volume by its label across Linux, macOS, and Windows, +Detects the target volume by its label across Linux, macOS, and Windows, copies the firmware .bin to it, and lets DAPLink auto-reset the target. +The default label is ``STeaMi`` (normal mode, used for MicroPython firmware). +For DAPLink firmware updates, the board must be in maintenance mode (boot +with the RESET button held) and the volume label is ``MAINTENANCE``. + Usage: python scripts/deploy_usb.py path/to/firmware.bin + python scripts/deploy_usb.py --label MAINTENANCE path/to/daplink.bin """ +import argparse import os import platform import shutil import subprocess import sys -VOLUME_LABEL = "STeaMi" - -def find_steami_linux(): - """Find STeaMi mount point on Linux via findmnt. +def find_volume_linux(label): + """Find the mount point of a labelled volume on Linux via findmnt. - Returns the mount path, or ``None`` if the board is not mounted + Returns the mount path, or ``None`` if the volume is not mounted or ``findmnt`` is not available. """ try: result = subprocess.run( - ["findmnt", "-n", "-o", "TARGET", "-S", "LABEL=" + VOLUME_LABEL], + ["findmnt", "-n", "-o", "TARGET", "-S", "LABEL=" + label], capture_output=True, text=True, check=False, @@ -37,22 +41,22 @@ def find_steami_linux(): return None -def find_steami_macos(): - """Find STeaMi mount point on macOS. +def find_volume_macos(label): + """Find the mount point of a labelled volume on macOS. - Returns ``/Volumes/STeaMi`` if the board is mounted, or ``None``. + Returns ``/Volumes/