Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .claude/skills/port-stm32-platform/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,14 @@ typedef whal_<OrigPlatform><Type>_PinCfg whal_<NewPlatform><Type>_PinCfg;

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.

**Test alias** — when a driver is reused via alias AND the original driver already has a platform-specific test file (`tests/<type>/test_<origplatform>_<type>.c`), create a matching test alias at `tests/<type>/test_<newplatform>_<type>.c`:

```c
#include "test_<origplatform>_<type>.c"
```

This is the same one-line pattern as the driver stub. The build system auto-discovers `test_$(PLATFORM)_$(t).c` files and defines `WHAL_TEST_ENABLE_<TYPE>_PLATFORM`, which gates the `whal_Test_<Type>_Platform()` call in `tests/main.c`. Examples in-tree: `tests/gpio/test_stm32c0_gpio.c`, `tests/gpio/test_stm32f0_gpio.c`.

### Common driver pitfalls (from prior ports)
- **Flash**: check if already unlocked before writing keys — double-unlock hard-faults. Bit positions (LOCK, STRT, PNB) differ between families; do not copy-paste.
- **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.
Expand All @@ -158,6 +166,9 @@ Exports `extern whal_<Type>` instances and declares `Board_Init`/`Board_Deinit`/
### `board.c`
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`.

### GPIO pin conflict check
After writing the `pinCfg` array in `board.c`, scan every entry pair and verify no two entries share the same physical port+pin. This is a common mistake when a pin serves double duty (e.g., PA5 used as both an LED and SPI1_SCK). If a conflict is found, consult the chip's alternate-function table in the datasheet and remap the conflicting peripheral to an alternate pin on a different port.

### `Makefile.inc`
Model on `boards/stm32wba55cg_nucleo/Makefile.inc`:
- `PLATFORM = <platform>` — matches the prefix used in `src/*/<platform>_*.c`
Expand All @@ -171,6 +182,14 @@ Copy from a similar board and update the `MEMORY` block's FLASH/RAM origins and
### `ivt.c`
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.).

### boards/README.md

Add a row to the **Supported Boards** table in `boards/README.md` with the board name, platform, CPU core, and directory. Keep the table sorted alphabetically by platform name.

### GitHub CI

Add the new board to `.github/workflows/boards.yml` by appending it to the `board` matrix list. This ensures the board builds are verified on every PR and push to main. If the board supports peripheral devices (BMI270, SPI-NOR, etc.), also add entries to `.github/workflows/peripheral-tests.yml`. If it supports watchdog, add entries to `.github/workflows/watchdog-tests.yml`.

## Phase 5 — Build and validate

1. `make BOARD=<board_name>` from the repo root. Fix errors in order. Typical failures:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/boards.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
board: [stm32wb55xx_nucleo, pic32cz_curiosity_ultra, stm32h563zi_nucleo, stm32f411_blackpill, stm32c031_nucleo]
board: [stm32wb55xx_nucleo, stm32wba55cg_nucleo, pic32cz_curiosity_ultra, stm32h563zi_nucleo, stm32f411_blackpill, stm32c031_nucleo, stm32f091rc_nucleo]
extra_cflags: ["", "-DWHAL_CFG_NO_TIMEOUT"]
include:
- board: stm32wb55xx_nucleo
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/watchdog-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ jobs:
watchdog: iwdg
- board: stm32wb55xx_nucleo
watchdog: wwdg
- board: stm32f091rc_nucleo
watchdog: iwdg
steps:
- uses: actions/checkout@v4

Expand Down
2 changes: 2 additions & 0 deletions boards/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ build configuration.
|-------|----------|-----|-----------|
| Microchip PIC32CZ CA Curiosity Ultra | PIC32CZ | Cortex-M7 | `pic32cz_curiosity_ultra/` |
| ST NUCLEO-C031C6 | STM32C0 | Cortex-M0+ | `stm32c031_nucleo/` |
| ST NUCLEO-F091RC | STM32F0 | Cortex-M0 | `stm32f091rc_nucleo/` |
| WeAct BlackPill STM32F411 | STM32F4 | Cortex-M4 | `stm32f411_blackpill/` |
| ST NUCLEO-H563ZI | STM32H5 | Cortex-M33 | `stm32h563zi_nucleo/` |
| ST NUCLEO-WB55RG | STM32WB | Cortex-M4 | `stm32wb55xx_nucleo/` |
| ST NUCLEO-WBA55CG | STM32WBA | Cortex-M33 | `stm32wba55cg_nucleo/` |

## Board Directory Contents

Expand Down
41 changes: 41 additions & 0 deletions boards/stm32f091rc_nucleo/Makefile.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
_BOARD_DIR := $(patsubst %/,%,$(dir $(lastword $(MAKEFILE_LIST))))

PLATFORM = stm32f0
TESTS ?= clock gpio timer flash uart spi

GCC = $(GCC_PATH)arm-none-eabi-gcc
LD = $(GCC_PATH)arm-none-eabi-gcc
OBJCOPY = $(GCC_PATH)arm-none-eabi-objcopy

CFLAGS += -Wall -Werror $(INCLUDE) -g3 \
-ffreestanding -nostdlib -mcpu=cortex-m0 -mthumb \
-DPLATFORM_STM32F0 -MMD -MP \
-DWHAL_CFG_GPIO_API_MAPPING_STM32F0 \
-DWHAL_CFG_CLOCK_API_MAPPING_STM32F0 \
-DWHAL_CFG_UART_API_MAPPING_STM32F0 \
-DWHAL_CFG_SPI_API_MAPPING_STM32F0 \
-DWHAL_CFG_I2C_API_MAPPING_STM32F0 \
$(if $(filter iwdg,$(WATCHDOG)),-DBOARD_WATCHDOG_IWDG)
LDFLAGS = -mcpu=cortex-m0 -mthumb -ffreestanding -nostartfiles \
-Wl,--omagic -static

LINKER_SCRIPT ?= $(_BOARD_DIR)/linker.ld

INCLUDE += -I$(_BOARD_DIR) -I$(WHAL_DIR)/boards/peripheral

BOARD_SOURCE = $(_BOARD_DIR)/ivt.c
BOARD_SOURCE += $(_BOARD_DIR)/board.c
BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*.c)
BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/timer.c)
BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/supply.c)
BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/flash.c)
BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/rng.c)
BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/crypto.c)
BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/sensor.c)
BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/block.c)
BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/watchdog.c)
BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/stm32f0_*.c)
BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/systick.c)

# Peripheral devices
include $(WHAL_DIR)/boards/peripheral/Makefile.inc
278 changes: 278 additions & 0 deletions boards/stm32f091rc_nucleo/board.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
#include <stdint.h>
#include <stddef.h>
#include "board.h"
#include <wolfHAL/platform/st/stm32f091xx.h>
#include "peripheral.h"

volatile uint32_t g_tick = 0;
volatile uint8_t g_waiting = 0;
volatile uint8_t g_tickOverflow = 0;

void SysTick_Handler()
{
uint32_t tickBefore = g_tick++;
if (g_waiting) {
if (tickBefore > g_tick)
g_tickOverflow = 1;
}
}

uint32_t Board_GetTick(void)
{
return g_tick;
}

whal_Timeout g_whalTimeout = {
.timeoutTicks = 1000,
.GetTick = Board_GetTick,
};

/* Clock — PLL at 48 MHz (HSI/2 * 12) */
whal_Clock g_whalClock = {
.regmap = { WHAL_STM32F091_RCC_REGMAP },

.cfg = &(whal_Stm32f0Rcc_Cfg) {
.sysClkSrc = WHAL_STM32F0_RCC_SYSCLK_SRC_PLL,
.pllCfg = &(whal_Stm32f0Rcc_PllCfg) {
.clkSrc = WHAL_STM32F0_RCC_PLLSRC_HSI_DIV2,
.prediv = 1,
.pllmul = 12,
},
},
};

