diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d8b7198..5651a63 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -12,6 +12,7 @@ target_include_directories(${PROJECT_NAME} PUBLIC target_link_libraries(${PROJECT_NAME} PRIVATE #blink + wwdg gcc ) @@ -62,3 +63,4 @@ add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD add_subdirectory(ADC) add_subdirectory(RegEdit) add_subdirectory(blink) +add_subdirectory(wwdg) diff --git a/src/wwdg/CMakeLists.txt b/src/wwdg/CMakeLists.txt new file mode 100644 index 0000000..f54f6ad --- /dev/null +++ b/src/wwdg/CMakeLists.txt @@ -0,0 +1,49 @@ +# File: src/wwdg/CMakeLists.txt +add_library(wwdg STATIC + wwdg.c +) + +# Current list dir: The directory where the cmake config file is. +# Source Dir: The dir where the root/main cmake config file is. + +if(NOT UNIT_TESTING) + target_include_directories(wwdg PUBLIC + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_SOURCE_DIR}/inc/ + ) + target_link_libraries(wwdg + RegEdit + ) + + target_compile_options(wwdg PUBLIC + -g + -Os + -flto + -ffunction-sections + -fdata-sections + -fmessage-length=0 + -msmall-data-limit=8 + -march=rv32ec + -mabi=ilp32e + ) + + +else() + target_include_directories(wwdg PUBLIC + ${CMAKE_CURRENT_LIST_DIR} + #First we include any module specific test dependencies. + ${CMAKE_SOURCE_DIR}/tests/wwdg/mocks/ + ${CMAKE_SOURCE_DIR}/tests/wwdg/fakes/ + ${CMAKE_SOURCE_DIR}/tests/wwdg/stubs/ + #Next comes the shared and non-module specific test depencencies. + ${CMAKE_SOURCE_DIR}/tests/shared/mocks/ + ${CMAKE_SOURCE_DIR}/tests/shared/fakes/ + ${CMAKE_SOURCE_DIR}/tests/shared/stubs/ + #Finally we include the local stuff, which has likely been overridden. + ${CMAKE_CURRENT_SOURCE_DIR} + ) + #Place Mocked/Regular dependencies here for unit testing. + target_link_libraries(wwdg + MockRegEdit + ) +endif() diff --git a/src/wwdg/wwdg.c b/src/wwdg/wwdg.c new file mode 100644 index 0000000..77c37b2 --- /dev/null +++ b/src/wwdg/wwdg.c @@ -0,0 +1,91 @@ +/* + * Author: Jake G + * Date: 2026 + * filename: wwdg.c + * description: Source for window watchdog implimentation. + */ + +#include "wwdg.h" +#include "RegEdit.h" +#include "ch32fun.h" + +#define WWDG_MIN_WINDOW_VAL 0x40 // 64 +#define WWDG_MAX_WINDOW_VAL 0x7F // 127 + +static inline void wwdg_rcc_enable(void) +{ + // RegEdit_u32_AND_Num((void *)&RCC->APB1PCENR, RCC_WWDGEN); + RegEdit_u32_SetBit((void *)&RCC->APB1PCENR, RCC_WWDGEN_BIT); +} + +void wwdg_enable(void) +{ + wwdg_rcc_enable(); + RegEdit_u32_SetBit((void *)&WWDG->CTLR, WWDG_CTLR_WDGA_BIT); + return; +} + +// maybe this should actually set the reset bit in the APB1 reg? +void wwdg_disable(void) +{ + RegEdit_u32_ClearBit((void *)&RCC->APB1PCENR, RCC_WWDGEN_BIT); + return; +} + +bool wwdg_set_clock_div(uint8_t div) +{ + if (div > 3) + { + return false; + } + + RegEdit_u16_ClearBit((void *)&WWDG->CFGR, 7); + RegEdit_u16_ClearBit((void *)&WWDG->CFGR, 8); + RegEdit_u16_OR_Num((void *)&WWDG->CFGR, div); + + return true; +} + +bool wwdg_set_window(uint8_t value) +{ + // Check if either the input is bigger than 7-Bits or less than the min. + if (value < WWDG_MIN_WINDOW_VAL || value > WWDG_MAX_WINDOW_VAL) + { + return false; + } + + // First we clear the value in the first 7-Bits. + // UINT16_MAX & ~127 = 0xFF80, aka bits [0:6] + RegEdit_u16_AND_Num((void *)&WWDG->CFGR, 0xFF80); + + // Now we set the value. + RegEdit_u16_OR_Num((void *)&WWDG->CFGR, value); + + return true; +} + +bool wwdg_reset_timer(uint8_t reset_value) +{ + if (reset_value > WWDG_MAX_WINDOW_VAL || reset_value < WWDG_MIN_WINDOW_VAL) + { + return false; + } + + // Clear the last six bits + RegEdit_u16_AND_Num((void *)&WWDG->CTLR, 0xFF80); + + // Set the last six bits. + RegEdit_u16_OR_Num((void *)&WWDG->CTLR, reset_value); + + return true; +} + +void wwdg_enable_interrupt(void) +{ + RegEdit_u16_SetBit((void *)&WWDG->CFGR, WWDG_EWI_BIT); +} + +void wwdg_disable_interrupt(void) +{ + RegEdit_u16_ClearBit((void *)&WWDG->CFGR, WWDG_EWI_BIT); +} diff --git a/src/wwdg/wwdg.h b/src/wwdg/wwdg.h new file mode 100644 index 0000000..d568f24 --- /dev/null +++ b/src/wwdg/wwdg.h @@ -0,0 +1,133 @@ +/** + * @brief Window Watchdog Timer Module. + * @details Window Watchdog Timer with bi-conditional reset. + * @author Jake G + * @date 2026 + * @copyright None + * @file wwdg.h + * + * Used to monitor system operation for software faults such as external + * disturbances unforseen logic errors and other conditions. It requires a + * counter refresh(dog feeding) within a specific window time(with upper and + * lower limits), otherwise earlier or later than this window time the + * watchdog circuit will generate a system reset. + * + * This is clocked off the AHB like so: + * + * AHB --> /4096 --> WDGTB + * + * Where the WDGTB is a user changable divdider. + * + * + * The minimum value also known as T3 is 0x40. When 0x40 is in the register the + * watchdog will have timed out. + */ + +// #pragma once +#ifndef WWDG_H +#define WWDG_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include +#include +#include +#include + +#define LOG2(x) (log(x) / log(2)) + +// These use the ch32fun defines and log to extract the bit position numbers. +// Those numbers are then used in the regedit module's functions. +#define RCC_WWDGEN_BIT LOG2(RCC_WWDGEN) +#define WWDG_CTLR_WDGA_BIT LOG2(WWDG_CTLR_WDGA) +#define WWDG_EWI_BIT LOG2(WWDG_CFGR_EWI) + +#define WWDG_MAX_VALUE 127 + + enum WWDG_DIVS + { + DIV_ONE = 0, + DIV_TWO = 1, + DIV_FOUR = 2, + DIV_EIGHT = 3, + }; + + typedef struct + { + uint8_t upper_limit; + uint8_t reset_value; + uint8_t clk_divider; + bool enable_ewi; + } WWDT_Config; + + // struct WWDG; + + /* + * @brief + * @param + */ + void wwdg_setup(WWDT_Config *conf); + + /* + * @brief Enables the Window Watchdog Timer. + * After the WWDG is enabled it cannot be disabled until the entire + * system is reset. + * + * The watchdog can technically be stopped from operating by disabling the + * AHB clock bit for it using the RCC register. + */ + void wwdg_enable(void); + + /** + * @brief Disables the Window Watchdog Timer. + * This function doesn't actully disable the peripheral itself but + * disables the clock bit that supplies the WWDG. + */ + void wwdg_disable(void); + + /** + * @brief Set's the clock divider value. + * @param[in] Divider value from 0-3 are valid as it's a 2Bit value. + * @return False on invalid dividers, true on success. + * + * 0: Divide by 1, aka do nothing. + * 1: Divide by 2. + * 2: Divide by 4. + * 3: Divide by 8. + */ + bool wwdg_set_clock_div(uint8_t div); + + /** + * @brief Sets the WWDG upper threshold. + * @param[in] 7-Bit counter value. + * + * The function set's the W[6:0] or upper threshold value that that the + * counter must be less than or equal to in order to refresh the WWDG, + * aka feed the dog. + */ + bool wwdg_set_window(uint8_t value); + + /** + * @brief Resets the WWDT counter. + * @param[in] The value the counter will be set, must be 7-Bit value. + */ + bool wwdg_reset_timer(uint8_t reset_value); + + /** + * @brief Enables the early wakeup interrupt bit. + */ + void wwdg_enable_interrupt(void); + + /** + * @brief Disables the early wakeup interrupt bit. + */ + void wwdg_disable_interrupt(void); + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // WWDG_H diff --git a/tests/wwdg/CMakeLists.txt b/tests/wwdg/CMakeLists.txt new file mode 100644 index 0000000..0ac13db --- /dev/null +++ b/tests/wwdg/CMakeLists.txt @@ -0,0 +1,24 @@ +# File: tests/wwdg/CMakeLists.txt + +add_subdirectory(mocks) +add_subdirectory(fakes) +add_subdirectory(stubs) + +# TEST_RUNNER +add_library(test_wwdg + test_wwdg.cpp +) + +target_link_libraries(test_wwdg + ${CPPUTEST_LIBRARIES} + wwdg +) + +target_include_directories(test_wwdg PUBLIC + ${CMAKE_CURRENT_LIST_DIR} + #Next comes the shared and non-module specific test depencencies. + ${CMAKE_SOURCE_DIR}/inc/ + ${CMAKE_SOURCE_DIR}/tests/shared/mocks/ + ${CMAKE_SOURCE_DIR}/tests/shared/fakes/ + ${CMAKE_SOURCE_DIR}/tests/shared/stubs/ +) diff --git a/tests/wwdg/fakes/CMakeLists.txt b/tests/wwdg/fakes/CMakeLists.txt new file mode 100644 index 0000000..e69de29 diff --git a/tests/wwdg/mocks/CMakeLists.txt b/tests/wwdg/mocks/CMakeLists.txt new file mode 100644 index 0000000..e69de29 diff --git a/tests/wwdg/stubs/CMakeLists.txt b/tests/wwdg/stubs/CMakeLists.txt new file mode 100644 index 0000000..e69de29 diff --git a/tests/wwdg/test_wwdg.cpp b/tests/wwdg/test_wwdg.cpp new file mode 100644 index 0000000..1eb40d2 --- /dev/null +++ b/tests/wwdg/test_wwdg.cpp @@ -0,0 +1,162 @@ +/* + * Author: username + * Date: todays_date + * filename: test_wwdg.c + * description: module_purpose + */ + +#include "CppUTest/CommandLineTestRunner.h" +#include "CppUTestExt/MockSupport.h" + +extern "C" +{ +#include "ch32fun.h" +#include "wwdg.h" +} + +TEST_GROUP(tg_wwdg) +{ + void setup() + { + + } + void teardown() + { + mock().checkExpectations(); + mock().clear(); + } +}; + + +TEST(tg_wwdg, EnableCallsCorrectFunctiions) +{ + //The APB1 contains the WWDG peripheral. + mock().expectOneCall("RegEdit_u32_SetBit") + .withPointerParameter("reg", (void *)&RCC->APB1PCENR) + .withUnsignedIntParameter("bit_num", RCC_WWDGEN_BIT); + + mock().expectOneCall("RegEdit_u32_SetBit") + .withPointerParameter("reg", (void *)&WWDG->CTLR) + .withUnsignedIntParameter("bit_num", WWDG_CTLR_WDGA_BIT); + + wwdg_enable(); +} + +TEST(tg_wwdg, DisableWWDG_DisablesPeriphClock) +{ + mock().expectOneCall("RegEdit_u32_ClearBit") + .withPointerParameter("reg", (void *)&RCC->APB1PCENR) + .withUnsignedIntParameter("bit_num", RCC_WWDGEN_BIT); + wwdg_disable(); +} + +TEST(tg_wwdg, SetClockDivReturnsFalseOnInvalidValues) +{ + for(uint8_t i = 4; i > 10; i++){ + CHECK_FALSE(wwdg_set_clock_div(i)); + } +} + +TEST(tg_wwdg, SetClockDivReturnsTrueOnValidValues) +{ + for(uint8_t i = 0; i <= 3; i++){ + mock().expectOneCall("RegEdit_u16_ClearBit") + .withPointerParameter("reg", (void *)&WWDG->CFGR) + .withUnsignedIntParameter("bit_num", 7); + mock().expectOneCall("RegEdit_u16_ClearBit") + .withPointerParameter("reg", (void *)&WWDG->CFGR) + .withUnsignedIntParameter("bit_num", 8); + + mock().expectOneCall("RegEdit_u16_OR_Num") + .withPointerParameter("reg", (void *)&WWDG->CFGR) + .withUnsignedIntParameter("num", i); + } + + for(uint8_t i = 0; i <= 3; i++){ + CHECK_TRUE(wwdg_set_clock_div(i)); + } +} + + +TEST(tg_wwdg, SetWindowCounterReturnsFalseOnInvalidInputs) +{ + //We take all zeros logically and'd with the first six bits inverted. + //uint16_t mask = UINT16_MAX & ~127; //0xFF80 + uint8_t value = 0; + bool ret = true; + + for(value = 0; value < 0x3F; value++){ + ret = wwdg_set_window(value); + CHECK_EQUAL(false, ret); + } + for(value = 128; value < 255; value++){ + ret = wwdg_set_window(value); + CHECK_EQUAL(false, ret); + } +} + +TEST(tg_wwdg, SetWindowCounterReturnsTrueOnValidInputs) +{ + //We take all zeros logically and'd with the first six bits inverted. + uint16_t mask = UINT16_MAX & ~127; //0xFF80 + uint8_t value = 0; + bool ret = true; + + for(value = 0x40; value <= 127; value++){ + //The first bits 0:6 need to be cleared before being set. + mock().expectOneCall("RegEdit_u16_AND_Num") + .withPointerParameter("reg", (void *)&WWDG->CFGR) + .withUnsignedIntParameter("num", mask); + + mock().expectOneCall("RegEdit_u16_OR_Num") + .withPointerParameter("reg", (void *)&WWDG->CFGR) + .withUnsignedIntParameter("num", value); + } + + for(value = 0x40; value <= 127; value++){ + ret = wwdg_set_window(value); + CHECK_EQUAL(true, ret); + } +} + +TEST(tg_wwdg, SetEalryWakeUpSetsBit) +{ + mock().expectOneCall("RegEdit_u16_SetBit") + .withPointerParameter("reg", (void *)&WWDG->CFGR) + .withUnsignedIntParameter("bit_num", WWDG_EWI_BIT); + + wwdg_enable_interrupt(); +} + + +TEST(tg_wwdg, ClearEalyWakeUpClearsBit) +{ + mock().expectOneCall("RegEdit_u16_ClearBit") + .withPointerParameter("reg", (void *)&WWDG->CFGR) + .withUnsignedIntParameter("bit_num", WWDG_EWI_BIT); + + wwdg_disable_interrupt(); +} + +TEST(tg_wwdg, ResetFunctionSetsRegister) +{ + //uint16_t fake_reg = 0; + uint16_t reset_value = 126; + + /* + mock().expectOneCall("RegEdit_u16_ReadReg") + .withPointerParameter("reg", (void *)&WWDG->CTLR); + //.andReturnValue(); + */ + + mock().expectOneCall("RegEdit_u16_AND_Num") + .withPointerParameter("reg", (void *)&WWDG->CTLR) + .withUnsignedIntParameter("num", 0xFF80); + + mock().expectOneCall("RegEdit_u16_OR_Num") + .withPointerParameter("reg", (void *)&WWDG->CTLR) + .withUnsignedIntParameter("num", reset_value); + + CHECK_TRUE(wwdg_reset_timer(reset_value)); +} +