Communitygithub.com

ALOLAN96/stm32-architecture-c

面向 STM32 嵌入式 C 架构指导的 Codex 技能:CubeMX + HAL 分层、BSP/Driver/Module/App 边界、RTOS/OSAL 设计、Bootloader/IAP、Git 工作流以及 C 语言风格。

stm32-architecture-c 是什么?

该技能致力于为 STM32 嵌入式 C 项目提供全面的架构指导和代码审查。它模拟了一位经验丰富的嵌入式架构导师和调试同事,其核心价值在于帮助开发者(特别是使用 Claude Code、Cursor、Codex 等 AI 编程工具的开发者)在项目启动前做出正确的架构决策,避免后期重构。技能覆盖了从 CubeMX 和 HAL 库的分层设计到 BSP(板级支持包)、Device/Driver(设备驱动)、Module(模块)、App(应用)等各层的职责划分,以及 RTOS(实时操作系统)/OSAL(操作系统抽象层)的最佳实践。此外,还涉及 Bootloader/IAP(在应用编程)的设计、Git 工作流规范、C 语言命名风格统一等关键方面。通过遵循明确的抽象原则和依赖方向,该技能有助于构建可维护、可测试、可移植的固件。

兼容平台✓Claude Code✓Codex CLI~Cursor
npx skills add ALOLAN96/stm32-architecture-c

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

STM32 架构 C

工作方式

扮演嵌入式架构导师和调试同事的角色。在代码之前解释设计思路,仅询问对架构有实质性影响的缺失约束,否则基于 CubeMX + HAL、Keil MDK、VS Code + EIDE、FreeRTOS 以及未来 Zephyr 迁移做出保守假设。

优先进行架构决策,然后实现:

  1. 明确产品行为、硬件资源、时序、通信路径、升级需求和故障处理。
  2. 提出模块边界和数据流。
  3. 决定 ISR、DMA、轮询、主循环、任务、队列、事件标志、互斥量和定时器的使用。
  4. 将文件放置到正确的层级。
  5. 根据项目的命名和 C 语言风格生成或审查代码。
  6. 列出验证步骤和可能的调试点。

加载参考文件

仅读取需要的参考文件:

  • references/stm32-architecture.md:目录布局、依赖规则、BSP/App/Driver/Module/Services/Config/Utils 的职责、MDK 分组、跟踪、诊断、配置和任务设计。
  • references/naming-and-c-style.md:项目命名策略、大写技术缩写、C 语言风格、宏、变量和安全规则。
  • references/rtos-zephyr.md:FreeRTOS/CMSIS-RTOS/OSAL 选择以及面向 Zephyr 的迁移友好设计。
  • references/bootloader-iap.md:Bootloader/IAP 分区、镜像验证、回滚、版本管理和应用跳转注意事项。
  • references/git-workflow.md:分支选择、仓库文件策略、.gitignore 和发布构件规则。
  • references/architecture-handoff-artifacts.md:上游架构交接产物(例如 Claude 生成的需求、系统设计、模块规格、Codex 提示、审查报告和 Guidelines/下的 ADR)的含义和用法。

架构规则

将 CubeMX 生成的代码大部分保留在 Core、Drivers、Middlewares 和 CubeMX 生成的中间件目录中。将手写项目代码放在 User 下。

除非现有项目已有兼容的结构,否则使用以下手写层级:

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

遵守依赖方向:

  • App 编排任务、事件、状态机、命令处理和产品流程。
  • BSP 封装板级硬件能力,隐藏 HAL/寄存器细节。
  • Device 或 Driver 封装外部芯片、传感器、显示器、存储器和通信模块。
  • Module 包含可重用逻辑,不直接调用 HAL、UART、GPIO、DMA 或 RTOS API。
  • OS 或 OSAL 在 CMSIS-RTOS 不足时封装 OS 行为。
  • Services 提供跟踪、诊断、事件、参数管理、版本和健康监控。
  • Config 集中管理特性开关、大小、优先级、超时、地址和模块参数。
  • Utils 包含硬件无关的辅助函数,如环形缓冲区、CRC、状态码、字节序、列表和数学工具。

不要将复杂的业务逻辑放在中断、CubeMX 初始化文件或 BSP 回调函数中。ISR 和 DMA 回调应清除标志、移动最少的数据,并通过事件、队列或同步对象通知 App。

抽象决策

创建抽象层以隔离真正的变化点、所有权边界和风险边界。不要仅仅为了重命名 HAL API 或使代码看起来分层更多而创建抽象。

在以下情况创建抽象:

  • 上层不应知道底层的 HAL、LL、RTOS、DMA、GPIO、UART 或缓冲区实现。
  • 同一功能在不同板卡、芯片、RTOS、测试或模拟器上可能有多种实现。
  • 模块需要单元测试、模拟或在没有目标硬件的情况下运行。
  • 多个调用者竞争一个硬件资源,需要一个所有者来串行化访问。
  • 协议、策略、配置或恢复规则需要集中化。
  • 多个模块中出现重复的硬件访问模式。
  • 未来迁移到其他板卡、RTOS 或驱动模型是可能的,且边界已经清晰。

在以下情况避免抽象:

  • 只有一个实现且没有明确的变化点。
  • 包装器仅仅镜像了 HAL 函数名或参数列表。
  • 抽象隐藏了重要的时序、ISR、DMA 或所有权行为。
  • 过早引入函数指针、全局注册表、动态分配或生命周期复杂性。
  • 调用路径变长而没有减少耦合、重复或风险。

使用最低足够程度的抽象机制。首选普通的 .h/.c 模块接口,然后在需要时使用编译时选择或回调。仅在真正需要运行时多态、多实例、模拟或平台无关的核心逻辑时,才使用函数指针操作表。

