CommunityArte y diseñogithub.com

ALOLAN96/stm32-architecture-c

A Codex skill for STM32 embedded C architecture guidance: CubeMX + HAL layering, BSP/Driver/Module/App boundaries, RTOS/OSAL design, Bootloader/IAP, Git workflow, and C style.

Compatible conClaude CodeCodex CLI~Cursor
npx skills add ALOLAN96/stm32-architecture-c

Documentación


name: stm32-architecture-c description: STM32 embedded C architecture guidance for CubeMX + HAL projects using Keil MDK or VS Code + EIDE, with FreeRTOS today and Zephyr migration in mind. Use when designing or reviewing STM32 project structure, module boundaries, BSP/Driver/Module/App layering, OSAL/RTOS task models, sensor acquisition, communication protocols, Bootloader/IAP, .gitignore, Git branch workflow, or embedded C coding conventions before or during implementation.

STM32 Architecture C

Operating Style

Act like an embedded architecture mentor and debugging colleague. Explain the reasoning before the code, ask only for missing constraints that materially affect architecture, and otherwise make conservative assumptions based on CubeMX + HAL, Keil MDK, VS Code + EIDE, FreeRTOS, and future Zephyr migration.

Prefer architecture decisions before implementation:

  1. Clarify product behavior, hardware resources, timing, communication paths, upgrade requirements, and failure handling.
  2. Propose module boundaries and data flow.
  3. Decide ISR, DMA, polling, main-loop, task, queue, event flag, mutex, and timer usage.
  4. Place files in the proper layer.
  5. Generate or review code using the project's naming and C style.
  6. List validation steps and likely debugging points.

Load References

Read only the needed reference file:

  • references/stm32-architecture.md: directory layout, dependency rules, BSP/App/Driver/Module/Services/Config/Utils responsibilities, MDK groups, trace, diagnostics, configuration, and task design.
  • references/naming-and-c-style.md: project naming policy, uppercase technical abbreviations, C style, macro, variable, and safety rules.
  • references/rtos-zephyr.md: FreeRTOS/CMSIS-RTOS/OSAL choices and migration-friendly design for Zephyr.
  • references/bootloader-iap.md: Bootloader/IAP partitioning, image verification, rollback, versioning, and app jump concerns.
  • references/git-workflow.md: branch choices, repository file policy, .gitignore, and release artifact rules.
  • references/architecture-handoff-artifacts.md: meaning and usage of upstream architecture handoff artifacts such as Claude-generated requirements, system design, module specs, Codex prompts, review reports, and ADRs under Guidelines/.

Architecture Rules

Keep CubeMX generated code mostly in Core, Drivers, Middlewares, and CubeMX-generated middleware directories. Put handwritten project code under User.

Use these handwritten layers unless the existing project already has a compatible structure:

User/App
User/BSP
User/Device or User/Driver
User/Module
User/OS
User/Services
User/Config
User/Utils
User/ThirdParty

Respect dependency direction:

  • App orchestrates tasks, events, state machines, command handling, and product flow.
  • BSP wraps board-level hardware capability and hides HAL/register details.
  • Device or Driver wraps external chips, sensors, displays, memories, and communication modules.
  • Module contains reusable logic that does not directly call HAL, UART, GPIO, DMA, or RTOS APIs.
  • OS or OSAL wraps OS behavior when CMSIS-RTOS is not sufficient.
  • Services provides trace, diagnostics, events, parameter management, version, and health monitoring.
  • Config centralizes feature switches, sizes, priorities, timeouts, addresses, and module parameters.
  • Utils contains hardware-independent helpers such as ring buffers, CRC, status codes, byte order, lists, and math helpers.

Do not put complex business logic in interrupts, CubeMX init files, or BSP callbacks. ISR and DMA callbacks should clear flags, move minimal data, and notify App through events, queues, or synchronization objects.

Abstraction Decision

Create abstractions to isolate real change points, ownership boundaries, and risk boundaries. Do not create abstractions merely to rename HAL APIs or make code look more layered.

Abstract when:

  • upper layers should not know the underlying HAL, LL, RTOS, DMA, GPIO, UART, or buffer implementation
  • the same capability may have multiple implementations across boards, chips, RTOSes, tests, or simulators
  • the module needs to be unit-tested, mocked, or run without target hardware
  • several callers compete for one hardware resource and a single owner must serialize access
  • protocol, policy, configuration, or recovery rules must be centralized
  • duplicated hardware access patterns appear in multiple modules
  • future migration to another board, RTOS, or driver model is plausible and the boundary is already clear

Avoid abstraction when:

  • there is only one implementation and no clear variation point
  • the wrapper only mirrors a HAL function name or argument list
  • the abstraction hides important timing, ISR, DMA, or ownership behavior
  • it introduces function pointers, global registries, dynamic allocation, or lifecycle complexity too early
  • call paths become longer without reducing coupling, duplication, or risk

Use the lowest sufficient abstraction mechanism. Prefer ordinary .h/.c module interfaces first, then compile-time selection or callbacks when needed. Use function-pointer operation tables only when runtime polymorphism, multiple instances, simulation, or platform-independent core logic is truly required.

CubeMX, HAL, And BSP Scope

Treat CubeMX-generated MX_xxx_Init() functions as static peripheral configuration: clocks, GPIO alternate functions, DMA linkage, NVIC setup, and HAL handle initialization. Keep generated code mostly in CubeMX-owned files and put handwritten runtime behavior under User.

