|
| 1 | +--- |
| 2 | +name: port-stm32-platform |
| 3 | +description: Port a new STM32 chip family to wolfHAL. Creates the platform header, scaffolds new drivers, adds alias header + stub .c for reused drivers, and optionally wires up a first board. |
| 4 | +argument-hint: <platform-short-name> [chip-name] [board-name] |
| 5 | +--- |
| 6 | + |
| 7 | +# Port a new STM32 chip family to wolfHAL |
| 8 | + |
| 9 | +You are helping the user port a new STM32 chip family (e.g., `stm32g4`, `stm32u5`, `stm32l5`) to wolfHAL. A "platform" in wolfHAL is a family of register-compatible chips that share drivers (e.g., `stm32wba` covers wba52/wba54/wba55). A specific chip (e.g., `stm32wba55cg`) gets its own platform header with concrete base addresses and clock macros. A board (e.g., `stm32wba55cg_nucleo`) ties a chip to a physical dev board. |
| 10 | + |
| 11 | +Arguments passed after the skill name identify the scope: |
| 12 | +- `$1`: platform short name (required, e.g., `stm32g4`) |
| 13 | +- `$2`: specific chip (optional, e.g., `stm32g431cb`) |
| 14 | +- `$3`: board name (optional, e.g., `stm32g431_nucleo`) |
| 15 | + |
| 16 | +If any are missing, ask the user before starting the relevant phase. |
| 17 | + |
| 18 | +## Prerequisites — gather before starting |
| 19 | + |
| 20 | +Ask the user to provide (or confirm paths to): |
| 21 | + |
| 22 | +1. **Reference Manual (TRM) PDF** for the chip family — e.g., `stm32g4_trm.pdf`. Place at repo root. |
| 23 | +2. **Datasheet PDF** for the specific chip — e.g., `stm32g431cb.pdf`. Place at repo root. |
| 24 | +3. **Board user guide PDF** if adding a board — e.g., `nucleo_g431.pdf`. Place at repo root. |
| 25 | + |
| 26 | +Do NOT proceed with register layout work without the TRM — guessing from similar chips is the #1 source of silent bugs. STM32 family differences (bit positions, register offsets, new CONDRST/FIFO/voltage-scaling fields) are subtle and costly to debug later. |
| 27 | + |
| 28 | +## Phase 1 — Discovery (read-only, no code changes) |
| 29 | + |
| 30 | +Goal: figure out what is register-compatible with an existing wolfHAL driver vs what needs a new driver. |
| 31 | + |
| 32 | +For each device type below, use `pdftotext -layout -f <start> -l <end> <trm.pdf>` on the TRM to extract the register map page, then compare against existing wolfHAL drivers in `src/<type>/`: |
| 33 | + |
| 34 | +| Device type | Existing drivers to diff against | What to check in TRM | |
| 35 | +|---|---|---| |
| 36 | +| Clock/RCC | `stm32wb_rcc.c`, `stm32wba_rcc.c`, `stm32h5_rcc.c`, `stm32f4_rcc.c` | PLL config register (PLLCFGR split vs unified), voltage scaling (PWR_VOSR), AHB/APB prescaler fields, HSI/HSE/LSI/LSE, peripheral clock enable register offsets (AHBxENR, APBxENR) | |
| 37 | +| GPIO | `stm32wb_gpio.c`, `stm32f4_gpio.c` | MODER/OTYPER/OSPEEDR/PUPDR/AFRL/AFRH — usually compatible across STM32 | |
| 38 | +| UART | `stm32wb_uart.c`, `stm32h5_uart.c`, `stm32c0_uart.c`, `stm32f4_uart.c` | BRR calc (16x vs 8x oversample), FIFO enable (CR1.FIFOEN bit 29), ISR/ICR flags, TDR/RDR offsets | |
| 39 | +| Flash | `stm32wb_flash.c`, `stm32wba_flash.c`, `stm32h5_flash.c`, `stm32f4_flash.c` | NSKEYR/KEYR unlock sequence, SR bit positions (BSY, EOP, errors), CR bit positions (PG, PER, STRT, LOCK, PNB), page size | |
| 40 | +| RNG | `stm32wb_rng.c`, `stm32wba_rng.c` | CR bits (RNGEN, CED, CONDRST), SR bits (DRDY, errors), clock source selection register (CCIPRx RNGSEL) | |
| 41 | +| I2C | `stm32wb_i2c.c` | CR1/CR2/TIMINGR — usually compatible on modern STM32 (V2 I2C) | |
| 42 | +| SPI | `stm32wb_spi.c`, `stm32h5_spi.c`, `stm32f4_spi.c` | CR1/CR2/SR layout (V1 SPI differs from V2 SPI used on newer chips) | |
| 43 | +| AES/crypto | `stm32wb_aes.c` | CR (KEYSIZE, MODE, CHMOD, ALGOMODE, DATATYPE), KEYR, IVR, DIN/DOUT | |
| 44 | +| DMA | `stm32wb_dma.c` (classic DMA+DMAMUX), `stm32wba_gpdma.c` (GPDMA) | Is it DMA+DMAMUX (older) or GPDMA (newer)? Not compatible. GPDMA appears on U5/WBA/H5/C0/G0 variants. | |
| 45 | +| Timer | `systick.c` (shared) | SysTick is architectural (Cortex-M), works unchanged | |
| 46 | +| IRQ | `cortex_m4_nvic.c`, `cortex_m33_nvic.c` | NVIC is architectural per core (M4 vs M33) | |
| 47 | +| Watchdog | `stm32wb_iwdg.c`, `stm32wb_wwdg.c` | IWDG/WWDG are very stable across STM32 | |
| 48 | + |
| 49 | +For each device type, decide **reuse existing driver (with alias)** OR **write a new driver**. Write the decision to a checklist and show the user before starting Phase 2. |
| 50 | + |
| 51 | +The clock driver is almost always new per family. GPIO, I2C, and IWDG/WWDG are almost always reusable. UART, flash, RNG, DMA require careful diffing. |
| 52 | + |
| 53 | +## Phase 2 — Platform header |
| 54 | + |
| 55 | +Create `wolfHAL/platform/st/<chip>.h` (e.g., `stm32g431cb.h`). Reference: `stm32wba55cg.h` (GPDMA chip) or `stm32wb55xx.h` (DMA+DMAMUX chip). |
| 56 | + |
| 57 | +Conventions: |
| 58 | +1. Every driver include uses the **new platform's prefix** — for reused drivers that means the alias header created in Phase 3, not the original family's header. |
| 59 | +2. Every device macro uses `.driver = &whal_<NewPlatform><Type>_Driver` — the alias header `#define`s this to the original symbol at preprocess time, so no renaming of actual code is needed but callers see the new-prefix name. |
| 60 | +3. Defines clock enable macros and, when applicable, DMA request mapping macros. |
| 61 | + |
| 62 | +Skeleton: |
| 63 | + |
| 64 | +```c |
| 65 | +#include <wolfHAL/platform/arm/cortex_m4.h> /* or cortex_m33.h / cortex_m7.h */ |
| 66 | + |
| 67 | +/* Every include uses the new platform's prefix — aliased or newly written */ |
| 68 | +#include <wolfHAL/clock/<platform>_rcc.h> |
| 69 | +#include <wolfHAL/gpio/<platform>_gpio.h> |
| 70 | +#include <wolfHAL/uart/<platform>_uart.h> |
| 71 | +/* ...one include per device type... */ |
| 72 | + |
| 73 | +#define WHAL_<PLATFORM>_USART1_DEVICE \ |
| 74 | + .regmap = { .base = 0x<addr>, .size = 0x400 }, \ |
| 75 | + .driver = &whal_<Platform>Uart_Driver |
| 76 | + |
| 77 | +#define WHAL_<PLATFORM>_USART1_CLOCK \ |
| 78 | + .regOffset = 0x<offset>, \ |
| 79 | + .enableMask = (1UL << <bit>), \ |
| 80 | + .enablePos = <bit> |
| 81 | +``` |
| 82 | +
|
| 83 | +## Phase 3 — Drivers |
| 84 | +
|
| 85 | +### For each device type marked "write new" |
| 86 | +
|
| 87 | +1. Read `docs/writing_a_driver.md` for the driver pattern. |
| 88 | +2. Copy the closest existing driver as a starting point. |
| 89 | +3. Update register offsets, bit positions, and sequences per the TRM. **Cross-check register-map diagrams against the textual bit descriptions** — they sometimes disagree, and the textual description is authoritative. |
| 90 | +4. Match the existing naming: `whal_<Platform><Type>_<Func>` for functions, `whal_<Platform><Type>_Driver` for the vtable, `whal_<Platform><Type>_Cfg` for the config struct. |
| 91 | +5. Place files at `wolfHAL/<type>/<platform>_<type>.h` and `src/<type>/<platform>_<type>.c`. |
| 92 | +6. Do not add cross-driver calls from inside a driver — clock enables, power sequencing, flash wait states, pin muxing are the board's responsibility. |
| 93 | +
|
| 94 | +### For each device type marked "reuse existing driver" — create an alias header + stub .c |
| 95 | +
|
| 96 | +Reference pattern in-tree: `wolfHAL/gpio/stm32f4_gpio.h` (alias header) and `src/gpio/stm32f4_gpio.c` (stub). Every symbol the caller sees must carry the new platform's prefix; aliasing is done at preprocess time via `typedef` and `#define`, so no code is duplicated and no GCC alias attribute is needed. |
| 97 | +
|
| 98 | +**Alias header** — `wolfHAL/<type>/<newplatform>_<type>.h`: |
| 99 | +
|
| 100 | +```c |
| 101 | +#ifndef WHAL_<NEWPLATFORM>_<TYPE>_H |
| 102 | +#define WHAL_<NEWPLATFORM>_<TYPE>_H |
| 103 | +
|
| 104 | +/* |
| 105 | + * @file <newplatform>_<type>.h |
| 106 | + * @brief <NewPlatform> <Type> driver (alias for <OrigPlatform> <Type>). |
| 107 | + * |
| 108 | + * The <NewPlatform> <Type> peripheral is register-compatible with the |
| 109 | + * <OrigPlatform> <Type>. This header re-exports the <OrigPlatform> driver |
| 110 | + * types and symbols under <NewPlatform>-specific names. The underlying |
| 111 | + * implementation is shared. |
| 112 | + */ |
| 113 | +
|
| 114 | +#include <wolfHAL/<type>/<origplatform>_<type>.h> |
| 115 | +
|
| 116 | +/* Type aliases — one typedef per exposed type from the original header */ |
| 117 | +typedef whal_<OrigPlatform><Type>_Cfg whal_<NewPlatform><Type>_Cfg; |
| 118 | +typedef whal_<OrigPlatform><Type>_PinCfg whal_<NewPlatform><Type>_PinCfg; |
| 119 | +/* ...repeat for every exposed type... */ |
| 120 | +
|
| 121 | +/* Driver and function aliases — one #define per exposed function/driver */ |
| 122 | +#define whal_<NewPlatform><Type>_Driver whal_<OrigPlatform><Type>_Driver |
| 123 | +#define whal_<NewPlatform><Type>_Init whal_<OrigPlatform><Type>_Init |
| 124 | +#define whal_<NewPlatform><Type>_Deinit whal_<OrigPlatform><Type>_Deinit |
| 125 | +/* ...repeat for every exposed function... */ |
| 126 | +
|
| 127 | +/* Macro / enum-value aliases — one #define per user-facing macro */ |
| 128 | +#define WHAL_<NEWPLATFORM>_<TYPE>_<CONST1> WHAL_<ORIGPLATFORM>_<TYPE>_<CONST1> |
| 129 | +/* ...repeat for every mode selector, port letter, pin-packing macro, etc... */ |
| 130 | +
|
| 131 | +#endif /* WHAL_<NEWPLATFORM>_<TYPE>_H */ |
| 132 | +``` |
| 133 | + |
| 134 | +**Stub .c** — `src/<type>/<newplatform>_<type>.c`: |
| 135 | + |
| 136 | +```c |
| 137 | +#include "<origplatform>_<type>.c" |
| 138 | +``` |
| 139 | + |
| 140 | +That is the entire file. Examples in-tree: `src/gpio/stm32f4_gpio.c`, `src/gpio/stm32wba_gpio.c`, `src/i2c/stm32wba_i2c.c`, `src/uart/stm32wba_uart.c`, `src/watchdog/stm32wba_iwdg.c` — each is one line. The stub exists so the board's Makefile wildcard (`src/*/<newplatform>_*.c`) compiles the original implementation under the new-prefix filename. The original `.c` is NOT added to the Makefile separately; the `#include` pulls it into this translation unit exactly once. |
| 141 | + |
| 142 | +### Common driver pitfalls (from prior ports) |
| 143 | +- **Flash**: check if already unlocked before writing keys — double-unlock hard-faults. Bit positions (LOCK, STRT, PNB) differ between families; do not copy-paste. |
| 144 | +- **RNG**: CONDRST + per-config register sequence goes in Init, not per Generate call. Select RNG clock source via `RCC_CCIPR`/`CCIPR2` before Init — default is often LSE, which requires LSE running. |
| 145 | +- **Clock**: on chips with voltage scaling, transition to VOS Range 1 BEFORE raising SYSCLK above ~16 MHz; skipping this hard-faults at the first PLL switch. |
| 146 | +- **DMA + USART**: enable `CR1.FIFOEN` when available — DMA→USART without FIFO has produced silent byte drops. |
| 147 | +- **Polling loops**: every hardware-flag wait must go through `whal_Timeout` — bare `while` loops violate a repo-wide preference. |
| 148 | +- **One-time hardware setup** (clock sources, trimming, feature selects): belongs in Init, not in per-operation functions. |
| 149 | + |
| 150 | +## Phase 4 — Board (optional, only if $3 provided) |
| 151 | + |
| 152 | +Create `boards/<board_name>/` with: |
| 153 | + |
| 154 | +### `board.h` |
| 155 | +Exports `extern whal_<Type>` instances and declares `Board_Init`/`Board_Deinit`/`Board_WaitMs`. Follow `docs/adding_a_board.md`. |
| 156 | + |
| 157 | +### `board.c` |
| 158 | +Define each `whal_<Type>` global with its platform macro + board-specific config (pins, baud rates, timeout, DMA channel assignments). Implement `Board_Init` in dependency order: PWR → Clock → peripheral clock enables → GPIO → UART → Timer → the rest. Keep the watchdog out of `Board_Init` (the app starts it when ready to refresh) per `docs/adding_a_board.md`. Guard DMA-specific setup under `#ifdef BOARD_DMA`, matching `boards/stm32wba55cg_nucleo/board.c`. |
| 159 | + |
| 160 | +### `Makefile.inc` |
| 161 | +Model on `boards/stm32wba55cg_nucleo/Makefile.inc`: |
| 162 | +- `PLATFORM = <platform>` — matches the prefix used in `src/*/<platform>_*.c` |
| 163 | +- `TESTS ?= clock gpio flash timer rng crypto uart spi i2c irq` — trim to what the board supports |
| 164 | +- `CFLAGS` — `-mcpu=cortex-m4`/`cortex-m33` to match the core, `-DPLATFORM_<UPPER>` for platform ifdefs |
| 165 | +- `BOARD_SOURCE` wildcards over `$(WHAL_DIR)/src/*/<platform>_*.c` — picks up both native drivers and the alias stub `.c` files created in Phase 3. **Do NOT also wildcard over the original family's prefix**, or the original `.c` will compile twice (once directly, once through the stub's include) and you'll get duplicate-symbol errors at link time. |
| 166 | + |
| 167 | +### `linker.ld` |
| 168 | +Copy from a similar board and update the `MEMORY` block's FLASH/RAM origins and lengths from the chip's datasheet. |
| 169 | + |
| 170 | +### `ivt.c` |
| 171 | +Copy from a similar board with the same core (M4/M33). Update the vector table — vectors from position 16 onward are device-specific and listed in the TRM's interrupt mapping table. At minimum: SysTick plus any peripheral IRQs the tests exercise (USART1_IRQHandler, GPDMAx_ChannelY_IRQHandler, etc.). |
| 172 | + |
| 173 | +## Phase 5 — Build and validate |
| 174 | + |
| 175 | +1. `make BOARD=<board_name>` from the repo root. Fix errors in order. Typical failures: |
| 176 | + - Missing symbol for an aliased driver → the alias header is missing a `#define` for that function/type, or the stub `.c` isn't present. |
| 177 | + - Duplicate symbol → the Makefile is picking up both `<newplatform>_*.c` and the original `<origplatform>_*.c`; restrict the wildcard to the new prefix only. |
| 178 | + - Implicit declaration warnings for `whal_Reg_*` / `whal_SetBits` in a stub include → you probably wrote `#include <wolfHAL/...>` with angle brackets in the stub instead of `#include "<origplatform>_<type>.c"` with quotes. The stub must use quoted include so the preprocessor finds the sibling `.c` in the same directory. |
| 179 | +2. Flash the binary (user runs this) and run the test suite. Each suite prints `PASS`, `FAIL`, or `SKIP`. |
| 180 | +3. If UART output is garbled or missing bytes and the chip uses DMA, enable `CR1.FIFOEN` in the UART init before chasing further — DMA→USART without FIFO has caused byte drops on WBA. |
| 181 | + |
| 182 | +## Output to the user |
| 183 | + |
| 184 | +At each phase transition, summarize in under 10 lines: |
| 185 | +- What was done |
| 186 | +- Which existing drivers were reused (and note the alias header + stub `.c` pair created for each) vs newly written |
| 187 | +- What the user should verify or flash |
| 188 | + |
| 189 | +Wait for the user to confirm before moving to the next phase, unless they asked for an all-in-one run upfront. |
0 commit comments