Merge pull request 'add stm32 dev template' (#36) from develop into release-25.11
All checks were successful
Deploy docs / build-and-deploy (push) Successful in 5s

Reviewed-on: #36
This commit is contained in:
sid 2026-05-07 18:59:06 +02:00
commit d31510c64c
8 changed files with 322 additions and 0 deletions

View file

@ -289,6 +289,10 @@
path = ./templates/dev/rs-hello; path = ./templates/dev/rs-hello;
description = "Rust hello world template."; description = "Rust hello world template.";
}; };
stm32-blink = {
path = ./templates/dev/esp-blink;
description = "STM32G4 blink template with libopencm3.";
};
}; };
}; };
} }

View file

@ -0,0 +1 @@
use flake

5
templates/dev/stm32-blink/.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
.cache/
.direnv/
build/
compile_commands.json
result

View file

@ -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)

View file

@ -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.

View file

@ -0,0 +1,6 @@
#!/usr/bin/env bash
set -euo pipefail
make clean
mkdir -p build
bear -- make

View file

@ -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++"
];
};
};
};
};
}
);
}

View file

@ -0,0 +1,50 @@
#include <libopencm3/cm3/systick.h>
#include <libopencm3/stm32/gpio.h>
#include <libopencm3/stm32/rcc.h>
/* 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;
}