Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
55 changes: 55 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,58 @@ jobs:
"steami-micropython-firmware-${VERSION}.hex" \
"steami-micropython-firmware-${VERSION}.bin" \
--clobber

daplink-firmware:
name: Build and attach DAPLink firmware
needs: release
if: needs.release.outputs.new-release-published == 'true'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Generate token
id: generate_token
uses: tibdex/github-app-token@v2
with:
app_id: ${{ secrets.RELEASE_APP_ID }}
private_key: ${{ secrets.RELEASE_APP_SECRET }}

- uses: actions/checkout@v4
with:
ref: v${{ needs.release.outputs.new-release-version }}

- name: Install build tools
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends ccache ninja-build python3-venv

- name: Clone DAPLink (needed for venv cache key)
run: make .build/DAPLink

- name: Cache gcc-arm-none-eabi 10.3-2021.10
uses: actions/cache@v4
with:
path: .build/gcc-arm-none-eabi-10.3-2021.10
key: gcc-arm-none-eabi-10.3-2021.10-linux-x86_64

- name: Cache DAPLink Python venv
uses: actions/cache@v4
with:
path: .build/DAPLink/venv
key: daplink-venv-${{ runner.os }}-${{ hashFiles('.build/DAPLink/requirements.txt') }}

- name: Build DAPLink firmware
run: make daplink-firmware

- name: Attach DAPLink firmware to release
env:
GH_TOKEN: ${{ steps.generate_token.outputs.token }}
run: |
VERSION="v${{ needs.release.outputs.new-release-version }}"
BUILD_DIR=".build/DAPLink/projectfiles/make_gcc_arm/stm32f103xb_steami32_if/build"
cp "${BUILD_DIR}/stm32f103xb_steami32_if_crc.bin" "steami-daplink-firmware-${VERSION}.bin"
cp "${BUILD_DIR}/stm32f103xb_steami32_if_crc.hex" "steami-daplink-firmware-${VERSION}.hex"
gh release upload "$VERSION" \
"steami-daplink-firmware-${VERSION}.bin" \
"steami-daplink-firmware-${VERSION}.hex" \
--clobber
33 changes: 29 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,9 @@ 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`; `make daplink-firmware` ignores the system toolchain and downloads its own pinned 10.3-2021.10 build)
* `ccache` and `ninja-build` (for `make daplink-firmware`)
* For `make daplink-firmware`: Linux x86_64, Linux aarch64, or Intel macOS only (the pinned toolchain is not published for Apple Silicon or Windows — use the dev container on those platforms)
* `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]"`)
Expand All @@ -129,7 +131,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*)
Expand Down Expand Up @@ -191,9 +193,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
Expand All @@ -215,6 +219,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
Expand Down
91 changes: 87 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -145,7 +147,15 @@ micropython-deploy-usb: $(MPY_DIR) ## Flash MicroPython firmware via DAPLink USB
define DEPRECATED_FIRMWARE
@echo "Error: 'make $(1)' is ambiguous. Use one of:"; \
echo " make micropython-$(2) (MicroPython firmware)"; \
echo " make daplink-$(2) (DAPLink firmware, see #377)"; \
echo " make daplink-$(2) (DAPLink firmware)"; \
exit 1
endef

# Variant for short names whose DAPLink counterpart does not exist yet
# (daplink-deploy-pyocd / daplink-deploy-openocd are tracked in #388).
define DEPRECATED_MICROPYTHON_ONLY
@echo "Error: 'make $(1)' has been renamed. Use:"; \
echo " make micropython-$(2) (MicroPython firmware)"; \
exit 1
endef

Expand All @@ -159,9 +169,9 @@ firmware-clean:
deploy:
$(call DEPRECATED_FIRMWARE,deploy,deploy)
deploy-pyocd:
$(call DEPRECATED_FIRMWARE,deploy-pyocd,deploy-pyocd)
$(call DEPRECATED_MICROPYTHON_ONLY,deploy-pyocd,deploy-pyocd)
deploy-openocd:
$(call DEPRECATED_FIRMWARE,deploy-openocd,deploy-openocd)
$(call DEPRECATED_MICROPYTHON_ONLY,deploy-openocd,deploy-openocd)
deploy-usb:
$(call DEPRECATED_FIRMWARE,deploy-usb,deploy-usb)

Expand Down Expand Up @@ -191,6 +201,79 @@ 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)

$(DAPLINK_GCC_DIR)/bin/arm-none-eabi-gcc:
@set -e
@if [ -z "$(DAPLINK_GCC_ARCHIVE)" ]; then \
echo "Error: no prebuilt gcc-arm-none-eabi $(DAPLINK_GCC_VERSION) for $(DAPLINK_GCC_HOST_OS)/$(DAPLINK_GCC_HOST_ARCH)."; \
echo "Supported by this target: Linux x86_64, Linux aarch64, macOS Intel."; \
echo "Other platforms: install the toolchain manually and override DAPLINK_GCC_DIR,"; \
echo "or build inside the dev container."; \
exit 1; \
fi
@echo "Downloading gcc-arm-none-eabi $(DAPLINK_GCC_VERSION) for $(DAPLINK_GCC_HOST_OS)/$(DAPLINK_GCC_HOST_ARCH)..."
@mkdir -p $(BUILD_DIR)
curl -fL -o $(BUILD_DIR)/gcc-arm-none-eabi.tar.bz2 "$(DAPLINK_GCC_URL)"
tar -xjf $(BUILD_DIR)/gcc-arm-none-eabi.tar.bz2 -C $(BUILD_DIR)
rm -f $(BUILD_DIR)/gcc-arm-none-eabi.tar.bz2

