# Cubemx Prj Create

> Create an STM32CubeMX project (CMake/VS Code ready) matching STM32F407ZGTx clocks and debug setup. Use when generating a new CubeMX-based STM32 project or updating this workflow; confirm the locally installed CubeMX version before creating or editing any `.ioc` so the project stays openable on the user's machine.

- Skill: `myfro9/cubemx-prj-create` (Agent Skill)
- Install (CLI): `npx skillmds@latest add myfro9/cubemx-prj-create`
- Raw SKILL.md: https://api.skillmd.com/api/skills/myfro9/cubemx-prj-create/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Productivity
- Author: Myfro9 (https://skillmd.com/u/myfro9)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/myfro9/cubemx-prj-create

---


# STM32CubeMX Project Creator

Teach Claude to create a new STM32CubeMX project for `STM32F407ZGTx`, output a CMake build for VS Code, and prep debug tasks. The generated project must preserve the required `USART2` behavior exactly, including DMA-based idle reception and the supporting user-code hooks in `usart.c`, `main.c`, and `syscalls.c`.

The skill must be self-contained. Do not inspect, copy from, diff against, or otherwise rely on other user projects as templates or references when generating a new project.

## Preconditions
- Confirm CubeMX exists at `C:/Program Files/STMicroelectronics/STM32Cube/STM32CubeMX/STM32CubeMX.exe`; otherwise stop and report.
- Confirm the installed CubeMX version before generating or editing the project. If the local version cannot be determined, stop and report instead of guessing.
- Ensure any written `.ioc` metadata matches the local CubeMX version; do not stamp a newer `MxCube.Version` than the user's installation.
- Use only the installed CubeMX, its bundled databases, and the requested project directory as inputs. Do not use another workspace or existing STM32 project as a reference.
- Have an output directory where the new project folder will be created.
- Ask the user for the project name.
- VS Code STM32 extensions are available (CMake Tools, Cortex-Debug, STM32 VS Code Extension).

## MCU + clock configuration
- MCU: `STM32F407ZGTx`, package `LQFP144`.
- Clock source: HSE crystal = `25_000_000 Hz` on `PH0/PH1`.
- PLL: source HSE, `PLLM=25`, `PLLN=168`, `PLLP=2`, `PLLQ=4`.
- Resulting clocks: `SYSCLK=84 MHz`, `HCLK=84 MHz`, `APB1=42 MHz`, `APB2=84 MHz`, `I2S=96 MHz`, `MCO2=84 MHz`, `Systick=1 ms`.
- Ensure RCC: `SYSCLK source = PLLCLK`, `LSI = 32000`, and keep the divider values listed here.

## Generation targets
- Toolchain/IDE: CMake (Ninja or Make). Enable `CMAKE_EXPORT_COMPILE_COMMANDS`.
- Languages: C + ASM, C standard C11.
- Linker script: device-generated `STM32F407ZGTx_FLASH.ld`.
- Peripherals to enable by default: RCC, SYS, NVIC, GPIO, DMA, USART2.
- GPIO baseline:
  - SWD: `PA13` = `SYS_JTMS-SWDIO`, `PA14` = `SYS_JTCK-SWCLK`
  - Oscillators: `PH0/PH1` for HSE
  - USART pins: `PD5` = `USART2_TX`, `PD6` = `USART2_RX`
  - Remove all other GPIO assignments unless the user explicitly asks for them
- NVIC baseline:
  - `NVIC_PRIORITYGROUP_4`
  - System handlers enabled as generated by CubeMX
  - `SysTick_IRQn` enabled with priority `15`
  - Enable `DMA1_Stream5_IRQn` and `DMA1_Stream6_IRQn`
- DMA baseline:
  - `USART2_RX`: `DMA1_Stream5`, `DMA_CHANNEL_4`, `DMA_PERIPH_TO_MEMORY`, `DMA_NORMAL`, byte alignment, memory increment enabled, peripheral increment disabled, priority low, FIFO disabled
  - `USART2_TX`: `DMA1_Stream6`, `DMA_CHANNEL_4`, `DMA_MEMORY_TO_PERIPH`, `DMA_NORMAL`, byte alignment, memory increment enabled, peripheral increment disabled, priority low, FIFO disabled
- USART2 baseline:
  - Virtual mode: `VM_ASYNC`
  - Parameters in generated init: `115200`, `8N1`, `TX_RX`, `UART_HWCONTROL_NONE`, `UART_OVERSAMPLING_16`
  - `HAL_UART_MspInit()` must configure `PD5/PD6` as `GPIO_AF7_USART2` and link both DMA handles with `__HAL_LINKDMA`
  - `stm32f4xx_it.c` must keep `DMA1_Stream5_IRQHandler()` and `DMA1_Stream6_IRQHandler()` calling `HAL_DMA_IRQHandler(&hdma_usart2_rx)` and `HAL_DMA_IRQHandler(&hdma_usart2_tx)`
- Do not add other peripherals unless the user explicitly requests them.

## USART2 runtime behavior
- `printf` redirection pattern:
  - Keep `syscalls.c` with `extern int __io_putchar(int ch) __attribute__((weak));`
  - Do not implement `__io_putchar()` in `syscalls.c`
  - Re-define `int __io_putchar(int ch)` in `Core/Src/usart.c`
  - Implementation must call `HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, 1000);` and return `ch`
- DMA receive flow:
  - In `main.c`, after `MX_GPIO_Init();`, `MX_DMA_Init();`, and `MX_USART2_UART_Init();`, call `HAL_UARTEx_ReceiveToIdle_DMA(&huart2, receiveBuf50, sizeof(receiveBuf50));`
  - Implement `HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)` in user code, not the legacy `HAL_UART_RxCpltCallback()`
  - In that callback, when `huart->Instance == USART2`, terminate the buffer safely, process the received command, then immediately re-arm `HAL_UARTEx_ReceiveToIdle_DMA(&huart2, receiveBuf50, sizeof(receiveBuf50));`
- Ensure the generated project includes all symbols used by this flow:
  - `UART_HandleTypeDef huart2`
  - `DMA_HandleTypeDef hdma_usart2_rx`
  - `DMA_HandleTypeDef hdma_usart2_tx`
  - a receive buffer such as `uint8_t receiveBuf50[50]`

## VS Code integration (mirror reference patterns)
Create `.vscode/` with:
- `settings.json`:
  - `"cmake.configurePreset": "Debug"`
  - `"cmake.buildPreset": "Debug"`
  - `"STM32VSCodeExtension.cubeCLT.path": "C:/ST/STM32CubeCLT_1.19.0"` (or user path)
- `c_cpp_properties.json`: `configurationProvider` = `ms-vscode.cmake-tools`
- `tasks.json`: include
  - A fixed `CMake: configure` task that runs `cmake --preset Debug`
  - A fixed default build task that runs `cmake --build --preset Debug`
  - A fixed clean rebuild task that runs `cmake --build --preset Debug --clean-first`
  - Mark the fixed build task as the default build group so VS Code `Ctrl+Shift+B` works immediately
  - Flash task: `STM32_Programmer_CLI --connect port=swd --download ${workspaceFolder}/build/Debug/<proj>.elf -hardRst -rst --start`
  - Aggregate "Build + Flash" task depending on rebuild + flash
  - Avoid depending on `${command:cmake.activeBuildPresetName}`, `${command:cmake.activeConfigurePresetName}`, or `${command:cmake.launchTargetPath}` for the default build path, because `Ctrl+Shift+B` may fail before CMake Tools initializes those values
- `launch.json`: Cortex-Debug launch + attach configs:
  - `servertype: stlink`, `device: STM32F407ZGTx`, `interface: swd`
  - `executable: ${workspaceFolder}/build/Debug/<proj>.elf`
  - `svdFile: ${config:STM32VSCodeExtension.cubeCLT.path}/STMicroelectronics_CMSIS_SVD/STM32F407.svd`
  - `serverpath`/`stlinkPath`: `${config:STM32VSCodeExtension.cubeCLT.path}/STLink-gdb-server/bin/ST-LINK_gdbserver`
  - `armToolchainPath`: `${config:STM32VSCodeExtension.cubeCLT.path}/GNU-tools-for-STM32/bin`
  - `gdbPath`: `${config:STM32VSCodeExtension.cubeCLT.path}/GNU-tools-for-STM32/bin/arm-none-eabi-gdb`
  - `preLaunchTask`: `CMake: clean rebuild` (for launch config)

## CMake scaffolding (root)
- `CMakeLists.txt`: include toolchain `cmake/gcc-arm-none-eabi.cmake`, set `CMAKE_C_STANDARD 11`, enable C & ASM, create executable `${CMAKE_PROJECT_NAME}`, `add_subdirectory(cmake/stm32cubemx)`, add user sources, link `stm32cubemx`.
- `CMakePresets.json`: generator Ninja, binary dir `build/${presetName}`, presets Debug/RelWithDebInfo/Release/MinSizeRel inheriting default.
- `cmake/gcc-arm-none-eabi.cmake`: set `TOOLCHAIN_PREFIX=arm-none-eabi-`, compilers, target flags `-mcpu=cortex-m4 -mfpu=fpv4-sp-d16 -mfloat-abi=hard`, link script `STM32F407ZGTx_FLASH.ld`, typical link flags with gc-sections and map file.
- `cmake/stm32cubemx/CMakeLists.txt`: interface lib with `USE_HAL_DRIVER`, `STM32F407xx`, includes for `Core/Inc` and `Drivers`, and CubeMX-generated sources for the minimal setup (at minimum `main.c`, `gpio.c`, `dma.c`, `usart.c`, `stm32f4xx_it.c`, `stm32f4xx_hal_msp.c`, `system_stm32f4xx.c`, `startup_stm32f407xx.s`, and required HAL driver files). Keep it aligned with CubeMX output; regenerate if peripherals differ.

## Execution steps
1) Verify CubeMX binary exists; if missing, stop.
2) Detect and record the installed CubeMX version before doing any `.ioc` work. Use that exact version when validating compatibility and when writing `MxCube.Version` / related metadata.
3) Do not handcraft a full STM32 project when CubeMX generation is available locally. Generate with the installed CubeMX first, then patch only the required user sections and build files.
4) Do not inspect or copy from any other project on disk. This skill must work independently from CubeMX metadata, generated output, and the instructions in this file only.
5) Ask user for project name and target directory.
6) Create project folder and open CubeMX:
   - Set MCU to STM32F407ZGTx (LQFP144).
   - Configure RCC: HSE 25 MHz on `PH0/PH1`, `PLLM=25`, `PLLN=168`, `PLLP=2`, `PLLQ=4`, `SYSCLK` from PLL.
   - Enable required peripherals for the default template: SYS, RCC, NVIC, GPIO, DMA, USART2.
   - Configure only these GPIO entries by default: `PH0`, `PH1`, `PA13`, `PA14`, `PD5`, `PD6`.
   - Configure `USART2` as asynchronous on `PD5/PD6`.
   - Configure DMA requests exactly as specified here: `USART2_RX -> DMA1_Stream5`, `USART2_TX -> DMA1_Stream6`.
   - Set `NVIC_PRIORITYGROUP_4`, keep `SysTick_IRQn` at priority `15`, and enable both DMA stream IRQs.
   - Project Manager -> Toolchain/IDE: CMake; set project name and path.
   - Code Generator: Generate peripheral initialization as "HAL", keep user sections.
   - Generate code.
