Embedded Stm32 logo

Embedded Stm32

Organization
Mindrally
embedded-stm32

Best practices for embedded C/C++ development on STM32 microcontrollers using the HAL, covering peripherals, DMA, interrupts, memory constraints, and hardware-focused testing. Use when writing STM32 HAL code, configuring peripherals generated by STM32CubeMX, working with interrupts or DMA, debugging with SWD/JTAG, managing memory-constrained firmware, or writing hardware-in-the-loop or host-build tests for embedded C.

Overview

PublisherMindrally
Repositoryskills
Skill nameembedded-stm32
Stars
259
Forks
41
Bundled files
Instructions only
LicenseApache-2.0
Links
  • Markdown instructions

    A SKILL.md file the model loads on demand, so it only costs tokens when a request actually matches.

  • Works with any LLM

    AI skills are plain Markdown, not provider-specific code, so this works with GPT, Claude, Gemini, Grok, or a local model.

  • Self-contained

    Everything the model needs lives in the instructions — no extra files to sync.

  • Open source

    Published by Mindrally on GitHub. Read the source before you install it.

Installation

Install the Embedded Stm32 AI skill in TypingMind to use it with any LLM, or drop it into another agent that reads SKILL.md.

1

Install in TypingMind

TypingMind installs a skill straight from its GitHub folder — it reads SKILL.md, bundles the resource files, and stores the result locally.

  1. Open the app and go to Plugins → Skills.
  2. Choose "Install from GitHub".
  3. Paste the skill folder URL below and confirm.
  4. Enable the skill in any chat where you want it available.
Plugins → Skills → Add skill → From GitHub URL, then paste the folder URL and press Continue.
2

Install in another agent

Any agent that reads the Agent Skills format can use this skill — copy the folder into that agent's skills directory.

Claude Code — .claude/skills
git clone --depth 1 https://github.com/Mindrally/skills.git /tmp/skills
mkdir -p .claude/skills
cp -r /tmp/skills/embedded-stm32 .claude/skills/embedded-stm32
Restart Claude Code after copying so it picks up the new skill.

Use it in TypingMind

Enable Embedded Stm32 in any TypingMind chat and the model takes it from there. Its name and description sit in the system prompt, and the moment a request matches, the model loads the full instructions itself — you never invoke it by hand, and it costs no tokens until it is actually used.

The model loads Embedded Stm32 on its own as soon as a request matches it.

Works with any AI model

AI skills are plain Markdown instructions rather than provider-specific code, so Embedded Stm32 is not tied to the model it was written for. Install it once in TypingMind and use it with GPT-5, Claude, Gemini, Grok, DeepSeek, Mistral, Llama, or a local model you run yourself — all on your own API keys.

  • Loaded only when it is needed

    The system prompt carries just the name and description. The instructions are fetched on the first matching request, so an idle skill costs nothing.

  • Switch models mid-chat

    Because the skill is instructions rather than code, changing model does not break it — the next model reads the same SKILL.md.

Skill instructions

This is the SKILL.md content the model loads. Read it before installing — a skill is instructions your model will follow.

Embedded STM32 / HAL Development

This skill covers firmware development for STM32 microcontrollers using the STM32 HAL, including project structure, peripheral and interrupt handling, memory and timing constraints, and testing strategies for hardware-focused code.

Workflow for STM32 HAL Firmware Development

  1. Configure the hardware in CubeMX — Set up clocks, pins, and peripherals in the .ioc file; generate the HAL initialization code.
  2. Separate generated and hand-written code — Keep CubeMX-generated files untouched except in their designated USER CODE BEGIN/END blocks; put application logic in separate files.
  3. Initialize peripherals once — Centralize HAL_*_Init() calls in main()/MX_*_Init() and avoid ad hoc reconfiguration elsewhere in the code.
  4. Write interrupt handlers — Keep ISRs (HAL_*_Callback functions, EXTI/DMA/timer IRQ handlers) short; set flags or push to a queue and defer real work to the main loop or an RTOS task.
  5. Use DMA for high-throughput I/O — Configure DMA for UART/SPI/I2C/ADC transfers that would otherwise block or burn CPU cycles on polling.
  6. Add timeouts everywhere — Every blocking HAL call and every hardware wait loop needs a timeout and an explicit error path.
  7. Test in layers — Unit-test pure logic on a host build (no hardware dependency), then validate peripheral behavior with hardware-in-the-loop tests.
  8. Flash and debug — Use SWD/JTAG (ST-Link, OpenOCD, or J-Link) with a debugger, plus rate-limited serial logs, to verify behavior on real hardware.

