Ask a coding assistant for "a UART driver on STM32" and you get code. It looks right. It uses HAL_UART_Init(), it sets a baud rate, it even adds a callback. Then you build it and discover the assistant assumed a different HAL version, forgot that the clock tree was configured in CubeMX, wrote to a register that does not exist on your part number, and put a blocking transmit inside an interrupt handler.
The model is not the problem. The context is. The assistant has no idea which board you use, which build system, which coding rules, or how your team flashes and validates a binary. Every session starts from zero and you re-explain everything.
Skills solve this. A Skill is a folder of instructions the agent loads on its own when the task matches. Written once, versioned in Git, shared with the team. This article shows how to prepare an STM32 project so an agent can work in it, and how to write your first STM32 Skill.
What a Skill actually is
A Skill is a directory containing a SKILL.md file. The file has two parts: YAML frontmatter and a Markdown body.
---
name: stm32-peripheral-bringup
description: Bring up an STM32 peripheral (UART, SPI, I2C, ADC, TIM, DMA) on the
project board using the house HAL patterns. Use when the user asks to add,
configure, or debug a peripheral on the STM32 target.
---
# STM32 peripheral bring-up
## Workflow
1. Read `docs/board.md` for the exact part number, clock tree and pin map.
2. Check whether the peripheral is already declared in the `.ioc` file.
...
Only name and description are required. The description is the important one: it is the text the agent matches your request against when deciding whether to load the Skill. Vague descriptions never trigger. Say what the Skill does and when to use it.
The mechanism behind this is progressive disclosure, and it is what makes Skills cheap:
- Level 1 is the frontmatter. Always in context, a few dozen tokens per Skill. You can install twenty Skills without paying for them.
- Level 2 is the body. Loaded only when the description matches the task.
- Level 3 is bundled files.
references/for documentation the agent reads on demand,scripts/for executable code,assets/for templates. Read only when the body points at them.
That third level matters on embedded projects. A 200-page reference manual extract belongs in references/, not pasted into the body. A flash command belongs in scripts/flash.sh, because a script runs identically every time while an instruction gets interpreted.
Project Skills live in .claude/skills//SKILL.md and are committed with the repository. Personal ones live in ~/.claude/skills/. The format follows the open Agent Skills standard, so the same folder works across several agent tools.
Step 1: make the project machine-workable
Before writing a single Skill, make sure an agent can build, flash and test without asking you. This is the part most teams skip, and it is the part that determines whether the rest works.
Use a scriptable build. STM32CubeMX generates CMake projects. Take that path rather than a GUI-only workflow. An agent can run CMake and read the compiler output. It cannot click a toolbar.
cmake -B build -G Ninja -DCMAKE_TOOLCHAIN_FILE=cmake/gcc-arm-none-eabi.cmake
cmake --build build
Make flashing one command. Wrap it in a script so there is exactly one way to do it.
# scripts/flash.sh
STM32_Programmer_CLI -c port=SWD -w build/app.elf -rst
Make validation one command too. Host-side unit tests on the logic layer, plus an on-target smoke test driven over the debug probe or a serial console. Without a pass/fail signal the agent cannot close the loop, and you are back to reviewing code by eye.
Write the project memory file. CLAUDE.md at the repository root is the always-loaded context: board and exact part number, HAL version, what is generated and must never be hand-edited, the build and flash commands, the coding rules, and the definition of done.
# Project context
Target: STM32U585AI, B-U585I-IOT02A board, 160 MHz from PLL1.
HAL: STM32Cube_FW_U5 V1.6.0. Do not use API from other Cube versions.
## Generated code
`Core/` and `Drivers/` are generated by CubeMX from `app.ioc`.
Only edit inside `USER CODE BEGIN/END` blocks. Never touch anything else.
## Rules
No dynamic allocation after init. No blocking calls in ISRs.
DMA buffers: 32-byte aligned, in SRAM1, never in DTCM.
Every peripheral init returns a status. No silent failures.
## Definition of done
Builds with -Werror, unit tests pass, flashes and runs the smoke test.
An hour spent on this file removes the same twenty corrections from every future session.
Step 2: write the first Skill
Start with the task you repeat most. On STM32 projects that is usually peripheral bring-up, because it is mechanical, error-prone, and follows house conventions the model cannot guess.
.claude/skills/stm32-peripheral-bringup/
├── SKILL.md
├── references/
│ ├── clock-tree.md
│ └── dma-request-map.md
└── scripts/
└── check_pin_conflicts.py
The body stays short and imperative. State what to do, not why.
# STM32 peripheral bring-up
## Workflow
1. Read `docs/board.md`. Confirm part number and available pins.
2. Verify the peripheral instance exists on this part. If unsure, stop and ask.
3. Check pin conflicts: `python scripts/check_pin_conflicts.py `.
4. If the peripheral is not in `app.ioc`, tell the user to add it in CubeMX
and regenerate. Do not hand-write generated init code.
5. Write the application layer in `Core/Src/app/` following the pattern in
`Core/Src/app/uart_service.c`.
6. Clock configuration: see `references/clock-tree.md`. Never assume a
peripheral clock frequency, derive it.
7. For DMA, read `references/dma-request-map.md` before choosing a stream.
8. Build, flash, run the smoke test. Report the actual output.
## Rules
- Return `HAL_StatusTypeDef` or the project error enum. No void init functions.
- No `HAL_Delay()` outside init code.
- Every register write that is not in the reference manual excerpt is a
hallucination. Do not guess bit names.
That last rule is worth keeping. It converts a known failure mode into an explicit instruction.
Step 3: test the Skill like code
A Skill is a prompt, and prompts regress. Test it the way you would test a driver.
Run it on three cases: one it was designed for, one adjacent, one it should refuse. Use a fresh session each time, since a session that already knows the answer proves nothing. Watch for two failures. If the Skill does not trigger, the description is too vague. If it triggers and the output still ignores your conventions, the body is too long or too narrative, and the rules are buried.
Keep the body tight. Once a Skill loads it stays in context for the rest of the conversation, so every line is a recurring cost. Move detail into references/ and let the agent fetch it when needed.
Step 4: build the set, then share it
One Skill is a convenience. A set is a workflow. A realistic STM32 repository ends up with something like:
stm32-peripheral-bringupfor configuration and init codestm32-build-flash-testfor the deterministic loop, wrapping the scriptsstm32-fsdto turn a one-line request into a functional specification before any code is writtenstm32-reviewfor the hardware-specific review pass: register misuse, ISR safety, DMA coherency, timing assumptions
They live in the repository, they go through code review, they get versioned with the firmware they describe. A new engineer clones the repo and the agent already knows the house rules. That is the real return: the conventions stop living in the heads of two senior people.
Where Skills fit next to everything else
Skills are one layer. CLAUDE.md holds context that is always relevant. Skills hold procedures loaded on demand. MCP servers give the agent live access to external systems, a datasheet corpus, an issue tracker, a debug probe. Agents and subagents orchestrate the whole thing, one writing against the specification while another reviews.
The layers only pay off together, and only on top of a project that is scriptable end to end. Get the build, flash and test commands right first. Everything else is built on that loop.
Going further
Our three-day course AI-Assisted Embedded Development (AI1) covers this end to end on real hardware: prompting for embedded problems, spec-driven development and the FSD, Claude Code and Codex CLI configuration, MCP servers, agent design, writing and sharing Skills, validating AI-generated firmware, and the IP and data-handling rules your company needs before any of this reaches production code.
Sessions run online in both European and US time zones. See the course page for the next dates, or request a session on your own dates, online or on-site.