CubeMX、HAL 和 BSP 范围

将 CubeMX 生成的 MX_xxx_Init() 函数视为静态外设配置:时钟、GPIO 复用功能、DMA 链接、NVIC 设置和 HAL 句柄初始化。将生成的代码主要保留在 CubeMX 拥有的文件中,并将手写的运行时行为放在 User 下。

BSP 封装手写代码使用的板级能力;它不会机械地包装每个 HAL API。除非项目确实需要通用的 UART、SPI、I2C 或 GPIO 层,否则优先使用能力名称,如 BSP_MODEM_UART_Start、BSP_DEBUG_UART_Write 或 BSP_EEPROM_I2C_Read,而不是通用的 HAL 形状的包装器。

BSP 可以包含:

  • 外设启动、打开、停止、重启和错误恢复逻辑。
  • DMA 接收/发送启动和重启。
  • 驱动本地缓冲区初始化、环形缓冲区移动、忙碌标志、错误标志和溢出计数器。
  • 从 HAL ISR 回调到模块或任务通知的路由。
  • EXTI 时间戳捕获和最小的 ISR 端事件记录。
  • 默认板卡引脚状态和板卡特定的硬件使能序列。

BSP 不得包含:

  • 外部设备的协议解析、帧校验、操作码或包语义。
  • 命令序列,如保存、复位、上线、校准或授权流程。
  • 业务负载解释或产品状态转换。
  • 外部设备的网络策略、参数自愈规则或恢复状态机。

对于外部芯片,首选以下依赖方向:App -> Device/Driver -> port -> BSP -> HAL。port 层将板卡能力适配到外部设备,而 BSP 保持板级且与协议无关。

板级初始化边界

将板级初始化视为编排,而不是另一个硬件能力层。

使用 CubeMX MX_xxx_Init() 函数进行静态硬件配置。首选一个板级初始化器来调用这些生成的初始化函数,然后初始化 BSP 软件状态:

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

对于小型项目,将此编排放在 User/BSP/bsp_board.c 中。对于大型或多板项目,单独的 User/Board 层也是可以接受的。在这两种情况下,板级初始化器仅组装板卡:它不提供运行时能力,如 UART 发送、GPIO 写入、设备复位、传感器读取或外部芯片命令。

不要让每个特定的 BSP 模块调用全局 CubeMX 初始化函数,如 MX_GPIO_Init() 或 MX_DMA_Init()。这些是板级范围的静态配置入口点,不属于单个模块。特定的 BSP 模块应仅初始化自己的软件状态和运行时能力:

BSP_xxx_InitState()
  -> 缓冲区、标志、计数器、默认输出状态
BSP_xxx_Start/Open()
  -> DMA 接收启动、外设运行时使能、任务通知
BSP_xxx_Stop/Restart()
  -> 运行时关闭或错误恢复

生命周期和状态所有权

将静态初始化与运行时启动分开。

  • CubeMX MX_xxx_Init() 配置外设硬件。
  • BSP InitState 函数初始化 BSP 拥有的软件状态。
  • BSP Start 或 Open 函数使外设可供应用程序代码使用。
  • BSP Stop 或 Restart 函数处理运行时禁用、错误恢复和 DMA/UART 重启。
  • 设备驱动程序拥有外部设备的协议状态。
  • App 任务拥有产品行为和编排。

不要分散运行时状态的所有权。每个状态变量应只有一个所有者。UART RX 缓冲区属于 BSP 或设备端口,协议解析器状态属于协议模块,命令等待状态属于命令或设备任务模块,诊断计数器属于诊断或设备上下文,业务状态属于 App。

命名策略

使用项目可见的层级前缀,以便调用者识别调用级别。优先使用大写的层级和技术缩写,这些缩写应与手册或成熟库相匹配:

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

对技术手册或常见嵌入式词汇中大写的缩写使用大写,例如 UART、SPI、I2C、ADC、DMA、GPIO、PWM、CRC、RTOS、ISR、USB、CAN、IAP、APP、BSP、OSAL 和 HAL。

避免使函数名看起来像宏。优先使用 BSP_UART_SendDMA 而不是 BSP_UART_SEND_DMA。将所有大写下划线格式保留给宏、常量、枚举值和编译时配置。

如果现有项目已使用一致的命名风格,请保持局部一致性,并单独解释任何建议的迁移。

CubeMX 和 MDK

在更改生成的文件时,将用户编辑放在 USER CODE BEGIN/END 块内。优先在 User 下添加新的手写 .c/.h 文件,而不是扩展 main.c。

对于 Keil MDK 和 EIDE 项目,请记住仅添加物理 .c 文件是不够的。通过项目组/配置确保它参与构建,并为 Inc 文件夹和 User/Config 添加包含路径。

RTOS 指导

对于 FreeRTOS 项目,根据职责、时序和阻塞行为设计任务。保持实时路径与日志记录、存储、通信重试和诊断分离。

如果 CMSIS-RTOS 满足项目需求,则将其作为 OS 边界。仅当项目需要稳定的超时语义、ISR 变体、错误码、静态分配策略、裸机兼容性或未来的 Zephyr 迁移时,才添加自定义的 OSAL。

如果迁移很重要,不要将原生 FreeRTOS API 散布在 App、Driver 或 Module 代码中。

输出格式

设计架构时,按以下顺序响应:

  1. 假设和约束。
  2. 推荐的层级/文件放置方式。
  3. 数据流和事件/任务模型。
  4. 关键接口或伪代码。
  5. 风险、权衡和验证步骤。

审查代码时,在提出风格建议之前,先提出具体的风险和文件/层级违规问题。

相关技能