static const whal_Stm32f0Rcc_Clk g_clocks[] = {
{WHAL_STM32F091_GPIOA_CLOCK},
{WHAL_STM32F091_GPIOB_CLOCK},
{WHAL_STM32F091_GPIOC_CLOCK},
{WHAL_STM32F091_USART2_CLOCK},
{WHAL_STM32F091_SPI1_CLOCK},
{WHAL_STM32F091_I2C1_CLOCK},
};
#define CLOCK_COUNT (sizeof(g_clocks) / sizeof(g_clocks[0]))

/* GPIO */
whal_Gpio g_whalGpio = {
.regmap = { WHAL_STM32F091_GPIO_REGMAP },

.cfg = &(whal_Stm32f0Gpio_Cfg) {
.pinCfg = (whal_Stm32f0Gpio_PinCfg[PIN_COUNT]) {
/* LD2 Green LED on PA5 */
[LED_PIN] = WHAL_STM32F0_GPIO_PIN(
WHAL_STM32F0_GPIO_PORT_A, 5, WHAL_STM32F0_GPIO_MODE_OUT,
WHAL_STM32F0_GPIO_OUTTYPE_PUSHPULL, WHAL_STM32F0_GPIO_SPEED_LOW,
WHAL_STM32F0_GPIO_PULL_NONE, 0),
/* USART2 TX on PA2, AF1 */
[UART_TX_PIN] = WHAL_STM32F0_GPIO_PIN(
WHAL_STM32F0_GPIO_PORT_A, 2, WHAL_STM32F0_GPIO_MODE_ALTFN,
WHAL_STM32F0_GPIO_OUTTYPE_PUSHPULL, WHAL_STM32F0_GPIO_SPEED_FAST,
WHAL_STM32F0_GPIO_PULL_UP, 1),
/* USART2 RX on PA3, AF1 */
[UART_RX_PIN] = WHAL_STM32F0_GPIO_PIN(
WHAL_STM32F0_GPIO_PORT_A, 3, WHAL_STM32F0_GPIO_MODE_ALTFN,
WHAL_STM32F0_GPIO_OUTTYPE_PUSHPULL, WHAL_STM32F0_GPIO_SPEED_FAST,
WHAL_STM32F0_GPIO_PULL_UP, 1),
/* SPI1 SCK on PB3, AF0 */
[SPI_SCK_PIN] = WHAL_STM32F0_GPIO_PIN(
WHAL_STM32F0_GPIO_PORT_B, 3, WHAL_STM32F0_GPIO_MODE_ALTFN,
WHAL_STM32F0_GPIO_OUTTYPE_PUSHPULL, WHAL_STM32F0_GPIO_SPEED_FAST,
WHAL_STM32F0_GPIO_PULL_NONE, 0),
/* SPI1 MISO on PB4, AF0 */
[SPI_MISO_PIN] = WHAL_STM32F0_GPIO_PIN(
WHAL_STM32F0_GPIO_PORT_B, 4, WHAL_STM32F0_GPIO_MODE_ALTFN,
WHAL_STM32F0_GPIO_OUTTYPE_PUSHPULL, WHAL_STM32F0_GPIO_SPEED_FAST,
WHAL_STM32F0_GPIO_PULL_NONE, 0),
/* SPI1 MOSI on PB5, AF0 */
[SPI_MOSI_PIN] = WHAL_STM32F0_GPIO_PIN(
WHAL_STM32F0_GPIO_PORT_B, 5, WHAL_STM32F0_GPIO_MODE_ALTFN,
WHAL_STM32F0_GPIO_OUTTYPE_PUSHPULL, WHAL_STM32F0_GPIO_SPEED_FAST,
WHAL_STM32F0_GPIO_PULL_NONE, 0),
/* SPI CS on PB6, output, push-pull */
[SPI_CS_PIN] = WHAL_STM32F0_GPIO_PIN(
WHAL_STM32F0_GPIO_PORT_B, 6, WHAL_STM32F0_GPIO_MODE_OUT,
WHAL_STM32F0_GPIO_OUTTYPE_PUSHPULL, WHAL_STM32F0_GPIO_SPEED_FAST,
WHAL_STM32F0_GPIO_PULL_UP, 0),
/* I2C1 SCL on PB8, AF1, open-drain */
[I2C_SCL_PIN] = WHAL_STM32F0_GPIO_PIN(
WHAL_STM32F0_GPIO_PORT_B, 8, WHAL_STM32F0_GPIO_MODE_ALTFN,
WHAL_STM32F0_GPIO_OUTTYPE_OPENDRAIN, WHAL_STM32F0_GPIO_SPEED_FAST,
WHAL_STM32F0_GPIO_PULL_UP, 1),
/* I2C1 SDA on PB9, AF1, open-drain */
[I2C_SDA_PIN] = WHAL_STM32F0_GPIO_PIN(
WHAL_STM32F0_GPIO_PORT_B, 9, WHAL_STM32F0_GPIO_MODE_ALTFN,
WHAL_STM32F0_GPIO_OUTTYPE_OPENDRAIN, WHAL_STM32F0_GPIO_SPEED_FAST,
WHAL_STM32F0_GPIO_PULL_UP, 1),
},
.pinCount = PIN_COUNT,
},
};