# Sentinel: re-runs pip install whenever DAPLink's requirements.txt changes
# (e.g. after `make daplink-update`). The order-only prerequisite on
# $(DAPLINK_DIR) guarantees the clone happens first on a fresh checkout, so
# requirements.txt exists by the time make checks it.
$(DAPLINK_DIR)/venv/.installed: $(DAPLINK_DIR)/requirements.txt | $(DAPLINK_DIR)
@echo "Setting up DAPLink Python virtualenv..."
@if [ ! -x "$(DAPLINK_DIR)/venv/bin/python" ]; then \
$(PYTHON) -m venv $(DAPLINK_DIR)/venv; \
fi
$(DAPLINK_DIR)/venv/bin/pip install -r $(DAPLINK_DIR)/requirements.txt
@touch $@
Comment on lines +231 to +241
Copy link

Copilot AI Apr 13, 2026

Choose a reason for hiding this comment

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

$(DAPLINK_DIR)/venv/.installed depends on $(DAPLINK_DIR)/requirements.txt, but there’s no rule that produces requirements.txt and it won’t exist until after $(DAPLINK_DIR) is cloned. With parallel builds (make -j) or different prerequisite evaluation order on a fresh checkout, this can fail with “No rule to make target '.../requirements.txt'”. Add an explicit (preferably order-only) dependency on $(DAPLINK_DIR) for the .installed sentinel (and/or for $(DAPLINK_DIR)/requirements.txt) so the clone always happens before venv setup.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in e8e001f: added an order-only prerequisite | $(DAPLINK_DIR) on the .installed sentinel so the clone runs before make checks requirements.txt, even on a fresh checkout or under make -j.


.PHONY: daplink-firmware
daplink-firmware: $(DAPLINK_DIR) $(DAPLINK_GCC_DIR)/bin/arm-none-eabi-gcc $(DAPLINK_DIR)/venv/.installed ## Build DAPLink interface firmware for the STeaMi STM32F103
@echo "Building DAPLink target $(DAPLINK_TARGET) with gcc-arm-none-eabi $(DAPLINK_GCC_VERSION)..."
cd $(CURDIR)/$(DAPLINK_DIR) && \
PATH="$(CURDIR)/$(DAPLINK_GCC_DIR)/bin:$(CURDIR)/$(DAPLINK_DIR)/venv/bin:$$PATH" \
./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
Expand Down
38 changes: 36 additions & 2 deletions env.mk
Original file line number Diff line number Diff line change
@@ -1,10 +1,44 @@
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

# DAPLink requires gcc-arm-none-eabi 10.3-2021.10. System toolchains >= 11.3
# produce code that overflows m_text (see DAPLink docs/DEVELOPERS-GUIDE.md and
# ARMmbed/DAPLink#1043). The toolchain is downloaded once into BUILD_DIR.
#
# ARM publishes 10.3-2021.10 binaries for: x86_64 Linux, aarch64 Linux, and
# Intel macOS. Apple Silicon and Windows are NOT supported by this target —
# users on those platforms must install the toolchain manually and override
# DAPLINK_GCC_DIR / DAPLINK_GCC_URL, or build inside the dev container.
DAPLINK_GCC_VERSION ?= 10.3-2021.10
DAPLINK_GCC_DIR ?= $(BUILD_DIR)/gcc-arm-none-eabi-$(DAPLINK_GCC_VERSION)

DAPLINK_GCC_HOST_OS := $(shell uname -s)
DAPLINK_GCC_HOST_ARCH := $(shell uname -m)
ifeq ($(DAPLINK_GCC_HOST_OS),Linux)
ifeq ($(DAPLINK_GCC_HOST_ARCH),x86_64)
DAPLINK_GCC_ARCHIVE ?= gcc-arm-none-eabi-$(DAPLINK_GCC_VERSION)-x86_64-linux.tar.bz2
else ifeq ($(DAPLINK_GCC_HOST_ARCH),aarch64)
DAPLINK_GCC_ARCHIVE ?= gcc-arm-none-eabi-$(DAPLINK_GCC_VERSION)-aarch64-linux.tar.bz2
endif
else ifeq ($(DAPLINK_GCC_HOST_OS),Darwin)
ifeq ($(DAPLINK_GCC_HOST_ARCH),x86_64)
DAPLINK_GCC_ARCHIVE ?= gcc-arm-none-eabi-$(DAPLINK_GCC_VERSION)-mac.tar.bz2
endif
endif
DAPLINK_GCC_URL ?= https://developer.arm.com/-/media/Files/downloads/gnu-rm/$(DAPLINK_GCC_VERSION)/$(DAPLINK_GCC_ARCHIVE)
Loading
Loading