BSP wraps board-level capabilities used by handwritten code; it does not mechanically wrap every HAL API. Prefer capability names such as BSP_MODEM_UART_Start, BSP_DEBUG_UART_Write, or BSP_EEPROM_I2C_Read over generic HAL-shaped wrappers unless the project truly needs a generic UART, SPI, I2C, or GPIO layer.

BSP may contain:

  • peripheral start, open, stop, restart, and error-recovery logic
  • DMA receive/transmit startup and restart
  • driver-local buffer initialization, ring-buffer movement, busy flags, error flags, and overflow counters
  • routing from HAL ISR callbacks to module or task notifications
  • EXTI timestamp capture and minimal ISR-side event recording
  • default board pin states and board-specific hardware enable sequencing

BSP must not contain:

  • external-device protocol parsing, frame checksums, opcodes, or packet semantics
  • command sequencing such as save, reset, go-online, calibration, or admission flows
  • business payload interpretation or product state transitions
  • network policy, parameter self-healing rules, or recovery state machines for an external device

For external chips, prefer this dependency direction: App -> Device/Driver -> port -> BSP -> HAL. The port layer adapts board capabilities to the external device, while BSP remains board-level and protocol-agnostic.

Board Initialization Boundary

Keep board initialization as orchestration, not another hardware capability layer.

Use CubeMX MX_xxx_Init() functions for static hardware configuration. Prefer one board-level initializer to call these generated init functions and then initialize BSP software state:

main/App
  -> BSP_BoardInit() or BOARD_InitHardware()
      -> MX_GPIO_Init()
      -> MX_DMA_Init()
      -> MX_USARTx_UART_Init()
      -> BSP_xxx_InitState()

For small projects, place this orchestration in User/BSP/bsp_board.c. For larger or multi-board projects, a separate User/Board layer is acceptable. In both cases, the board initializer only assembles the board: it does not provide runtime capabilities such as UART send, GPIO write, device reset, sensor read, or external-chip commands.

Do not let each specific BSP module call global CubeMX init functions such as MX_GPIO_Init() or MX_DMA_Init(). Those are board-wide static configuration entry points, not owned by a single module. Specific BSP modules should initialize their own software state and runtime capability only:

BSP_xxx_InitState()
  -> buffers, flags, counters, default output state
BSP_xxx_Start/Open()
  -> DMA receive start, peripheral runtime enable, task notifications
BSP_xxx_Stop/Restart()
  -> runtime shutdown or error recovery

Lifecycle And State Ownership

Separate static initialization from runtime startup.

  • CubeMX MX_xxx_Init() configures peripheral hardware.
  • BSP InitState functions initialize BSP-owned software state.
  • BSP Start or Open functions make the peripheral usable by application code.
  • BSP Stop or Restart functions handle runtime disable, error recovery, and DMA/UART restarts.
  • Device drivers own external-device protocol state.
  • App tasks own product behavior and orchestration.

Do not scatter ownership of runtime state. Each state variable should have one owner. UART RX buffers belong to BSP or the device port, protocol parser state belongs to the protocol module, command wait state belongs to the command or device task module, diagnostic counters belong to diagnostics or the owning device context, and business state belongs to App.

Naming Policy

Use project-visible layer prefixes so callers can identify the calling level. Prefer uppercase layer and technical abbreviations that match manuals or established libraries:

APP_EventPost(...)
BSP_UART_SendDMA(...)
DRV_FlashWrite(...)
MOD_FilterProcess(...)
OSAL_QueueSendFromISR(...)
SVC_TraceInfo(...)
UTIL_CRC16Calc(...)

Use uppercase for abbreviations that are uppercase in technical manuals or common embedded vocabulary, such as UART, SPI, I2C, ADC, DMA, GPIO, PWM, CRC, RTOS, ISR, USB, CAN, IAP, APP, BSP, OSAL, and HAL.

Avoid making function names look like macros. Prefer BSP_UART_SendDMA over BSP_UART_SEND_DMA. Reserve all-uppercase with underscores for macros, constants, enum values, and compile-time configuration.

If an existing project already uses a consistent naming style, preserve local consistency and explain any suggested migration separately.

CubeMX And MDK

Put user edits inside USER CODE BEGIN/END blocks when changing generated files. Prefer adding new handwritten .c/.h files under User instead of expanding main.c.

For Keil MDK and EIDE projects, remember that adding a physical .c file is not enough. Ensure it participates in the build through the project group/configuration, and add include paths for Inc folders and User/Config.

RTOS Guidance

For FreeRTOS projects, design tasks by responsibility, timing, and blocking behavior. Keep real-time paths separate from logging, storage, communication retries, and diagnostics.

Prefer CMSIS-RTOS as the OS boundary if it satisfies the project. Add a custom OSAL only when the project needs stable timeout semantics, ISR variants, error codes, static allocation policy, bare-metal compatibility, or future Zephyr migration.

Do not scatter native FreeRTOS APIs through App, Driver, or Module code if migration matters.

Output Format

When designing architecture, respond in this order:

  1. Assumptions and constraints.
  2. Recommended layer/file placement.
  3. Data flow and event/task model.
  4. Key interfaces or pseudocode.
  5. Risks, tradeoffs, and validation steps.

When reviewing code, lead with concrete risks and file/layer violations before style suggestions.

Skills relacionados