/* Timer — SysTick at 1 ms */
whal_Timer g_whalTimer = {
.regmap = { WHAL_CORTEX_M0_SYSTICK_REGMAP },
.driver = WHAL_CORTEX_M0_SYSTICK_DRIVER,

.cfg = &(whal_SysTick_Cfg) {
.cyclesPerTick = 48000000 / 1000,
.clkSrc = WHAL_SYSTICK_CLKSRC_SYSCLK,
.tickInt = WHAL_SYSTICK_TICKINT_ENABLED,
},
};

/* UART — USART2 at 115200 baud */
whal_Uart g_whalUart = {
.regmap = { WHAL_STM32F091_USART2_REGMAP },

.cfg = &(whal_Stm32f0Uart_Cfg) {
.timeout = &g_whalTimeout,
.brr = WHAL_STM32F0_UART_BRR(48000000, 115200),
},
};

/* SPI */
whal_Spi g_whalSpi = {
.regmap = { WHAL_STM32F091_SPI1_REGMAP },

.cfg = &(whal_Stm32f0Spi_Cfg) {
.pclk = 48000000,
.timeout = &g_whalTimeout,
},
};

/* I2C — I2C1 */
whal_I2c g_whalI2c = {
.regmap = { WHAL_STM32F091_I2C1_REGMAP },

.cfg = &(whal_Stm32f0I2c_Cfg) {
.pclk = 48000000,
.timeout = &g_whalTimeout,
},
};