7) Validate the generated output before patching:
   - Confirm `main.c` exists and `SystemClock_Config()` uses `HSE`, `PLL_ON`, `PLLM=25`, `PLLN=168`, `PLLP=RCC_PLLP_DIV2`, `PLLQ=4`, `SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK`, `AHB = DIV1`, `APB1 = DIV2`, `APB2 = DIV1`.
   - Confirm the generated project includes `USART2`, `DMA1_Stream5`, `DMA1_Stream6`, and `USART2_IRQn`.
   - Confirm the generated `.ioc` does not report required-field incompatibilities for RCC, DMA, SYS, GPIO, or USART2.
   - If CubeMX output falls back to `HSI`, `PLL_NONE`, missing DMA streams, or missing USART2 init, treat generation as failed and fix generation inputs before patching.
8) Patch the generated sources to preserve the required `USART2` runtime behavior:
   - In `Core/Src/usart.c`, add `__io_putchar()` in user code and keep it bound to `HAL_UART_Transmit(&huart2, ...)`.
   - In `Core/Src/syscalls.c`, leave `__io_putchar()` as a weak external declaration only.
   - In `Core/Src/main.c`, declare a receive buffer, call `HAL_UARTEx_ReceiveToIdle_DMA(&huart2, receiveBuf50, sizeof(receiveBuf50));` during initialization, and implement `HAL_UARTEx_RxEventCallback()` to process and re-arm reception.
   - In `Core/Src/stm32f4xx_it.c`, keep both DMA IRQ handlers for `USART2`.