Project Structure

  • Keep board support (pin/clock configuration), drivers, middleware (e.g., FreeRTOS, USB stack), application logic, and tests in clearly separated directories.
  • Isolate CubeMX-generated or vendor code (Core/Src/main.c, Drivers/) from hand-written application code so regenerating with CubeMX doesn't clobber custom logic — only edit inside /* USER CODE BEGIN */ ... /* USER CODE END */ markers in generated files.
  • Put hardware access behind narrow interfaces (e.g., a motor_driver.h with motor_set_speed()) so application logic can be unit-tested on a host build without real peripherals.
  • Document the clock tree (SYSCLK, HCLK, PCLK1/PCLK2 and their max rates), pin mappings, peripheral ownership, and interrupt priority assignments in a single reference (README or header comments) — this is the first thing a debugging session needs.

STM32 HAL and Peripherals

  • Initialize each peripheral in exactly one place; avoid scattering HAL_*_Init()/HAL_*_MspInit() calls or ad hoc register writes across multiple files.
  • Always check the return value of HAL calls (HAL_OK, HAL_ERROR, HAL_BUSY, HAL_TIMEOUT) and handle timeout/error cases explicitly — a silently ignored HAL_TIMEOUT from HAL_UART_Transmit is a classic source of "it works on my desk" bugs.
  • Keep blocking HAL calls (HAL_UART_Transmit, HAL_I2C_Master_Receive without _IT/_DMA suffix) out of time-critical paths like control loops or ISRs.
  • Use DMA for high-throughput UART, SPI, I2C, ADC, or timer-capture paths when the CPU shouldn't spend cycles byte-shuffling.
  • Document buffer ownership and lifetime for every DMA operation — a buffer being read by DMA must not be modified or freed by the CPU until the transfer-complete callback fires.
  • Use volatile only for memory shared with an ISR or memory-mapped hardware registers; volatile is not a substitute for a proper memory barrier or critical section when data is shared between contexts.

Example: Non-Blocking UART Receive with DMA and Idle-Line Detection

c
/* USER CODE BEGIN Includes */
#include "main.h"
#include <string.h>

#define RX_BUF_SIZE 128

static uint8_t rx_buf[RX_BUF_SIZE];
static volatile uint8_t rx_ready = 0;
static volatile uint16_t rx_len = 0;

extern UART_HandleTypeDef huart2;
extern DMA_HandleTypeDef hdma_usart2_rx;
/* USER CODE END Includes */

/* USER CODE BEGIN 2 */
void app_uart_start_receive(void)
{
    /* Enable idle-line interrupt so a packet of unknown length completes
     * the transfer without waiting for the buffer to fill. */
    __HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE);
    if (HAL_UART_Receive_DMA(&huart2, rx_buf, RX_BUF_SIZE) != HAL_OK) {
        Error_Handler();
    }
}
/* USER CODE END 2 */

/* USER CODE BEGIN 4 */
void USART2_IRQHandler(void)
{
    if (__HAL_UART_GET_FLAG(&huart2, UART_FLAG_IDLE)) {
        __HAL_UART_CLEAR_IDLEFLAG(&huart2);

        HAL_UART_DMAStop(&huart2);
        rx_len = RX_BUF_SIZE - __HAL_DMA_GET_COUNTER(&hdma_usart2_rx);
        rx_ready = 1; /* Deferred: main loop processes the packet. */

        /* Re-arm for the next packet. */
        HAL_UART_Receive_DMA(&huart2, rx_buf, RX_BUF_SIZE);
        return;
    }
    HAL_UART_IRQHandler(&huart2);
}
/* USER CODE END 4 */

/* Main loop excerpt: heavy work deferred out of the ISR. */
void app_main_loop(void)
{
    if (rx_ready) {
        rx_ready = 0;
        uint16_t len = rx_len;
        /* Copy out or parse rx_buf[0..len) here. Do NOT touch rx_buf
         * again until this point, since DMA may already be refilling it. */
        (void)len;
    }
}

Interrupts and Concurrency

  • Keep ISRs short and deterministic — set a flag, copy a small fixed-size value, or push to a lock-free queue, then return.
  • Defer heavy work (parsing, computation, logging) from interrupts to the main loop, an RTOS task, or an event queue processed outside interrupt context.
  • Protect data shared between an ISR and the main context with critical sections (__disable_irq()/__enable_irq(), or taskENTER_CRITICAL() under an RTOS), atomics, or lock-free queues — never assume a multi-byte read/write is atomic.
  • Avoid dynamic allocation (malloc/new) inside interrupt handlers; allocation is neither deterministic nor guaranteed reentrant-safe.
  • Make interrupt priority decisions explicit and documented (NVIC_SetPriority) — a mis-prioritized interrupt can starve time-critical peripherals or violate FreeRTOS's configMAX_SYSCALL_INTERRUPT_PRIORITY constraint.

