From 93c8d40a988eddbe5dbfc51c1dac1f862311d866 Mon Sep 17 00:00:00 2001 From: sid Date: Thu, 7 May 2026 18:57:46 +0200 Subject: [PATCH] add stm32 dev template --- flake.nix | 4 + templates/dev/stm32-blink/.envrc | 1 + templates/dev/stm32-blink/.gitignore | 5 + templates/dev/stm32-blink/Makefile | 67 +++++++++++ templates/dev/stm32-blink/README.md | 28 +++++ templates/dev/stm32-blink/build.sh | 6 + templates/dev/stm32-blink/flake.nix | 161 +++++++++++++++++++++++++++ templates/dev/stm32-blink/src/main.c | 50 +++++++++ 8 files changed, 322 insertions(+) create mode 100644 templates/dev/stm32-blink/.envrc create mode 100644 templates/dev/stm32-blink/.gitignore create mode 100644 templates/dev/stm32-blink/Makefile create mode 100644 templates/dev/stm32-blink/README.md create mode 100755 templates/dev/stm32-blink/build.sh create mode 100644 templates/dev/stm32-blink/flake.nix create mode 100644 templates/dev/stm32-blink/src/main.c diff --git a/flake.nix b/flake.nix index 2c658d4..6c60c07 100644 --- a/flake.nix +++ b/flake.nix @@ -289,6 +289,10 @@ path = ./templates/dev/rs-hello; description = "Rust hello world template."; }; + stm32-blink = { + path = ./templates/dev/esp-blink; + description = "STM32G4 blink template with libopencm3."; + }; }; }; } diff --git a/templates/dev/stm32-blink/.envrc b/templates/dev/stm32-blink/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/templates/dev/stm32-blink/.envrc @@ -0,0 +1 @@ +use flake diff --git a/templates/dev/stm32-blink/.gitignore b/templates/dev/stm32-blink/.gitignore new file mode 100644 index 0000000..5f20868 --- /dev/null +++ b/templates/dev/stm32-blink/.gitignore @@ -0,0 +1,5 @@ +.cache/ +.direnv/ +build/ +compile_commands.json +result diff --git a/templates/dev/stm32-blink/Makefile b/templates/dev/stm32-blink/Makefile new file mode 100644 index 0000000..2a03551 --- /dev/null +++ b/templates/dev/stm32-blink/Makefile @@ -0,0 +1,67 @@ +PREFIX ?= arm-none-eabi- +CC = $(PREFIX)gcc +OBJCOPY = $(PREFIX)objcopy +SIZE = $(PREFIX)size + +PNAME := blink +BUILD := build + +# libopencm3 is provided by the Nix shell +OPENCM3_DIR ?= "" + +SRCS := src/main.c +OBJS := $(addprefix $(BUILD)/, $(SRCS:.c=.o)) + +# STM32G4 +CPU_FLAGS := -mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 + +CFLAGS := $(CPU_FLAGS) \ + -DSTM32G4 \ + -Os -Wall -Wextra \ + -ffunction-sections -fdata-sections \ + -I$(OPENCM3_DIR)/include + +LDFLAGS := $(CPU_FLAGS) \ + -nostartfiles \ + -Wl,--gc-sections \ + -L$(OPENCM3_DIR)/lib \ + -lopencm3_stm32g4 + +ELF := $(BUILD)/$(PNAME).elf +BIN := $(BUILD)/$(PNAME).bin +HEX := $(BUILD)/$(PNAME).hex + +.PHONY: all clean flash size + +all: $(BIN) $(HEX) size + +$(BUILD)/%.o: %.c + @mkdir -p $(@D) + $(CC) $(CFLAGS) -c $< -o $@ + +LDSCRIPT := $(BUILD)/stm32g474xe.ld + +$(LDSCRIPT): + @mkdir -p $(@D) + $(CC) -I$(OPENCM3_DIR)/include $(shell python3 $(OPENCM3_DIR)/scripts/genlink.py $(OPENCM3_DIR)/ld/devices.data stm32g474xe DEFS) -P -E $(OPENCM3_DIR)/ld/linker.ld.S -o $@ + +$(ELF): $(OBJS) $(LDSCRIPT) + $(CC) $(OBJS) $(LDFLAGS) -T$(LDSCRIPT) -o $@ + +$(BIN): $(ELF) + $(OBJCOPY) -O binary $< $@ + +$(HEX): $(ELF) + $(OBJCOPY) -O ihex $< $@ + +size: $(ELF) + $(SIZE) $< + +flash: $(BIN) + openocd \ + -f interface/stlink.cfg \ + -f target/stm32g4x.cfg \ + -c "program $(BIN) verify reset exit 0x08000000" + +clean: + rm -rf $(BUILD) diff --git a/templates/dev/stm32-blink/README.md b/templates/dev/stm32-blink/README.md new file mode 100644 index 0000000..0da9414 --- /dev/null +++ b/templates/dev/stm32-blink/README.md @@ -0,0 +1,28 @@ +# STM32G4 blink template + +Blinks the LD2 LED (PA5) on a [Nucleo-G474RE](https://www.st.com/en/evaluation-tools/nucleo-g474re.html) using [libopencm3](https://github.com/libopencm3/libopencm3). + +Set `BLINK_PORT` / `BLINK_PIN` for your board in [`src/main.c`](./src/main.c). + +## Toolchain + +The Nix dev shell provides: + +- `arm-none-eabi-gcc` (via `gcc-arm-embedded`) +- `openocd` for flashing and debugging +- `stlink` utilities (`st-flash`, `st-info`) +- `libopencm3` built for `stm32/g4` (exposed as `$LIBOPENCM3_DIR`) + +## Build + +```bash +make +``` + +## Flash + +```bash +make flash +``` + +This uses OpenOCD with the built-in ST-Link interface config. diff --git a/templates/dev/stm32-blink/build.sh b/templates/dev/stm32-blink/build.sh new file mode 100755 index 0000000..ae17afe --- /dev/null +++ b/templates/dev/stm32-blink/build.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -euo pipefail + +make clean +mkdir -p build +bear -- make diff --git a/templates/dev/stm32-blink/flake.nix b/templates/dev/stm32-blink/flake.nix new file mode 100644 index 0000000..26b4bc7 --- /dev/null +++ b/templates/dev/stm32-blink/flake.nix @@ -0,0 +1,161 @@ +{ + description = "A blink template for STM32G4"; + + inputs = { + nixpkgs.url = "nixpkgs/nixpkgs-unstable"; + utils.url = "github:numtide/flake-utils"; + libopencm3-src = { + url = "github:libopencm3/libopencm3"; + flake = false; + }; + pre-commit-hooks = { + url = "github:cachix/pre-commit-hooks.nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = + { + self, + nixpkgs, + utils, + libopencm3-src, + pre-commit-hooks, + ... + }: + { + overlays.default = final: prev: { + libopencm3-stm32 = final.stdenvNoCC.mkDerivation { + pname = "libopencm3-stm32"; + version = "latest"; + + src = libopencm3-src; + + postPatch = '' + patchShebangs scripts + ''; + + nativeBuildInputs = [ + final.gcc-arm-embedded + final.python3 + final.gnumake + ]; + + enableParallelBuilding = true; + + makeFlags = [ + "PREFIX=arm-none-eabi-" + ]; + + TARGETS = "stm32/f0 stm32/f1 stm32/f2 stm32/f3 stm32/f4 stm32/f7 stm32/l0 stm32/l1 stm32/l4 stm32/g0 stm32/g4 stm32/h7 stm32/u5"; + + installPhase = '' + runHook preInstall + + mkdir -p $out + cp -r include lib mk scripts ld $out/ + + patchShebangs $out/scripts + + runHook postInstall + ''; + + meta = with final.lib; { + description = "Open source ARM Cortex-M microcontroller library (STM32 targets)"; + homepage = "http://libopencm3.org/"; + license = licenses.lgpl3Plus; + platforms = platforms.all; + }; + }; + }; + } + // utils.lib.eachDefaultSystem ( + system: + let + pkgs = import nixpkgs { + inherit system; + overlays = [ self.overlays.default ]; + }; + pname = "blink"; + version = "0.1.0"; + in + { + packages = { + inherit (pkgs) libopencm3-stm32; + default = pkgs.stdenvNoCC.mkDerivation { + inherit pname version; + src = pkgs.lib.cleanSource ./.; + + nativeBuildInputs = with pkgs; [ + gcc-arm-embedded + gnumake + python3 + ]; + + makeFlags = [ + "OPENCM3_DIR=${pkgs.libopencm3-stm32}" + "PREFIX=arm-none-eabi-" + ]; + + installPhase = '' + runHook preInstall + mkdir -p $out/bin + cp build/${pname}.elf $out/bin/ + cp build/${pname}.bin $out/bin/ + cp build/${pname}.hex $out/bin/ + runHook postInstall + ''; + }; + }; + + devShells.default = + let + # FIXME: 'gnu/stubs-32.h' file not found + clangdWrapper = pkgs.writeShellScriptBin "clangd" '' + exec ${pkgs.clang-tools}/bin/clangd \ + --query-driver=${pkgs.gcc-arm-embedded}/bin/arm-none-eabi-* \ + "$@" + ''; + in + pkgs.mkShell { + name = "stm32-blink"; + packages = self.packages.${system}.default.nativeBuildInputs ++ [ + clangdWrapper + + pkgs.bear + pkgs.openocd + pkgs.stlink + ]; + OPENCM3_DIR = pkgs.libopencm3-stm32; + PREFIX = "arm-none-eabi-"; + }; + + formatter = + let + inherit (self.checks.${system}.pre-commit-check.config) package configFile; + script = '' + ${pkgs.lib.getExe package} run --all-files --config ${configFile} + ''; + in + pkgs.writeShellScriptBin "pre-commit-run" script; + + checks = { + build-packages = pkgs.linkFarm "flake-packages-${system}" self.packages.${system}; + + pre-commit-check = pre-commit-hooks.lib.${system}.run { + src = ./.; + hooks = { + nixfmt.enable = true; + clang-format = { + enable = true; + types_or = pkgs.lib.mkForce [ + "c" + "c++" + ]; + }; + }; + }; + }; + } + ); +} diff --git a/templates/dev/stm32-blink/src/main.c b/templates/dev/stm32-blink/src/main.c new file mode 100644 index 0000000..4ed6e04 --- /dev/null +++ b/templates/dev/stm32-blink/src/main.c @@ -0,0 +1,50 @@ +#include +#include +#include + +/* Nucleo-G474RE: LD2 is on PA5 */ +#define BLINK_PORT GPIOA +#define BLINK_PIN GPIO5 +#define BLINK_PERIOD_MS 1000 + +static volatile uint32_t s_ticks = 0; + +void sys_tick_handler(void) { s_ticks++; } + +static void delay_ms(uint32_t ms) { + uint32_t until = s_ticks + ms; + while (s_ticks < until) + ; +} + +static void clock_setup(void) { + rcc_clock_setup_pll(&rcc_hsi_configs[RCC_CLOCK_3V3_170MHZ]); +} + +static void systick_setup(void) { + /* 1 ms tick at 170 MHz core clock */ + systick_set_reload(rcc_ahb_frequency / 1000 - 1); + systick_set_clocksource(STK_CSR_CLKSOURCE_AHB); + systick_counter_enable(); + systick_interrupt_enable(); +} + +static void gpio_setup(void) { + rcc_periph_clock_enable(RCC_GPIOA); + gpio_mode_setup(BLINK_PORT, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, BLINK_PIN); + gpio_set_output_options(BLINK_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_LOW, + BLINK_PIN); +} + +int main(void) { + clock_setup(); + systick_setup(); + gpio_setup(); + + while (1) { + gpio_toggle(BLINK_PORT, BLINK_PIN); + delay_ms(BLINK_PERIOD_MS); + } + + return 0; +} -- 2.51.2