/* Flash — 256 KB */
whal_Flash g_whalFlash = {
.regmap = { WHAL_STM32F091_FLASH_REGMAP },
.driver = WHAL_STM32F091_FLASH_DRIVER,

.cfg = &(whal_Stm32f0Flash_Cfg) {
.startAddr = 0x08000000,
.size = 0x40000,
.timeout = &g_whalTimeout,
},
};

#ifdef BOARD_WATCHDOG_IWDG
whal_Watchdog g_whalWatchdog = {
.regmap = { WHAL_STM32F091_IWDG_REGMAP },
.driver = WHAL_STM32F091_IWDG_DRIVER,

.cfg = &(whal_Stm32f0Iwdg_Cfg) {
.prescaler = WHAL_STM32F0_IWDG_PR_64,
.reload = 500,
.timeout = &g_whalTimeout,
},
};
#endif

void Board_WaitMs(size_t ms)
{
uint32_t startCount = g_tick;
while ((g_tick - startCount) < ms)
;
}

whal_Error Board_Init(void)
{
whal_Error err;

/* Set flash latency before increasing clock speed */
err = whal_Stm32f0Flash_Ext_SetLatency(&g_whalFlash,
WHAL_STM32F0_FLASH_LATENCY_1);
if (err)
return err;

err = whal_Clock_Init(&g_whalClock);
if (err)
return err;

for (size_t i = 0; i < CLOCK_COUNT; i++) {
err = whal_Clock_Enable(&g_whalClock, &g_clocks[i]);
if (err)
return err;
}

err = whal_Gpio_Init(&g_whalGpio);
if (err)
return err;

err = whal_Uart_Init(&g_whalUart);
if (err)
return err;

err = whal_Spi_Init(&g_whalSpi);
if (err)
return err;

err = whal_I2c_Init(&g_whalI2c);
if (err)
return err;

err = whal_Timer_Init(&g_whalTimer);
if (err)
return err;

err = whal_Timer_Start(&g_whalTimer);
if (err)
return err;

err = Peripheral_Init();
if (err)
return err;

return WHAL_SUCCESS;
}

whal_Error Board_Deinit(void)
{
whal_Error err;

err = Peripheral_Deinit();
if (err)
return err;

err = whal_Timer_Stop(&g_whalTimer);
if (err)
return err;

err = whal_Timer_Deinit(&g_whalTimer);
if (err)
return err;

err = whal_Spi_Deinit(&g_whalSpi);
if (err)
return err;

err = whal_I2c_Deinit(&g_whalI2c);
if (err)
return err;

err = whal_Uart_Deinit(&g_whalUart);
if (err)
return err;

err = whal_Gpio_Deinit(&g_whalGpio);
if (err)
return err;

for (size_t i = 0; i < CLOCK_COUNT; i++) {
err = whal_Clock_Disable(&g_whalClock, &g_clocks[i]);
if (err)
return err;
}

err = whal_Clock_Deinit(&g_whalClock);
if (err)
return err;

return WHAL_SUCCESS;
}
Loading
Loading