9) Normalize the generated layout to the required root structure if CubeMX emits a different but equivalent layout:
   - Required end state is root `Core/Inc`, `Core/Src`, `Drivers`, `cmake`, `.vscode`, linker script, startup file, and root `.ioc`.
   - If CubeMX emits `Inc/` and `Src/` at the root, move them to `Core/Inc` and `Core/Src` before patching build files.
10) Add/adjust the CMake scaffolding files (root CMakeLists, presets, toolchain, stm32cubemx CMakeLists) to match the templates above if CubeMX did not emit them.
11) Add `.vscode` files as above; set `executable` paths to the chosen project name.
12) Validate VS Code build behavior:
   - Confirm the default VS Code build task maps to `cmake --build --preset Debug`.
   - Confirm build tasks do not rely on unresolved CMake Tools command substitutions for the default build path.
   - If needed, prefer fixed `Debug` preset names and fixed `${workspaceFolder}/build/Debug/<proj>.elf` paths over dynamic commands.
13) Before finishing, re-check the generated `.ioc` header and confirm its CubeMX version metadata still matches the local installation. Fix it if it drifted.
14) Run `cmake --preset Debug` then `cmake --build --preset Debug` to verify build; ensure `${workspaceFolder}/build/Debug/<proj>.elf` exists.
15) Verify VS Code `Ctrl+Shift+B` can invoke the generated default build task without manual preset reselection.
16) For flashing, run VS Code task "Build + Flash" or `STM32_Programmer_CLI --connect port=swd --download build/Debug/<proj>.elf -hardRst -rst --start`.
17) For debugging, launch "Build & Debug Microcontroller - ST-Link" in VS Code (Cortex-Debug).