Memory and Timing

  • Avoid heap allocation in firmware unless the project explicitly allows and budgets for it — prefer static allocation and fixed-size buffers/pools.
  • Check stack usage for both ISRs and RTOS tasks (link-time stack usage reports, or uxTaskGetStackHighWaterMark() under FreeRTOS) — stack overflow on embedded targets typically corrupts silently.
  • Keep lookup tables const so the linker places them in flash instead of consuming scarce RAM.
  • Use fixed-width integer types (uint8_t, int32_t, uint32_t) for anything hardware-facing (register values, protocol fields, buffer sizes) instead of int/long, whose width isn't guaranteed.
  • Add a timeout to every hardware wait — polling a status flag with no bound will hang forever if the hardware never sets it (a common outcome of a misconfigured clock or a disconnected peripheral).
  • Treat the independent/window watchdog as part of application design from day one, not a late add-on — decide the refresh strategy before writing the main loop, not after a field failure.

Testing and Debugging

  • Unit test pure logic (protocol parsing, state machines, math) on a host build (native gcc/clang) with the hardware layer mocked or stubbed out behind the narrow interfaces from the project structure.
  • Use hardware-in-the-loop tests for actual peripheral behavior (timing, electrical signaling, real sensor data) that a host build can't exercise.
  • Add assertions (assert() or a custom configASSERT-style macro) for impossible hardware states in debug builds, compiled out in release builds if code size is tight.
  • Use SWD/JTAG (ST-Link/V2, OpenOCD, J-Link) for live debugging, a logic analyzer for signal-level issues, and serial logs with rate limiting (never flood a UART inside a tight loop or ISR).
  • Keep fault handlers (HardFault_Handler, etc.) useful: capture the reset reason (RCC->CSR), relevant fault status registers (SCB->CFSR, SCB->HFSR), and firmware build version/hash so a field crash is diagnosable after the fact.

Common Mistakes

  • Modifying CubeMX-generated files outside USER CODE blocks, so the next regeneration silently deletes the changes.
  • Busy-waiting forever on a hardware status flag with no timeout, hanging the firmware on any hardware anomaly.
  • Sharing a buffer between DMA and the CPU without synchronization (cache invalidation on cores with a data cache, or simply reading before the transfer-complete flag/callback fires).
  • Assuming a peripheral's register state is unchanged after waking from a low-power mode (Stop/Standby) — many peripherals require re-initialization after these modes.

Frequently asked questions

What does the Embedded Stm32 AI skill do?

Best practices for embedded C/C++ development on STM32 microcontrollers using the HAL, covering peripherals, DMA, interrupts, memory constraints, and hardware-focused testing. Use when writing STM32 HAL code, configuring peripherals generated by STM32CubeMX, working with interrupts or DMA, debugging with SWD/JTAG, managing memory-constrained firmware, or writing hardware-in-the-loop or host-build tests for embedded C.

Why use Embedded Stm32 on TypingMind?

Because you install it once and use it with any model. Embedded Stm32 is plain Markdown rather than provider-specific code, so the same skill runs on GPT-5, Claude, Gemini, Grok, or a local model — and you can switch model mid-chat without it breaking. TypingMind runs on your own API keys, so you pay providers directly instead of a per-seat subscription, and your skills and chats stay in your own storage.

How do I install Embedded Stm32 in TypingMind?

Open Plugins → Skills → Install from GitHub in TypingMind and paste https://github.com/Mindrally/skills/tree/main/embedded-stm32. TypingMind reads its SKILL.md and installs it as a skill you can enable per chat.

Which AI models can use Embedded Stm32?

Any model you connect in TypingMind. AI skills are plain Markdown instructions rather than provider-specific code, so GPT, Claude, Gemini, Grok, and local models can all load this skill when a request matches it.

How many AI models can I use with Embedded Stm32?

As many as you like. As long as a model supports skills, you can use Embedded Stm32 with it — GPT, Claude, Gemini, Grok, DeepSeek, Mistral, Llama and more — all on TypingMind with your own API keys.

Is the Embedded Stm32 AI skill free?

Yes. It is published on GitHub by Mindrally under the Apache-2.0 license. You only pay your own AI provider for the tokens you use.

What are AI skills?

An AI skill is a reusable instruction bundle that teaches an AI model how to do one specific task. It follows the open Agent Skills format: a SKILL.md file with a name and description, plus any scripts, templates or reference files the model may need. The model reads the instructions only when your request matches the skill, so an installed skill costs nothing until it is used.

How are AI skills different from plugins or MCP servers?

A plugin or MCP server gives a model new tools to call — code that runs somewhere and returns a result. An AI skill gives the model knowledge and process instead: how to approach a task, which steps to follow, what good output looks like. Skills are plain Markdown, so they need no server, no API key and no runtime, and they work with any model.

View all

Set up your own AI workspace now

Get notified about new features and future giveaways by subscribing to our newsletter 👇