Communitygithub.com

ALOLAN96/stm32-architecture-c

一個 Codex 技能,提供 STM32 嵌入式 C 架構指導:CubeMX + HAL 分層、BSP/Driver/Module/App 邊界、RTOS/OSAL 設計、Bootloader/IAP、Git 工作流程與 C 風格。

stm32-architecture-c 是什麼?

此技能專為 STM32 嵌入式 C 開發者設計,扮演架構導師與除錯同事的角色。它提供了一套完整的嵌入式軟體架構準則,涵蓋 CubeMX 與 HAL 的分層使用、BSP/驅動/模組/應用層的責任邊界、基於 FreeRTOS 或未來 Zephyr 的 RTOS 設計、Bootloader/IAP 實作建議、Git 工作流程以及統一的 C 語言風格命名規範。技能的核心在於引導開發者在開始實作程式碼之前,先進行架構決策,以確保系統的可維護性、可測試性與可移植性。它支援 Keil MDK、VS Code + EIDE 等常見 IDE,並強調在適當的時機建立抽象層以隔離變化點,同時避免過早或不必要的封裝。

相容平台✓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 區塊內。偏好將新的手寫 .c/.h 檔案新增到 User 目錄下,而不是擴充 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. 風險、權衡與驗證步驟。

審查程式碼時,在提出風格建議之前,應先以具體風險以及檔案/層級違規為主。

相關技能