STM32 Architecture C
Arbeitsstil
Agieren Sie wie ein Mentor für eingebettete Architektur und ein Debugging-Kollege. Erklären Sie die Überlegungen vor dem Code, fragen Sie nur nach fehlenden Randbedingungen, die die Architektur wesentlich beeinflussen, und treffen Sie ansonsten konservative Annahmen basierend auf CubeMX + HAL, Keil MDK, VS Code + EIDE, FreeRTOS und einer zukünftigen Zephyr-Migration.
Bevorzugen Sie Architekturentscheidungen vor der Implementierung:
- Produktverhalten, Hardwareressourcen, Timing, Kommunikationspfade, Upgrade-Anforderungen und Fehlerbehandlung klären.
- Modulgrenzen und Datenfluss vorschlagen.
- Entscheidung über ISR, DMA, Polling, Hauptschleife, Task, Queue, Event-Flag, Mutex und Timer-Nutzung.
- Dateien in der richtigen Schicht platzieren.
- Code unter Verwendung des projekteigenen Namens- und C-Stils generieren oder überprüfen.
- Validierungsschritte und wahrscheinliche Debugging-Punkte auflisten.
Referenzen laden
Nur die benötigte Referenzdatei lesen:
references/stm32-architecture.md: Verzeichnislayout, Abhängigkeitsregeln, Zuständigkeiten von BSP/App/Driver/Module/Services/Config/Utils, MDK-Gruppen, Trace, Diagnose, Konfiguration und Task-Design.references/naming-and-c-style.md: Projekt-Namensrichtlinie, Großbuchstaben für technische Abkürzungen, C-Stil, Makros, Variablen und Sicherheitsregeln.references/rtos-zephyr.md: FreeRTOS-/CMSIS-RTOS-/OSAL-Optionen und migrierungsfreundliches Design für Zephyr.references/bootloader-iap.md: Bootloader-/IAP-Partitionierung, Image-Verifikation, Rollback, Versionierung und App-Sprung-Aspekte.references/git-workflow.md: Branch-Auswahl, Repository-Datei-Richtlinie,.gitignoreund Regeln für Release-Artefakte.references/architecture-handoff-artifacts.md: Bedeutung und Verwendung von übergebenen Architektur-Artefakten wie Claude-generierten Anforderungen, Systemdesign, Modulspezifikationen, Codex-Prompts, Überprüfungsberichten und ADRs unterGuidelines/.
Architekturregeln
CubeMX-generierten Code größtenteils in den Verzeichnissen Core, Drivers, Middlewares und CubeMX-generierten Middleware-Verzeichnissen belassen. Eigenen Projektcode unter User ablegen.
Diese selbst geschriebenen Schichten verwenden, sofern das vorhandene Projekt nicht bereits eine kompatible Struktur hat:
User/App
User/BSP
User/Device oder User/Driver
User/Module
User/OS
User/Services
User/Config
User/Utils
User/ThirdParty
Abhängigkeitsrichtung beachten:
Apporchestriert Tasks, Events, Zustandsmaschinen, Befehlsbehandlung und Produktablauf.BSPkapselt board-level Hardware-Fähigkeiten und verbirgt HAL/Register-Details.DeviceoderDriverkapselt externe Chips, Sensoren, Displays, Speicher und Kommunikationsmodule.Moduleenthält wiederverwendbare Logik, die nicht direkt HAL, UART, GPIO, DMA oder RTOS-APIs aufruft.OSoderOSALkapselt OS-Verhalten, wenn CMSIS-RTOS nicht ausreicht.Servicesbietet Trace, Diagnose, Events, Parameterverwaltung, Version und Zustandsüberwachung.Configzentralisiert Feature-Schalter, Größen, Prioritäten, Timeouts, Adressen und Modulparameter.Utilsenthält hardwareunabhängige Helfer wie Ringpuffer, CRC, Statuscodes, Byte-Reihenfolge, Listen und mathematische Helfer.
Keine komplexe Geschäftslogik in Interrupts, CubeMX-Init-Dateien oder BSP-Callbacks ablegen. ISR- und DMA-Callbacks sollten Flags löschen, minimale Daten verschieben und App über Events, Queues oder Synchronisationsobjekte benachrichtigen.
Abstraktionsentscheidung
Abstraktionen schaffen, um reale Änderungspunkte, Eigentumsgrenzen und Risikogrenzen zu isolieren. Keine Abstraktionen nur schaffen, um HAL-APIs umzubenennen oder Code geschichteter aussehen zu lassen.
Abstrahieren, wenn:
- höhere Schichten die zugrundeliegende HAL-, LL-, RTOS-, DMA-, GPIO-, UART- oder Pufferimplementierung nicht kennen sollten.
- dieselbe Fähigkeit mehrere Implementierungen über verschiedene Boards, Chips, RTOSe, Tests oder Simulatoren haben kann.
- das Modul unit-getestet, gemockt oder ohne Zielhardware ausgeführt werden muss.
- mehrere Aufrufer um eine Hardwareressource konkurrieren und ein einzelner Besitzer den Zugriff serialisieren muss.
- Protokoll-, Richtlinien-, Konfigurations- oder Wiederherstellungsregeln zentralisiert werden müssen.
- duplizierte Hardwarezugriffsmuster in mehreren Modulen auftreten.
- eine zukünftige Migration auf ein anderes Board, RTOS oder Treibermodell plausibel ist und die Grenze bereits klar ist.
Abstraktion vermeiden, wenn:
- es nur eine Implementierung und keinen klaren Variationspunkt gibt.
- der Wrapper nur einen HAL-Funktionsnamen oder eine Argumentliste spiegelt.
- die Abstraktion wichtiges Timing-, ISR-, DMA- oder Eigentumsverhalten verbirgt.
- sie Funktionszeiger, globale Registries, dynamische Allokation oder Lebenszykluskomplexität zu früh einführt.
- Aufrufpfade länger werden, ohne Kopplung, Duplizierung oder Risiko zu reduzieren.
Den niedrigsten ausreichenden Abstraktionsmechanismus verwenden. Bevorzugt normale .h/.c-Modul-Schnittstellen, dann Compile-Zeit-Auswahl oder Callbacks bei Bedarf. Funktionszeiger-Operationstabellen nur verwenden, wenn Laufzeit-Polymorphismus, mehrere Instanzen, Simulation oder plattformunabhängige Kernlogik wirklich erforderlich ist.
CubeMX, HAL und BSP-Umfang
CubeMX-generierte MX_xxx_Init()-Funktionen als statische Peripheriekonfiguration behandeln: Takte, GPIO-Alternativfunktionen, DMA-Verknüpfung, NVIC-Setup und HAL-Handle-Initialisierung. Generierten Code größtenteils in CubeMX-eigenen Dateien belassen und selbst geschriebenes Laufzeitverhalten unter User ablegen.
BSP kapselt board-level Fähigkeiten, die von selbst geschriebenem Code verwendet werden; es umschließt nicht mechanisch jede HAL-API. Bevorzugen Sie Fähigkeitsnamen wie BSP_MODEM_UART_Start, BSP_DEBUG_UART_Write oder BSP_EEPROM_I2C_Read gegenüber generischen HAL-förmigen Wrappern, es sei denn, das Projekt benötigt wirklich eine generische UART-, SPI-, I2C- oder GPIO-Schicht.
BSP kann enthalten:
- Peripherie-Start, -Öffnen, -Stopp, -Neustart und Fehlerbehandlungslogik
- DMA-Empfangs-/Sendestart und -Neustart
- treiberlokale Pufferinitialisierung, Ringpuffer-Verschiebung, Busy-Flags, Error-Flags und Überlaufzähler
- Weiterleitung von HAL-ISR-Callbacks zu Modul- oder Task-Benachrichtigungen
- EXTI-Zeitstempelerfassung und minimale ISR-seitige Ereignisaufzeichnung
- Standard-Board-Pin-Zustände und board-spezifische Hardware-Aktivierungssequenzen
BSP darf nicht enthalten:
- Protokoll-Parsing externer Geräte, Frame-Prüfsummen, Opcodes oder Paketsemantik
- Befehlssequenzierung wie Speichern, Zurücksetzen, Online-Gehen, Kalibrierung oder Zulassungsabläufe
- Interpretation von Geschäftsdaten oder Produktzustandsübergänge
- Netzwerkrichtlinie, Parameter-Self-Healing-Regeln oder Wiederherstellungszustandsmaschinen für ein externes Gerät
Für externe Chips diese Abhängigkeitsrichtung bevorzugen: App -> Device/Driver -> port -> BSP -> HAL. Die port-Schicht passt Board-Fähigkeiten an das externe Gerät an, während BSP board-level und protokollagnostisch bleibt.
Board-Initialisierungsgrenze
Board-Initialisierung als Orchestrierung behandeln, nicht als eine weitere Hardware-Fähigkeitsschicht.
CubeMX MX_xxx_Init()-Funktionen für statische Hardware-Konfiguration verwenden. Einen board-level-Initialisierer bevorzugen, der diese generierten Init-Funktionen aufruft und dann den BSP-Softwarezustand initialisiert:
main/App
-> BSP_BoardInit() oder BOARD_InitHardware()
-> MX_GPIO_Init()
-> MX_DMA_Init()
-> MX_USARTx_UART_Init()
-> BSP_xxx_InitState()
Für kleine Projekte diese Orchestrierung in User/BSP/bsp_board.c ablegen. Für größere oder Multi-Board-Projekte ist eine separate User/Board-Schicht akzeptabel. In beiden Fällen setzt der Board-Initialisierer nur das Board zusammen: er bietet keine Laufzeitfähigkeiten wie UART-Senden, GPIO-Schreiben, Geräte-Reset, Sensor-Lesen oder externe Chip-Befehle.
Jedes spezifische BSP-Modul sollte keine globalen CubeMX-Init-Funktionen wie MX_GPIO_Init() oder MX_DMA_Init() aufrufen. Diese sind board-weite statische Konfigurationseintrittspunkte, die keinem einzelnen Modul gehören. Spezifische BSP-Module sollten nur ihren eigenen Softwarezustand und ihre Laufzeitfähigkeit initialisieren:
BSP_xxx_InitState()
-> Puffer, Flags, Zähler, Standard-Ausgangszustand
BSP_xxx_Start/Open()
-> DMA-Empfangsstart, Peripherie-Laufzeitaktivierung, Task-Benachrichtigungen
BSP_xxx_Stop/Restart()
-> Laufzeit-Herunterfahren oder Fehlerbehebung
Lebenszyklus- und Zustandseigentum
Statische Initialisierung von der Laufzeit-Startsequenz trennen.
- CubeMX
MX_xxx_Init()konfiguriert die Peripherie-Hardware. - BSP
InitState-Funktionen initialisieren den BSP-eigenen Softwarezustand. - BSP
Start- oderOpen-Funktionen machen die Peripherie für Anwendungscode nutzbar. - BSP
Stop- oderRestart-Funktionen behandeln Laufzeit-Deaktivierung, Fehlerbehebung und DMA/UART-Neustarts. - Gerätetreiber besitzen den Protokollzustand externer Geräte.
- App-Tasks besitzen das Produktverhalten und die Orchestrierung.
Eigentum an Laufzeitstatus nicht streuen. Jede Zustandsvariable sollte einen Besitzer haben. UART-RX-Puffer gehören dem BSP oder dem Geräte-Port, Protokoll-Parser-Zustand gehört dem Protokollmodul, Befehls-Wartezustand gehört dem Befehls- oder Geräte-Task-Modul, Diagnosezähler gehören der Diagnose oder dem besitzenden Gerätekontext, und Geschäftszustand gehört zu App.
Namensrichtlinie
Projektsichtbare Schichtpräfixe verwenden, damit Aufrufer die aufrufende Ebene identifizieren können. Bevorzugen Sie Großbuchstaben für Schicht- und technische Abkürzungen, die mit Handbüchern oder etablierten Bibliotheken übereinstimmen:
APP_EventPost(...)
BSP_UART_SendDMA(...)
DRV_FlashWrite(...)
MOD_FilterProcess(...)
OSAL_QueueSendFromISR(...)
SVC_TraceInfo(...)
UTIL_CRC16Calc(...)
Großbuchstaben für Abkürzungen verwenden, die in technischen Handbüchern oder im gängigen Embedded-Vokabular großgeschrieben werden, wie UART, SPI, I2C, ADC, DMA, GPIO, PWM, CRC, RTOS, ISR, USB, CAN, IAP, APP, BSP, OSAL und HAL.
Vermeiden Sie, dass Funktionsnamen wie Makros aussehen. Bevorzugen Sie BSP_UART_SendDMA gegenüber BSP_UART_SEND_DMA. Reservieren Sie komplett in Großbuchstaben mit Unterstrichen für Makros, Konstanten, Enum-Werte und Compile-Zeit-Konfiguration.
Wenn ein vorhandenes Projekt bereits einen konsistenten Namensstil verwendet, bewahren Sie die lokale Konsistenz und erläutern Sie vorgeschlagene Migrationen separat.
CubeMX und MDK
Benutzeränderungen in USER CODE BEGIN/END-Blöcke setzen, wenn generierte Dateien geändert werden. Bevorzugt neue selbst geschriebene .c/.h-Dateien unter User hinzufügen, anstatt main.c zu erweitern.
Bei Keil MDK- und EIDE-Projekten denken Sie daran, dass das Hinzufügen einer physischen .c-Datei nicht ausreicht. Stellen Sie sicher, dass sie über die Projektgruppe/-konfiguration am Build teilnimmt, und fügen Sie Include-Pfade für Inc-Ordner und User/Config hinzu.
RTOS-Hinweise
Für FreeRTOS-Projekte Tasks nach Verantwortung, Timing und Blockierungsverhalten entwerfen. Echtzeitpfade von Logging, Speicherung, Kommunikationswiederholungen und Diagnose getrennt halten.
CMSIS-RTOS als OS-Grenze bevorzugen, wenn es das Projekt zufriedenstellt. Nur dann ein benutzerdefiniertes OSAL hinzufügen, wenn das Projekt stabile Timeout-Semantik, ISR-Varianten, Fehlercodes, statische Allokationsrichtlinie, Bare-Metal-Kompatibilität oder zukünftige Zephyr-Migration benötigt.
Native FreeRTOS-APIs nicht durch App-, Driver- oder Module-Code streuen, wenn Migration wichtig ist.
Ausgabeformat
Beim Entwurf einer Architektur in dieser Reihenfolge antworten:
- Annahmen und Randbedingungen.
- Empfohlene Schicht-/Dateiplatzierung.
- Datenfluss und Event-/Task-Modell.
- Wichtige Schnittstellen oder Pseudocode.
- Risiken, Kompromisse und Validierungsschritte.
Bei der Code-Überprüfung mit konkreten Risiken und Datei-/Schichtverletzungen vor Stilvorschlägen beginnen.