## Notes & guardrails
- Keep `SKILL.md` concise; move verbose reference to separate files if added.
- If user chooses another MCU, re-derive clocks accordingly instead of using the above constants.
- Never assume the CubeMX version from memory, from a previous machine, or from the latest release. Detect the local version for the current task.
- On this machine, prefer headless generation via `jre/bin/java.exe -jar STM32CubeMX.exe -q <script>` when automating CubeMX. Do not assume direct `STM32CubeMX.exe -q` behaves the same unless verified for the current install.
- Do not continue from an invalid CubeMX load that logs missing required RCC, DMA, SYS, GPIO, or USART fields. Fix the `.ioc` / generation inputs first.
- Do not continue patching if generated `main.c` shows `HSI` or `PLL_NONE` instead of the required `HSE + PLL` clock tree.
- This skill must remain independent. Do not read another STM32 project to discover valid `.ioc` keys, clock parameters, or file layouts.
- Prefer deterministic VS Code tasks. Do not assume VS Code `Ctrl+Shift+B` will work if the tasks depend on unresolved extension-provided command variables.
- Do not refer to the VS Code build shortcut as `Ctrl+B`; in standard VS Code usage the build shortcut is `Ctrl+Shift+B`.
- For the default build path, prefer fixed `Debug` preset tasks over dynamic `${command:...}` substitutions unless those substitutions are explicitly verified in the current workspace.
- Unless the user asks for more peripherals, remove any auto-enabled middleware or peripheral blocks beyond DMA, GPIO, NVIC, RCC, SYS, and USART2 from both the CubeMX configuration and generated build files.
- When stripping peripherals from a copied `.ioc`, also remove their dependent pins, DMA requests, NVIC entries, generated source files, and middleware references so the result stays internally consistent.
- For GPIO specifically, keep only these default pins: `PH0`, `PH1`, `PA13`, `PA14`, `PD5`, `PD6`.
- Do not replace the required `HAL_UARTEx_ReceiveToIdle_DMA()` + `HAL_UARTEx_RxEventCallback()` flow with polling, interrupt-only receive, or `HAL_UART_Receive_DMA()`.
- Do not move `__io_putchar()` into `syscalls.c`; keep the actual implementation in `usart.c` so `printf` stays coupled to `USART2`.

