diff --git a/.github/workflows/boards.yml b/.github/workflows/boards.yml new file mode 100644 index 0000000..71d3980 --- /dev/null +++ b/.github/workflows/boards.yml @@ -0,0 +1,31 @@ +name: Board Builds + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + boards: + runs-on: ubuntu-latest + strategy: + matrix: + board: [stm32wb55xx_nucleo, pic32cz_curiosity_ultra, stm32h563zi_nucleo, stm32f411_blackpill, stm32c031_nucleo] + extra_cflags: ["", "-DWHAL_CFG_NO_TIMEOUT"] + include: + - board: stm32wb55xx_nucleo + extra_cflags: "-DBOARD_DMA" + steps: + - uses: actions/checkout@v4 + + - name: Install ARM toolchain + run: sudo apt-get update && sudo apt-get install -y gcc-arm-none-eabi + + - name: Build blinky + working-directory: examples/blinky + run: CFLAGS="${{ matrix.extra_cflags }}" make BOARD=${{ matrix.board }} + + - name: Build tests + working-directory: tests + run: CFLAGS="${{ matrix.extra_cflags }}" make BOARD=${{ matrix.board }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 50a2b11..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,67 +0,0 @@ -name: CI - -on: - push: - branches: [main] - pull_request: - branches: [main] - -jobs: - core-tests: - runs-on: ubuntu-latest - strategy: - matrix: - extra_cflags: ["", "-DWHAL_CFG_NO_TIMEOUT"] - steps: - - uses: actions/checkout@v4 - - - name: Build and run core tests - working-directory: tests/core - run: CFLAGS="${{ matrix.extra_cflags }}" make run - - boards: - runs-on: ubuntu-latest - strategy: - matrix: - board: [stm32wb55xx_nucleo, pic32cz_curiosity_ultra, stm32h563zi_nucleo, stm32f411_blackpill, stm32c031_nucleo] - extra_cflags: ["", "-DWHAL_CFG_NO_TIMEOUT"] - include: - - board: stm32wb55xx_nucleo - extra_cflags: "-DBOARD_DMA" - steps: - - uses: actions/checkout@v4 - - - name: Install ARM toolchain - run: sudo apt-get update && sudo apt-get install -y gcc-arm-none-eabi - - - name: Build blinky - working-directory: examples/blinky - run: CFLAGS="${{ matrix.extra_cflags }}" make BOARD=${{ matrix.board }} - - - name: Build tests - working-directory: tests - run: CFLAGS="${{ matrix.extra_cflags }}" make BOARD=${{ matrix.board }} - - peripheral-tests: - runs-on: ubuntu-latest - strategy: - matrix: - include: - - board: stm32wb55xx_nucleo - peripherals: bmi270 - tests: bmi270 - - board: stm32wb55xx_nucleo - peripherals: spi_nor_w25q64 - tests: flash - - board: stm32wb55xx_nucleo - peripherals: sdhc_spi_sdcard32gb - tests: block - steps: - - uses: actions/checkout@v4 - - - name: Install ARM toolchain - run: sudo apt-get update && sudo apt-get install -y gcc-arm-none-eabi - - - name: Build tests - working-directory: tests - run: make BOARD=${{ matrix.board }} PERIPHERALS="${{ matrix.peripherals }}" TESTS="${{ matrix.tests }}" diff --git a/.github/workflows/core-tests.yml b/.github/workflows/core-tests.yml new file mode 100644 index 0000000..f122277 --- /dev/null +++ b/.github/workflows/core-tests.yml @@ -0,0 +1,20 @@ +name: Core Tests + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + core-tests: + runs-on: ubuntu-latest + strategy: + matrix: + extra_cflags: ["", "-DWHAL_CFG_NO_TIMEOUT"] + steps: + - uses: actions/checkout@v4 + + - name: Build and run core tests + working-directory: tests/core + run: CFLAGS="${{ matrix.extra_cflags }}" make run diff --git a/.github/workflows/peripheral-tests.yml b/.github/workflows/peripheral-tests.yml new file mode 100644 index 0000000..a53c97b --- /dev/null +++ b/.github/workflows/peripheral-tests.yml @@ -0,0 +1,32 @@ +name: Peripheral Tests + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + peripheral-tests: + runs-on: ubuntu-latest + strategy: + matrix: + include: + - board: stm32wb55xx_nucleo + peripherals: bmi270 + tests: bmi270 + - board: stm32wb55xx_nucleo + peripherals: spi_nor_w25q64 + tests: flash + - board: stm32wb55xx_nucleo + peripherals: sdhc_spi_sdcard32gb + tests: block + steps: + - uses: actions/checkout@v4 + + - name: Install ARM toolchain + run: sudo apt-get update && sudo apt-get install -y gcc-arm-none-eabi + + - name: Build tests + working-directory: tests + run: make BOARD=${{ matrix.board }} PERIPHERALS="${{ matrix.peripherals }}" TESTS="${{ matrix.tests }}" diff --git a/.github/workflows/watchdog-tests.yml b/.github/workflows/watchdog-tests.yml new file mode 100644 index 0000000..f3668dd --- /dev/null +++ b/.github/workflows/watchdog-tests.yml @@ -0,0 +1,27 @@ +name: Watchdog Tests + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + watchdog-tests: + runs-on: ubuntu-latest + strategy: + matrix: + include: + - board: stm32wb55xx_nucleo + watchdog: iwdg + - board: stm32wb55xx_nucleo + watchdog: wwdg + steps: + - uses: actions/checkout@v4 + + - name: Install ARM toolchain + run: sudo apt-get update && sudo apt-get install -y gcc-arm-none-eabi + + - name: Build tests + working-directory: tests + run: make BOARD=${{ matrix.board }} TESTS=watchdog WATCHDOG=${{ matrix.watchdog }} diff --git a/boards/stm32wb55xx_nucleo/Makefile.inc b/boards/stm32wb55xx_nucleo/Makefile.inc index 2b20420..5a9f133 100644 --- a/boards/stm32wb55xx_nucleo/Makefile.inc +++ b/boards/stm32wb55xx_nucleo/Makefile.inc @@ -10,7 +10,9 @@ OBJCOPY = $(GCC_PATH)arm-none-eabi-objcopy CFLAGS += -Wall -Werror $(INCLUDE) -g3 \ -ffreestanding -nostdlib -mcpu=cortex-m4 \ -DPLATFORM_STM32WB -MMD -MP \ - $(if $(DMA),-DBOARD_DMA) + $(if $(DMA),-DBOARD_DMA) \ + $(if $(filter iwdg,$(WATCHDOG)),-DBOARD_WATCHDOG_IWDG) \ + $(if $(filter wwdg,$(WATCHDOG)),-DBOARD_WATCHDOG_WWDG) LDFLAGS = --omagic -static LINKER_SCRIPT ?= $(_BOARD_DIR)/linker.ld @@ -29,6 +31,7 @@ BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/flash.c) BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/spi.c) BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/i2c.c) BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/sensor.c) +BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/watchdog.c) BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/rng.c) BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/crypto.c) BOARD_SOURCE += $(wildcard $(WHAL_DIR)/src/*/block.c) diff --git a/boards/stm32wb55xx_nucleo/board.c b/boards/stm32wb55xx_nucleo/board.c index fa1384e..43c6987 100644 --- a/boards/stm32wb55xx_nucleo/board.c +++ b/boards/stm32wb55xx_nucleo/board.c @@ -59,6 +59,9 @@ static const whal_Stm32wbRcc_Clk g_clocks[] = { {WHAL_STM32WB55_RNG_CLOCK}, {WHAL_STM32WB55_AES1_CLOCK}, {WHAL_STM32WB55_I2C1_CLOCK}, +#ifdef BOARD_WATCHDOG_WWDG + {WHAL_STM32WB55_WWDG_CLOCK}, +#endif }; #define CLOCK_COUNT (sizeof(g_clocks) / sizeof(g_clocks[0])) @@ -244,6 +247,28 @@ whal_Crypto g_whalCrypto = { }, }; +#ifdef BOARD_WATCHDOG_IWDG +whal_Watchdog g_whalWatchdog = { + WHAL_STM32WB55_IWDG_DEVICE, + + .cfg = &(whal_Stm32wbIwdg_Cfg) { + .prescaler = WHAL_STM32WB_IWDG_PR_32, + .reload = 100, + .timeout = &g_whalTimeout, + }, +}; +#elif defined(BOARD_WATCHDOG_WWDG) +whal_Watchdog g_whalWatchdog = { + WHAL_STM32WB55_WWDG_DEVICE, + + .cfg = &(whal_Stm32wbWwdg_Cfg) { + .prescaler = WHAL_STM32WB_WWDG_TB_128, + .window = 0x7F, + .counter = 0x7F, + }, +}; +#endif + void Board_WaitMs(size_t ms) { uint32_t startCount = g_tick; @@ -276,6 +301,14 @@ whal_Error Board_Init(void) return err; } +#ifdef BOARD_WATCHDOG_IWDG + /* Enable LSI osc required by the IWDG */ + err = whal_Stm32wbRcc_Ext_EnableLsi(&g_whalClock, 1); + if (err) { + return err; + } +#endif + /* Enable clocks */ for (size_t i = 0; i < CLOCK_COUNT; i++) { err = whal_Clock_Enable(&g_whalClock, &g_clocks[i]); @@ -445,6 +478,13 @@ whal_Error Board_Deinit(void) return err; } +#ifdef BOARD_WATCHDOG_IWDG + err = whal_Stm32wbRcc_Ext_EnableLsi(&g_whalClock, 0); + if (err) { + return err; + } +#endif + err = whal_Clock_Deinit(&g_whalClock); if (err) { return err; diff --git a/boards/stm32wb55xx_nucleo/board.h b/boards/stm32wb55xx_nucleo/board.h index 0ed42b3..5a180fd 100644 --- a/boards/stm32wb55xx_nucleo/board.h +++ b/boards/stm32wb55xx_nucleo/board.h @@ -15,6 +15,7 @@ extern whal_Rng g_whalRng; extern whal_Crypto g_whalCrypto; extern whal_I2c g_whalI2c; extern whal_Irq g_whalIrq; +extern whal_Watchdog g_whalWatchdog; extern whal_Timeout g_whalTimeout; extern volatile uint32_t g_tick; diff --git a/docs/adding_a_board.md b/docs/adding_a_board.md index 7a93ddf..229e163 100644 --- a/docs/adding_a_board.md +++ b/docs/adding_a_board.md @@ -106,7 +106,18 @@ whal_Error Board_Init(void) return WHAL_SUCCESS; } +``` + +The watchdog is intentionally excluded from `Board_Init()` and `Board_Deinit()`. +Once started, most watchdog peripherals cannot be stopped — if `Board_Init()` +starts the watchdog, any delay before the application begins refreshing it will +cause an unexpected reset. The application or test should call +`whal_Watchdog_Init()` directly when it is ready to begin refreshing. The board +still defines the `g_whalWatchdog` instance and enables any required clocks +(e.g., WWDG APB clock, IWDG LSI oscillator) so the watchdog is ready to be +started. +```c whal_Error Board_Deinit(void) { whal_Error err; diff --git a/docs/writing_a_driver.md b/docs/writing_a_driver.md index f216beb..5b54e1e 100644 --- a/docs/writing_a_driver.md +++ b/docs/writing_a_driver.md @@ -1053,6 +1053,50 @@ a transfer needs to be cancelled. --- +## Watchdog + +Header: `wolfHAL/watchdog/watchdog.h` + +The watchdog driver provides access to hardware watchdog timers that reset the +system if not periodically refreshed. On most hardware, the watchdog cannot be +stopped once started — only a system reset clears it. + +### Init + +Configure and start the watchdog. This typically involves: + +- Setting the prescaler and reload/counter values from the configuration +- Enabling the watchdog peripheral + +On some hardware (e.g., STM32 IWDG), the watchdog must be started before its +configuration registers can be written. The driver should handle this ordering +internally. + +The board must enable any required clocks before calling Init. For example, the +STM32 WWDG requires an APB clock, while the IWDG requires the LSI oscillator. + +The watchdog is NOT initialized in `Board_Init` — the test or application +controls when it starts, since once started it cannot be stopped. + +### Deinit + +Shut down the watchdog. On hardware where the watchdog cannot be stopped (e.g., +STM32 IWDG), this function is a no-op. + +### Refresh + +Reload the watchdog counter to prevent a reset. Must be called periodically +within the configured timeout window. The exact mechanism is hardware-specific +(e.g., writing a reload key to IWDG_KR, or writing the counter value back to +WWDG_CR). + +For window watchdogs, the refresh must occur within the valid window — refreshing +too early or too late triggers a reset. The driver does not enforce window timing; +it writes the reload value unconditionally and relies on the hardware to enforce +the window. + +--- + ## EthPhy Header: `wolfHAL/eth_phy/eth_phy.h` diff --git a/src/clock/stm32wb_rcc.c b/src/clock/stm32wb_rcc.c index 0a13a5a..d14d425 100644 --- a/src/clock/stm32wb_rcc.c +++ b/src/clock/stm32wb_rcc.c @@ -131,6 +131,14 @@ #define RCC_APB1ENR2_LPTIM2EN_Pos 5 /* LPTIM2 clock enable */ #define RCC_APB1ENR2_LPTIM2EN_Msk (1UL << RCC_APB1ENR2_LPTIM2EN_Pos) +/* Control/Status Register - LSI oscillator control */ +#define RCC_CSR_REG 0x094 +#define RCC_CSR_LSI1ON_Pos 0 /* LSI1 oscillator enable */ +#define RCC_CSR_LSI1ON_Msk (1UL << RCC_CSR_LSI1ON_Pos) + +#define RCC_CSR_LSI1RDY_Pos 1 /* LSI1 oscillator ready */ +#define RCC_CSR_LSI1RDY_Msk (1UL << RCC_CSR_LSI1RDY_Pos) + /* Clock Recovery RC Register - HSI48 oscillator control */ #define RCC_CRRCR_REG 0x098 #define RCC_CRRCR_HSI48ON_Pos 0 /* HSI48 oscillator enable */ @@ -361,6 +369,26 @@ whal_Error whal_Stm32wbRcc_Ext_EnableHsi48(whal_Clock *clkDev, uint8_t enable) return WHAL_SUCCESS; } +whal_Error whal_Stm32wbRcc_Ext_EnableLsi(whal_Clock *clkDev, uint8_t enable) +{ + if (!clkDev) { + return WHAL_EINVAL; + } + + whal_Reg_Update(clkDev->regmap.base, RCC_CSR_REG, RCC_CSR_LSI1ON_Msk, + whal_SetBits(RCC_CSR_LSI1ON_Msk, RCC_CSR_LSI1ON_Pos, enable)); + + if (enable) { + size_t rdy; + do { + whal_Reg_Get(clkDev->regmap.base, RCC_CSR_REG, + RCC_CSR_LSI1RDY_Msk, RCC_CSR_LSI1RDY_Pos, &rdy); + } while (!rdy); + } + + return WHAL_SUCCESS; +} + const whal_ClockDriver whal_Stm32wbRccPll_Driver = { .Init = whal_Stm32wbRccPll_Init, .Deinit = whal_Stm32wbRccPll_Deinit, diff --git a/src/watchdog/stm32wb_iwdg.c b/src/watchdog/stm32wb_iwdg.c new file mode 100644 index 0000000..1cb40e8 --- /dev/null +++ b/src/watchdog/stm32wb_iwdg.c @@ -0,0 +1,100 @@ +#include +#include +#include +#include + +/* + * STM32WB IWDG Register Definitions + * + * The IWDG is a free-running downcounter clocked from the LSI (~32 kHz). + * Once started via the key register, it cannot be stopped. If the counter + * reaches zero without being refreshed, a system reset is generated. + */ + +/* Key Register — write-only, controls access and refresh */ +#define IWDG_KR_REG 0x00 +#define IWDG_KEY_START 0xCCCC /* Start the watchdog */ +#define IWDG_KEY_RELOAD 0xAAAA /* Reload the counter */ +#define IWDG_KEY_ACCESS 0x5555 /* Enable write access to PR, RLR, WINR */ + +/* Prescaler Register */ +#define IWDG_PR_REG 0x04 +#define IWDG_PR_Pos 0 +#define IWDG_PR_Msk (0x7UL << IWDG_PR_Pos) + +/* Reload Register */ +#define IWDG_RLR_REG 0x08 +#define IWDG_RLR_Pos 0 +#define IWDG_RLR_Msk (0xFFFUL << IWDG_RLR_Pos) + +/* Status Register */ +#define IWDG_SR_REG 0x0C +#define IWDG_SR_PVU_Pos 0 /* Prescaler value update */ +#define IWDG_SR_PVU_Msk (1UL << IWDG_SR_PVU_Pos) +#define IWDG_SR_RVU_Pos 1 /* Reload value update */ +#define IWDG_SR_RVU_Msk (1UL << IWDG_SR_RVU_Pos) + +whal_Error whal_Stm32wbIwdg_Init(whal_Watchdog *wdgDev) +{ + const whal_Regmap *reg; + whal_Stm32wbIwdg_Cfg *cfg; + whal_Error err; + + if (!wdgDev || !wdgDev->cfg) { + return WHAL_EINVAL; + } + + reg = &wdgDev->regmap; + cfg = wdgDev->cfg; + + if (cfg->prescaler > 6 || cfg->reload > 0xFFF) { + return WHAL_EINVAL; + } + + /* Start the IWDG */ + whal_Reg_Write(reg->base, IWDG_KR_REG, IWDG_KEY_START); + + /* Enable register access */ + whal_Reg_Write(reg->base, IWDG_KR_REG, IWDG_KEY_ACCESS); + + /* Set prescaler */ + whal_Reg_Write(reg->base, IWDG_PR_REG, cfg->prescaler); + + /* Set reload value */ + whal_Reg_Write(reg->base, IWDG_RLR_REG, cfg->reload); + + /* Wait for registers to update */ + err = whal_Reg_ReadPoll(reg->base, IWDG_SR_REG, + IWDG_SR_PVU_Msk | IWDG_SR_RVU_Msk, 0, + cfg->timeout); + if (err) + return err; + + /* Refresh counter with new reload value */ + whal_Reg_Write(reg->base, IWDG_KR_REG, IWDG_KEY_RELOAD); + + return WHAL_SUCCESS; +} + +whal_Error whal_Stm32wbIwdg_Deinit(whal_Watchdog *wdgDev) +{ + (void)wdgDev; + return WHAL_SUCCESS; +} + +whal_Error whal_Stm32wbIwdg_Refresh(whal_Watchdog *wdgDev) +{ + if (!wdgDev) { + return WHAL_EINVAL; + } + + whal_Reg_Write(wdgDev->regmap.base, IWDG_KR_REG, IWDG_KEY_RELOAD); + + return WHAL_SUCCESS; +} + +const whal_WatchdogDriver whal_Stm32wbIwdg_Driver = { + .Init = whal_Stm32wbIwdg_Init, + .Deinit = whal_Stm32wbIwdg_Deinit, + .Refresh = whal_Stm32wbIwdg_Refresh, +}; diff --git a/src/watchdog/stm32wb_wwdg.c b/src/watchdog/stm32wb_wwdg.c new file mode 100644 index 0000000..520074b --- /dev/null +++ b/src/watchdog/stm32wb_wwdg.c @@ -0,0 +1,81 @@ +#include +#include +#include +#include +#include + +#define CR_REG 0x00 + +#define CR_T_Pos 0 +#define CR_T_Msk (WHAL_BITMASK(7) << CR_T_Pos) + +#define CR_WDGA_Pos 7 +#define CR_WDGA_Msk (1UL << CR_WDGA_Pos) + + +#define CFR_REG 0x04 + +#define CFR_W_Pos 0 +#define CFR_W_Msk (WHAL_BITMASK(7) << CFR_W_Pos) + +#define CFR_EWI_Pos 9 +#define CFR_EWI_Msk (1UL << CFR_EWI_Pos) + +#define CFR_WDGTB_Pos 11 +#define CFR_WDGTB_Msk (WHAL_BITMASK(3) << CFR_WDGTB_Pos) + +whal_Error whal_Stm32wbWwdg_Init(whal_Watchdog *wdgDev) +{ + size_t base; + whal_Stm32wbWwdg_Cfg *cfg; + + if (!wdgDev || !wdgDev->cfg) + return WHAL_EINVAL; + + base = wdgDev->regmap.base; + cfg = wdgDev->cfg; + + if (cfg->prescaler > 7 || cfg->window > 0x7F || cfg->counter > 0x7F) + return WHAL_EINVAL; + + /* Configure window and prescaler */ + whal_Reg_Update(base, CFR_REG, CFR_W_Msk | CFR_WDGTB_Msk, + whal_SetBits(CFR_W_Msk, CFR_W_Pos, cfg->window) | + whal_SetBits(CFR_WDGTB_Msk, CFR_WDGTB_Pos, cfg->prescaler)); + + /* Set counter and enable WWDG */ + whal_Reg_Update(base, CR_REG, CR_T_Msk | CR_WDGA_Msk, + whal_SetBits(CR_T_Msk, CR_T_Pos, cfg->counter) | + whal_SetBits(CR_WDGA_Msk, CR_WDGA_Pos, 1)); + + return WHAL_SUCCESS; +} + +whal_Error whal_Stm32wbWwdg_Deinit(whal_Watchdog *wdgDev) +{ + (void)wdgDev; + return WHAL_SUCCESS; +} + +whal_Error whal_Stm32wbWwdg_Refresh(whal_Watchdog *wdgDev) +{ + size_t base; + whal_Stm32wbWwdg_Cfg *cfg; + + if (!wdgDev || !wdgDev->cfg) + return WHAL_EINVAL; + + base = wdgDev->regmap.base; + cfg = wdgDev->cfg; + + whal_Reg_Update(base, CR_REG, CR_T_Msk, + whal_SetBits(CR_T_Msk, CR_T_Pos, cfg->counter)); + + return WHAL_SUCCESS; +} + +const whal_WatchdogDriver whal_Stm32wbWwdg_Driver = { + .Init = whal_Stm32wbWwdg_Init, + .Deinit = whal_Stm32wbWwdg_Deinit, + .Refresh = whal_Stm32wbWwdg_Refresh, +}; diff --git a/src/watchdog/watchdog.c b/src/watchdog/watchdog.c new file mode 100644 index 0000000..d9eeaaa --- /dev/null +++ b/src/watchdog/watchdog.c @@ -0,0 +1,29 @@ +#include +#include + +inline whal_Error whal_Watchdog_Init(whal_Watchdog *wdgDev) +{ + if (!wdgDev || !wdgDev->driver || !wdgDev->driver->Init) { + return WHAL_EINVAL; + } + + return wdgDev->driver->Init(wdgDev); +} + +inline whal_Error whal_Watchdog_Deinit(whal_Watchdog *wdgDev) +{ + if (!wdgDev || !wdgDev->driver || !wdgDev->driver->Deinit) { + return WHAL_EINVAL; + } + + return wdgDev->driver->Deinit(wdgDev); +} + +inline whal_Error whal_Watchdog_Refresh(whal_Watchdog *wdgDev) +{ + if (!wdgDev || !wdgDev->driver || !wdgDev->driver->Refresh) { + return WHAL_EINVAL; + } + + return wdgDev->driver->Refresh(wdgDev); +} diff --git a/tests/main.c b/tests/main.c index 3a8e850..b9613eb 100644 --- a/tests/main.c +++ b/tests/main.c @@ -65,6 +65,10 @@ void whal_Test_Eth_Platform(void); void whal_Test_Bmi270(void); #endif +#ifdef WHAL_TEST_ENABLE_WATCHDOG +void whal_Test_Watchdog(void); +#endif + int g_whalTestPassed; int g_whalTestFailed; int g_whalTestSkipped; @@ -158,6 +162,10 @@ void main(void) whal_Test_Bmi270(); #endif +#ifdef WHAL_TEST_ENABLE_WATCHDOG + whal_Test_Watchdog(); +#endif + WHAL_TEST_SUMMARY(); if (g_whalTestFailed == 0) { diff --git a/tests/watchdog/test_watchdog.c b/tests/watchdog/test_watchdog.c new file mode 100644 index 0000000..1714d85 --- /dev/null +++ b/tests/watchdog/test_watchdog.c @@ -0,0 +1,96 @@ +#include +#include +#include "board.h" +#include "test.h" + +/* + * Multi-phase watchdog test. + * + * Phase 0: Write a magic value to flash, start the watchdog, refresh + * it several times to verify refresh works, then stop + * refreshing and let the watchdog reset the system. + * Phase 1: After reboot, read flash and verify the magic value is + * present, confirming the watchdog fired. Erase the flash + * sector to clean up. + * + * Uses the board's flash test area (BOARD_FLASH_TEST_ADDR) to persist + * state across resets. Flash survives all reset types including + * power-on reset. + */ + +#define WDG_TEST_MAGIC 0x57444F47 /* "WDOG" */ +#define WDG_TEST_REFRESH_OK 0x524F4B21 /* "ROK!" */ + +typedef struct { + uint32_t magic; + uint32_t refreshOk; + uint32_t pad[2]; +} wdg_test_state_t; + +static void Test_Watchdog(void) +{ + wdg_test_state_t state; + + WHAL_ASSERT_EQ(whal_Flash_Read(&g_whalFlash, BOARD_FLASH_TEST_ADDR, + &state, sizeof(state)), WHAL_SUCCESS); + + if (state.magic != WDG_TEST_MAGIC) { + /* Phase 0: write magic, init watchdog, refresh, then let it reset */ + whal_Error err; + + state.magic = WDG_TEST_MAGIC; + state.refreshOk = 0; + state.pad[0] = 0; + state.pad[1] = 0; + + WHAL_ASSERT_EQ(whal_Flash_Unlock(&g_whalFlash, BOARD_FLASH_TEST_ADDR, + BOARD_FLASH_SECTOR_SZ), WHAL_SUCCESS); + WHAL_ASSERT_EQ(whal_Flash_Erase(&g_whalFlash, BOARD_FLASH_TEST_ADDR, + BOARD_FLASH_SECTOR_SZ), WHAL_SUCCESS); + do { + err = whal_Flash_Write(&g_whalFlash, BOARD_FLASH_TEST_ADDR, + &state, sizeof(state)); + } while (err == WHAL_ENOTREADY); + WHAL_ASSERT_EQ(err, WHAL_SUCCESS); + + WHAL_ASSERT_EQ(whal_Watchdog_Init(&g_whalWatchdog), WHAL_SUCCESS); + + /* Verify refresh keeps us alive */ + for (int i = 0; i < 1000; i++) { + Board_WaitMs(1); + WHAL_ASSERT_EQ(whal_Watchdog_Refresh(&g_whalWatchdog), + WHAL_SUCCESS); + } + + /* Mark that refresh loop completed */ + state.refreshOk = WDG_TEST_REFRESH_OK; + WHAL_ASSERT_EQ(whal_Flash_Erase(&g_whalFlash, BOARD_FLASH_TEST_ADDR, + BOARD_FLASH_SECTOR_SZ), WHAL_SUCCESS); + do { + err = whal_Flash_Write(&g_whalFlash, BOARD_FLASH_TEST_ADDR, + &state, sizeof(state)); + } while (err == WHAL_ENOTREADY); + WHAL_ASSERT_EQ(err, WHAL_SUCCESS); + WHAL_ASSERT_EQ(whal_Flash_Lock(&g_whalFlash, BOARD_FLASH_TEST_ADDR, + BOARD_FLASH_SECTOR_SZ), WHAL_SUCCESS); + + /* Stop refreshing — watchdog should reset us */ + while (1); + } + + /* Phase 1: watchdog fired — verify refresh loop completed and clean up */ + WHAL_ASSERT_EQ(state.refreshOk, WDG_TEST_REFRESH_OK); + WHAL_ASSERT_EQ(whal_Flash_Unlock(&g_whalFlash, BOARD_FLASH_TEST_ADDR, + BOARD_FLASH_SECTOR_SZ), WHAL_SUCCESS); + WHAL_ASSERT_EQ(whal_Flash_Erase(&g_whalFlash, BOARD_FLASH_TEST_ADDR, + BOARD_FLASH_SECTOR_SZ), WHAL_SUCCESS); + WHAL_ASSERT_EQ(whal_Flash_Lock(&g_whalFlash, BOARD_FLASH_TEST_ADDR, + BOARD_FLASH_SECTOR_SZ), WHAL_SUCCESS); +} + +void whal_Test_Watchdog(void) +{ + WHAL_TEST_SUITE_START("watchdog"); + WHAL_TEST(Test_Watchdog); + WHAL_TEST_SUITE_END(); +} diff --git a/wolfHAL/clock/stm32wb_rcc.h b/wolfHAL/clock/stm32wb_rcc.h index 378850b..cf023ed 100644 --- a/wolfHAL/clock/stm32wb_rcc.h +++ b/wolfHAL/clock/stm32wb_rcc.h @@ -219,4 +219,17 @@ whal_Error whal_Stm32wbRccMsi_GetRate(whal_Clock *clkDev, size_t *rateOut); */ whal_Error whal_Stm32wbRcc_Ext_EnableHsi48(whal_Clock *clkDev, uint8_t enable); +/* + * @brief Enable or disable the LSI oscillator required by the IWDG. + * + * When enabled, blocks until LSI1RDY is set. + * + * @param clkDev Clock controller instance. + * @param enable 1 to enable, 0 to disable. + * + * @retval WHAL_SUCCESS LSI enabled and ready, or disabled. + * @retval WHAL_EINVAL Invalid arguments. + */ +whal_Error whal_Stm32wbRcc_Ext_EnableLsi(whal_Clock *clkDev, uint8_t enable); + #endif /* WHAL_STM32WB_RCC_H */ diff --git a/wolfHAL/platform/st/stm32wb55xx.h b/wolfHAL/platform/st/stm32wb55xx.h index 9a9f90d..f4ee4ca 100644 --- a/wolfHAL/platform/st/stm32wb55xx.h +++ b/wolfHAL/platform/st/stm32wb55xx.h @@ -13,6 +13,8 @@ #include #include #include +#include +#include /* * @file stm32wb55xx.h @@ -89,6 +91,20 @@ }, \ .driver = &whal_Stm32wbI2c_Driver +#define WHAL_STM32WB55_IWDG_DEVICE \ + .regmap = { \ + .base = 0x40003000, \ + .size = 0x400, \ + }, \ + .driver = &whal_Stm32wbIwdg_Driver + +#define WHAL_STM32WB55_WWDG_DEVICE \ + .regmap = { \ + .base = 0x40002C00, \ + .size = 0x400, \ + }, \ + .driver = &whal_Stm32wbWwdg_Driver + #define WHAL_STM32WB55_FLASH_DEVICE \ .regmap = { \ .base = 0x58004000, \ @@ -141,6 +157,11 @@ .enableMask = (1UL << 16), \ .enablePos = 16 +#define WHAL_STM32WB55_WWDG_CLOCK \ + .regOffset = 0x58, \ + .enableMask = (1UL << 11), \ + .enablePos = 11 + #define WHAL_STM32WB55_RNG_CLOCK \ .regOffset = 0x50, \ .enableMask = (1UL << 18), \ diff --git a/wolfHAL/watchdog/stm32wb_iwdg.h b/wolfHAL/watchdog/stm32wb_iwdg.h new file mode 100644 index 0000000..9701206 --- /dev/null +++ b/wolfHAL/watchdog/stm32wb_iwdg.h @@ -0,0 +1,80 @@ +#ifndef WHAL_STM32WB_IWDG_H +#define WHAL_STM32WB_IWDG_H + +#include +#include +#include + +/* + * @file stm32wb_iwdg.h + * @brief STM32WB independent watchdog (IWDG) driver. + * + * The IWDG is clocked from the LSI (~32 kHz) and cannot be stopped + * once started. The timeout is configured via prescaler and reload: + * + * timeout = (reload + 1) * (4 << prescaler) / f_LSI + * + * Prescaler index: 0=/4, 1=/8, 2=/16, 3=/32, 4=/64, 5=/128, 6=/256. + */ + +/* Prescaler divider indices */ +#define WHAL_STM32WB_IWDG_PR_4 0 +#define WHAL_STM32WB_IWDG_PR_8 1 +#define WHAL_STM32WB_IWDG_PR_16 2 +#define WHAL_STM32WB_IWDG_PR_32 3 +#define WHAL_STM32WB_IWDG_PR_64 4 +#define WHAL_STM32WB_IWDG_PR_128 5 +#define WHAL_STM32WB_IWDG_PR_256 6 + +/* + * @brief IWDG device configuration. + */ +typedef struct { + uint8_t prescaler; /* Prescaler index (WHAL_STM32WB_IWDG_PR_*) */ + uint16_t reload; /* 12-bit reload value (0-4095) */ + whal_Timeout *timeout; +} whal_Stm32wbIwdg_Cfg; + +/* + * @brief Driver instance for STM32WB IWDG. + */ +extern const whal_WatchdogDriver whal_Stm32wbIwdg_Driver; + +/* + * @brief Configure and start the STM32WB IWDG. + * + * Sets the prescaler and reload value, then starts the watchdog. + * Once started, the IWDG cannot be stopped. + * + * @param wdgDev Watchdog device instance. + * + * @retval WHAL_SUCCESS Watchdog started. + * @retval WHAL_EINVAL Invalid arguments. + */ +whal_Error whal_Stm32wbIwdg_Init(whal_Watchdog *wdgDev); + +/* + * @brief Deinitialize the STM32WB IWDG. + * + * The IWDG cannot be stopped by software. This function has no effect. + * + * @param wdgDev Watchdog device instance. + * + * @retval WHAL_SUCCESS Always. + */ +whal_Error whal_Stm32wbIwdg_Deinit(whal_Watchdog *wdgDev); + +/* + * @brief Refresh the IWDG counter to prevent reset. + * + * Writes the reload key (0xAAAA) to IWDG_KR, reloading the + * downcounter with the value from IWDG_RLR. + * + * @param wdgDev Watchdog device instance. + * + * @retval WHAL_SUCCESS Counter refreshed. + * @retval WHAL_EINVAL Null pointer. + */ +whal_Error whal_Stm32wbIwdg_Refresh(whal_Watchdog *wdgDev); + +#endif /* WHAL_STM32WB_IWDG_H */ diff --git a/wolfHAL/watchdog/stm32wb_wwdg.h b/wolfHAL/watchdog/stm32wb_wwdg.h new file mode 100644 index 0000000..f1d2000 --- /dev/null +++ b/wolfHAL/watchdog/stm32wb_wwdg.h @@ -0,0 +1,80 @@ +#ifndef WHAL_STM32WB_WWDG_H +#define WHAL_STM32WB_WWDG_H + +#include +#include +#include + +/* + * @file stm32wb_wwdg.h + * @brief STM32WB window watchdog (WWDG) driver. + * + * The WWDG is clocked from PCLK1 and must be refreshed within a + * configurable window. Refreshing too early or too late triggers + * a system reset. An early wakeup interrupt can be enabled to + * allow state saving before reset. + * + * Timeout = (4096 * 2^WDGTB * (counter - 0x3F)) / f_PCLK1 + */ + +/* Timebase prescaler values */ +#define WHAL_STM32WB_WWDG_TB_1 0 +#define WHAL_STM32WB_WWDG_TB_2 1 +#define WHAL_STM32WB_WWDG_TB_4 2 +#define WHAL_STM32WB_WWDG_TB_8 3 +#define WHAL_STM32WB_WWDG_TB_16 4 +#define WHAL_STM32WB_WWDG_TB_32 5 +#define WHAL_STM32WB_WWDG_TB_64 6 +#define WHAL_STM32WB_WWDG_TB_128 7 + +/* + * @brief WWDG device configuration. + */ +typedef struct { + uint8_t prescaler; /* Timebase prescaler (WHAL_STM32WB_WWDG_TB_*) */ + uint8_t window; /* 7-bit window value (must be > 0x3F) */ + uint8_t counter; /* 7-bit counter value (must be > 0x3F) */ +} whal_Stm32wbWwdg_Cfg; + +/* + * @brief Driver instance for STM32WB WWDG. + */ +extern const whal_WatchdogDriver whal_Stm32wbWwdg_Driver; + +/* + * @brief Configure and start the STM32WB WWDG. + * + * Sets the window, prescaler, and counter values, then enables + * the watchdog. + * + * @param wdgDev Watchdog device instance. + * + * @retval WHAL_SUCCESS Watchdog started. + * @retval WHAL_EINVAL Invalid arguments. + */ +whal_Error whal_Stm32wbWwdg_Init(whal_Watchdog *wdgDev); + +/* + * @brief Deinitialize the STM32WB WWDG. + * + * @param wdgDev Watchdog device instance. + * + * @retval WHAL_SUCCESS Always (no-op, WWDG can only be stopped by reset). + */ +whal_Error whal_Stm32wbWwdg_Deinit(whal_Watchdog *wdgDev); + +/* + * @brief Refresh the WWDG counter within the window. + * + * Must be called after the counter has counted down past the + * window value but before it reaches 0x3F. Refreshing outside + * this window triggers a reset. + * + * @param wdgDev Watchdog device instance. + * + * @retval WHAL_SUCCESS Counter refreshed. + * @retval WHAL_EINVAL Null pointer. + */ +whal_Error whal_Stm32wbWwdg_Refresh(whal_Watchdog *wdgDev); + +#endif /* WHAL_STM32WB_WWDG_H */ diff --git a/wolfHAL/watchdog/watchdog.h b/wolfHAL/watchdog/watchdog.h new file mode 100644 index 0000000..c7cdaa8 --- /dev/null +++ b/wolfHAL/watchdog/watchdog.h @@ -0,0 +1,86 @@ +#ifndef WHAL_WATCHDOG_H +#define WHAL_WATCHDOG_H + +#include +#include +#include +#include + +/* + * @file watchdog.h + * @brief Generic watchdog timer abstraction and driver interface. + * + * The watchdog timer resets the system if not refreshed within a + * configured timeout period. Init configures and starts the watchdog. + * On most hardware, once started the watchdog cannot be stopped — + * only a system reset clears it. + */ + +typedef struct whal_Watchdog whal_Watchdog; + +/* + * @brief Driver vtable for watchdog devices. + */ +typedef struct { + /* Configure and start the watchdog. */ + whal_Error (*Init)(whal_Watchdog *wdgDev); + /* Deinitialize the watchdog (may be unsupported by hardware). */ + whal_Error (*Deinit)(whal_Watchdog *wdgDev); + /* Refresh the watchdog counter to prevent reset. */ + whal_Error (*Refresh)(whal_Watchdog *wdgDev); +} whal_WatchdogDriver; + +/* + * @brief Watchdog device instance tying a register map and driver. + */ +struct whal_Watchdog { + const whal_Regmap regmap; + const whal_WatchdogDriver *driver; + void *cfg; +}; + +#ifdef WHAL_CFG_DIRECT_CALLBACKS +#define whal_Watchdog_Init(wdgDev) ((wdgDev)->driver->Init((wdgDev))) +#define whal_Watchdog_Deinit(wdgDev) ((wdgDev)->driver->Deinit((wdgDev))) +#define whal_Watchdog_Refresh(wdgDev) ((wdgDev)->driver->Refresh((wdgDev))) +#else +/* + * @brief Configure and start the watchdog. + * + * Configures the watchdog timeout from the platform-specific cfg + * and starts the countdown. On most hardware, the watchdog cannot + * be stopped after this call. + * + * @param wdgDev Pointer to the watchdog instance. + * + * @retval WHAL_SUCCESS Watchdog started. + * @retval WHAL_EINVAL Null pointer or invalid configuration. + */ +whal_Error whal_Watchdog_Init(whal_Watchdog *wdgDev); +/* + * @brief Deinitialize the watchdog. + * + * On hardware that does not support stopping the watchdog (e.g. + * STM32 IWDG), this function has no effect. + * + * @param wdgDev Pointer to the watchdog instance. + * + * @retval WHAL_SUCCESS Watchdog stopped or no-op completed. + * @retval WHAL_EINVAL Null pointer. + */ +whal_Error whal_Watchdog_Deinit(whal_Watchdog *wdgDev); +/* + * @brief Refresh the watchdog counter to prevent reset. + * + * Must be called periodically within the configured timeout + * window. Failure to call this results in a system reset. + * + * @param wdgDev Pointer to the watchdog instance. + * + * @retval WHAL_SUCCESS Counter refreshed. + * @retval WHAL_EINVAL Null pointer. + */ +whal_Error whal_Watchdog_Refresh(whal_Watchdog *wdgDev); +#endif + +#endif /* WHAL_WATCHDOG_H */ diff --git a/wolfHAL/wolfHAL.h b/wolfHAL/wolfHAL.h index 3976170..fcd6b75 100644 --- a/wolfHAL/wolfHAL.h +++ b/wolfHAL/wolfHAL.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include