diff --git a/.gitignore b/.gitignore index c6127b3..65aa3b1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,52 +1,5 @@ -# Prerequisites -*.d - -# Object files -*.o -*.ko -*.obj -*.elf - -# Linker output -*.ilk -*.map -*.exp - -# Precompiled Headers -*.gch -*.pch - -# Libraries -*.lib -*.a -*.la -*.lo - -# Shared objects (inc. Windows DLLs) -*.dll -*.so -*.so.* -*.dylib - -# Executables -*.exe -*.out -*.app -*.i*86 -*.x86_64 -*.hex - -# Debug files -*.dSYM/ -*.su -*.idb -*.pdb - -# Kernel Module Compile Results -*.mod* -*.cmd -.tmp_versions/ -modules.order -Module.symvers -Mkfile.old -dkms.conf +[Bb]uild/ +*.vscode/ +*.vs/ +*.autosave* +*.code-workspace \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index b873ace..6fa507d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,34 +1,77 @@ cmake_minimum_required(VERSION 3.20) -# Use AVR GCC toolchain -set(CMAKE_TOOLCHAIN_FILE ${CMAKE_SOURCE_DIR}/Toolchain/avr8-gcc-toolchain.cmake) -project(LabBenchPowerSupply_Firmware C) +# Use AVR GCC toolchain (needs to be added before the project is declared, otherwise CMake does not take it into account) +if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) + set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_SOURCE_DIR}/Toolchain/avr8-gcc-toolchain.cmake) +endif() + -set(AVR_MCU atmega328p) -set(AVR_MCU_SPEED 16000000) +project(AvrAsyncCoreLibs C) set(CMAKE_CXX_STANDARD 17) -set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD 17) + + +# This option, disabled by default, allows CMake to configure subproject and build examples that are located within +# Driver's or Module's "examples" folder. +# The aim behind that is to validate that the examples are well declared and build fine altogether with regular codebase +option(BUILD_EXAMPLES "Will configure example projects as well" OFF ) + +############################################################################################################################################ +############################################# Checking environment is setup correctly ###################################################### +############################################################################################################################################ -add_compile_definitions( - F_CPU=${AVR_MCU_SPEED} - __AVR_ATmega328P__ - I2C_IMPLEM_MASTER_FULL -) -add_compile_options(-mmcu=${AVR_MCU}) +# If we are not in a submodule (this project is used as a standalone project) +# we do not want to set the CONFIG FILE +if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) + # This variable is needed by some drivers in order to locate the config.h file + # Provided by the application + set (CONFIG_FILE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/ExampleApp/inc) + set(AVR_MCU atmega328pb) + set(AVR_MCU_SPEED 16000000) + set(AVR_MCU_HEADER_DEFINITION __AVR_ATmega328P__) -set(CMAKE_EXE_LINKER_FLAGS "-mmcu=${AVR_MCU}") + # Global definitions for avr-gcc + add_compile_definitions( + F_CPU=${AVR_MCU_SPEED} + ${AVR_MCU_HEADER_DEFINITION} + ) + add_compile_options(-mmcu=${AVR_MCU}) -if(CMAKE_BUILD_TYPE MATCHES Debug) - add_definitions(-DDEBUG_WITH_SIMAVR) endif() -# This variable is needed by some drivers in order to locate the config.h file -# Provided by the application -set (CONFIG_FILE_DIR ${CMAKE_SOURCE_DIR}/App/inc) +if (NOT DEFINED CMAKE_TOOLCHAIN_FILE) + message(ERROR " This async core needs to know about toolchain file, please set the CMAKE_TOOLCHAIN_FILE variable to a valid toolchain file !") + message(STATUS "Some example toolchain files can be found at : https://github.com/bebenlebricolo/CMakeAS7-Toolchains") +endif() + +# Check that the config file directory was specified +if (NOT DEFINED CONFIG_FILE_DIR) + message(ERROR " Some drivers need to know the CONFIG_FILE_DIR variable ! You must use it to point to your config.h file's parent directory !") +endif() -add_subdirectory(${CMAKE_SOURCE_DIR}/Utils) -add_subdirectory(${CMAKE_SOURCE_DIR}/Drivers) -add_subdirectory(${CMAKE_SOURCE_DIR}/Modules) -add_subdirectory(${CMAKE_SOURCE_DIR}/Sensors) -add_subdirectory(${CMAKE_SOURCE_DIR}/App) +if (NOT DEFINED AVR_MCU) + message(WARNING " AVR_MCU was not found at this point, drivers need that variable in order to target the right MCU !") +endif() + +if (NOT DEFINED AVR_MCU_SPEED) + message(WARNING " AVR_MCU_SPEED was not found at this point, drivers need that variable in order to handle the right CPU clock speed !") +endif() + +if (NOT DEFINED AVR_MCU_HEADER_DEFINITION) + message(WARNING " AVR_MCU_HEADER_DEFINITION was not found at this point, drivers need that variable in order to consume the right files in atmel's lib !") +endif() + +############################################################################################################################################ +###################################################### Configuring all the libs ############################################################ +############################################################################################################################################ + +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Drivers) +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Modules) +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Utils) + +# If we are not in a submodule (this project is used as a standalone project) +# we do not want to set the CONFIG FILE +if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR) + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/ExampleApp) +endif() diff --git a/Drivers/Adc/CMakeLists.txt b/Drivers/Adc/CMakeLists.txt index 3d81bbd..532ea6d 100644 --- a/Drivers/Adc/CMakeLists.txt +++ b/Drivers/Adc/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0) +cmake_minimum_required(VERSION 3.20) add_library(adc_driver STATIC ${CMAKE_CURRENT_SOURCE_DIR}/src/adc_stack.c diff --git a/Drivers/Adc/Tests/CMakeLists.txt b/Drivers/Adc/Tests/CMakeLists.txt index 109f76a..eacf3ea 100644 --- a/Drivers/Adc/Tests/CMakeLists.txt +++ b/Drivers/Adc/Tests/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0) +cmake_minimum_required(VERSION 3.20) project(adc_tests) enable_testing() diff --git a/Drivers/CMakeLists.txt b/Drivers/CMakeLists.txt index 8da096f..243dd08 100644 --- a/Drivers/CMakeLists.txt +++ b/Drivers/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0) +cmake_minimum_required(VERSION 3.20) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Adc) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Timers/Timer_8_bit) diff --git a/Drivers/I2c/CMakeLists.txt b/Drivers/I2c/CMakeLists.txt index 1519e48..f54fb8f 100644 --- a/Drivers/I2c/CMakeLists.txt +++ b/Drivers/I2c/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0) +cmake_minimum_required(VERSION 3.20) # Stop here as we need an extra config.h file in order to build this driver # This file provides symbols for the driver to work properly diff --git a/Drivers/I2c/Examples/slave_firmware/App/inc/i2c_slave_handler.h b/Drivers/I2c/Examples/slave_firmware/App/inc/i2c_slave_handler.h index 54d8f6e..36095f5 100644 --- a/Drivers/I2c/Examples/slave_firmware/App/inc/i2c_slave_handler.h +++ b/Drivers/I2c/Examples/slave_firmware/App/inc/i2c_slave_handler.h @@ -8,7 +8,7 @@ * When a read operation (master reads from this slave), current_byte is considered as an output. * When a write operation (master writes to this slave), current_byte is considered as an input. * The request parameter informs about the nature of the current i2c transaction (read or write requests) - * @param[in/out] current_byte : interface byte used by the I2C driver + * @param[in,out] current_byte : interface byte used by the I2C driver * @param[in] request : nature of the I2C transaction currently taking place * @return I2C_SLAVE_HANDLER_ERROR_OK or any other error from the i2c_slave_handler_error_t enum in case of failure. */ diff --git a/Drivers/I2c/Tests/CMakeLists.txt b/Drivers/I2c/Tests/CMakeLists.txt index de53f94..67e197a 100644 --- a/Drivers/I2c/Tests/CMakeLists.txt +++ b/Drivers/I2c/Tests/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0) +cmake_minimum_required(VERSION 3.20) project(i2c_tests) enable_testing() diff --git a/Drivers/I2c/Tests/Stub/i2c_fake_slave_application_data.h b/Drivers/I2c/Tests/Stub/i2c_fake_slave_application_data.h index dcac787..8c5d8a1 100644 --- a/Drivers/I2c/Tests/Stub/i2c_fake_slave_application_data.h +++ b/Drivers/I2c/Tests/Stub/i2c_fake_slave_application_data.h @@ -4,7 +4,7 @@ @ FreeMyCode version : 1.0 RC alpha Author : bebenlebricolo - License : + License : name : GPLv3 url : https://www.gnu.org/licenses/quick-guide-gplv3.html Date : 12/02/2021 @@ -78,7 +78,7 @@ void i2c_fake_slave_application_init(void); /** * @brief handles the first byte of a read/write operation when configured as a slave - * @param[in/out] byte : gives the current byte on which we are working + * @param[in,out] byte : gives the current byte on which we are working * @param[in] request : gives the current I2C operation from master's point of view * => when master reads from slave, request shall be set to I2C_REQUEST_READ */ diff --git a/Drivers/I2c/inc/i2c.h b/Drivers/I2c/inc/i2c.h index 370b75d..92f9d78 100644 --- a/Drivers/I2c/inc/i2c.h +++ b/Drivers/I2c/inc/i2c.h @@ -4,7 +4,7 @@ @ FreeMyCode version : 1.0 RC alpha Author : bebenlebricolo - License : + License : name : GPLv3 url : https://www.gnu.org/licenses/quick-guide-gplv3.html Date : 12/02/2021 @@ -206,7 +206,7 @@ typedef enum * When a read operation (master reads from this slave), current_byte is considered as an output. * When a write operation (master writes to this slave), current_byte is considered as an input. * The request parameter informs about the nature of the current i2c transaction (read or write requests) - * @param[in/out] current_byte : interface byte used by the I2C driver + * @param[in,out] current_byte : interface byte used by the I2C driver * @param[in] request : nature of the I2C transaction currently taking place * * Example given : diff --git a/Drivers/Io/CMakeLists.txt b/Drivers/Io/CMakeLists.txt index 7374cd4..233587c 100644 --- a/Drivers/Io/CMakeLists.txt +++ b/Drivers/Io/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0) +cmake_minimum_required(VERSION 3.20) add_library(io_driver STATIC ${CMAKE_CURRENT_SOURCE_DIR}/src/io.c diff --git a/Drivers/Io/Tests/CMakeLists.txt b/Drivers/Io/Tests/CMakeLists.txt index 56ad700..a22a9e4 100644 --- a/Drivers/Io/Tests/CMakeLists.txt +++ b/Drivers/Io/Tests/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0) +cmake_minimum_required(VERSION 3.20) project(io_driver_tests) diff --git a/Drivers/Io/Tests/Stub/config.c b/Drivers/Io/Tests/Stub/config.c index f12642d..c591c38 100644 --- a/Drivers/Io/Tests/Stub/config.c +++ b/Drivers/Io/Tests/Stub/config.c @@ -54,7 +54,7 @@ io_t io_pins_lut[IO_MAX_PINS] = }; -io_config_t io_config = +io_reg_config_t io_reg_config = { .mcucr_reg = &stubbed_registers.st_mcucr, .porta_cfg = diff --git a/Drivers/Io/Tests/io_tests.cpp b/Drivers/Io/Tests/io_tests.cpp index 63131f5..19c6449 100644 --- a/Drivers/Io/Tests/io_tests.cpp +++ b/Drivers/Io/Tests/io_tests.cpp @@ -32,9 +32,24 @@ along with this program. If not, see . #include "io.h" #include "config.h" -TEST(io_driver, test_init) +class IoDriverTests : public ::testing::Test { - io_init(); +public : + void SetUp() override + { + } + + void TearDown() override + { + memset(get_stubbed_registers(), 0, sizeof(stubbed_registers_t)); + (void) io_deinit(); + } +}; + +TEST_F(IoDriverTests, test_init) +{ + io_error_t err = io_init(); + ASSERT_EQ(IO_ERROR_OK, err); auto stubs = get_stubbed_registers(); ASSERT_EQ(stubs->st_mcucr, 0); @@ -53,29 +68,59 @@ TEST(io_driver, test_init) ASSERT_EQ(stubs->st_portd.st_port_reg, (uint8_t)(1 << 5U)); ASSERT_EQ(stubs->st_portd.st_pin_reg, 0); ASSERT_EQ(stubs->st_portd.st_ddr_reg, 1 << 5U | 1 << 1U); + + ASSERT_TRUE(io_is_initialised()); } -TEST(io_driver, test_read) +TEST_F(IoDriverTests, test_deinit) { - io_init(); + io_error_t err = io_init(); + ASSERT_EQ(IO_ERROR_OK, err); + ASSERT_TRUE(io_is_initialised()); + + err = io_deinit(); + ASSERT_FALSE(io_is_initialised()); +} + +TEST_F(IoDriverTests, test_read) +{ + io_state_t state = IO_STATE_UNDEFINED; + io_error_t err = IO_ERROR_OK; + err = io_read(PIN_POWER_SWITCH, &state); + ASSERT_EQ(IO_ERROR_NOT_INITIALISED, err); + + err = io_init(); + ASSERT_EQ(IO_ERROR_OK, err); auto stubs = get_stubbed_registers(); stubs->st_portc.st_pin_reg |= 0xF0; - auto state = io_read(PIN_POWER_SWITCH); + // Check out of range detection + err = io_read(IO_MAX_PINS, &state); + ASSERT_EQ(IO_ERROR_INDEX_OUT_OF_RANGE, err); + + err = io_read(PIN_POWER_SWITCH, &state); ASSERT_EQ(state, IO_STATE_LOW); + ASSERT_EQ(IO_ERROR_OK, err); stubs->st_portc.st_pin_reg |= (1 << 3U); - state = io_read(PIN_POWER_SWITCH); + err = io_read(PIN_POWER_SWITCH, &state); ASSERT_EQ(state, IO_STATE_HIGH); + ASSERT_EQ(IO_ERROR_OK, err); stubs->st_portc.st_pin_reg &= ~(1 << 3U); - state = io_read(PIN_POWER_SWITCH); + err = io_read(PIN_POWER_SWITCH, &state); ASSERT_EQ(state, IO_STATE_LOW); + ASSERT_EQ(IO_ERROR_OK, err); } -TEST(io_driver, test_write) +TEST_F(IoDriverTests, test_write) { - io_init(); + io_error_t err = IO_ERROR_OK; + err = io_write(PIN_POWER_SWITCH, IO_STATE_HIGH); + ASSERT_EQ(IO_ERROR_NOT_INITIALISED, err); + + err = io_init(); + ASSERT_EQ(IO_ERROR_OK, err); auto stubs = get_stubbed_registers(); ASSERT_EQ(stubs->st_portb.st_port_reg, 0x2); diff --git a/Drivers/Io/inc/io.h b/Drivers/Io/inc/io.h index 871c1da..fd37d8e 100644 --- a/Drivers/Io/inc/io.h +++ b/Drivers/Io/inc/io.h @@ -1,5 +1,5 @@ -#ifndef IOMANAGER_HEADER -#define IOMANAGER_HEADER +#ifndef IO_DRIVER_HEADER +#define IO_DRIVER_HEADER #include #include @@ -15,6 +15,15 @@ extern "C" #pragma message("IO_MAX_PINS macro was not defined in this compilation unit, IO_MAX_PINS will take its default size.") #endif +typedef enum +{ + IO_ERROR_OK, /**< Operation succeeded */ + IO_ERROR_CONFIG, /**< Configuration issues */ + IO_ERROR_ALREADY_INITIALISED, /**< Io driver was already initialised */ + IO_ERROR_NOT_INITIALISED, /**< Used to reject actions on pins if driver is not initialised */ + IO_ERROR_INDEX_OUT_OF_RANGE /**< User tried to use an IO index greater than registered */ +} io_error_t; + /** * @brief describes a simplified interface for any * given pin to be configured (using enums instead of plain registers) @@ -33,8 +42,14 @@ typedef enum */ typedef enum { - IO_STATE_LOW = 0U, - IO_STATE_HIGH = 1U + IO_STATE_LOW = 0U, + IO_STATE_HIGH = 1U, + IO_STATE_UNDEFINED = 2U, /**< Undefined state is used by client code (drivers, modules, application) to tell IO driver to leave this pin untouched */ + // This is useful when client code provides the ability to set a given pin to a predefined state when starting operation. + // For instance, the software PWM driver may initialise a given IO pin to a predefined state when PWM is not running + // to prevent hardware/electrical issues such as overheating, etc. + // In such cases, the "safe" state for a pin can be specified either by LOW or HIGH state, and sometimes we can use UNDEFINED + // state to indicate to the client code not to interact with the pin and leave it in its current state } io_state_t; /** @@ -82,7 +97,7 @@ typedef struct io_port_config_t portb_cfg; /**< Port B configuration */ io_port_config_t portc_cfg; /**< Port C configuration */ io_port_config_t portd_cfg; /**< Port D configuration */ -} io_config_t; +} io_reg_config_t; /** * @brief the following extern symbol is a lookup table with pins configuration. @@ -98,7 +113,7 @@ extern io_t io_pins_lut[IO_MAX_PINS]; * Further calls to those addresses (e.g. while reading or writing to a pin) * will normally by optimized by the compiler as direct calls afterwards (no pointer dereferencing then) */ -extern io_config_t io_config; +extern io_reg_config_t io_reg_config; /** * @brief reads data on a given io, using its index as an input (referencing an @@ -108,7 +123,7 @@ extern io_config_t io_config; * true : pin is set (high logic level) * false : pin is not set (low logic level) */ -io_state_t io_read(const uint8_t index); +io_error_t io_read(const uint8_t index, io_state_t * const state); /** * @brief writes data to a single pin, using its index (from the lookup table) @@ -116,16 +131,30 @@ io_state_t io_read(const uint8_t index); * in case where the pin is set as an input and that you write to it, you may change the pin configuration ! * @param[in] index : index of the pin in the lookup table * @param[in] state : new state of the pin + * @return + * IO_ERROR_OK : operation succeeded + * IO_ERROR_CONFIG : input state is still set to undefined, that's a config issue + * IO_ERROR_INDEX_OUT_OF_RANGE : given index is greater than registered IO_MAX_PINS macro */ -void io_write(const uint8_t index, const io_state_t state); +io_error_t io_write(const uint8_t index, const io_state_t state); /** * @brief configures all registered pins and give them default state, if any */ -void io_init(void); +io_error_t io_init(void); + +/** + * @brief Reverts all previously configured pins as being high impedance inputs + */ +io_error_t io_deinit(void); + +/** + * @brief Checks if IO driver was initialised + */ +bool io_is_initialised(void); #ifdef __cplusplus } #endif -#endif /* IOMANAGER_HEADER */ \ No newline at end of file +#endif /* IO_DRIVER_HEADER */ \ No newline at end of file diff --git a/Drivers/Io/src/io.c b/Drivers/Io/src/io.c index 3fafb88..a19a110 100644 --- a/Drivers/Io/src/io.c +++ b/Drivers/Io/src/io.c @@ -2,14 +2,18 @@ #define PUD_MSK (0x10) +static bool initialised = false; + static io_port_config_t * port_lut[IO_PORT_COUNT] = { - &io_config.porta_cfg, - &io_config.portb_cfg, - &io_config.portc_cfg, - &io_config.portd_cfg + &io_reg_config.porta_cfg, + &io_reg_config.portb_cfg, + &io_reg_config.portc_cfg, + &io_reg_config.portd_cfg }; +static inline io_error_t check_index(const uint8_t index); + static inline void configure_single_pin(io_port_config_t * config, io_t * io) { if(io->direction == IO_OUT_PUSH_PULL) @@ -38,8 +42,14 @@ static inline void configure_single_pin(io_port_config_t * config, io_t * io) } } -void io_init(void) +io_error_t io_init(void) { + // Skip reinitialisation if driver is already initialised + if(initialised) + { + return IO_ERROR_ALREADY_INITIALISED; + } + bool needs_pull_up = false; for (uint8_t i = 0 ; i < IO_MAX_PINS ; i++) { @@ -51,27 +61,92 @@ void io_init(void) // Only enables pull ups if requested if (true == needs_pull_up) { - *io_config.mcucr_reg |= PUD_MSK; + *io_reg_config.mcucr_reg |= PUD_MSK; } + + initialised = true; + return IO_ERROR_OK; } -io_state_t io_read(const uint8_t index) +io_error_t io_deinit(void) { - io_t * io = &io_pins_lut[index]; - io_state_t state = (io_state_t) ((*port_lut[io->port]->pin_reg & (1 << io->pin)) >> io->pin); - return state; + for (uint8_t i = 0 ; i < IO_MAX_PINS ; i++) + { + // Copy the io as we don't want to modify original io configuration in-place + // with this function + io_t io = io_pins_lut[i]; + io.direction = IO_IN_TRISTATE; + configure_single_pin(port_lut[io.port], &io); + } + // Disables Pull ups globally + *io_reg_config.mcucr_reg &= ~PUD_MSK; + initialised = false; + return IO_ERROR_OK; } -void io_write(const uint8_t index, const io_state_t state) +bool io_is_initialised(void) { + return initialised; +} + +io_error_t io_read(const uint8_t index, io_state_t * const state) +{ + io_error_t ret = check_index(index); + if(IO_ERROR_OK != ret) + { + return ret; + } + + // Reject action if not initialised (because we might be messing up with the pins which might be in the wrong state) + if(false == initialised) + { + return IO_ERROR_NOT_INITIALISED; + } + io_t * io = &io_pins_lut[index]; - if ( IO_STATE_HIGH == state ) + *state = (io_state_t) ((*port_lut[io->port]->pin_reg & (1 << io->pin)) >> io->pin); + return ret; +} + +io_error_t io_write(const uint8_t index, const io_state_t state) +{ + io_error_t ret = check_index(index); + if(IO_ERROR_OK != ret) { - *port_lut[io->port]->port_reg |= (1 << io->pin); + return ret; } - else + + // Reject action if not initialised (because we might be messing up with the pins which might be in the wrong state) + if(false == initialised) + { + return IO_ERROR_NOT_INITIALISED; + } + + io_t * io = &io_pins_lut[index]; + switch (state) { + case IO_STATE_HIGH: + *port_lut[io->port]->port_reg |= (1 << io->pin); + break; + + case IO_STATE_LOW : *port_lut[io->port]->port_reg &= ~(1 << io->pin); + break; + + // Undefined state is used by other drivers to indicate "do not interact with this pin" + case IO_STATE_UNDEFINED : + default: + ret = IO_ERROR_CONFIG; + break; } + return ret; } +static inline io_error_t check_index(const uint8_t index) +{ + if(index >= IO_MAX_PINS) + { + return IO_ERROR_INDEX_OUT_OF_RANGE; + } + return IO_ERROR_OK; +} diff --git a/Drivers/Lcd_screen/CMakeLists.txt b/Drivers/Lcd_screen/CMakeLists.txt index f4caeea..fef8d66 100644 --- a/Drivers/Lcd_screen/CMakeLists.txt +++ b/Drivers/Lcd_screen/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0) +cmake_minimum_required(VERSION 3.20) add_library(HD44780_lcd_driver STATIC ${CMAKE_CURRENT_SOURCE_DIR}/src/HD44780_lcd.c diff --git a/Drivers/Lcd_screen/Examples/App/src/module_setup.c b/Drivers/Lcd_screen/Examples/App/src/module_setup.c index 81a4d1d..ba4b89d 100644 --- a/Drivers/Lcd_screen/Examples/App/src/module_setup.c +++ b/Drivers/Lcd_screen/Examples/App/src/module_setup.c @@ -4,7 +4,7 @@ module_setup_error_t module_init_timebase(void) { timebase_config_t config = {0}; - config.cpu_freq = 16000000; + config.clock_freq = 16000000; config.timer.index = 0; config.timer.type = TIMEBASE_TIMER_8_BIT_ASYNC; config.timescale = TIMEBASE_TIMESCALE_MILLISECONDS; diff --git a/Drivers/Lcd_screen/Tests/CMakeLists.txt b/Drivers/Lcd_screen/Tests/CMakeLists.txt index 018a460..ae9c271 100644 --- a/Drivers/Lcd_screen/Tests/CMakeLists.txt +++ b/Drivers/Lcd_screen/Tests/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0) +cmake_minimum_required(VERSION 3.20) project(HD44780_lcd) enable_testing() diff --git a/Drivers/Lcd_screen/inc/HD44780_lcd.h b/Drivers/Lcd_screen/inc/HD44780_lcd.h index ca7aa7e..208eeca 100644 --- a/Drivers/Lcd_screen/inc/HD44780_lcd.h +++ b/Drivers/Lcd_screen/inc/HD44780_lcd.h @@ -4,7 +4,7 @@ @ FreeMyCode version : 1.0 RC alpha Author : bebenlebricolo - License : + License : name : GPLv3 url : https://www.gnu.org/licenses/quick-guide-gplv3.html Date : 12/02/2021 @@ -223,7 +223,7 @@ hd44780_lcd_error_t hd44780_lcd_init(hd44780_lcd_config_t const * const config); /** * @brief Gives a default configuration to start with - * @param[in/out] config : device configuration + * @param[in,out] config : device configuration * @return hd44780_lcd_error_t * HD44780_LCD_ERROR_OK : Operation succeeded * HD44780_LCD_ERROR_NULL_POINTER : Given pointer is uninitialised diff --git a/Drivers/Timers/CMakeLists.txt b/Drivers/Timers/CMakeLists.txt index af2a2d2..e73dac8 100644 --- a/Drivers/Timers/CMakeLists.txt +++ b/Drivers/Timers/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0) +cmake_minimum_required(VERSION 3.20) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Timer_8_bit) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Timer_8_bit_async) diff --git a/Drivers/Timers/Timer_16_bit/CMakeLists.txt b/Drivers/Timers/Timer_16_bit/CMakeLists.txt index 2242eb1..bd81c12 100644 --- a/Drivers/Timers/Timer_16_bit/CMakeLists.txt +++ b/Drivers/Timers/Timer_16_bit/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0) +cmake_minimum_required(VERSION 3.20) add_library(timer_16_bit_driver STATIC ${CMAKE_CURRENT_SOURCE_DIR}/src/timer_16_bit.c diff --git a/Drivers/Timers/Timer_16_bit/Tests/CMakeLists.txt b/Drivers/Timers/Timer_16_bit/Tests/CMakeLists.txt index 235f081..b0777b9 100644 --- a/Drivers/Timers/Timer_16_bit/Tests/CMakeLists.txt +++ b/Drivers/Timers/Timer_16_bit/Tests/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0) +cmake_minimum_required(VERSION 3.20) project(timer_16_bit_driver_test) enable_testing() @@ -26,6 +26,8 @@ target_include_directories(timer_16_bit_driver_tests PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/Stub ${CMAKE_CURRENT_SOURCE_DIR}/../inc ${CMAKE_CURRENT_SOURCE_DIR}/../../Timer_generic/inc + ${CMAKE_CURRENT_SOURCE_DIR} + ) target_include_directories(timer_16_bit_driver_tests SYSTEM PUBLIC diff --git a/Drivers/Timers/Timer_16_bit/Tests/Stub/timer_16_bit_registers_stub.c b/Drivers/Timers/Timer_16_bit/Tests/Stub/timer_16_bit_registers_stub.c index e1880ec..90cdea4 100644 --- a/Drivers/Timers/Timer_16_bit/Tests/Stub/timer_16_bit_registers_stub.c +++ b/Drivers/Timers/Timer_16_bit/Tests/Stub/timer_16_bit_registers_stub.c @@ -4,7 +4,7 @@ @ FreeMyCode version : 1.0 RC alpha Author : bebenlebricolo - License : + License : name : GPLv3 url : https://www.gnu.org/licenses/quick-guide-gplv3.html Date : 12/02/2021 @@ -28,8 +28,9 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -#include "timer_16_bit_registers_stub.h" #include +#include "timer_16_bit.h" +#include "timer_16_bit_registers_stub.h" timer_16_bit_registers_stub_t timer_16_bit_registers_stub = {0}; @@ -38,24 +39,23 @@ void timer_16_bit_registers_stub_erase(void) memset(&timer_16_bit_registers_stub, 0, sizeof(timer_16_bit_registers_stub_t)); } -void timer_16_bit_registers_stub_init_handle(timer_16_bit_handle_t * handle) +timer_16_bit_handle_t timer_16_bit_static_handle[TIMER_16_BIT_COUNT] = { - if (NULL != handle) { - handle->TCCRA = &timer_16_bit_registers_stub.TCCRA; - handle->TCCRB = &timer_16_bit_registers_stub.TCCRB; - handle->TCCRC = &timer_16_bit_registers_stub.TCCRC; - handle->OCRA_H = &timer_16_bit_registers_stub.OCRA_H; - handle->OCRA_L = &timer_16_bit_registers_stub.OCRA_L; - handle->OCRB_H = &timer_16_bit_registers_stub.OCRB_H; - handle->OCRB_L = &timer_16_bit_registers_stub.OCRB_L; - handle->TCNT_H = &timer_16_bit_registers_stub.TCNT_H; - handle->TCNT_L = &timer_16_bit_registers_stub.TCNT_L; - handle->ICR_H = &timer_16_bit_registers_stub.ICR_H; - handle->ICR_L = &timer_16_bit_registers_stub.ICR_L; - handle->TIMSK = &timer_16_bit_registers_stub.TIMSK; - handle->TIFR = &timer_16_bit_registers_stub.TIFR; + .TCCRA = &timer_16_bit_registers_stub.TCCRA, + .TCCRB = &timer_16_bit_registers_stub.TCCRB, + .TCCRC = &timer_16_bit_registers_stub.TCCRC, + .OCRA_H = &timer_16_bit_registers_stub.OCRA_H, + .OCRA_L = &timer_16_bit_registers_stub.OCRA_L, + .OCRB_H = &timer_16_bit_registers_stub.OCRB_H, + .OCRB_L = &timer_16_bit_registers_stub.OCRB_L, + .TCNT_H = &timer_16_bit_registers_stub.TCNT_H, + .TCNT_L = &timer_16_bit_registers_stub.TCNT_L, + .ICR_H = &timer_16_bit_registers_stub.ICR_H, + .ICR_L = &timer_16_bit_registers_stub.ICR_L, + .TIMSK = &timer_16_bit_registers_stub.TIMSK, + .TIFR = &timer_16_bit_registers_stub.TIFR } -} +}; diff --git a/Drivers/Timers/Timer_16_bit/Tests/timer_16_bit_tests.cpp b/Drivers/Timers/Timer_16_bit/Tests/timer_16_bit_tests.cpp index c7c55c9..486adac 100644 --- a/Drivers/Timers/Timer_16_bit/Tests/timer_16_bit_tests.cpp +++ b/Drivers/Timers/Timer_16_bit/Tests/timer_16_bit_tests.cpp @@ -4,7 +4,7 @@ @ FreeMyCode version : 1.0 RC alpha Author : bebenlebricolo - License : + License : name : GPLv3 url : https://www.gnu.org/licenses/quick-guide-gplv3.html Date : 12/02/2021 @@ -46,8 +46,7 @@ class Timer16BitFixture : public ::testing::Test { timer_16_bit_registers_stub_erase(); (void) timer_16_bit_get_default_config(&config); - timer_16_bit_registers_stub_init_handle(&config.handle); - (void) timer_16_bit_set_handle(DT_ID, &config.handle); + timer_16_bit_clear_init_states(); } void TearDown() override { @@ -60,97 +59,76 @@ TEST(timer_16_bit_driver_tests, guard_null_handle) timer_error_t ret = timer_16_bit_get_default_config(&config); ASSERT_EQ(TIMER_ERROR_OK, ret); - /* We should have a null handle at the moment */ - ASSERT_TRUE(NULL == (void*) (config.handle.OCRA_H)); - ASSERT_TRUE(NULL == (void*) (config.handle.OCRA_L)); - ASSERT_TRUE(NULL == (void*) (config.handle.OCRB_H)); - ASSERT_TRUE(NULL == (void*) (config.handle.OCRB_L)); - ASSERT_TRUE(NULL == (void*) (config.handle.TCCRA)); - ASSERT_TRUE(NULL == (void*) (config.handle.TCCRB)); - ASSERT_TRUE(NULL == (void*) (config.handle.TCCRC)); - ASSERT_TRUE(NULL == (void*) (config.handle.TCNT_H)); - ASSERT_TRUE(NULL == (void*) (config.handle.TCNT_L)); - ASSERT_TRUE(NULL == (void*) (config.handle.ICR_H)); - ASSERT_TRUE(NULL == (void*) (config.handle.ICR_L)); - ASSERT_TRUE(NULL == (void*) (config.handle.TIFR)); - ASSERT_TRUE(NULL == (void*) (config.handle.TIMSK)); - ret = timer_16_bit_init(DT_ID, &config); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); ret = timer_16_bit_reconfigure(DT_ID, &config); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); /* Test compare match mode A get/set api */ ret = timer_16_bit_set_compare_match_A(DT_ID, config.timing_config.comp_match_a); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); ret = timer_16_bit_get_compare_match_A(DT_ID, &config.timing_config.comp_match_a); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); /* Test compare match mode B get/set api */ ret = timer_16_bit_set_compare_match_B(DT_ID, config.timing_config.comp_match_b); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); - ret = timer_16_bit_get_compare_match_B(DT_ID, &config.timing_config.comp_match_b); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); - - /* Test handle setting function */ - ret = timer_16_bit_set_handle(DT_ID, &config.handle); ASSERT_EQ(TIMER_ERROR_OK, ret); - ret = timer_16_bit_get_handle(DT_ID, &config.handle); + ret = timer_16_bit_get_compare_match_B(DT_ID, &config.timing_config.comp_match_b); ASSERT_EQ(TIMER_ERROR_OK, ret); /* Test interrupt config get/set api */ ret = timer_16_bit_set_interrupt_config(DT_ID, &config.interrupt_config); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); ret = timer_16_bit_get_interrupt_config(DT_ID, &config.interrupt_config); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); /* Test OCRA get/set api */ ret = timer_16_bit_set_ocra_register_value(DT_ID, &config.timing_config.ocra_val); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); ret = timer_16_bit_get_ocra_register_value(DT_ID, &config.timing_config.ocra_val); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); /* Test OCRB get/set api */ ret = timer_16_bit_set_ocrb_register_value(DT_ID, &config.timing_config.ocrb_val); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); ret = timer_16_bit_get_ocrb_register_value(DT_ID, &config.timing_config.ocrb_val); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); /* Test counter get/set api */ ret = timer_16_bit_set_counter_value(DT_ID, &config.timing_config.counter); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); ret = timer_16_bit_get_counter_value(DT_ID, &config.timing_config.counter); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); /* Test force compare flags get/set api */ ret = timer_16_bit_set_force_compare_config(DT_ID, &config.force_compare); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); ret = timer_16_bit_get_force_compare_config(DT_ID, &config.force_compare); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); /* Test prescaler get/set api */ ret = timer_16_bit_set_prescaler(DT_ID, config.timing_config.prescaler); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); ret = timer_16_bit_get_prescaler(DT_ID, &config.timing_config.prescaler); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); /* Test waveform generation get/set api */ ret = timer_16_bit_set_waveform_generation(DT_ID, config.timing_config.waveform_mode); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); ret = timer_16_bit_get_waveform_generation(DT_ID, &config.timing_config.waveform_mode); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); /* Test input capture edge select get/set api */ ret = timer_16_bit_set_input_compare_edge_select(DT_ID, config.input_capture.edge_select); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); ret = timer_16_bit_get_input_compare_edge_select(DT_ID, &config.input_capture.edge_select); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); /* Test input capture noise canceler get/set api */ ret = timer_16_bit_set_input_compare_noise_canceler(DT_ID, config.input_capture.use_noise_canceler); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); ret = timer_16_bit_get_input_compare_noise_canceler(DT_ID, &config.input_capture.use_noise_canceler); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); } TEST(timer_16_bit_driver_tests, guard_null_pointer) @@ -172,13 +150,6 @@ TEST(timer_16_bit_driver_tests, guard_null_pointer) ret = timer_16_bit_init(DT_ID, nullptr_config); ASSERT_EQ(TIMER_ERROR_NULL_POINTER, ret); - timer_16_bit_handle_t * nullptr_handle = NULL; - /* Test handle setting function */ - ret = timer_16_bit_set_handle(DT_ID, nullptr_handle); - ASSERT_EQ(TIMER_ERROR_NULL_POINTER, ret); - ret = timer_16_bit_get_handle(DT_ID, nullptr_handle); - ASSERT_EQ(TIMER_ERROR_NULL_POINTER, ret); - /* Test interrupt config get/set api */ timer_16_bit_interrupt_config_t * nullptr_interrupt_config = NULL; ret = timer_16_bit_set_interrupt_config(DT_ID, nullptr_interrupt_config); @@ -245,12 +216,6 @@ TEST(timer_16_bit_driver_tests, guard_wrong_id) ret = timer_16_bit_get_compare_match_B(targeted_id, &config.timing_config.comp_match_b); ASSERT_EQ(TIMER_ERROR_UNKNOWN_TIMER, ret); - /* Test handle setting function */ - ret = timer_16_bit_set_handle(targeted_id, &config.handle); - ASSERT_EQ(TIMER_ERROR_UNKNOWN_TIMER, ret); - ret = timer_16_bit_get_handle(targeted_id, &config.handle); - ASSERT_EQ(TIMER_ERROR_UNKNOWN_TIMER, ret); - /* Test interrupt config get/set api */ ret = timer_16_bit_set_interrupt_config(targeted_id, &config.interrupt_config); ASSERT_EQ(TIMER_ERROR_UNKNOWN_TIMER, ret); @@ -612,19 +577,19 @@ TEST_F(Timer16BitFixture, test_initialisation_deinitialisation) TEST(timer_16_bit_driver_tests, test_parameters_computation_prescaler) { - uint32_t cpu_freq = 16'000'000; + uint32_t clock_freq = 16'000'000; uint32_t target_freq = 1'000; uint16_t ocra = 0; uint16_t accumulator = 0; - + timer_generic_resolution_t resolution = TIMER_GENERIC_RESOLUTION_16_BIT; timer_16_bit_prescaler_selection_t prescaler = TIMER16BIT_CLK_PRESCALER_1; - timer_16_bit_compute_matching_parameters(&cpu_freq, &target_freq, &prescaler, &ocra, &accumulator); + timer_16_bit_compute_matching_parameters(&clock_freq, &target_freq, resolution, &prescaler, &ocra, &accumulator); ASSERT_EQ(prescaler, TIMER16BIT_CLK_PRESCALER_1); ASSERT_EQ(ocra, 15999U); ASSERT_EQ(accumulator, 0U); target_freq = 12'000'000; - timer_16_bit_compute_matching_parameters(&cpu_freq, &target_freq, &prescaler, &ocra, &accumulator); + timer_16_bit_compute_matching_parameters(&clock_freq, &target_freq , resolution, &prescaler, &ocra, &accumulator); // This timer could not achieve such a target frequency because even in its fastest configuration, // with a prescaler of 1 and ocra of 0, the timer will tick at 16 MHz. // As output frequency is dependent on ocra, an ocra of 1 divides input cpu frequency by 2, which gives 8 MHz. @@ -634,26 +599,67 @@ TEST(timer_16_bit_driver_tests, test_parameters_computation_prescaler) ASSERT_EQ(accumulator, 0U); target_freq = 8'000'000; - timer_16_bit_compute_matching_parameters(&cpu_freq, &target_freq, &prescaler, &ocra, &accumulator); + timer_16_bit_compute_matching_parameters(&clock_freq, &target_freq, resolution, &prescaler, &ocra, &accumulator); ASSERT_EQ(prescaler, TIMER16BIT_CLK_PRESCALER_1); ASSERT_EQ(ocra, 1U); ASSERT_EQ(accumulator, 0U); - cpu_freq = 8'000'000; + clock_freq = 8'000'000; target_freq = 3'000; - timer_16_bit_compute_matching_parameters(&cpu_freq, &target_freq, &prescaler, &ocra, &accumulator); + timer_16_bit_compute_matching_parameters(&clock_freq, &target_freq, resolution, &prescaler, &ocra, &accumulator); ASSERT_EQ(prescaler, TIMER16BIT_CLK_PRESCALER_1); ASSERT_EQ(ocra, 2665U); ASSERT_EQ(accumulator, 0U); - cpu_freq = 16'000'000; + clock_freq = 16'000'000; target_freq = 1U; - timer_16_bit_compute_matching_parameters(&cpu_freq, &target_freq, &prescaler, &ocra, &accumulator); + timer_16_bit_compute_matching_parameters(&clock_freq, &target_freq, resolution, &prescaler, &ocra, &accumulator); ASSERT_EQ(prescaler, TIMER16BIT_CLK_PRESCALER_256); ASSERT_EQ(ocra, 62499U); ASSERT_EQ(accumulator, 0U); } +TEST(timer_16_bit_driver_tests, test_find_closest_prescaler_function) +{ + timer_error_t err = TIMER_ERROR_OK; + uint32_t clock_freq = 16'000'000; + uint32_t target_freq = 1'000'000; + + timer_16_bit_prescaler_selection_t prescaler = TIMER16BIT_CLK_PRESCALER_1; + err = timer_16_bit_compute_closest_prescaler(&clock_freq, &target_freq, TIMER_GENERIC_RESOLUTION_16_BIT, &prescaler); + ASSERT_EQ(err, TIMER_ERROR_OK); + ASSERT_EQ(prescaler, TIMER16BIT_CLK_PRESCALER_1); + + target_freq = 1'000; + err = timer_16_bit_compute_closest_prescaler(&clock_freq, &target_freq, TIMER_GENERIC_RESOLUTION_16_BIT, &prescaler); + ASSERT_EQ(err, TIMER_ERROR_OK); + ASSERT_EQ(prescaler, TIMER16BIT_CLK_PRESCALER_1); + + + target_freq = 3'000; + err = timer_16_bit_compute_closest_prescaler(&clock_freq, &target_freq, TIMER_GENERIC_RESOLUTION_16_BIT, &prescaler); + ASSERT_EQ(err, TIMER_ERROR_OK); + ASSERT_EQ(prescaler, TIMER16BIT_CLK_PRESCALER_1); + + target_freq = 5'000; + err = timer_16_bit_compute_closest_prescaler(&clock_freq, &target_freq, TIMER_GENERIC_RESOLUTION_16_BIT, &prescaler); + ASSERT_EQ(err, TIMER_ERROR_OK); + ASSERT_EQ(prescaler, TIMER16BIT_CLK_PRESCALER_1); + + clock_freq = 8'000'000; + target_freq = 440; + err = timer_16_bit_compute_closest_prescaler(&clock_freq, &target_freq, TIMER_GENERIC_RESOLUTION_16_BIT, &prescaler); + ASSERT_EQ(err, TIMER_ERROR_OK); + ASSERT_EQ(prescaler, TIMER16BIT_CLK_PRESCALER_1); + + clock_freq = 16'000'000; + target_freq = 1; + err = timer_16_bit_compute_closest_prescaler(&clock_freq, &target_freq, TIMER_GENERIC_RESOLUTION_16_BIT, &prescaler); + ASSERT_EQ(err, TIMER_ERROR_OK); + ASSERT_EQ(prescaler, TIMER16BIT_CLK_PRESCALER_256); +} + + int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); diff --git a/Drivers/Timers/Timer_16_bit/inc/timer_16_bit.h b/Drivers/Timers/Timer_16_bit/inc/timer_16_bit.h index ab2baa5..2a28eac 100644 --- a/Drivers/Timers/Timer_16_bit/inc/timer_16_bit.h +++ b/Drivers/Timers/Timer_16_bit/inc/timer_16_bit.h @@ -4,7 +4,7 @@ @ FreeMyCode version : 1.0 RC alpha Author : bebenlebricolo - License : + License : name : GPLv3 url : https://www.gnu.org/licenses/quick-guide-gplv3.html Date : 12/02/2021 @@ -33,6 +33,7 @@ along with this program. If not, see . #include #include "timer_16_bit_reg.h" +#include "config.h" #ifdef __cplusplus extern "C" @@ -77,7 +78,6 @@ typedef struct timer_16_bit_interrupt_config_t interrupt_config; /**< Handles interrupt configuraitons for 16 bit timers */ timer_16_bit_force_compare_config_t force_compare; /**< Handles force compare flags on output A and B, generic configuration among timers */ timer_16_bit_input_capture_noise_canceler_config_t input_capture; /**< Handles input capture noise canceler configuration for 16-bit timers */ - timer_16_bit_handle_t handle; /**< Stores pointer locations to peripheral registers */ } timer_16_bit_config_t; /* ############################################################################################################## @@ -96,30 +96,6 @@ typedef struct */ timer_error_t timer_16_bit_get_default_config(timer_16_bit_config_t * config); -/** - * @brief sets the handle of timer_16_bit driver - * @param[in] id : targeted timer id (used to fetch internal configuration based on ids) - * @param[in] handle : handle to be copied into internal configuration - * @return - * TIMER_ERROR_OK : operation succeeded - * TIMER_ERROR_UNKNOWN_TIMER : given id is out of range - * TIMER_ERROR_NULL_POINTER : given force_comp_config parameter points to NULL -*/ -timer_error_t timer_16_bit_set_handle(uint8_t id, timer_16_bit_handle_t * const handle); - -/** - * @brief gets the handle of timer_16_bit driver - * @param[in] id : targeted timer id (used to fetch internal configuration based on ids) - * @param[in] handle : handle to be copied from internal configuration - * @return - * TIMER_ERROR_OK : operation succeeded - * TIMER_ERROR_UNKNOWN_TIMER : given id is out of range - * TIMER_ERROR_NULL_POINTER : given force_comp_config parameter points to NULL -*/ -timer_error_t timer_16_bit_get_handle(uint8_t id, timer_16_bit_handle_t * const handle); - - - /* ################################ Force compare flags configuration ############################### */ /** * @brief sets the given force compare configuration object to targeted timer @@ -183,6 +159,11 @@ timer_error_t timer_16_bit_get_interrupt_config(uint8_t id, timer_16_bit_interru * TIMER_ERROR_NULL_POINTER : given it_config parameter points to NULL */ timer_error_t timer_16_bit_get_interrupt_flags(uint8_t id, timer_16_bit_interrupt_config_t * it_flags); + +/** + * @brief Resets internal configuration of timer 16 bit driver + */ +void timer_16_bit_clear_init_states(void); #endif /** @@ -365,7 +346,25 @@ timer_error_t timer_16_bit_set_ocrb_register_value(uint8_t id, const uint16_t * */ timer_error_t timer_16_bit_get_ocrb_register_value(uint8_t id, uint16_t * const ocrb); +/** + * @brief sets the targeted timer Input Capture Register value + * @param[in] id : targeted timer id (used to fetch internal configuration based on ids) + * @param[in] icr : icr value to be set + * @return + * TIMER_ERROR_OK : operation succeeded + * TIMER_ERROR_UNKNOWN_TIMER : given id is out of range +*/ +timer_error_t timer_16_bit_set_icr_register_value(uint8_t id, const uint16_t icr); +/** + * @brief fetches given timer Input Capture Register value from memory + * @param[in] id : targeted timer id (used to fetch internal configuration based on ids) + * @param[in] icr : pointer to icr value + * @return + * TIMER_ERROR_OK : operation succeeded + * TIMER_ERROR_UNKNOWN_TIMER : given id is out of range +*/ +timer_error_t timer_16_bit_get_icr_register_value(uint8_t id, uint16_t * const icr); @@ -473,24 +472,61 @@ timer_error_t timer_16_bit_start(uint8_t id); */ timer_error_t timer_16_bit_stop(uint8_t id); -#define TIMER_16_BIT_MAX_PRESCALER_COUNT (5U) +/** + * @brief Computes timing parameters such as prescaler, ocr value and accumulator in order to satisfy the requested target frequency, + * using CPU main clock frequency as the time constraint. + * + * @param[in] clock_freq : current CPU main clock frequency + * @param[in] target_freq : desired output frequency of the timer (assuming ocr is the top value) + * @param[in] resolution : encodes the resolution of the hardware timer : 8,9,10 or 16 bits resolutions are supported + * @param[out] prescaler : output prescaler parameter + * @param[out] ocr : output ocr value + * @param[out] accumulator : output accumulator value ; used by the timebase module for instance to count events and extend timer's counter capabilities + */ +timer_error_t timer_16_bit_compute_matching_parameters(const uint32_t * const clock_freq, + const uint32_t * const target_freq, + const timer_generic_resolution_t resolution, + timer_16_bit_prescaler_selection_t * const prescaler, + uint16_t * const ocr, + uint16_t * const accumulator); + +/** + * @brief Computes timing parameters such as prescaler, ocr value and accumulator in order to satisfy the requested target frequency, + * using CPU main clock frequency as the time constraint. + * + * @param[in] clock_freq : current CPU main clock frequency + * @param[in] target_freq : desired output frequency of the timer (assuming ocr is the top value) + * @param[out] prescaler : output prescaler parameter + */ +timer_error_t timer_16_bit_compute_closest_prescaler(const uint32_t * const clock_freq, + const uint32_t * const target_freq, + const timer_generic_resolution_t resolution, + timer_16_bit_prescaler_selection_t * const prescaler); -void timer_16_bit_compute_matching_parameters(const uint32_t * const cpu_freq, - const uint32_t * const target_freq, - timer_16_bit_prescaler_selection_t * const prescaler, - uint16_t * const ocra, - uint16_t * const accumulator); +#define TIMER_16_BIT_MAX_PRESCALER_COUNT (5U) /** - * @brief Timer 16 bit prescaler table, ascending order. Used to compute the closest prescaler - * which can be used to generate any given frequency -*/ -extern const timer_generic_prescaler_pair_t timer_16_bit_prescaler_table[TIMER_16_BIT_MAX_PRESCALER_COUNT]; - + * @brief Converts a single prescaler enum to its value-based counterpart. + * Behaves as a lookup table + * @param prescaler : input prescaler + * @return uint16_t : returns the translated value (from 1 to 1024) + */ uint16_t timer_16_bit_prescaler_to_value(const timer_16_bit_prescaler_selection_t prescaler); + +/** + * @brief Translates back a plain prescaler value to its enum counterpart. + * If no direct equivalent is found, returns the TIMER8BIT_CLK_NO_CLOCK enum value + * @param input_prescaler : input prescaler parameter + * @return enum value + */ timer_16_bit_prescaler_selection_t timer_16_bit_prescaler_from_value(uint16_t const * const input_prescaler); +/** + * @brief this handle has to be declared somewhere and initialised appropriately + */ +extern timer_16_bit_handle_t timer_16_bit_static_handle[TIMER_16_BIT_COUNT]; + #ifdef __cplusplus } #endif diff --git a/Drivers/Timers/Timer_16_bit/inc/timer_16_bit_reg.h b/Drivers/Timers/Timer_16_bit/inc/timer_16_bit_reg.h index 4e2aeec..9c78bcf 100644 --- a/Drivers/Timers/Timer_16_bit/inc/timer_16_bit_reg.h +++ b/Drivers/Timers/Timer_16_bit/inc/timer_16_bit_reg.h @@ -4,7 +4,7 @@ @ FreeMyCode version : 1.0 RC alpha Author : bebenlebricolo - License : + License : name : GPLv3 url : https://www.gnu.org/licenses/quick-guide-gplv3.html Date : 12/02/2021 @@ -172,7 +172,7 @@ typedef struct timer_16_bit_compare_output_mode_t comp_match_a; /**< Equivalent to TCCRnA COMnA0 and COMnA1 bits */ timer_16_bit_compare_output_mode_t comp_match_b; /**< Equivalent to TCCRnA COMnB0 and COMnB1 bits */ timer_16_bit_waveform_generation_t waveform_mode; /**< Selects the right waveform mode and dispatch it to the right registers */ - timer_16_bit_prescaler_selection_t prescaler; /**< Selects the right prescaler to be fed in the timer */ + timer_16_bit_prescaler_selection_t prescaler; /**< Selects the right prescaler to be fed in the timer */ uint16_t counter; /**< Gives the starting counter value when configured */ uint16_t ocra_val; /**< Selects the OCRA value to be used for timer events triggering */ uint16_t ocrb_val; /**< Selects the OCRB value to be used for timer events triggering */ diff --git a/Drivers/Timers/Timer_16_bit/src/timer_16_bit.c b/Drivers/Timers/Timer_16_bit/src/timer_16_bit.c index 0261274..c205334 100644 --- a/Drivers/Timers/Timer_16_bit/src/timer_16_bit.c +++ b/Drivers/Timers/Timer_16_bit/src/timer_16_bit.c @@ -4,7 +4,7 @@ @ FreeMyCode version : 1.0 RC alpha Author : bebenlebricolo - License : + License : name : GPLv3 url : https://www.gnu.org/licenses/quick-guide-gplv3.html Date : 12/02/2021 @@ -42,7 +42,6 @@ along with this program. If not, see . static struct { - timer_16_bit_handle_t handle; timer_16_bit_prescaler_selection_t prescaler; bool is_initialised; } internal_config[TIMER_16_BIT_COUNT] = {0}; @@ -80,106 +79,71 @@ uint16_t timer_16_bit_prescaler_to_value(const timer_16_bit_prescaler_selection_ return 0; } -void timer_16_bit_compute_matching_parameters(const uint32_t * const cpu_freq, - const uint32_t * const target_freq, - timer_16_bit_prescaler_selection_t * const prescaler, - uint16_t * const ocra, - uint16_t * const accumulator) +timer_error_t timer_16_bit_compute_matching_parameters(const uint32_t * const clock_freq, + const uint32_t * const target_freq, + const timer_generic_resolution_t resolution, + timer_16_bit_prescaler_selection_t * const prescaler, + uint16_t * const ocr, + uint16_t * const accumulator) { timer_generic_parameters_t parameters = { .input = { - .cpu_frequency = *cpu_freq, + .clock_freq = *clock_freq, .target_frequency = *target_freq, - .resolution = TIMER_GENERIC_RESOLUTION_16_BIT, + .resolution = resolution, .prescaler_lookup_array.array = timer_16_bit_prescaler_table, .prescaler_lookup_array.size = TIMER_16_BIT_MAX_PRESCALER_COUNT, }, }; - timer_generic_compute_parameters(¶meters); - *prescaler = timer_16_bit_prescaler_from_value(¶meters.output.prescaler); - *ocra = parameters.output.ocra; - *accumulator = parameters.output.accumulator; -} -static inline timer_error_t check_handle(timer_16_bit_handle_t * const handle) -{ - bool found_null = false; - if (NULL == handle) + if( TIMER_ERROR_OK != timer_generic_compute_parameters(¶meters)) { - /* Not the use case we really want to check, but this is a case of error anyway - which will generate segfaults errors if we let it propagate further ... */ - found_null = true; + return TIMER_ERROR_CONFIG; } - else - { - found_null |= (NULL == handle->TCCRA); - found_null |= (NULL == handle->TCCRB); - found_null |= (NULL == handle->TCCRC); - - found_null |= (NULL == handle->OCRA_H); - found_null |= (NULL == handle->OCRA_L); - found_null |= (NULL == handle->OCRB_H); - found_null |= (NULL == handle->OCRB_L); - - found_null |= (NULL == handle->TCNT_H); - found_null |= (NULL == handle->TCNT_L); - found_null |= (NULL == handle->ICR_H); - found_null |= (NULL == handle->ICR_L); - - found_null |= (NULL == handle->TIMSK); - found_null |= (NULL == handle->TIFR); - } - if (found_null) - { - return TIMER_ERROR_NULL_HANDLE; - } - return TIMER_ERROR_OK;; -} + *prescaler = timer_16_bit_prescaler_from_value(¶meters.output.prescaler); + *ocr = parameters.output.ocr; + *accumulator = parameters.output.accumulator; -static inline timer_error_t check_id(uint8_t id) -{ - if (id >= TIMER_16_BIT_COUNT) - { - return TIMER_ERROR_UNKNOWN_TIMER; - } return TIMER_ERROR_OK; } -timer_error_t timer_16_bit_set_handle(uint8_t id, timer_16_bit_handle_t * const handle) +timer_error_t timer_16_bit_compute_closest_prescaler(const uint32_t * const clock_freq, + const uint32_t * const target_freq, + const timer_generic_resolution_t resolution, + timer_16_bit_prescaler_selection_t * const prescaler) { - timer_error_t ret = check_id(id); - if (TIMER_ERROR_OK != ret) + timer_generic_parameters_t parameters = { - return ret; - } + .input = + { + .clock_freq = *clock_freq, + .target_frequency = *target_freq, + .resolution = resolution, + .prescaler_lookup_array.array = timer_16_bit_prescaler_table, + .prescaler_lookup_array.size = TIMER_16_BIT_MAX_PRESCALER_COUNT, + }, + }; - if (NULL == handle) + if(TIMER_ERROR_OK != timer_generic_find_closest_prescaler(¶meters)) { - return TIMER_ERROR_NULL_POINTER; + return TIMER_ERROR_CONFIG; } - memcpy(&internal_config[id].handle, handle, sizeof(timer_16_bit_handle_t)); - return ret; + *prescaler = timer_16_bit_prescaler_from_value(¶meters.output.prescaler); + + return TIMER_ERROR_OK; } -timer_error_t timer_16_bit_get_handle(uint8_t id, timer_16_bit_handle_t * const handle) +static inline timer_error_t check_id(uint8_t id) { - timer_error_t ret = check_id(id); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - - if (NULL == handle) + if (id >= TIMER_16_BIT_COUNT) { - return TIMER_ERROR_NULL_POINTER; + return TIMER_ERROR_UNKNOWN_TIMER; } - - memcpy(handle, &internal_config[id].handle, sizeof(timer_16_bit_handle_t)); - return ret; + return TIMER_ERROR_OK; } timer_error_t timer_16_bit_get_default_config(timer_16_bit_config_t * config) @@ -214,26 +178,13 @@ timer_error_t timer_16_bit_get_default_config(timer_16_bit_config_t * config) config->input_capture.edge_select = TIMER16BIT_INPUT_CAPTURE_EDGE_FALLING_EDGE; config->input_capture.use_noise_canceler = false; - /* Architecture and device dependent, must be set at configuration time */ - config->handle.OCRA_H = NULL; - config->handle.OCRA_L = NULL; - config->handle.OCRB_H = NULL; - config->handle.OCRB_L = NULL; - config->handle.TCCRA = NULL; - config->handle.TCCRB = NULL; - config->handle.TCCRC = NULL; - config->handle.TCNT_H = NULL; - config->handle.TCNT_L = NULL; - config->handle.ICR_H = NULL; - config->handle.ICR_L = NULL; - config->handle.TIFR = NULL; - config->handle.TIMSK = NULL; return ret; } timer_error_t timer_16_bit_set_force_compare_config(uint8_t id, timer_16_bit_force_compare_config_t * const force_comp_config) { timer_error_t ret = check_id(id); + timer_16_bit_handle_t * handle = &timer_16_bit_static_handle[id]; if (TIMER_ERROR_OK != ret) { @@ -244,31 +195,24 @@ timer_error_t timer_16_bit_set_force_compare_config(uint8_t id, timer_16_bit_for return TIMER_ERROR_NULL_POINTER; } - /* Not fully configured handle, do not attempt to write to it until configured !*/ - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - /* Handles force output compare A flags */ if (true == force_comp_config->force_comp_match_a) { - *(internal_config[id].handle.TCCRC) |= FOCA_MSK ; + *(handle->TCCRC) |= FOCA_MSK ; } else { - *(internal_config[id].handle.TCCRC) &= ~FOCA_MSK ; + *(handle->TCCRC) &= ~FOCA_MSK ; } /* Handles force output compare A flags */ if (true == force_comp_config->force_comp_match_b) { - *(internal_config[id].handle.TCCRC) |= FOCB_MSK ; + *(handle->TCCRC) |= FOCB_MSK ; } else { - *(internal_config[id].handle.TCCRC) &= ~FOCB_MSK ; + *(handle->TCCRC) &= ~FOCB_MSK ; } return ret; } @@ -276,6 +220,8 @@ timer_error_t timer_16_bit_set_force_compare_config(uint8_t id, timer_16_bit_for timer_error_t timer_16_bit_get_force_compare_config(uint8_t id, timer_16_bit_force_compare_config_t * force_comp_config) { timer_error_t ret = check_id(id); + timer_16_bit_handle_t * handle = &timer_16_bit_static_handle[id]; + if (TIMER_ERROR_OK != ret) { return ret; @@ -284,15 +230,10 @@ timer_error_t timer_16_bit_get_force_compare_config(uint8_t id, timer_16_bit_for { return TIMER_ERROR_NULL_POINTER; } - /* Not fully configured handle, do not attempt to write to it until configured !*/ - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } + /* Handles Force Compare Output A flag */ - if (0U == (*(internal_config[id].handle.TCCRC) & FOCA_MSK)) + if (0U == (*(handle->TCCRC) & FOCA_MSK)) { force_comp_config->force_comp_match_a = false; } @@ -302,7 +243,7 @@ timer_error_t timer_16_bit_get_force_compare_config(uint8_t id, timer_16_bit_for } /* Handles Force Compare Output B flag */ - if (0U == (*(internal_config[id].handle.TCCRC) & FOCB_MSK)) + if (0U == (*(handle->TCCRC) & FOCB_MSK)) { force_comp_config->force_comp_match_b = false; } @@ -316,6 +257,8 @@ timer_error_t timer_16_bit_get_force_compare_config(uint8_t id, timer_16_bit_for timer_error_t timer_16_bit_set_interrupt_config(uint8_t id, timer_16_bit_interrupt_config_t * const it_config) { timer_error_t ret = check_id(id); + timer_16_bit_handle_t * handle = &timer_16_bit_static_handle[id]; + if (TIMER_ERROR_OK != ret) { return ret; @@ -326,49 +269,45 @@ timer_error_t timer_16_bit_set_interrupt_config(uint8_t id, timer_16_bit_interru return TIMER_ERROR_NULL_POINTER; } - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } + /* Input capture interrupt enable bit */ if (true == it_config->it_input_capture) { - *(internal_config[id].handle.TIMSK) |= ICIE_MSK; + *(handle->TIMSK) |= ICIE_MSK; } else { - *(internal_config[id].handle.TIMSK) &= ~ICIE_MSK; + *(handle->TIMSK) &= ~ICIE_MSK; } /* TIMSK register */ if (true == it_config->it_comp_match_a) { - *(internal_config[id].handle.TIMSK) |= OCIEA_MSK; + *(handle->TIMSK) |= OCIEA_MSK; } else { - *(internal_config[id].handle.TIMSK) &= ~OCIEA_MSK; + *(handle->TIMSK) &= ~OCIEA_MSK; } if (true == it_config->it_comp_match_b) { - *(internal_config[id].handle.TIMSK) |= OCIEB_MSK; + *(handle->TIMSK) |= OCIEB_MSK; } else { - *(internal_config[id].handle.TIMSK) &= ~OCIEB_MSK; + *(handle->TIMSK) &= ~OCIEB_MSK; } /* TOIE interrupt flag is the first bit, no need to bitshift it */ if (true == it_config->it_timer_overflow) { - *(internal_config[id].handle.TIMSK) |= TOIE_MSK; + *(handle->TIMSK) |= TOIE_MSK; } else { - *(internal_config[id].handle.TIMSK) &= ~TOIE_MSK; + *(handle->TIMSK) &= ~TOIE_MSK; } return ret; @@ -378,6 +317,8 @@ timer_error_t timer_16_bit_set_interrupt_config(uint8_t id, timer_16_bit_interru timer_error_t timer_16_bit_get_interrupt_config(uint8_t id, timer_16_bit_interrupt_config_t * it_config) { timer_error_t ret = check_id(id); + timer_16_bit_handle_t * handle = &timer_16_bit_static_handle[id]; + if (TIMER_ERROR_OK != ret) { return ret; @@ -388,14 +329,10 @@ timer_error_t timer_16_bit_get_interrupt_config(uint8_t id, timer_16_bit_interru return TIMER_ERROR_NULL_POINTER; } - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } + /* Input capture interrupt enable flag */ - if (0U == (*(internal_config[id].handle.TIMSK) & ICIE_MSK)) + if (0U == (*(handle->TIMSK) & ICIE_MSK)) { it_config->it_input_capture = false; } @@ -405,7 +342,7 @@ timer_error_t timer_16_bit_get_interrupt_config(uint8_t id, timer_16_bit_interru } /* Output Compare Match A Interrupt Flag */ - if (0U == (*(internal_config[id].handle.TIMSK) & OCIEA_MSK)) + if (0U == (*(handle->TIMSK) & OCIEA_MSK)) { it_config->it_comp_match_a = false; } @@ -415,7 +352,7 @@ timer_error_t timer_16_bit_get_interrupt_config(uint8_t id, timer_16_bit_interru } /* Output Compare Match B Interrupt Flag */ - if (0U == (*(internal_config[id].handle.TIMSK) & OCIEB_MSK)) + if (0U == (*(handle->TIMSK) & OCIEB_MSK)) { it_config->it_comp_match_b = false; } @@ -425,7 +362,7 @@ timer_error_t timer_16_bit_get_interrupt_config(uint8_t id, timer_16_bit_interru } /* Timer Overflow Interrupt Flag */ - if (0U == (*(internal_config[id].handle.TIMSK) & TOV_MSK)) + if (0U == (*(handle->TIMSK) & TOV_MSK)) { it_config->it_timer_overflow = false; } @@ -440,6 +377,8 @@ timer_error_t timer_16_bit_get_interrupt_config(uint8_t id, timer_16_bit_interru timer_error_t timer_16_bit_get_interrupt_flags(uint8_t id, timer_16_bit_interrupt_config_t * it_flags) { timer_error_t ret = check_id(id); + timer_16_bit_handle_t * handle = &timer_16_bit_static_handle[id]; + if (TIMER_ERROR_OK != ret) { return ret; @@ -450,14 +389,10 @@ timer_error_t timer_16_bit_get_interrupt_flags(uint8_t id, timer_16_bit_interrup return TIMER_ERROR_NULL_POINTER; } - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } + /* Input capture interrupt flag */ - if (0U == (*(internal_config[id].handle.TIFR) & ICF_MSK)) + if (0U == (*(handle->TIFR) & ICF_MSK)) { it_flags->it_input_capture = false; } @@ -467,7 +402,7 @@ timer_error_t timer_16_bit_get_interrupt_flags(uint8_t id, timer_16_bit_interrup } /* Output Compare Match A Interrupt Flag */ - if (0U == (*(internal_config[id].handle.TIFR) & OCIEA_MSK)) + if (0U == (*(handle->TIFR) & OCIEA_MSK)) { it_flags->it_comp_match_a = false; } @@ -477,7 +412,7 @@ timer_error_t timer_16_bit_get_interrupt_flags(uint8_t id, timer_16_bit_interrup } /* Output Compare Match B Interrupt Flag */ - if (0U == (*(internal_config[id].handle.TIFR) & OCIEB_MSK)) + if (0U == (*(handle->TIFR) & OCIEB_MSK)) { it_flags->it_comp_match_b = false; } @@ -487,7 +422,7 @@ timer_error_t timer_16_bit_get_interrupt_flags(uint8_t id, timer_16_bit_interrup } /* Timer Overflow Interrupt Flag */ - if (0U == (*(internal_config[id].handle.TIFR) & TOV_MSK)) + if (0U == (*(handle->TIFR) & TOV_MSK)) { it_flags->it_timer_overflow = false; } @@ -499,31 +434,38 @@ timer_error_t timer_16_bit_get_interrupt_flags(uint8_t id, timer_16_bit_interrup return ret; } -#endif +void timer_16_bit_clear_init_states(void) +{ + for (uint8_t i = 0 ; i < TIMER_16_BIT_COUNT ; i++) + { + internal_config[i].is_initialised = false; + } +} +#endif timer_error_t timer_16_bit_set_prescaler(uint8_t id, const timer_16_bit_prescaler_selection_t prescaler) { timer_error_t ret = check_id(id); - if (TIMER_ERROR_OK != ret) - { - return ret; - } + timer_16_bit_handle_t * handle = &timer_16_bit_static_handle[id]; - ret = check_handle(&internal_config[id].handle); if (TIMER_ERROR_OK != ret) { return ret; } - *(internal_config[id].handle.TCCRB) = (*(internal_config[id].handle.TCCRB) & ~CS_MSK) | prescaler; + + + *(handle->TCCRB) = (*(handle->TCCRB) & ~CS_MSK) | prescaler; return ret; } timer_error_t timer_16_bit_get_prescaler(uint8_t id, timer_16_bit_prescaler_selection_t * prescaler) { timer_error_t ret = check_id(id); + timer_16_bit_handle_t * handle = &timer_16_bit_static_handle[id]; + if (TIMER_ERROR_OK != ret) { return ret; @@ -534,37 +476,29 @@ timer_error_t timer_16_bit_get_prescaler(uint8_t id, timer_16_bit_prescaler_sele return TIMER_ERROR_NULL_POINTER; } - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - - *prescaler = (*(internal_config[id].handle.TCCRB) & CS_MSK); + *prescaler = (*(handle->TCCRB) & CS_MSK); return ret; } timer_error_t timer_16_bit_set_compare_match_A(uint8_t id, const timer_16_bit_compare_output_mode_t compA) { timer_error_t ret = check_id(id); - if (TIMER_ERROR_OK != ret) - { - return ret; - } + timer_16_bit_handle_t * handle = &timer_16_bit_static_handle[id]; - ret = check_handle(&internal_config[id].handle); if (TIMER_ERROR_OK != ret) { return ret; } - *(internal_config[id].handle.TCCRA) = (*(internal_config[id].handle.TCCRA) & ~COMA_MSK) | (compA << COMA0_BIT); + *(handle->TCCRA) = (*(handle->TCCRA) & ~COMA_MSK) | (compA << COMA0_BIT); return ret; } timer_error_t timer_16_bit_get_compare_match_A(uint8_t id, timer_16_bit_compare_output_mode_t * compA) { timer_error_t ret = check_id(id); + timer_16_bit_handle_t * handle = &timer_16_bit_static_handle[id]; + if (TIMER_ERROR_OK != ret) { return ret; @@ -575,37 +509,29 @@ timer_error_t timer_16_bit_get_compare_match_A(uint8_t id, timer_16_bit_compare_ return TIMER_ERROR_NULL_POINTER; } - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - - *compA = ((*(internal_config[id].handle.TCCRA) & COMA_MSK) >> COMA0_BIT); + *compA = ((*(handle->TCCRA) & COMA_MSK) >> COMA0_BIT); return ret; } timer_error_t timer_16_bit_set_compare_match_B(uint8_t id, timer_16_bit_compare_output_mode_t compB) { timer_error_t ret = check_id(id); - if (TIMER_ERROR_OK != ret) - { - return ret; - } + timer_16_bit_handle_t * handle = &timer_16_bit_static_handle[id]; - ret = check_handle(&internal_config[id].handle); if (TIMER_ERROR_OK != ret) { return ret; } - *(internal_config[id].handle.TCCRA) = (*(internal_config[id].handle.TCCRA) & ~COMB_MSK) | (compB << COMB0_BIT); + *(handle->TCCRA) = (*(handle->TCCRA) & ~COMB_MSK) | (compB << COMB0_BIT); return ret; } timer_error_t timer_16_bit_get_compare_match_B(uint8_t id, timer_16_bit_compare_output_mode_t * compB) { timer_error_t ret = check_id(id); + timer_16_bit_handle_t * handle = &timer_16_bit_static_handle[id]; + if (TIMER_ERROR_OK != ret) { return ret; @@ -616,33 +542,23 @@ timer_error_t timer_16_bit_get_compare_match_B(uint8_t id, timer_16_bit_compare_ return TIMER_ERROR_NULL_POINTER; } - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - - *compB = ((*(internal_config[id].handle.TCCRA) & COMB_MSK) >> COMB0_BIT); + *compB = ((*(handle->TCCRA) & COMB_MSK) >> COMB0_BIT); return ret; } timer_error_t timer_16_bit_set_waveform_generation(uint8_t id, const timer_16_bit_waveform_generation_t waveform) { timer_error_t ret = check_id(id); - if (TIMER_ERROR_OK != ret) - { - return ret; - } + timer_16_bit_handle_t * handle = &timer_16_bit_static_handle[id]; - ret = check_handle(&internal_config[id].handle); if (TIMER_ERROR_OK != ret) { return ret; } - *(internal_config[id].handle.TCCRA) = (*(internal_config[id].handle.TCCRA) & ~(WGM0_MSK | WGM1_MSK)) | (waveform & (WGM0_MSK | WGM1_MSK)); + *(handle->TCCRA) = (*(handle->TCCRA) & ~(WGM0_MSK | WGM1_MSK)) | (waveform & (WGM0_MSK | WGM1_MSK)); /* Select bit index 2 and 3 (0x3 -> 0b11) of waveform mode (matches datasheet bit mapping) and store it to bit index 3 of TCCRB with one more bitshift */ - *(internal_config[id].handle.TCCRB) = (*(internal_config[id].handle.TCCRB) & ~(WGM2_MSK | WGM3_MSK)) | ((waveform & 0x0C) << 1U); + *(handle->TCCRB) = (*(handle->TCCRB) & ~(WGM2_MSK | WGM3_MSK)) | ((waveform & 0x0C) << 1U); return ret; } @@ -650,12 +566,8 @@ timer_error_t timer_16_bit_set_waveform_generation(uint8_t id, const timer_16_bi timer_error_t timer_16_bit_set_input_compare_noise_canceler(uint8_t id, const bool enabled) { timer_error_t ret = check_id(id); - if (TIMER_ERROR_OK != ret) - { - return ret; - } + timer_16_bit_handle_t * handle = &timer_16_bit_static_handle[id]; - ret = check_handle(&internal_config[id].handle); if (TIMER_ERROR_OK != ret) { return ret; @@ -663,11 +575,11 @@ timer_error_t timer_16_bit_set_input_compare_noise_canceler(uint8_t id, const bo if (enabled) { - *(internal_config[id].handle.TCCRB) |= INC_MSK; + *(handle->TCCRB) |= INC_MSK; } else { - *(internal_config[id].handle.TCCRB) &= ~INC_MSK; + *(handle->TCCRB) &= ~INC_MSK; } return ret; @@ -676,6 +588,8 @@ timer_error_t timer_16_bit_set_input_compare_noise_canceler(uint8_t id, const bo timer_error_t timer_16_bit_get_input_compare_noise_canceler(uint8_t id, bool * const enabled) { timer_error_t ret = check_id(id); + timer_16_bit_handle_t * handle = &timer_16_bit_static_handle[id]; + if (TIMER_ERROR_OK != ret) { return ret; @@ -686,37 +600,29 @@ timer_error_t timer_16_bit_get_input_compare_noise_canceler(uint8_t id, bool * c return TIMER_ERROR_NULL_POINTER; } - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - - *enabled = ((*(internal_config[id].handle.TCCRB) & INC_MSK) != 0U); + *enabled = ((*(handle->TCCRB) & INC_MSK) != 0U); return ret; } timer_error_t timer_16_bit_set_input_compare_edge_select(uint8_t id, const timer_16_bit_input_capture_edge_select_flag_t edge) { timer_error_t ret = check_id(id); - if (TIMER_ERROR_OK != ret) - { - return ret; - } + timer_16_bit_handle_t * handle = &timer_16_bit_static_handle[id]; - ret = check_handle(&internal_config[id].handle); if (TIMER_ERROR_OK != ret) { return ret; } - *(internal_config[id].handle.TCCRB) = (*(internal_config[id].handle.TCCRB) & ~ICES_MSK) | (edge << ICES_BIT); + *(handle->TCCRB) = (*(handle->TCCRB) & ~ICES_MSK) | (edge << ICES_BIT); return ret; } timer_error_t timer_16_bit_get_input_compare_edge_select(uint8_t id, timer_16_bit_input_capture_edge_select_flag_t * const edge) { timer_error_t ret = check_id(id); + timer_16_bit_handle_t * handle = &timer_16_bit_static_handle[id]; + if (TIMER_ERROR_OK != ret) { return ret; @@ -727,13 +633,7 @@ timer_error_t timer_16_bit_get_input_compare_edge_select(uint8_t id, timer_16_bi return TIMER_ERROR_NULL_POINTER; } - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - - if (0 != (*(internal_config[id].handle.TCCRB) & ICES_MSK)) + if (0 != (*(handle->TCCRB) & ICES_MSK)) { *edge = TIMER16BIT_INPUT_CAPTURE_EDGE_RISING_EDGE; } @@ -747,6 +647,8 @@ timer_error_t timer_16_bit_get_input_compare_edge_select(uint8_t id, timer_16_bi timer_error_t timer_16_bit_get_input_capture_value(uint8_t id, uint16_t * ticks) { timer_error_t ret = check_id(id); + timer_16_bit_handle_t * handle = &timer_16_bit_static_handle[id]; + if (TIMER_ERROR_OK != ret) { return ret; @@ -757,14 +659,8 @@ timer_error_t timer_16_bit_get_input_capture_value(uint8_t id, uint16_t * ticks) return TIMER_ERROR_NULL_POINTER; } - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - - *ticks = *(internal_config[id].handle.TCNT_L); - *ticks |= *(internal_config[id].handle.TCNT_H) << 8U; + *ticks = *(handle->TCNT_L); + *ticks |= *(handle->TCNT_H) << 8U; return ret; } @@ -772,6 +668,8 @@ timer_error_t timer_16_bit_get_input_capture_value(uint8_t id, uint16_t * ticks) timer_error_t timer_16_bit_get_waveform_generation(uint8_t id, timer_16_bit_waveform_generation_t * waveform) { timer_error_t ret = check_id(id); + timer_16_bit_handle_t * handle = &timer_16_bit_static_handle[id]; + if (TIMER_ERROR_OK != ret) { return ret; @@ -782,35 +680,25 @@ timer_error_t timer_16_bit_get_waveform_generation(uint8_t id, timer_16_bit_wave return TIMER_ERROR_NULL_POINTER; } - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - *waveform = (timer_16_bit_waveform_generation_t)(0U); - *waveform |= (*(internal_config[id].handle.TCCRA) & (WGM0_MSK | WGM1_MSK)); - *waveform |= (*(internal_config[id].handle.TCCRB) & (WGM2_MSK | WGM3_MSK)) >> 1U; + *waveform |= (*(handle->TCCRA) & (WGM0_MSK | WGM1_MSK)); + *waveform |= (*(handle->TCCRB) & (WGM2_MSK | WGM3_MSK)) >> 1U; return ret; } timer_error_t timer_16_bit_set_counter_value(uint8_t id, const uint16_t * const ticks) { timer_error_t ret = check_id(id); - if (TIMER_ERROR_OK != ret) - { - return ret; - } + timer_16_bit_handle_t * handle = &timer_16_bit_static_handle[id]; - ret = check_handle(&internal_config[id].handle); if (TIMER_ERROR_OK != ret) { return ret; } /* Write new value to internal timer/counter register */ - *(internal_config[id].handle.TCNT_L) = *ticks & 0xFF; - *(internal_config[id].handle.TCNT_H) = (*ticks & 0xFF00) >> 8U; + *(handle->TCNT_L) = *ticks & 0xFF; + *(handle->TCNT_H) = (*ticks & 0xFF00) >> 8U; return ret; } @@ -818,6 +706,8 @@ timer_error_t timer_16_bit_set_counter_value(uint8_t id, const uint16_t * const timer_error_t timer_16_bit_get_counter_value(uint8_t id, uint16_t * const ticks) { timer_error_t ret = check_id(id); + timer_16_bit_handle_t * handle = &timer_16_bit_static_handle[id]; + if (TIMER_ERROR_OK != ret) { return ret; @@ -828,40 +718,32 @@ timer_error_t timer_16_bit_get_counter_value(uint8_t id, uint16_t * const ticks) return TIMER_ERROR_NULL_POINTER; } - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - /* Transfer data from internal device's timer/count main register */ - *ticks = (*internal_config[id].handle.TCNT_L); - *ticks |= (*internal_config[id].handle.TCNT_H) << 8U; + *ticks = (*handle->TCNT_L); + *ticks |= (*handle->TCNT_H) << 8U; return ret; } timer_error_t timer_16_bit_set_ocra_register_value(uint8_t id, const uint16_t * const ocra) { timer_error_t ret = check_id(id); - if (TIMER_ERROR_OK != ret) - { - return ret; - } + timer_16_bit_handle_t * handle = &timer_16_bit_static_handle[id]; - ret = check_handle(&internal_config[id].handle); if (TIMER_ERROR_OK != ret) { return ret; } - *(internal_config[id].handle.OCRA_H) = (*ocra & 0xFF00) >> 8U; - *(internal_config[id].handle.OCRA_L) = (*ocra & 0xFF); + *(handle->OCRA_H) = (*ocra & 0xFF00) >> 8U; + *(handle->OCRA_L) = (*ocra & 0xFF); return ret; } timer_error_t timer_16_bit_get_ocra_register_value(uint8_t id, uint16_t * const ocra) { timer_error_t ret = check_id(id); + timer_16_bit_handle_t * handle = &timer_16_bit_static_handle[id]; + if (TIMER_ERROR_OK != ret) { return ret; @@ -872,64 +754,85 @@ timer_error_t timer_16_bit_get_ocra_register_value(uint8_t id, uint16_t * const return TIMER_ERROR_NULL_POINTER; } - ret = check_handle(&internal_config[id].handle); + *ocra = (*handle->OCRA_L); + *ocra |= (*handle->OCRA_H << 8U); + return ret; +} + +timer_error_t timer_16_bit_set_ocrb_register_value(uint8_t id, const uint16_t * const ocrb) +{ + timer_error_t ret = check_id(id); + timer_16_bit_handle_t * handle = &timer_16_bit_static_handle[id]; + if (TIMER_ERROR_OK != ret) { return ret; } - *ocra = (*internal_config[id].handle.OCRA_L); - *ocra |= (*internal_config[id].handle.OCRA_H << 8U); + *handle->OCRB_H = (*ocrb & 0xFF00) >> 8U; + *handle->OCRB_L = (*ocrb & 0xFF);; return ret; } -timer_error_t timer_16_bit_set_ocrb_register_value(uint8_t id, const uint16_t * const ocrb) +timer_error_t timer_16_bit_get_ocrb_register_value(uint8_t id, uint16_t * const ocrb) { timer_error_t ret = check_id(id); + timer_16_bit_handle_t * handle = &timer_16_bit_static_handle[id]; + if (TIMER_ERROR_OK != ret) { return ret; } - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) + if ( NULL == ocrb) { - return ret; + return TIMER_ERROR_NULL_POINTER; } - *internal_config[id].handle.OCRB_H = (*ocrb & 0xFF00) >> 8U; - *internal_config[id].handle.OCRB_L = (*ocrb & 0xFF);; + *ocrb = (*handle->OCRB_L); + *ocrb |= (*handle->OCRB_H << 8U); return ret; } -timer_error_t timer_16_bit_get_ocrb_register_value(uint8_t id, uint16_t * const ocrb) +timer_error_t timer_16_bit_set_icr_register_value(uint8_t id, const uint16_t icr) { timer_error_t ret = check_id(id); + timer_16_bit_handle_t * handle = &timer_16_bit_static_handle[id]; + if (TIMER_ERROR_OK != ret) { return ret; } - if ( NULL == ocrb) - { - return TIMER_ERROR_NULL_POINTER; - } + *handle->ICR_H = (icr & 0xFF00) >> 8U; + *handle->ICR_L = (icr & 0xFF);; + return ret; +} + +timer_error_t timer_16_bit_get_icr_register_value(uint8_t id, uint16_t * const icr) +{ + timer_error_t ret = check_id(id); + timer_16_bit_handle_t * handle = &timer_16_bit_static_handle[id]; - ret = check_handle(&internal_config[id].handle); if (TIMER_ERROR_OK != ret) { return ret; } - *ocrb = (*internal_config[id].handle.OCRB_L); - *ocrb |= (*internal_config[id].handle.OCRB_H << 8U); + if ( NULL == icr) + { + return TIMER_ERROR_NULL_POINTER; + } + + *icr = (*handle->ICR_L); + *icr |= (*handle->ICR_H << 8U); return ret; } static timer_error_t timer_16_bit_write_config(uint8_t id, timer_16_bit_config_t * const config) { timer_error_t ret = TIMER_ERROR_OK; - timer_16_bit_handle_t * handle = &internal_config[id].handle; + timer_16_bit_handle_t * handle = &timer_16_bit_static_handle[id]; internal_config[id].prescaler = config->timing_config.prescaler; @@ -941,7 +844,7 @@ static timer_error_t timer_16_bit_write_config(uint8_t id, timer_16_bit_config_t *(handle->TCNT_L) = (config->timing_config.counter & 0xFF); /* Clear TCCRA register first, otherwise we can't reconfigure the OCRA/OCRB regs!*/ - *(handle->TCCRA) = 0; + *(handle->TCCRA) = 0; /* TCCRA register */ *(handle->OCRA_H) = (config->timing_config.ocra_val & 0xFF00) >> 8U; @@ -1046,12 +949,6 @@ timer_error_t timer_16_bit_deinit(uint8_t id) return ret; } - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - /* Retrieve a config object to write back default configuration into timer registers */ timer_16_bit_config_t config; ret = timer_16_bit_stop(id); @@ -1083,28 +980,16 @@ timer_error_t timer_16_bit_reconfigure(uint8_t id, timer_16_bit_config_t * const return TIMER_ERROR_NULL_POINTER; } - ret = check_handle(&config->handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - - ret = timer_16_bit_set_handle(id, &config->handle); - if (TIMER_ERROR_OK != ret) + /* Stop the timer before reconfiguring it */ + if (true == internal_config[id].is_initialised) { - return ret; + ret = timer_16_bit_stop(id); + if (TIMER_ERROR_OK != ret) + { + return ret; + } } - /* Stop the timer before reconfiguring it */ - if (true == internal_config[id].is_initialised) - { - ret = timer_16_bit_stop(id); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - } - ret = timer_16_bit_write_config(id,config); if (TIMER_ERROR_OK == ret) { @@ -1118,13 +1003,9 @@ timer_error_t timer_16_bit_reconfigure(uint8_t id, timer_16_bit_config_t * const timer_error_t timer_16_bit_start(uint8_t id) { timer_error_t ret = check_id(id); - if(TIMER_ERROR_OK != ret) - { - return ret; - } + timer_16_bit_handle_t * handle = &timer_16_bit_static_handle[id]; - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) + if(TIMER_ERROR_OK != ret) { return ret; } @@ -1135,13 +1016,15 @@ timer_error_t timer_16_bit_start(uint8_t id) } /* This time, set the prescaler to start the timer, unless prescaler is set to NO_CLOCK source */ - *(internal_config[id].handle.TCCRB) = (*(internal_config[id].handle.TCCRB) & ~CS_MSK) | internal_config[id].prescaler; + *(handle->TCCRB) = (*(handle->TCCRB) & ~CS_MSK) | internal_config[id].prescaler; return ret; } timer_error_t timer_16_bit_stop(uint8_t id) { timer_error_t ret = check_id(id); + timer_16_bit_handle_t * handle = &timer_16_bit_static_handle[id]; + if(TIMER_ERROR_OK != ret) { return ret; @@ -1153,7 +1036,7 @@ timer_error_t timer_16_bit_stop(uint8_t id) } /* Reset prescaler to NO_CLOCK*/ - *(internal_config[id].handle.TCCRB) = (*(internal_config[id].handle.TCCRB) & ~CS_MSK) | TIMER16BIT_CLK_NO_CLOCK; + *(handle->TCCRB) = (*(handle->TCCRB) & ~CS_MSK) | TIMER16BIT_CLK_NO_CLOCK; return ret; } diff --git a/Drivers/Timers/Timer_8_bit/CMakeLists.txt b/Drivers/Timers/Timer_8_bit/CMakeLists.txt index d6c6d91..e3c5075 100644 --- a/Drivers/Timers/Timer_8_bit/CMakeLists.txt +++ b/Drivers/Timers/Timer_8_bit/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0) +cmake_minimum_required(VERSION 3.20) add_library(timer_8_bit_driver STATIC ${CMAKE_CURRENT_SOURCE_DIR}/src/timer_8_bit.c diff --git a/Drivers/Timers/Timer_8_bit/Tests/CMakeLists.txt b/Drivers/Timers/Timer_8_bit/Tests/CMakeLists.txt index bae153e..d0a1f8d 100644 --- a/Drivers/Timers/Timer_8_bit/Tests/CMakeLists.txt +++ b/Drivers/Timers/Timer_8_bit/Tests/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0) +cmake_minimum_required(VERSION 3.20) project(timer_8_bit_driver_test) enable_testing() @@ -8,25 +8,28 @@ enable_testing() ### timer_8_bit_driver library ### add_library(timer_8_bit_driver STATIC -../src/timer_8_bit.c + ../src/timer_8_bit.c ) + target_include_directories(timer_8_bit_driver PRIVATE -${CMAKE_CURRENT_SOURCE_DIR}/../inc -${CMAKE_CURRENT_SOURCE_DIR}/../../Timer_generic/inc -${CMAKE_CURRENT_SOURCE_DIR}/ + ${CMAKE_CURRENT_SOURCE_DIR}/../inc + ${CMAKE_CURRENT_SOURCE_DIR}/../../Timer_generic/inc + ${CMAKE_CURRENT_SOURCE_DIR}/ ) ########## Timer 8 bit driver tests ########## add_executable(timer_8_bit_driver_tests -timer_8_bit_tests.cpp -Stub/timer_8_bit_registers_stub.c + timer_8_bit_tests.cpp + Stub/timer_8_bit_registers_stub.c + config.c ) target_include_directories(timer_8_bit_driver_tests PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/Stub ${CMAKE_CURRENT_SOURCE_DIR}/../inc ${CMAKE_CURRENT_SOURCE_DIR}/../../Timer_generic/inc + ${CMAKE_CURRENT_SOURCE_DIR}/ ) target_include_directories(timer_8_bit_driver_tests SYSTEM PUBLIC diff --git a/Drivers/Timers/Timer_8_bit/Tests/Stub/timer_8_bit_registers_stub.c b/Drivers/Timers/Timer_8_bit/Tests/Stub/timer_8_bit_registers_stub.c index 3818bde..0e95de0 100644 --- a/Drivers/Timers/Timer_8_bit/Tests/Stub/timer_8_bit_registers_stub.c +++ b/Drivers/Timers/Timer_8_bit/Tests/Stub/timer_8_bit_registers_stub.c @@ -4,7 +4,7 @@ @ FreeMyCode version : 1.0 RC alpha Author : bebenlebricolo - License : + License : name : GPLv3 url : https://www.gnu.org/licenses/quick-guide-gplv3.html Date : 12/02/2021 @@ -38,18 +38,4 @@ void timer_8_bit_registers_stub_erase(void) memset(&timer_8_bit_registers_stub, 0, sizeof(timer_8_bit_registers_stub_t)); } -void timer_8_bit_registers_stub_init_handle(timer_8_bit_handle_t * handle) -{ - if (NULL != handle) - { - handle->OCRA = &timer_8_bit_registers_stub.OCRA; - handle->OCRB = &timer_8_bit_registers_stub.OCRB; - handle->TCCRA = &timer_8_bit_registers_stub.TCCRA; - handle->TCCRB = &timer_8_bit_registers_stub.TCCRB; - handle->TCNT = &timer_8_bit_registers_stub.TCNT; - handle->TIMSK = &timer_8_bit_registers_stub.TIMSK; - handle->TIFR = &timer_8_bit_registers_stub.TIFR; - } -} - diff --git a/Drivers/Timers/Timer_8_bit/Tests/Stub/timer_8_bit_registers_stub.h b/Drivers/Timers/Timer_8_bit/Tests/Stub/timer_8_bit_registers_stub.h index 23cddc2..3eb21a7 100644 --- a/Drivers/Timers/Timer_8_bit/Tests/Stub/timer_8_bit_registers_stub.h +++ b/Drivers/Timers/Timer_8_bit/Tests/Stub/timer_8_bit_registers_stub.h @@ -4,7 +4,7 @@ @ FreeMyCode version : 1.0 RC alpha Author : bebenlebricolo - License : + License : name : GPLv3 url : https://www.gnu.org/licenses/quick-guide-gplv3.html Date : 12/02/2021 @@ -55,7 +55,6 @@ typedef struct extern timer_8_bit_registers_stub_t timer_8_bit_registers_stub; void timer_8_bit_registers_stub_erase(void); -void timer_8_bit_registers_stub_init_handle(timer_8_bit_handle_t * handle); #ifdef __cplusplus } diff --git a/Drivers/Timers/Timer_8_bit/Tests/config.c b/Drivers/Timers/Timer_8_bit/Tests/config.c new file mode 100644 index 0000000..acd6221 --- /dev/null +++ b/Drivers/Timers/Timer_8_bit/Tests/config.c @@ -0,0 +1,17 @@ +#include "config.h" +#include "timer_8_bit.h" +#include "timer_8_bit_registers_stub.h" + + +timer_8_bit_handle_t timer_8_bit_static_handle[TIMER_8_BIT_COUNT] = +{ + { + .OCRA = &timer_8_bit_registers_stub.OCRA, + .OCRB = &timer_8_bit_registers_stub.OCRB, + .TCCRA = &timer_8_bit_registers_stub.TCCRA, + .TCCRB = &timer_8_bit_registers_stub.TCCRB, + .TCNT = &timer_8_bit_registers_stub.TCNT, + .TIFR = &timer_8_bit_registers_stub.TIFR, + .TIMSK = &timer_8_bit_registers_stub.TIMSK + } +}; \ No newline at end of file diff --git a/Drivers/Timers/Timer_8_bit/Tests/timer_8_bit_tests.cpp b/Drivers/Timers/Timer_8_bit/Tests/timer_8_bit_tests.cpp index 608b997..f1b06e0 100644 --- a/Drivers/Timers/Timer_8_bit/Tests/timer_8_bit_tests.cpp +++ b/Drivers/Timers/Timer_8_bit/Tests/timer_8_bit_tests.cpp @@ -4,7 +4,7 @@ @ FreeMyCode version : 1.0 RC alpha Author : bebenlebricolo - License : + License : name : GPLv3 url : https://www.gnu.org/licenses/quick-guide-gplv3.html Date : 12/02/2021 @@ -28,7 +28,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -#include "gtest/gtest.h" +#include #include "config.h" #include "timer_8_bit.h" #include "timer_8_bit_registers_stub.h" @@ -45,8 +45,7 @@ class Timer8BitFixture : public ::testing::Test { timer_8_bit_registers_stub_erase(); (void) timer_8_bit_get_default_config(&config); - timer_8_bit_registers_stub_init_handle(&config.handle); - (void) timer_8_bit_set_handle(DT_ID, &config.handle); + timer_8_bit_clear_init_states(); } void TearDown() override { @@ -59,77 +58,64 @@ TEST(timer_8_bit_driver_tests, guard_null_handle) timer_error_t ret = timer_8_bit_get_default_config(&config); ASSERT_EQ(TIMER_ERROR_OK, ret); - /* We should have a null handle at the moment */ - ASSERT_TRUE(NULL == (void*) (config.handle.OCRA) ); - ASSERT_TRUE(NULL == (void*) (config.handle.OCRB) ); - ASSERT_TRUE(NULL == (void*) (config.handle.TCCRA) ); - ASSERT_TRUE(NULL == (void*) (config.handle.TCCRB) ); - ASSERT_TRUE(NULL == (void*) (config.handle.TCNT) ); - ASSERT_TRUE(NULL == (void*) (config.handle.TIFR) ); - ASSERT_TRUE(NULL == (void*) (config.handle.TIMSK) ); - ret = timer_8_bit_reconfigure(DT_ID, &config); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); ret = timer_8_bit_init(DT_ID, &config); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); /* Test compare match mode A get/set api */ - ret = timer_8_bit_set_compare_match_A(DT_ID, config.timing_config.comp_match_a); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); - ret = timer_8_bit_get_compare_match_A(DT_ID, &config.timing_config.comp_match_a); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ret = timer_8_bit_set_compare_match_A(DT_ID, config.timing_config.comp_mode_a); + ASSERT_EQ(TIMER_ERROR_OK, ret); + ret = timer_8_bit_get_compare_match_A(DT_ID, &config.timing_config.comp_mode_a); + ASSERT_EQ(TIMER_ERROR_OK, ret); /* Test compare match mode B get/set api */ - ret = timer_8_bit_set_compare_match_B(DT_ID, config.timing_config.comp_match_b); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); - ret = timer_8_bit_get_compare_match_B(DT_ID, &config.timing_config.comp_match_b); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); - - /* Test handle setting function */ - ret = timer_8_bit_set_handle(DT_ID, &config.handle); + ret = timer_8_bit_set_compare_match_B(DT_ID, config.timing_config.comp_mode_b); + ASSERT_EQ(TIMER_ERROR_OK, ret); + ret = timer_8_bit_get_compare_match_B(DT_ID, &config.timing_config.comp_mode_b); ASSERT_EQ(TIMER_ERROR_OK, ret); /* Test interrupt config get/set api */ ret = timer_8_bit_set_interrupt_config(DT_ID, &config.interrupt_config); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); ret = timer_8_bit_get_interrupt_config(DT_ID, &config.interrupt_config); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); /* Test OCRA get/set api */ ret = timer_8_bit_set_ocra_register_value(DT_ID, config.timing_config.ocra_val); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); ret = timer_8_bit_get_ocra_register_value(DT_ID, &config.timing_config.ocra_val); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); /* Test OCRB get/set api */ ret = timer_8_bit_set_ocrb_register_value(DT_ID, config.timing_config.ocrb_val); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); ret = timer_8_bit_get_ocrb_register_value(DT_ID, &config.timing_config.ocrb_val); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); /* Test counter get/set api */ ret = timer_8_bit_set_counter_value(DT_ID, config.timing_config.counter); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); ret = timer_8_bit_get_counter_value(DT_ID, &config.timing_config.counter); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); /* Test force compare flags get/set api */ ret = timer_8_bit_set_force_compare_config(DT_ID, &config.force_compare); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); ret = timer_8_bit_get_force_compare_config(DT_ID, &config.force_compare); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); /* Test prescaler get/set api */ ret = timer_8_bit_set_prescaler(DT_ID, config.timing_config.prescaler); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); ret = timer_8_bit_get_prescaler(DT_ID, &config.timing_config.prescaler); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); /* Test waveforme generation get/set api */ ret = timer_8_bit_set_waveform_generation(DT_ID, config.timing_config.waveform_mode); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); ret = timer_8_bit_get_waveform_generation(DT_ID, &config.timing_config.waveform_mode); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); } TEST(timer_8_bit_driver_tests, guard_null_pointer) @@ -146,11 +132,6 @@ TEST(timer_8_bit_driver_tests, guard_null_pointer) ret = timer_8_bit_get_compare_match_B(DT_ID, nullptr_compare_output_mode); ASSERT_EQ(TIMER_ERROR_NULL_POINTER, ret); - timer_8_bit_handle_t * nullptr_handle = NULL; - /* Test handle setting function */ - ret = timer_8_bit_set_handle(DT_ID, nullptr_handle); - ASSERT_EQ(TIMER_ERROR_NULL_POINTER, ret); - /* Test interrupt config get/set api */ timer_8_bit_interrupt_config_t * nullptr_interrupt_config = NULL; ret = timer_8_bit_set_interrupt_config(DT_ID, nullptr_interrupt_config); @@ -187,9 +168,6 @@ TEST(timer_8_bit_driver_tests, guard_null_pointer) ret = timer_8_bit_reconfigure(DT_ID, nullptr_config); ASSERT_EQ(TIMER_ERROR_NULL_POINTER, ret); - - ret = timer_8_bit_get_handle(DT_ID, nullptr_handle); - ASSERT_EQ(TIMER_ERROR_NULL_POINTER, ret); } TEST(timer_8_bit_driver_tests, guard_wrong_id) @@ -208,15 +186,9 @@ TEST(timer_8_bit_driver_tests, guard_wrong_id) ASSERT_EQ(TIMER_ERROR_UNKNOWN_TIMER, ret); /* Test compare match mode A get/set api */ - ret = timer_8_bit_get_compare_match_A(targeted_id, &config.timing_config.comp_match_a); + ret = timer_8_bit_get_compare_match_A(targeted_id, &config.timing_config.comp_mode_a); ASSERT_EQ(TIMER_ERROR_UNKNOWN_TIMER, ret); - ret = timer_8_bit_get_compare_match_B(targeted_id, &config.timing_config.comp_match_b); - ASSERT_EQ(TIMER_ERROR_UNKNOWN_TIMER, ret); - - /* Test handle setting function */ - ret = timer_8_bit_set_handle(targeted_id, &config.handle); - ASSERT_EQ(TIMER_ERROR_UNKNOWN_TIMER, ret); - ret = timer_8_bit_get_handle(targeted_id, &config.handle); + ret = timer_8_bit_get_compare_match_B(targeted_id, &config.timing_config.comp_mode_b); ASSERT_EQ(TIMER_ERROR_UNKNOWN_TIMER, ret); /* Test interrupt config get/set api */ @@ -262,9 +234,9 @@ TEST_F(Timer8BitFixture, test_handle_is_set_correctly) // Data should be reset to its default state when we begin // Testing TCCRA register - config.timing_config.comp_match_a = TIMER8BIT_CMOD_SET_OCnX; // 11 + config.timing_config.comp_mode_a = TIMER8BIT_CMOD_SET_OCnX; // 11 ASSERT_EQ(timer_8_bit_registers_stub.TCCRA & COMA_MSK, 0U); - ret = timer_8_bit_set_compare_match_A(DT_ID, config.timing_config.comp_match_a); + ret = timer_8_bit_set_compare_match_A(DT_ID, config.timing_config.comp_mode_a); ASSERT_EQ(TIMER_ERROR_OK, ret); ASSERT_EQ(timer_8_bit_registers_stub.TCCRA & COMA_MSK, 0x3 << COMA_BIT); @@ -333,16 +305,16 @@ TEST_F(Timer8BitFixture, test_timing_configuration_unitary_functions) uint8_t received_counter_val; timer_8_bit_prescaler_selection_t received_prescaler; timer_8_bit_waveform_generation_t received_waveform; - memcpy(&received_compare_mode_a, &config.timing_config.comp_match_a, sizeof(timer_8_bit_compare_output_mode_t)); - memcpy(&received_compare_mode_b, &config.timing_config.comp_match_b, sizeof(timer_8_bit_compare_output_mode_t)); + memcpy(&received_compare_mode_a, &config.timing_config.comp_mode_a, sizeof(timer_8_bit_compare_output_mode_t)); + memcpy(&received_compare_mode_b, &config.timing_config.comp_mode_b, sizeof(timer_8_bit_compare_output_mode_t)); memcpy(&received_ocra_val, &config.timing_config.ocra_val, sizeof(uint8_t)); memcpy(&received_ocrb_val, &config.timing_config.ocrb_val, sizeof(uint8_t)); memcpy(&received_counter_val, &config.timing_config.counter, sizeof(uint8_t)); memcpy(&received_prescaler, &config.timing_config.prescaler, sizeof(timer_8_bit_prescaler_selection_t)); memcpy(&received_waveform, &config.timing_config.waveform_mode, sizeof(timer_8_bit_waveform_generation_t)); - config.timing_config.comp_match_a = TIMER8BIT_CMOD_TOGGLE_OCnX; - config.timing_config.comp_match_b = TIMER8BIT_CMOD_SET_OCnX; + config.timing_config.comp_mode_a = TIMER8BIT_CMOD_TOGGLE_OCnX; + config.timing_config.comp_mode_b = TIMER8BIT_CMOD_SET_OCnX; config.timing_config.ocra_val = 33U; config.timing_config.ocrb_val = 156U; config.timing_config.prescaler = TIMER8BIT_CLK_PRESCALER_64; @@ -350,23 +322,23 @@ TEST_F(Timer8BitFixture, test_timing_configuration_unitary_functions) // Compare match A mode ASSERT_EQ(timer_8_bit_registers_stub.TCCRA & COMA_MSK, 0U); - ret = timer_8_bit_set_compare_match_A(DT_ID, config.timing_config.comp_match_a); + ret = timer_8_bit_set_compare_match_A(DT_ID, config.timing_config.comp_mode_a); ASSERT_EQ(TIMER_ERROR_OK, ret); - ASSERT_EQ(timer_8_bit_registers_stub.TCCRA & COMA_MSK, config.timing_config.comp_match_a << COMA_BIT); + ASSERT_EQ(timer_8_bit_registers_stub.TCCRA & COMA_MSK, config.timing_config.comp_mode_a << COMA_BIT); // Counter check value can also be read back from registers ... ret = timer_8_bit_get_compare_match_A(DT_ID,&received_compare_mode_a); ASSERT_EQ(TIMER_ERROR_OK, ret); - ASSERT_EQ(0, memcmp(&received_compare_mode_a, &config.timing_config.comp_match_a, sizeof(timer_8_bit_compare_output_mode_t))); + ASSERT_EQ(0, memcmp(&received_compare_mode_a, &config.timing_config.comp_mode_a, sizeof(timer_8_bit_compare_output_mode_t))); // Compare match B mode ASSERT_EQ(timer_8_bit_registers_stub.TCCRA & COMB_MSK, 0U); - ret = timer_8_bit_set_compare_match_B(DT_ID, config.timing_config.comp_match_b); + ret = timer_8_bit_set_compare_match_B(DT_ID, config.timing_config.comp_mode_b); ASSERT_EQ(TIMER_ERROR_OK, ret); - ASSERT_EQ(timer_8_bit_registers_stub.TCCRA & COMB_MSK, config.timing_config.comp_match_b << COMB_BIT); + ASSERT_EQ(timer_8_bit_registers_stub.TCCRA & COMB_MSK, config.timing_config.comp_mode_b << COMB_BIT); // Counter check value can also be read back from registers ... ret = timer_8_bit_get_compare_match_B(DT_ID,&received_compare_mode_b); ASSERT_EQ(TIMER_ERROR_OK, ret); - ASSERT_EQ(0, memcmp(&received_compare_mode_b, &config.timing_config.comp_match_b, sizeof(timer_8_bit_compare_output_mode_t))); + ASSERT_EQ(0, memcmp(&received_compare_mode_b, &config.timing_config.comp_mode_b, sizeof(timer_8_bit_compare_output_mode_t))); // Waveform modes ASSERT_EQ(timer_8_bit_registers_stub.TCCRA & (WGM0_MSK | WGM1_MSK), 0U); @@ -506,51 +478,91 @@ TEST_F(Timer8BitFixture, test_initialisation_deinitialisation) TEST(timer_8_bit_driver_tests, test_parameters_computation_prescaler) { - uint32_t cpu_freq = 16'000'000; + uint32_t clock_freq = 16'000'000; uint32_t target_freq = 1'000; uint8_t ocra = 0; uint16_t accumulator = 0; timer_8_bit_prescaler_selection_t prescaler = TIMER8BIT_CLK_PRESCALER_1; - timer_8_bit_compute_matching_parameters(&cpu_freq, &target_freq, &prescaler, &ocra, &accumulator); + timer_8_bit_compute_matching_parameters(&clock_freq, &target_freq, &prescaler, &ocra, &accumulator); ASSERT_EQ(prescaler, TIMER8BIT_CLK_PRESCALER_64); ASSERT_EQ(ocra, 249U); ASSERT_EQ(accumulator, 0U); target_freq = 3'000; - timer_8_bit_compute_matching_parameters(&cpu_freq, &target_freq, &prescaler, &ocra, &accumulator); + timer_8_bit_compute_matching_parameters(&clock_freq, &target_freq, &prescaler, &ocra, &accumulator); ASSERT_EQ(prescaler, TIMER8BIT_CLK_PRESCALER_64); ASSERT_EQ(ocra, 82U); ASSERT_EQ(accumulator, 0U); target_freq = 5'000; - timer_8_bit_compute_matching_parameters(&cpu_freq, &target_freq, &prescaler, &ocra, &accumulator); + timer_8_bit_compute_matching_parameters(&clock_freq, &target_freq, &prescaler, &ocra, &accumulator); ASSERT_EQ(prescaler, TIMER8BIT_CLK_PRESCALER_64); ASSERT_EQ(ocra, 49U); ASSERT_EQ(accumulator, 0U); target_freq = 1'000'000; - timer_8_bit_compute_matching_parameters(&cpu_freq, &target_freq, &prescaler, &ocra, &accumulator); + timer_8_bit_compute_matching_parameters(&clock_freq, &target_freq, &prescaler, &ocra, &accumulator); ASSERT_EQ(prescaler, TIMER8BIT_CLK_PRESCALER_1); ASSERT_EQ(ocra, 15U); ASSERT_EQ(accumulator, 0U); - cpu_freq = 8'000'000; + clock_freq = 8'000'000; target_freq = 440; - timer_8_bit_compute_matching_parameters(&cpu_freq, &target_freq, &prescaler, &ocra, &accumulator); + timer_8_bit_compute_matching_parameters(&clock_freq, &target_freq, &prescaler, &ocra, &accumulator); ASSERT_EQ(prescaler, TIMER8BIT_CLK_PRESCALER_256); ASSERT_EQ(ocra, 70U); ASSERT_EQ(accumulator, 0U); - cpu_freq = 16'000'000; + clock_freq = 16'000'000; target_freq = 1; - timer_8_bit_compute_matching_parameters(&cpu_freq, &target_freq, &prescaler, &ocra, &accumulator); + timer_8_bit_compute_matching_parameters(&clock_freq, &target_freq, &prescaler, &ocra, &accumulator); ASSERT_EQ(prescaler, TIMER8BIT_CLK_PRESCALER_1024); ASSERT_EQ(ocra, 124U); ASSERT_EQ(accumulator, 124U); } +TEST(timer_8_bit_driver_tests, test_find_closest_prescaler_function) +{ + timer_error_t err = TIMER_ERROR_OK; + uint32_t clock_freq = 16'000'000; + uint32_t target_freq = 1'000'000; + + timer_8_bit_prescaler_selection_t prescaler = TIMER8BIT_CLK_PRESCALER_1; + err = timer_8_bit_compute_closest_prescaler(&clock_freq, &target_freq, &prescaler); + ASSERT_EQ(err, TIMER_ERROR_OK); + ASSERT_EQ(prescaler, TIMER8BIT_CLK_PRESCALER_1); + + target_freq = 1'000; + err = timer_8_bit_compute_closest_prescaler(&clock_freq, &target_freq, &prescaler); + ASSERT_EQ(err, TIMER_ERROR_OK); + ASSERT_EQ(prescaler, TIMER8BIT_CLK_PRESCALER_64); + + + target_freq = 3'000; + err = timer_8_bit_compute_closest_prescaler(&clock_freq, &target_freq, &prescaler); + ASSERT_EQ(err, TIMER_ERROR_OK); + ASSERT_EQ(prescaler, TIMER8BIT_CLK_PRESCALER_64); + + target_freq = 5'000; + err = timer_8_bit_compute_closest_prescaler(&clock_freq, &target_freq, &prescaler); + ASSERT_EQ(err, TIMER_ERROR_OK); + ASSERT_EQ(prescaler, TIMER8BIT_CLK_PRESCALER_64); + + clock_freq = 8'000'000; + target_freq = 440; + err = timer_8_bit_compute_closest_prescaler(&clock_freq, &target_freq, &prescaler); + ASSERT_EQ(err, TIMER_ERROR_OK); + ASSERT_EQ(prescaler, TIMER8BIT_CLK_PRESCALER_256); + + clock_freq = 16'000'000; + target_freq = 1; + err = timer_8_bit_compute_closest_prescaler(&clock_freq, &target_freq, &prescaler); + ASSERT_EQ(err, TIMER_ERROR_OK); + ASSERT_EQ(prescaler, TIMER8BIT_CLK_PRESCALER_1024); +} + int main(int argc, char **argv) { diff --git a/Drivers/Timers/Timer_8_bit/inc/timer_8_bit.h b/Drivers/Timers/Timer_8_bit/inc/timer_8_bit.h index e55f94b..acc5b39 100644 --- a/Drivers/Timers/Timer_8_bit/inc/timer_8_bit.h +++ b/Drivers/Timers/Timer_8_bit/inc/timer_8_bit.h @@ -4,7 +4,7 @@ @ FreeMyCode version : 1.0 RC alpha Author : bebenlebricolo - License : + License : name : GPLv3 url : https://www.gnu.org/licenses/quick-guide-gplv3.html Date : 12/02/2021 @@ -34,6 +34,7 @@ along with this program. If not, see . #include #include "timer_generic.h" #include "timer_8_bit_reg.h" +#include "config.h" #ifdef __cplusplus extern "C" @@ -72,7 +73,6 @@ typedef struct timer_8_bit_timing_config_t timing_config; /**< Handles basic timing configuration for 8 bit timers */ timer_8_bit_interrupt_config_t interrupt_config; /**< Handles interrupt configuraitons for 8 bit timers */ timer_8_bit_force_compare_config_t force_compare; /**< Handles force compare flags on output A and B, generic configuration among timers */ - timer_8_bit_handle_t handle; /**< Stores pointer locations to peripheral registers */ } timer_8_bit_config_t; @@ -92,30 +92,6 @@ typedef struct */ timer_error_t timer_8_bit_get_default_config(timer_8_bit_config_t * config); -/** - * @brief sets the handle of timer_8_bit driver - * @param[in] id : targeted timer id (used to fetch internal configuration based on ids) - * @param[in] handle : handle to be copied into internal configuration - * @return - * TIMER_ERROR_OK : operation succeeded - * TIMER_ERROR_UNKNOWN_TIMER : given id is out of range - * TIMER_ERROR_NULL_POINTER : given force_comp_config parameter points to NULL -*/ -timer_error_t timer_8_bit_set_handle(uint8_t id, timer_8_bit_handle_t * const handle); - -/** - * @brief fetches the handle of timer_8_bit driver - * @param[in] id : targeted timer id (used to fetch internal configuration based on ids) - * @param[out] handle : output handle extracted from internal driver memory - * @return - * TIMER_ERROR_OK : operation succeeded - * TIMER_ERROR_UNKNOWN_TIMER : given id is out of range - * TIMER_ERROR_NULL_POINTER : given force_comp_config parameter points to NULL -*/ -timer_error_t timer_8_bit_get_handle(uint8_t id, timer_8_bit_handle_t * const handle); - - - /* ################################ Force compare flags configuration ############################### */ /** * @brief sets the given force compare configuration object to targeted timer @@ -179,6 +155,11 @@ timer_error_t timer_8_bit_get_interrupt_config(uint8_t id, timer_8_bit_interrupt * TIMER_ERROR_NULL_POINTER : given it_config parameter points to NULL */ timer_error_t timer_8_bit_get_interrupt_flags(uint8_t id, timer_8_bit_interrupt_config_t * it_flags); + +/** + * @brief Resets internal configuration of timer 8 bit driver + */ +void timer_8_bit_clear_init_states(void); #endif @@ -412,23 +393,65 @@ timer_error_t timer_8_bit_start(uint8_t id); */ timer_error_t timer_8_bit_stop(uint8_t id); -#define TIMER_8_BIT_MAX_PRESCALER_COUNT (5U) + /** - * @brief Timer 8 bit prescaler table, ascending order. Used to compute the closest prescaler - * which can be used to generate any given frequency -*/ -extern const timer_generic_prescaler_pair_t timer_8_bit_prescaler_table[TIMER_8_BIT_MAX_PRESCALER_COUNT]; + * @brief Computes timing parameters such as prescaler, ocr value and accumulator in order to satisfy the requested target frequency, + * using CPU main clock frequency as the time constraint. + * + * @param[in] clock_freq : current CPU main clock frequency + * @param[in] target_freq : desired output frequency of the timer (assuming ocr is the top value) + * @param[out] prescaler : output prescaler parameter + * @param[out] ocr : output ocr value + * @param[out] accumulator : output accumulator value ; used by the timebase module for instance to count events and extend timer's counter capabilities + * @return + * TIMER_ERROR_OK : operation succeeded + * TIMER_ERROR_CONFIG : Something was wrong in the input configuration (probably unachievable frequency parameters) + */ +timer_error_t timer_8_bit_compute_matching_parameters(const uint32_t * const clock_freq, + const uint32_t * const target_freq, + timer_8_bit_prescaler_selection_t * const prescaler, + uint8_t * const ocr, + uint16_t * const accumulator); + +/** + * @brief Computes timing parameters such as prescaler, ocr value and accumulator in order to satisfy the requested target frequency, + * using CPU main clock frequency as the time constraint. + * + * @param[in] clock_freq : current CPU main clock frequency + * @param[in] target_freq : desired output frequency of the timer (assuming ocr is the top value) + * @param[out] prescaler : output prescaler parameter + * @return + * TIMER_ERROR_OK : operation succeeded + * TIMER_ERROR_CONFIG : Something was wrong in the input configuration (probably unachievable frequency parameters) + */ +timer_error_t timer_8_bit_compute_closest_prescaler(const uint32_t * const clock_freq, + const uint32_t * const target_freq, + timer_8_bit_prescaler_selection_t * const prescaler); -void timer_8_bit_compute_matching_parameters(const uint32_t * const cpu_freq, - const uint32_t * const target_freq, - timer_8_bit_prescaler_selection_t * const prescaler, - uint8_t * const ocra, - uint16_t * const accumulator); +#define TIMER_8_BIT_MAX_PRESCALER_COUNT 5U +/** + * @brief Converts a single prescaler enum to its value-based counterpart. + * Behaves as a lookup table + * @param prescaler : input prescaler + * @return uint16_t : returns the translated value (from 1 to 1024) + */ uint16_t timer_8_bit_prescaler_to_value(const timer_8_bit_prescaler_selection_t prescaler); + +/** + * @brief Translates back a plain prescaler value to its enum counterpart. + * If no direct equivalent is found, returns the TIMER8BIT_CLK_NO_CLOCK enum value + * @param input_prescaler : input prescaler parameter + * @return enum value + */ timer_8_bit_prescaler_selection_t timer_8_bit_prescaler_from_value(uint16_t const * const input_prescaler); +/** + * @brief this handle has to be declared somewhere and initialised appropriately + */ +extern timer_8_bit_handle_t timer_8_bit_static_handle[TIMER_8_BIT_COUNT]; + #ifdef __cplusplus } #endif diff --git a/Drivers/Timers/Timer_8_bit/inc/timer_8_bit_reg.h b/Drivers/Timers/Timer_8_bit/inc/timer_8_bit_reg.h index e3265a4..2d8cf50 100644 --- a/Drivers/Timers/Timer_8_bit/inc/timer_8_bit_reg.h +++ b/Drivers/Timers/Timer_8_bit/inc/timer_8_bit_reg.h @@ -4,7 +4,7 @@ @ FreeMyCode version : 1.0 RC alpha Author : bebenlebricolo - License : + License : name : GPLv3 url : https://www.gnu.org/licenses/quick-guide-gplv3.html Date : 12/02/2021 @@ -149,8 +149,8 @@ typedef struct uint8_t counter; /**< Main Counter value used to start the timer */ uint8_t ocra_val; /**< Value to be set inside OCRA register to control PWM for instance */ uint8_t ocrb_val; /**< Value to be set inside OCRB register to control PWM for instance */ - timer_8_bit_compare_output_mode_t comp_match_a; /**< Equivalent to TCCRnA COMnA0 and COMnA1 bits */ - timer_8_bit_compare_output_mode_t comp_match_b; /**< Equivalent to TCCRnA COMnB0 and COMnB1 bits */ + timer_8_bit_compare_output_mode_t comp_mode_a; /**< Describes the kind of compare match behavior ; equivalent to TCCRnA COMnA0 and COMnA1 bits */ + timer_8_bit_compare_output_mode_t comp_mode_b; /**< Describes the kind of compare match behavior ; equivalent to TCCRnA COMnB0 and COMnB1 bits */ timer_8_bit_waveform_generation_t waveform_mode; /**< Selects the right waveform mode and dispatch it to the right registers */ timer_8_bit_prescaler_selection_t prescaler; /**< Selects the right prescaler to be fed in the timer */ } timer_8_bit_timing_config_t; diff --git a/Drivers/Timers/Timer_8_bit/src/timer_8_bit.c b/Drivers/Timers/Timer_8_bit/src/timer_8_bit.c index 85018e3..15514a4 100644 --- a/Drivers/Timers/Timer_8_bit/src/timer_8_bit.c +++ b/Drivers/Timers/Timer_8_bit/src/timer_8_bit.c @@ -4,7 +4,7 @@ @ FreeMyCode version : 1.0 RC alpha Author : bebenlebricolo - License : + License : name : GPLv3 url : https://www.gnu.org/licenses/quick-guide-gplv3.html Date : 12/02/2021 @@ -49,7 +49,6 @@ along with this program. If not, see . static struct { - timer_8_bit_handle_t handle; timer_8_bit_prescaler_selection_t prescaler; bool is_initialised; } internal_config[TIMER_8_BIT_COUNT] = {0}; @@ -87,55 +86,56 @@ uint16_t timer_8_bit_prescaler_to_value(const timer_8_bit_prescaler_selection_t return 0; } -void timer_8_bit_compute_matching_parameters(const uint32_t * const cpu_freq, +timer_error_t timer_8_bit_compute_matching_parameters(const uint32_t * const clock_freq, const uint32_t * const target_freq, timer_8_bit_prescaler_selection_t * const prescaler, - uint8_t * const ocra, + uint8_t * const ocr, uint16_t * const accumulator) { timer_generic_parameters_t parameters = { .input = { - .cpu_frequency = *cpu_freq, + .clock_freq = *clock_freq, .target_frequency = *target_freq, .resolution = TIMER_GENERIC_RESOLUTION_8_BIT, .prescaler_lookup_array.array = timer_8_bit_prescaler_table, .prescaler_lookup_array.size = TIMER_8_BIT_MAX_PRESCALER_COUNT, }, }; - timer_generic_compute_parameters(¶meters); + if(TIMER_ERROR_OK != timer_generic_compute_parameters(¶meters)) + { + return TIMER_ERROR_CONFIG; + } + *prescaler = timer_8_bit_prescaler_from_value(¶meters.output.prescaler); - *ocra = (uint8_t) parameters.output.ocra; + *ocr = (uint8_t) parameters.output.ocr; *accumulator = parameters.output.accumulator; + return TIMER_ERROR_OK; } - - -static inline timer_error_t check_handle(timer_8_bit_handle_t * const handle) +timer_error_t timer_8_bit_compute_closest_prescaler(const uint32_t * const clock_freq, + const uint32_t * const target_freq, + timer_8_bit_prescaler_selection_t * const prescaler) { - bool found_null = false; - if (NULL == handle) - { - /* Not the use case we really want to check, but this is a case of error anyway - which will generate segfaults errors if we let it propagate further ... */ - found_null = true; - } - else + timer_generic_parameters_t parameters = { - found_null |= (NULL == handle->OCRA); - found_null |= (NULL == handle->OCRB); - found_null |= (NULL == handle->TCCRA); - found_null |= (NULL == handle->TCCRB); - found_null |= (NULL == handle->TCNT); - found_null |= (NULL == handle->TIFR); - found_null |= (NULL == handle->TIMSK); - } - if (found_null) + .input = + { + .clock_freq = *clock_freq, + .target_frequency = *target_freq, + .resolution = TIMER_GENERIC_RESOLUTION_8_BIT, + .prescaler_lookup_array.array = timer_8_bit_prescaler_table, + .prescaler_lookup_array.size = TIMER_8_BIT_MAX_PRESCALER_COUNT, + }, + }; + if(TIMER_ERROR_OK != timer_generic_find_closest_prescaler(¶meters)) { - return TIMER_ERROR_NULL_HANDLE; + return TIMER_ERROR_CONFIG; } - return TIMER_ERROR_OK;; + *prescaler = timer_8_bit_prescaler_from_value(¶meters.output.prescaler); + + return TIMER_ERROR_OK; } static inline timer_error_t check_id(uint8_t id) @@ -147,41 +147,6 @@ static inline timer_error_t check_id(uint8_t id) return TIMER_ERROR_OK; } -timer_error_t timer_8_bit_set_handle(uint8_t id, timer_8_bit_handle_t * const handle) -{ - timer_error_t ret = check_id(id); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - - if (NULL == handle) - { - return TIMER_ERROR_NULL_POINTER; - } - - memcpy(&internal_config[id].handle, handle, sizeof(timer_8_bit_handle_t)); - return ret; -} - -timer_error_t timer_8_bit_get_handle(uint8_t id, timer_8_bit_handle_t * const handle) -{ - timer_error_t ret = check_id(id); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - - if (NULL == handle) - { - return TIMER_ERROR_NULL_POINTER; - } - - memcpy(handle, &internal_config[id].handle, sizeof(timer_8_bit_handle_t)); - return ret; -} - - timer_error_t timer_8_bit_get_default_config(timer_8_bit_config_t * config) { timer_error_t ret = TIMER_ERROR_OK; @@ -205,26 +170,19 @@ timer_error_t timer_8_bit_get_default_config(timer_8_bit_config_t * config) config->timing_config.ocrb_val = 0U; config->timing_config.prescaler = TIMER8BIT_CLK_NO_CLOCK; config->timing_config.waveform_mode = TIMER8BIT_WG_NORMAL; - config->timing_config.comp_match_a = TIMER8BIT_CMOD_NORMAL; - config->timing_config.comp_match_b = TIMER8BIT_CMOD_NORMAL; + config->timing_config.comp_mode_a = TIMER8BIT_CMOD_NORMAL; + config->timing_config.comp_mode_b = TIMER8BIT_CMOD_NORMAL; config->force_compare.force_comp_match_a = false; config->force_compare.force_comp_match_b = false; - /* Architecture and device dependent, must be set at configuration time */ - config->handle.OCRA = NULL; - config->handle.OCRB = NULL; - config->handle.TCCRA = NULL; - config->handle.TCCRB = NULL; - config->handle.TCNT = NULL; - config->handle.TIFR = NULL; - config->handle.TIMSK = NULL; return ret; } timer_error_t timer_8_bit_set_force_compare_config(uint8_t id, timer_8_bit_force_compare_config_t * const force_comp_config) { timer_error_t ret = check_id(id); + timer_8_bit_handle_t * handle = &timer_8_bit_static_handle[id]; if (TIMER_ERROR_OK != ret) { @@ -235,31 +193,24 @@ timer_error_t timer_8_bit_set_force_compare_config(uint8_t id, timer_8_bit_force return TIMER_ERROR_NULL_POINTER; } - /* Not fully configured handle, do not attempt to write to it until configured !*/ - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - /* Handles force output compare A flags */ if (true == force_comp_config->force_comp_match_a) { - *(internal_config[id].handle.TCCRB) |= (1U << FOCA_BIT) ; + *(handle->TCCRB) |= (1U << FOCA_BIT) ; } else { - *(internal_config[id].handle.TCCRB) &= ~(1U << FOCA_BIT) ; + *(handle->TCCRB) &= ~(1U << FOCA_BIT) ; } /* Handles force output compare A flags */ if (true == force_comp_config->force_comp_match_b) { - *(internal_config[id].handle.TCCRB) |= (1U << FOCB_BIT) ; + *(handle->TCCRB) |= (1U << FOCB_BIT) ; } else { - *(internal_config[id].handle.TCCRB) &= ~(1U << FOCB_BIT) ; + *(handle->TCCRB) &= ~(1U << FOCB_BIT) ; } return ret; } @@ -267,6 +218,8 @@ timer_error_t timer_8_bit_set_force_compare_config(uint8_t id, timer_8_bit_force timer_error_t timer_8_bit_get_force_compare_config(uint8_t id, timer_8_bit_force_compare_config_t * force_comp_config) { timer_error_t ret = check_id(id); + timer_8_bit_handle_t * handle = &timer_8_bit_static_handle[id]; + if (TIMER_ERROR_OK != ret) { return ret; @@ -275,15 +228,9 @@ timer_error_t timer_8_bit_get_force_compare_config(uint8_t id, timer_8_bit_force { return TIMER_ERROR_NULL_POINTER; } - /* Not fully configured handle, do not attempt to write to it until configured !*/ - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } /* Handles Force Compare Output A flag */ - if (0U == (*(internal_config[id].handle.TCCRB) & FOCA_MSK)) + if (0U == (*(handle->TCCRB) & FOCA_MSK)) { force_comp_config->force_comp_match_a = false; } @@ -293,7 +240,7 @@ timer_error_t timer_8_bit_get_force_compare_config(uint8_t id, timer_8_bit_force } /* Handles Force Compare Output B flag */ - if (0U == (*(internal_config[id].handle.TCCRB) & FOCB_MSK)) + if (0U == (*(handle->TCCRB) & FOCB_MSK)) { force_comp_config->force_comp_match_b = false; } @@ -307,6 +254,8 @@ timer_error_t timer_8_bit_get_force_compare_config(uint8_t id, timer_8_bit_force timer_error_t timer_8_bit_set_interrupt_config(uint8_t id, timer_8_bit_interrupt_config_t * const it_config) { timer_error_t ret = check_id(id); + timer_8_bit_handle_t * handle = &timer_8_bit_static_handle[id]; + if (TIMER_ERROR_OK != ret) { return ret; @@ -316,39 +265,34 @@ timer_error_t timer_8_bit_set_interrupt_config(uint8_t id, timer_8_bit_interrupt { return TIMER_ERROR_NULL_POINTER; } - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } /* TIMSK register */ if (true == it_config->it_comp_match_a) { - *(internal_config[id].handle.TIMSK) |= 1U << OCIEA_BIT; + *(handle->TIMSK) |= 1U << OCIEA_BIT; } else { - *(internal_config[id].handle.TIMSK) &= ~(1U << OCIEA_BIT); + *(handle->TIMSK) &= ~(1U << OCIEA_BIT); } if (true == it_config->it_comp_match_b) { - *(internal_config[id].handle.TIMSK) |= 1U << OCIEB_BIT; + *(handle->TIMSK) |= 1U << OCIEB_BIT; } else { - *(internal_config[id].handle.TIMSK) &= ~(1U << OCIEB_BIT); + *(handle->TIMSK) &= ~(1U << OCIEB_BIT); } /* TOIE interrupt flag is the first bit, no need to bitshift it */ if (true == it_config->it_timer_overflow) { - *(internal_config[id].handle.TIMSK) |= 1U; + *(handle->TIMSK) |= 1U; } else { - *(internal_config[id].handle.TIMSK) &= ~1U; + *(handle->TIMSK) &= ~1U; } return ret; } @@ -357,6 +301,8 @@ timer_error_t timer_8_bit_set_interrupt_config(uint8_t id, timer_8_bit_interrupt timer_error_t timer_8_bit_get_interrupt_config(uint8_t id, timer_8_bit_interrupt_config_t * it_config) { timer_error_t ret = check_id(id); + timer_8_bit_handle_t * handle = &timer_8_bit_static_handle[id]; + if (TIMER_ERROR_OK != ret) { return ret; @@ -367,14 +313,10 @@ timer_error_t timer_8_bit_get_interrupt_config(uint8_t id, timer_8_bit_interrupt return TIMER_ERROR_NULL_POINTER; } - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } + /* Output Compare Match A Interrupt Flag */ - if (0U == (*(internal_config[id].handle.TIMSK) & OCIEA_MSK)) + if (0U == (*(handle->TIMSK) & OCIEA_MSK)) { it_config->it_comp_match_a = false; } @@ -384,7 +326,7 @@ timer_error_t timer_8_bit_get_interrupt_config(uint8_t id, timer_8_bit_interrupt } /* Output Compare Match B Interrupt Flag */ - if (0U == (*(internal_config[id].handle.TIMSK) & OCIEB_MSK)) + if (0U == (*(handle->TIMSK) & OCIEB_MSK)) { it_config->it_comp_match_b = false; } @@ -394,7 +336,7 @@ timer_error_t timer_8_bit_get_interrupt_config(uint8_t id, timer_8_bit_interrupt } /* Timer Overflow Interrupt Flag */ - if (0U == (*(internal_config[id].handle.TIMSK) & 1U)) + if (0U == (*(handle->TIMSK) & 1U)) { it_config->it_timer_overflow = false; } @@ -409,6 +351,8 @@ timer_error_t timer_8_bit_get_interrupt_config(uint8_t id, timer_8_bit_interrupt timer_error_t timer_8_bit_get_interrupt_flags(uint8_t id, timer_8_bit_interrupt_config_t * it_flags) { timer_error_t ret = check_id(id); + timer_8_bit_handle_t * handle = &timer_8_bit_static_handle[id]; + if (TIMER_ERROR_OK != ret) { return ret; @@ -419,15 +363,8 @@ timer_error_t timer_8_bit_get_interrupt_flags(uint8_t id, timer_8_bit_interrupt_ return TIMER_ERROR_NULL_POINTER; } - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - - /* Output Compare Match A Interrupt Flag */ - if (0U == (*(internal_config[id].handle.TIFR) & OCIEA_MSK)) + if (0U == (*(handle->TIFR) & OCIEA_MSK)) { it_flags->it_comp_match_a = false; } @@ -437,7 +374,7 @@ timer_error_t timer_8_bit_get_interrupt_flags(uint8_t id, timer_8_bit_interrupt_ } /* Output Compare Match B Interrupt Flag */ - if (0U == (*(internal_config[id].handle.TIFR) & OCIEB_MSK)) + if (0U == (*(handle->TIFR) & OCIEB_MSK)) { it_flags->it_comp_match_b = false; } @@ -447,7 +384,7 @@ timer_error_t timer_8_bit_get_interrupt_flags(uint8_t id, timer_8_bit_interrupt_ } /* Timer Overflow Interrupt Flag */ - if (0U == (*(internal_config[id].handle.TIFR) & 1U)) + if (0U == (*(handle->TIFR) & 1U)) { it_flags->it_timer_overflow = false; } @@ -457,7 +394,14 @@ timer_error_t timer_8_bit_get_interrupt_flags(uint8_t id, timer_8_bit_interrupt_ } return ret; +} +void timer_8_bit_clear_init_states(void) +{ + for (uint8_t i = 0 ; i < TIMER_8_BIT_COUNT ; i++) + { + internal_config[i].is_initialised = false; + } } #endif @@ -466,24 +410,22 @@ timer_error_t timer_8_bit_get_interrupt_flags(uint8_t id, timer_8_bit_interrupt_ timer_error_t timer_8_bit_set_prescaler(uint8_t id, const timer_8_bit_prescaler_selection_t prescaler) { timer_error_t ret = check_id(id); - if (TIMER_ERROR_OK != ret) - { - return ret; - } + timer_8_bit_handle_t * handle = &timer_8_bit_static_handle[id]; - ret = check_handle(&internal_config[id].handle); if (TIMER_ERROR_OK != ret) { return ret; } - *(internal_config[id].handle.TCCRB) = (*(internal_config[id].handle.TCCRB) & ~CS_MSK) | prescaler; + *(handle->TCCRB) = (*(handle->TCCRB) & ~CS_MSK) | prescaler; return ret; } timer_error_t timer_8_bit_get_prescaler(uint8_t id, timer_8_bit_prescaler_selection_t * prescaler) { timer_error_t ret = check_id(id); + timer_8_bit_handle_t * handle = &timer_8_bit_static_handle[id]; + if (TIMER_ERROR_OK != ret) { return ret; @@ -494,37 +436,29 @@ timer_error_t timer_8_bit_get_prescaler(uint8_t id, timer_8_bit_prescaler_select return TIMER_ERROR_NULL_POINTER; } - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - - *prescaler = (*(internal_config[id].handle.TCCRB) & CS_MSK); + *prescaler = (*(handle->TCCRB) & CS_MSK); return ret; } timer_error_t timer_8_bit_set_compare_match_A(uint8_t id, const timer_8_bit_compare_output_mode_t compA) { timer_error_t ret = check_id(id); - if (TIMER_ERROR_OK != ret) - { - return ret; - } + timer_8_bit_handle_t * handle = &timer_8_bit_static_handle[id]; - ret = check_handle(&internal_config[id].handle); if (TIMER_ERROR_OK != ret) { return ret; } - *(internal_config[id].handle.TCCRA) = (*(internal_config[id].handle.TCCRA) & ~COMA_MSK) | (compA << COMA_BIT); + *(handle->TCCRA) = (*(handle->TCCRA) & ~COMA_MSK) | (compA << COMA_BIT); return ret; } timer_error_t timer_8_bit_get_compare_match_A(uint8_t id, timer_8_bit_compare_output_mode_t * compA) { timer_error_t ret = check_id(id); + timer_8_bit_handle_t * handle = &timer_8_bit_static_handle[id]; + if (TIMER_ERROR_OK != ret) { return ret; @@ -535,37 +469,29 @@ timer_error_t timer_8_bit_get_compare_match_A(uint8_t id, timer_8_bit_compare_ou return TIMER_ERROR_NULL_POINTER; } - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - - *compA = ((*(internal_config[id].handle.TCCRA) & COMA_MSK) >> COMA_BIT); + *compA = ((*(handle->TCCRA) & COMA_MSK) >> COMA_BIT); return ret; } timer_error_t timer_8_bit_set_compare_match_B(uint8_t id, timer_8_bit_compare_output_mode_t compB) { timer_error_t ret = check_id(id); - if (TIMER_ERROR_OK != ret) - { - return ret; - } + timer_8_bit_handle_t * handle = &timer_8_bit_static_handle[id]; - ret = check_handle(&internal_config[id].handle); if (TIMER_ERROR_OK != ret) { return ret; } - *(internal_config[id].handle.TCCRA) = (*(internal_config[id].handle.TCCRA) & ~COMB_MSK) | (compB << COMB_BIT); + *(handle->TCCRA) = (*(handle->TCCRA) & ~COMB_MSK) | (compB << COMB_BIT); return ret; } timer_error_t timer_8_bit_get_compare_match_B(uint8_t id, timer_8_bit_compare_output_mode_t * compB) { timer_error_t ret = check_id(id); + timer_8_bit_handle_t * handle = &timer_8_bit_static_handle[id]; + if (TIMER_ERROR_OK != ret) { return ret; @@ -576,39 +502,31 @@ timer_error_t timer_8_bit_get_compare_match_B(uint8_t id, timer_8_bit_compare_ou return TIMER_ERROR_NULL_POINTER; } - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - - *compB = ((*(internal_config[id].handle.TCCRA) & COMB_MSK) >> COMB_BIT); + *compB = ((*(handle->TCCRA) & COMB_MSK) >> COMB_BIT); return ret; } timer_error_t timer_8_bit_set_waveform_generation(uint8_t id, const timer_8_bit_waveform_generation_t waveform) { timer_error_t ret = check_id(id); - if (TIMER_ERROR_OK != ret) - { - return ret; - } + timer_8_bit_handle_t * handle = &timer_8_bit_static_handle[id]; - ret = check_handle(&internal_config[id].handle); if (TIMER_ERROR_OK != ret) { return ret; } - *(internal_config[id].handle.TCCRA) = (*(internal_config[id].handle.TCCRA) & ~(WGM0_MSK | WGM1_MSK)) | (waveform & (WGM0_MSK | WGM1_MSK)); + *(handle->TCCRA) = (*(handle->TCCRA) & ~(WGM0_MSK | WGM1_MSK)) | (waveform & (WGM0_MSK | WGM1_MSK)); /* Select bit index 2 of waveform mode (matches datasheet bit mapping) and store it to bit index 3 of TCCRB with one more bitshift */ - *(internal_config[id].handle.TCCRB) = (*(internal_config[id].handle.TCCRB) & ~WGM2_MSK) | (waveform & (1U << 2U) << 1U); + *(handle->TCCRB) = (*(handle->TCCRB) & ~WGM2_MSK) | (waveform & (1U << 2U) << 1U); return ret; } timer_error_t timer_8_bit_get_waveform_generation(uint8_t id, timer_8_bit_waveform_generation_t * waveform) { timer_error_t ret = check_id(id); + timer_8_bit_handle_t * handle = &timer_8_bit_static_handle[id]; + if (TIMER_ERROR_OK != ret) { return ret; @@ -619,40 +537,32 @@ timer_error_t timer_8_bit_get_waveform_generation(uint8_t id, timer_8_bit_wavefo return TIMER_ERROR_NULL_POINTER; } - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - *waveform = (timer_8_bit_waveform_generation_t)(0U); - *waveform |= (*(internal_config[id].handle.TCCRA) & (WGM0_MSK | WGM1_MSK)); - *waveform |= (*(internal_config[id].handle.TCCRB) & (WGM2_MSK)) >> 1U; + *waveform |= (*(handle->TCCRA) & (WGM0_MSK | WGM1_MSK)); + *waveform |= (*(handle->TCCRB) & (WGM2_MSK)) >> 1U; return ret; } timer_error_t timer_8_bit_set_counter_value(uint8_t id, const uint8_t ticks) { timer_error_t ret = check_id(id); - if (TIMER_ERROR_OK != ret) - { - return ret; - } + timer_8_bit_handle_t * handle = &timer_8_bit_static_handle[id]; - ret = check_handle(&internal_config[id].handle); if (TIMER_ERROR_OK != ret) { return ret; } /* Write new value to internal timer/counter register */ - *(internal_config[id].handle.TCNT) = ticks; + *(handle->TCNT) = ticks; return ret; } timer_error_t timer_8_bit_get_counter_value(uint8_t id, uint8_t * ticks) { timer_error_t ret = check_id(id); + timer_8_bit_handle_t * handle = &timer_8_bit_static_handle[id]; + if (TIMER_ERROR_OK != ret) { return ret; @@ -663,38 +573,30 @@ timer_error_t timer_8_bit_get_counter_value(uint8_t id, uint8_t * ticks) return TIMER_ERROR_NULL_POINTER; } - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - /* Transfer data from internal device's timer/count main register */ - *ticks = *internal_config[id].handle.TCNT; + *ticks = *handle->TCNT; return ret; } timer_error_t timer_8_bit_set_ocra_register_value(uint8_t id, uint8_t ocra) { timer_error_t ret = check_id(id); - if (TIMER_ERROR_OK != ret) - { - return ret; - } + timer_8_bit_handle_t * handle = &timer_8_bit_static_handle[id]; - ret = check_handle(&internal_config[id].handle); if (TIMER_ERROR_OK != ret) { return ret; } - *internal_config[id].handle.OCRA = ocra; + *handle->OCRA = ocra; return ret; } timer_error_t timer_8_bit_get_ocra_register_value(uint8_t id, uint8_t * ocra) { timer_error_t ret = check_id(id); + timer_8_bit_handle_t * handle = &timer_8_bit_static_handle[id]; + if (TIMER_ERROR_OK != ret) { return ret; @@ -705,37 +607,30 @@ timer_error_t timer_8_bit_get_ocra_register_value(uint8_t id, uint8_t * ocra) return TIMER_ERROR_NULL_POINTER; } - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - *ocra = *internal_config[id].handle.OCRA; + *ocra = *handle->OCRA; return ret; } timer_error_t timer_8_bit_set_ocrb_register_value(uint8_t id, uint8_t ocrb) { timer_error_t ret = check_id(id); - if (TIMER_ERROR_OK != ret) - { - return ret; - } + timer_8_bit_handle_t * handle = &timer_8_bit_static_handle[id]; - ret = check_handle(&internal_config[id].handle); if (TIMER_ERROR_OK != ret) { return ret; } - *internal_config[id].handle.OCRB = ocrb; + *handle->OCRB = ocrb; return ret; } timer_error_t timer_8_bit_get_ocrb_register_value(uint8_t id, uint8_t * ocrb) { timer_error_t ret = check_id(id); + timer_8_bit_handle_t * handle = &timer_8_bit_static_handle[id]; + if (TIMER_ERROR_OK != ret) { return ret; @@ -746,20 +641,14 @@ timer_error_t timer_8_bit_get_ocrb_register_value(uint8_t id, uint8_t * ocrb) return TIMER_ERROR_NULL_POINTER; } - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - - *ocrb = *internal_config[id].handle.OCRB; + *ocrb = *handle->OCRB; return ret; } static timer_error_t timer_8_bit_write_config(uint8_t id, timer_8_bit_config_t * const config) { timer_error_t ret = TIMER_ERROR_OK; - timer_8_bit_handle_t * handle = &internal_config[id].handle; + const timer_8_bit_handle_t * handle = &timer_8_bit_static_handle[id]; internal_config[id].prescaler = config->timing_config.prescaler; /* Initialise counter as well */ @@ -774,8 +663,8 @@ static timer_error_t timer_8_bit_write_config(uint8_t id, timer_8_bit_config_t * /* TCCRA register */ *(handle->OCRA) = config->timing_config.ocra_val; *(handle->OCRB) = config->timing_config.ocrb_val; - *(handle->TCCRA) = (*(handle->TCCRA) & ~COMA_MSK) | (config->timing_config.comp_match_a << COMA_BIT); - *(handle->TCCRA) = (*(handle->TCCRA) & ~COMB_MSK) | (config->timing_config.comp_match_b << COMB_BIT); + *(handle->TCCRA) = (*(handle->TCCRA) & ~COMA_MSK) | (config->timing_config.comp_mode_a << COMA_BIT); + *(handle->TCCRA) = (*(handle->TCCRA) & ~COMB_MSK) | (config->timing_config.comp_mode_b << COMB_BIT); *(handle->TCCRA) = (*(handle->TCCRA) & ~(WGM0_MSK | WGM1_MSK)) | (config->timing_config.waveform_mode & (WGM0_MSK | WGM1_MSK)); /* TCCRB register */ @@ -866,18 +755,6 @@ timer_error_t timer_8_bit_reconfigure(uint8_t id, timer_8_bit_config_t * const c return TIMER_ERROR_NULL_POINTER; } - ret = check_handle(&config->handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - - ret = timer_8_bit_set_handle(id, &config->handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - /* Stop the timer before reconfiguring it */ if (true == internal_config[id].is_initialised) { @@ -905,12 +782,6 @@ timer_error_t timer_8_bit_deinit(uint8_t id) return ret; } - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - /* Retrieve a config object to write back default configuration into timer registers */ timer_8_bit_config_t config; ret = timer_8_bit_stop(id); @@ -933,13 +804,9 @@ timer_error_t timer_8_bit_deinit(uint8_t id) timer_error_t timer_8_bit_start(uint8_t id) { timer_error_t ret = check_id(id); - if(TIMER_ERROR_OK != ret) - { - return ret; - } + timer_8_bit_handle_t * handle = &timer_8_bit_static_handle[id]; - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) + if(TIMER_ERROR_OK != ret) { return ret; } @@ -950,13 +817,15 @@ timer_error_t timer_8_bit_start(uint8_t id) } /* This time, set the prescaler to start the timer, unless prescaler is set to NO_CLOCK source */ - *(internal_config[id].handle.TCCRB) = (*(internal_config[id].handle.TCCRB) & ~CS_MSK) | internal_config[id].prescaler; + *(handle->TCCRB) = (*(handle->TCCRB) & ~CS_MSK) | internal_config[id].prescaler; return ret; } timer_error_t timer_8_bit_stop(uint8_t id) { timer_error_t ret = check_id(id); + timer_8_bit_handle_t * handle = &timer_8_bit_static_handle[id]; + if(TIMER_ERROR_OK != ret) { return ret; @@ -968,7 +837,7 @@ timer_error_t timer_8_bit_stop(uint8_t id) } /* Reset prescaler to NO_CLOCK*/ - *(internal_config[id].handle.TCCRB) = (*(internal_config[id].handle.TCCRB) & ~CS_MSK) | TIMER8BIT_CLK_NO_CLOCK; + *(handle->TCCRB) = (*(handle->TCCRB) & ~CS_MSK) | TIMER8BIT_CLK_NO_CLOCK; return ret; } diff --git a/Drivers/Timers/Timer_8_bit_async/CMakeLists.txt b/Drivers/Timers/Timer_8_bit_async/CMakeLists.txt index 328a728..e10f826 100644 --- a/Drivers/Timers/Timer_8_bit_async/CMakeLists.txt +++ b/Drivers/Timers/Timer_8_bit_async/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0) +cmake_minimum_required(VERSION 3.20) add_library(timer_8_bit_async_driver STATIC ${CMAKE_CURRENT_SOURCE_DIR}/src/timer_8_bit_async.c diff --git a/Drivers/Timers/Timer_8_bit_async/Tests/CMakeLists.txt b/Drivers/Timers/Timer_8_bit_async/Tests/CMakeLists.txt index 6739f90..1ff5081 100644 --- a/Drivers/Timers/Timer_8_bit_async/Tests/CMakeLists.txt +++ b/Drivers/Timers/Timer_8_bit_async/Tests/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0) +cmake_minimum_required(VERSION 3.20) project(timer_8_bit_async_driver_test) enable_testing() @@ -27,6 +27,7 @@ target_include_directories(timer_8_bit_async_driver_tests PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/Stub ${CMAKE_CURRENT_SOURCE_DIR}/../inc ${CMAKE_CURRENT_SOURCE_DIR}/../../Timer_generic/inc + ${CMAKE_CURRENT_SOURCE_DIR} ) target_include_directories(timer_8_bit_async_driver_tests SYSTEM PUBLIC diff --git a/Drivers/Timers/Timer_8_bit_async/Tests/Stub/timer_8_bit_async_registers_stub.c b/Drivers/Timers/Timer_8_bit_async/Tests/Stub/timer_8_bit_async_registers_stub.c index c6c6ecc..da81d79 100644 --- a/Drivers/Timers/Timer_8_bit_async/Tests/Stub/timer_8_bit_async_registers_stub.c +++ b/Drivers/Timers/Timer_8_bit_async/Tests/Stub/timer_8_bit_async_registers_stub.c @@ -4,7 +4,7 @@ @ FreeMyCode version : 1.0 RC alpha Author : bebenlebricolo - License : + License : name : GPLv3 url : https://www.gnu.org/licenses/quick-guide-gplv3.html Date : 12/02/2021 @@ -28,8 +28,9 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -#include "timer_8_bit_async_registers_stub.h" #include +#include "timer_8_bit_async.h" +#include "timer_8_bit_async_registers_stub.h" timer_8_bit_async_registers_stub_t timer_8_bit_async_registers_stub = {0}; @@ -38,19 +39,18 @@ void timer_8_bit_async_registers_stub_erase(void) memset(&timer_8_bit_async_registers_stub, 0, sizeof(timer_8_bit_async_registers_stub_t)); } -void timer_8_bit_async_registers_stub_init_handle(timer_8_bit_async_handle_t * handle) +timer_8_bit_async_handle_t timer_8_bit_async_static_handle[TIMER_8_BIT_ASYNC_COUNT] = { - if (NULL != handle) { - handle->OCRA = &timer_8_bit_async_registers_stub.OCRA; - handle->OCRB = &timer_8_bit_async_registers_stub.OCRB; - handle->TCCRA = &timer_8_bit_async_registers_stub.TCCRA; - handle->TCCRB = &timer_8_bit_async_registers_stub.TCCRB; - handle->TCNT = &timer_8_bit_async_registers_stub.TCNT; - handle->TIMSK = &timer_8_bit_async_registers_stub.TIMSK; - handle->TIFR = &timer_8_bit_async_registers_stub.TIFR; - handle->ASSR_REG = &timer_8_bit_async_registers_stub.ASSR_REG; + .OCRA = &timer_8_bit_async_registers_stub.OCRA, + .OCRB = &timer_8_bit_async_registers_stub.OCRB, + .TCCRA = &timer_8_bit_async_registers_stub.TCCRA, + .TCCRB = &timer_8_bit_async_registers_stub.TCCRB, + .TCNT = &timer_8_bit_async_registers_stub.TCNT, + .TIMSK = &timer_8_bit_async_registers_stub.TIMSK, + .TIFR = &timer_8_bit_async_registers_stub.TIFR, + .ASSR_REG = &timer_8_bit_async_registers_stub.ASSR_REG } -} +}; diff --git a/Drivers/Timers/Timer_8_bit_async/Tests/timer_8_bit_async_tests.cpp b/Drivers/Timers/Timer_8_bit_async/Tests/timer_8_bit_async_tests.cpp index fcd7578..dee4971 100644 --- a/Drivers/Timers/Timer_8_bit_async/Tests/timer_8_bit_async_tests.cpp +++ b/Drivers/Timers/Timer_8_bit_async/Tests/timer_8_bit_async_tests.cpp @@ -4,7 +4,7 @@ @ FreeMyCode version : 1.0 RC alpha Author : bebenlebricolo - License : + License : name : GPLv3 url : https://www.gnu.org/licenses/quick-guide-gplv3.html Date : 12/02/2021 @@ -45,8 +45,7 @@ class Timer8BitAsyncFixture : public ::testing::Test { timer_8_bit_async_registers_stub_erase(); (void) timer_8_bit_async_get_default_config(&config); - timer_8_bit_async_registers_stub_init_handle(&config.handle); - (void) timer_8_bit_async_set_handle(DT_ID, &config.handle); + timer_8_bit_async_clear_init_states(); } void TearDown() override { @@ -59,92 +58,69 @@ TEST(timer_8_bit_async_driver_tests, guard_null_handle) timer_error_t ret = timer_8_bit_async_get_default_config(&config); ASSERT_EQ(TIMER_ERROR_OK, ret); - /* We should have a null handle at the moment */ - ASSERT_TRUE(NULL == config.handle.OCRA ); - ASSERT_TRUE(NULL == config.handle.OCRB ); - ASSERT_TRUE(NULL == config.handle.TCCRA ); - ASSERT_TRUE(NULL == config.handle.TCCRB ); - ASSERT_TRUE(NULL == config.handle.TCNT ); - ASSERT_TRUE(NULL == config.handle.TIFR ); - ASSERT_TRUE(NULL == config.handle.TIMSK ); - ASSERT_TRUE(NULL == config.handle.ASSR_REG ); - ret = timer_8_bit_async_init(DT_ID, &config); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); ret = timer_8_bit_async_reconfigure(DT_ID, &config); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); /* Test compare match mode A get/set api */ ret = timer_8_bit_async_set_compare_match_A(DT_ID, config.timing_config.comp_match_a); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); ret = timer_8_bit_async_get_compare_match_A(DT_ID, &config.timing_config.comp_match_a); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); /* Test compare match mode B get/set api */ ret = timer_8_bit_async_set_compare_match_B(DT_ID, config.timing_config.comp_match_b); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); ret = timer_8_bit_async_get_compare_match_B(DT_ID, &config.timing_config.comp_match_b); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); - - /* Test handle setting function */ - ret = timer_8_bit_async_set_handle(DT_ID, &config.handle); ASSERT_EQ(TIMER_ERROR_OK, ret); /* Test interrupt config get/set api */ ret = timer_8_bit_async_set_interrupt_config(DT_ID, &config.interrupt_config); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); ret = timer_8_bit_async_get_interrupt_config(DT_ID, &config.interrupt_config); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); /* Test OCRA get/set api */ ret = timer_8_bit_async_set_ocra_register_value(DT_ID, config.timing_config.ocra_val); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); ret = timer_8_bit_async_get_ocra_register_value(DT_ID, &config.timing_config.ocra_val); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); /* Test OCRB get/set api */ ret = timer_8_bit_async_set_ocrb_register_value(DT_ID, config.timing_config.ocrb_val); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); ret = timer_8_bit_async_get_ocrb_register_value(DT_ID, &config.timing_config.ocrb_val); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); /* Test counter get/set api */ ret = timer_8_bit_async_set_counter_value(DT_ID, config.timing_config.counter); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); ret = timer_8_bit_async_get_counter_value(DT_ID, &config.timing_config.counter); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); /* Test force compare flags get/set api */ ret = timer_8_bit_async_set_force_compare_config(DT_ID, &config.force_compare); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); ret = timer_8_bit_async_get_force_compare_config(DT_ID, &config.force_compare); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); /* Test prescaler get/set api */ ret = timer_8_bit_async_set_prescaler(DT_ID, config.timing_config.prescaler); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); ret = timer_8_bit_async_get_prescaler(DT_ID, &config.timing_config.prescaler); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); /* Test waveforme generation get/set api */ ret = timer_8_bit_async_set_waveform_generation(DT_ID, config.timing_config.waveform_mode); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); ret = timer_8_bit_async_get_waveform_generation(DT_ID, &config.timing_config.waveform_mode); - ASSERT_EQ(TIMER_ERROR_NULL_HANDLE, ret); + ASSERT_EQ(TIMER_ERROR_OK, ret); } TEST_F(Timer8BitAsyncFixture, guard_reg_busy) { timer_error_t ret = TIMER_ERROR_OK; - /* We should have a null handle at the moment */ - ASSERT_TRUE(NULL != config.handle.OCRA ); - ASSERT_TRUE(NULL != config.handle.OCRB ); - ASSERT_TRUE(NULL != config.handle.TCCRA ); - ASSERT_TRUE(NULL != config.handle.TCCRB ); - ASSERT_TRUE(NULL != config.handle.TCNT ); - ASSERT_TRUE(NULL != config.handle.TIFR ); - ASSERT_TRUE(NULL != config.handle.TIMSK ); - ASSERT_TRUE(NULL != config.handle.ASSR_REG ); /* Test compare match mode A get/set api */ timer_8_bit_async_registers_stub.ASSR_REG |= TCRAUB_MSK; @@ -230,13 +206,6 @@ TEST(timer_8_bit_async_driver_tests, guard_null_pointer) ret = timer_8_bit_async_get_compare_match_B(DT_ID, nullptr_compare_output_mode); ASSERT_EQ(TIMER_ERROR_NULL_POINTER, ret); - timer_8_bit_async_handle_t * nullptr_handle = NULL; - /* Test handle setting function */ - ret = timer_8_bit_async_set_handle(DT_ID, nullptr_handle); - ASSERT_EQ(TIMER_ERROR_NULL_POINTER, ret); - ret = timer_8_bit_async_get_handle(DT_ID, nullptr_handle); - ASSERT_EQ(TIMER_ERROR_NULL_POINTER, ret); - /* Test interrupt config get/set api */ timer_8_bit_async_interrupt_config_t * nullptr_interrupt_config = NULL; ret = timer_8_bit_async_set_interrupt_config(DT_ID, nullptr_interrupt_config); @@ -293,12 +262,6 @@ TEST(timer_8_bit_async_driver_tests, guard_wrong_id) ret = timer_8_bit_async_get_compare_match_B(targeted_id, &config.timing_config.comp_match_b); ASSERT_EQ(TIMER_ERROR_UNKNOWN_TIMER, ret); - /* Test handle setting function */ - ret = timer_8_bit_async_set_handle(targeted_id, &config.handle); - ASSERT_EQ(TIMER_ERROR_UNKNOWN_TIMER, ret); - ret = timer_8_bit_async_get_handle(targeted_id, &config.handle); - ASSERT_EQ(TIMER_ERROR_UNKNOWN_TIMER, ret); - /* Test interrupt config get/set api */ ret = timer_8_bit_async_set_interrupt_config(targeted_id, &config.interrupt_config); ASSERT_EQ(TIMER_ERROR_UNKNOWN_TIMER, ret); @@ -587,51 +550,92 @@ TEST_F(Timer8BitAsyncFixture, test_initialisation_deinitialisation) TEST(timer_8_bit_async_driver_tests, test_parameters_computation_prescaler) { - uint32_t cpu_freq = 16'000'000; + uint32_t clock_freq = 16'000'000; uint32_t target_freq = 1'000; uint8_t ocra = 0; uint16_t accumulator = 0; timer_8_bit_async_prescaler_selection_t prescaler = TIMER8BIT_ASYNC_CLK_PRESCALER_1; - timer_8_bit_async_compute_matching_parameters(&cpu_freq, &target_freq, &prescaler, &ocra, &accumulator); + timer_8_bit_async_compute_matching_parameters(&clock_freq, &target_freq, &prescaler, &ocra, &accumulator); ASSERT_EQ(prescaler, TIMER8BIT_ASYNC_CLK_PRESCALER_64); ASSERT_EQ(ocra, 249U); ASSERT_EQ(accumulator, 0U); target_freq = 3'000; - timer_8_bit_async_compute_matching_parameters(&cpu_freq, &target_freq, &prescaler, &ocra, &accumulator); + timer_8_bit_async_compute_matching_parameters(&clock_freq, &target_freq, &prescaler, &ocra, &accumulator); ASSERT_EQ(prescaler, TIMER8BIT_ASYNC_CLK_PRESCALER_32); ASSERT_EQ(ocra, 165U); ASSERT_EQ(accumulator, 0U); target_freq = 5'000; - timer_8_bit_async_compute_matching_parameters(&cpu_freq, &target_freq, &prescaler, &ocra, &accumulator); + timer_8_bit_async_compute_matching_parameters(&clock_freq, &target_freq, &prescaler, &ocra, &accumulator); ASSERT_EQ(prescaler, TIMER8BIT_ASYNC_CLK_PRESCALER_32); ASSERT_EQ(ocra, 99U); ASSERT_EQ(accumulator, 0U); target_freq = 1'000'000; - timer_8_bit_async_compute_matching_parameters(&cpu_freq, &target_freq, &prescaler, &ocra, &accumulator); + timer_8_bit_async_compute_matching_parameters(&clock_freq, &target_freq, &prescaler, &ocra, &accumulator); ASSERT_EQ(prescaler, TIMER8BIT_ASYNC_CLK_PRESCALER_1); ASSERT_EQ(ocra, 15U); ASSERT_EQ(accumulator, 0U); - cpu_freq = 8'000'000; + clock_freq = 8'000'000; target_freq = 440; - timer_8_bit_async_compute_matching_parameters(&cpu_freq, &target_freq, &prescaler, &ocra, &accumulator); + timer_8_bit_async_compute_matching_parameters(&clock_freq, &target_freq, &prescaler, &ocra, &accumulator); ASSERT_EQ(prescaler, TIMER8BIT_ASYNC_CLK_PRESCALER_128); ASSERT_EQ(ocra, 141U); ASSERT_EQ(accumulator, 0U); - cpu_freq = 16'000'000; + clock_freq = 16'000'000; target_freq = 1; - timer_8_bit_async_compute_matching_parameters(&cpu_freq, &target_freq, &prescaler, &ocra, &accumulator); + timer_8_bit_async_compute_matching_parameters(&clock_freq, &target_freq, &prescaler, &ocra, &accumulator); ASSERT_EQ(prescaler, TIMER8BIT_ASYNC_CLK_PRESCALER_1024); ASSERT_EQ(ocra, 124U); ASSERT_EQ(accumulator, 124U); } +TEST(timer_8_bit_async_async_driver_tests, test_find_closest_prescaler_function) +{ + timer_error_t err = TIMER_ERROR_OK; + uint32_t clock_freq = 16'000'000; + uint32_t target_freq = 1'000'000; + + timer_8_bit_async_prescaler_selection_t prescaler = TIMER8BIT_ASYNC_CLK_PRESCALER_1; + err = timer_8_bit_async_compute_closest_prescaler(&clock_freq, &target_freq, &prescaler); + ASSERT_EQ(err, TIMER_ERROR_OK); + ASSERT_EQ(prescaler, TIMER8BIT_ASYNC_CLK_PRESCALER_1); + + target_freq = 1'000; + err = timer_8_bit_async_compute_closest_prescaler(&clock_freq, &target_freq, &prescaler); + ASSERT_EQ(err, TIMER_ERROR_OK); + ASSERT_EQ(prescaler, TIMER8BIT_ASYNC_CLK_PRESCALER_64); + + + target_freq = 3'000; + err = timer_8_bit_async_compute_closest_prescaler(&clock_freq, &target_freq, &prescaler); + ASSERT_EQ(err, TIMER_ERROR_OK); + ASSERT_EQ(prescaler, TIMER8BIT_ASYNC_CLK_PRESCALER_32); + + target_freq = 5'000; + err = timer_8_bit_async_compute_closest_prescaler(&clock_freq, &target_freq, &prescaler); + ASSERT_EQ(err, TIMER_ERROR_OK); + ASSERT_EQ(prescaler, TIMER8BIT_ASYNC_CLK_PRESCALER_32); + + clock_freq = 8'000'000; + target_freq = 440; + err = timer_8_bit_async_compute_closest_prescaler(&clock_freq, &target_freq, &prescaler); + ASSERT_EQ(err, TIMER_ERROR_OK); + ASSERT_EQ(prescaler, TIMER8BIT_ASYNC_CLK_PRESCALER_128); + + clock_freq = 16'000'000; + target_freq = 1; + err = timer_8_bit_async_compute_closest_prescaler(&clock_freq, &target_freq, &prescaler); + ASSERT_EQ(err, TIMER_ERROR_OK); + ASSERT_EQ(prescaler, TIMER8BIT_ASYNC_CLK_PRESCALER_1024); +} + + int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); diff --git a/Drivers/Timers/Timer_8_bit_async/inc/timer_8_bit_async.h b/Drivers/Timers/Timer_8_bit_async/inc/timer_8_bit_async.h index 2bd147d..a89572f 100644 --- a/Drivers/Timers/Timer_8_bit_async/inc/timer_8_bit_async.h +++ b/Drivers/Timers/Timer_8_bit_async/inc/timer_8_bit_async.h @@ -4,7 +4,7 @@ @ FreeMyCode version : 1.0 RC alpha Author : bebenlebricolo - License : + License : name : GPLv3 url : https://www.gnu.org/licenses/quick-guide-gplv3.html Date : 12/02/2021 @@ -34,6 +34,7 @@ along with this program. If not, see . #include #include "timer_generic.h" #include "timer_8_bit_async_reg.h" +#include "config.h" #ifdef __cplusplus extern "C" @@ -83,7 +84,6 @@ typedef struct timer_8_bit_async_timing_config_t timing_config; /**< Handles basic timing configuration for 8 bit timers */ timer_8_bit_async_interrupt_config_t interrupt_config; /**< Handles interrupt configuraitons for 8 bit timers */ timer_8_bit_async_force_compare_config_t force_compare; /**< Handles force compare flags on output A and B, generic configuration among timers */ - timer_8_bit_async_handle_t handle; /**< Stores pointer locations to peripheral registers */ } timer_8_bit_async_config_t; @@ -103,31 +103,6 @@ typedef struct */ timer_error_t timer_8_bit_async_get_default_config(timer_8_bit_async_config_t * config); -/** - * @brief sets the handle of timer_8_bit driver - * @param[in] id : targeted timer id (used to fetch internal configuration based on ids) - * @param[in] handle : handle to be copied into internal configuration - * @return - * TIMER_ERROR_OK : operation succeeded - * TIMER_ERROR_UNKNOWN_TIMER : given id is out of range - * TIMER_ERROR_NULL_POINTER : given force_comp_config parameter points to NULL -*/ -timer_error_t timer_8_bit_async_set_handle(uint8_t id, timer_8_bit_async_handle_t * const handle); - -/** - * @brief gets the handle of timer_8_bit driver - * @param[in] id : targeted timer id (used to fetch internal configuration based on ids) - * @param[in] handle : handle to be fetched from internal configuration - * @return - * TIMER_ERROR_OK : operation succeeded - * TIMER_ERROR_UNKNOWN_TIMER : given id is out of range - * TIMER_ERROR_NULL_POINTER : given force_comp_config parameter points to NULL -*/ -timer_error_t timer_8_bit_async_get_handle(uint8_t id, timer_8_bit_async_handle_t * const handle); - - - - /* ################################ Force compare flags configuration ############################### */ /** * @brief sets the given force compare configuration object to targeted timer @@ -191,6 +166,11 @@ timer_error_t timer_8_bit_async_get_interrupt_config(uint8_t id, timer_8_bit_asy * TIMER_ERROR_NULL_POINTER : given it_config parameter points to NULL */ timer_error_t timer_8_bit_async_get_interrupt_flags(uint8_t id, timer_8_bit_async_interrupt_config_t * it_flags); + +/** + * @brief Resets internal configuration of timer 8 bit async driver + */ +void timer_8_bit_async_clear_init_states(void); #endif @@ -428,23 +408,62 @@ timer_error_t timer_8_bit_async_start(uint8_t id); */ timer_error_t timer_8_bit_async_stop(uint8_t id); -#define TIMER_8_BIT_ASYNC_MAX_PRESCALER_COUNT (7U) +/** + * @brief Computes timing parameters such as prescaler, ocr value and accumulator in order to satisfy the requested target frequency, + * using CPU main clock frequency as the time constraint. + * + * @param[in] clock_freq : current CPU main clock frequency + * @param[in] target_freq : desired output frequency of the timer (assuming ocr is the top value) + * @param[out] prescaler : output prescaler parameter + * @param[out] ocr : output ocr value + * @param[out] accumulator : output accumulator value ; used by the timebase module for instance to count events and extend timer's counter capabilities + * @return + * TIMER_ERROR_OK : operation succeeded + * TIMER_ERROR_CONFIG : Something was wrong in the input configuration (probably unachievable frequency parameters) + */ +timer_error_t timer_8_bit_async_compute_matching_parameters(const uint32_t * const clock_freq, + const uint32_t * const target_freq, + timer_8_bit_async_prescaler_selection_t * const prescaler, + uint8_t * const ocra, + uint16_t * const accumulator); /** - * @brief Timer 8 bit prescaler table, ascending order. Used to compute the closest prescaler - * which can be used to generate any given frequency -*/ -extern const timer_generic_prescaler_pair_t timer_8_bit_async_prescaler_table[TIMER_8_BIT_ASYNC_MAX_PRESCALER_COUNT]; + * @brief Computes timing parameters such as prescaler, ocr value and accumulator in order to satisfy the requested target frequency, + * using CPU main clock frequency as the time constraint. + * + * @param[in] clock_freq : current CPU main clock frequency + * @param[in] target_freq : desired output frequency of the timer (assuming ocr is the top value) + * @param[out] prescaler : output prescaler parameter + * @return + * TIMER_ERROR_OK : operation succeeded + * TIMER_ERROR_CONFIG : Something was wrong in the input configuration (probably unachievable frequency parameters) + */ +timer_error_t timer_8_bit_async_compute_closest_prescaler(const uint32_t * const clock_freq, + const uint32_t * const target_freq, + timer_8_bit_async_prescaler_selection_t * const prescaler); -void timer_8_bit_async_compute_matching_parameters(const uint32_t * const cpu_freq, - const uint32_t * const target_freq, - timer_8_bit_async_prescaler_selection_t * const prescaler, - uint8_t * const ocra, - uint16_t * const accumulator); +#define TIMER_8_BIT_ASYNC_MAX_PRESCALER_COUNT (7U) +/** + * @brief Converts a single prescaler enum to its value-based counterpart. + * Behaves as a lookup table + * @param prescaler : input prescaler + * @return uint16_t : returns the translated value (from 1 to 1024) + */ uint16_t timer_8_bit_async_prescaler_to_value(const timer_8_bit_async_prescaler_selection_t prescaler); + +/** + * @brief Translates back a plain prescaler value to its enum counterpart. + * If no direct equivalent is found, returns the TIMER8BIT_CLK_NO_CLOCK enum value + * @param input_prescaler : input prescaler parameter + * @return enum value + */ timer_8_bit_async_prescaler_selection_t timer_8_bit_async_prescaler_from_value(uint16_t const * const input_prescaler); +/** + * @brief this handle has to be declared somewhere and initialised appropriately + */ +extern timer_8_bit_async_handle_t timer_8_bit_async_static_handle[TIMER_8_BIT_ASYNC_COUNT]; #ifdef __cplusplus } diff --git a/Drivers/Timers/Timer_8_bit_async/src/timer_8_bit_async.c b/Drivers/Timers/Timer_8_bit_async/src/timer_8_bit_async.c index 8498edd..d7f9402 100644 --- a/Drivers/Timers/Timer_8_bit_async/src/timer_8_bit_async.c +++ b/Drivers/Timers/Timer_8_bit_async/src/timer_8_bit_async.c @@ -4,7 +4,7 @@ @ FreeMyCode version : 1.0 RC alpha Author : bebenlebricolo - License : + License : name : GPLv3 url : https://www.gnu.org/licenses/quick-guide-gplv3.html Date : 12/02/2021 @@ -42,11 +42,11 @@ along with this program. If not, see . static struct { - timer_8_bit_async_handle_t handle; timer_8_bit_async_prescaler_selection_t prescaler; bool is_initialised; } internal_config[TIMER_8_BIT_ASYNC_COUNT] = {0}; + const timer_generic_prescaler_pair_t timer_8_bit_async_prescaler_table[TIMER_8_BIT_ASYNC_MAX_PRESCALER_COUNT] = { {.value = 1, .type = (uint8_t) TIMER8BIT_ASYNC_CLK_PRESCALER_1 }, @@ -82,54 +82,55 @@ uint16_t timer_8_bit_async_prescaler_to_value(const timer_8_bit_async_prescaler_ return 0; } -void timer_8_bit_async_compute_matching_parameters(const uint32_t * const cpu_freq, - const uint32_t * const target_freq, - timer_8_bit_async_prescaler_selection_t * const prescaler, - uint8_t * const ocra, - uint16_t * const accumulator) +timer_error_t timer_8_bit_async_compute_matching_parameters(const uint32_t * const clock_freq, + const uint32_t * const target_freq, + timer_8_bit_async_prescaler_selection_t * const prescaler, + uint8_t * const ocra, + uint16_t * const accumulator) { timer_generic_parameters_t parameters = { .input = { - .cpu_frequency = *cpu_freq, + .clock_freq = *clock_freq, .target_frequency = *target_freq, .resolution = TIMER_GENERIC_RESOLUTION_8_BIT, .prescaler_lookup_array.array = timer_8_bit_async_prescaler_table, .prescaler_lookup_array.size = TIMER_8_BIT_ASYNC_MAX_PRESCALER_COUNT, }, }; - timer_generic_compute_parameters(¶meters); + if(TIMER_ERROR_OK != timer_generic_compute_parameters(¶meters)) + { + return TIMER_ERROR_CONFIG; + } *prescaler = timer_8_bit_async_prescaler_from_value(¶meters.output.prescaler); - *ocra = (uint8_t) parameters.output.ocra; + *ocra = (uint8_t) parameters.output.ocr; *accumulator = parameters.output.accumulator; + return TIMER_ERROR_OK; } -static inline timer_error_t check_handle(timer_8_bit_async_handle_t * const handle) +timer_error_t timer_8_bit_async_compute_closest_prescaler(const uint32_t * const clock_freq, + const uint32_t * const target_freq, + timer_8_bit_async_prescaler_selection_t * const prescaler) { - bool found_null = false; - if (NULL == handle) - { - /* Not the use case we really want to check, but this is a case of error anyway - which will generate segfaults errors if we let it propagate further ... */ - found_null = true; - } - else + timer_generic_parameters_t parameters = { - found_null |= (NULL == handle->OCRA); - found_null |= (NULL == handle->OCRB); - found_null |= (NULL == handle->TCCRA); - found_null |= (NULL == handle->TCCRB); - found_null |= (NULL == handle->TCNT); - found_null |= (NULL == handle->TIFR); - found_null |= (NULL == handle->TIMSK); - found_null |= (NULL == handle->ASSR_REG); - } - if (found_null) + .input = + { + .clock_freq = *clock_freq, + .target_frequency = *target_freq, + .resolution = TIMER_GENERIC_RESOLUTION_8_BIT, + .prescaler_lookup_array.array = timer_8_bit_async_prescaler_table, + .prescaler_lookup_array.size = TIMER_8_BIT_ASYNC_MAX_PRESCALER_COUNT, + }, + }; + if(TIMER_ERROR_OK != timer_generic_find_closest_prescaler(¶meters)) { - return TIMER_ERROR_NULL_HANDLE; + return TIMER_ERROR_CONFIG; } - return TIMER_ERROR_OK;; + *prescaler = timer_8_bit_async_prescaler_from_value(¶meters.output.prescaler); + + return TIMER_ERROR_OK; } static inline timer_error_t check_id(uint8_t id) @@ -143,47 +144,13 @@ static inline timer_error_t check_id(uint8_t id) static inline timer_error_t check_reg_busy(uint8_t id, uint8_t mask) { - if (0 != (*internal_config[id].handle.ASSR_REG & mask)) + if (0 != (*(timer_8_bit_async_static_handle[id].ASSR_REG) & mask)) { return TIMER_ERROR_REGISTER_IS_BUSY; } return TIMER_ERROR_OK; } -timer_error_t timer_8_bit_async_set_handle(uint8_t id, timer_8_bit_async_handle_t * const handle) -{ - timer_error_t ret = check_id(id); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - - if (NULL == handle) - { - return TIMER_ERROR_NULL_POINTER; - } - - memcpy(&internal_config[id].handle, handle, sizeof(timer_8_bit_async_handle_t)); - return ret; -} - -timer_error_t timer_8_bit_async_get_handle(uint8_t id, timer_8_bit_async_handle_t * const handle) -{ - timer_error_t ret = check_id(id); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - - if (NULL == handle) - { - return TIMER_ERROR_NULL_POINTER; - } - - memcpy(handle, &internal_config[id].handle, sizeof(timer_8_bit_async_handle_t)); - return ret; -} - timer_error_t timer_8_bit_async_get_default_config(timer_8_bit_async_config_t * config) { timer_error_t ret = TIMER_ERROR_OK; @@ -214,15 +181,6 @@ timer_error_t timer_8_bit_async_get_default_config(timer_8_bit_async_config_t * config->force_compare.force_comp_match_a = false; config->force_compare.force_comp_match_b = false; - /* Architecture and device dependent, must be set at configuration time */ - config->handle.OCRA = NULL; - config->handle.OCRB = NULL; - config->handle.TCCRA = NULL; - config->handle.TCCRB = NULL; - config->handle.TCNT = NULL; - config->handle.TIFR = NULL; - config->handle.TIMSK = NULL; - config->handle.ASSR_REG = NULL; return ret; } @@ -239,13 +197,6 @@ timer_error_t timer_8_bit_async_set_force_compare_config(uint8_t id, timer_8_bit return TIMER_ERROR_NULL_POINTER; } - /* Not fully configured handle, do not attempt to write to it until configured !*/ - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - /* If register is asynchronously updated, it will be blocked by hardware and any read/write operation will be discarded. See datasheet for further details */ ret = check_reg_busy(id, TCRBUB_MSK); @@ -257,21 +208,21 @@ timer_error_t timer_8_bit_async_set_force_compare_config(uint8_t id, timer_8_bit /* Handles force output compare A flags */ if (true == force_comp_config->force_comp_match_a) { - *(internal_config[id].handle.TCCRB) |= (1U << FOCA_BIT) ; + *(timer_8_bit_async_static_handle[id].TCCRB) |= (1U << FOCA_BIT) ; } else { - *(internal_config[id].handle.TCCRB) &= ~(1U << FOCA_BIT) ; + *(timer_8_bit_async_static_handle[id].TCCRB) &= ~(1U << FOCA_BIT) ; } /* Handles force output compare A flags */ if (true == force_comp_config->force_comp_match_b) { - *(internal_config[id].handle.TCCRB) |= (1U << FOCB_BIT) ; + *(timer_8_bit_async_static_handle[id].TCCRB) |= (1U << FOCB_BIT) ; } else { - *(internal_config[id].handle.TCCRB) &= ~(1U << FOCB_BIT) ; + *(timer_8_bit_async_static_handle[id].TCCRB) &= ~(1U << FOCB_BIT) ; } return ret; } @@ -287,12 +238,6 @@ timer_error_t timer_8_bit_async_get_force_compare_config(uint8_t id, timer_8_bit { return TIMER_ERROR_NULL_POINTER; } - /* Not fully configured handle, do not attempt to write to it until configured !*/ - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } ret = check_reg_busy(id, TCRBUB_MSK); if (TIMER_ERROR_OK != ret) @@ -301,7 +246,7 @@ timer_error_t timer_8_bit_async_get_force_compare_config(uint8_t id, timer_8_bit } /* Handles Force Compare Output A flag */ - if (0U == (*(internal_config[id].handle.TCCRB) & FOCA_MSK)) + if (0U == (*(timer_8_bit_async_static_handle[id].TCCRB) & FOCA_MSK)) { force_comp_config->force_comp_match_a = false; } @@ -311,7 +256,7 @@ timer_error_t timer_8_bit_async_get_force_compare_config(uint8_t id, timer_8_bit } /* Handles Force Compare Output B flag */ - if (0U == (*(internal_config[id].handle.TCCRB) & FOCB_MSK)) + if (0U == (*(timer_8_bit_async_static_handle[id].TCCRB) & FOCB_MSK)) { force_comp_config->force_comp_match_b = false; } @@ -334,39 +279,34 @@ timer_error_t timer_8_bit_async_set_interrupt_config(uint8_t id, timer_8_bit_asy { return TIMER_ERROR_NULL_POINTER; } - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } /* TIMSK register */ if (true == it_config->it_comp_match_a) { - *(internal_config[id].handle.TIMSK) |= 1U << OCIEA_BIT; + *(timer_8_bit_async_static_handle[id].TIMSK) |= 1U << OCIEA_BIT; } else { - *(internal_config[id].handle.TIMSK) &= ~(1U << OCIEA_BIT); + *(timer_8_bit_async_static_handle[id].TIMSK) &= ~(1U << OCIEA_BIT); } if (true == it_config->it_comp_match_b) { - *(internal_config[id].handle.TIMSK) |= 1U << OCIEB_BIT; + *(timer_8_bit_async_static_handle[id].TIMSK) |= 1U << OCIEB_BIT; } else { - *(internal_config[id].handle.TIMSK) &= ~(1U << OCIEB_BIT); + *(timer_8_bit_async_static_handle[id].TIMSK) &= ~(1U << OCIEB_BIT); } /* TOIE interrupt flag is the first bit, no need to bitshift it */ if (true == it_config->it_timer_overflow) { - *(internal_config[id].handle.TIMSK) |= 1U; + *(timer_8_bit_async_static_handle[id].TIMSK) |= 1U; } else { - *(internal_config[id].handle.TIMSK) &= ~1U; + *(timer_8_bit_async_static_handle[id].TIMSK) &= ~1U; } return ret; } @@ -385,14 +325,8 @@ timer_error_t timer_8_bit_async_get_interrupt_config(uint8_t id, timer_8_bit_asy return TIMER_ERROR_NULL_POINTER; } - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - /* Output Compare Match A Interrupt Flag */ - if (0U == (*(internal_config[id].handle.TIMSK) & OCIEA_MSK)) + if (0U == (*(timer_8_bit_async_static_handle[id].TIMSK) & OCIEA_MSK)) { it_config->it_comp_match_a = false; } @@ -402,7 +336,7 @@ timer_error_t timer_8_bit_async_get_interrupt_config(uint8_t id, timer_8_bit_asy } /* Output Compare Match B Interrupt Flag */ - if (0U == (*(internal_config[id].handle.TIMSK) & OCIEB_MSK)) + if (0U == (*(timer_8_bit_async_static_handle[id].TIMSK) & OCIEB_MSK)) { it_config->it_comp_match_b = false; } @@ -412,7 +346,7 @@ timer_error_t timer_8_bit_async_get_interrupt_config(uint8_t id, timer_8_bit_asy } /* Timer Overflow Interrupt Flag */ - if (0U == (*(internal_config[id].handle.TIMSK) & 1U)) + if (0U == (*(timer_8_bit_async_static_handle[id].TIMSK) & 1U)) { it_config->it_timer_overflow = false; } @@ -437,15 +371,8 @@ timer_error_t timer_8_bit_async_get_interrupt_flags(uint8_t id, timer_8_bit_asyn return TIMER_ERROR_NULL_POINTER; } - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - - /* Output Compare Match A Interrupt Flag */ - if (0U == (*(internal_config[id].handle.TIFR) & OCIEA_MSK)) + if (0U == (*(timer_8_bit_async_static_handle[id].TIFR) & OCIEA_MSK)) { it_flags->it_comp_match_a = false; } @@ -455,7 +382,7 @@ timer_error_t timer_8_bit_async_get_interrupt_flags(uint8_t id, timer_8_bit_asyn } /* Output Compare Match B Interrupt Flag */ - if (0U == (*(internal_config[id].handle.TIFR) & OCIEB_MSK)) + if (0U == (*(timer_8_bit_async_static_handle[id].TIFR) & OCIEB_MSK)) { it_flags->it_comp_match_b = false; } @@ -465,7 +392,7 @@ timer_error_t timer_8_bit_async_get_interrupt_flags(uint8_t id, timer_8_bit_asyn } /* Timer Overflow Interrupt Flag */ - if (0U == (*(internal_config[id].handle.TIFR) & 1U)) + if (0U == (*(timer_8_bit_async_static_handle[id].TIFR) & 1U)) { it_flags->it_timer_overflow = false; } @@ -475,8 +402,16 @@ timer_error_t timer_8_bit_async_get_interrupt_flags(uint8_t id, timer_8_bit_asyn } return ret; +} +void timer_8_bit_async_clear_init_states(void) +{ + for (uint8_t i = 0 ; i < TIMER_8_BIT_ASYNC_COUNT ; i++) + { + internal_config[i].is_initialised = false; + } } + #endif @@ -489,12 +424,6 @@ timer_error_t timer_8_bit_async_set_prescaler(uint8_t id, const timer_8_bit_asyn return ret; } - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - /* If register is asynchronously updated, it will be blocked by hardware and any read/write operation will be discarded. See datasheet for further details */ ret = check_reg_busy(id, TCRBUB_MSK); @@ -503,7 +432,7 @@ timer_error_t timer_8_bit_async_set_prescaler(uint8_t id, const timer_8_bit_asyn return ret; } - *(internal_config[id].handle.TCCRB) = (*(internal_config[id].handle.TCCRB) & ~CS_MSK) | prescaler; + *(timer_8_bit_async_static_handle[id].TCCRB) = (*(timer_8_bit_async_static_handle[id].TCCRB) & ~CS_MSK) | prescaler; return ret; } @@ -520,12 +449,6 @@ timer_error_t timer_8_bit_async_get_prescaler(uint8_t id, timer_8_bit_async_pres return TIMER_ERROR_NULL_POINTER; } - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - /* If register is asynchronously updated, it will be blocked by hardware and any read/write operation will be discarded. See datasheet for further details */ ret = check_reg_busy(id, TCRBUB_MSK); @@ -534,7 +457,7 @@ timer_error_t timer_8_bit_async_get_prescaler(uint8_t id, timer_8_bit_async_pres return ret; } - *prescaler = (*(internal_config[id].handle.TCCRB) & CS_MSK); + *prescaler = (*(timer_8_bit_async_static_handle[id].TCCRB) & CS_MSK); return ret; } @@ -546,13 +469,7 @@ timer_error_t timer_8_bit_async_set_compare_match_A(uint8_t id, const timer_8_bi return ret; } - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - - /* If register is asynchronously updated, it will be blocked by hardware and any read/write operation + /* If register is asynchronously updated, it will be blocked by hardware and any read/write operation will be discarded. See datasheet for further details */ ret = check_reg_busy(id, TCRAUB_MSK); if (TIMER_ERROR_OK != ret) @@ -560,7 +477,7 @@ timer_error_t timer_8_bit_async_set_compare_match_A(uint8_t id, const timer_8_bi return ret; } - *(internal_config[id].handle.TCCRA) = (*(internal_config[id].handle.TCCRA) & ~COMA_MSK) | (compA << COMA_BIT); + *(timer_8_bit_async_static_handle[id].TCCRA) = (*(timer_8_bit_async_static_handle[id].TCCRA) & ~COMA_MSK) | (compA << COMA_BIT); return ret; } @@ -577,12 +494,6 @@ timer_error_t timer_8_bit_async_get_compare_match_A(uint8_t id, timer_8_bit_asyn return TIMER_ERROR_NULL_POINTER; } - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - /* If register is asynchronously updated, it will be blocked by hardware and any read/write operation will be discarded. See datasheet for further details */ ret = check_reg_busy(id, TCRAUB_MSK); @@ -591,7 +502,7 @@ timer_error_t timer_8_bit_async_get_compare_match_A(uint8_t id, timer_8_bit_asyn return ret; } - *compA = ((*(internal_config[id].handle.TCCRA) & COMA_MSK) >> COMA_BIT); + *compA = ((*(timer_8_bit_async_static_handle[id].TCCRA) & COMA_MSK) >> COMA_BIT); return ret; } @@ -603,12 +514,6 @@ timer_error_t timer_8_bit_async_set_compare_match_B(uint8_t id, timer_8_bit_asyn return ret; } - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - /* If register is asynchronously updated, it will be blocked by hardware and any read/write operation will be discarded. See datasheet for further details */ ret = check_reg_busy(id, TCRAUB_MSK); @@ -617,7 +522,7 @@ timer_error_t timer_8_bit_async_set_compare_match_B(uint8_t id, timer_8_bit_asyn return ret; } - *(internal_config[id].handle.TCCRA) = (*(internal_config[id].handle.TCCRA) & ~COMB_MSK) | (compB << COMB_BIT); + *(timer_8_bit_async_static_handle[id].TCCRA) = (*(timer_8_bit_async_static_handle[id].TCCRA) & ~COMB_MSK) | (compB << COMB_BIT); return ret; } @@ -634,12 +539,6 @@ timer_error_t timer_8_bit_async_get_compare_match_B(uint8_t id, timer_8_bit_asyn return TIMER_ERROR_NULL_POINTER; } - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - /* If register is asynchronously updated, it will be blocked by hardware and any read/write operation * will be discarded. See datasheet for further details */ ret = check_reg_busy(id, TCRAUB_MSK); @@ -648,7 +547,7 @@ timer_error_t timer_8_bit_async_get_compare_match_B(uint8_t id, timer_8_bit_asyn return ret; } - *compB = ((*(internal_config[id].handle.TCCRA) & COMB_MSK) >> COMB_BIT); + *compB = ((*(timer_8_bit_async_static_handle[id].TCCRA) & COMB_MSK) >> COMB_BIT); return ret; } @@ -660,21 +559,15 @@ timer_error_t timer_8_bit_async_set_waveform_generation(uint8_t id, const timer_ return ret; } - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - ret = check_reg_busy(id, TCRAUB_MSK | TCRBUB_MSK); if (TIMER_ERROR_OK != ret) { return ret; } - *(internal_config[id].handle.TCCRA) = (*(internal_config[id].handle.TCCRA) & ~(WGM0_MSK | WGM1_MSK)) | (waveform & (WGM0_MSK | WGM1_MSK)); + *(timer_8_bit_async_static_handle[id].TCCRA) = (*(timer_8_bit_async_static_handle[id].TCCRA) & ~(WGM0_MSK | WGM1_MSK)) | (waveform & (WGM0_MSK | WGM1_MSK)); /* Select bit index 2 of waveform mode (matches datasheet bit mapping) and store it to bit index 3 of TCCRB with one more bitshift */ - *(internal_config[id].handle.TCCRB) = (*(internal_config[id].handle.TCCRB) & ~WGM2_MSK) | (waveform & (1U << 2U) << 1U); + *(timer_8_bit_async_static_handle[id].TCCRB) = (*(timer_8_bit_async_static_handle[id].TCCRB) & ~WGM2_MSK) | (waveform & (1U << 2U) << 1U); return ret; } @@ -691,12 +584,6 @@ timer_error_t timer_8_bit_async_get_waveform_generation(uint8_t id, timer_8_bit_ return TIMER_ERROR_NULL_POINTER; } - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - ret = check_reg_busy(id, TCRAUB_MSK | TCRBUB_MSK); if (TIMER_ERROR_OK != ret) { @@ -704,8 +591,8 @@ timer_error_t timer_8_bit_async_get_waveform_generation(uint8_t id, timer_8_bit_ } *waveform = (timer_8_bit_async_waveform_generation_t)(0U); - *waveform |= (*(internal_config[id].handle.TCCRA) & (WGM0_MSK | WGM1_MSK)); - *waveform |= (*(internal_config[id].handle.TCCRB) & (WGM2_MSK)) >> 1U; + *waveform |= (*(timer_8_bit_async_static_handle[id].TCCRA) & (WGM0_MSK | WGM1_MSK)); + *waveform |= (*(timer_8_bit_async_static_handle[id].TCCRB) & (WGM2_MSK)) >> 1U; return ret; } @@ -717,12 +604,6 @@ timer_error_t timer_8_bit_async_set_counter_value(uint8_t id, const uint8_t tick return ret; } - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - /* If register is asynchronously updated, it will be blocked by hardware and any read/write operation * will be discarded. See datasheet for further details */ ret = check_reg_busy(id, TCNUB_MSK); @@ -732,7 +613,7 @@ timer_error_t timer_8_bit_async_set_counter_value(uint8_t id, const uint8_t tick } /* Write new value to internal timer/counter register */ - *(internal_config[id].handle.TCNT) = ticks; + *(timer_8_bit_async_static_handle[id].TCNT) = ticks; return ret; } @@ -749,12 +630,6 @@ timer_error_t timer_8_bit_async_get_counter_value(uint8_t id, uint8_t * ticks) return TIMER_ERROR_NULL_POINTER; } - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - /* If register is asynchronously updated, it will be blocked by hardware and any read/write operation * will be discarded. See datasheet for further details */ ret = check_reg_busy(id, TCNUB_MSK); @@ -764,7 +639,7 @@ timer_error_t timer_8_bit_async_get_counter_value(uint8_t id, uint8_t * ticks) } /* Transfer data from internal device's timer/count main register */ - *ticks = *internal_config[id].handle.TCNT; + *ticks = *(timer_8_bit_async_static_handle[id].TCNT); return ret; } @@ -776,12 +651,6 @@ timer_error_t timer_8_bit_async_set_ocra_register_value(uint8_t id, uint8_t ocra return ret; } - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - /* If register is asynchronously updated, it will be blocked by hardware and any read/write operation * will be discarded. See datasheet for further details */ ret = check_reg_busy(id, OCRAUB_MSK); @@ -790,7 +659,7 @@ timer_error_t timer_8_bit_async_set_ocra_register_value(uint8_t id, uint8_t ocra return ret; } - *internal_config[id].handle.OCRA = ocra; + *(timer_8_bit_async_static_handle[id].OCRA) = ocra; return ret; } @@ -807,12 +676,6 @@ timer_error_t timer_8_bit_async_get_ocra_register_value(uint8_t id, uint8_t * oc return TIMER_ERROR_NULL_POINTER; } - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - /* If register is asynchronously updated, it will be blocked by hardware and any read/write operation * will be discarded. See datasheet for further details */ ret = check_reg_busy(id, OCRAUB_MSK); @@ -821,7 +684,7 @@ timer_error_t timer_8_bit_async_get_ocra_register_value(uint8_t id, uint8_t * oc return ret; } - *ocra = *internal_config[id].handle.OCRA; + *ocra = *(timer_8_bit_async_static_handle[id].OCRA); return ret; } @@ -833,12 +696,6 @@ timer_error_t timer_8_bit_async_set_ocrb_register_value(uint8_t id, uint8_t ocrb return ret; } - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - /* If register is asynchronously updated, it will be blocked by hardware and any read/write operation * will be discarded. See datasheet for further details */ ret = check_reg_busy(id, OCRBUB_MSK); @@ -847,7 +704,7 @@ timer_error_t timer_8_bit_async_set_ocrb_register_value(uint8_t id, uint8_t ocrb return ret; } - *internal_config[id].handle.OCRB = ocrb; + *(timer_8_bit_async_static_handle[id].OCRB) = ocrb; return ret; } @@ -864,12 +721,6 @@ timer_error_t timer_8_bit_async_get_ocrb_register_value(uint8_t id, uint8_t * oc return TIMER_ERROR_NULL_POINTER; } - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - /* If register is asynchronously updated, it will be blocked by hardware and any read/write operation * will be discarded. See datasheet for further details */ ret = check_reg_busy(id, OCRBUB_MSK); @@ -878,14 +729,14 @@ timer_error_t timer_8_bit_async_get_ocrb_register_value(uint8_t id, uint8_t * oc return ret; } - *ocrb = *internal_config[id].handle.OCRB; + *ocrb = *(timer_8_bit_async_static_handle[id].OCRB); return ret; } static timer_error_t timer_8_bit_async_write_config(uint8_t id, timer_8_bit_async_config_t * const config) { timer_error_t ret = TIMER_ERROR_OK; - timer_8_bit_async_handle_t * handle = &internal_config[id].handle; + timer_8_bit_async_handle_t * handle = &timer_8_bit_async_static_handle[id]; /* If register is asynchronously updated, it will be blocked by hardware and any read/write operation * will be discarded. See datasheet for further details */ @@ -905,7 +756,7 @@ static timer_error_t timer_8_bit_async_write_config(uint8_t id, timer_8_bit_asyn /* Clear TCCRA register first, otherwise we can't reconfigure the OCRA/OCRB regs!*/ *(handle->TCCRA) = 0; - + /* TCCRA register */ *(handle->OCRA) = config->timing_config.ocra_val; *(handle->OCRB) = config->timing_config.ocrb_val; @@ -1014,12 +865,6 @@ timer_error_t timer_8_bit_async_deinit(uint8_t id) return ret; } - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - /* Retrieve a config object to write back default configuration into timer registers */ timer_8_bit_async_config_t config; ret = timer_8_bit_async_stop(id); @@ -1051,18 +896,6 @@ timer_error_t timer_8_bit_async_reconfigure(uint8_t id, timer_8_bit_async_config return TIMER_ERROR_NULL_POINTER; } - ret = check_handle(&config->handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - - ret = timer_8_bit_async_set_handle(id, &config->handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - /* Stop the timer before reconfiguring it */ if (true == internal_config[id].is_initialised) { @@ -1091,12 +924,6 @@ timer_error_t timer_8_bit_async_start(uint8_t id) return ret; } - ret = check_handle(&internal_config[id].handle); - if (TIMER_ERROR_OK != ret) - { - return ret; - } - if (false == internal_config[id].is_initialised) { return TIMER_ERROR_NOT_INITIALISED; @@ -1111,7 +938,7 @@ timer_error_t timer_8_bit_async_start(uint8_t id) } /* This time, set the prescaler to start the timer, unless prescaler is set to NO_CLOCK source */ - *(internal_config[id].handle.TCCRB) = (*(internal_config[id].handle.TCCRB) & ~CS_MSK) | internal_config[id].prescaler; + *(timer_8_bit_async_static_handle[id].TCCRB) = (*(timer_8_bit_async_static_handle[id].TCCRB) & ~CS_MSK) | internal_config[id].prescaler; return ret; } @@ -1137,6 +964,6 @@ timer_error_t timer_8_bit_async_stop(uint8_t id) } /* Reset prescaler to NO_CLOCK*/ - *(internal_config[id].handle.TCCRB) = (*(internal_config[id].handle.TCCRB) & ~CS_MSK) | TIMER8BIT_ASYNC_CLK_NO_CLOCK; + *(timer_8_bit_async_static_handle[id].TCCRB) = (*(timer_8_bit_async_static_handle[id].TCCRB) & ~CS_MSK) | TIMER8BIT_ASYNC_CLK_NO_CLOCK; return ret; } diff --git a/Drivers/Timers/Timer_generic/CMakeLists.txt b/Drivers/Timers/Timer_generic/CMakeLists.txt index 1944968..f7bd755 100644 --- a/Drivers/Timers/Timer_generic/CMakeLists.txt +++ b/Drivers/Timers/Timer_generic/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0) +cmake_minimum_required(VERSION 3.20) add_library(timer_generic_driver STATIC ${CMAKE_CURRENT_SOURCE_DIR}/src/timer_generic.c diff --git a/Drivers/Timers/Timer_generic/Tests/CMakeLists.txt b/Drivers/Timers/Timer_generic/Tests/CMakeLists.txt index 436e986..283f138 100644 --- a/Drivers/Timers/Timer_generic/Tests/CMakeLists.txt +++ b/Drivers/Timers/Timer_generic/Tests/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0) +cmake_minimum_required(VERSION 3.20) project(timer_generic_tests) enable_testing() @@ -25,6 +25,7 @@ target_include_directories(timer_generic_driver_tests PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../../Timer_8_bit/inc ${CMAKE_CURRENT_SOURCE_DIR}/../../Timer_8_bit_async/inc ${CMAKE_CURRENT_SOURCE_DIR}/../../Timer_16_bit/inc + ${CMAKE_CURRENT_SOURCE_DIR} ) target_include_directories(timer_generic_driver_tests SYSTEM PUBLIC diff --git a/Drivers/Timers/Timer_generic/Tests/config.h b/Drivers/Timers/Timer_generic/Tests/config.h new file mode 100644 index 0000000..e76babc --- /dev/null +++ b/Drivers/Timers/Timer_generic/Tests/config.h @@ -0,0 +1,39 @@ +/* + +------------------ +@ +FreeMyCode version : 1.0 RC alpha + Author : bebenlebricolo + License : + name : GPLv3 + url : https://www.gnu.org/licenses/quick-guide-gplv3.html + Date : 12/02/2021 + Project : LabBenchPowerSupply + Description : The Lab Bench Power Supply provides a simple design based around an Arduino Nano board to convert AC main voltage into + smaller ones, ranging from 0V to 16V, with voltage and current regulations +@ +------------------ + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#ifndef CONFIG_HEADER +#define CONFIG_HEADER + +/* Tells timer 8 bit driver to allocate space for 1 timer 8 bit */ +#define TIMER_8_BIT_COUNT 1 +#define TIMER_8_BIT_ASYNC_COUNT 1 +#define TIMER_16_BIT_COUNT 1 + +#endif /* CONFIG_HEADER */ \ No newline at end of file diff --git a/Drivers/Timers/Timer_generic/Tests/timer_generic_tests.cpp b/Drivers/Timers/Timer_generic/Tests/timer_generic_tests.cpp index 0a9a9e1..6bdddfe 100644 --- a/Drivers/Timers/Timer_generic/Tests/timer_generic_tests.cpp +++ b/Drivers/Timers/Timer_generic/Tests/timer_generic_tests.cpp @@ -4,7 +4,7 @@ @ FreeMyCode version : 1.0 RC alpha Author : bebenlebricolo - License : + License : name : GPLv3 url : https://www.gnu.org/licenses/quick-guide-gplv3.html Date : 12/02/2021 @@ -49,7 +49,7 @@ TEST(timer_generic_driver_tests, test_compute_parameters) {0,0} }; timer_generic_parameters_t parameters; - parameters.input.cpu_frequency = 16'000'000U; + parameters.input.clock_freq = 16'000'000U; parameters.input.target_frequency = 1'000'000U; parameters.input.resolution = TIMER_GENERIC_RESOLUTION_8_BIT; parameters.input.prescaler_lookup_array.array = array; @@ -57,32 +57,73 @@ TEST(timer_generic_driver_tests, test_compute_parameters) timer_generic_compute_parameters(¶meters); ASSERT_EQ(parameters.output.prescaler, 1U); - ASSERT_EQ(parameters.output.ocra, 15U); + ASSERT_EQ(parameters.output.ocr, 15U); ASSERT_EQ(parameters.output.accumulator, 0U); - parameters.input.cpu_frequency = 16'000'000U; + parameters.input.clock_freq = 16'000'000U; parameters.input.target_frequency = 1'000U; timer_generic_compute_parameters(¶meters); ASSERT_EQ(parameters.output.prescaler, 64U); - ASSERT_EQ(parameters.output.ocra, 249U); + ASSERT_EQ(parameters.output.ocr, 249U); ASSERT_EQ(parameters.output.accumulator, 0U); - parameters.input.cpu_frequency = 16'000'000U; + parameters.input.clock_freq = 16'000'000U; parameters.input.target_frequency = 1U; timer_generic_compute_parameters(¶meters); ASSERT_EQ(parameters.output.prescaler, 1024U); - ASSERT_EQ(parameters.output.ocra, 124U); + ASSERT_EQ(parameters.output.ocr, 124U); ASSERT_EQ(parameters.output.accumulator, 124U); - parameters.input.cpu_frequency = 24'000'000U; + parameters.input.clock_freq = 24'000'000U; parameters.input.target_frequency = 1U; timer_generic_compute_parameters(¶meters); ASSERT_EQ(parameters.output.prescaler, 1024U); - ASSERT_EQ(parameters.output.ocra, 22U); + ASSERT_EQ(parameters.output.ocr, 22U); ASSERT_EQ(parameters.output.accumulator, 1018U); } +TEST(timer_generic_driver_tests, test_compute_prescaler) +{ + const uint8_t max_size = 7U; + const uint8_t array_size = 5U; + timer_generic_prescaler_pair_t array[max_size] = + { + {1U, 1U}, + {8U, 2U}, + {64U, 3U}, + {256U, 4U}, + {1024U, 5U}, + {0,0}, + {0,0} + }; + timer_generic_parameters_t parameters; + parameters.input.clock_freq = 16'000'000U; + parameters.input.target_frequency = 1'000'000U; + parameters.input.resolution = TIMER_GENERIC_RESOLUTION_8_BIT; + parameters.input.prescaler_lookup_array.array = array; + parameters.input.prescaler_lookup_array.size = array_size; + + timer_generic_find_closest_prescaler(¶meters); + ASSERT_EQ(parameters.output.prescaler, 1U); + + parameters.input.clock_freq = 16'000'000U; + parameters.input.target_frequency = 1'000U; + timer_generic_find_closest_prescaler(¶meters); + ASSERT_EQ(parameters.output.prescaler, 64U); + + parameters.input.clock_freq = 16'000'000U; + parameters.input.target_frequency = 1U; + timer_generic_find_closest_prescaler(¶meters); + ASSERT_EQ(parameters.output.prescaler, 1024U); + + parameters.input.clock_freq = 24'000'000U; + parameters.input.target_frequency = 1U; + timer_generic_find_closest_prescaler(¶meters); + ASSERT_EQ(parameters.output.prescaler, 1024U); + +} + int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); diff --git a/Drivers/Timers/Timer_generic/inc/timer_generic.h b/Drivers/Timers/Timer_generic/inc/timer_generic.h index 9d0ccd5..3cbf016 100644 --- a/Drivers/Timers/Timer_generic/inc/timer_generic.h +++ b/Drivers/Timers/Timer_generic/inc/timer_generic.h @@ -4,7 +4,7 @@ @ FreeMyCode version : 1.0 RC alpha Author : bebenlebricolo - License : + License : name : GPLv3 url : https://www.gnu.org/licenses/quick-guide-gplv3.html Date : 12/02/2021 @@ -52,51 +52,126 @@ typedef enum TIMER_ERROR_OK, /**< Everything went well so far */ TIMER_ERROR_CONFIG, /**< Given configuration is not well-formed */ TIMER_ERROR_NULL_POINTER, /**< One or more parameters were set to NULL */ - TIMER_ERROR_NULL_HANDLE, /**< Timer handle still points to NULL */ TIMER_ERROR_UNKNOWN_TIMER, /**< Given timer id exceeds the range of registered timers */ TIMER_ERROR_NOT_INITIALISED, /**< Given configuration is not well-formed */ TIMER_ERROR_REGISTER_IS_BUSY, /**< Selected register cannot be written/read : register is busy */ TIMER_ERROR_ALREADY_INITIALISED, /**< Timer has already been initialised */ } timer_error_t; -#define TIMER_GENERIC_8_BIT_LIMIT_VALUE (256U) -#define TIMER_GENERIC_16_BIT_LIMIT_VALUE (65536U) +#define TIMER_GENERIC_8_BIT_LIMIT_VALUE (255U) +#define TIMER_GENERIC_9_BIT_LIMIT_VALUE (511U) +#define TIMER_GENERIC_10_BIT_LIMIT_VALUE (1023U) +#define TIMER_GENERIC_16_BIT_LIMIT_VALUE (65535U) +/** + * @brief Encodes the various resolutions that are available in the hardware timer implementations. + * @note both 8 bit and 8 bit async timer share the same counter interface, and 16 bit timer can be configured to act as + * an 8bit, 9bit, 10bit or 16bit timer depending on the selected PWM modes. + */ typedef enum { - TIMER_GENERIC_RESOLUTION_8_BIT, - TIMER_GENERIC_RESOLUTION_16_BIT, + TIMER_GENERIC_RESOLUTION_UNDEFINED, /**< Default state for this resolution enum */ + TIMER_GENERIC_RESOLUTION_8_BIT, /**< Used by all three hardware timer implementations, 8 bit counter */ + TIMER_GENERIC_RESOLUTION_9_BIT, /**< Used only by 16 bit timer, reduced counter span mode (9bits) */ + TIMER_GENERIC_RESOLUTION_10_BIT, /**< Used only by 16 bit timer, reduced counter span mode (10bits) */ + TIMER_GENERIC_RESOLUTION_16_BIT, /**< Used only by 16 bit timer, full span 16 bits counter resolution */ + TIMER_GENERIC_RESOLUTION_COUNT, /**< Gives the end of the enum values scope, internal use only */ } timer_generic_resolution_t; +/** + * @brief Converts the enum version of timer_generic_resolution_t into its numeric representation + * @param resolution : input resolution, enum form + * @return uint16_t : translated value. If input resolution is set to TIMER_GENERIC_RESOLUTION_UNDEFINED, 0 is returned. + */ +uint16_t timer_generic_resolution_to_top_value(const timer_generic_resolution_t resolution); + +/** + * @brief Converts the a counter top value into its enum representation + * @param top_value : counter top value + * @return timer_generic_resolution_t -> matching enum value, or TIMER_GENERIC_RESOLUTION_UNDEFINED if top_value does not map to + * an existing enum value counterpart + */ +timer_generic_resolution_t timer_generic_resolution_from_top_value(const uint16_t top_value); + +/** + * @brief Encodes the various timer architecture found in AVR world + */ +typedef enum +{ + TIMER_ARCH_UNDEFINED, /**< Default value */ + TIMER_ARCH_8_BIT, /**< Regular 8 bit timer counter architecture */ + TIMER_ARCH_8_BIT_ASYNC, /**< 8 bit timer counter with asynchronous features */ + TIMER_ARCH_16_BIT /**< Enhanced 16 bit timer counter architecture */ +} timer_arch_t; + +/** + * @brief this bitfield structure is used to pack both prescaler value and + * its enum representation altogether in a tight space. It is mainly used to perform prescaler calculations + * and conversions/mapping + */ typedef struct { uint16_t value : 11; uint16_t type : 5; } timer_generic_prescaler_pair_t; +/** + * @brief this structure represents a basic parameter set used to compute a basic timer configuration. + * The input field represents the timing characteristics, current system clock frequency, etc. + * The output field yields the results of the conducted calculations. + * + * It is used as the interface to the generic compute parameters function, which in turn is used by each specific timer driver in order to compute a timer configuration + * matching as closely as possible the user provided timing characteristics. + */ typedef struct { struct { - uint32_t cpu_frequency; - uint32_t target_frequency; - timer_generic_resolution_t resolution; + uint32_t clock_freq; /**< Current clock frequency used as timer clocking system. */ + /**< Note that asynchronous timers can be clocked by an external + clock source. Upon such cases, the clock_freq field + should match the frequency of the external clock source in order to yield + adequate results */ + uint32_t target_frequency; /**< Expected resulting frequency of underlying timer (represents the number of counter cycles per second) */ + timer_generic_resolution_t resolution; /**< Underlying timer resolution. Can be 8, 9, 10 or 16 bits or custom (ie : governed by either ICR or OCRA dependending + on the selected timer hardware configuration) */ struct { - timer_generic_prescaler_pair_t const * array; - uint8_t size; - } prescaler_lookup_array; - } input; + timer_generic_prescaler_pair_t const * array; /**< Internal prescaler lookup table, implemented in each specific timer driver variants */ + uint8_t size; /**< States the size of the prescaler array so that we do not do a buffer overflow */ + } prescaler_lookup_array; /**< Prescaler lookup array is used by timing computing algorithm in order to select the + closest prescaler value which matches the desired output timing characteristics */ + } input; /**< Input field of this whole parameters structure, consumed as read-only by the this timer driver */ struct { - uint16_t prescaler; - uint16_t ocra; - uint32_t accumulator; - } output; + uint16_t prescaler; /**< Calculated prescaler, which tries to match output frequency as closely as possible while trying to + use the maximum counter capacity (in order to provide greater control over duty cycle and frequency, + if possible */ + + uint16_t ocr; /**< Calculated trigger value, usually represents either OCRA or OCRB values, but for timer 16 bits implementations + it can represent ICR values as well */ + + uint16_t accumulator; /**< Software accumulator limit value, this value is set to 0 if accumulator is not required but switches to + a real value in case requested frequency is slower than what hardware timer can achieve using prescalers alone. + It is effectively used as a software extension to the hardware based timer counters and as a result allows far + lower frequencies */ + uint16_t top_value; /**< Stores the top value of the counter based on resolution enum input parameter */ + } output; /**< Ouput field of this struvture is used to provide results of computations to the caller of timer_generic_compute_parameters */ } timer_generic_parameters_t; -void timer_generic_compute_parameters(timer_generic_parameters_t * const parameters); +/** + * @brief Finds the timer parameters in order to get the closest frequency out of the a given Timer + * @param parameters : takes the .input field and computes closest values for prescaler, ocr and accumulator values + */ +timer_error_t timer_generic_compute_parameters(timer_generic_parameters_t * const parameters); + +/** + * @brief finds the closest prescaler value that allows the selected timer to achieve the requested frequency. + * Note that its implementation is independent of hardware timer limitations and only results from calculations on frequencies. + * @param parameters : input parameters, also serves as output parameter block + */ +timer_error_t timer_generic_find_closest_prescaler(timer_generic_parameters_t * const parameters); #ifdef __cplusplus } diff --git a/Drivers/Timers/Timer_generic/src/timer_generic.c b/Drivers/Timers/Timer_generic/src/timer_generic.c index 0d6f2ef..9bb2c99 100644 --- a/Drivers/Timers/Timer_generic/src/timer_generic.c +++ b/Drivers/Timers/Timer_generic/src/timer_generic.c @@ -4,7 +4,7 @@ @ FreeMyCode version : 1.0 RC alpha Author : bebenlebricolo - License : + License : name : GPLv3 url : https://www.gnu.org/licenses/quick-guide-gplv3.html Date : 12/02/2021 @@ -30,51 +30,69 @@ along with this program. If not, see . #include "timer_generic.h" -void timer_generic_compute_parameters(timer_generic_parameters_t * const parameters) +static const uint16_t resolution_lookup_table[TIMER_GENERIC_RESOLUTION_COUNT] = { - const uint32_t freq_ratio = parameters->input.cpu_frequency / parameters->input.target_frequency; - const uint16_t limit_value = (parameters->input.resolution == TIMER_GENERIC_RESOLUTION_8_BIT) ? (TIMER_GENERIC_8_BIT_LIMIT_VALUE - 1) : (TIMER_GENERIC_16_BIT_LIMIT_VALUE - 1); + [TIMER_GENERIC_RESOLUTION_UNDEFINED] = 0, + [TIMER_GENERIC_RESOLUTION_8_BIT] = TIMER_GENERIC_8_BIT_LIMIT_VALUE, + [TIMER_GENERIC_RESOLUTION_9_BIT] = TIMER_GENERIC_9_BIT_LIMIT_VALUE, + [TIMER_GENERIC_RESOLUTION_10_BIT] = TIMER_GENERIC_10_BIT_LIMIT_VALUE, + [TIMER_GENERIC_RESOLUTION_16_BIT] = TIMER_GENERIC_16_BIT_LIMIT_VALUE, +}; - // It is possible that this operation produces aliasing because we do not check if - // the remainder of this division is exactly 0 (no remainder, clean euclidean division) - const uint32_t min_prescaler = freq_ratio / (uint32_t) limit_value; - - parameters->output.prescaler = 1U; - uint16_t target_prescaler = 1U; +uint16_t timer_generic_resolution_to_top_value(const timer_generic_resolution_t resolution) +{ + if((uint8_t)resolution < TIMER_GENERIC_RESOLUTION_COUNT) + { + return resolution_lookup_table[(uint8_t) resolution]; + } + return 0U; +} - for (uint8_t i = 0 ; i < parameters->input.prescaler_lookup_array.size ; i++) +timer_generic_resolution_t timer_generic_resolution_from_top_value(const uint16_t top_value) +{ + for(uint8_t i = 0 ; i < TIMER_GENERIC_RESOLUTION_COUNT ; i++) { - parameters->output.prescaler = parameters->input.prescaler_lookup_array.array[i].value; - target_prescaler = parameters->input.prescaler_lookup_array.array[i].value; - if (parameters->input.prescaler_lookup_array.array[i].value >= min_prescaler) + if(resolution_lookup_table[i] == top_value) { - break; + return (timer_generic_resolution_t) i; } } + return TIMER_GENERIC_RESOLUTION_UNDEFINED; +} + +timer_error_t timer_generic_compute_parameters(timer_generic_parameters_t * const parameters) +{ + const uint32_t freq_ratio = parameters->input.clock_freq / parameters->input.target_frequency; + uint16_t top_value = timer_generic_resolution_to_top_value(parameters->input.resolution); + parameters->output.top_value = top_value; - uint32_t computed_ocra = 0; + if (TIMER_ERROR_OK != timer_generic_find_closest_prescaler(parameters)) + { + return TIMER_ERROR_CONFIG; + } + + uint16_t computed_ocra = 0; parameters->output.accumulator = 0; - if (0 != target_prescaler) + if (0 != parameters->output.prescaler) { // It is possible that this operation produces aliasing because we do not check if // the remainder of this division is exactly 0 (no remainder, clean euclidean division) - computed_ocra = freq_ratio / (uint32_t) target_prescaler; + computed_ocra = (uint16_t) (freq_ratio / (uint32_t) parameters->output.prescaler); } // Happens when timescale is really large compared to CPU frequency // We have to create an accumulator which will act as a second-stage prescaler - if (computed_ocra >= limit_value) + if (computed_ocra >= top_value) { // Select a remainder arbitrarily high to start the algorithm uint16_t min_remainder = 50U; - parameters->output.prescaler = target_prescaler; // linear search, starting from the end of the resolution range // This will select the greatest value of OCRA while trying to minize the remainder. // Note that 1 is the only candidate for which the remainder will always be 0. // If this takes too long / too much computing power, fallback using 1 and use the accumulator value to // account for the remaining values. - for (uint16_t i = limit_value ; i >= 1 ; i--) + for (uint16_t i = parameters->output.top_value ; i >= 1 ; i--) { uint16_t remainder = (computed_ocra % i); if ((0 != remainder) && (remainder < min_remainder)) @@ -100,11 +118,44 @@ void timer_generic_compute_parameters(timer_generic_parameters_t * const paramet if (computed_ocra != 0) { - parameters->output.ocra = (computed_ocra - 1U); + parameters->output.ocr = (computed_ocra - 1U); } else { //TODO : We might be a bit off. In this case, normally we would have to raise the prescaler one step further and recompute ocra value. - parameters->output.ocra = computed_ocra; + parameters->output.ocr = computed_ocra; + } + + return TIMER_ERROR_OK; +} + + +timer_error_t timer_generic_find_closest_prescaler(timer_generic_parameters_t * const parameters) +{ + const uint32_t freq_ratio = parameters->input.clock_freq / parameters->input.target_frequency; + + // It is possible that this operation produces aliasing because we do not check if + // the remainder of this division is exactly 0 (no remainder, clean euclidean division) + uint16_t top_value = timer_generic_resolution_to_top_value(parameters->input.resolution); + if (top_value == 0) + { + return TIMER_ERROR_CONFIG; + } + + const uint16_t min_prescaler = (uint16_t) (freq_ratio / (uint32_t) top_value); + + parameters->output.prescaler = 1U; + uint16_t target_prescaler = 1U; + + for (uint8_t i = 0 ; i < parameters->input.prescaler_lookup_array.size ; i++) + { + parameters->output.prescaler = parameters->input.prescaler_lookup_array.array[i].value; + target_prescaler = parameters->input.prescaler_lookup_array.array[i].value; + if (parameters->input.prescaler_lookup_array.array[i].value >= min_prescaler) + { + break; + } } + parameters->output.prescaler = target_prescaler; + return TIMER_ERROR_OK; } diff --git a/App/CMakeLists.txt b/ExampleApp/CMakeLists.txt similarity index 62% rename from App/CMakeLists.txt rename to ExampleApp/CMakeLists.txt index 4d4052e..02e6c69 100644 --- a/App/CMakeLists.txt +++ b/ExampleApp/CMakeLists.txt @@ -1,32 +1,33 @@ +cmake_minimum_required(VERSION 3.15) +project(ExampleApp C) + +##################################################################################################################### +########################################## Typical example application ############################################## +##################################################################################################################### + add_executable(firmware ${CMAKE_CURRENT_SOURCE_DIR}/src/main.c ${CMAKE_CURRENT_SOURCE_DIR}/src/driver_setup.c ${CMAKE_CURRENT_SOURCE_DIR}/src/module_setup.c + ${CMAKE_CURRENT_SOURCE_DIR}/src/config.c ) # Produce a Map file set (Map_file firmware.map) -# Disable full debug optimizations for now -string(REPLACE "-Og" "-O0" CMAKE_C_FLAGS_DEBUG ${CMAKE_C_FLAGS_DEBUG}) - +# Setup C flags for each configuration set (CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Wl,-Map=${Map_file}") set (CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -Wl,-Map=${Map_file}") set (CMAKE_C_FLAGS_MINSIZEREL "${CMAKE_C_FLAGS_MINSIZEREL} -Wl,-Map=${Map_file}") set (CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} -Wl,-Map=${Map_file}") +# Specify include directories for this application +# Note that CMake automatically handles transitive include dependencies target_include_directories(firmware PUBLIC ${CMAKE_SOURCE_DIR}/App/inc - ${CMAKE_SOURCE_DIR}/Drivers/Adc/inc - ${CMAKE_SOURCE_DIR}/Drivers/Timers/Timer_generic/inc - ${CMAKE_SOURCE_DIR}/Drivers/Timers/Timer_8_bit/inc - ${CMAKE_SOURCE_DIR}/Drivers/Timers/Timer_8_bit_async/inc - ${CMAKE_SOURCE_DIR}/Drivers/Timers/Timer_16_bit/inc - ${CMAKE_SOURCE_DIR}/Drivers/Lcd_screen/inc - ${CMAKE_SOURCE_DIR}/Drivers/I2c/inc - ${SIMAVR_INCLUDE_DIR} ) +# Link our application with the built libraries target_link_libraries(firmware adc_driver timer_8_bit_driver diff --git a/App/inc/config.h b/ExampleApp/inc/config.h similarity index 95% rename from App/inc/config.h rename to ExampleApp/inc/config.h index 64b5a7e..1b99dee 100644 --- a/App/inc/config.h +++ b/ExampleApp/inc/config.h @@ -38,6 +38,8 @@ along with this program. If not, see . #define TIMEBASE_MAX_MODULES 3U #define I2C_DEVICES_COUNT 1U +#define PWM_MAX_SOFT_INSTANCES 2U +#define PWM_MAX_HARD_INSTANCES 2U // Only implement master tx driver #define I2C_IMPLEM_MASTER_TX diff --git a/App/inc/driver_setup.h b/ExampleApp/inc/driver_setup.h similarity index 100% rename from App/inc/driver_setup.h rename to ExampleApp/inc/driver_setup.h diff --git a/App/inc/module_setup.h b/ExampleApp/inc/module_setup.h similarity index 100% rename from App/inc/module_setup.h rename to ExampleApp/inc/module_setup.h diff --git a/App/inc/pin_mapping.h b/ExampleApp/inc/pin_mapping.h similarity index 100% rename from App/inc/pin_mapping.h rename to ExampleApp/inc/pin_mapping.h diff --git a/ExampleApp/src/config.c b/ExampleApp/src/config.c new file mode 100644 index 0000000..92a1a7f --- /dev/null +++ b/ExampleApp/src/config.c @@ -0,0 +1,38 @@ +#include "config.h" +#include "timebase.h" +#include "timer_8_bit.h" +#include "timer_8_bit_async.h" +#include "timer_16_bit.h" +#include "avr/io.h" + +#ifndef F_CPU +#pragma message("F_CPU macro was not set, defaulting to 8MHz") + #define F_CPU (8'000'000ULL) +#endif + +timebase_config_t timebase_static_config[TIMEBASE_MAX_MODULES] = +{ + [0] = + { + .timer = + { + .type = TIMER_ARCH_8_BIT_ASYNC, + .index = 0, + }, + .clock_freq = F_CPU, + .timescale = TIMEBASE_TIMESCALE_MILLISECONDS + } +}; + +timer_8_bit_handle_t timer_8_bit_static_handle[TIMER_8_BIT_COUNT] = +{ + { + .OCRA = &OCR0A, + .OCRB = &OCR0B, + .TCCRA = &TCCR0A, + .TCCRB = &TCCR0B, + .TCNT = &TCNT0, + .TIFR = &TIFR0, + .TIMSK = &TIMSK0 + } +}; \ No newline at end of file diff --git a/App/src/driver_setup.c b/ExampleApp/src/driver_setup.c similarity index 84% rename from App/src/driver_setup.c rename to ExampleApp/src/driver_setup.c index 4deb996..1307c92 100644 --- a/App/src/driver_setup.c +++ b/ExampleApp/src/driver_setup.c @@ -4,7 +4,7 @@ @ FreeMyCode version : 1.0 RC alpha Author : bebenlebricolo - License : + License : name : GPLv3 url : https://www.gnu.org/licenses/quick-guide-gplv3.html Date : 12/02/2021 @@ -71,15 +71,6 @@ driver_setup_error_t driver_init_timer_0(void) } } - /* Configuring handle */ - config.handle.OCRA = &OCR0A; - config.handle.OCRB = &OCR0B; - config.handle.TCCRA = &TCCR0A; - config.handle.TCCRB = &TCCR0B; - config.handle.TCNT = &TCNT0; - config.handle.TIFR = &TIFR0; - config.handle.TIMSK = &TIMSK0; - /* Enable OC0A and OC0B as outputs */ OC0A_DDR_REG |= (1 << OC0A_PIN_NUMBER); OC0B_DDR_REG |= (1 << OC0B_PIN_NUMBER); @@ -87,7 +78,7 @@ driver_setup_error_t driver_init_timer_0(void) /* Set original port state of OC0A/OC0B pins before any OCRx overrides them */ /* This is done in opposing polarities to respect mosfet push-pull driver pair starting point*/ OC0A_PORT_REG |= (1 << OC0A_PIN_NUMBER); - OC0B_PORT_REG &= ~(1 << OC0B_PIN_NUMBER); + //OC0B_PORT_REG &= ~(1 << OC0B_PIN_NUMBER); /* Configuring timer */ /* F_CPU = 16'000'000 Hz ; F_TIMER0 2'000'000 Hz*/ @@ -95,8 +86,8 @@ driver_setup_error_t driver_init_timer_0(void) config.timing_config.prescaler = TIMER8BIT_CLK_PRESCALER_8; config.timing_config.waveform_mode = TIMER8BIT_WG_PWM_FAST_FULL_RANGE; /* Invert OC0x behavior to provide a correct PWM for a push-pull driver pair */ - config.timing_config.comp_match_a = TIMER8BIT_CMOD_CLEAR_OCnX; - config.timing_config.comp_match_b = TIMER8BIT_CMOD_SET_OCnX; + config.timing_config.comp_mode_a = TIMER8BIT_CMOD_CLEAR_OCnX; + config.timing_config.comp_mode_b = TIMER8BIT_CMOD_SET_OCnX; /* Duty cycle : 39 % */ config.timing_config.ocra_val = 99; config.timing_config.ocrb_val = 99; @@ -123,20 +114,6 @@ driver_setup_error_t driver_init_timer_1(void) } } - /* Configuring handle */ - config.handle.OCRA_H = &OCR1AH; - config.handle.OCRA_L = &OCR1AL; - config.handle.OCRB_H = &OCR1BH; - config.handle.OCRB_L = &OCR1BL; - config.handle.TCCRA = &TCCR1A; - config.handle.TCCRB = &TCCR1B; - config.handle.TCCRC = &TCCR1C; - config.handle.TCNT_H = &TCNT1H; - config.handle.TCNT_L = &TCNT1L; - config.handle.TIFR = &TIFR1; - config.handle.TIMSK = &TIMSK1; - config.handle.ICR_H = &ICR1H; - config.handle.ICR_L = &ICR1L; /* Enable OC0A and OC0B as outputs */ /* PD6 = OC0A on arduino nano */ @@ -147,7 +124,7 @@ driver_setup_error_t driver_init_timer_1(void) /* Set original port state of OC0A/OC0B pins before any OCRx overrides them */ /* This is done in opposing polarities to respect mosfet push-pull driver pair starting point*/ OC1A_PORT_REG |= (1 << OC1A_PIN_NUMBER); // Pulled to Vcc (low side of mosfet driver pulls mosfet's gate to ground) - OC1B_PORT_REG &= ~(1 << OC1B_PIN_NUMBER); // Pulled to ground (high side of mosfet driver is depleted) + //OC1B_PORT_REG &= ~(1 << OC1B_PIN_NUMBER); // Pulled to ground (high side of mosfet driver is depleted) /* Configuring timer */ /* F_CPU = 16'000'000 Hz ; F_TIMER0 2'000'000 Hz*/ @@ -184,16 +161,6 @@ driver_setup_error_t driver_init_timer_2(void) } } - /* Configuring handle */ - config.handle.OCRA = &OCR2A; - config.handle.OCRB = &OCR2B; - config.handle.TCCRA = &TCCR2A; - config.handle.TCCRB = &TCCR2B; - config.handle.TCNT = &TCNT2; - config.handle.TIFR = &TIFR2; - config.handle.TIMSK = &TIMSK2; - config.handle.ASSR_REG = &ASSR; - /* Enable OC0A and OC0B as outputs */ OC2A_DDR_REG |= (1 << OC2A_PIN_NUMBER); OC2B_DDR_REG |= (1 << OC2B_PIN_NUMBER); @@ -201,7 +168,7 @@ driver_setup_error_t driver_init_timer_2(void) /* Set original port state of OC0A/OC0B pins before any OCRx overrides them */ /* This is done in opposing polarities to respect mosfet push-pull driver pair starting point*/ OC2A_PORT_REG |= (1 << OC2A_PIN_NUMBER); - OC2B_PORT_REG &= ~(1 << OC2B_PIN_NUMBER); + //OC2B_PORT_REG &= ~(1 << OC2B_PIN_NUMBER); /* Configuring timer */ /* F_CPU = 16'000'000 Hz ; F_TIMER0 2'000'000 Hz*/ diff --git a/App/src/main.c b/ExampleApp/src/main.c similarity index 99% rename from App/src/main.c rename to ExampleApp/src/main.c index 61673b1..2ddc603 100644 --- a/App/src/main.c +++ b/ExampleApp/src/main.c @@ -4,7 +4,7 @@ @ FreeMyCode version : 1.0 RC alpha Author : bebenlebricolo - License : + License : name : GPLv3 url : https://www.gnu.org/licenses/quick-guide-gplv3.html Date : 12/02/2021 @@ -28,6 +28,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ +#include "config.h" #include "adc.h" #include "timer_8_bit.h" #include "timer_8_bit_async.h" diff --git a/App/src/module_setup.c b/ExampleApp/src/module_setup.c similarity index 82% rename from App/src/module_setup.c rename to ExampleApp/src/module_setup.c index d54efaa..733c34d 100644 --- a/App/src/module_setup.c +++ b/ExampleApp/src/module_setup.c @@ -4,7 +4,7 @@ @ FreeMyCode version : 1.0 RC alpha Author : bebenlebricolo - License : + License : name : GPLv3 url : https://www.gnu.org/licenses/quick-guide-gplv3.html Date : 12/02/2021 @@ -33,12 +33,7 @@ along with this program. If not, see . module_setup_error_t module_init_timebase(void) { - timebase_config_t config = {0}; - config.cpu_freq = 16000000; - config.timer.index = 0; - config.timer.type = TIMEBASE_TIMER_8_BIT_ASYNC; - config.timescale = TIMEBASE_TIMESCALE_MILLISECONDS; - timebase_error_t err = timebase_init(0U, &config); + timebase_error_t err = timebase_init(0U); if (TIMEBASE_ERROR_OK != err) { return MODULE_SETUP_ERROR_INIT_FAILED; diff --git a/Modules/CMakeLists.txt b/Modules/CMakeLists.txt index 3aef0ab..730afd6 100644 --- a/Modules/CMakeLists.txt +++ b/Modules/CMakeLists.txt @@ -1,4 +1,5 @@ -cmake_minimum_required(VERSION 3.0) +cmake_minimum_required(VERSION 3.20) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Timebase) +add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Pwm) diff --git a/Modules/Pwm/CMakeLists.txt b/Modules/Pwm/CMakeLists.txt new file mode 100644 index 0000000..f48760d --- /dev/null +++ b/Modules/Pwm/CMakeLists.txt @@ -0,0 +1,42 @@ +cmake_minimum_required(VERSION 3.20) + +################################################################################################# +##################################### PWM driver library ######################################## +################################################################################################# + +project(pwm_driver C) + +set(SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/src/pwm.c +) + +set(HEADERS + ${CMAKE_CURRENT_SOURCE_DIR}/inc/pwm.h +) + +add_library(pwm_driver STATIC + ${SOURCES} + ${HEADERS} +) + +target_include_directories(pwm_driver PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/inc + ${AVR_INCLUDES} + ${CONFIG_FILE_DIR} +) + +target_link_libraries(pwm_driver + timer_8_bit_driver + timer_16_bit_driver + timer_8_bit_async_driver + timebase_module + io_driver +) + +################################################################################################# +################################## PWM driver example code ###################################### +################################################################################################# +if(BUILD_EXAMPLES) + message(STATUS "Configuring PWM driver examples project") + add_subdirectory(examples ${CMAKE_CURRENT_BINARY_DIR}/examples) +endif() \ No newline at end of file diff --git a/Modules/Pwm/Readme.md b/Modules/Pwm/Readme.md new file mode 100644 index 0000000..f4630f5 --- /dev/null +++ b/Modules/Pwm/Readme.md @@ -0,0 +1,118 @@ +# Index +- [Index](#index) +- [PWM driver](#pwm-driver) +- [Interaction with hardware timer peripherals](#interaction-with-hardware-timer-peripherals) + - [Limitations](#limitations) +- [Driving 8 bit timers](#driving-8-bit-timers) + - [Dual channel PWM configuration](#dual-channel-pwm-configuration) + - [Single channel, high speed PWM configuration](#single-channel-high-speed-pwm-configuration) +- [Driving 16 bits timers](#driving-16-bits-timers) +- [Basic configuration](#basic-configuration) + +# PWM driver +[Back to top](#index) + +This PWM driver builds upon the various timer driver found in the AvrAsyncCore SDK. +It basically provides a frontend which abstract some inner mechanisms of the timer themselves, trying to make its interface more generic and more +portable across the Avr family. + +# Interaction with hardware timer peripherals +[Back to top](#index) + +This PWM drivers **builds on top of the various Timer drivers and interfaces**. +It is able to **probe the current timer hardware configuration** by reading the registers and will try to accommodate a working pwm configuration to achieve both frequency and/or duty cycle if possible. Note that this is not a strong guarantee, because of some limitations found in hardware timer implementations. + +Amongst other things, this driver does not mess with the Waveform Generation bits other than pure reading. +However, it is able to modify the `COMxA/B` registers in order to select the PWM start polarity. + +## Limitations +[Back to top](#index) + +Because of hardware limitations, some use cases such as using a 8 bit timer to generate a PWM signal might result in a PWM which might be quite off compared to the desired PWM signal. This is mainly due to the lack of functionality when it comes to dual PWM configurations for a single hardware timer. + +Software is also limited in speed and refresh rate : it needs the main application to call the `process()` function at a high enough rate to cope with the software PWM requirements. In that regard, it is better to keep the software PWM frequency low enough, like at least ***10 times lower than the main `while loop` refresh rate.*** + +# Driving 8 bit timers +[Back to top](#index) + +Regular 8 bit timers and 8 bit asynchronous timers share a common peripheral interface, where one can choose how configure them as PWM units. +As per the other kinds of timers, they have two units, A and B, which are sharing a common counter register (usually TCNTx). + +> Note : as both 8 regular 8 bit timer and 8 bit asynchronous timer share a very similar architecture, following aspects and code snippets can apply to both of them. +> Their specific implementation can be found in their `timer_8_bit{...}_reg.h` header file which encodes all available enum types. + +Timer configuration is performed through the correct use of their `waveform generation` bits, located within the `TCCRxA` and `TCCRxB` registers. +However, one might note that the available `Waveform Generation` bits combinations state that the TOP value of the counters can either be `0xFF` or `OCRxA`. +Those Waveform Generation configurations are depicted in [timer_8_bit_reg.h](../../Drivers/Timers/Timer_8_bit/inc/timer_8_bit_reg.h) and [timer_8_bit__async_reg.h](../../ +Drivers/Timers/Timer_8_bit_async/inc/timer_8_bit_async.h) header files like so : + +```C +/** + * @brief Describes 8 bits timers waveform generation modes +*/ +typedef enum +{ /**< | Mode of operation | TOP | Update of OCRx at | TOV Flag set on |*/ + TIMER8BIT_WG_NORMAL = 0U, /**< | Normal operation mode | 0xFF | Immediate | MAX |*/ +/* --------------------------------------------------------------------------------------------------------------------------------------- */ + TIMER8BIT_WG_PWM_FAST_FULL_RANGE = 3U, /**< | Fast PWM | 0xFF | BOTTOM | MAX |*/ + TIMER8BIT_WG_PWM_FAST_OCRA_MAX = 7U, /**< | Fast PWM | OCRA | BOTTOM | MAX |*/ +/* --------------------------------------------------------------------------------------------------------------------------------------- */ + TIMER8BIT_WG_PWM_PHASE_CORRECT_FULL_RANGE = 1U, /**< | PWM, phase correct | 0xFF | TOP | BOTTOM |*/ + TIMER8BIT_WG_PWM_PHASE_CORRECT_OCRA_MAX = 5U, /**< | PWM, phase correct | OCRA | TOP | BOTTOM |*/ +/* --------------------------------------------------------------------------------------------------------------------------------------- */ + TIMER8BIT_WG_CTC = 2U, /**< | Clear Timer Compare match | OCRA | Immediate | MAX |*/ +} timer_8_bit_waveform_generation_t; +``` +So as we can see, both fast and phase correct PWM modes alternatively use `OCRA` and `0xFF` as TOP values (autoreload trigger) by the timer peripheral. +This implies that regarding PWM modes, we have essentially two available solutions in order to generate a pure hardware PWM signal : +* Dual channel configuration with almost no control over the frequency, and full control over duty_cyle +* Single channel, high speed PWM with full frequency and duty cycle control + +## Dual channel PWM configuration +[Back to top](#index) + +This configuration is achieved when either `TIMER8BIT_WG_PWM_FAST_FULL_RANGE` or `TIMER8BIT_WG_PWM_PHASE_CORRECT_FULL_RANGE` is used to configure a given timer. +As a result, the top value of the counter is always set to the maximum value of the 8 bit counter, which is `0xFF`. +Then, the only way to change the frequency of the output PWMs will be to change the prescaler used by the timer itself, or by varying the CPU frequency as the output frequency is governed by the following equation : + +$$\dfrac{F\_CPU}{Prescaler \times TOP}$$ + +Hence, for a CPU frequency of, say 16 MHz it gives the following table: +| Prescaler | 1 | 8 | 64 | 256 | 1024 | +|-----------------------|-------|------|-----|-----|------| +| output frequency (Hz) | 62500 | 7812 | 976 | 244 | 61 | + +So if we want a slow PWM, this is perfectly suitable and we can even setup a complementary output PWM with this kind of configuration, with dead-time generation and and the good stuff. + +For high speed PWM however, this is really limiting and the fact that we cannot choose an output frequency apart from the one listed above is really the main limiting factor. + +![](docs/Timer8bit_dual_channel.png) + +## Single channel, high speed PWM configuration +[Back to top](#index) + +This configuration sacrifices the unit A of the timer, as it its OCRA value is used by the timer to determine the `TOP` value. +It is achieved through the use of `TIMER8BIT_WG_PWM_FAST_OCRA_MAX` or `TIMER8BIT_WG_PWM_PHASE_CORRECT_OCRA_MAX` and correctly handling `COMAxx` and `COMBxx` registers. +Hence, the B unit will benefit from the `OCRA` value in order to pinpoint the closest frequency we want to work with, and the `OCRB` value will then be used to set the `duty_cycle` of the B unit. + +At best, the A PWM channel can output a 50% duty_cycle PWM with half the frequency of B unit's if setup as a pin toggle event (`TIMER8BIT_CMOD_TOGGLE_OCnX`). +* Using `TIMER8BIT_CMOD_CLEAR_OCnX` will pull the output pin up to VCC permanently (pin is set to HIGH when timer reaches the BOTTOM value = 0) +* Using `TIMER8BIT_CMOD_SET_OCnX` will pull the output pin down to GND permanently (pin is cleared when timer reaches the BOTTOM value = 0) + +![](docs/Timer8bit_single_channel.png) + +# Driving 16 bits timers +[Back to top](#index) + +16 bit timers are a bit more full-featured and allow the generation of accurate, complementary PWM signals out of a single timer. +Still, both channels share the same base prescaler, counter and `TOP` value, hence the same output frequency. +However, we can use the `ICR` register in order to provide an external value to the counter which will serve as the new `TOP` value, as well as reducing the counter's resolution using the appropriate WG mode. + +# Basic configuration +This driver uses a static configuration variable which needs to be exposed somewhere in the application code. +This is usually done by implementing the external symbol `pwm_config` in a `config.c` file, compiled by the firmware. +Both Hardware and Software PWMs configurations are mixed in this array, so one might need to retain the indices of each PWM instance in order to access them later on when using the PWM driver. + +```C + +``` \ No newline at end of file diff --git a/Modules/Pwm/Tests/CMakeLists.txt b/Modules/Pwm/Tests/CMakeLists.txt new file mode 100644 index 0000000..0975c21 --- /dev/null +++ b/Modules/Pwm/Tests/CMakeLists.txt @@ -0,0 +1,65 @@ +cmake_minimum_required(VERSION 3.20) + +project(pwm_module_tests C CXX) + +######### Compile tested modules as individual libraries ######### + +if(WIN32) + set(THREAD_LIB "") +else() + set(THREAD_LIB pthread) +endif() + +### timebase_module library ### +add_library(pwm_module STATIC + ${CMAKE_CURRENT_SOURCE_DIR}/../src/pwm.c +) +target_include_directories(pwm_module PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/inc + ${CMAKE_CURRENT_SOURCE_DIR}/../inc + ${CMAKE_CURRENT_SOURCE_DIR}/../../../Drivers/Timers/Timer_generic/inc + ${CMAKE_CURRENT_SOURCE_DIR}/../../../Drivers/Timers/Timer_8_bit/inc + ${CMAKE_CURRENT_SOURCE_DIR}/../../../Drivers/Timers/Timer_8_bit_async/inc + ${CMAKE_CURRENT_SOURCE_DIR}/../../../Drivers/Timers/Timer_16_bit/inc + ${CMAKE_CURRENT_SOURCE_DIR}/../../../Drivers/Io/inc + ${CMAKE_CURRENT_SOURCE_DIR}/../../Timebase/inc + ${CMAKE_CURRENT_SOURCE_DIR}/Stubs +) + +########## Timebase module tests ########## + +add_executable(pwm_module_tests + ${CMAKE_CURRENT_SOURCE_DIR}/pwm_tests.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/Stubs/timer_8_bit_async_stub.c + ${CMAKE_CURRENT_SOURCE_DIR}/Stubs/timer_8_bit_stub.c + ${CMAKE_CURRENT_SOURCE_DIR}/Stubs/timer_16_bit_stub.c + ${CMAKE_CURRENT_SOURCE_DIR}/Stubs/config.c + ${CMAKE_CURRENT_SOURCE_DIR}/Stubs/io_stub.c + ${CMAKE_CURRENT_SOURCE_DIR}/Stubs/timebase_stub.c +) + +target_include_directories(pwm_module_tests PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/inc + ${CMAKE_CURRENT_SOURCE_DIR}/Stubs + ${CMAKE_CURRENT_SOURCE_DIR}/../inc + ${CMAKE_CURRENT_SOURCE_DIR}/../../../Drivers/Timers/Timer_generic/inc + ${CMAKE_CURRENT_SOURCE_DIR}/../../../Drivers/Timers/Timer_8_bit/inc + ${CMAKE_CURRENT_SOURCE_DIR}/../../../Drivers/Timers/Timer_8_bit_async/inc + ${CMAKE_CURRENT_SOURCE_DIR}/../../../Drivers/Timers/Timer_16_bit/inc +) + +target_include_directories(pwm_module_tests SYSTEM PUBLIC + ${GTEST_INCLUDE_DIRS} +) + +target_link_libraries(pwm_module_tests + pwm_module + timer_generic_driver + ${GTEST_LIBRARIES} + ${THREAD_LIB} +) + +set_target_properties(pwm_module_tests + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/Modules/Pwm +) \ No newline at end of file diff --git a/Modules/Pwm/Tests/Stubs/config.c b/Modules/Pwm/Tests/Stubs/config.c new file mode 100644 index 0000000..c028903 --- /dev/null +++ b/Modules/Pwm/Tests/Stubs/config.c @@ -0,0 +1,32 @@ +#include "config.h" + +#include "pwm.h" + +pwm_static_config_t pwm_config = +{ + .soft = + { + // Fans usually require slow PWMs and can easily be handled by software PWM module + // so that we don't waste hardware PWM channels. + // As a result, it requires the IO index as per written in IO driver static configuration + // and the timebase used by this program, so we can select different timebases if necessary + [FAN_SOFT_PWM_INDEX] = + { + .io_index = FAN_IO_INDEX, + .timebase_index = FAN_TIMEBASE_INDEX + } + }, + .hard = + { + // Motors usually require fast PWMs signals in order to fully take advantage of + // their inductances's current smoothing capabilities. + // Also, a good timing might be observed in order to keep the motor in sync, so we need a predictable/repeatable + // PWM signal generation scheme, which is achievable through the use of a hardware PWM channel. + [MOTOR_HARD_PWM_INDEX] = + { + .arch = TIMER_ARCH_8_BIT, + .timer_index = 0U, + .unit = PWM_HARD_TIMER_UNIT_A + } + } +}; \ No newline at end of file diff --git a/Modules/Pwm/Tests/Stubs/config.h b/Modules/Pwm/Tests/Stubs/config.h new file mode 100644 index 0000000..c2c1006 --- /dev/null +++ b/Modules/Pwm/Tests/Stubs/config.h @@ -0,0 +1,28 @@ +#ifndef CONFIG_STUB_HEADER +#define CONFIG_STUB_HEADER + +#ifdef __cplusplus +extern "C" +{ +#endif + +#define PWM_MAX_HARD_INSTANCES 1U +#define PWM_MAX_SOFT_INSTANCES 1U +#define TIMEBASE_MAX_MODULES 1U +#define TIMER_8_BIT_COUNT 1U +#define TIMER_8_BIT_ASYNC_COUNT 1U +#define TIMER_16_BIT_COUNT 1U +#define IO_MAX_PINS 28U + + +// Mapping of PWM signals as per configured in pwm_config, in config.c file +#define MOTOR_HARD_PWM_INDEX 0U +#define FAN_SOFT_PWM_INDEX 0U +#define FAN_IO_INDEX 0U /**< Gives the index of the IO pin in IO driver static configuration */ +#define FAN_TIMEBASE_INDEX 0U /**< Gives the index of the reference timebase as per written in timebase static configuration */ + +#ifdef __cplusplus +} +#endif + +#endif /* CONFIG_STUB_HEADER */ \ No newline at end of file diff --git a/Modules/Pwm/Tests/Stubs/io_stub.c b/Modules/Pwm/Tests/Stubs/io_stub.c new file mode 100644 index 0000000..1846bab --- /dev/null +++ b/Modules/Pwm/Tests/Stubs/io_stub.c @@ -0,0 +1,10 @@ +#include "io.h" +#include "io_stub.h" + +io_error_t io_write(const uint8_t index, const io_state_t state) +{ + (void) index; + (void) state; + + return IO_ERROR_OK; +} diff --git a/Modules/Pwm/Tests/Stubs/io_stub.h b/Modules/Pwm/Tests/Stubs/io_stub.h new file mode 100644 index 0000000..837ac5c --- /dev/null +++ b/Modules/Pwm/Tests/Stubs/io_stub.h @@ -0,0 +1,14 @@ +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + + +extern bool io_stub_initialised; + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/Modules/Pwm/Tests/Stubs/timebase_stub.c b/Modules/Pwm/Tests/Stubs/timebase_stub.c new file mode 100644 index 0000000..0fcdf24 --- /dev/null +++ b/Modules/Pwm/Tests/Stubs/timebase_stub.c @@ -0,0 +1,39 @@ +#include "timebase.h" +#include "timebase_stub.h" + +bool timebase_stub_initialised = false; +static uint16_t next_duration = 0; + +timebase_error_t timebase_get_tick(const uint8_t id, uint16_t * const tick) +{ + (void) id; + (void) tick; + return TIMEBASE_ERROR_OK; +} + +timebase_error_t timebase_is_initialised(const uint8_t id, bool * const initialised) +{ + (void) id; + *initialised = timebase_stub_initialised; + return TIMEBASE_ERROR_OK; +} + +timebase_error_t timebase_get_duration(uint16_t const * const reference, uint16_t const * const new_tick, uint16_t * const duration) +{ + (void) reference; + (void) new_tick; + (void) duration; + + return TIMEBASE_ERROR_OK; +} + +void timebase_stub_set_duration(const uint16_t duration) +{ + next_duration = duration; +} + +void timebase_stub_reset(void) +{ + next_duration = 0; + timebase_stub_initialised = false; +} \ No newline at end of file diff --git a/Modules/Pwm/Tests/Stubs/timebase_stub.h b/Modules/Pwm/Tests/Stubs/timebase_stub.h new file mode 100644 index 0000000..44f70e6 --- /dev/null +++ b/Modules/Pwm/Tests/Stubs/timebase_stub.h @@ -0,0 +1,17 @@ +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + + +void timebase_stub_set_next_duration(const uint16_t duration); +void timebase_stub_reset(void); + +extern bool timebase_stub_initialised; + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/Modules/Pwm/Tests/Stubs/timer_16_bit_stub.c b/Modules/Pwm/Tests/Stubs/timer_16_bit_stub.c new file mode 100644 index 0000000..0d8b278 --- /dev/null +++ b/Modules/Pwm/Tests/Stubs/timer_16_bit_stub.c @@ -0,0 +1,522 @@ +/* + +------------------ +@ +FreeMyCode version : 1.0 RC alpha + Author : bebenlebricolo + License : + name : GPLv3 + url : https://www.gnu.org/licenses/quick-guide-gplv3.html + Date : 12/02/2021 + Project : LabBenchPowerSupply + Description : The Lab Bench Power Supply provides a simple design based around an Arduino Nano board to convert AC main voltage into + smaller ones, ranging from 0V to 16V, with voltage and current regulations +@ +------------------ + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "timer_16_bit_stub.h" +#include "string.h" + +static timer_16_bit_stub_configuration_t configuration = {0}; +static timer_error_t next_error = TIMER_ERROR_OK; + +static inline bool id_is_valid(const uint8_t id) +{ + return (id < TIMER_16_BIT_STUB_MAX_INSTANCES); +} + +timer_16_bit_stub_configuration_t* timer_16_bit_stub_get_config(void) +{ + return &configuration; +} + +void timer_16_bit_stub_set_next_parameters(const timer_16_bit_prescaler_selection_t prescaler, const uint16_t ocra, const uint32_t accumulator) +{ + configuration.prescaler = prescaler; + configuration.ocra = ocra; + configuration.accumulator = accumulator; +} + +void timer_16_bit_stub_set_initialised(const bool initialised) +{ + configuration.initialised = initialised; +} + +void timer_16_bit_stub_reset(void) +{ + memset(&configuration, 0, sizeof(timer_16_bit_stub_configuration_t)); +} + + +const timer_generic_prescaler_pair_t timer_16_bit_prescaler_table[TIMER_16_BIT_MAX_PRESCALER_COUNT] = +{ + {.value = 1, .type = (uint8_t) TIMER16BIT_CLK_PRESCALER_1 }, + {.value = 8, .type = (uint8_t) TIMER16BIT_CLK_PRESCALER_8 }, + {.value = 64, .type = (uint8_t) TIMER16BIT_CLK_PRESCALER_64 }, + {.value = 256, .type = (uint8_t) TIMER16BIT_CLK_PRESCALER_256 }, + {.value = 1024, .type = (uint8_t) TIMER16BIT_CLK_PRESCALER_1024 }, +}; + +timer_error_t timer_16_bit_compute_matching_parameters(const uint32_t * const clock_freq, + const uint32_t * const target_freq, + const timer_generic_resolution_t resolution, + timer_16_bit_prescaler_selection_t * const prescaler, + uint16_t * const ocr, + uint16_t * const accumulator) +{ + timer_generic_parameters_t parameters = + { + .input = + { + .clock_freq = *clock_freq, + .target_frequency = *target_freq, + .resolution = resolution, + .prescaler_lookup_array.array = timer_16_bit_prescaler_table, + .prescaler_lookup_array.size = TIMER_16_BIT_MAX_PRESCALER_COUNT, + }, + }; + + if( TIMER_ERROR_OK != timer_generic_compute_parameters(¶meters)) + { + return TIMER_ERROR_CONFIG; + } + + *prescaler = timer_16_bit_prescaler_from_value(¶meters.output.prescaler); + *ocr = parameters.output.ocr; + *accumulator = parameters.output.accumulator; + + return next_error; +} + +timer_error_t timer_16_bit_compute_closest_prescaler(const uint32_t * const clock_freq, + const uint32_t * const target_freq, + const timer_generic_resolution_t resolution, + timer_16_bit_prescaler_selection_t * const prescaler) +{ + timer_generic_parameters_t parameters = + { + .input = + { + .clock_freq = *clock_freq, + .target_frequency = *target_freq, + .resolution = resolution, + .prescaler_lookup_array.array = timer_16_bit_prescaler_table, + .prescaler_lookup_array.size = TIMER_16_BIT_MAX_PRESCALER_COUNT, + }, + }; + + if(TIMER_ERROR_OK != timer_generic_find_closest_prescaler(¶meters)) + { + return TIMER_ERROR_CONFIG; + } + + *prescaler = timer_16_bit_prescaler_from_value(¶meters.output.prescaler); + + return next_error; +} + +timer_16_bit_prescaler_selection_t timer_16_bit_prescaler_from_value(uint16_t const * const input_prescaler) +{ + for (uint8_t i = 0 ; i < TIMER_16_BIT_MAX_PRESCALER_COUNT ; i++) + { + if (*input_prescaler == timer_16_bit_prescaler_table[i].value) + { + return (timer_16_bit_prescaler_selection_t) timer_16_bit_prescaler_table[i].type; + } + } + return TIMER16BIT_CLK_NO_CLOCK; +} + +uint16_t timer_16_bit_prescaler_to_value(const timer_16_bit_prescaler_selection_t prescaler) +{ + for (uint8_t i = 0 ; i < TIMER_16_BIT_MAX_PRESCALER_COUNT ; i++) + { + if (prescaler == timer_16_bit_prescaler_table[i].type) + { + return timer_16_bit_prescaler_table[i].value; + } + } + return 0; +} + +timer_error_t timer_16_bit_set_icr_register_value(uint8_t id, const uint16_t icr) +{ + (void) id; + configuration.icr = icr; + return next_error; +} + + +timer_error_t timer_16_bit_get_default_config(timer_16_bit_config_t * config) +{ + timer_error_t ret = TIMER_ERROR_OK; + if (TIMER_ERROR_OK != ret) + { + return ret; + } + + if (NULL == config) + { + return TIMER_ERROR_NULL_POINTER; + } + + /* Resets everything */ + config->interrupt_config.it_comp_match_a = false; + config->interrupt_config.it_comp_match_b = false; + config->interrupt_config.it_timer_overflow = false; + config->interrupt_config.it_input_capture = false ; + + config->timing_config.counter = 0U; + config->timing_config.ocra_val = 0U; + config->timing_config.ocrb_val = 0U; + config->timing_config.prescaler = TIMER16BIT_CLK_NO_CLOCK; + config->timing_config.waveform_mode = TIMER16BIT_WG_NORMAL; + config->timing_config.comp_match_a = TIMER16BIT_CMOD_NORMAL; + config->timing_config.comp_match_b = TIMER16BIT_CMOD_NORMAL; + + config->force_compare.force_comp_match_a = false; + config->force_compare.force_comp_match_b = false; + config->input_capture.edge_select = TIMER16BIT_INPUT_CAPTURE_EDGE_FALLING_EDGE; + config->input_capture.use_noise_canceler = false; + + return ret; +} + +timer_error_t timer_16_bit_set_handle(uint8_t id, timer_16_bit_handle_t * const handle) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + (void) handle; + return next_error; +} + +timer_error_t timer_16_bit_get_handle(uint8_t id, timer_16_bit_handle_t * const handle) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + (void) handle; + return next_error; +} + +timer_error_t timer_16_bit_set_force_compare_config(uint8_t id, timer_16_bit_force_compare_config_t * const force_comp_config) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + configuration.force_comp = *force_comp_config; + return next_error; +} + +timer_error_t timer_16_bit_get_force_compare_config(uint8_t id, timer_16_bit_force_compare_config_t * force_comp_config) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + *force_comp_config = configuration.force_comp; + return next_error; +} + +timer_error_t timer_16_bit_set_interrupt_config(uint8_t id, timer_16_bit_interrupt_config_t * const it_config) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + configuration.it_config = *it_config; + return next_error; +} + +timer_error_t timer_16_bit_get_interrupt_config(uint8_t id, timer_16_bit_interrupt_config_t * it_config) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + *it_config = configuration.it_config; + return next_error; +} + +#ifdef UNIT_TESTING +timer_error_t timer_16_bit_get_interrupt_flags(uint8_t id, timer_16_bit_interrupt_config_t * it_flags) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + *it_flags = configuration.it_config; + return next_error; +} +#endif + +timer_error_t timer_16_bit_set_prescaler(uint8_t id, const timer_16_bit_prescaler_selection_t prescaler) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + configuration.prescaler = prescaler; + return next_error; +} + +timer_error_t timer_16_bit_get_prescaler(uint8_t id, timer_16_bit_prescaler_selection_t * prescaler) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + *prescaler = configuration.prescaler; + return next_error; +} + +timer_error_t timer_16_bit_set_compare_match_A(uint8_t id, const timer_16_bit_compare_output_mode_t compA) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + configuration.compA = compA; + return next_error; +} + +timer_error_t timer_16_bit_get_compare_match_A(uint8_t id, timer_16_bit_compare_output_mode_t * compA) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + *compA = configuration.compA; + return next_error; +} + + +timer_error_t timer_16_bit_set_input_compare_noise_canceler(uint8_t id, const bool enabled) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + configuration.noise_canceler_config.use_noise_canceler = enabled; + return next_error; +} + +timer_error_t timer_16_bit_get_input_compare_noise_canceler(uint8_t id, bool * const enabled) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + *enabled = configuration.noise_canceler_config.use_noise_canceler; + return next_error; +} + +timer_error_t timer_16_bit_set_input_compare_edge_select(uint8_t id, const timer_16_bit_input_capture_edge_select_flag_t edge) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + configuration.noise_canceler_config.edge_select = edge; + return next_error; +} + +timer_error_t timer_16_bit_get_input_compare_edge_select(uint8_t id, timer_16_bit_input_capture_edge_select_flag_t * const edge) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + *edge = configuration.noise_canceler_config.edge_select; + return next_error; +} + +timer_error_t timer_16_bit_get_input_capture_value(uint8_t id, uint16_t * ticks) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + *ticks = configuration.icr; + return next_error; +} + +timer_error_t timer_16_bit_set_compare_match_B(uint8_t id, timer_16_bit_compare_output_mode_t compB) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + configuration.compB = compB; + return next_error; +} + +timer_error_t timer_16_bit_get_compare_match_B(uint8_t id, timer_16_bit_compare_output_mode_t * compB) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + *compB = configuration.compB; + return next_error; +} + +timer_error_t timer_16_bit_set_waveform_generation(uint8_t id, const timer_16_bit_waveform_generation_t waveform) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + configuration.waveform = waveform; + return next_error; +} + +timer_error_t timer_16_bit_get_waveform_generation(uint8_t id, timer_16_bit_waveform_generation_t * waveform) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + *waveform = configuration.waveform; + return next_error; +} + +timer_error_t timer_16_bit_set_counter_value(uint8_t id, const uint16_t * const ticks) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + configuration.counter = *ticks; + return next_error; +} + +timer_error_t timer_16_bit_get_counter_value(uint8_t id, uint16_t * const ticks) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + *ticks = configuration.counter; + return next_error; +} + +timer_error_t timer_16_bit_set_ocra_register_value(uint8_t id, const uint16_t * const ocra) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + configuration.ocra = *ocra; + return next_error; +} + +timer_error_t timer_16_bit_get_ocra_register_value(uint8_t id, uint16_t * const ocra) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + *ocra = configuration.ocra; + return next_error; +} + +timer_error_t timer_16_bit_set_ocrb_register_value(uint8_t id, const uint16_t * const ocrb) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + configuration.ocrb = *ocrb; + return next_error; +} + +timer_error_t timer_16_bit_get_ocrb_register_value(uint8_t id, uint16_t * const ocrb) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + *ocrb = configuration.ocrb; + return next_error; +} + +timer_error_t timer_16_bit_init(uint8_t id, timer_16_bit_config_t * const config) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + (void) config; + timer_16_bit_stub_reset(); + + return next_error; +} + +timer_error_t timer_16_bit_reconfigure(uint8_t id, timer_16_bit_config_t * const config) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + (void) config; + return next_error; +} + +timer_error_t timer_16_bit_deinit(uint8_t id) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + timer_16_bit_stub_reset(); + return next_error; +} + +timer_error_t timer_16_bit_start(uint8_t id) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + configuration.started = true; + return next_error; +} + +timer_error_t timer_16_bit_stop(uint8_t id) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + configuration.started = false; + return next_error; +} + +timer_error_t timer_16_bit_is_initialised(const uint8_t id, bool * const initialised) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + *initialised = configuration.initialised; + return next_error; +} \ No newline at end of file diff --git a/Modules/Pwm/Tests/Stubs/timer_16_bit_stub.h b/Modules/Pwm/Tests/Stubs/timer_16_bit_stub.h new file mode 100644 index 0000000..e090415 --- /dev/null +++ b/Modules/Pwm/Tests/Stubs/timer_16_bit_stub.h @@ -0,0 +1,71 @@ +/* + +------------------ +@ +FreeMyCode version : 1.0 RC alpha + Author : bebenlebricolo + License : + name : GPLv3 + url : https://www.gnu.org/licenses/quick-guide-gplv3.html + Date : 12/02/2021 + Project : LabBenchPowerSupply + Description : The Lab Bench Power Supply provides a simple design based around an Arduino Nano board to convert AC main voltage into + smaller ones, ranging from 0V to 16V, with voltage and current regulations +@ +------------------ + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#ifndef TIMER_16_BIT_STUB_HEADER +#define TIMER_16_BIT_STUB_HEADER + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include "timer_16_bit.h" +#define TIMER_16_BIT_STUB_MAX_INSTANCES (1U) + +typedef struct +{ + bool initialised; + bool started; + uint16_t counter; + uint16_t ocra; + uint16_t ocrb; + uint16_t icr; + uint32_t accumulator; + timer_16_bit_prescaler_selection_t prescaler; + timer_16_bit_config_t driver_config; + timer_16_bit_waveform_generation_t waveform; + timer_16_bit_compare_output_mode_t compA; + timer_16_bit_compare_output_mode_t compB; + timer_16_bit_force_compare_config_t force_comp; + timer_16_bit_interrupt_config_t it_config; + timer_16_bit_input_capture_noise_canceler_config_t noise_canceler_config; +} timer_16_bit_stub_configuration_t; + +void timer_16_bit_stub_set_next_parameters(const timer_16_bit_prescaler_selection_t prescaler, const uint16_t ocra, const uint32_t accumulator); +void timer_16_bit_stub_set_initialised(const bool initialised); +void timer_16_bit_stub_reset(void); +timer_16_bit_stub_configuration_t* timer_16_bit_stub_get_config(void); + +#ifdef __cplusplus +} +#endif + + +#endif /* TIMER_16_BIT_STUB_HEADER */ \ No newline at end of file diff --git a/Modules/Pwm/Tests/Stubs/timer_8_bit_async_stub.c b/Modules/Pwm/Tests/Stubs/timer_8_bit_async_stub.c new file mode 100644 index 0000000..5acc1a0 --- /dev/null +++ b/Modules/Pwm/Tests/Stubs/timer_8_bit_async_stub.c @@ -0,0 +1,439 @@ +/* + +------------------ +@ +FreeMyCode version : 1.0 RC alpha + Author : bebenlebricolo + License : + name : GPLv3 + url : https://www.gnu.org/licenses/quick-guide-gplv3.html + Date : 12/02/2021 + Project : LabBenchPowerSupply + Description : The Lab Bench Power Supply provides a simple design based around an Arduino Nano board to convert AC main voltage into + smaller ones, ranging from 0V to 16V, with voltage and current regulations +@ +------------------ + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "timer_8_bit_async_stub.h" +#include "string.h" + +static timer_8_bit_async_stub_configuration_t configuration = {0}; +static timer_error_t next_error = TIMER_ERROR_OK; + +static inline bool id_is_valid(const uint8_t id) +{ + return (id < TIMER_8_BIT_ASYNC_STUB_MAX_INSTANCES); +} + +timer_8_bit_async_stub_configuration_t* timer_8_bit_async_stub_get_config(void) +{ + return &configuration; +} + +void timer_8_bit_async_stub_set_next_parameters(const timer_8_bit_async_prescaler_selection_t prescaler, const uint8_t ocra, const uint32_t accumulator) +{ + configuration.prescaler = prescaler; + configuration.ocra = ocra; + configuration.accumulator = accumulator; +} + +void timer_8_bit_async_stub_set_initialised(const bool initialised) +{ + configuration.initialised = initialised; +} + +void timer_8_bit_async_stub_reset(void) +{ + memset(&configuration, 0, sizeof(timer_8_bit_async_stub_configuration_t)); +} + +timer_error_t timer_8_bit_async_compute_matching_parameters( const uint32_t * const clock_freq, + const uint32_t * const target_freq, + timer_8_bit_async_prescaler_selection_t * const prescaler, + uint8_t * const ocra, + uint16_t * const accumulator) +{ + (void) clock_freq; + (void) target_freq; + *prescaler = configuration.prescaler; + *ocra = configuration.ocra; + *accumulator = configuration.accumulator; + return TIMER_ERROR_OK; +} + +const timer_generic_prescaler_pair_t timer_8_bit_async_prescaler_table[TIMER_8_BIT_ASYNC_MAX_PRESCALER_COUNT] = +{ + {.value = 1, .type = (uint8_t) TIMER8BIT_ASYNC_CLK_PRESCALER_1 }, + {.value = 8, .type = (uint8_t) TIMER8BIT_ASYNC_CLK_PRESCALER_8 }, + {.value = 32, .type = (uint8_t) TIMER8BIT_ASYNC_CLK_PRESCALER_32 }, + {.value = 64, .type = (uint8_t) TIMER8BIT_ASYNC_CLK_PRESCALER_64 }, + {.value = 128, .type = (uint8_t) TIMER8BIT_ASYNC_CLK_PRESCALER_128 }, + {.value = 256, .type = (uint8_t) TIMER8BIT_ASYNC_CLK_PRESCALER_256 }, + {.value = 1024, .type = (uint8_t) TIMER8BIT_ASYNC_CLK_PRESCALER_1024 }, +}; + +timer_error_t timer_8_bit_async_compute_closest_prescaler(const uint32_t * const clock_freq, + const uint32_t * const target_freq, + timer_8_bit_async_prescaler_selection_t * const prescaler) +{ + timer_error_t ret = TIMER_ERROR_OK; + timer_generic_parameters_t parameters = + { + .input = + { + .clock_freq = *clock_freq, + .target_frequency = *target_freq, + .resolution = TIMER_GENERIC_RESOLUTION_8_BIT, + .prescaler_lookup_array.array = timer_8_bit_async_prescaler_table, + .prescaler_lookup_array.size = TIMER_8_BIT_ASYNC_MAX_PRESCALER_COUNT, + }, + }; + ret = timer_generic_find_closest_prescaler(¶meters); + *prescaler = parameters.output.prescaler; + return ret; +} + + +timer_8_bit_async_prescaler_selection_t timer_8_bit_async_prescaler_from_value(uint16_t const * const input_prescaler) +{ + for (uint8_t i = 0 ; i < TIMER_8_BIT_ASYNC_MAX_PRESCALER_COUNT ; i++) + { + if (*input_prescaler == timer_8_bit_async_prescaler_table[i].value) + { + return (timer_8_bit_async_prescaler_selection_t) timer_8_bit_async_prescaler_table[i].type; + } + } + return TIMER8BIT_ASYNC_CLK_NO_CLOCK; +} + +uint16_t timer_8_bit_async_prescaler_to_value(const timer_8_bit_async_prescaler_selection_t prescaler) +{ + for (uint8_t i = 0 ; i < TIMER_8_BIT_ASYNC_MAX_PRESCALER_COUNT ; i++) + { + if (prescaler == timer_8_bit_async_prescaler_table[i].type) + { + return timer_8_bit_async_prescaler_table[i].value; + } + } + return 0; +} + +timer_error_t timer_8_bit_async_get_default_config(timer_8_bit_async_config_t * config) +{ + timer_error_t ret = TIMER_ERROR_OK; + if (TIMER_ERROR_OK != ret) + { + return ret; + } + + if (NULL == config) + { + return TIMER_ERROR_NULL_POINTER; + } + + /* Resets everything */ + config->interrupt_config.it_comp_match_a = false; + config->interrupt_config.it_comp_match_b = false; + config->interrupt_config.it_timer_overflow = false; + + config->timing_config.counter = 0U; + config->timing_config.ocra_val = 0U; + config->timing_config.ocrb_val = 0U; + config->timing_config.prescaler = TIMER8BIT_ASYNC_CLK_NO_CLOCK; + config->timing_config.waveform_mode = TIMER8BIT_ASYNC_WG_NORMAL; + config->timing_config.comp_match_a = TIMER8BIT_ASYNC_CMOD_NORMAL; + config->timing_config.comp_match_b = TIMER8BIT_ASYNC_CMOD_NORMAL; + config->timing_config.clock_source = TIMER8BIT_ASYNC_CLK_SOURCE_INTERNAL; + + config->force_compare.force_comp_match_a = false; + config->force_compare.force_comp_match_b = false; + + return next_error; +} + +timer_error_t timer_8_bit_async_set_handle(uint8_t id, timer_8_bit_async_handle_t * const handle) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + (void) handle; + return next_error; +} + +timer_error_t timer_8_bit_async_get_handle(uint8_t id, timer_8_bit_async_handle_t * const handle) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + (void) handle; + return next_error; +} + +timer_error_t timer_8_bit_async_set_force_compare_config(uint8_t id, timer_8_bit_async_force_compare_config_t * const force_comp_config) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + configuration.force_comp = *force_comp_config; + return next_error; +} + +timer_error_t timer_8_bit_async_get_force_compare_config(uint8_t id, timer_8_bit_async_force_compare_config_t * force_comp_config) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + *force_comp_config = configuration.force_comp; + return next_error; +} + +timer_error_t timer_8_bit_async_set_interrupt_config(uint8_t id, timer_8_bit_async_interrupt_config_t * const it_config) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + configuration.it_config = *it_config; + return next_error; +} + +timer_error_t timer_8_bit_async_get_interrupt_config(uint8_t id, timer_8_bit_async_interrupt_config_t * it_config) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + *it_config = configuration.it_config; + return next_error; +} + +#ifdef UNIT_TESTING +timer_error_t timer_8_bit_async_get_interrupt_flags(uint8_t id, timer_8_bit_async_interrupt_config_t * it_flags) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + *it_flags = configuration.it_config; + return next_error; +} +#endif + +timer_error_t timer_8_bit_async_set_prescaler(uint8_t id, const timer_8_bit_async_prescaler_selection_t prescaler) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + configuration.prescaler = prescaler; + return next_error; +} + +timer_error_t timer_8_bit_async_get_prescaler(uint8_t id, timer_8_bit_async_prescaler_selection_t * prescaler) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + *prescaler = configuration.prescaler; + return next_error; +} + +timer_error_t timer_8_bit_async_set_compare_match_A(uint8_t id, const timer_8_bit_async_compare_output_mode_t compA) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + configuration.compA = compA; + return next_error; +} + +timer_error_t timer_8_bit_async_get_compare_match_A(uint8_t id, timer_8_bit_async_compare_output_mode_t * compA) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + *compA = configuration.compA; + return next_error; +} + +timer_error_t timer_8_bit_async_set_compare_match_B(uint8_t id, timer_8_bit_async_compare_output_mode_t compB) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + configuration.compB = compB; + return next_error; +} + +timer_error_t timer_8_bit_async_get_compare_match_B(uint8_t id, timer_8_bit_async_compare_output_mode_t * compB) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + *compB = configuration.compB; + return next_error; +} + +timer_error_t timer_8_bit_async_set_waveform_generation(uint8_t id, const timer_8_bit_async_waveform_generation_t waveform) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + configuration.waveform = waveform; + return next_error; +} + +timer_error_t timer_8_bit_async_get_waveform_generation(uint8_t id, timer_8_bit_async_waveform_generation_t * const waveform) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + *waveform = configuration.waveform; + return next_error; +} + +timer_error_t timer_8_bit_async_set_counter_value(uint8_t id, const uint8_t ticks) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + configuration.counter = ticks; + return next_error; +} + +timer_error_t timer_8_bit_async_get_counter_value(uint8_t id, uint8_t * ticks) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + *ticks = configuration.counter; + return next_error; +} + +timer_error_t timer_8_bit_async_set_ocra_register_value(uint8_t id, uint8_t ocra) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + configuration.ocra = ocra; + return next_error; +} + +timer_error_t timer_8_bit_async_get_ocra_register_value(uint8_t id, uint8_t * ocra) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + *ocra = configuration.ocra; + return next_error; +} + +timer_error_t timer_8_bit_async_set_ocrb_register_value(uint8_t id, uint8_t ocrb) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + configuration.ocrb = ocrb; + return next_error; +} + +timer_error_t timer_8_bit_async_get_ocrb_register_value(uint8_t id, uint8_t * ocrb) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + *ocrb = configuration.ocrb; + return next_error; +} + +timer_error_t timer_8_bit_async_init(uint8_t id, timer_8_bit_async_config_t * const config) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + timer_8_bit_async_stub_reset(); + (void) config; + return next_error; +} + +timer_error_t timer_8_bit_async_reconfigure(uint8_t id, timer_8_bit_async_config_t * const config) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + (void) config; + return next_error; +} + +timer_error_t timer_8_bit_async_deinit(uint8_t id) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + timer_8_bit_async_stub_reset(); + return next_error; +} + +timer_error_t timer_8_bit_async_start(uint8_t id) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + configuration.started = true; + return next_error; +} + +timer_error_t timer_8_bit_async_stop(uint8_t id) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + configuration.started = false; + return next_error; +} + +timer_error_t timer_8_bit_async_is_initialised(const uint8_t id, bool * const initialised) +{ + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + }; + *initialised = configuration.initialised; + return next_error; +} \ No newline at end of file diff --git a/Modules/Pwm/Tests/Stubs/timer_8_bit_async_stub.h b/Modules/Pwm/Tests/Stubs/timer_8_bit_async_stub.h new file mode 100644 index 0000000..514d55e --- /dev/null +++ b/Modules/Pwm/Tests/Stubs/timer_8_bit_async_stub.h @@ -0,0 +1,69 @@ +/* + +------------------ +@ +FreeMyCode version : 1.0 RC alpha + Author : bebenlebricolo + License : + name : GPLv3 + url : https://www.gnu.org/licenses/quick-guide-gplv3.html + Date : 12/02/2021 + Project : LabBenchPowerSupply + Description : The Lab Bench Power Supply provides a simple design based around an Arduino Nano board to convert AC main voltage into + smaller ones, ranging from 0V to 16V, with voltage and current regulations +@ +------------------ + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#ifndef TIMER_8_BIT_ASYNC_STUB_HEADER +#define TIMER_8_BIT_ASYNC_STUB_HEADER + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include "timer_8_bit_async.h" +#define TIMER_8_BIT_ASYNC_STUB_MAX_INSTANCES (1U) + +typedef struct +{ + bool initialised; + bool started; + uint8_t counter; + uint8_t ocra; + uint8_t ocrb; + uint32_t accumulator; + timer_8_bit_async_prescaler_selection_t prescaler; + timer_8_bit_async_config_t driver_config; + timer_8_bit_async_waveform_generation_t waveform; + timer_8_bit_async_compare_output_mode_t compA; + timer_8_bit_async_compare_output_mode_t compB; + timer_8_bit_async_force_compare_config_t force_comp; + timer_8_bit_async_interrupt_config_t it_config; +} timer_8_bit_async_stub_configuration_t; + +void timer_8_bit_async_stub_set_next_parameters(const timer_8_bit_async_prescaler_selection_t prescaler, const uint8_t ocra, const uint32_t accumulator); +void timer_8_bit_async_stub_set_initialised(const bool initialised); +void timer_8_bit_async_stub_reset(void); +timer_8_bit_async_stub_configuration_t* timer_8_bit_async_stub_get_config(void); + +#ifdef __cplusplus +} +#endif + + +#endif /* TIMER_8_BIT_ASYNC_STUB_HEADER */ \ No newline at end of file diff --git a/Modules/Pwm/Tests/Stubs/timer_8_bit_stub.c b/Modules/Pwm/Tests/Stubs/timer_8_bit_stub.c new file mode 100644 index 0000000..a48d9a4 --- /dev/null +++ b/Modules/Pwm/Tests/Stubs/timer_8_bit_stub.c @@ -0,0 +1,368 @@ +/* + +------------------ +@ +FreeMyCode version : 1.0 RC alpha + Author : bebenlebricolo + License : + name : GPLv3 + url : https://www.gnu.org/licenses/quick-guide-gplv3.html + Date : 12/02/2021 + Project : LabBenchPowerSupply + Description : The Lab Bench Power Supply provides a simple design based around an Arduino Nano board to convert AC main voltage into + smaller ones, ranging from 0V to 16V, with voltage and current regulations +@ +------------------ + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "timer_8_bit_stub.h" +#include "string.h" + + + +static timer_8_bit_stub_configuration_t configuration = {0}; +static timer_error_t next_error = TIMER_ERROR_OK; + +static inline bool id_is_valid(const uint8_t id) +{ + return (id < TIMER_8_BIT_STUB_MAX_INSTANCES); +} + +timer_8_bit_stub_configuration_t* timer_8_bit_stub_get_config(void) +{ + return &configuration; +} + +void timer_8_bit_stub_set_next_parameters(const timer_8_bit_prescaler_selection_t prescaler, const uint8_t ocra, const uint32_t accumulator) +{ + configuration.prescaler = prescaler; + configuration.ocra = ocra; + configuration.accumulator = accumulator; +} + +void timer_8_bit_stub_set_initialised(const bool initialised) +{ + configuration.initialised = initialised; +} + +void timer_8_bit_stub_reset(void) +{ + memset(&configuration, 0, sizeof(timer_8_bit_stub_configuration_t)); +} + +void timer_8_bit_stub_get_driver_configuration(timer_8_bit_config_t * const config) +{ + *config = configuration.driver_config; +} + +timer_error_t timer_8_bit_compute_matching_parameters(const uint32_t * const clock_freq, + const uint32_t * const target_freq, + timer_8_bit_prescaler_selection_t * const prescaler, + uint8_t * const ocra, + uint16_t * const accumulator) +{ + (void) clock_freq; + (void) target_freq; + *prescaler = configuration.prescaler; + *ocra = configuration.ocra; + *accumulator = configuration.accumulator; + return TIMER_ERROR_OK; +} + +const timer_generic_prescaler_pair_t timer_8_bit_prescaler_table[TIMER_8_BIT_MAX_PRESCALER_COUNT] = +{ + {.value = 1U, .type = (uint8_t) TIMER8BIT_CLK_PRESCALER_1 }, + {.value = 8U, .type = (uint8_t) TIMER8BIT_CLK_PRESCALER_8 }, + {.value = 64U, .type = (uint8_t) TIMER8BIT_CLK_PRESCALER_64 }, + {.value = 256U, .type = (uint8_t) TIMER8BIT_CLK_PRESCALER_256 }, + {.value = 1024U, .type = (uint8_t) TIMER8BIT_CLK_PRESCALER_1024 }, +}; + +timer_error_t timer_8_bit_compute_closest_prescaler(const uint32_t * const clock_freq, + const uint32_t * const target_freq, + timer_8_bit_prescaler_selection_t * const prescaler) +{ + timer_error_t ret = TIMER_ERROR_OK; + timer_generic_parameters_t parameters = + { + .input = + { + .clock_freq = *clock_freq, + .target_frequency = *target_freq, + .resolution = TIMER_GENERIC_RESOLUTION_8_BIT, + .prescaler_lookup_array.array = timer_8_bit_prescaler_table, + .prescaler_lookup_array.size = TIMER_8_BIT_MAX_PRESCALER_COUNT, + }, + }; + ret = timer_generic_find_closest_prescaler(¶meters); + *prescaler = parameters.output.prescaler; + return ret; +} + + +timer_8_bit_prescaler_selection_t timer_8_bit_prescaler_from_value(uint16_t const * const input_prescaler) +{ + for (uint8_t i = 0 ; i < TIMER_8_BIT_MAX_PRESCALER_COUNT ; i++) + { + if (*input_prescaler == timer_8_bit_prescaler_table[i].value) + { + return (timer_8_bit_prescaler_selection_t) timer_8_bit_prescaler_table[i].type; + } + } + return TIMER8BIT_CLK_NO_CLOCK; +} + +uint16_t timer_8_bit_prescaler_to_value(const timer_8_bit_prescaler_selection_t prescaler) +{ + for (uint8_t i = 0 ; i < TIMER_8_BIT_MAX_PRESCALER_COUNT ; i++) + { + if (prescaler == timer_8_bit_prescaler_table[i].type) + { + return timer_8_bit_prescaler_table[i].value; + } + } + return 0; +} + +timer_error_t timer_8_bit_get_default_config(timer_8_bit_config_t * config) +{ + timer_error_t ret = TIMER_ERROR_OK; + if (TIMER_ERROR_OK != ret) + { + return ret; + } + + if (NULL == config) + { + return TIMER_ERROR_NULL_POINTER; + } + + /* Resets everything */ + config->interrupt_config.it_comp_match_a = false; + config->interrupt_config.it_comp_match_b = false; + config->interrupt_config.it_timer_overflow = false; + + config->timing_config.counter = 0U; + config->timing_config.ocra_val = 0U; + config->timing_config.ocrb_val = 0U; + config->timing_config.prescaler = TIMER8BIT_CLK_NO_CLOCK; + config->timing_config.waveform_mode = TIMER8BIT_WG_NORMAL; + config->timing_config.comp_mode_a = TIMER8BIT_CMOD_NORMAL; + config->timing_config.comp_mode_b = TIMER8BIT_CMOD_NORMAL; + + config->force_compare.force_comp_match_a = false; + config->force_compare.force_comp_match_b = false; + + return ret; +} + +timer_error_t timer_8_bit_set_handle(uint8_t id, timer_8_bit_handle_t * const handle) +{ + (void) handle; + (void) id; + return next_error; +} + +timer_error_t timer_8_bit_get_handle(uint8_t id, timer_8_bit_handle_t * const handle) +{ + (void) handle; + if (!id_is_valid(id)) + { + return TIMER_ERROR_UNKNOWN_TIMER; + } + return next_error; +} + +timer_error_t timer_8_bit_set_force_compare_config(uint8_t id, timer_8_bit_force_compare_config_t * const force_comp_config) +{ + configuration.force_comp = *force_comp_config; + (void) id; + return next_error; +} + +timer_error_t timer_8_bit_get_force_compare_config(uint8_t id, timer_8_bit_force_compare_config_t * force_comp_config) +{ + *force_comp_config = configuration.force_comp; + (void) id; + return next_error; +} + +timer_error_t timer_8_bit_set_interrupt_config(uint8_t id, timer_8_bit_interrupt_config_t * const it_config) +{ + configuration.it_config = *it_config; + (void) id; + return next_error; +} + +timer_error_t timer_8_bit_get_interrupt_config(uint8_t id, timer_8_bit_interrupt_config_t * it_config) +{ + *it_config = configuration.it_config; + (void) id; + return next_error; +} + +#ifdef UNIT_TESTING +timer_error_t timer_8_bit_get_interrupt_flags(uint8_t id, timer_8_bit_interrupt_config_t * it_flags) +{ + *it_flags = configuration.it_config; + (void) id; + return next_error; +} +#endif + +timer_error_t timer_8_bit_set_prescaler(uint8_t id, const timer_8_bit_prescaler_selection_t prescaler) +{ + configuration.prescaler = prescaler; + (void) id; + return next_error; +} + +timer_error_t timer_8_bit_get_prescaler(uint8_t id, timer_8_bit_prescaler_selection_t * prescaler) +{ + *prescaler = configuration.prescaler; + (void) id; + return next_error; +} + +timer_error_t timer_8_bit_set_compare_match_A(uint8_t id, const timer_8_bit_compare_output_mode_t compA) +{ + configuration.compA = compA; + (void) id; + return next_error; +} + +timer_error_t timer_8_bit_get_compare_match_A(uint8_t id, timer_8_bit_compare_output_mode_t * compA) +{ + *compA = configuration.compA; + (void) id; + return next_error; +} + +timer_error_t timer_8_bit_set_compare_match_B(uint8_t id, timer_8_bit_compare_output_mode_t compB) +{ + configuration.compB = compB; + (void) id; + return next_error; +} + +timer_error_t timer_8_bit_get_compare_match_B(uint8_t id, timer_8_bit_compare_output_mode_t * compB) +{ + *compB = configuration.compB; + (void) id; + return next_error; +} + +timer_error_t timer_8_bit_set_waveform_generation(uint8_t id, const timer_8_bit_waveform_generation_t waveform) +{ + configuration.waveform = waveform; + (void) id; + return next_error; +} + +timer_error_t timer_8_bit_get_waveform_generation(uint8_t id, timer_8_bit_waveform_generation_t * waveform) +{ + *waveform = configuration.waveform; + (void) id; + return next_error; +} + +timer_error_t timer_8_bit_set_counter_value(uint8_t id, const uint8_t ticks) +{ + configuration.counter = ticks; + (void) id; + return next_error; +} + +timer_error_t timer_8_bit_get_counter_value(uint8_t id, uint8_t * ticks) +{ + *ticks = configuration.counter; + (void) id; + return next_error; +} + +timer_error_t timer_8_bit_set_ocra_register_value(uint8_t id, uint8_t ocra) +{ + configuration.ocra = ocra; + (void) id; + + return next_error; +} + +timer_error_t timer_8_bit_get_ocra_register_value(uint8_t id, uint8_t * ocra) +{ + *ocra = configuration.ocra; + (void) id; + + return next_error; +} + +timer_error_t timer_8_bit_set_ocrb_register_value(uint8_t id, uint8_t ocrb) +{ + (void) id; + configuration.ocrb = ocrb; + return next_error; +} + +timer_error_t timer_8_bit_get_ocrb_register_value(uint8_t id, uint8_t * ocrb) +{ + (void) id; + *ocrb = configuration.ocrb; + return next_error; +} + +timer_error_t timer_8_bit_init(uint8_t id, timer_8_bit_config_t * const config) +{ + (void) config; + (void) id; + timer_8_bit_stub_reset(); + + return next_error; +} + +timer_error_t timer_8_bit_reconfigure(uint8_t id, timer_8_bit_config_t * const config) +{ + (void) id; + configuration.driver_config = *config; + return next_error; +} + +timer_error_t timer_8_bit_deinit(uint8_t id) +{ + (void) id; + timer_8_bit_stub_reset(); + return next_error; +} + +timer_error_t timer_8_bit_start(uint8_t id) +{ + configuration.started = true; + (void) id; + return next_error; +} + +timer_error_t timer_8_bit_stop(uint8_t id) +{ + configuration.started = false; + (void) id; + return next_error; +} + +timer_error_t timer_8_bit_is_initialised(const uint8_t id, bool * const initialised) +{ + (void) id; + *initialised = configuration.initialised; + return next_error; +} \ No newline at end of file diff --git a/Modules/Pwm/Tests/Stubs/timer_8_bit_stub.h b/Modules/Pwm/Tests/Stubs/timer_8_bit_stub.h new file mode 100644 index 0000000..7354f2d --- /dev/null +++ b/Modules/Pwm/Tests/Stubs/timer_8_bit_stub.h @@ -0,0 +1,70 @@ +/* + +------------------ +@ +FreeMyCode version : 1.0 RC alpha + Author : bebenlebricolo + License : + name : GPLv3 + url : https://www.gnu.org/licenses/quick-guide-gplv3.html + Date : 12/02/2021 + Project : LabBenchPowerSupply + Description : The Lab Bench Power Supply provides a simple design based around an Arduino Nano board to convert AC main voltage into + smaller ones, ranging from 0V to 16V, with voltage and current regulations +@ +------------------ + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#ifndef TIMER_8_BIT_STUB_HEADER +#define TIMER_8_BIT_STUB_HEADER + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include "timer_8_bit.h" +#define TIMER_8_BIT_STUB_MAX_INSTANCES (1U) + +typedef struct +{ + bool initialised; + bool started; + timer_8_bit_prescaler_selection_t prescaler; + uint8_t counter; + uint8_t ocra; + uint8_t ocrb; + uint32_t accumulator; + timer_8_bit_config_t driver_config; + timer_8_bit_waveform_generation_t waveform; + timer_8_bit_compare_output_mode_t compA; + timer_8_bit_compare_output_mode_t compB; + timer_8_bit_force_compare_config_t force_comp; + timer_8_bit_interrupt_config_t it_config; +} timer_8_bit_stub_configuration_t; + +void timer_8_bit_stub_set_next_parameters(const timer_8_bit_prescaler_selection_t prescaler, const uint8_t ocra, const uint32_t accumulator); +void timer_8_bit_stub_set_initialised(const bool initialised); +void timer_8_bit_stub_reset(void); +void timer_8_bit_stub_get_driver_configuration(timer_8_bit_config_t * const config); +timer_8_bit_stub_configuration_t* timer_8_bit_stub_get_config(void); + +#ifdef __cplusplus +} +#endif + + +#endif /* TIMER_8_BIT_STUB_HEADER */ \ No newline at end of file diff --git a/Modules/Pwm/Tests/pwm_tests.cpp b/Modules/Pwm/Tests/pwm_tests.cpp new file mode 100644 index 0000000..394c150 --- /dev/null +++ b/Modules/Pwm/Tests/pwm_tests.cpp @@ -0,0 +1,1207 @@ +#include + +#include "pwm.h" +#include "timebase.h" + +#include "timer_8_bit.h" +#include "timer_8_bit_stub.h" + +#include "timer_16_bit.h" +#include "timer_16_bit_stub.h" + +#include "timer_8_bit_async.h" +#include "timer_8_bit_async_stub.h" + + +/** + * @brief Global PWM test suite class + * It will be used to test mixed configurations + */ +class PwmModuleTestSuite : public ::testing::Test +{ +public: + void SetUp(void) override + { + } + + void TearDown(void) override + { + timer_8_bit_stub_reset(); + timer_16_bit_stub_reset(); + timer_8_bit_async_stub_reset(); + } +}; + +/** + * @brief Test suite dedicated to test 8 bit timers wrapping by PWM module specifically + */ +class PwmModule8BitTimerTestSuite : public ::testing::Test +{ +public: + void SetUp(void) override + { + } + + void TearDown(void) override + { + timer_8_bit_stub_reset(); + } +}; + +/** + * @brief Test suite dedicated to test 8 bit timers wrapping by PWM module specifically + */ +class PwmModule8BitAsyncTimerTestSuite : public ::testing::Test +{ +public: + void SetUp(void) override + { + } + + void TearDown(void) override + { + timer_8_bit_async_stub_reset(); + } +}; + +/** + * @brief Test suite dedicated to test 8 bit timers wrapping by PWM module specifically + */ +class PwmModule16BitTimerTestSuite : public ::testing::Test +{ +public: + void SetUp(void) override + { + } + + void TearDown(void) override + { + timer_16_bit_stub_reset(); + } +}; + +TEST_F(PwmModuleTestSuite, test_single_hard_config) +{ + // Rewriting parts of pwm_config + pwm_config.hard[0].arch = TIMER_ARCH_8_BIT; + pwm_config.hard[0].timer_index = 0U; + pwm_config.hard[0].unit = PWM_HARD_TIMER_UNIT_A; + + pwm_error_t error = PWM_ERROR_OK; + + //Should return a PWM_ERR_CONFIG as underlying timers are not initialised yet + error = pwm_init(); + ASSERT_EQ(PWM_ERROR_CONFIG, error); + + // Init the timer_8_bit beforehand + timer_8_bit_stub_set_initialised(true); + error = pwm_init(); + ASSERT_EQ(PWM_ERROR_OK, error); + +} + +TEST_F(PwmModuleTestSuite, test_single_soft_init) +{ + // Rewriting parts of pwm_config + pwm_config.soft[0].io_index = 0U; + pwm_config.soft[0].timebase_index = 0U; + + pwm_config.hard[0].arch = TIMER_ARCH_8_BIT; + pwm_config.hard[0].timer_index = 0U; + pwm_config.hard[0].unit = PWM_HARD_TIMER_UNIT_A; + + pwm_error_t error = PWM_ERROR_OK; + + // Should not return an error config, as the + // pwm_config does not contain hardware configuration anymore + error = pwm_init(); + ASSERT_EQ(PWM_ERROR_CONFIG, error); + + timer_8_bit_stub_set_initialised(true); + + // Forcing this timer to started state + timer_8_bit_stub_get_config()->started = true; + + error = pwm_init(); + ASSERT_EQ(PWM_ERROR_OK, error); + // Timer should be forcibly stopped at this point + ASSERT_FALSE(timer_8_bit_stub_get_config()->started); + +} + +TEST_F(PwmModuleTestSuite, single_8_bit_pwm_hard_configuration_waveforms) +{ + // Rewriting parts of pwm_config + pwm_config.hard[0].arch = TIMER_ARCH_8_BIT; + pwm_config.hard[0].timer_index = 0U; + pwm_config.hard[0].unit = PWM_HARD_TIMER_UNIT_A; + + // Setting up a software PWM + pwm_config.soft[0].io_index = 0U; + pwm_config.soft[0].timebase_index = 0U; + + pwm_error_t error = PWM_ERROR_OK; + + // Init the timer_8_bit beforehand + timer_8_bit_stub_set_initialised(true); + + // Init the timer_8_bit beforehand + timer_8_bit_stub_set_initialised(true); + error = pwm_init(); + ASSERT_EQ(PWM_ERROR_OK, error); + + pwm_props_t properties = + { + .frequency = 1 MHz, + .duty_cycle = 50U, + .pol = PWM_POLARITY_INVERTED + }; + const uint32_t cpu_freq = 1 MHz; + + // Normal Waveform generation should return an error, this pwm module does not want + // to interfere with other components using Timers, such as a timebase for instance. + (void) timer_8_bit_set_waveform_generation(0U, TIMER8BIT_WG_NORMAL); + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_CONFIG, error); + + // Same goes for CTC mode + (void) timer_8_bit_set_waveform_generation(0U, TIMER8BIT_WG_CTC); + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_CONFIG, error); + + + // Check that all 4 waveform modes are correct + const timer_8_bit_waveform_generation_t valid_modes[] = + { + TIMER8BIT_WG_PWM_FAST_FULL_RANGE, + TIMER8BIT_WG_PWM_FAST_OCRA_MAX, + TIMER8BIT_WG_PWM_PHASE_CORRECT_FULL_RANGE, + TIMER8BIT_WG_PWM_PHASE_CORRECT_OCRA_MAX, + }; + + // Select a valid waveform generation mode + for (const auto mode : valid_modes) + { + (void) timer_8_bit_set_waveform_generation(0U, mode); + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + EXPECT_EQ(PWM_ERROR_OK, error); + } + + // Start the timer + ASSERT_FALSE(timer_8_bit_stub_get_config()->started); + error = pwm_start(0U, PWM_TYPE_HARDWARE); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_TRUE(timer_8_bit_stub_get_config()->started); + + // Check that timer is indeed stopped + error = pwm_stop(0U, PWM_TYPE_HARDWARE); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_FALSE(timer_8_bit_stub_get_config()->started); +} + +TEST_F(PwmModuleTestSuite, single_8_bit_async_pwm_hard_configuration) +{ + // Rewriting parts of pwm_config + pwm_config.hard[0].arch = TIMER_ARCH_8_BIT_ASYNC; + pwm_config.hard[0].timer_index = 0U; + pwm_config.hard[0].unit = PWM_HARD_TIMER_UNIT_A; + + // Setting up a software PWM + pwm_config.soft[0].io_index = 0U; + pwm_config.soft[0].timebase_index = 0U; + + pwm_error_t error = PWM_ERROR_OK; + + // Init the timer_8_bit beforehand + timer_8_bit_async_stub_set_initialised(true); + + // Init the timer_8_bit beforehand + timer_8_bit_async_stub_set_initialised(true); + error = pwm_init(); + ASSERT_EQ(PWM_ERROR_OK, error); + + pwm_props_t properties = + { + .frequency = 1 MHz, + .duty_cycle = 50U, + .pol = PWM_POLARITY_INVERTED + }; + const uint32_t cpu_freq = 1 MHz; + + // Normal Waveform generation should return an error, this pwm module does not want + // to interfere with other components using Timers, such as a timebase for instance. + (void) timer_8_bit_async_set_waveform_generation(0U, TIMER8BIT_ASYNC_WG_NORMAL); + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_CONFIG, error); + + // Same goes for CTC mode + (void) timer_8_bit_async_set_waveform_generation(0U, TIMER8BIT_ASYNC_WG_CTC); + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_CONFIG, error); + + + // Check that all 4 waveform modes are correct + const timer_8_bit_async_waveform_generation_t valid_modes[] = + { + TIMER8BIT_ASYNC_WG_PWM_FAST_FULL_RANGE, + TIMER8BIT_ASYNC_WG_PWM_FAST_OCRA_MAX, + TIMER8BIT_ASYNC_WG_PWM_PHASE_CORRECT_FULL_RANGE, + TIMER8BIT_ASYNC_WG_PWM_PHASE_CORRECT_OCRA_MAX, + }; + + // Select a valid waveform generation mode + for (const auto mode : valid_modes) + { + (void) timer_8_bit_async_set_waveform_generation(0U, mode); + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + EXPECT_EQ(PWM_ERROR_OK, error); + } + + // Start the timer + ASSERT_FALSE(timer_8_bit_async_stub_get_config()->started); + error = pwm_start(0U, PWM_TYPE_HARDWARE); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_TRUE(timer_8_bit_async_stub_get_config()->started); + + // Check that timer is indeed stopped + error = pwm_stop(0U, PWM_TYPE_HARDWARE); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_FALSE(timer_8_bit_async_stub_get_config()->started); +} + +TEST_F(PwmModuleTestSuite, single_16_bit_pwm_hard_configuration) +{ + // Rewriting parts of pwm_config + pwm_config.hard[0].arch = TIMER_ARCH_16_BIT; + pwm_config.hard[0].timer_index = 0U; + pwm_config.hard[0].unit = PWM_HARD_TIMER_UNIT_A; + + // Setting up a software PWM + pwm_config.soft[0].io_index = 0U; + pwm_config.soft[0].timebase_index = 0U; + + pwm_error_t error = PWM_ERROR_OK; + + // Init the timer_16_bit beforehand + timer_16_bit_stub_set_initialised(true); + + // Init the timer_16_bit beforehand + timer_16_bit_stub_set_initialised(true); + error = pwm_init(); + ASSERT_EQ(PWM_ERROR_OK, error); + + pwm_props_t properties = + { + .frequency = 1 MHz, + .duty_cycle = 50U, + .pol = PWM_POLARITY_INVERTED + }; + const uint32_t cpu_freq = 16 MHz; + + // Normal Waveform generation should return an error, this pwm module does not want + // to interfere with other components using Timers, such as a timebase for instance. + (void) timer_16_bit_set_waveform_generation(0U, TIMER16BIT_WG_NORMAL); + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_CONFIG, error); + + // Same goes for CTC mode + (void) timer_16_bit_set_waveform_generation(0U, TIMER16BIT_WG_CTC_ICR_MAX); + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_CONFIG, error); + + (void) timer_16_bit_set_waveform_generation(0U, TIMER16BIT_WG_CTC_OCRA_MAX); + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_CONFIG, error); + + + // Check that all 4 waveform modes are correct + const timer_16_bit_waveform_generation_t valid_modes[] = + { + TIMER16BIT_WG_PWM_FAST_10_bit_FULL_RANGE, + TIMER16BIT_WG_PWM_FAST_9_bit_FULL_RANGE, + TIMER16BIT_WG_PWM_FAST_8_bit_FULL_RANGE, + TIMER16BIT_WG_PWM_FAST_ICR_MAX, + TIMER16BIT_WG_PWM_FAST_OCRA_MAX, // Error + + TIMER16BIT_WG_PWM_PHASE_AND_FREQ_CORRECT_ICR_MAX, + TIMER16BIT_WG_PWM_PHASE_AND_FREQ_CORRECT_OCRA_MAX, //Error + + TIMER16BIT_WG_PWM_PHASE_CORRECT_10_bit_FULL_RANGE, + TIMER16BIT_WG_PWM_PHASE_CORRECT_9_bit_FULL_RANGE, + TIMER16BIT_WG_PWM_PHASE_CORRECT_8_bit_FULL_RANGE, + TIMER16BIT_WG_PWM_PHASE_CORRECT_ICR_MAX, + TIMER16BIT_WG_PWM_PHASE_CORRECT_OCRA_MAX, // Error + }; + + // Select a valid waveform generation mode + for (const auto mode : valid_modes) + { + (void) timer_16_bit_set_waveform_generation(0U, mode); + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + EXPECT_EQ(PWM_ERROR_OK, error); + } + + // Start the timer + ASSERT_FALSE(timer_16_bit_stub_get_config()->started); + error = pwm_start(0U, PWM_TYPE_HARDWARE); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_TRUE(timer_16_bit_stub_get_config()->started); + + // Check that timer is indeed stopped + error = pwm_stop(0U, PWM_TYPE_HARDWARE); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_FALSE(timer_16_bit_stub_get_config()->started); +} + +// ################################################################# +// ######## Hardware timers behavioral testing starts here ######### +// ################################################################# + +TEST_F(PwmModule8BitTimerTestSuite, unit_a_behavioral_testing) +{ + // Rewriting parts of pwm_config + pwm_config.hard[0].arch = TIMER_ARCH_8_BIT; + pwm_config.hard[0].timer_index = 0U; + pwm_config.hard[0].unit = PWM_HARD_TIMER_UNIT_A; + pwm_error_t error = PWM_ERROR_OK; + + // Init the timer_8_bit beforehand + timer_8_bit_stub_set_initialised(true); + error = pwm_init(); + ASSERT_EQ(PWM_ERROR_OK, error); + + + // Checking Pwm Fast Full Range behavior + { + pwm_props_t properties = { .frequency = 1 MHz, .duty_cycle = 50U, .pol = PWM_POLARITY_NORMAL }; + const uint32_t cpu_freq = 16 MHz; + + (void) timer_8_bit_set_waveform_generation(0U, TIMER8BIT_WG_PWM_FAST_FULL_RANGE); + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_8_bit_stub_get_config()->compA, TIMER8BIT_CMOD_CLEAR_OCnX); // Normal polarity means we want to clear pin when reaching OCR value + ASSERT_EQ(timer_8_bit_stub_get_config()->compB, TIMER8BIT_CMOD_NORMAL); // unchanged + ASSERT_EQ(timer_8_bit_stub_get_config()->prescaler, TIMER8BIT_CLK_PRESCALER_1); + ASSERT_EQ(timer_8_bit_stub_get_config()->ocra, (properties.duty_cycle * COUNTER_MAX_VALUE_8_BIT) / 100U); + ASSERT_EQ(properties.duty_cycle, 50U); // Duty cycle is preserved + ASSERT_EQ(properties.frequency, (cpu_freq / (timer_8_bit_stub_get_config()->prescaler * (COUNTER_MAX_VALUE_8_BIT + 1)))); // Actual output frequency is updated accordingly + + properties.pol = PWM_POLARITY_INVERTED; + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_8_bit_stub_get_config()->compA, TIMER8BIT_CMOD_SET_OCnX); // Normal polarity means we want to clear pin when reaching OCR value + ASSERT_EQ(timer_8_bit_stub_get_config()->compB, TIMER8BIT_CMOD_NORMAL); // unchanged + timer_8_bit_stub_reset(); + timer_8_bit_stub_set_initialised(true); + } + + // Checking Pwm Phase Correct Full Range behavior + { + pwm_props_t properties = { .frequency = 1 MHz, .duty_cycle = 50U, .pol = PWM_POLARITY_NORMAL }; + const uint32_t cpu_freq = 16 MHz; + + (void) timer_8_bit_set_waveform_generation(0U, TIMER8BIT_WG_PWM_PHASE_CORRECT_FULL_RANGE); + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_8_bit_stub_get_config()->compA, TIMER8BIT_CMOD_CLEAR_OCnX); // Normal polarity means we want to clear pin when reaching OCR value + ASSERT_EQ(timer_8_bit_stub_get_config()->compB, TIMER8BIT_CMOD_NORMAL); // unchanged + ASSERT_EQ(timer_8_bit_stub_get_config()->prescaler, TIMER8BIT_CLK_PRESCALER_1); + ASSERT_EQ(timer_8_bit_stub_get_config()->ocra, (properties.duty_cycle * COUNTER_MAX_VALUE_8_BIT) / 100U); + ASSERT_EQ(properties.duty_cycle, 50U); // Duty cycle is preserved + ASSERT_EQ(properties.frequency, (cpu_freq / (timer_8_bit_stub_get_config()->prescaler * (COUNTER_MAX_VALUE_8_BIT + 1)))); // Actual output frequency is updated accordingly + + properties.pol = PWM_POLARITY_INVERTED; + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_8_bit_stub_get_config()->compA, TIMER8BIT_CMOD_SET_OCnX); // Normal polarity means we want to clear pin when reaching OCR value + ASSERT_EQ(timer_8_bit_stub_get_config()->compB, TIMER8BIT_CMOD_NORMAL); // unchanged + + timer_8_bit_stub_reset(); + timer_8_bit_stub_set_initialised(true); + } + + // Checking Pwm Fast Mode OCRA Max behavior + { + pwm_props_t properties = { .frequency = 1 MHz, .duty_cycle = 25U, .pol = PWM_POLARITY_NORMAL }; + const uint32_t cpu_freq = 16 MHz; + + (void) timer_8_bit_set_waveform_generation(0U, TIMER8BIT_WG_PWM_FAST_OCRA_MAX); + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_8_bit_stub_get_config()->compA, TIMER8BIT_CMOD_TOGGLE_OCnX); // Switched to toggling pin, frequency halved (or compensated) + ASSERT_EQ(timer_8_bit_stub_get_config()->compB, TIMER8BIT_CMOD_NORMAL); // unchanged + ASSERT_EQ(timer_8_bit_stub_get_config()->prescaler, TIMER8BIT_CLK_PRESCALER_1); + ASSERT_EQ(timer_8_bit_stub_get_config()->ocra, 7U); // 1MHz frequency with 50% duty cycle (pin toggled, so ocra halved) + + ASSERT_EQ(properties.duty_cycle, 50U); // Duty cycle is preserved + ASSERT_EQ(properties.frequency, 1 MHz); // Actual output frequency is updated accordingly + + timer_8_bit_stub_reset(); + timer_8_bit_stub_set_initialised(true); + } + + // Checking Pwm Phase Correct OCRA Max behavior + { + pwm_props_t properties = { .frequency = 1 MHz, .duty_cycle = 25U, .pol = PWM_POLARITY_NORMAL }; + const uint32_t cpu_freq = 16 MHz; + + (void) timer_8_bit_set_waveform_generation(0U, TIMER8BIT_WG_PWM_PHASE_CORRECT_OCRA_MAX); + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_8_bit_stub_get_config()->compA, TIMER8BIT_CMOD_TOGGLE_OCnX); // Switched to toggling pin, frequency halved (or compensated) + ASSERT_EQ(timer_8_bit_stub_get_config()->compB, TIMER8BIT_CMOD_NORMAL); // unchanged + ASSERT_EQ(timer_8_bit_stub_get_config()->prescaler, TIMER8BIT_CLK_PRESCALER_1); + ASSERT_EQ(timer_8_bit_stub_get_config()->ocra, 7U); // 1MHz frequency with 50% duty cycle (pin toggled, so ocra halved) + + ASSERT_EQ(properties.duty_cycle, 50U); // Duty cycle is preserved + ASSERT_EQ(properties.frequency, 1 MHz); // Actual output frequency is updated accordingly + + timer_8_bit_stub_reset(); + timer_8_bit_stub_set_initialised(true); + } +} + +TEST_F(PwmModule8BitTimerTestSuite, unit_b_behavioral_testing) +{ + // Rewriting parts of pwm_config + pwm_config.hard[0].arch = TIMER_ARCH_8_BIT; + pwm_config.hard[0].timer_index = 0U; + pwm_config.hard[0].unit = PWM_HARD_TIMER_UNIT_B; + pwm_error_t error = PWM_ERROR_OK; + + // Init the timer_8_bit beforehand + timer_8_bit_stub_set_initialised(true); + error = pwm_init(); + ASSERT_EQ(PWM_ERROR_OK, error); + + + // Checking Pwm Fast Full Range behavior + { + pwm_props_t properties = { .frequency = 1 MHz, .duty_cycle = 25U, .pol = PWM_POLARITY_NORMAL }; + const uint32_t cpu_freq = 16 MHz; + + (void) timer_8_bit_set_waveform_generation(0U, TIMER8BIT_WG_PWM_FAST_FULL_RANGE); + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_8_bit_stub_get_config()->compA, TIMER8BIT_CMOD_NORMAL); // unchanged + ASSERT_EQ(timer_8_bit_stub_get_config()->compB, TIMER8BIT_CMOD_CLEAR_OCnX); // Normal polarity means we want to clear pin when reaching OCR value + ASSERT_EQ(timer_8_bit_stub_get_config()->prescaler, TIMER8BIT_CLK_PRESCALER_1); + ASSERT_EQ(timer_8_bit_stub_get_config()->ocrb, (properties.duty_cycle * COUNTER_MAX_VALUE_8_BIT) / 100U); + ASSERT_EQ(properties.duty_cycle, 25U); // Duty cycle is preserved + ASSERT_EQ(properties.frequency, (cpu_freq / (timer_8_bit_stub_get_config()->prescaler * (COUNTER_MAX_VALUE_8_BIT + 1)))); // Actual output frequency is updated accordingly + + properties.pol = PWM_POLARITY_INVERTED; + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_8_bit_stub_get_config()->compA, TIMER8BIT_CMOD_NORMAL); // unchanged + ASSERT_EQ(timer_8_bit_stub_get_config()->compB, TIMER8BIT_CMOD_SET_OCnX); // Normal polarity means we want to clear pin when reaching OCR value + timer_8_bit_stub_reset(); + timer_8_bit_stub_set_initialised(true); + } + + // Checking Pwm Phase Correct Full Range behavior + { + pwm_props_t properties = { .frequency = 1 MHz, .duty_cycle = 50U, .pol = PWM_POLARITY_NORMAL }; + const uint32_t cpu_freq = 16 MHz; + + (void) timer_8_bit_set_waveform_generation(0U, TIMER8BIT_WG_PWM_PHASE_CORRECT_FULL_RANGE); + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_8_bit_stub_get_config()->compA, TIMER8BIT_CMOD_NORMAL); // unchanged + ASSERT_EQ(timer_8_bit_stub_get_config()->compB, TIMER8BIT_CMOD_CLEAR_OCnX); // Normal polarity means we want to clear pin when reaching OCR value + ASSERT_EQ(timer_8_bit_stub_get_config()->prescaler, TIMER8BIT_CLK_PRESCALER_1); + ASSERT_EQ(timer_8_bit_stub_get_config()->ocrb, (properties.duty_cycle * COUNTER_MAX_VALUE_8_BIT) / 100U); + ASSERT_EQ(properties.duty_cycle, 50U); // Duty cycle is preserved + ASSERT_EQ(properties.frequency, (cpu_freq / (timer_8_bit_stub_get_config()->prescaler * (COUNTER_MAX_VALUE_8_BIT + 1)))); // Actual output frequency is updated accordingly + + properties.pol = PWM_POLARITY_INVERTED; + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_8_bit_stub_get_config()->compA, TIMER8BIT_CMOD_NORMAL); // unchanged + ASSERT_EQ(timer_8_bit_stub_get_config()->compB, TIMER8BIT_CMOD_SET_OCnX); // Normal polarity means we want to clear pin when reaching OCR value + + timer_8_bit_stub_reset(); + timer_8_bit_stub_set_initialised(true); + } + + // Checking Pwm Fast Mode OCRA Max behavior + { + pwm_props_t properties = { .frequency = 1 MHz, .duty_cycle = 25U, .pol = PWM_POLARITY_NORMAL }; + const uint32_t cpu_freq = 16 MHz; + + (void) timer_8_bit_set_waveform_generation(0U, TIMER8BIT_WG_PWM_FAST_OCRA_MAX); + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_8_bit_stub_get_config()->compA, TIMER8BIT_CMOD_NORMAL); // unchanged + ASSERT_EQ(timer_8_bit_stub_get_config()->compB, TIMER8BIT_CMOD_CLEAR_OCnX); // Normal polarity means we want to clear pin when reaching OCR value + ASSERT_EQ(timer_8_bit_stub_get_config()->prescaler, TIMER8BIT_CLK_PRESCALER_1); + ASSERT_EQ(timer_8_bit_stub_get_config()->ocra, 15U); + ASSERT_EQ(timer_8_bit_stub_get_config()->ocrb, (properties.duty_cycle * timer_8_bit_stub_get_config()->ocra) / 100U); + + ASSERT_EQ(properties.duty_cycle, 25U); // Duty cycle is preserved + ASSERT_EQ(properties.frequency, 1 MHz); // Actual output frequency is updated accordingly + + properties.pol = PWM_POLARITY_INVERTED; + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_8_bit_stub_get_config()->compA, TIMER8BIT_CMOD_NORMAL); // unchanged + ASSERT_EQ(timer_8_bit_stub_get_config()->compB, TIMER8BIT_CMOD_SET_OCnX); // Normal polarity means we want to clear pin when reaching OCR value + + timer_8_bit_stub_reset(); + timer_8_bit_stub_set_initialised(true); + } + + // Checking Pwm Phase Correct OCRA Max behavior + { + pwm_props_t properties = { .frequency = 1 MHz, .duty_cycle = 25U, .pol = PWM_POLARITY_NORMAL }; + const uint32_t cpu_freq = 16 MHz; + + (void) timer_8_bit_set_waveform_generation(0U, TIMER8BIT_WG_PWM_PHASE_CORRECT_OCRA_MAX); + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_8_bit_stub_get_config()->compA, TIMER8BIT_CMOD_NORMAL); // unchanged + ASSERT_EQ(timer_8_bit_stub_get_config()->compB, TIMER8BIT_CMOD_CLEAR_OCnX); // Normal polarity means we want to clear pin when reaching OCR value + ASSERT_EQ(timer_8_bit_stub_get_config()->prescaler, TIMER8BIT_CLK_PRESCALER_1); + ASSERT_EQ(timer_8_bit_stub_get_config()->ocra, 15U); + ASSERT_EQ(timer_8_bit_stub_get_config()->ocrb, (properties.duty_cycle * timer_8_bit_stub_get_config()->ocra) / 100U); + + ASSERT_EQ(properties.duty_cycle, 25U); // Duty cycle is preserved + ASSERT_EQ(properties.frequency, 1 MHz); // Actual output frequency is updated accordingly + + properties.pol = PWM_POLARITY_INVERTED; + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_8_bit_stub_get_config()->compA, TIMER8BIT_CMOD_NORMAL); // unchanged + ASSERT_EQ(timer_8_bit_stub_get_config()->compB, TIMER8BIT_CMOD_SET_OCnX); // Normal polarity means we want to clear pin when reaching OCR value + + timer_8_bit_stub_reset(); + timer_8_bit_stub_set_initialised(true); + } +} + +TEST_F(PwmModule8BitAsyncTimerTestSuite, unit_a_behavioral_testing) +{ + // Rewriting parts of pwm_config + pwm_config.hard[0].arch = TIMER_ARCH_8_BIT_ASYNC; + pwm_config.hard[0].timer_index = 0U; + pwm_config.hard[0].unit = PWM_HARD_TIMER_UNIT_A; + pwm_error_t error = PWM_ERROR_OK; + + // Init the timer_8_bit beforehand + timer_8_bit_async_stub_set_initialised(true); + error = pwm_init(); + ASSERT_EQ(PWM_ERROR_OK, error); + + + // Checking Pwm Fast Full Range behavior + { + pwm_props_t properties = { .frequency = 1 MHz, .duty_cycle = 50U, .pol = PWM_POLARITY_NORMAL }; + const uint32_t cpu_freq = 16 MHz; + + (void) timer_8_bit_async_set_waveform_generation(0U, TIMER8BIT_ASYNC_WG_PWM_FAST_FULL_RANGE); + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_8_bit_async_stub_get_config()->compA, TIMER8BIT_ASYNC_CMOD_CLEAR_OCnX); // Normal polarity means we want to clear pin when reaching OCR value + ASSERT_EQ(timer_8_bit_async_stub_get_config()->compB, TIMER8BIT_ASYNC_CMOD_NORMAL); // unchanged + ASSERT_EQ(timer_8_bit_async_stub_get_config()->prescaler, TIMER8BIT_ASYNC_CLK_PRESCALER_1); + ASSERT_EQ(timer_8_bit_async_stub_get_config()->ocra, (properties.duty_cycle * COUNTER_MAX_VALUE_8_BIT) / 100U); + ASSERT_EQ(properties.duty_cycle, 50U); // Duty cycle is preserved + ASSERT_EQ(properties.frequency, (cpu_freq / (timer_8_bit_async_stub_get_config()->prescaler * (COUNTER_MAX_VALUE_8_BIT + 1)))); // Actual output frequency is updated accordingly + + properties.pol = PWM_POLARITY_INVERTED; + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_8_bit_async_stub_get_config()->compA, TIMER8BIT_ASYNC_CMOD_SET_OCnX); // Normal polarity means we want to clear pin when reaching OCR value + ASSERT_EQ(timer_8_bit_async_stub_get_config()->compB, TIMER8BIT_ASYNC_CMOD_NORMAL); // unchanged + timer_8_bit_async_stub_reset(); + timer_8_bit_async_stub_set_initialised(true); + } + + // Checking Pwm Phase Correct Full Range behavior + { + pwm_props_t properties = { .frequency = 1 MHz, .duty_cycle = 50U, .pol = PWM_POLARITY_NORMAL }; + const uint32_t cpu_freq = 16 MHz; + + (void) timer_8_bit_async_set_waveform_generation(0U, TIMER8BIT_ASYNC_WG_PWM_PHASE_CORRECT_FULL_RANGE); + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_8_bit_async_stub_get_config()->compA, TIMER8BIT_ASYNC_CMOD_CLEAR_OCnX); // Normal polarity means we want to clear pin when reaching OCR value + ASSERT_EQ(timer_8_bit_async_stub_get_config()->compB, TIMER8BIT_ASYNC_CMOD_NORMAL); // unchanged + ASSERT_EQ(timer_8_bit_async_stub_get_config()->prescaler, TIMER8BIT_ASYNC_CLK_PRESCALER_1); + ASSERT_EQ(timer_8_bit_async_stub_get_config()->ocra, (properties.duty_cycle * COUNTER_MAX_VALUE_8_BIT) / 100U); + ASSERT_EQ(properties.duty_cycle, 50U); // Duty cycle is preserved + ASSERT_EQ(properties.frequency, (cpu_freq / (timer_8_bit_async_stub_get_config()->prescaler * (COUNTER_MAX_VALUE_8_BIT + 1)))); // Actual output frequency is updated accordingly + + properties.pol = PWM_POLARITY_INVERTED; + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_8_bit_async_stub_get_config()->compA, TIMER8BIT_ASYNC_CMOD_SET_OCnX); // Normal polarity means we want to clear pin when reaching OCR value + ASSERT_EQ(timer_8_bit_async_stub_get_config()->compB, TIMER8BIT_ASYNC_CMOD_NORMAL); // unchanged + + timer_8_bit_async_stub_reset(); + timer_8_bit_async_stub_set_initialised(true); + } + + // Checking Pwm Fast Mode OCRA Max behavior + { + pwm_props_t properties = { .frequency = 1 MHz, .duty_cycle = 25U, .pol = PWM_POLARITY_NORMAL }; + const uint32_t cpu_freq = 16 MHz; + + (void) timer_8_bit_async_set_waveform_generation(0U, TIMER8BIT_ASYNC_WG_PWM_FAST_OCRA_MAX); + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_8_bit_async_stub_get_config()->compA, TIMER8BIT_ASYNC_CMOD_TOGGLE_OCnX); // Switched to toggling pin, frequency halved (or compensated) + ASSERT_EQ(timer_8_bit_async_stub_get_config()->compB, TIMER8BIT_ASYNC_CMOD_NORMAL); // unchanged + ASSERT_EQ(timer_8_bit_async_stub_get_config()->prescaler, TIMER8BIT_ASYNC_CLK_PRESCALER_1); + ASSERT_EQ(timer_8_bit_async_stub_get_config()->ocra, 7U); // 1MHz frequency with 50% duty cycle (pin toggled, so ocra halved) + + ASSERT_EQ(properties.duty_cycle, 50U); // Duty cycle is preserved + ASSERT_EQ(properties.frequency, 1 MHz); // Actual output frequency is updated accordingly + + timer_8_bit_async_stub_reset(); + timer_8_bit_async_stub_set_initialised(true); + } + + // Checking Pwm Phase Correct OCRA Max behavior + { + pwm_props_t properties = { .frequency = 1 MHz, .duty_cycle = 25U, .pol = PWM_POLARITY_NORMAL }; + const uint32_t cpu_freq = 16 MHz; + + (void) timer_8_bit_async_set_waveform_generation(0U, TIMER8BIT_ASYNC_WG_PWM_PHASE_CORRECT_OCRA_MAX); + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_8_bit_async_stub_get_config()->compA, TIMER8BIT_ASYNC_CMOD_TOGGLE_OCnX); // Switched to toggling pin, frequency halved (or compensated) + ASSERT_EQ(timer_8_bit_async_stub_get_config()->compB, TIMER8BIT_ASYNC_CMOD_NORMAL); // unchanged + ASSERT_EQ(timer_8_bit_async_stub_get_config()->prescaler, TIMER8BIT_ASYNC_CLK_PRESCALER_1); + ASSERT_EQ(timer_8_bit_async_stub_get_config()->ocra, 7U); // 1MHz frequency with 50% duty cycle (pin toggled, so ocra halved) + + ASSERT_EQ(properties.duty_cycle, 50U); // Duty cycle is preserved + ASSERT_EQ(properties.frequency, 1 MHz); // Actual output frequency is updated accordingly + + timer_8_bit_async_stub_reset(); + timer_8_bit_async_stub_set_initialised(true); + } +} + +TEST_F(PwmModule8BitAsyncTimerTestSuite, unit_b_behavioral_testing) +{ + // Rewriting parts of pwm_config + pwm_config.hard[0].arch = TIMER_ARCH_8_BIT_ASYNC; + pwm_config.hard[0].timer_index = 0U; + pwm_config.hard[0].unit = PWM_HARD_TIMER_UNIT_B; + pwm_error_t error = PWM_ERROR_OK; + + // Init the timer_8_bit beforehand + timer_8_bit_async_stub_set_initialised(true); + error = pwm_init(); + ASSERT_EQ(PWM_ERROR_OK, error); + + + // Checking Pwm Fast Full Range behavior + { + pwm_props_t properties = { .frequency = 1 MHz, .duty_cycle = 25U, .pol = PWM_POLARITY_NORMAL }; + const uint32_t cpu_freq = 16 MHz; + + (void) timer_8_bit_async_set_waveform_generation(0U, TIMER8BIT_ASYNC_WG_PWM_FAST_FULL_RANGE); + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_8_bit_async_stub_get_config()->compA, TIMER8BIT_ASYNC_CMOD_NORMAL); // unchanged + ASSERT_EQ(timer_8_bit_async_stub_get_config()->compB, TIMER8BIT_ASYNC_CMOD_CLEAR_OCnX); // Normal polarity means we want to clear pin when reaching OCR value + ASSERT_EQ(timer_8_bit_async_stub_get_config()->prescaler, TIMER8BIT_ASYNC_CLK_PRESCALER_1); + ASSERT_EQ(timer_8_bit_async_stub_get_config()->ocrb, (properties.duty_cycle * COUNTER_MAX_VALUE_8_BIT) / 100U); + ASSERT_EQ(properties.duty_cycle, 25U); // Duty cycle is preserved + ASSERT_EQ(properties.frequency, (cpu_freq / (timer_8_bit_async_stub_get_config()->prescaler * (COUNTER_MAX_VALUE_8_BIT + 1)))); // Actual output frequency is updated accordingly + + properties.pol = PWM_POLARITY_INVERTED; + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_8_bit_async_stub_get_config()->compA, TIMER8BIT_ASYNC_CMOD_NORMAL); // unchanged + ASSERT_EQ(timer_8_bit_async_stub_get_config()->compB, TIMER8BIT_ASYNC_CMOD_SET_OCnX); // Normal polarity means we want to clear pin when reaching OCR value + timer_8_bit_async_stub_reset(); + timer_8_bit_async_stub_set_initialised(true); + } + + // Checking Pwm Phase Correct Full Range behavior + { + pwm_props_t properties = { .frequency = 1 MHz, .duty_cycle = 50U, .pol = PWM_POLARITY_NORMAL }; + const uint32_t cpu_freq = 16 MHz; + + (void) timer_8_bit_async_set_waveform_generation(0U, TIMER8BIT_ASYNC_WG_PWM_PHASE_CORRECT_FULL_RANGE); + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_8_bit_async_stub_get_config()->compA, TIMER8BIT_ASYNC_CMOD_NORMAL); // unchanged + ASSERT_EQ(timer_8_bit_async_stub_get_config()->compB, TIMER8BIT_ASYNC_CMOD_CLEAR_OCnX); // Normal polarity means we want to clear pin when reaching OCR value + ASSERT_EQ(timer_8_bit_async_stub_get_config()->prescaler, TIMER8BIT_ASYNC_CLK_PRESCALER_1); + ASSERT_EQ(timer_8_bit_async_stub_get_config()->ocrb, (properties.duty_cycle * COUNTER_MAX_VALUE_8_BIT) / 100U); + ASSERT_EQ(properties.duty_cycle, 50U); // Duty cycle is preserved + ASSERT_EQ(properties.frequency, (cpu_freq / (timer_8_bit_async_stub_get_config()->prescaler * (COUNTER_MAX_VALUE_8_BIT + 1)))); // Actual output frequency is updated accordingly + + properties.pol = PWM_POLARITY_INVERTED; + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_8_bit_async_stub_get_config()->compA, TIMER8BIT_ASYNC_CMOD_NORMAL); // unchanged + ASSERT_EQ(timer_8_bit_async_stub_get_config()->compB, TIMER8BIT_ASYNC_CMOD_SET_OCnX); // Normal polarity means we want to clear pin when reaching OCR value + + timer_8_bit_async_stub_reset(); + timer_8_bit_async_stub_set_initialised(true); + } + + // Checking Pwm Fast Mode OCRA Max behavior + { + pwm_props_t properties = { .frequency = 1 MHz, .duty_cycle = 25U, .pol = PWM_POLARITY_NORMAL }; + const uint32_t cpu_freq = 16 MHz; + + (void) timer_8_bit_async_set_waveform_generation(0U, TIMER8BIT_ASYNC_WG_PWM_FAST_OCRA_MAX); + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_8_bit_async_stub_get_config()->compA, TIMER8BIT_ASYNC_CMOD_NORMAL); // unchanged + ASSERT_EQ(timer_8_bit_async_stub_get_config()->compB, TIMER8BIT_ASYNC_CMOD_CLEAR_OCnX); // Normal polarity means we want to clear pin when reaching OCR value + ASSERT_EQ(timer_8_bit_async_stub_get_config()->prescaler, TIMER8BIT_ASYNC_CLK_PRESCALER_1); + ASSERT_EQ(timer_8_bit_async_stub_get_config()->ocra, 15U); + ASSERT_EQ(timer_8_bit_async_stub_get_config()->ocrb, (properties.duty_cycle * timer_8_bit_async_stub_get_config()->ocra) / 100U); + + ASSERT_EQ(properties.duty_cycle, 25U); // Duty cycle is preserved + ASSERT_EQ(properties.frequency, 1 MHz); // Actual output frequency is updated accordingly + + properties.pol = PWM_POLARITY_INVERTED; + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_8_bit_async_stub_get_config()->compA, TIMER8BIT_ASYNC_CMOD_NORMAL); // unchanged + ASSERT_EQ(timer_8_bit_async_stub_get_config()->compB, TIMER8BIT_ASYNC_CMOD_SET_OCnX); // Normal polarity means we want to clear pin when reaching OCR value + + timer_8_bit_async_stub_reset(); + timer_8_bit_async_stub_set_initialised(true); + } + + // Checking Pwm Phase Correct OCRA Max behavior + { + pwm_props_t properties = { .frequency = 1 MHz, .duty_cycle = 25U, .pol = PWM_POLARITY_NORMAL }; + const uint32_t cpu_freq = 16 MHz; + + (void) timer_8_bit_async_set_waveform_generation(0U, TIMER8BIT_ASYNC_WG_PWM_PHASE_CORRECT_OCRA_MAX); + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_8_bit_async_stub_get_config()->compA, TIMER8BIT_ASYNC_CMOD_NORMAL); // unchanged + ASSERT_EQ(timer_8_bit_async_stub_get_config()->compB, TIMER8BIT_ASYNC_CMOD_CLEAR_OCnX); // Normal polarity means we want to clear pin when reaching OCR value + ASSERT_EQ(timer_8_bit_async_stub_get_config()->prescaler, TIMER8BIT_ASYNC_CLK_PRESCALER_1); + ASSERT_EQ(timer_8_bit_async_stub_get_config()->ocra, 15U); + ASSERT_EQ(timer_8_bit_async_stub_get_config()->ocrb, (properties.duty_cycle * timer_8_bit_async_stub_get_config()->ocra) / 100U); + + ASSERT_EQ(properties.duty_cycle, 25U); // Duty cycle is preserved + ASSERT_EQ(properties.frequency, 1 MHz); // Actual output frequency is updated accordingly + + properties.pol = PWM_POLARITY_INVERTED; + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_8_bit_async_stub_get_config()->compA, TIMER8BIT_ASYNC_CMOD_NORMAL); // unchanged + ASSERT_EQ(timer_8_bit_async_stub_get_config()->compB, TIMER8BIT_ASYNC_CMOD_SET_OCnX); // Normal polarity means we want to clear pin when reaching OCR value + + timer_8_bit_async_stub_reset(); + timer_8_bit_async_stub_set_initialised(true); + } +} + +TEST_F(PwmModule16BitTimerTestSuite, unit_a_behavioral_testing) +{ + // Rewriting parts of pwm_config + pwm_config.hard[0].arch = TIMER_ARCH_16_BIT; + pwm_config.hard[0].timer_index = 0U; + pwm_config.hard[0].unit = PWM_HARD_TIMER_UNIT_A; + pwm_error_t error = PWM_ERROR_OK; + + // Init the timer_16_bit beforehand + timer_16_bit_stub_set_initialised(true); + error = pwm_init(); + ASSERT_EQ(PWM_ERROR_OK, error); + + + // Checking Pwm 8 bit Full Range behavior + { + const uint32_t cpu_freq = 16 MHz; + + timer_16_bit_waveform_generation_t configs[2U] = + { + TIMER16BIT_WG_PWM_FAST_8_bit_FULL_RANGE, + TIMER16BIT_WG_PWM_PHASE_CORRECT_8_bit_FULL_RANGE + }; + + for(const auto config : configs) + { + pwm_props_t properties = { .frequency = 1 MHz, .duty_cycle = 50U, .pol = PWM_POLARITY_NORMAL }; + (void) timer_16_bit_set_waveform_generation(0U, config); + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_16_bit_stub_get_config()->compA, TIMER16BIT_CMOD_CLEAR_OCnX); // Normal polarity means we want to clear pin when reaching OCR value + ASSERT_EQ(timer_16_bit_stub_get_config()->compB, TIMER16BIT_CMOD_NORMAL); // unchanged + ASSERT_EQ(timer_16_bit_stub_get_config()->prescaler, TIMER16BIT_CLK_PRESCALER_1); + ASSERT_EQ(timer_16_bit_stub_get_config()->ocra, (properties.duty_cycle * COUNTER_MAX_VALUE_8_BIT) / 100U); + ASSERT_EQ(timer_16_bit_stub_get_config()->ocrb, 0U); + ASSERT_EQ(timer_16_bit_stub_get_config()->icr, 0U); + ASSERT_EQ(properties.duty_cycle, 50U); // Duty cycle is preserved + ASSERT_EQ(properties.frequency, (cpu_freq / (timer_16_bit_stub_get_config()->prescaler * (COUNTER_MAX_VALUE_8_BIT + 1)))); // Actual output frequency is updated accordingly + + properties.pol = PWM_POLARITY_INVERTED; + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_16_bit_stub_get_config()->compA, TIMER16BIT_CMOD_SET_OCnX); // Normal polarity means we want to clear pin when reaching OCR value + ASSERT_EQ(timer_16_bit_stub_get_config()->compB, TIMER16BIT_CMOD_NORMAL); // unchanged + timer_16_bit_stub_reset(); + timer_16_bit_stub_set_initialised(true); + } + } + + // Checking Pwm 9 bit Full Range behavior + { + const uint32_t cpu_freq = 16 MHz; + + timer_16_bit_waveform_generation_t configs[2U] = + { + TIMER16BIT_WG_PWM_FAST_9_bit_FULL_RANGE, + TIMER16BIT_WG_PWM_PHASE_CORRECT_9_bit_FULL_RANGE + }; + + for(const auto config : configs) + { + pwm_props_t properties = { .frequency = 1 MHz, .duty_cycle = 50U, .pol = PWM_POLARITY_NORMAL }; + (void) timer_16_bit_set_waveform_generation(0U, config); + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_16_bit_stub_get_config()->compA, TIMER16BIT_CMOD_CLEAR_OCnX); // Normal polarity means we want to clear pin when reaching OCR value + ASSERT_EQ(timer_16_bit_stub_get_config()->compB, TIMER16BIT_CMOD_NORMAL); // unchanged + ASSERT_EQ(timer_16_bit_stub_get_config()->prescaler, TIMER16BIT_CLK_PRESCALER_1); + ASSERT_EQ(timer_16_bit_stub_get_config()->ocra, (properties.duty_cycle * COUNTER_MAX_VALUE_9_BIT) / 100U); + ASSERT_EQ(timer_16_bit_stub_get_config()->ocrb, 0U); + ASSERT_EQ(timer_16_bit_stub_get_config()->icr, 0U); + ASSERT_EQ(properties.duty_cycle, 50U); // Duty cycle is preserved + ASSERT_EQ(properties.frequency, (cpu_freq / (timer_16_bit_stub_get_config()->prescaler * (COUNTER_MAX_VALUE_9_BIT + 1)))); // Actual output frequency is updated accordingly + + properties.pol = PWM_POLARITY_INVERTED; + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_16_bit_stub_get_config()->compA, TIMER16BIT_CMOD_SET_OCnX); // Normal polarity means we want to clear pin when reaching OCR value + ASSERT_EQ(timer_16_bit_stub_get_config()->compB, TIMER16BIT_CMOD_NORMAL); // unchanged + timer_16_bit_stub_reset(); + timer_16_bit_stub_set_initialised(true); + } + } + + // Checking Pwm 10 bit Full Range behavior + { + const uint32_t cpu_freq = 16 MHz; + + + timer_16_bit_waveform_generation_t configs[2U] = + { + TIMER16BIT_WG_PWM_FAST_10_bit_FULL_RANGE, + TIMER16BIT_WG_PWM_PHASE_CORRECT_10_bit_FULL_RANGE + }; + + for(const auto config : configs) + { + pwm_props_t properties = { .frequency = 1 MHz, .duty_cycle = 50U, .pol = PWM_POLARITY_NORMAL }; + (void) timer_16_bit_set_waveform_generation(0U, config); + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_16_bit_stub_get_config()->compA, TIMER16BIT_CMOD_CLEAR_OCnX); // Normal polarity means we want to clear pin when reaching OCR value + ASSERT_EQ(timer_16_bit_stub_get_config()->compB, TIMER16BIT_CMOD_NORMAL); // unchanged + ASSERT_EQ(timer_16_bit_stub_get_config()->prescaler, TIMER16BIT_CLK_PRESCALER_1); + ASSERT_EQ(timer_16_bit_stub_get_config()->ocra, (properties.duty_cycle * COUNTER_MAX_VALUE_10_BIT) / 100U); + ASSERT_EQ(timer_16_bit_stub_get_config()->ocrb, 0U); + ASSERT_EQ(timer_16_bit_stub_get_config()->icr, 0U); + ASSERT_EQ(properties.duty_cycle, 50U); // Duty cycle is preserved + ASSERT_EQ(properties.frequency, (cpu_freq / (timer_16_bit_stub_get_config()->prescaler * (COUNTER_MAX_VALUE_10_BIT + 1)))); // Actual output frequency is updated accordingly + + properties.pol = PWM_POLARITY_INVERTED; + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_16_bit_stub_get_config()->compA, TIMER16BIT_CMOD_SET_OCnX); // Normal polarity means we want to clear pin when reaching OCR value + ASSERT_EQ(timer_16_bit_stub_get_config()->compB, TIMER16BIT_CMOD_NORMAL); // unchanged + timer_16_bit_stub_reset(); + timer_16_bit_stub_set_initialised(true); + } + } + + // Checking Pwm Phase Correct Full Range behavior + { + const uint32_t cpu_freq = 16 MHz; + + + timer_16_bit_waveform_generation_t configs[3U] = + { + TIMER16BIT_WG_PWM_FAST_ICR_MAX, + TIMER16BIT_WG_PWM_PHASE_CORRECT_ICR_MAX, + TIMER16BIT_WG_PWM_PHASE_AND_FREQ_CORRECT_ICR_MAX + }; + + for(const auto config : configs) + { + pwm_props_t properties = { .frequency = 1 MHz, .duty_cycle = 50U, .pol = PWM_POLARITY_NORMAL }; + + (void) timer_16_bit_set_waveform_generation(0U, config); + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_16_bit_stub_get_config()->compA, TIMER16BIT_CMOD_CLEAR_OCnX); // Normal polarity means we want to clear pin when reaching OCR value + ASSERT_EQ(timer_16_bit_stub_get_config()->compB, TIMER16BIT_CMOD_NORMAL); // unchanged + ASSERT_EQ(timer_16_bit_stub_get_config()->prescaler, TIMER16BIT_CLK_PRESCALER_1); + ASSERT_EQ(timer_16_bit_stub_get_config()->ocra, 7U); + ASSERT_EQ(timer_16_bit_stub_get_config()->icr, 15U); + ASSERT_EQ(timer_16_bit_stub_get_config()->ocrb, 0U); + + ASSERT_EQ(properties.duty_cycle, 50U); // Duty cycle is preserved + ASSERT_EQ(properties.frequency, 1 MHz); // Actual output frequency is updated accordingly + + properties.pol = PWM_POLARITY_INVERTED; + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_16_bit_stub_get_config()->compA, TIMER16BIT_CMOD_SET_OCnX); // Normal polarity means we want to clear pin when reaching OCR value + ASSERT_EQ(timer_16_bit_stub_get_config()->compB, TIMER16BIT_CMOD_NORMAL); // unchanged + + timer_16_bit_stub_reset(); + timer_16_bit_stub_set_initialised(true); + } + } + + // Checking Pwm Fast Mode OCRA Max behavior + { + const uint32_t cpu_freq = 16 MHz; + + + timer_16_bit_waveform_generation_t configs[3U] = + { + TIMER16BIT_WG_PWM_FAST_OCRA_MAX, + TIMER16BIT_WG_PWM_PHASE_CORRECT_OCRA_MAX, + TIMER16BIT_WG_PWM_PHASE_AND_FREQ_CORRECT_OCRA_MAX + }; + + for(const auto config : configs) + { + pwm_props_t properties = { .frequency = 1 MHz, .duty_cycle = 25U, .pol = PWM_POLARITY_NORMAL }; + + (void) timer_16_bit_set_waveform_generation(0U, config); + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_16_bit_stub_get_config()->compA, TIMER16BIT_CMOD_TOGGLE_OCnX); // Normal polarity means we want to clear pin when reaching OCR value + ASSERT_EQ(timer_16_bit_stub_get_config()->compB, TIMER16BIT_CMOD_NORMAL); // unchanged + ASSERT_EQ(timer_16_bit_stub_get_config()->prescaler, TIMER16BIT_CLK_PRESCALER_1); + ASSERT_EQ(timer_16_bit_stub_get_config()->ocra, 7U); + ASSERT_EQ(timer_16_bit_stub_get_config()->icr, 0U); + ASSERT_EQ(timer_16_bit_stub_get_config()->ocrb, 0U); + + ASSERT_EQ(properties.duty_cycle, 50U); // Duty cycle is preserved + ASSERT_EQ(properties.frequency, 1 MHz); // Actual output frequency is updated accordingly + + timer_16_bit_stub_reset(); + timer_16_bit_stub_set_initialised(true); + } + } +} + +TEST_F(PwmModule16BitTimerTestSuite, unit_b_behavioral_testing) +{ + // Rewriting parts of pwm_config + pwm_config.hard[0].arch = TIMER_ARCH_16_BIT; + pwm_config.hard[0].timer_index = 0U; + pwm_config.hard[0].unit = PWM_HARD_TIMER_UNIT_B; + pwm_error_t error = PWM_ERROR_OK; + + // Init the timer_16_bit beforehand + timer_16_bit_stub_set_initialised(true); + error = pwm_init(); + ASSERT_EQ(PWM_ERROR_OK, error); + + + // Checking Pwm 8 bit Full Range behavior + { + const uint32_t cpu_freq = 16 MHz; + + timer_16_bit_waveform_generation_t configs[2U] = + { + TIMER16BIT_WG_PWM_FAST_8_bit_FULL_RANGE, + TIMER16BIT_WG_PWM_PHASE_CORRECT_8_bit_FULL_RANGE + }; + + for(const auto config : configs) + { + pwm_props_t properties = { .frequency = 1 MHz, .duty_cycle = 50U, .pol = PWM_POLARITY_NORMAL }; + (void) timer_16_bit_set_waveform_generation(0U, config); + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_16_bit_stub_get_config()->compA, TIMER16BIT_CMOD_NORMAL); // unchanged + ASSERT_EQ(timer_16_bit_stub_get_config()->compB, TIMER16BIT_CMOD_CLEAR_OCnX); // Normal polarity means we want to clear pin when reaching OCR value + ASSERT_EQ(timer_16_bit_stub_get_config()->prescaler, TIMER16BIT_CLK_PRESCALER_1); + ASSERT_EQ(timer_16_bit_stub_get_config()->ocrb, (properties.duty_cycle * COUNTER_MAX_VALUE_8_BIT) / 100U); + ASSERT_EQ(timer_16_bit_stub_get_config()->ocra, 0U); + ASSERT_EQ(timer_16_bit_stub_get_config()->icr, 0U); + ASSERT_EQ(properties.duty_cycle, 50U); // Duty cycle is preserved + ASSERT_EQ(properties.frequency, (cpu_freq / (timer_16_bit_stub_get_config()->prescaler * (COUNTER_MAX_VALUE_8_BIT + 1)))); // Actual output frequency is updated accordingly + + properties.pol = PWM_POLARITY_INVERTED; + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_16_bit_stub_get_config()->compA, TIMER16BIT_CMOD_NORMAL); // unchanged + ASSERT_EQ(timer_16_bit_stub_get_config()->compB, TIMER16BIT_CMOD_SET_OCnX); // Normal polarity means we want to clear pin when reaching OCR value + timer_16_bit_stub_reset(); + timer_16_bit_stub_set_initialised(true); + } + } + + // Checking Pwm 9 bit Full Range behavior + { + const uint32_t cpu_freq = 16 MHz; + + timer_16_bit_waveform_generation_t configs[2U] = + { + TIMER16BIT_WG_PWM_FAST_9_bit_FULL_RANGE, + TIMER16BIT_WG_PWM_PHASE_CORRECT_9_bit_FULL_RANGE + }; + + for(const auto config : configs) + { + pwm_props_t properties = { .frequency = 1 MHz, .duty_cycle = 50U, .pol = PWM_POLARITY_NORMAL }; + (void) timer_16_bit_set_waveform_generation(0U, config); + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_16_bit_stub_get_config()->compA, TIMER16BIT_CMOD_NORMAL); // unchanged + ASSERT_EQ(timer_16_bit_stub_get_config()->compB, TIMER16BIT_CMOD_CLEAR_OCnX); // Normal polarity means we want to clear pin when reaching OCR value + ASSERT_EQ(timer_16_bit_stub_get_config()->prescaler, TIMER16BIT_CLK_PRESCALER_1); + ASSERT_EQ(timer_16_bit_stub_get_config()->ocra, 0U); + ASSERT_EQ(timer_16_bit_stub_get_config()->ocrb, (properties.duty_cycle * COUNTER_MAX_VALUE_9_BIT) / 100U); + ASSERT_EQ(timer_16_bit_stub_get_config()->icr, 0U); + ASSERT_EQ(properties.duty_cycle, 50U); // Duty cycle is preserved + ASSERT_EQ(properties.frequency, (cpu_freq / (timer_16_bit_stub_get_config()->prescaler * (COUNTER_MAX_VALUE_9_BIT + 1)))); // Actual output frequency is updated accordingly + + properties.pol = PWM_POLARITY_INVERTED; + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_16_bit_stub_get_config()->compA, TIMER16BIT_CMOD_NORMAL); // unchanged + ASSERT_EQ(timer_16_bit_stub_get_config()->compB, TIMER16BIT_CMOD_SET_OCnX); // Normal polarity means we want to clear pin when reaching OCR value + timer_16_bit_stub_reset(); + timer_16_bit_stub_set_initialised(true); + } + } + + // Checking Pwm 10 bit Full Range behavior + { + const uint32_t cpu_freq = 16 MHz; + + + timer_16_bit_waveform_generation_t configs[2U] = + { + TIMER16BIT_WG_PWM_FAST_10_bit_FULL_RANGE, + TIMER16BIT_WG_PWM_PHASE_CORRECT_10_bit_FULL_RANGE + }; + + for(const auto config : configs) + { + pwm_props_t properties = { .frequency = 1 MHz, .duty_cycle = 50U, .pol = PWM_POLARITY_NORMAL }; + (void) timer_16_bit_set_waveform_generation(0U, config); + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_16_bit_stub_get_config()->compA, TIMER16BIT_CMOD_NORMAL); // unchanged + ASSERT_EQ(timer_16_bit_stub_get_config()->compB, TIMER16BIT_CMOD_CLEAR_OCnX); // Normal polarity means we want to clear pin when reaching OCR value + ASSERT_EQ(timer_16_bit_stub_get_config()->prescaler, TIMER16BIT_CLK_PRESCALER_1); + ASSERT_EQ(timer_16_bit_stub_get_config()->ocra, 0U); + ASSERT_EQ(timer_16_bit_stub_get_config()->ocrb, (properties.duty_cycle * COUNTER_MAX_VALUE_10_BIT) / 100U); + ASSERT_EQ(timer_16_bit_stub_get_config()->icr, 0U); + ASSERT_EQ(properties.duty_cycle, 50U); // Duty cycle is preserved + ASSERT_EQ(properties.frequency, (cpu_freq / (timer_16_bit_stub_get_config()->prescaler * (COUNTER_MAX_VALUE_10_BIT + 1)))); // Actual output frequency is updated accordingly + + properties.pol = PWM_POLARITY_INVERTED; + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_16_bit_stub_get_config()->compA, TIMER16BIT_CMOD_NORMAL); // unchanged + ASSERT_EQ(timer_16_bit_stub_get_config()->compB, TIMER16BIT_CMOD_SET_OCnX); // Normal polarity means we want to clear pin when reaching OCR value + timer_16_bit_stub_reset(); + timer_16_bit_stub_set_initialised(true); + } + } + + // Checking Pwm ICR mode + { + const uint32_t cpu_freq = 16 MHz; + + + timer_16_bit_waveform_generation_t configs[3U] = + { + TIMER16BIT_WG_PWM_FAST_ICR_MAX, + TIMER16BIT_WG_PWM_PHASE_CORRECT_ICR_MAX, + TIMER16BIT_WG_PWM_PHASE_AND_FREQ_CORRECT_ICR_MAX + }; + + for(const auto config : configs) + { + pwm_props_t properties = { .frequency = 1 MHz, .duty_cycle = 50U, .pol = PWM_POLARITY_NORMAL }; + + (void) timer_16_bit_set_waveform_generation(0U, config); + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_16_bit_stub_get_config()->compA, TIMER16BIT_CMOD_NORMAL); // unchanged + ASSERT_EQ(timer_16_bit_stub_get_config()->compB, TIMER16BIT_CMOD_CLEAR_OCnX); // Normal polarity means we want to clear pin when reaching OCR value + ASSERT_EQ(timer_16_bit_stub_get_config()->prescaler, TIMER16BIT_CLK_PRESCALER_1); + ASSERT_EQ(timer_16_bit_stub_get_config()->icr, 15U); + ASSERT_EQ(timer_16_bit_stub_get_config()->ocrb, 7U); + ASSERT_EQ(timer_16_bit_stub_get_config()->ocra, 0U); + + ASSERT_EQ(properties.duty_cycle, 50U); // Duty cycle is preserved + ASSERT_EQ(properties.frequency, 1 MHz); // Actual output frequency is updated accordingly + + properties.pol = PWM_POLARITY_INVERTED; + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_16_bit_stub_get_config()->compB, TIMER16BIT_CMOD_SET_OCnX); // Normal polarity means we want to clear pin when reaching OCR value + ASSERT_EQ(timer_16_bit_stub_get_config()->compA, TIMER16BIT_CMOD_NORMAL); // unchanged + + timer_16_bit_stub_reset(); + timer_16_bit_stub_set_initialised(true); + } + } + + // Checking Pwm Fast Mode OCRA Max behavior + { + const uint32_t cpu_freq = 16 MHz; + + + timer_16_bit_waveform_generation_t configs[3U] = + { + TIMER16BIT_WG_PWM_FAST_OCRA_MAX, + TIMER16BIT_WG_PWM_PHASE_CORRECT_OCRA_MAX, + TIMER16BIT_WG_PWM_PHASE_AND_FREQ_CORRECT_OCRA_MAX + }; + + for(const auto config : configs) + { + pwm_props_t properties = { .frequency = 1 MHz, .duty_cycle = 25U, .pol = PWM_POLARITY_NORMAL }; + + (void) timer_16_bit_set_waveform_generation(0U, config); + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_16_bit_stub_get_config()->compA, TIMER16BIT_CMOD_NORMAL); // Normal polarity means we want to clear pin when reaching OCR value + ASSERT_EQ(timer_16_bit_stub_get_config()->compB, TIMER16BIT_CMOD_CLEAR_OCnX); // unchanged + ASSERT_EQ(timer_16_bit_stub_get_config()->prescaler, TIMER16BIT_CLK_PRESCALER_1); + ASSERT_EQ(timer_16_bit_stub_get_config()->ocra, 15U); + ASSERT_EQ(timer_16_bit_stub_get_config()->icr, 0U); + ASSERT_EQ(timer_16_bit_stub_get_config()->ocrb, 3U); + + ASSERT_EQ(properties.duty_cycle, 25U); // Duty cycle is preserved + ASSERT_EQ(properties.frequency, 1 MHz); // Actual output frequency is updated accordingly + + properties.pol = PWM_POLARITY_INVERTED; + error = pwm_config_single(0U, PWM_TYPE_HARDWARE, &properties, &cpu_freq); + ASSERT_EQ(PWM_ERROR_OK, error); + ASSERT_EQ(timer_16_bit_stub_get_config()->compA, TIMER16BIT_CMOD_NORMAL); // unchanged + ASSERT_EQ(timer_16_bit_stub_get_config()->compB, TIMER16BIT_CMOD_SET_OCnX); // Normal polarity means we want to clear pin when reaching OCR value + + timer_16_bit_stub_reset(); + timer_16_bit_stub_set_initialised(true); + } + } +} + + +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} + + diff --git a/Modules/Pwm/docs/Timer8bit_dual_channel.png b/Modules/Pwm/docs/Timer8bit_dual_channel.png new file mode 100644 index 0000000..a4fd765 Binary files /dev/null and b/Modules/Pwm/docs/Timer8bit_dual_channel.png differ diff --git a/Modules/Pwm/docs/Timer8bit_dual_channel.xopp b/Modules/Pwm/docs/Timer8bit_dual_channel.xopp new file mode 100644 index 0000000..5a43ee8 Binary files /dev/null and b/Modules/Pwm/docs/Timer8bit_dual_channel.xopp differ diff --git a/Modules/Pwm/docs/Timer8bit_single_channel.png b/Modules/Pwm/docs/Timer8bit_single_channel.png new file mode 100644 index 0000000..e88ba9c Binary files /dev/null and b/Modules/Pwm/docs/Timer8bit_single_channel.png differ diff --git a/Modules/Pwm/docs/Timer8bit_single_channel.xopp b/Modules/Pwm/docs/Timer8bit_single_channel.xopp new file mode 100644 index 0000000..a1590be Binary files /dev/null and b/Modules/Pwm/docs/Timer8bit_single_channel.xopp differ diff --git a/Modules/Pwm/examples/CMakeLists.txt b/Modules/Pwm/examples/CMakeLists.txt new file mode 100644 index 0000000..af95b13 --- /dev/null +++ b/Modules/Pwm/examples/CMakeLists.txt @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 3.20) + +################################################################################################# +##################################### PWM driver library ######################################## +################################################################################################# + +project(pwm_driver_example C) + +set(SOURCES + ${CMAKE_CURRENT_SOURCE_DIR}/main.c + ${CMAKE_CURRENT_SOURCE_DIR}/config.c +) + +set(HEADERS + ${CMAKE_CURRENT_SOURCE_DIR}/config.h +) + +add_executable(pwm_driver_example + ${SOURCES} + ${HEADERS} +) + +target_include_directories(pwm_driver_example PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR} + ${AVR_INCLUDES} +) + +target_link_libraries(pwm_driver_example pwm_driver ) diff --git a/Modules/Pwm/examples/config.c b/Modules/Pwm/examples/config.c new file mode 100644 index 0000000..e38844c --- /dev/null +++ b/Modules/Pwm/examples/config.c @@ -0,0 +1,77 @@ +#include "config.h" +#include "pwm.h" +#include "timebase.h" +#include "timer_8_bit.h" +#include "timer_8_bit_async.h" +#include "timer_16_bit.h" + +#ifndef F_CPU +#pragma message("F_CPU macro was not set, defaulting to 8MHz") + #define F_CPU (8'000'000ULL) +#endif + +timebase_config_t timebase_static_config[TIMEBASE_MAX_INSTANCES] = +{ + [0] = + { + .timer = + { + .type = TIMER_ARCH_8_BIT, + .index = TIMEBASE_TARGETED_TIMER_IDX, + }, + .clock_freq = F_CPU, + .timescale = TIMEBASE_TIMESCALE_MILLISECONDS + } +}; + +pwm_static_config_t pwm_config = +{ + .hard = + { + // Hardware based PWM configuration block + [MOTOR_1_HARD_PWM_IDX] = + { + .arch = TIMER_ARCH_16_BIT, + .unit = PWM_HARD_TIMER_UNIT_A, + .timer_index = MOTOR_1_TIMER_IDX + }, + [MOTOR_2_HARD_PWM_IDX] = + { + .arch = TIMER_ARCH_16_BIT, + .unit = PWM_HARD_TIMER_UNIT_B, + .timer_index = MOTOR_2_TIMER_IDX + } + }, + .soft = + { + // Software based PWM configuration block + [FAN_1_SOFT_PWM_IDX] = + { + .io_index = FAN_1_IO_IDX, + .timebase_index = FAN_1_TIMEBASE_IDX + }, + [FAN_2_SOFT_PWM_IDX] = + { + .io_index = FAN_2_IO_IDX, + .timebase_index = FAN_2_TIMEBASE_IDX + } + } +}; + +io_t io_pins_lut[IO_MAX_PINS] = +{ + [FAN_1_IO_IDX] = + { + .direction = IO_OUT_PUSH_PULL, + .pin = FAN_1_IO_PIN, + .port = IO_PORT_B, + .state = IO_STATE_LOW + }, + [FAN_2_IO_IDX] = + { + .direction = IO_OUT_PUSH_PULL, + .pin = FAN_2_IO_PIN, + .port = IO_PORT_B, + .state = IO_STATE_LOW + } +} \ No newline at end of file diff --git a/Modules/Pwm/examples/config.h b/Modules/Pwm/examples/config.h new file mode 100644 index 0000000..d106cab --- /dev/null +++ b/Modules/Pwm/examples/config.h @@ -0,0 +1,67 @@ +#ifndef PWM_DRIVER_CONFIG_HEADER_EXAMPLE +#define PWM_DRIVER_CONFIG_HEADER_EXAMPLE + +#ifdef __cplusplus +extern "C" { +#endif + +// Pseudo timebase configuration (needed by the software PWM driver) +#define TIMEBASE_MAX_INSTANCES 1U +#define TIMEBASE_TARGETED_TIMER_IDX 0U +#define IO_MAX_PINS 2U + +// Pseudo timer configuration as per registered in Timer 8 bit driver +#define MOTOR_1_TIMER_IDX 0U +#define MOTOR_2_TIMER_IDX MOTOR_1_TIMER_IDX // both motor output pins are driven by the same timer + +// Software PWM implementation +// Pseudo pin mapping -> interacts with the IO driver + +#define FAN_1_IO_IDX 0U +#define FAN_1_IO_PIN 2U +// FAN_1_IO_PORT is directly defined in config.c to benefit from type checking + +#define FAN_2_IO_IDX 1U +#define FAN_2_IO_PIN 3U +// FAN_2_IO_PORT is directly defined in config.c to benefit from type checking + +#define FAN_1_TIMEBASE_IDX 0U // Selecting the first timebase module +#define FAN_2_TIMEBASE_IDX FAN_1_TIMEBASE_IDX // Same as FAN_1_TIMEBASE module + +// Configure PWM driver to have 2 instances +#define PWM_MAX_HARD_INSTANCES 2 +#define PWM_MAX_SOFT_INSTANCES 2 + +// Pseudo PWM mapping +#define FAN_1_SOFT_PWM_IDX 0 +#define FAN_2_SOFT_PWM_IDX 1 +#define MOTOR_1_HARD_PWM_IDX 0 +#define MOTOR_2_HARD_PWM_IDX 1 + + + + + + + + + + + + + + + + + + + + + + + +#ifdef __cplusplus +} +#endif + +#endif /* PWM_DRIVER_CONFIG_HEADER_EXAMPLE */ \ No newline at end of file diff --git a/Modules/Pwm/examples/main.c b/Modules/Pwm/examples/main.c new file mode 100644 index 0000000..fd87afa --- /dev/null +++ b/Modules/Pwm/examples/main.c @@ -0,0 +1,47 @@ +#include "config.h" +#include "timer_8_bit.h" +#include "pwm.h" +#include "avr/io.h" + +/** + * @brief Drivers initialisation is carried out by this function. + * + * @return true : initialisation succeeded + * @return false : initialisation failed + */ +static bool driver_init(void) +{ + bool ret = true; + { + timer_8_bit_config_t config = {0}; + timer_error_t err = timer_8_bit_get_default_config(&config); + config.handle.OCRA = OCR0A; + config.handle.OCRB = OCR0B; + config.handle.TCCRA = TCCR0A; + config.handle.TCCRB = TCCR0B; + config.handle.TCNT = TCNT0; + config.handle.TIFR = TIFR0; + config.handle.TIMSK = TIMSK0; + config.timing_config.prescaler = TIMER8BIT_CLK_PRESCALER_1; + config.timing_config.waveform_mode = TIMER8BIT_WG_PWM_FAST_FULL_RANGE; + config.timing_config.comp_match_a = TIMER8BIT_CMOD_NORMAL; + config.timing_config.comp_match_a = TIMER8BIT_CMOD_NORMAL; + // TODO : properly setup the config object before initialising the timer itself + err = timer_8_bit_init(MOTOR_1_TIMER_IDX, &config); + ret &= TIMER_ERROR_OK == err; + } + { + pwm_error_t err = pwm_init(); + ret &= PWM_ERR_OK == err; + } + +} + +int main(void) +{ + while(1) + { + asm("NOP"); + } + return 0; +} \ No newline at end of file diff --git a/Modules/Pwm/inc/pwm.h b/Modules/Pwm/inc/pwm.h new file mode 100644 index 0000000..2553613 --- /dev/null +++ b/Modules/Pwm/inc/pwm.h @@ -0,0 +1,352 @@ +#ifndef PWM_HEADER +#define PWM_HEADER + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include + +#include "io.h" +#include "config.h" +#include "timer_generic.h" + +#ifndef PWM_MAX_SOFT_INSTANCES +#define PWM_MAX_SOFT_INSTANCES 1U +#warning "PWM_MAX_SOFT_INSTANCES macro not defined in config.h, fallsback to 1U by default" +#endif + +#ifndef PWM_MAX_HARD_INSTANCES +#define PWM_MAX_HARD_INSTANCES 1U +#warning "PWM_MAX_HARD_INSTANCES macro not defined in config.h, fallsback to 1U by default" +#endif + +#define MHz * 1000000UL +#define kHz * 1000U + +#define COUNTER_MAX_VALUE_8_BIT 255U +#define COUNTER_MAX_VALUE_9_BIT 511U +#define COUNTER_MAX_VALUE_10_BIT 1023U +#define COUNTER_MAX_VALUE_16_BIT 65535U + +/** + * @brief PWM module error types used to inform caller how operations performed. +*/ +typedef enum +{ + PWM_ERROR_OK = 0, /**< Operation succeeded, no issues */ + PWM_ERROR_CONFIG, /**< Something is wrong with the configuration */ + PWM_ERROR_IO_ISSUE, /**< Error caught when communicating with IO driver */ + PWM_ERROR_TIMER_ISSUE, /**< Timer drivers encountered some errors */ + PWM_ERROR_TIMEBASE_ISSUE, /**< Software timebase has encountered some errors */ + PWM_ERROR_INDEX_OUT_OF_RANGE, /**< Given pwm instance index is out of range */ + PWM_ERROR_NOT_IMPLEMENTED /**< Specific function is not implemented (yet) */ +} pwm_error_t; + +/** + * @brief Encodes the two kinds of hardware unit (slices?) of avr timers. + * There are usually two of them, units A and B, using the same counter and master settings, but using different OCRxx values + */ +typedef enum +{ + PWM_HARD_TIMER_UNIT_UNDEFINED, /**< Default value, no unit selected */ + PWM_HARD_TIMER_UNIT_A, /**< Hardware timer unit A */ + PWM_HARD_TIMER_UNIT_B, /**< Hardware timer unit B */ +} pwm_hard_timer_unit_t; + +/** + * @brief Encodes the two kinds of PWM this driver handles (either software based or hardware based PWMs) + */ +typedef enum +{ + PWM_TYPE_UNKNOWN, /**< Default configuration for PWM type enum */ + PWM_TYPE_SOFTWARE, /**< Software based PWM, software wrapper will be used to toggle discrete IOs */ + PWM_TYPE_HARDWARE /**< Hardware based PWM, used to get the maximum speed, reliability and accuracy straight from hardware timers */ +} pwm_type_t; + +typedef enum +{ + PWM_POLARITY_NORMAL, /**< PWM signal starts at the HIGH state (output pin is set to VCC when counter reaches TOP) */ + PWM_POLARITY_INVERTED, /**< PWM signal starts at the LOW state (output pin is set to GND when counter reaches TOP) */ +} pwm_polarity_t; + +// ################################################################################################ +// ############################ Compile time static configuration ################################# +// ################################################################################################ + +/** + * @brief Compile-time configuration used to configure this Pwm module. + * Note that a union is used, so soft and hard configurations occupy the same memory space. +*/ +typedef struct +{ + timer_arch_t arch; /**< Tells which kind of timer is targeted by this PWM instance */ + uint8_t timer_index; /**< Tells which timer we need to use for this PWM instance */ + pwm_hard_timer_unit_t unit; /**< Gives the unit kind of this PWM instance */ +} pwm_hard_static_config_t; + +/** + * @brief Compile-time configuration used to configure this Pwm module. +*/ +typedef struct +{ + uint8_t io_index; /**< IO index from the Io lookup table @see static io configuration */ + uint8_t timebase_index; /**< Timebase index used to look at the watch */ + io_state_t safe_state; /**< Safe io state used by the driver when initialising and when stopped to configure the targeted pin to a known safe state */ +} pwm_soft_static_config_t; + +/** + * @brief Packs the two kinds of PWM configurations (either software or hardware configs) + */ +typedef struct +{ + pwm_hard_static_config_t hard[PWM_MAX_HARD_INSTANCES]; /**< Encodes the hardware static configuration */ + pwm_soft_static_config_t soft[PWM_MAX_SOFT_INSTANCES]; /**< Encodes the software static configuration */ +} pwm_static_config_t; + + +// ################################################################################################ +// ########################### Software based PWM event generation################################# +// ################################################################################################ + +typedef void (*pwm_soft_event_callback_t)(void); + +/** + * @brief Describes the various events supported by the software based PWM generation mode + */ +typedef enum +{ + PWM_SOFT_EVENT_RISING_EDGE, /**< Event is generated when PWM signal goes from low state to high state (rising edge) */ + PWM_SOFT_EVENT_FALLING_EDGE,/**< Event is generated when PWM signal goes from high state to low state (falling edge) */ + PWM_SOFT_EVENT_RESET, /**< Event is generated when a single PWM period is achieved (when the PWM resets) */ + PWM_SOFT_EVENT_TOGGLED, /**< Event is generated when PWM signal changes states (either Falling/Rising edge), covers the 3 above cases. */ +} pwm_soft_event_t; + +/** + * @brief Encodes the various kind of TOP value used by a hardware timer counter to set the resolution (impacts frequency as well) + * They are essentially used by this PWM driver to configure Timer registers when configuring resolution and frequency. + * As TOP value varies depending on Timer waveform generation modes, + * we shall take this into account and update the right register accordingly + */ +typedef enum +{ + PWM_HARD_TIMER_TOP_UNDEFINED, /**< Default value, no top value selected */ + PWM_HARD_TIMER_TOP_MAX_RES, /**< Counter will reach max value permitted by WG mode (see datasheet for that) */ + PWM_HARD_TIMER_TOP_OCR, /**< Counter will go until the OCRx1 value before starting over */ + PWM_HARD_TIMER_TOP_ICR, /**< Counter will go until the ICRx1 value before starting over */ +} pwm_hard_timer_top_t; + +// ################################################################################################ +// ################################ PWM signal characterisation ################################### +// ################################################################################################ + +/** + * @brief PWM intrinsic properties. Used to describe and characterise a single PWM signal + */ +typedef struct +{ + uint32_t frequency; /**< Desired frequency of the timer counter */ + uint8_t duty_cycle; /**< Desired duty cycle, ranging from 0 to 100 */ + pwm_polarity_t pol; /**< Desired start polarity on the targeted pin */ +} pwm_props_t; + +/** + * @brief Hardware PWM complementary configuration structure + * Used to produce alternating patterns of PWM for a single Timer unit. + * Note that it only works with double slope configurations such as : + * - TIMER8BIT_WG_PWM_PHASE_CORRECT_XXX => Timer 8 bit phase correct setting + * - TIMER16BIT_WG_PWM_PHASE_CORRECT_XXX => Timer 16 bit phase correct setting + * - TIMER16BIT_WG_PWM_PHASE_AND_FREQ_CORRECT_XXX => Timer 16 bit phase and frequency correct setting + * - TIMER8BIT_ASYNC_WG_PWM_PHASE_CORRECT_XXX => Timer 8 bit async phase correct setting + * + * @details This structure configures the drivers to produce such patterns : + * time |start ----------restart------------> + * |───┐ ┊ ┌───┊───┐ ┊ ┌───| + * OCRnA | └────┊────┘ ┊ └────┊────┘ | There is a dead time being generated when OCRnA or OCRnB is pulled down, so both lines are down for some time + * | ┌───┊───┐ ┊ ┌───┊───┐ | before the other is switched on again. + * OCRnB |────┘ ┊ └────┊────┘ ┊ └────| Here, OCRnA is approx 40% duty cycle and OCRnB is approx 50% + * | ^ ┊ ^ | + * ╵ ╵ + * BTM TOP TOP BTM BTM : timer's counter reached BOTTOM value and starts counting up again (ascending slope) + * TOP : timer's counter reached TOP value and starts counting down (descending slope) + */ +typedef struct +{ + pwm_props_t properties; /**< PWM intrinsic properties */ + uint8_t timer_index; /**< Timer index as registered in pwm config structure */ + int8_t dead_time; /**< Dead time generation feature, ranging from [ -100, (100 - duty_cycle) ] + Note : dead time can be negative (we want some overlap). + Dead time is used to generate complementary PWM that do not overlap + It basically provides a time when none of the PWM is turned ON, which is + useful to counteract inductive/capacitive parasitic coupling and gives + enough time for a mosfet to fully turn off, for instance */ +} pwm_hard_compl_config_t; + +// ################################################################################################ +// ####################################### API definition ######################################### +// ################################################################################################ + +/** + * @brief initialises this pwm driver using the static configuration written in config.c + * Note : this functions does not take care about initialising other modules it depends on, this + * should be handled by application software before this function is called. + * @warning Pwm driver does not alter Timer's hardware configuration, so in order for this driver to work properly, + * you must ensure Timer drivers are initialised properly with the right settings ! + * This is done like so because it's quite difficult, from the PWM driver's point of view, to know exactly what the user wants. + * So instead of providing a complete frontend (i.e : another wrapping api) to the Timer's stack, its easier and clearer to rely on proper + * timer initialisation instead. + * @return pwm_error_t: + * PWM_ERR_OK : operation succeeded + * PWM_ERR_TIMEBASE_ISSUE : operation did not succeed because of timebase module errors + * PWM_ERROR_TIMER_ISSUE : operation did not succeed because of timer drivers errors +*/ +pwm_error_t pwm_init(void); + +/** + * @brief processes data for the PWM module (essentially used for the software pwm) + * @return pwm_error_t: + * PWM_ERR_OK : operation succeeded + * PWM_ERR_TIMEBASE_ISSUE : operation did not succeed because of timebase module errors + * PWM_ERROR_TIMER_ISSUE : operation did not succeed because of timer drivers errors +*/ +pwm_error_t pwm_process(void); + +/** + * @brief Starts a single PWM. Has no effect if the PWM is already started + * @param index : index of targeted PWM instance within configuration structure @see pwm_config. + * @param type : states the type of PWM, either software or hardware. + * This is needed because configurations are separated in two different configuration arrays + * @return pwm_error_t: + * PWM_ERR_OK : operation succeeded + * PWM_ERR_TIMEBASE_ISSUE : operation did not succeed because of timebase module errors + * PWM_ERROR_TIMER_ISSUE : operation did not succeed because of timer drivers errors +*/ +pwm_error_t pwm_start(const uint8_t index, const pwm_type_t type); + +/** + * @brief Starts all registered PWMs in a row + * @return pwm_error_t: + * PWM_ERR_OK : operation succeeded + * PWM_ERR_TIMEBASE_ISSUE : operation did not succeed because of timebase module errors + * PWM_ERROR_TIMER_ISSUE : operation did not succeed because of timer drivers errors +*/ +pwm_error_t pwm_start_all(void); + +/** + * @brief Stops a single PWM. Has no effect if the PWM is already stopped + * @param index : index of targeted PWM instance within configuration structure @see pwm_config. + * @return pwm_error_t: + * PWM_ERR_OK : operation succeeded + * PWM_ERR_TIMEBASE_ISSUE : operation did not succeed because of timebase module errors + * PWM_ERROR_TIMER_ISSUE : operation did not succeed because of timer drivers errors +*/ +pwm_error_t pwm_stop(const uint8_t index, const pwm_type_t type); + +/** + * @brief Stops all registered PWM instances in a row. + * @return pwm_error_t: + * PWM_ERR_OK : operation succeeded + * PWM_ERR_TIMEBASE_ISSUE : operation did not succeed because of timebase module errors + * PWM_ERROR_TIMER_ISSUE : operation did not succeed because of timer drivers errors +*/ +pwm_error_t pwm_stop_all(void); + +/** + * @brief Configures a single PWM instance (either software based or hardware based) and tries to achieve given pwm characteristics. + * @note Depending on the underlying selected hardware timer, some limitations might be encountered such as : + * - Dual pwm channel on 8 bit timers are only available with a frequency fixed by the prescaler (TOP value of counter is always 0xFF) + * -> duty cycle has a fine control over the full range 0 - 255 + * -> Output frequency is limit to a few frequencies as per this relation : F_CPU/(prescaler*TOP) ; where TOP = 0xFF + * which for a CPU frequency of 16 MHz gives : | prescaler | 1 | 8 | 64 | 256 | 1024 | + * | Frequency (Hz) | 62500 | 7812 | 976 | 244 | 61 | + * - Single pwm channel on 8 bit timers need to sacrifice the unit A in order to get full control over B unit's frequency AND duty cycle alltogether. + * -> This is achieve through the use of WG FAST PWM OCRA MAX mode and WG PHASE CORRECT OCRA MAX modes + * This driver checks the actual values of TCCRxx, looking for WG modes and COMxA/B modes. + * Those values will then be taken into account in order to configure the timer accordingly, but this function will not configure WG and COM modes for + * you, this is the responsibility of the Timer initialisation steps. + * + * @note This function might not, under some circumstances, achieve the desired output frequency or desired duty cycle, because of underlying hardware timers limitations. + * The properties parameters is used as an output and will reflect the 'real' achieved PWM characteristics, as per configured in Timer's hardware registers. + * @param[in] index : pwm instance index as per given in config.c file + * @param[in] type : kind of PWM driver used (either software based or hardware based) + * @param[in] clock_freq : current CPU frequency (note that if CPU frequency changes, output PWM will be off as well) + * @param[in,out] properties : [in] : gives the desired output PWM characteristics, [out] : carries the actual frequency and duty cycle as per configured by this function + * @return pwm_error_t: + * PWM_ERR_OK : operation succeeded + * PWM_ERR_TIMEBASE_ISSUE : operation did not succeed because of timebase module errors + * PWM_ERROR_TIMER_ISSUE : operation did not succeed because of timer drivers errors + * PWM_ERROR_CONFIG : PWM configuration error, driver or dependencies were not configured correctly +*/ +pwm_error_t pwm_config_single(const uint8_t index, const pwm_type_t type, pwm_props_t * const properties, const uint32_t * clock_freq); + +/** + * @brief probes actual configuration of a single PWM instance. + * This function can be used to determine actual pwm frequency and duty cycle, accounting underlying hardware/software limitations. + * Its functionality is already covered by the @see pwm_config_single function, which returns the actual current properties of a targeted PWM instance, + * but this one is read-only and does not write into Timer's registers. + * + * @param[in] index + * @param[in] type + * @param[out] properties + * @return pwm_error_t + */ +pwm_error_t pwm_probe_config(const uint8_t index, const pwm_type_t type, pwm_props_t * const properties, const uint32_t * clock_freq); + +/** + * @brief Configures a particular timer to output complementary PWM with dead time generation. + * It uses the Phase correct and phase and frequency correct modes in order to achieve proper dead time generation + * and symmetrical PWM output signals. + * @see pwm_hard_compl_config_t structure for more details. + * @param config : input configuration structure + * @param clock_freq : current CPU frequency, used to calculate timer's configuration parameters + * @return pwm_error_t: + * PWM_ERR_OK : operation succeeded + * PWM_ERROR_TIMER_ISSUE : operation did not succeed because of timer drivers errors + */ +pwm_error_t pwm_hard_config_complementary(pwm_hard_compl_config_t const * const config, const uint32_t * clock_freq ); + +/** + * @brief Configures a single event for the targeted software PWM + * + * @param[in] index : index of targeted software PWM as per configured in static configuration (@see pwm_config.soft) + * @param[in] callback : callback function which will be called when the event is reached + * @param[in] when : on what kind of event the callback should be registered + * @return pwm_error_t + * PWM_ERR_OK : operation succeeded + * PWM_ERROR_CONFIG : input do not make sense (probably and issue with the "when" parameter) + * PWM_ERROR_INDEX_OUT_OF_RANGE : targeted index is out of range + */ +pwm_error_t pwm_soft_register_event(const uint8_t index, const pwm_soft_event_callback_t callback, const pwm_soft_event_t when); + +/** + * @brief Pops a single event from a software pwm instance + * + * @param index : index of targeted software PWM as per configured in static configuration (@see pwm_config.soft) + * @param when : what kind of event should be cleared + * @return pwm_error_t + * PWM_ERR_OK : operation succeeded + * PWM_ERROR_INDEX_OUT_OF_RANGE : targeted index is out of range + * PWM_ERROR_CONFIG : input do not make sense (probably and issue with the "when" parameter) + */ +pwm_error_t pwm_soft_remove_event(const uint8_t index, const pwm_soft_event_t when); + +/** + * @brief Removes all events at once for a single software PWM instance + * + * @param index + * @return pwm_error_t + * PWM_ERR_OK : operation succeeded + * PWM_ERROR_INDEX_OUT_OF_RANGE : targeted index is out of range + */ +pwm_error_t pwm_soft_clear_all_events(const uint8_t index); + +// Static configuration for both software based and hardware based pwms +extern pwm_static_config_t pwm_config; /** Static compile-time configuration used by this driver (needs to be implemented in config.c)*/ + + +#ifdef __cplusplus +} +#endif + +#endif /* PWM_HEADER */ \ No newline at end of file diff --git a/Modules/Pwm/src/pwm.c b/Modules/Pwm/src/pwm.c new file mode 100644 index 0000000..fb80c36 --- /dev/null +++ b/Modules/Pwm/src/pwm.c @@ -0,0 +1,1237 @@ +#include "io.h" +#include "pwm.h" +#include "timebase.h" +#include "timer_8_bit.h" +#include "timer_16_bit.h" +#include "timer_8_bit_async.h" + + +#ifndef PWM_MAX_SOFT_INSTANCES +#define PWM_MAX_SOFT_INSTANCES 1U +#warning "PWM_MAX_SOFT_INSTANCES macro not defined in config.h, fallsback to 1U by default" +#endif + +#ifndef PWM_MAX_HARD_INSTANCES +#define PWM_MAX_HARD_INSTANCES 1U +#warning "PWM_MAX_HARD_INSTANCES macro not defined in config.h, fallsback to 1U by default" +#endif + + +/** + * @brief Static storage for pwm configuration + * This is done this way to enhance the dynamic nature of PWMs, so that we can + * still override their config at anytime (especially frequencies and duty cycles, not) +*/ +static struct +{ + uint16_t period; /**< Gives the time period in ticks for a single software PWM */ + uint16_t switch_tick; /**< Used to implement duty cycle and events. */ + // The switch tick indicates when a pin has to change its state */ + uint16_t last_tick; /**< Holds the last tick retrieved from timebase module */ + uint16_t start_tick; /**< Holds the start tick of the current frame/period retrieved from timebase module */ + io_state_t state; /**< Records the current IO state, so that we can keep track of the duty cycle as well */ + bool started; /**< Records if a specific software pwm is started or not */ + struct + { + pwm_soft_event_callback_t rising_edge; /**< Rising edge event */ + pwm_soft_event_callback_t falling_edge; /**< Falling edge event */ + pwm_soft_event_callback_t toggling; /**< Toggled pin event */ + pwm_soft_event_callback_t reset; /**< Counter reset event*/ + } events; +} soft_config[PWM_MAX_SOFT_INSTANCES] = {0}; + + +static pwm_error_t pwm_init_single_hard(pwm_hard_static_config_t const * const config); + +/** + * @brief Configures a single timer 8 bit instance based on common parameters + * @param index : pwm instance index + * @param properties : pwm specifications (aka properties) + * @param clock_freq : current CPU frequency + * @return pwm_error_t : + * PWM_ERROR_OK : operation succeeded + * PWM_ERROR_CONFIG : PWM configuration error, driver or dependencies were not configured correctly + * PWM_ERROR_TIMER_ISSUE : encountered issues when configuring underlying timer, probably a global configuration error + */ +static pwm_error_t configure_timer_8_bit_single(const uint8_t index, pwm_props_t * const properties, const uint32_t * clock_freq); + +/** + * @brief Configures a single timer 8 bit asynchronous instance based on common parameters + * @param index : pwm instance index + * @param properties : pwm specifications (aka properties) + * @param clock_freq : current CPU frequency + * @return pwm_error_t : + * PWM_ERROR_OK : operation succeeded + * PWM_ERROR_CONFIG : PWM configuration error, driver or dependencies were not configured correctly + * PWM_ERROR_TIMER_ISSUE : encountered issues when configuring underlying timer, probably a global configuration error + */ +static pwm_error_t configure_timer_8_bit_async_single(const uint8_t index, pwm_props_t * const properties, const uint32_t * clock_freq); + +/** + * @brief Configures a single timer 16 bit instance based on common parameters + * @param index : pwm instance index + * @param properties : pwm specifications (aka properties) + * @param clock_freq : current CPU frequency + * @return pwm_error_t : + * PWM_ERROR_OK : operation succeeded + * PWM_ERROR_CONFIG : PWM configuration error, driver or dependencies were not configured correctly + * PWM_ERROR_TIMER_ISSUE : encountered issues when configuring underlying timer, probably a global configuration error + */ +static pwm_error_t configure_timer_16_bit_single(const uint8_t index, pwm_props_t * const properties, const uint32_t * clock_freq); + +/** + * @brief Verifies that pwm index fits within the range + * + * @param index : index to be checked + * @return true : index is valid and can be used without buffer overflow issues + * @return false : index is out of range and cannot be used as is. + */ +static inline bool index_valid(const uint8_t index, const pwm_type_t type); + +pwm_error_t pwm_init(void) +{ + pwm_error_t err = PWM_ERROR_OK; + + // Check IO Driver is initialised + // And check Timebase module is initialised as well + + + // Iterate over pwm_config and search hard configurations + for (uint8_t i = 0 ; i < PWM_MAX_HARD_INSTANCES ; i++) + { + err |= pwm_init_single_hard(&pwm_config.hard[i]); + } + + if (err != PWM_ERROR_OK) + { + err = PWM_ERROR_CONFIG; + } + + return err; +} + + +static pwm_error_t check_initialisation(pwm_hard_static_config_t const * const config) +{ + pwm_error_t err = PWM_ERROR_OK; + bool timer_initialised = false; + timer_error_t timerr = TIMER_ERROR_OK; + switch(config->arch) + { + case TIMER_ARCH_8_BIT: + timerr = timer_8_bit_is_initialised(config->timer_index, &timer_initialised); + break; + + case TIMER_ARCH_8_BIT_ASYNC: + timerr = timer_8_bit_async_is_initialised(config->timer_index, &timer_initialised); + break; + + case TIMER_ARCH_16_BIT: + timerr = timer_16_bit_is_initialised(config->timer_index, &timer_initialised); + break; + + default: + err = PWM_ERROR_CONFIG; + break; + } + // Handle the valid timer arch cases when api failed + if ((PWM_ERROR_OK == err) && (TIMER_ERROR_OK != timerr)) + { + err = PWM_ERROR_CONFIG; + } + + // Reject requests if timer drivers are not initialised beforehand + if(false == timer_initialised) + { + err = PWM_ERROR_CONFIG; + } + + return err; +} + +// This function essentially checks timer are setup correctly +// and initialisation steps were performed prior to use the pwm driver. +static pwm_error_t pwm_init_single_hard(pwm_hard_static_config_t const * const config) +{ + pwm_error_t ret = PWM_ERROR_OK; + timer_error_t timerr = TIMER_ERROR_OK; + + ret = check_initialisation(config); + if(PWM_ERROR_OK == ret) + { + switch(config->arch) + { + case TIMER_ARCH_8_BIT: + timerr = timer_8_bit_stop(config->timer_index); + break; + + case TIMER_ARCH_8_BIT_ASYNC: + timerr = timer_8_bit_async_stop(config->timer_index); + break; + + case TIMER_ARCH_16_BIT: + timerr = timer_16_bit_stop(config->timer_index); + break; + + default: + ret = PWM_ERROR_CONFIG; + break; + } + } + + + // Timer should be already initialised, otherwise we are trying to use peripheral in an undefined state + // that might not behave as we expect. + if (TIMER_ERROR_OK != timerr) + { + ret = PWM_ERROR_CONFIG; + } + + return ret; +} + +pwm_error_t pwm_start(const uint8_t index, const pwm_type_t type) +{ + pwm_error_t ret = PWM_ERROR_OK; + if (!index_valid(index, type)) + { + return PWM_ERROR_INDEX_OUT_OF_RANGE; + } + + if( PWM_TYPE_SOFTWARE == type) + { + // Start software PWM + pwm_soft_static_config_t * config = &pwm_config.soft[index]; + timebase_error_t timberr = TIMEBASE_ERROR_OK; + io_error_t ioerr = IO_ERROR_OK; + + ioerr = io_write(config->io_index, config->safe_state); + if(IO_ERROR_OK != ioerr) + { + ret = PWM_ERROR_IO_ISSUE; + } + + // Handling timebase module interaction + if(PWM_ERROR_OK == ret) + { + uint16_t tick = 0; + timberr = timebase_get_tick(config->timebase_index, &tick); + if(TIMEBASE_ERROR_OK != timberr) + { + ret = PWM_ERROR_TIMEBASE_ISSUE; + } + else + { + soft_config[index].start_tick = tick; + soft_config[index].last_tick = tick; + soft_config[index].started = true; + } + } + } + else + { + pwm_hard_static_config_t * config = &pwm_config.hard[index]; + timer_error_t timerr = TIMER_ERROR_OK; + switch(config->arch) + { + case TIMER_ARCH_8_BIT : + timerr = timer_8_bit_start(config->timer_index); + break; + + case TIMER_ARCH_8_BIT_ASYNC : + timerr = timer_8_bit_async_start(config->timer_index); + break; + + case TIMER_ARCH_16_BIT : + timerr = timer_16_bit_start(config->timer_index); + break; + + default: + ret = PWM_ERROR_CONFIG; + break; + } + + // Caught some issue with the timer drivers + if(TIMER_ERROR_OK != timerr) + { + ret = PWM_ERROR_TIMER_ISSUE; + } + } + + return ret; +} + +pwm_error_t pwm_stop(const uint8_t index, const pwm_type_t type) +{ + pwm_error_t ret = PWM_ERROR_OK; + if (!index_valid(index, type)) + { + return PWM_ERROR_INDEX_OUT_OF_RANGE; + } + + if( PWM_TYPE_SOFTWARE == type) + { + // Stop software pwm + } + else + { + pwm_hard_static_config_t * config = &pwm_config.hard[index]; + timer_error_t timerr = TIMER_ERROR_OK; + switch(config->arch) + { + case TIMER_ARCH_8_BIT : + timerr = timer_8_bit_stop(config->timer_index); + break; + + case TIMER_ARCH_8_BIT_ASYNC : + timerr = timer_8_bit_async_stop(config->timer_index); + break; + + case TIMER_ARCH_16_BIT : + timerr = timer_16_bit_stop(config->timer_index); + break; + + default: + ret = PWM_ERROR_CONFIG; + break; + } + + // Caught some issue with the timer drivers + if(TIMER_ERROR_OK != timerr) + { + ret = PWM_ERROR_TIMER_ISSUE; + } + } + + return ret; +} + +pwm_error_t pwm_config_single(const uint8_t index, const pwm_type_t type, pwm_props_t * const properties, const uint32_t * clock_freq) +{ + pwm_error_t ret = PWM_ERROR_OK; + if (PWM_TYPE_HARDWARE == type) + { + pwm_hard_static_config_t * timer_config = &pwm_config.hard[index]; + + // At this point the accumulator should be 0, otherwise it means the generated PWM is not slow enough + // and this driver does not support it for now + switch(timer_config->arch) + { + case TIMER_ARCH_8_BIT: + ret = configure_timer_8_bit_single(index, properties, clock_freq); + break; + + case TIMER_ARCH_8_BIT_ASYNC: + ret = configure_timer_8_bit_async_single(index, properties, clock_freq); + break; + + case TIMER_ARCH_16_BIT: + ret = configure_timer_16_bit_single(index, properties, clock_freq); + break; + + default: + ret = PWM_ERROR_CONFIG; + break; + + } + } + // Software based PWM + else + { + pwm_soft_static_config_t * timer_config = &pwm_config.soft[index]; + io_error_t err = io_write(timer_config->io_index, timer_config->safe_state); + if (IO_ERROR_OK != err) + { + ret = PWM_ERROR_CONFIG; + } + + // Same, reject timebase misinitialisation as well + bool timebase_initialised = false; + timebase_error_t timberr = timebase_is_initialised(timer_config->timebase_index, &timebase_initialised); + if(TIMEBASE_ERROR_OK != timberr || false == timebase_initialised) + { + ret = PWM_ERROR_CONFIG; + } + uint16_t period = 0; + + if(PWM_ERROR_OK == ret) + { + timberr = timebase_compute_period_from_frequency(timer_config->timebase_index, &properties->frequency, TIMEBASE_FREQUENCY_HZ, &period); + if(TIMEBASE_ERROR_OK != timberr || false == timebase_initialised) + { + ret = PWM_ERROR_CONFIG; + } + } + + // Transaction with IO driver and Timebase Module are validated, we can now start to work with the registered pin. + if (PWM_ERROR_OK == ret) + { + soft_config[index].period = period; + soft_config[index].switch_tick = (properties->duty_cycle * period) / 100U; + } + } + + return ret; +} + + +static pwm_error_t configure_timer_8_bit_single(const uint8_t index, pwm_props_t * const properties, const uint32_t * clock_freq) +{ + timer_error_t timerr = TIMER_ERROR_OK; + pwm_error_t ret = PWM_ERROR_OK; + + uint8_t ocr_value = 0; + uint16_t prescaler_value = 1U; + + timer_8_bit_prescaler_selection_t prescaler = TIMER8BIT_CLK_NO_CLOCK; + timer_8_bit_waveform_generation_t waveform = TIMER8BIT_WG_NORMAL; + pwm_hard_static_config_t * timer_config = &pwm_config.hard[index]; + + // Compute closest prescaler first + timer_8_bit_compute_closest_prescaler(clock_freq, &properties->frequency, &prescaler); + prescaler_value = timer_8_bit_prescaler_to_value(prescaler); + timerr = timer_8_bit_set_prescaler(index, prescaler); + if (TIMER_ERROR_OK != timerr) + { + return PWM_ERROR_TIMER_ISSUE; + } + + // We need to probe the waveform generation modes from Timer driver because it drives the way we configure a PWM afterwards + // (Because timer8 bit has some limitations when it comes to having PWMs with full frequency and duty cycle control, a lot depends on the Waveform Generation) + timerr = timer_8_bit_get_waveform_generation(timer_config->timer_index, &waveform); + if (TIMER_ERROR_OK != timerr) + { + return PWM_ERROR_TIMER_ISSUE; + } + + // Handles the hardware PWM unit A or B + if ( PWM_HARD_TIMER_UNIT_A == timer_config->unit) + { + // The waveform selection mode tells us about the kind of desired PWM + switch(waveform) + { + // TOP value is set to 255 + case TIMER8BIT_WG_PWM_FAST_FULL_RANGE: + case TIMER8BIT_WG_PWM_PHASE_CORRECT_FULL_RANGE: + { + // Select the polarity + if(properties->pol == PWM_POLARITY_NORMAL) + { + // When OCRA is hit, output pin should be cleared (means that output pin is SET high when counter reaches TOP) + timerr = timer_8_bit_set_compare_match_A(timer_config->timer_index, TIMER8BIT_CMOD_CLEAR_OCnX); + } + else + { + // When OCRA is hit, output pin should be set (means that output pin is CLEARED low when counter reaches TOP) + timerr = timer_8_bit_set_compare_match_A(timer_config->timer_index, TIMER8BIT_CMOD_SET_OCnX); + } + if(TIMER_ERROR_OK != timerr) + { + return PWM_ERROR_TIMER_ISSUE; + } + + // OCRA value represents the duty_cycle value now + ocr_value = (properties->duty_cycle * COUNTER_MAX_VALUE_8_BIT) / 100U; + timerr = timer_8_bit_set_ocra_register_value(timer_config->timer_index, ocr_value); + if(TIMER_ERROR_OK != timerr) + { + return PWM_ERROR_TIMER_ISSUE; + } + + // Properties object needs to be updated with the actual frequency used by the timer + properties->frequency = (*clock_freq / (prescaler_value * (COUNTER_MAX_VALUE_8_BIT + 1))); + + break; + } + // TOP value is controlled by OCRA value + case TIMER8BIT_WG_PWM_FAST_OCRA_MAX: + case TIMER8BIT_WG_PWM_PHASE_CORRECT_OCRA_MAX: + // Here, the only configuration that will make the pin work as a real PWM output (wihtout glitches) is the TIMER8BIT_CMOD_TOGGLE_OCnX + // So it will effectively divide the output frequency by 2 in both Fast PWM mode and Phase correct PWM mode (which already divides output frequency by 2) + timerr = timer_8_bit_set_compare_match_A(timer_config->timer_index, TIMER8BIT_CMOD_TOGGLE_OCnX); + if (TIMER_ERROR_OK != timerr) + { + return PWM_ERROR_TIMER_ISSUE; + } + + ocr_value = (*clock_freq / (prescaler_value * properties->frequency)) - 1; + // Trying to compensate for the 50% duty cycle and toggling mode which halves the output frequency + if(ocr_value > 2 ) + { + ocr_value /= 2; + } + // TODO : consider using the prescaler as well, for instance if ocra is too small, we might be able to use a higher prescaler + // E.g : ocra can be divided by 8 (say 248 / 8 => 31) and prescaler = 1 then we can use a prescaler of 8 and have ocra at 31, and vice-versa if needed. + timerr = timer_8_bit_set_ocra_register_value(index, ocr_value); + if (TIMER_ERROR_OK != timerr) + { + return PWM_ERROR_TIMER_ISSUE; + } + + // Properties object needs to be updated with the actual frequency used by the timer + // Note that OCRA needs to be multiplied by 2 because of the toggling nature of the output pin ; + // So it behaves as if ocra was twice as large with a normal COMP mode switching + properties->frequency = (*clock_freq / (prescaler_value * (ocr_value + 1) * 2)); + properties->duty_cycle = 50U; + break; + + // Misconfigured Timer, could not proceed further + case TIMER8BIT_WG_CTC: + case TIMER8BIT_WG_NORMAL: + default: + return PWM_ERROR_CONFIG; + } + + } + // UNIT B has a different treatment than UNIT A on timer 8 bit and variants + else /* UNIT B */ + { + // Select the polarity + if(properties->pol == PWM_POLARITY_NORMAL) + { + // When OCRB is hit, output pin should be cleared (means that output pin is SET high when counter reaches TOP) + timerr = timer_8_bit_set_compare_match_B(timer_config->timer_index, TIMER8BIT_CMOD_CLEAR_OCnX); + } + else + { + // When OCRB is hit, output pin should be set (means that output pin is CLEARED low when counter reaches TOP) + timerr = timer_8_bit_set_compare_match_B(timer_config->timer_index, TIMER8BIT_CMOD_SET_OCnX); + } + + // The waveform selection mode tells us about the kind of desired PWM + switch(waveform) + { + // TOP value is set to 255 + // This configuration is valid for fixed frequency PWM with precise control over duty_cycle + case TIMER8BIT_WG_PWM_FAST_FULL_RANGE: + case TIMER8BIT_WG_PWM_PHASE_CORRECT_FULL_RANGE: + { + // OCRB value represents the duty_cycle value now + ocr_value = (properties->duty_cycle * COUNTER_MAX_VALUE_8_BIT) / 100U; + timerr = timer_8_bit_set_ocrb_register_value(timer_config->timer_index, ocr_value); + if(TIMER_ERROR_OK != timerr) + { + return PWM_ERROR_TIMER_ISSUE; + } + properties->frequency = (*clock_freq / (prescaler_value * (COUNTER_MAX_VALUE_8_BIT + 1))); + break; + } + + // TOP value is controlled by OCRA value + case TIMER8BIT_WG_PWM_FAST_OCRA_MAX: + case TIMER8BIT_WG_PWM_PHASE_CORRECT_OCRA_MAX: + { + + // Compute exact frequency value for OCRA + // We don't try to modify OCRA value to compensate for 50% output frequency, because in this case + // the output pin is not toggled + ocr_value = (*clock_freq / (prescaler_value * properties->frequency)) - 1; + // Set timer ocra value, to control output frequency + timerr = timer_8_bit_set_ocra_register_value(index, ocr_value); + if (TIMER_ERROR_OK != timerr) + { + return PWM_ERROR_TIMER_ISSUE; + } + properties->frequency = (*clock_freq / (prescaler_value * (ocr_value + 1))); + + // Compute exact duty cycle value for OCRB (reuse the ocr_value variable) + ocr_value = (properties->duty_cycle * ocr_value) / 100U; + // Set timer ocrb value, to control duty_cycle + timerr = timer_8_bit_set_ocrb_register_value(index, ocr_value); + if (TIMER_ERROR_OK != timerr) + { + return PWM_ERROR_TIMER_ISSUE; + } + break; + } + // Misconfigured Timer, could not proceed further + case TIMER8BIT_WG_CTC: + case TIMER8BIT_WG_NORMAL: + default: + return PWM_ERROR_CONFIG; + } + } + return ret; +} + +// Timer 8 bit async share the same infrastructure as the 8 bit one +static pwm_error_t configure_timer_8_bit_async_single(const uint8_t index, pwm_props_t * const properties, const uint32_t * clock_freq) +{ + timer_error_t timerr = TIMER_ERROR_OK; + pwm_error_t ret = PWM_ERROR_OK; + + uint8_t ocr_value = 0; + uint16_t prescaler_value = 1U; + + timer_8_bit_async_prescaler_selection_t prescaler = TIMER8BIT_ASYNC_CLK_NO_CLOCK; + timer_8_bit_async_waveform_generation_t waveform = TIMER8BIT_ASYNC_WG_NORMAL; + pwm_hard_static_config_t * timer_config = &pwm_config.hard[index]; + + // Compute closest prescaler first + timer_8_bit_async_compute_closest_prescaler(clock_freq, &properties->frequency, &prescaler); + prescaler_value = timer_8_bit_async_prescaler_to_value(prescaler); + timerr = timer_8_bit_async_set_prescaler(index, prescaler); + if (TIMER_ERROR_OK != timerr) + { + return PWM_ERROR_TIMER_ISSUE; + } + + // We need to probe the waveform generation modes from Timer driver because it drives the way we configure a PWM afterwards + // (Because timer8 bit has some limitations when it comes to having PWMs with full frequency and duty cycle control, a lot depends on the Waveform Generation) + timerr = timer_8_bit_async_get_waveform_generation(timer_config->timer_index, &waveform); + if (TIMER_ERROR_OK != timerr) + { + return PWM_ERROR_TIMER_ISSUE; + } + + // Handles the hardware PWM unit A or B + if ( PWM_HARD_TIMER_UNIT_A == timer_config->unit) + { + // The waveform selection mode tells us about the kind of desired PWM + switch(waveform) + { + // TOP value is set to 255 + case TIMER8BIT_ASYNC_WG_PWM_FAST_FULL_RANGE: + case TIMER8BIT_ASYNC_WG_PWM_PHASE_CORRECT_FULL_RANGE: + { + // Select the polarity + if(properties->pol == PWM_POLARITY_NORMAL) + { + // When OCRA is hit, output pin should be cleared (means that output pin is SET high when counter reaches TOP) + timerr = timer_8_bit_async_set_compare_match_A(timer_config->timer_index, TIMER8BIT_ASYNC_CMOD_CLEAR_OCnX); + } + else + { + // When OCRA is hit, output pin should be set (means that output pin is CLEARED low when counter reaches TOP) + timerr = timer_8_bit_async_set_compare_match_A(timer_config->timer_index, TIMER8BIT_ASYNC_CMOD_SET_OCnX); + } + if(TIMER_ERROR_OK != timerr) + { + return PWM_ERROR_TIMER_ISSUE; + } + + // OCRA value represents the duty_cycle value now + ocr_value = (properties->duty_cycle * COUNTER_MAX_VALUE_8_BIT) / 100U; + timerr = timer_8_bit_async_set_ocra_register_value(timer_config->timer_index, ocr_value); + if(TIMER_ERROR_OK != timerr) + { + return PWM_ERROR_TIMER_ISSUE; + } + + // Properties object needs to be updated with the actual frequency used by the timer + properties->frequency = (*clock_freq / (prescaler_value * (COUNTER_MAX_VALUE_8_BIT + 1))); + + break; + } + // TOP value is controlled by OCRA value + case TIMER8BIT_ASYNC_WG_PWM_FAST_OCRA_MAX: + case TIMER8BIT_ASYNC_WG_PWM_PHASE_CORRECT_OCRA_MAX: + // Here, the only configuration that will make the pin work as a real PWM output (wihtout glitches) is the TIMER8BIT_CMOD_TOGGLE_OCnX + // So it will effectively divide the output frequency by 2 in both Fast PWM mode and Phase correct PWM mode (which already divides output frequency by 2) + timerr = timer_8_bit_async_set_compare_match_A(timer_config->timer_index, TIMER8BIT_ASYNC_CMOD_TOGGLE_OCnX); + if (TIMER_ERROR_OK != timerr) + { + return PWM_ERROR_TIMER_ISSUE; + } + + ocr_value = (*clock_freq / (prescaler_value * properties->frequency)) - 1; + // Trying to compensate for the 50% duty cycle and toggling mode which halves the output frequency + if(ocr_value > 2 ) + { + ocr_value /= 2; + } + // TODO : consider using the prescaler as well, for instance if ocra is too small, we might be able to use a higher prescaler + // E.g : ocra can be divided by 8 (say 248 / 8 => 31) and prescaler = 1 then we can use a prescaler of 8 and have ocra at 31, and vice-versa if needed. + timerr = timer_8_bit_async_set_ocra_register_value(index, ocr_value); + if (TIMER_ERROR_OK != timerr) + { + return PWM_ERROR_TIMER_ISSUE; + } + + // Properties object needs to be updated with the actual frequency used by the timer + // Note that OCRA needs to be multiplied by 2 because of the toggling nature of the output pin ; + // So it behaves as if ocra was twice as large with a normal COMP mode switching + properties->frequency = (*clock_freq / (prescaler_value * (ocr_value + 1) * 2)); + properties->duty_cycle = 50U; + + break; + + // Misconfigured Timer, could not proceed further + case TIMER8BIT_ASYNC_WG_CTC: + case TIMER8BIT_ASYNC_WG_NORMAL: + default: + return PWM_ERROR_CONFIG; + } + + } + // UNIT B has a different treatment than UNIT A on timer 8 bit and variants + else /* UNIT B */ + { + // Select the polarity + if(properties->pol == PWM_POLARITY_NORMAL) + { + // When OCRB is hit, output pin should be cleared (means that output pin is SET high when counter reaches TOP) + timerr = timer_8_bit_async_set_compare_match_B(timer_config->timer_index, TIMER8BIT_ASYNC_CMOD_CLEAR_OCnX); + } + else + { + // When OCRB is hit, output pin should be set (means that output pin is CLEARED low when counter reaches TOP) + timerr = timer_8_bit_async_set_compare_match_B(timer_config->timer_index, TIMER8BIT_ASYNC_CMOD_SET_OCnX); + } + + // The waveform selection mode tells us about the kind of desired PWM + switch(waveform) + { + // TOP value is set to 255 + // This configuration is valid for fixed frequency PWM with precise control over duty_cycle + case TIMER8BIT_ASYNC_WG_PWM_FAST_FULL_RANGE: + case TIMER8BIT_ASYNC_WG_PWM_PHASE_CORRECT_FULL_RANGE: + { + // OCRB value represents the duty_cycle value now + ocr_value = (properties->duty_cycle * COUNTER_MAX_VALUE_8_BIT) / 100U; + timerr = timer_8_bit_async_set_ocrb_register_value(index, ocr_value); + if (TIMER_ERROR_OK != timerr) + { + return PWM_ERROR_TIMER_ISSUE; + } + properties->frequency = (*clock_freq / (prescaler_value * (COUNTER_MAX_VALUE_8_BIT + 1))); + + break; + } + + // TOP value is controlled by OCRA value + case TIMER8BIT_ASYNC_WG_PWM_FAST_OCRA_MAX: + case TIMER8BIT_ASYNC_WG_PWM_PHASE_CORRECT_OCRA_MAX: + { + + // Compute exact frequency value for OCRA + // We don't try to modify OCRA value to compensate for 50% output frequency, because in this case + // the output pin is not toggled + ocr_value = (*clock_freq / (prescaler_value * properties->frequency)) - 1; + // Set timer ocra value, to control output frequency + timerr = timer_8_bit_async_set_ocra_register_value(index, ocr_value); + if (TIMER_ERROR_OK != timerr) + { + return PWM_ERROR_TIMER_ISSUE; + } + properties->frequency = (*clock_freq / (prescaler_value * (ocr_value + 1))); + + // Compute exact duty cycle value for OCRB (reuse the ocr_value variable) + ocr_value = (properties->duty_cycle * ocr_value) / 100U; + // Set timer ocrb value, to control duty_cycle + timerr = timer_8_bit_async_set_ocrb_register_value(index, ocr_value); + if (TIMER_ERROR_OK != timerr) + { + return PWM_ERROR_TIMER_ISSUE; + } + break; + } + // Misconfigured Timer, could not proceed further + case TIMER8BIT_ASYNC_WG_CTC: + case TIMER8BIT_ASYNC_WG_NORMAL: + default: + return PWM_ERROR_CONFIG; + } + } + return ret; +} + +/** + * @brief Retrieves the current time resolution (either 8, 9, 10 or 16 bits) based on the given waveform generation mode + * + * @param waveform : waveform generation mode used by hardware timer implementation + * @return converted value, or TIMER_GENERIC_RESOLUTION_UNDEFINED if input waveform does not match a valid selection + */ +static timer_generic_resolution_t derive_16_bit_timer_resolution_from_waveform_selection(const timer_16_bit_waveform_generation_t waveform) +{ + timer_generic_resolution_t resolution = TIMER_GENERIC_RESOLUTION_UNDEFINED; + switch(waveform) + { + // TOP value is set to 255 + case TIMER16BIT_WG_PWM_FAST_8_bit_FULL_RANGE: + case TIMER16BIT_WG_PWM_PHASE_CORRECT_8_bit_FULL_RANGE: + resolution = TIMER_GENERIC_RESOLUTION_8_BIT; + break; + + // TOP value is set to 511 + case TIMER16BIT_WG_PWM_FAST_9_bit_FULL_RANGE: + case TIMER16BIT_WG_PWM_PHASE_CORRECT_9_bit_FULL_RANGE: + resolution = TIMER_GENERIC_RESOLUTION_9_BIT; + break; + + // Top value is set to 1023 + case TIMER16BIT_WG_PWM_FAST_10_bit_FULL_RANGE: + case TIMER16BIT_WG_PWM_PHASE_CORRECT_10_bit_FULL_RANGE: + resolution = TIMER_GENERIC_RESOLUTION_10_BIT; + break; + // TOP value is governed by ICR register + case TIMER16BIT_WG_PWM_FAST_ICR_MAX: + case TIMER16BIT_WG_PWM_PHASE_CORRECT_ICR_MAX: + case TIMER16BIT_WG_PWM_PHASE_AND_FREQ_CORRECT_ICR_MAX: + case TIMER16BIT_WG_PWM_FAST_OCRA_MAX: + case TIMER16BIT_WG_PWM_PHASE_CORRECT_OCRA_MAX: + case TIMER16BIT_WG_PWM_PHASE_AND_FREQ_CORRECT_OCRA_MAX: + resolution = TIMER_GENERIC_RESOLUTION_16_BIT; + break; + + // Misconfigured Timer, could not proceed further + // The following modes are generally used to trigger interrupts and are not used as PWM modes + // In this context, those values are not valid + case TIMER16BIT_WG_NORMAL: + case TIMER16BIT_WG_CTC_ICR_MAX: + case TIMER16BIT_WG_CTC_OCRA_MAX: + default : + break; + } + + return resolution; +} + +/** + * @brief Specifically targets and configures a single 16 bit hardware timer using PWM expected properties, PWM instance and current timer's input clock frequency + * + * @param index : index of the referenced PWM instance as per configured in static PWM driver configuration table + * @param properties : expected (or desired) PWM properties + * @param clock_freq : current timer's input clock frequency + * @return pwm_error_t + * PWM_ERROR_OK : configuration is successful + * PWM_ERROR_TIMER_ISSUE : encountered an issue with the underlying timer's capabilities and / or configuration + * PWM_ERROR_CONFIG : something is off in the base configuration of either PWM driver or underlying timer 16 bit driver (waveform generation can be off for instance) + */ +static pwm_error_t configure_timer_16_bit_single(const uint8_t index, pwm_props_t * const properties, const uint32_t * clock_freq) +{ + timer_error_t timerr = TIMER_ERROR_OK; + pwm_error_t ret = PWM_ERROR_OK; + + uint16_t ocr_value = 0; + + timer_16_bit_prescaler_selection_t prescaler = TIMER16BIT_CLK_NO_CLOCK; + timer_16_bit_waveform_generation_t waveform = TIMER16BIT_WG_NORMAL; + pwm_hard_static_config_t * timer_config = &pwm_config.hard[index]; + uint16_t prescaler_value = 1U; + + // We need to probe the waveform generation modes from Timer driver because it drives the way we configure a PWM afterwards + // (Because timer8 bit has some limitations when it comes to having PWMs with full frequency and duty cycle control, a lot depends on the Waveform Generation) + timerr = timer_16_bit_get_waveform_generation(timer_config->timer_index, &waveform); + if (TIMER_ERROR_OK != timerr) + { + return PWM_ERROR_TIMER_ISSUE; + } + + // Retrieve actual current timer resolution based on current waveform generation mode as per set in timer 16 bit registers + timer_generic_resolution_t resolution = derive_16_bit_timer_resolution_from_waveform_selection(waveform); + + // Compute closest prescaler first + timerr = timer_16_bit_compute_closest_prescaler(clock_freq, &properties->frequency, resolution, &prescaler); + if (TIMER_ERROR_OK != timerr) + { + return PWM_ERROR_CONFIG; + } + + timerr = timer_16_bit_set_prescaler(index, prescaler); + if (TIMER_ERROR_OK != timerr) + { + return PWM_ERROR_TIMER_ISSUE; + } + prescaler_value = timer_16_bit_prescaler_to_value(prescaler); + + + // Select the polarity globally, checking special cases later on + if( PWM_HARD_TIMER_UNIT_A == timer_config->unit) + { + if(properties->pol == PWM_POLARITY_NORMAL) + { + // When OCRA is hit, output pin should be cleared (means that output pin is SET high when counter reaches TOP) + timerr = timer_16_bit_set_compare_match_A(timer_config->timer_index, TIMER16BIT_CMOD_CLEAR_OCnX); + } + else + { + // When OCRA is hit, output pin should be set (means that output pin is CLEARED low when counter reaches TOP) + timerr = timer_16_bit_set_compare_match_A(timer_config->timer_index, TIMER16BIT_CMOD_SET_OCnX); + } + } + else + { + if(properties->pol == PWM_POLARITY_NORMAL) + { + // When OCRA is hit, output pin should be cleared (means that output pin is SET high when counter reaches TOP) + timerr = timer_16_bit_set_compare_match_B(timer_config->timer_index, TIMER16BIT_CMOD_CLEAR_OCnX); + } + else + { + // When OCRA is hit, output pin should be set (means that output pin is CLEARED low when counter reaches TOP) + timerr = timer_16_bit_set_compare_match_B(timer_config->timer_index, TIMER16BIT_CMOD_SET_OCnX); + } + } + + if(TIMER_ERROR_OK != timerr) + { + return PWM_ERROR_TIMER_ISSUE; + } + + // The waveform selection mode tells us about the kind of desired PWM + switch(waveform) + { + // TOP value is set to 255 + case TIMER16BIT_WG_PWM_FAST_8_bit_FULL_RANGE: + case TIMER16BIT_WG_PWM_PHASE_CORRECT_8_bit_FULL_RANGE: + // OCR value represents duty_cycle + ocr_value = (properties->duty_cycle * COUNTER_MAX_VALUE_8_BIT) / 100U; + if( PWM_HARD_TIMER_UNIT_A == timer_config->unit) + { + timerr |= timer_16_bit_set_ocra_register_value(timer_config->timer_index, &ocr_value); + } + else + { + timerr |= timer_16_bit_set_ocrb_register_value(timer_config->timer_index, &ocr_value); + } + + // Actual output frequency is affected by this configuration, because of timer's construction + properties->frequency = *clock_freq / (prescaler_value * (COUNTER_MAX_VALUE_8_BIT + 1)); + break; + + // TOP value is set to 511 + case TIMER16BIT_WG_PWM_FAST_9_bit_FULL_RANGE: + case TIMER16BIT_WG_PWM_PHASE_CORRECT_9_bit_FULL_RANGE: + // OCR value represents duty_cycle + ocr_value = (properties->duty_cycle * COUNTER_MAX_VALUE_9_BIT) / 100U; + if( PWM_HARD_TIMER_UNIT_A == timer_config->unit) + { + timerr |= timer_16_bit_set_ocra_register_value(timer_config->timer_index, &ocr_value); + } + else + { + timerr |= timer_16_bit_set_ocrb_register_value(timer_config->timer_index, &ocr_value); + } + + // Actual output frequency is affected by this configuration, because of timer's construction + properties->frequency = *clock_freq / (prescaler_value * (COUNTER_MAX_VALUE_9_BIT + 1)); + + break; + + // Top value is set to 1023 + case TIMER16BIT_WG_PWM_FAST_10_bit_FULL_RANGE: + case TIMER16BIT_WG_PWM_PHASE_CORRECT_10_bit_FULL_RANGE: + // OCR value represents duty_cycle + ocr_value = (properties->duty_cycle * COUNTER_MAX_VALUE_10_BIT) / 100U; + if( PWM_HARD_TIMER_UNIT_A == timer_config->unit) + { + timerr |= timer_16_bit_set_ocra_register_value(timer_config->timer_index, &ocr_value); + } + else + { + timerr |= timer_16_bit_set_ocrb_register_value(timer_config->timer_index, &ocr_value); + } + + // Actual output frequency is affected by this configuration, because of timer's construction + properties->frequency = *clock_freq / (prescaler_value * (COUNTER_MAX_VALUE_10_BIT + 1)); + break; + + // TOP value is governed by ICR register + case TIMER16BIT_WG_PWM_FAST_ICR_MAX: + case TIMER16BIT_WG_PWM_PHASE_CORRECT_ICR_MAX: + case TIMER16BIT_WG_PWM_PHASE_AND_FREQ_CORRECT_ICR_MAX: + // ICR value governs frequency, wheras OCRA/B govern duty cycle + { + uint16_t icr_value = (*clock_freq / (prescaler * properties->frequency)) - 1; + ocr_value = (properties->duty_cycle * icr_value) / 100U; + timerr = timer_16_bit_set_icr_register_value(timer_config->timer_index, icr_value); + if (TIMER_ERROR_OK != timerr) + { + return PWM_ERROR_TIMER_ISSUE; + } + if( PWM_HARD_TIMER_UNIT_A == timer_config->unit) + { + timerr |= timer_16_bit_set_ocra_register_value(timer_config->timer_index, &ocr_value); + } + else + { + timerr |= timer_16_bit_set_ocrb_register_value(timer_config->timer_index, &ocr_value); + } + // Output frequency characteristics should match the input requested properties at this point + } + break; + + // TOP value is governed by OCRA value + case TIMER16BIT_WG_PWM_FAST_OCRA_MAX: + case TIMER16BIT_WG_PWM_PHASE_CORRECT_OCRA_MAX: + case TIMER16BIT_WG_PWM_PHASE_AND_FREQ_CORRECT_OCRA_MAX: + ocr_value = (*clock_freq / (prescaler * properties->frequency)) - 1; + if ( PWM_HARD_TIMER_UNIT_A == timer_config->unit) + { + // We need to explicitely check that ocr value is greater than 4, because the OCR value has + // to be greater than one, as written in the datasheets + if(ocr_value >= 4U) + { + ocr_value = ocr_value / 2U; + } + else + { + return PWM_ERROR_CONFIG; + } + timerr = timer_16_bit_set_ocra_register_value(timer_config->timer_index, &ocr_value); + + // Frequency is governed by OCRA, duty cycle is 50% and frequency is halved because output pin is toggled + // So divide OCRA by 2 (multiplying resulting frequency) in order to still get a 50% duty cycle PWM with the right frequency + timerr |= timer_16_bit_set_compare_match_A(timer_config->timer_index, TIMER16BIT_CMOD_TOGGLE_OCnX); + properties->frequency = *clock_freq / (prescaler_value * (ocr_value + 1)*2); + properties->duty_cycle = 50U; // output duty cycle is fixed + } + else + { + // OCRA is used to control output frequency, + // OCRB fine tunes the duty_cycle + + // Ocr value will become the OCRB value, using OCRA as the limit counter value (TOP) + ocr_value = (*clock_freq /(prescaler_value * properties->frequency) - 1); + timerr = timer_16_bit_set_ocra_register_value(timer_config->timer_index, &ocr_value); + + ocr_value = (properties->duty_cycle * ocr_value) / 100U; + timerr |= timer_16_bit_set_ocrb_register_value(timer_config->timer_index, &ocr_value); + + // In that case, both frequency and duty cycle are achieved through the use of this configuration. + // So output frequency characteristics should match the requested input pwm properties parameters + } + break; + + // Misconfigured Timer, could not proceed further + // The following modes are generally used to trigger interrupts and are not used as PWM modes + case TIMER16BIT_WG_NORMAL: + case TIMER16BIT_WG_CTC_ICR_MAX: + case TIMER16BIT_WG_CTC_OCRA_MAX: + default: + return PWM_ERROR_CONFIG; + } + + if(TIMER_ERROR_OK != timerr) + { + return PWM_ERROR_TIMER_ISSUE; + } + + + return ret; +} + +pwm_error_t pwm_hard_config_complementary(pwm_hard_compl_config_t const * const config, const uint32_t * clock_freq ) +{ + (void) clock_freq; + (void) config; + return PWM_ERROR_NOT_IMPLEMENTED; +} + +static inline pwm_error_t process_soft_end_of_period(const uint8_t index) +{ + soft_config[index].start_tick = soft_config[index].last_tick; + + io_error_t ioerr = io_write(pwm_config.soft[index].io_index, pwm_config.soft[index].safe_state); + if(IO_ERROR_OK != ioerr) + { + return PWM_ERROR_IO_ISSUE; + } + + // Generate the reset event + if(NULL != soft_config[index].events.reset) + { + soft_config[index].events.reset(); + } + + // Call the toggling event as well (we're changing state !) + if(NULL != soft_config[index].events.toggling) + { + soft_config[index].events.toggling(); + } + + return PWM_ERROR_OK;; +} + +static inline pwm_error_t process_soft_toggling(const uint8_t index) +{ + pwm_error_t ret = PWM_ERROR_OK; + // If starting safe state matches last updated state, it means we are still on the 'left' part of the PWM cycle + // So we need to operate the transition + if(pwm_config.soft[index].safe_state == soft_config[index].state) + { + // Update last_tick + soft_config[index].start_tick = soft_config[index].last_tick; + io_error_t ioerr = IO_ERROR_OK; + if(IO_STATE_LOW == soft_config[index].state) + { + ioerr = io_write(pwm_config.soft[index].io_index, IO_STATE_HIGH); + + // Generate event + if(IO_ERROR_OK == ioerr && NULL != soft_config[index].events.rising_edge) + { + soft_config[index].events.rising_edge(); + } + } + else + { + ioerr = io_write(pwm_config.soft[index].io_index, IO_STATE_LOW); + + // Generate event + if(IO_ERROR_OK == ioerr && NULL != soft_config[index].events.falling_edge) + { + soft_config[index].events.falling_edge(); + } + } + + // Global IOError check + if(IO_ERROR_OK != ioerr) + { + ret = PWM_ERROR_IO_ISSUE; + } + else + { + // Call the toggling event as well (we're changing state !) + if(NULL != soft_config[index].events.toggling) + { + soft_config[index].events.toggling(); + } + } + } + return ret; +} + +pwm_error_t pwm_process(void) +{ + pwm_error_t ret = PWM_ERROR_OK; + timebase_error_t timberr = TIMEBASE_ERROR_OK; + uint16_t duration = 0; + uint8_t timebase_index = 0; + + // Nothing to do for the hardware part, this is already automatically handled + // by the timers themselves. + for(uint8_t index = 0 ; index < PWM_MAX_SOFT_INSTANCES; index++) + { + if(true == soft_config[index].started) + { + timebase_index = pwm_config.soft[index].timebase_index; + timberr = timebase_get_tick(timebase_index, &soft_config[index].last_tick); + if (TIMEBASE_ERROR_OK != timberr) + { + ret = PWM_ERROR_TIMEBASE_ISSUE; + continue; + } + + timberr = timebase_get_duration(&soft_config[index].start_tick, &soft_config[index].last_tick, &duration); + if (TIMEBASE_ERROR_OK != timberr) + { + ret = PWM_ERROR_TIMEBASE_ISSUE; + continue; + } + + // We reached the end of the current period, it's time to start a new one ! + if(duration >= soft_config[index].period) + { + ret = process_soft_end_of_period(index); + continue; + } + + // We are now in the 'right' part of the PWM, there's is probably some work to do ! + if(duration >= soft_config[index].switch_tick) + { + ret = process_soft_toggling(index); + continue; + } + } + } + + // Because of the above code structure, a single error case in ret will be caught and brought back + // to calling layer for error handling + return ret; +} + + + +static inline bool index_valid(const uint8_t index, const pwm_type_t type) +{ + if (PWM_TYPE_HARDWARE == type) + { + return index < PWM_MAX_HARD_INSTANCES; + } + return index < PWM_MAX_SOFT_INSTANCES; +} + + +pwm_error_t pwm_soft_register_event(const uint8_t index, const pwm_soft_event_callback_t callback, const pwm_soft_event_t when) +{ + if(false == index_valid(index, PWM_TYPE_SOFTWARE)) + { + return PWM_ERROR_INDEX_OUT_OF_RANGE; + } + + pwm_error_t ret = PWM_ERROR_OK; + switch(when) + { + case PWM_SOFT_EVENT_FALLING_EDGE: + soft_config[index].events.falling_edge = callback; + break; + + case PWM_SOFT_EVENT_TOGGLED: + soft_config[index].events.toggling = callback; + break; + + case PWM_SOFT_EVENT_RESET: + soft_config[index].events.reset = callback; + break; + + case PWM_SOFT_EVENT_RISING_EDGE: + soft_config[index].events.rising_edge = callback; + break; + + default: + ret = PWM_ERROR_CONFIG; + break; + } + + return ret; +} + +pwm_error_t pwm_soft_remove_event(const uint8_t index, const pwm_soft_event_t when) +{ + if(false == index_valid(index, PWM_TYPE_SOFTWARE)) + { + return PWM_ERROR_INDEX_OUT_OF_RANGE; + } + + pwm_error_t ret = PWM_ERROR_OK; + switch(when) + { + case PWM_SOFT_EVENT_FALLING_EDGE: + soft_config[index].events.falling_edge = NULL; + break; + + case PWM_SOFT_EVENT_TOGGLED: + soft_config[index].events.toggling = NULL; + break; + + case PWM_SOFT_EVENT_RESET: + soft_config[index].events.reset = NULL; + break; + + case PWM_SOFT_EVENT_RISING_EDGE: + soft_config[index].events.rising_edge = NULL; + break; + + default: + ret = PWM_ERROR_CONFIG; + break; + } + + return ret; +} + +pwm_error_t pwm_soft_clear_all_events(const uint8_t index) +{ + if(false == index_valid(index, PWM_TYPE_SOFTWARE)) + { + return PWM_ERROR_INDEX_OUT_OF_RANGE; + } + + soft_config[index].events.falling_edge = NULL; + soft_config[index].events.rising_edge = NULL; + soft_config[index].events.toggling = NULL; + soft_config[index].events.reset = NULL; + + return PWM_ERROR_OK; +} diff --git a/Modules/Timebase/CMakeLists.txt b/Modules/Timebase/CMakeLists.txt index 0516c95..baad836 100644 --- a/Modules/Timebase/CMakeLists.txt +++ b/Modules/Timebase/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0) +cmake_minimum_required(VERSION 3.20) add_library(timebase_module STATIC ${CMAKE_CURRENT_SOURCE_DIR}/src/timebase.c diff --git a/Modules/Timebase/Tests/CMakeLists.txt b/Modules/Timebase/Tests/CMakeLists.txt index 011a376..61f002c 100644 --- a/Modules/Timebase/Tests/CMakeLists.txt +++ b/Modules/Timebase/Tests/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.0) +cmake_minimum_required(VERSION 3.20) project(timebase_module_tests) enable_testing() @@ -27,6 +27,7 @@ add_executable(timebase_module_tests Stubs/timer_8_bit_stub.c Stubs/timer_8_bit_async_stub.c Stubs/timer_16_bit_stub.c + Stubs/config.c ) target_include_directories(timebase_module_tests PUBLIC diff --git a/Modules/Timebase/Tests/Stubs/config.c b/Modules/Timebase/Tests/Stubs/config.c new file mode 100644 index 0000000..bda98f5 --- /dev/null +++ b/Modules/Timebase/Tests/Stubs/config.c @@ -0,0 +1,31 @@ +#include "config.h" +#include "timebase.h" +#include "timer_generic.h" +#include "timer_8_bit.h" +#include "timer_8_bit_async.h" +#include "timer_16_bit.h" + +timebase_config_t timebase_static_config[TIMEBASE_MAX_MODULES] = +{ + { + .clock_freq = 16000000UL, + .custom_target_freq = 0, + .timescale = TIMEBASE_TIMESCALE_MICROSECONDS, + .timer = {.index = 0, .type = TIMER_ARCH_8_BIT_ASYNC} + } +}; + +timer_8_bit_handle_t timer_8_bit_static_handle[TIMER_8_BIT_COUNT] = +{ + {0} +}; + +timer_8_bit_async_handle_t timer_8_bit_async_static_handle[TIMER_8_BIT_ASYNC_COUNT] = +{ + {0} +}; + +timer_16_bit_handle_t timer_16_bit_static_handle[TIMER_16_BIT_COUNT] = +{ + {0} +}; \ No newline at end of file diff --git a/Modules/Timebase/Tests/Stubs/timer_16_bit_stub.c b/Modules/Timebase/Tests/Stubs/timer_16_bit_stub.c index 320970d..f34d14b 100644 --- a/Modules/Timebase/Tests/Stubs/timer_16_bit_stub.c +++ b/Modules/Timebase/Tests/Stubs/timer_16_bit_stub.c @@ -4,7 +4,7 @@ @ FreeMyCode version : 1.0 RC alpha Author : bebenlebricolo - License : + License : name : GPLv3 url : https://www.gnu.org/licenses/quick-guide-gplv3.html Date : 12/02/2021 @@ -63,17 +63,21 @@ void timer_16_bit_stub_reset(void) memset(&configuration, 0, sizeof(configuration_t)); } -void timer_16_bit_compute_matching_parameters(const uint32_t * const cpu_freq, - const uint32_t * const target_freq, - timer_16_bit_prescaler_selection_t * const prescaler, - uint16_t * const ocra, - uint16_t * const accumulator) +timer_error_t timer_16_bit_compute_matching_parameters(const uint32_t * const clock_freq, + const uint32_t * const target_freq, + const timer_generic_resolution_t resolution, + timer_16_bit_prescaler_selection_t * const prescaler, + uint16_t * const ocr, + uint16_t * const accumulator) { - (void) cpu_freq; + (void) clock_freq; (void) target_freq; + (void) resolution; *prescaler = configuration.prescaler; - *ocra = configuration.ocra; + *ocr = configuration.ocra; *accumulator = configuration.accumulator; + + return TIMER_ERROR_OK; } const timer_generic_prescaler_pair_t timer_16_bit_prescaler_table[TIMER_16_BIT_MAX_PRESCALER_COUNT] = @@ -141,20 +145,6 @@ timer_error_t timer_16_bit_get_default_config(timer_16_bit_config_t * config) config->input_capture.edge_select = TIMER16BIT_INPUT_CAPTURE_EDGE_FALLING_EDGE; config->input_capture.use_noise_canceler = false; - /* Architecture and device dependent, must be set at configuration time */ - config->handle.OCRA_H = NULL; - config->handle.OCRA_L = NULL; - config->handle.OCRB_H = NULL; - config->handle.OCRB_L = NULL; - config->handle.TCCRA = NULL; - config->handle.TCCRB = NULL; - config->handle.TCCRC = NULL; - config->handle.TCNT_H = NULL; - config->handle.TCNT_L = NULL; - config->handle.ICR_H = NULL; - config->handle.ICR_L = NULL; - config->handle.TIFR = NULL; - config->handle.TIMSK = NULL; return ret; } diff --git a/Modules/Timebase/Tests/Stubs/timer_8_bit_async_stub.c b/Modules/Timebase/Tests/Stubs/timer_8_bit_async_stub.c index ecda04d..08d7a66 100644 --- a/Modules/Timebase/Tests/Stubs/timer_8_bit_async_stub.c +++ b/Modules/Timebase/Tests/Stubs/timer_8_bit_async_stub.c @@ -4,7 +4,7 @@ @ FreeMyCode version : 1.0 RC alpha Author : bebenlebricolo - License : + License : name : GPLv3 url : https://www.gnu.org/licenses/quick-guide-gplv3.html Date : 12/02/2021 @@ -63,17 +63,18 @@ void timer_8_bit_async_stub_reset(void) memset(&configuration, 0, sizeof(configuration_t)); } -void timer_8_bit_async_compute_matching_parameters( const uint32_t * const cpu_freq, - const uint32_t * const target_freq, - timer_8_bit_async_prescaler_selection_t * const prescaler, - uint8_t * const ocra, - uint16_t * const accumulator) +timer_error_t timer_8_bit_async_compute_matching_parameters( const uint32_t * const clock_freq, + const uint32_t * const target_freq, + timer_8_bit_async_prescaler_selection_t * const prescaler, + uint8_t * const ocra, + uint16_t * const accumulator) { - (void) cpu_freq; + (void) clock_freq; (void) target_freq; *prescaler = configuration.prescaler; *ocra = configuration.ocra; *accumulator = configuration.accumulator; + return TIMER_ERROR_OK; } const timer_generic_prescaler_pair_t timer_8_bit_async_prescaler_table[TIMER_8_BIT_ASYNC_MAX_PRESCALER_COUNT] = @@ -141,15 +142,6 @@ timer_error_t timer_8_bit_async_get_default_config(timer_8_bit_async_config_t * config->force_compare.force_comp_match_a = false; config->force_compare.force_comp_match_b = false; - /* Architecture and device dependent, must be set at configuration time */ - config->handle.OCRA = NULL; - config->handle.OCRB = NULL; - config->handle.TCCRA = NULL; - config->handle.TCCRB = NULL; - config->handle.TCNT = NULL; - config->handle.TIFR = NULL; - config->handle.TIMSK = NULL; - config->handle.ASSR_REG = NULL; return ret; } diff --git a/Modules/Timebase/Tests/Stubs/timer_8_bit_stub.c b/Modules/Timebase/Tests/Stubs/timer_8_bit_stub.c index 019f627..b0e170a 100644 --- a/Modules/Timebase/Tests/Stubs/timer_8_bit_stub.c +++ b/Modules/Timebase/Tests/Stubs/timer_8_bit_stub.c @@ -4,7 +4,7 @@ @ FreeMyCode version : 1.0 RC alpha Author : bebenlebricolo - License : + License : name : GPLv3 url : https://www.gnu.org/licenses/quick-guide-gplv3.html Date : 12/02/2021 @@ -70,17 +70,18 @@ void timer_8_bit_stub_get_driver_configuration(timer_8_bit_config_t * const conf *config = configuration.driver_config; } -void timer_8_bit_compute_matching_parameters(const uint32_t * const cpu_freq, - const uint32_t * const target_freq, - timer_8_bit_prescaler_selection_t * const prescaler, - uint8_t * const ocra, - uint16_t * const accumulator) +timer_error_t timer_8_bit_compute_matching_parameters(const uint32_t * const clock_freq, + const uint32_t * const target_freq, + timer_8_bit_prescaler_selection_t * const prescaler, + uint8_t * const ocra, + uint16_t * const accumulator) { - (void) cpu_freq; + (void) clock_freq; (void) target_freq; *prescaler = configuration.prescaler; *ocra = configuration.ocra; *accumulator = configuration.accumulator; + return TIMER_ERROR_OK; } const timer_generic_prescaler_pair_t timer_8_bit_prescaler_table[TIMER_8_BIT_MAX_PRESCALER_COUNT] = @@ -139,20 +140,12 @@ timer_error_t timer_8_bit_get_default_config(timer_8_bit_config_t * config) config->timing_config.ocrb_val = 0U; config->timing_config.prescaler = TIMER8BIT_CLK_NO_CLOCK; config->timing_config.waveform_mode = TIMER8BIT_WG_NORMAL; - config->timing_config.comp_match_a = TIMER8BIT_CMOD_NORMAL; - config->timing_config.comp_match_b = TIMER8BIT_CMOD_NORMAL; + config->timing_config.comp_mode_a = TIMER8BIT_CMOD_NORMAL; + config->timing_config.comp_mode_b = TIMER8BIT_CMOD_NORMAL; config->force_compare.force_comp_match_a = false; config->force_compare.force_comp_match_b = false; - /* Architecture and device dependent, must be set at configuration time */ - config->handle.OCRA = NULL; - config->handle.OCRB = NULL; - config->handle.TCCRA = NULL; - config->handle.TCCRB = NULL; - config->handle.TCNT = NULL; - config->handle.TIFR = NULL; - config->handle.TIMSK = NULL; return ret; } diff --git a/Modules/Timebase/Tests/inc/config.h b/Modules/Timebase/Tests/inc/config.h index 1c375ba..205bdc4 100644 --- a/Modules/Timebase/Tests/inc/config.h +++ b/Modules/Timebase/Tests/inc/config.h @@ -4,7 +4,7 @@ @ FreeMyCode version : 1.0 RC alpha Author : bebenlebricolo - License : + License : name : GPLv3 url : https://www.gnu.org/licenses/quick-guide-gplv3.html Date : 12/02/2021 @@ -32,5 +32,8 @@ along with this program. If not, see . #define CONFIG_HEADER_STUB #define TIMEBASE_MAX_MODULES 3U +#define TIMER_8_BIT_COUNT 1 +#define TIMER_8_BIT_ASYNC_COUNT 1 +#define TIMER_16_BIT_COUNT 1 #endif /* CONFIG_HEADER_STUB */ \ No newline at end of file diff --git a/Modules/Timebase/Tests/timebase_tests.cpp b/Modules/Timebase/Tests/timebase_tests.cpp index a24db0a..5fce8a5 100644 --- a/Modules/Timebase/Tests/timebase_tests.cpp +++ b/Modules/Timebase/Tests/timebase_tests.cpp @@ -4,7 +4,7 @@ @ FreeMyCode version : 1.0 RC alpha Author : bebenlebricolo - License : + License : name : GPLv3 url : https://www.gnu.org/licenses/quick-guide-gplv3.html Date : 12/02/2021 @@ -50,17 +50,16 @@ class TimebaseModuleBasicConfig : public ::testing::Test timer_8_bit_async_stub_reset(); timer_16_bit_stub_reset(); - config.cpu_freq = 16'000'000; - config.timescale = TIMEBASE_TIMESCALE_MILLISECONDS; - config.timer.type = TIMEBASE_TIMER_16_BIT; - config.timer.index = 0U; + timebase_static_config[0].clock_freq = 16'000'000; + timebase_static_config[0].timescale = TIMEBASE_TIMESCALE_MILLISECONDS; + timebase_static_config[0].timer.type = TIMER_ARCH_16_BIT; + timebase_static_config[0].timer.index = 0U; } void TearDown(void) override { } - timebase_config_t config; }; class TimebaseModule8BitInitialised : public TimebaseModuleBasicConfig @@ -71,8 +70,8 @@ class TimebaseModule8BitInitialised : public TimebaseModuleBasicConfig TimebaseModuleBasicConfig::SetUp(); timer_8_bit_stub_set_initialised(true); - config.timer.type = TIMEBASE_TIMER_8_BIT; - config.timer.index = 0U; + timebase_static_config[0].timer.type = TIMER_ARCH_8_BIT; + timebase_static_config[0].timer.index = 0U; timer_8_bit_prescaler_selection_t prescaler = TIMER8BIT_CLK_PRESCALER_64; uint32_t accumulator = 5U; @@ -80,18 +79,17 @@ class TimebaseModule8BitInitialised : public TimebaseModuleBasicConfig timer_8_bit_stub_set_next_parameters(prescaler, ocra, accumulator); - timebase_error_t err = timebase_init(0U, &config); + timebase_error_t err = timebase_init(0U); ASSERT_EQ(TIMEBASE_ERROR_OK, err); } }; TEST(timebase_module_tests, test_compute_timer_parameters) { - timebase_config_t config; - config.cpu_freq = 16'000'000; - config.timescale = TIMEBASE_TIMESCALE_MILLISECONDS; - config.timer.type = TIMEBASE_TIMER_16_BIT; - config.timer.index = 0U; + timebase_static_config[0].clock_freq = 16'000'000; + timebase_static_config[0].timescale = TIMEBASE_TIMESCALE_MILLISECONDS; + timebase_static_config[0].timer.type = TIMER_ARCH_16_BIT; + timebase_static_config[0].timer.index = 0U; uint16_t prescaler = 0; uint16_t ocr_value = 0; @@ -99,25 +97,25 @@ TEST(timebase_module_tests, test_compute_timer_parameters) timer_16_bit_stub_set_next_parameters(TIMER16BIT_CLK_PRESCALER_1, 15999U, 0U); - timebase_error_t err = timebase_compute_timer_parameters(&config, &prescaler, &ocr_value, &accumulator); + timebase_error_t err = timebase_compute_timer_parameters(0U, &prescaler, &ocr_value, &accumulator); ASSERT_EQ(TIMEBASE_ERROR_OK, err); ASSERT_EQ(0U, accumulator); ASSERT_EQ(prescaler, 1U); ASSERT_EQ(ocr_value, 15999U); - config.timer.type = TIMEBASE_TIMER_8_BIT; - config.timer.index = 0U; + timebase_static_config[0].timer.type = TIMER_ARCH_8_BIT; + timebase_static_config[0].timer.index = 0U; timer_8_bit_stub_set_next_parameters(TIMER8BIT_CLK_PRESCALER_64, 250, 3U); - err = timebase_compute_timer_parameters(&config, &prescaler, &ocr_value, &accumulator); + err = timebase_compute_timer_parameters(0U, &prescaler, &ocr_value, &accumulator); ASSERT_EQ(TIMEBASE_ERROR_OK, err); ASSERT_EQ(3U, accumulator); ASSERT_EQ(prescaler, 64U); ASSERT_EQ(ocr_value, 250U); - config.timer.type = TIMEBASE_TIMER_8_BIT_ASYNC; - config.timer.index = 0U; + timebase_static_config[0].timer.type = TIMER_ARCH_8_BIT_ASYNC; + timebase_static_config[0].timer.index = 0U; timer_8_bit_async_stub_set_next_parameters(TIMER8BIT_ASYNC_CLK_PRESCALER_1024, 127, 4U); - err = timebase_compute_timer_parameters(&config, &prescaler, &ocr_value, &accumulator); + err = timebase_compute_timer_parameters(0U, &prescaler, &ocr_value, &accumulator); ASSERT_EQ(TIMEBASE_ERROR_OK, err); ASSERT_EQ(4U, accumulator); ASSERT_EQ(prescaler, 1024); @@ -127,36 +125,30 @@ TEST(timebase_module_tests, test_compute_timer_parameters) TEST(timebase_module_test, test_guard_wrong_parameters) { { - timebase_config_t config; - memset(&config, 0, sizeof(timebase_config_t)); + memset(&timebase_static_config[0], 0, sizeof(timebase_config_t)); uint16_t * null_prescaler = nullptr; uint16_t * null_ocr_value = nullptr; uint16_t * null_accumulator = nullptr; - auto ret = timebase_compute_timer_parameters(&config, null_prescaler, null_ocr_value, null_accumulator); + auto ret = timebase_compute_timer_parameters(0U, null_prescaler, null_ocr_value, null_accumulator); ASSERT_EQ(ret, TIMEBASE_ERROR_NULL_POINTER); uint16_t prescaler = 0; uint16_t accumulator = 0; uint16_t ocr_value = 0; // Forcing a wrong timer type - config.timer.type = TIMEBASE_TIMER_UNDEFINED; - config.timescale = TIMEBASE_TIMESCALE_MICROSECONDS; - ret = timebase_compute_timer_parameters(&config, &prescaler, &ocr_value, &accumulator); + timebase_static_config[0].timer.type = TIMER_ARCH_UNDEFINED; + timebase_static_config[0].timescale = TIMEBASE_TIMESCALE_MICROSECONDS; + ret = timebase_compute_timer_parameters(0U, &prescaler, &ocr_value, &accumulator); ASSERT_EQ(ret, TIMEBASE_ERROR_UNSUPPORTED_TIMER_TYPE); - config.timer.type = TIMEBASE_TIMER_16_BIT; - config.timescale = TIMEBASE_TIMESCALE_UNDEFINED; - ret = timebase_compute_timer_parameters(&config, &prescaler, &ocr_value, &accumulator); + timebase_static_config[0].timer.type = TIMER_ARCH_16_BIT; + timebase_static_config[0].timescale = TIMEBASE_TIMESCALE_UNDEFINED; + ret = timebase_compute_timer_parameters(0U, &prescaler, &ocr_value, &accumulator); ASSERT_EQ(ret, TIMEBASE_ERROR_UNSUPPORTED_TIMESCALE); } { - timebase_config_t * null_config = nullptr; - timebase_config_t config; - memset(&config, 0, sizeof(timebase_config_t)); - auto ret = timebase_init(0U, null_config); - ASSERT_EQ(ret, TIMEBASE_ERROR_NULL_POINTER); - - ret = timebase_init(TIMEBASE_MAX_MODULES, &config); + timebase_error_t ret = TIMEBASE_ERROR_OK; + ret = timebase_init(TIMEBASE_MAX_MODULES); ASSERT_EQ(ret, TIMEBASE_ERROR_INVALID_INDEX); } { @@ -212,56 +204,54 @@ TEST(timebase_module_test, test_guard_wrong_parameters) TEST(timebase_module_tests, test_wrong_index_error_forwarding) { - timebase_config_t config; - config.cpu_freq = 16'000'000; - config.timescale = TIMEBASE_TIMESCALE_MILLISECONDS; + timebase_static_config[0].clock_freq = 16'000'000; + timebase_static_config[0].timescale = TIMEBASE_TIMESCALE_MILLISECONDS; // This index should break execution as this timer driver does not exist (only '0' is declared) - config.timer.index = 1U; + timebase_static_config[0].timer.index = 1U; - config.timer.type = TIMEBASE_TIMER_16_BIT; - timebase_error_t err = timebase_init(0U, &config); + timebase_static_config[0].timer.type = TIMER_ARCH_16_BIT; + timebase_error_t err = timebase_init(0U); ASSERT_EQ(TIMEBASE_ERROR_TIMER_ERROR, err); - config.timer.type = TIMEBASE_TIMER_8_BIT; - err = timebase_init(0U, &config); + timebase_static_config[0].timer.type = TIMER_ARCH_8_BIT; + err = timebase_init(0U); ASSERT_EQ(TIMEBASE_ERROR_TIMER_ERROR, err); - config.timer.type = TIMEBASE_TIMER_8_BIT_ASYNC; - err = timebase_init(0U, &config); + timebase_static_config[0].timer.type = TIMER_ARCH_8_BIT_ASYNC; + err = timebase_init(0U); ASSERT_EQ(TIMEBASE_ERROR_TIMER_ERROR, err); // Repeat this with a wrong index for timebase module this time - config.timer.index = 0U; - err = timebase_init(TIMEBASE_MAX_MODULES, &config); + timebase_static_config[0].timer.index = 0U; + err = timebase_init(TIMEBASE_MAX_MODULES); ASSERT_EQ(TIMEBASE_ERROR_INVALID_INDEX, err); } TEST(timebase_module_tests, test_uninitialised_timer_error) { - timebase_config_t config; - config.cpu_freq = 16'000'000; - config.timescale = TIMEBASE_TIMESCALE_MILLISECONDS; - config.timer.type = TIMEBASE_TIMER_16_BIT; - config.timer.index = 0U; + timebase_static_config[0].clock_freq = 16'000'000; + timebase_static_config[0].timescale = TIMEBASE_TIMESCALE_MILLISECONDS; + timebase_static_config[0].timer.type = TIMER_ARCH_16_BIT; + timebase_static_config[0].timer.index = 0U; - timebase_error_t err = timebase_init(0U, &config); + timebase_error_t err = timebase_init(0U); ASSERT_EQ(TIMEBASE_ERROR_TIMER_UNINITIALISED, err); - config.timer.type = TIMEBASE_TIMER_8_BIT; - err = timebase_init(0U, &config); + timebase_static_config[0].timer.type = TIMER_ARCH_8_BIT; + err = timebase_init(0U); ASSERT_EQ(TIMEBASE_ERROR_TIMER_UNINITIALISED, err); - config.timer.type = TIMEBASE_TIMER_8_BIT_ASYNC; - err = timebase_init(0U, &config); + timebase_static_config[0].timer.type = TIMER_ARCH_8_BIT_ASYNC; + err = timebase_init(0U); ASSERT_EQ(TIMEBASE_ERROR_TIMER_UNINITIALISED, err); } TEST_F(TimebaseModuleBasicConfig, test_timer_initialisation) { timer_8_bit_stub_set_initialised(true); - config.timer.type = TIMEBASE_TIMER_8_BIT; - config.timer.index = 0U; + timebase_static_config[0].timer.type = TIMER_ARCH_8_BIT; + timebase_static_config[0].timer.index = 0U; timer_8_bit_prescaler_selection_t prescaler = TIMER8BIT_CLK_PRESCALER_64; uint32_t accumulator = 5U; @@ -269,7 +259,7 @@ TEST_F(TimebaseModuleBasicConfig, test_timer_initialisation) timer_8_bit_stub_set_next_parameters(prescaler, ocra, accumulator); - timebase_error_t err = timebase_init(0U, &config); + timebase_error_t err = timebase_init(0U); ASSERT_EQ(TIMEBASE_ERROR_OK, err); timer_8_bit_config_t driver_config; memset(&driver_config, 0, sizeof(timer_8_bit_config_t)); @@ -306,6 +296,55 @@ TEST_F(TimebaseModule8BitInitialised, test_ticks_and_durations) ASSERT_EQ(duration,((uint32_t) (timebase_internal_config[0U].tick) + USHRT_MAX) - (uint32_t) reference); } +TEST_F(TimebaseModule8BitInitialised, test_period_from_frequency_calculations) +{ + uint32_t frequency = 1000; + uint16_t period = 0; + timebase_error_t err = TIMEBASE_ERROR_OK; + err = timebase_compute_period_from_frequency(0U, &frequency, TIMEBASE_FREQUENCY_HZ, &period); + ASSERT_EQ(err, TIMEBASE_ERROR_FREQUENCY_TOO_HIGH); + ASSERT_EQ(period, 0); + + timebase_static_config[0].clock_freq = 16'000'000; + timebase_static_config[0].timescale = TIMEBASE_TIMESCALE_MICROSECONDS; + timebase_static_config[0].timer.type = TIMER_ARCH_8_BIT; + timebase_static_config[0].timer.index = 0U; + + err = timebase_init(0U); + ASSERT_EQ(err, TIMEBASE_ERROR_OK); + err = timebase_compute_period_from_frequency(0U, &frequency, TIMEBASE_FREQUENCY_HZ, &period); + ASSERT_EQ(err, TIMEBASE_ERROR_OK); + ASSERT_EQ(period, 1000U); + + // What about singing a song ? + frequency = 440; + err = timebase_compute_period_from_frequency(0U, &frequency, TIMEBASE_FREQUENCY_HZ, &period); + ASSERT_EQ(err, TIMEBASE_ERROR_OK); + ASSERT_EQ(period, 2272); + + frequency = 880; + err = timebase_compute_period_from_frequency(0U, &frequency, TIMEBASE_FREQUENCY_HZ, &period); + ASSERT_EQ(err, TIMEBASE_ERROR_OK); + ASSERT_EQ(period, 1136); + + // Trying with custom frequencies as well + timebase_static_config[0].timescale = TIMEBASE_TIMESCALE_CUSTOM; + timebase_static_config[0].custom_target_freq = 44'000UL; + err = timebase_init(0U); + ASSERT_EQ(err, TIMEBASE_ERROR_OK); + + frequency = 880; + err = timebase_compute_period_from_frequency(0U, &frequency, TIMEBASE_FREQUENCY_HZ, &period); + ASSERT_EQ(err, TIMEBASE_ERROR_OK); + ASSERT_EQ(period, 50); + + // Highest pitched 'E' hearable by humans + frequency = 21'098; + err = timebase_compute_period_from_frequency(0U, &frequency, TIMEBASE_FREQUENCY_HZ, &period); + ASSERT_EQ(err, TIMEBASE_ERROR_OK); + ASSERT_EQ(period, 2); +} + int main(int argc, char **argv) { diff --git a/Modules/Timebase/inc/timebase.h b/Modules/Timebase/inc/timebase.h index 57d8777..fdf9ac4 100644 --- a/Modules/Timebase/inc/timebase.h +++ b/Modules/Timebase/inc/timebase.h @@ -4,7 +4,7 @@ @ FreeMyCode version : 1.0 RC alpha Author : bebenlebricolo - License : + License : name : GPLv3 url : https://www.gnu.org/licenses/quick-guide-gplv3.html Date : 12/02/2021 @@ -38,6 +38,8 @@ extern "C" #include #include +#include "config.h" +#include "timer_generic.h" /** * @brief Describes available error codes for this timebase module @@ -50,24 +52,14 @@ typedef enum yet, so timebase may run eratically) */ TIMEBASE_ERROR_NULL_POINTER, /**< One or more parameters are not initialised properly */ TIMEBASE_ERROR_INVALID_INDEX, /**< Index is not set correctly, probably out of bounds */ - TIMEBASE_ERROR_UNSUPPORTED_TIMER_TYPE, /**< Given timer type is not compatible with timebase_timer_t enum */ + TIMEBASE_ERROR_UNSUPPORTED_TIMER_TYPE, /**< Given timer type is not compatible with timer_arch_t enum */ TIMEBASE_ERROR_UNSUPPORTED_TIMESCALE, /**< Given timescale is not relevant to timebase module */ + TIMEBASE_ERROR_FREQUENCY_TOO_HIGH, /**< Targeted timebase instance could not implement given frequency */ TIMEBASE_ERROR_TIMER_UNINITIALISED, /**< Underlying timer is not initialised */ TIMEBASE_ERROR_TIMER_ERROR, /**< Encountered an error while using underlying timer driver */ } timebase_error_t; -/** - * @brief Allows to select the underlying timer driver -*/ -typedef enum -{ - TIMEBASE_TIMER_UNDEFINED, /**< Undefined timer type, default value */ - TIMEBASE_TIMER_8_BIT, /**< Uses a regular 8 bit timer */ - TIMEBASE_TIMER_16_BIT, /**< Uses a regular 16 bit timer */ - TIMEBASE_TIMER_8_BIT_ASYNC, /**< Uses an advanced / async capable 8 bit timer */ -} timebase_timer_t; - /** * @brief Selects the reference timebase */ @@ -80,6 +72,18 @@ typedef enum TIMEBASE_TIMESCALE_CUSTOM, /**< Custom timescale, allows to use a custom configuration to handle timebase generation */ } timebase_timescale_t; +/** + * @brief Packs frequencies base units. + * @details This enum is quite strange but allows us to calculate timebase ticks periods for frequencies that are lower than 1 Hz. + * Amongst other things, it prevents from using floating point arithmetics (which is heavy) and allows us to + * Generate very slow events, that could make sense from a pure timing point of view. + */ +typedef enum +{ + TIMEBASE_FREQUENCY_HZ, /**< Default Hz unit for frequencies */ + TIMEBASE_FREQUENCY_MILLI_HZ, /**< Quite weird to see this, but that's the only way we can calculate periods for frequencies that are lower than 1 HZ */ +} timebase_frequency_t; + /** * @brief Initialisation structure */ @@ -87,15 +91,17 @@ typedef struct { struct { - timebase_timer_t type; /**< Used to select a timer from its type */ + timer_arch_t type; /**< Used to select a timer from its type */ uint8_t index; /**< Used to select a particular timer from the available ones */ } timer; - uint32_t cpu_freq; /**< Gives the CPU frequency to compute the right prescaler for the timebase */ + uint32_t clock_freq; /**< Gives the CPU frequency to compute the right prescaler for the timebase */ struct { - timebase_timescale_t timescale; /**< Selects what is the resolution of the timer */ - uint32_t custom_target_freq; /**< Custom target frequency */ + timebase_timescale_t timescale; /**< Selects what is the resolution of the timer */ + uint32_t custom_target_freq; /**< Custom target frequency (only used if timescale = TIMEBASE_TIMESCALE_CUSTOM enum value ) */ + // it basically allows to set a specific target frequency which does not fall in common use cases + // such as 1000Hz, 1Hz, 1000000Hz etc. }; } timebase_config_t; @@ -110,18 +116,17 @@ typedef struct * TIMEBASE_ERROR_UNSUPPORTED_TIMER_TYPE : targeted timer type does not exist * TIMEBASE_ERROR_UNSUPPORTED_TIMESCALE : timescale is not relevant to timebase module */ -timebase_error_t timebase_compute_timer_parameters(timebase_config_t const * const config, uint16_t * const prescaler_val, uint16_t * const ocr_value, uint16_t * const accumulator); +timebase_error_t timebase_compute_timer_parameters(const uint8_t id, uint16_t * const prescaler_val, uint16_t * const ocr_value, uint16_t * const accumulator); /** * @brief Initialises the timebase module using an id and a configuration. * @param[in] id : index of timebase module to be initialised - * @param[in] config : configuration to be used to initialise the targeted timebase module * @return * TIMEBASE_ERROR_OK : operation succeeded * TIMEBASE_ERROR_NULL_POINTER : given parameter is uninitialised * TIMEBASE_ERROR_INVALID_INDEX : given module id is out of bounds */ -timebase_error_t timebase_init(const uint8_t id, timebase_config_t const * const config); +timebase_error_t timebase_init(const uint8_t id); /** @@ -187,6 +192,25 @@ timebase_error_t timebase_get_duration_now(const uint8_t id, uint16_t const * co */ void timebase_interrupt_callback(const uint8_t id); +/** + * @brief Computes a time period (in ticks) for a specific instance of Timebase, using the desired frequency parameter as + * the main input. + * + * @param[in] id : id of the targeted timebase instance (holds the information about its own timepace) + * @param[in] frequency : input absolute frequency to be converted into an amount of ticks from that timebase + * @param[out] period : output period calculated in ticks of this specific timebase instance + * * @return + * TIMEBASE_ERROR_OK : operation succeeded + * TIMEBASE_ERROR_INVALID_INDEX : given module id is out of bounds + */ +timebase_error_t timebase_compute_period_from_frequency(const uint8_t id, uint32_t const * const frequency, const timebase_frequency_t funit, uint16_t * const period); + +/** + * @brief Encodes the static configuration used by this module + * Each slot of this array codes for a single timebase instance, based on a single individual hardware timer + */ +extern timebase_config_t timebase_static_config[TIMEBASE_MAX_MODULES]; + #ifdef __cplusplus } #endif diff --git a/Modules/Timebase/inc/timebase_internal.h b/Modules/Timebase/inc/timebase_internal.h index c986721..9d78f92 100644 --- a/Modules/Timebase/inc/timebase_internal.h +++ b/Modules/Timebase/inc/timebase_internal.h @@ -4,7 +4,7 @@ @ FreeMyCode version : 1.0 RC alpha Author : bebenlebricolo - License : + License : name : GPLv3 url : https://www.gnu.org/licenses/quick-guide-gplv3.html Date : 12/02/2021 @@ -41,7 +41,7 @@ extern "C" typedef struct { - timebase_timer_t timer; + timer_arch_t arch; uint8_t timer_id; struct { diff --git a/Modules/Timebase/src/timebase.c b/Modules/Timebase/src/timebase.c index edba38e..1ffb0ad 100644 --- a/Modules/Timebase/src/timebase.c +++ b/Modules/Timebase/src/timebase.c @@ -4,7 +4,7 @@ @ FreeMyCode version : 1.0 RC alpha Author : bebenlebricolo - License : + License : name : GPLv3 url : https://www.gnu.org/licenses/quick-guide-gplv3.html Date : 12/02/2021 @@ -41,7 +41,8 @@ along with this program. If not, see . #include "timer_8_bit_async.h" #ifndef TIMEBASE_MAX_MODULES - #error "TIMEBASE_MAX_MODULES define is missing, please set the maximum number of available timebase modules in your config.h" + #warning "TIMEBASE_MAX_MODULES define is missing, please set the maximum number of available timebase modules in your config.h" + #define TIMEBASE_MAX_MODULES 1U #endif timebase_internal_config_t timebase_internal_config[TIMEBASE_MAX_MODULES] = {0}; @@ -61,15 +62,17 @@ static void reset_internal_config(const uint8_t id) timebase_internal_config[id].accumulator.programmed = 0; timebase_internal_config[id].accumulator.running = 0; timebase_internal_config[id].tick = 0; - timebase_internal_config[id].timer = TIMEBASE_TIMER_UNDEFINED; + timebase_internal_config[id].arch = TIMER_ARCH_UNDEFINED; timebase_internal_config[id].timer_id = 0; timebase_internal_config[id].initialised = false; } -static inline timebase_error_t setup_8_bit_timer(const uint8_t timebase_id, uint32_t const * const cpu_freq, uint32_t const * const target_freq) +static inline timebase_error_t setup_8_bit_timer(const uint8_t timebase_id, uint32_t const * const clock_freq, uint32_t const * const target_freq) { bool initialised = false; timer_error_t err = timer_8_bit_is_initialised(timebase_internal_config[timebase_id].timer_id, &initialised); + uint8_t timer_id = timebase_internal_config[timebase_id].timer_id; + if (TIMER_ERROR_OK != err) { return TIMEBASE_ERROR_TIMER_ERROR; @@ -83,20 +86,13 @@ static inline timebase_error_t setup_8_bit_timer(const uint8_t timebase_id, uint uint8_t ocra = 0; timebase_internal_config[timebase_id].accumulator.programmed = 0; timer_8_bit_prescaler_selection_t prescaler; - timer_8_bit_compute_matching_parameters(cpu_freq, + timer_8_bit_compute_matching_parameters(clock_freq, target_freq, &prescaler, &ocra, &timebase_internal_config[timebase_id].accumulator.programmed); - timer_error_t ret = timer_8_bit_stop(timebase_internal_config[timebase_id].timer_id); - timer_8_bit_handle_t handle = {0}; - ret = timer_8_bit_get_handle(timebase_internal_config[timebase_id].timer_id, &handle); - - if (TIMER_ERROR_OK != ret) - { - return TIMEBASE_ERROR_TIMER_ERROR; - } + timer_error_t ret = timer_8_bit_stop(timer_id); timer_8_bit_config_t config = {0}; ret = timer_8_bit_get_default_config(&config); @@ -106,9 +102,8 @@ static inline timebase_error_t setup_8_bit_timer(const uint8_t timebase_id, uint } // Use old handle - config.handle = handle; - config.timing_config.comp_match_a = TIMER8BIT_CMOD_CLEAR_OCnX; - config.timing_config.comp_match_b = TIMER8BIT_CMOD_NORMAL; + config.timing_config.comp_mode_a = TIMER8BIT_CMOD_CLEAR_OCnX; + config.timing_config.comp_mode_b = TIMER8BIT_CMOD_NORMAL; config.timing_config.ocra_val = ocra; config.timing_config.prescaler = prescaler; config.interrupt_config.it_comp_match_a = true; @@ -122,10 +117,11 @@ static inline timebase_error_t setup_8_bit_timer(const uint8_t timebase_id, uint return TIMEBASE_ERROR_OK; } -static inline timebase_error_t setup_8_bit_async_timer(const uint8_t timebase_id, uint32_t const * const cpu_freq, uint32_t const * const target_freq) +static inline timebase_error_t setup_8_bit_async_timer(const uint8_t timebase_id, uint32_t const * const clock_freq, uint32_t const * const target_freq) { bool initialised = false; timer_error_t err = timer_8_bit_async_is_initialised(timebase_internal_config[timebase_id].timer_id, &initialised); + uint8_t timer_id = timebase_internal_config[timebase_id].timer_id; if (TIMER_ERROR_OK != err) { return TIMEBASE_ERROR_TIMER_ERROR; @@ -139,21 +135,13 @@ static inline timebase_error_t setup_8_bit_async_timer(const uint8_t timebase_id uint8_t ocra = 0; timebase_internal_config[timebase_id].accumulator.programmed = 0; timer_8_bit_async_prescaler_selection_t prescaler; - timer_8_bit_async_compute_matching_parameters(cpu_freq, + timer_8_bit_async_compute_matching_parameters(clock_freq, target_freq, &prescaler, &ocra, &timebase_internal_config[timebase_id].accumulator.programmed); - timer_error_t ret = timer_8_bit_async_stop(timebase_internal_config[timebase_id].timer_id); - timer_8_bit_async_handle_t handle = {0}; - ret = timer_8_bit_async_get_handle(timebase_internal_config[timebase_id].timer_id, &handle); - - if (TIMER_ERROR_OK != ret) - { - return TIMEBASE_ERROR_TIMER_ERROR; - } - + timer_error_t ret = timer_8_bit_async_stop(timer_id); timer_8_bit_async_config_t config = {0}; ret = timer_8_bit_async_get_default_config(&config); if (TIMER_ERROR_OK != ret) @@ -162,7 +150,6 @@ static inline timebase_error_t setup_8_bit_async_timer(const uint8_t timebase_id } // Use old handle - config.handle = handle; config.timing_config.comp_match_a = TIMER8BIT_ASYNC_CMOD_CLEAR_OCnX; config.timing_config.comp_match_b = TIMER8BIT_ASYNC_CMOD_NORMAL; config.timing_config.ocra_val = ocra; @@ -178,7 +165,7 @@ static inline timebase_error_t setup_8_bit_async_timer(const uint8_t timebase_id return TIMEBASE_ERROR_OK; } -static inline timebase_error_t setup_16_bit_timer(const uint8_t timebase_id, uint32_t const * const cpu_freq, uint32_t const * const target_freq) +static inline timebase_error_t setup_16_bit_timer(const uint8_t timebase_id, uint32_t const * const clock_freq, uint32_t const * const target_freq) { bool initialised = false; timer_error_t err = timer_16_bit_is_initialised(timebase_internal_config[timebase_id].timer_id, &initialised); @@ -195,15 +182,14 @@ static inline timebase_error_t setup_16_bit_timer(const uint8_t timebase_id, uin uint16_t ocra = 0; timebase_internal_config[timebase_id].accumulator.programmed = 0; timer_16_bit_prescaler_selection_t prescaler; - timer_16_bit_compute_matching_parameters(cpu_freq, + timer_16_bit_compute_matching_parameters(clock_freq, target_freq, + TIMER_GENERIC_RESOLUTION_16_BIT, &prescaler, &ocra, &timebase_internal_config[timebase_id].accumulator.programmed); timer_error_t ret = timer_16_bit_stop(timebase_internal_config[timebase_id].timer_id); - timer_16_bit_handle_t handle = {0}; - ret = timer_16_bit_get_handle(timebase_internal_config[timebase_id].timer_id, &handle); if (TIMER_ERROR_OK != ret) { @@ -218,7 +204,6 @@ static inline timebase_error_t setup_16_bit_timer(const uint8_t timebase_id, uin } // Use old handle - config.handle = handle; config.timing_config.comp_match_a = TIMER16BIT_CMOD_CLEAR_OCnX; config.timing_config.comp_match_b = TIMER16BIT_CMOD_NORMAL; config.timing_config.ocra_val = ocra; @@ -234,10 +219,10 @@ static inline timebase_error_t setup_16_bit_timer(const uint8_t timebase_id, uin return TIMEBASE_ERROR_OK; } -static inline timebase_error_t convert_timescale_to_frequency(const timebase_config_t * const config, uint32_t * const target_frequency) +static inline timebase_error_t convert_timescale_to_frequency(const uint8_t id, uint32_t * const target_frequency) { /* Handle target frequency */ - switch (config->timescale) + switch (timebase_static_config[id].timescale) { case TIMEBASE_TIMESCALE_MICROSECONDS: *target_frequency = 1000000UL; @@ -249,7 +234,7 @@ static inline timebase_error_t convert_timescale_to_frequency(const timebase_con *target_frequency = 1UL; break; case TIMEBASE_TIMESCALE_CUSTOM: - *target_frequency = config->custom_target_freq; + *target_frequency = timebase_static_config[id].custom_target_freq; break; default: return TIMEBASE_ERROR_UNSUPPORTED_TIMESCALE; @@ -257,16 +242,16 @@ static inline timebase_error_t convert_timescale_to_frequency(const timebase_con return TIMEBASE_ERROR_OK; } -timebase_error_t timebase_compute_timer_parameters(timebase_config_t const * const config, uint16_t * const prescaler_val, uint16_t * const ocr_value, uint16_t * const accumulator) +timebase_error_t timebase_compute_timer_parameters(const uint8_t id, uint16_t * const prescaler_val, uint16_t * const ocr_value, uint16_t * const accumulator) { timer_8_bit_prescaler_selection_t prescaler; uint32_t target_frequency = 0; - if (NULL == config || NULL == prescaler_val || NULL == ocr_value || NULL == accumulator) + if (NULL == prescaler_val || NULL == ocr_value || NULL == accumulator) { return TIMEBASE_ERROR_NULL_POINTER; } - timebase_error_t ret = convert_timescale_to_frequency(config, &target_frequency); + timebase_error_t ret = convert_timescale_to_frequency(id, &target_frequency); if (TIMEBASE_ERROR_OK != ret) { return ret; @@ -274,10 +259,10 @@ timebase_error_t timebase_compute_timer_parameters(timebase_config_t const * con uint16_t ocra = 0; // Initialise each timer using the right parameters set - switch(config->timer.type) + switch(timebase_static_config[id].timer.type) { - case TIMEBASE_TIMER_8_BIT: - timer_8_bit_compute_matching_parameters(&config->cpu_freq, + case TIMER_ARCH_8_BIT: + timer_8_bit_compute_matching_parameters(&timebase_static_config[id].clock_freq, &target_frequency, &prescaler, (uint8_t*) &ocra, @@ -286,8 +271,8 @@ timebase_error_t timebase_compute_timer_parameters(timebase_config_t const * con *ocr_value = ocra; break; - case TIMEBASE_TIMER_8_BIT_ASYNC: - timer_8_bit_async_compute_matching_parameters(&config->cpu_freq, + case TIMER_ARCH_8_BIT_ASYNC: + timer_8_bit_async_compute_matching_parameters(&timebase_static_config[id].clock_freq, &target_frequency, (timer_8_bit_async_prescaler_selection_t *) &prescaler, (uint8_t*) &ocra, @@ -296,9 +281,10 @@ timebase_error_t timebase_compute_timer_parameters(timebase_config_t const * con *ocr_value = ocra; break; - case TIMEBASE_TIMER_16_BIT: - timer_16_bit_compute_matching_parameters(&config->cpu_freq, + case TIMER_ARCH_16_BIT: + timer_16_bit_compute_matching_parameters(&timebase_static_config[id].clock_freq, &target_frequency, + TIMER_GENERIC_RESOLUTION_16_BIT, (timer_16_bit_prescaler_selection_t *)&prescaler, &ocra, accumulator); @@ -313,49 +299,44 @@ timebase_error_t timebase_compute_timer_parameters(timebase_config_t const * con } -timebase_error_t timebase_init(const uint8_t timebase_id, timebase_config_t const * const config) +timebase_error_t timebase_init(const uint8_t id) { timebase_error_t ret = TIMEBASE_ERROR_OK; - if (false == is_index_valid(timebase_id)) + if (false == is_index_valid(id)) { return TIMEBASE_ERROR_INVALID_INDEX; } - if (NULL == config) - { - return TIMEBASE_ERROR_NULL_POINTER; - } - - timebase_internal_config[timebase_id].timer_id = config->timer.index; - timebase_internal_config[timebase_id].timer = config->timer.type; + timebase_internal_config[id].timer_id = timebase_static_config[id].timer.index; + timebase_internal_config[id].arch = timebase_static_config[id].timer.type; uint32_t target_freq = 0; - ret = convert_timescale_to_frequency(config, &target_freq); + ret = convert_timescale_to_frequency(id, &target_freq); if (TIMEBASE_ERROR_OK != ret) { return ret; } // Initialise each timer using the right parameters set - switch(config->timer.type) + switch(timebase_static_config[id].timer.type) { - case TIMEBASE_TIMER_8_BIT: - ret = setup_8_bit_timer(timebase_id, &(config->cpu_freq), &target_freq); + case TIMER_ARCH_8_BIT: + ret = setup_8_bit_timer(id, &(timebase_static_config[id].clock_freq), &target_freq); break; - case TIMEBASE_TIMER_8_BIT_ASYNC: - ret = setup_8_bit_async_timer(timebase_id, &(config->cpu_freq), &target_freq); + case TIMER_ARCH_8_BIT_ASYNC: + ret = setup_8_bit_async_timer(id, &(timebase_static_config[id].clock_freq), &target_freq); break; - case TIMEBASE_TIMER_16_BIT: - ret = setup_16_bit_timer(timebase_id, &(config->cpu_freq), &target_freq); + case TIMER_ARCH_16_BIT: + ret = setup_16_bit_timer(id, &(timebase_static_config[id].clock_freq), &target_freq); break; default: return TIMEBASE_ERROR_UNSUPPORTED_TIMER_TYPE; } - timebase_internal_config[timebase_id].initialised = true; + timebase_internal_config[id].initialised = true; return ret; } @@ -387,6 +368,56 @@ void timebase_interrupt_callback(const uint8_t timebase_id) } } +timebase_error_t timebase_compute_period_from_frequency(const uint8_t id, uint32_t const * const frequency, const timebase_frequency_t funit, uint16_t * const period) +{ + uint32_t calculated_period = 0; + uint16_t multiplicator = 1; + if(TIMEBASE_FREQUENCY_MILLI_HZ == funit) + { + // Note, having this kind of multiplication happening at runtime is not performant at all, + // However, even for small avr chips it does not seem too bad (gcc quickly calls for __udivmodsi4, __mulis3, etc..) + // And it happens only at configuration time, it won't impede runtime performances + multiplicator = 1000U; + } + + switch(timebase_static_config[id].timescale) + { + // Timebase in seconds needs special handling because frequency parameter is in Hertz, + // So if we want to achieve lower frequencies than 1Hz, we need to use a different strategy + case TIMEBASE_TIMESCALE_SECONDS: + calculated_period = multiplicator * 1U / *frequency ; + break; + + case TIMEBASE_TIMESCALE_MILLISECONDS: + calculated_period = multiplicator * 1000U / *frequency; + break; + + case TIMEBASE_TIMESCALE_MICROSECONDS: + calculated_period = multiplicator * 1000000U / *frequency ; + break; + + case TIMEBASE_TIMESCALE_CUSTOM: + calculated_period = multiplicator * timebase_static_config[id].custom_target_freq / *frequency; + break; + + // Should never get there + case TIMEBASE_TIMESCALE_UNDEFINED: + default: + return TIMEBASE_ERROR_UNSUPPORTED_TIMESCALE; + } + + // If we reach 0, it means we cannot achieve the required frequency + if(calculated_period <= 1) + { + return TIMEBASE_ERROR_FREQUENCY_TOO_HIGH; + } + + *period = (uint16_t) calculated_period; + + return TIMEBASE_ERROR_OK; +} + + timebase_error_t timebase_deinit(const uint8_t id) { if (false == is_index_valid(id)) diff --git a/Sensors/CMakeLists.txt b/Sensors/CMakeLists.txt deleted file mode 100644 index 5c78b6f..0000000 --- a/Sensors/CMakeLists.txt +++ /dev/null @@ -1,4 +0,0 @@ -cmake_minimum_required(VERSION 3.0) - -add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Thermistor) - diff --git a/Sensors/Thermistor/CMakeLists.txt b/Sensors/Thermistor/CMakeLists.txt deleted file mode 100644 index 165c8cc..0000000 --- a/Sensors/Thermistor/CMakeLists.txt +++ /dev/null @@ -1,19 +0,0 @@ -cmake_minimum_required(VERSION 3.0) - -add_library(thermistor_driver STATIC - ${CMAKE_CURRENT_SOURCE_DIR}/src/thermistor.c -) - -target_include_directories(thermistor_driver PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR}/inc - ${CONFIG_FILE_DIR} - ${AVR_INCLUDES} -) - -target_include_directories(thermistor_driver PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/private_inc - ${CONFIG_FILE_DIR} - ${AVR_INCLUDES} -) - -target_link_libraries(thermistor_driver adc_driver) \ No newline at end of file diff --git a/Sensors/Thermistor/Tests/CMakeLists.txt b/Sensors/Thermistor/Tests/CMakeLists.txt deleted file mode 100644 index a8130ba..0000000 --- a/Sensors/Thermistor/Tests/CMakeLists.txt +++ /dev/null @@ -1,53 +0,0 @@ -cmake_minimum_required(VERSION 3.0) - -project(thermistor_test) - -######### Compile tested modules as individual libraries ######### - -### thermistor_driver library ### -add_library(thermistor_driver STATIC - ${CMAKE_CURRENT_SOURCE_DIR}/../src/thermistor.c -) -target_include_directories(thermistor_driver PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR}/../inc - ${CMAKE_CURRENT_SOURCE_DIR}/../private_inc - ${CMAKE_CURRENT_SOURCE_DIR}/../../../Drivers/Adc/inc - ${CMAKE_CURRENT_SOURCE_DIR}/Stubs -) - -target_compile_definitions(thermistor_driver PRIVATE - -DUNIT_TESTING -) - - -########## Thermistor driver tests ########## - -add_executable(thermistor_driver_test - ${CMAKE_CURRENT_SOURCE_DIR}/thermistor_driver_tests.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/Stubs/config.c - ${CMAKE_CURRENT_SOURCE_DIR}/Stubs/adc_stub.c -) - -target_compile_definitions(thermistor_driver_test PRIVATE - -DUNIT_TESTING -) - -target_include_directories(thermistor_driver_test PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR}/../inc - ${CMAKE_CURRENT_SOURCE_DIR}/Stubs -) - -target_include_directories(thermistor_driver_test SYSTEM PUBLIC - ${GTEST_INCLUDE_DIRS} -) - -if(WIN32) - target_link_libraries(thermistor_driver_test thermistor_driver ${GTEST_LIBRARIES}) -else() - target_link_libraries(thermistor_driver_test thermistor_driver ${GTEST_LIBRARIES} pthread) -endif() - -set_target_properties(thermistor_driver_test - PROPERTIES - RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin/Thermistor -) diff --git a/Sensors/Thermistor/Tests/Stubs/adc_stub.c b/Sensors/Thermistor/Tests/Stubs/adc_stub.c deleted file mode 100644 index a99016e..0000000 --- a/Sensors/Thermistor/Tests/Stubs/adc_stub.c +++ /dev/null @@ -1,81 +0,0 @@ -#include - -#include "adc.h" -#include "adc_stub.h" - -static struct -{ - adc_mux_t channel; - adc_millivolts_t reading; - bool registered; -} stub_registered_channels[ADC_MUX_COUNT] = { - {.channel = ADC_MUX_ADC0}, - {.channel = ADC_MUX_ADC1}, - {.channel = ADC_MUX_ADC2}, - {.channel = ADC_MUX_ADC3}, - {.channel = ADC_MUX_ADC4}, - {.channel = ADC_MUX_ADC5}, - {.channel = ADC_MUX_ADC6}, - {.channel = ADC_MUX_ADC7}, - {.channel = ADC_MUX_INTERNAL_TEMPERATURE}, - {.channel = ADC_MUX_1v1_REF}, - {.channel = ADC_MUX_GND}, -}; - -static adc_error_t yielded_error = ADC_ERROR_OK; - -adc_error_t adc_read_millivolt(const adc_mux_t channel, adc_millivolts_t * const reading) -{ - if (!stub_registered_channels[channel].registered) - { - return ADC_ERROR_CHANNEL_NOT_FOUND; - } - - if (reading == NULL) - { - return ADC_ERROR_NULL_POINTER; - } - - *reading = stub_registered_channels[channel].reading; - - return yielded_error; -} - -void stub_adc_set_readings(const adc_mux_t channel, const adc_millivolts_t reading) -{ - stub_registered_channels[channel].reading = reading; -} - -void stub_adc_clear_readings(void) -{ - for (uint8_t i = 0 ; i < ADC_MUX_COUNT ; i++) - { - stub_registered_channels[i].reading = 0; - } -} - -void stub_adc_register_channel(const adc_mux_t channel) -{ - stub_registered_channels[channel].registered = true; -} - -void stub_adc_clear_registered_channels(void) -{ - for (uint8_t i = 0 ; i < ADC_MUX_COUNT ; i++) - { - stub_registered_channels[i].registered = false; - } -} - -void stub_adc_clear_all(void) -{ - stub_adc_clear_registered_channels(); - stub_adc_clear_readings(); - yielded_error = ADC_ERROR_OK; -} - -void stub_adc_set_error(const adc_error_t error) -{ - yielded_error = error; -} - diff --git a/Sensors/Thermistor/Tests/Stubs/adc_stub.h b/Sensors/Thermistor/Tests/Stubs/adc_stub.h deleted file mode 100644 index 1f1b288..0000000 --- a/Sensors/Thermistor/Tests/Stubs/adc_stub.h +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef ADC_STUB_HEADER -#define ADC_STUB_HEADER - -#ifdef __cplusplus -extern "C" -{ -#endif - -#include "adc.h" - -#define ADC_STUB_MAX_READINGS 5U - -/** - * @brief updates internal data storage for selected adc channel -*/ -void stub_adc_set_readings(const adc_mux_t channel, const adc_millivolts_t reading); - -/** - * @brief clears all fakes adc readings -*/ -void stub_adc_clear_readings(void); - -/** - * @brief performs a fake channel registration, as if it has effectively been registered by application program -*/ -void stub_adc_register_channel(const adc_mux_t channel); - -/** - * @brief clears registered channels -*/ -void stub_adc_clear_registered_channels(void); - -/** - * @brief resets this fake driver -*/ -void stub_adc_clear_all(void); - -/** - * @brief sets the adc_error_t that functions will return -*/ -void stub_adc_set_error(const adc_error_t error); - -#ifdef __cplusplus -} -#endif - -#endif /* ADC_STUB_HEADER */ \ No newline at end of file diff --git a/Sensors/Thermistor/Tests/Stubs/config.c b/Sensors/Thermistor/Tests/Stubs/config.c deleted file mode 100644 index a3583aa..0000000 --- a/Sensors/Thermistor/Tests/Stubs/config.c +++ /dev/null @@ -1,47 +0,0 @@ -#include "config.h" -#include "thermistor.h" - -static const thermistor_curve_discrete_t hot_side_curve = -{ - .count = 5U, - .data = { - {100, 25}, /**< hot half is always less than 1, so we can retrieve percentages by multiplying by 100 to ease computations */ - {25, 39}, - {7, 53}, - {3, 64}, - {1, 77}, - } -}; - -static const thermistor_curve_discrete_t cold_side_curve = -{ - .count = 6U, - .data = { - {16, 1}, /**< cold half exceeds ratio exceeds 1 so we don't multiply the ratio by 100 to get correct computation */ - {12, 3}, - {8, 6}, - {4, 12}, - {2, 19}, - {1, 25} - } -}; - -thermistor_config_t thermistor_driver_config[THERMISTOR_MAX_SENSORS] = -{ - { - .adc_index = THERMISTOR_CONFIG_ADC_INDEX, - .model = - { - .curve = - { - .data = {.discrete.hot_side = &hot_side_curve, - .discrete.cold_side = &cold_side_curve}, - .type = THERMISTOR_CURVE_DISCRETE - }, - .bridge_res = 20U, - .calibration.resistance = 20U, - .calibration.temperature = 25U, - .ref_voltage = 5000, - } - } -}; \ No newline at end of file diff --git a/Sensors/Thermistor/Tests/Stubs/config.h b/Sensors/Thermistor/Tests/Stubs/config.h deleted file mode 100644 index 3c03266..0000000 --- a/Sensors/Thermistor/Tests/Stubs/config.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef CONFIG_STUB_HEADER -#define CONFIG_STUB_HEADER - -#ifdef __cplusplus -extern "C" -{ -#endif - -#include "adc.h" - -#define THERMISTOR_MAX_SAMPLES_COUNT 8U -#define THERMISTOR_MAX_SENSORS 1U - -#define THERMISTOR_CONFIG_ADC_INDEX (ADC_MUX_ADC1) - - -#ifdef __cplusplus -} -#endif - -#endif /* CONFIG_STUB_HEADER */ \ No newline at end of file diff --git a/Sensors/Thermistor/Tests/thermistor_driver_tests.cpp b/Sensors/Thermistor/Tests/thermistor_driver_tests.cpp deleted file mode 100644 index e95eb86..0000000 --- a/Sensors/Thermistor/Tests/thermistor_driver_tests.cpp +++ /dev/null @@ -1,191 +0,0 @@ -/* - ------------------- -@ -FreeMyCode version : 1.0 RC alpha - Author : bebenlebricolo - License : - name : GPLv3 - url : https://www.gnu.org/licenses/quick-guide-gplv3.html - Date : 12/02/2021 - Project : LabBenchPowerSupply - Description : The Lab Bench Power Supply provides a simple design based around an Arduino Nano board to convert AC main voltage into - smaller ones, ranging from 0V to 16V, with voltage and current regulations -@ ------------------- - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -#include "gtest/gtest.h" - -#include "config.h" -#include "adc_stub.h" -#include "thermistor.h" -#include "thermistor_private.h" - -TEST(thermistor_tests, test_find_segment) -{ - uint8_t target_ratio = 11; - thermistor_curve_discrete_pair_t const * lower_bound = NULL; - thermistor_curve_discrete_pair_t const * upper_bound = NULL; - bool ret = find_segment(thermistor_driver_config[0].model.curve.data.discrete.cold_side, target_ratio, RATIO_COEF_1, &lower_bound, &upper_bound ); - ASSERT_TRUE(ret); - ASSERT_EQ(lower_bound->ratio, 12U); - ASSERT_EQ(upper_bound->ratio, 8U); - - // Using the x10 representation, actual ratio is 8 - target_ratio = 80U; - ret = find_segment(thermistor_driver_config[0].model.curve.data.discrete.cold_side, target_ratio, RATIO_COEF_10, &lower_bound, &upper_bound ); - ASSERT_TRUE(ret); - ASSERT_EQ(lower_bound->ratio, 8U); - ASSERT_EQ(upper_bound->ratio, 8U); - ASSERT_EQ(lower_bound, upper_bound); - - // Using the x10 representation, actual ratio is 8 - target_ratio = 63U; - ret = find_segment(thermistor_driver_config[0].model.curve.data.discrete.cold_side, target_ratio, RATIO_COEF_10, &lower_bound, &upper_bound ); - ASSERT_TRUE(ret); - ASSERT_EQ(lower_bound->ratio, 8U); - ASSERT_EQ(upper_bound->ratio, 4U); - - // Using the x100 representation, actual ratio is 0.53 (53%) - target_ratio = 53U; - ret = find_segment(thermistor_driver_config[0].model.curve.data.discrete.hot_side, target_ratio, RATIO_COEF_100, &lower_bound, &upper_bound ); - ASSERT_TRUE(ret); - ASSERT_EQ(lower_bound->ratio, 100U); - ASSERT_EQ(upper_bound->ratio, 25U); - - // Using the x100 representation, actual ratio is 0.53 (53%) - target_ratio = 0U; - ret = find_segment(thermistor_driver_config[0].model.curve.data.discrete.hot_side, target_ratio, RATIO_COEF_100, &lower_bound, &upper_bound ); - ASSERT_FALSE(ret); - ASSERT_EQ(lower_bound->ratio, 1U); - ASSERT_EQ(upper_bound->ratio, 1U); -} - -TEST(thermistor_tests, test_compute_ratios) -{ - // Cold side first - adc_millivolts_t reading = 2125U; - uint8_t ratio = 0; - ratio_coefficients_t coef = compute_ratio(&thermistor_driver_config[0].model, &reading, &ratio); - ASSERT_EQ(coef, RATIO_COEF_10); - ASSERT_EQ(ratio, 13U); - - reading = 1200U; - coef = compute_ratio(&thermistor_driver_config[0].model, &reading, &ratio); - ASSERT_EQ(coef, RATIO_COEF_10); - ASSERT_EQ(ratio, 31U); - - reading = 250U; - coef = compute_ratio(&thermistor_driver_config[0].model, &reading, &ratio); - ASSERT_EQ(coef, RATIO_COEF_1); - ASSERT_EQ(ratio, 19); - - reading = 120U; - coef = compute_ratio(&thermistor_driver_config[0].model, &reading, &ratio); - ASSERT_EQ(coef, RATIO_COEF_1); - ASSERT_EQ(ratio, 40); - - // Hot side - reading = 3000U; - coef = compute_ratio(&thermistor_driver_config[0].model, &reading, &ratio); - ASSERT_EQ(coef, RATIO_COEF_100); - ASSERT_EQ(ratio, 65); - - reading = 4500U; - coef = compute_ratio(&thermistor_driver_config[0].model, &reading, &ratio); - ASSERT_EQ(coef, RATIO_COEF_100); - ASSERT_EQ(ratio, 10U); -} - -TEST(thermistor_tests, test_compute_temperature) -{ - adc_millivolts_t reading = 2125U; - int8_t temp = compute_temperature(0, &reading); - ASSERT_EQ(temp, 23U); - - reading = 2500U; - temp = compute_temperature(0, &reading); - ASSERT_EQ(temp, 25U); - - reading = 2700U; - temp = compute_temperature(0, &reading); - ASSERT_EQ(temp, 27U); - - reading = 3200U; - temp = compute_temperature(0, &reading); - ASSERT_EQ(temp, 33U); - - reading = 4123U; - temp = compute_temperature(0, &reading); - ASSERT_EQ(temp, 42U); - - reading = 4980U; - temp = compute_temperature(0, &reading); - ASSERT_EQ(temp, 77); -} - - -TEST(thermistor_tests, test_process_sensors) -{ - adc_millivolts_t reading = 2125U; - thermistor_error_t error = THERMISTOR_ERR_OK; - int8_t temperature = 0; - stub_adc_set_readings(THERMISTOR_CONFIG_ADC_INDEX, reading); - - stub_adc_set_error(ADC_ERROR_CONFIG); - error = thermistor_process(); - ASSERT_EQ(error, THERMISTOR_ERR_WRONG_CONFIG); - - stub_adc_register_channel(ADC_MUX_ADC1); - - stub_adc_set_error(ADC_ERROR_OK); - error = thermistor_process(); - ASSERT_EQ(error, THERMISTOR_ERR_OK); - error = thermistor_read(0U, &temperature); - EXPECT_EQ(temperature, 23U); - - reading = 2000; - stub_adc_set_readings(THERMISTOR_CONFIG_ADC_INDEX, reading); - error = thermistor_process(); - ASSERT_EQ(error, THERMISTOR_ERR_OK); - error = thermistor_read(0U, &temperature); - ASSERT_EQ(error, THERMISTOR_ERR_OK); - EXPECT_EQ(temperature, 22); - - reading = 3200U; - stub_adc_set_readings(THERMISTOR_CONFIG_ADC_INDEX, reading); - error = thermistor_process(); - ASSERT_EQ(error, THERMISTOR_ERR_OK); - error = thermistor_read(0U, &temperature); - ASSERT_EQ(error, THERMISTOR_ERR_OK); - EXPECT_EQ(temperature, 33); - - reading = 4980U; - stub_adc_set_readings(THERMISTOR_CONFIG_ADC_INDEX, reading); - error = thermistor_process(); - ASSERT_EQ(error, THERMISTOR_ERR_OK); - error = thermistor_read(0U, &temperature); - ASSERT_EQ(error, THERMISTOR_ERR_OK); - EXPECT_EQ(temperature, 77); - -} - -int main(int argc, char **argv) -{ - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); -} \ No newline at end of file diff --git a/Sensors/Thermistor/inc/thermistor.h b/Sensors/Thermistor/inc/thermistor.h deleted file mode 100644 index 663f66c..0000000 --- a/Sensors/Thermistor/inc/thermistor.h +++ /dev/null @@ -1,144 +0,0 @@ -#ifndef THERMISTOR_HEADER -#define THERMISTOR_HEADER - -#ifdef __cplusplus -extern "C" -{ -#endif - -#include - -#include "adc.h" -#include "config.h" - -#ifndef THERMISTOR_CURVE_MAX_SAMPLES -#define THERMISTOR_CURVE_MAX_SAMPLES 16U -#endif - -#ifndef THERMISTOR_MAX_THERMISTORS -#define THERMISTOR_MAX_THERMISTORS 1U -#endif - -/** - * @brief Thermistor sensor error types -*/ -typedef enum -{ - THERMISTOR_ERR_OK, - THERMISTOR_ERR_WRONG_CONFIG, - THERMISTOR_ERR_INDEX_OUT_OF_BOUNDS, -} thermistor_error_t; - - -/** - * @brief thermistor intrinsic type (positive thermal coefficient, negative thermal coefficient, ...) -*/ -typedef enum -{ - THERMISTOR_CURVE_LINEAR, /**< Linear curve */ - THERMISTOR_CURVE_DISCRETE, /**< Discrete points (requires array of data for the modeled thermistor) */ -} thermistor_curve_type_t; - -typedef struct -{ - uint8_t ratio; /**< Rtherm/Rcalibration */ - int8_t temperature; /**< matching temperature in Celsius degrees (°C) */ -} thermistor_curve_discrete_pair_t; - -/** - * @brief discrete curve implementation. Curve should be filled with consecutive data points with no doubles - * List of points should be ordered as well. -*/ -typedef struct -{ - thermistor_curve_discrete_pair_t data[THERMISTOR_CURVE_MAX_SAMPLES / 2U + 1]; /**< Hot half curve (ratio <= 1) */ - uint8_t count; /**< Stores how many samples are used in this curve */ -} thermistor_curve_discrete_t; - -/** - * @brief linear curve using classic affine equation of type T = a.X + b - * where : - * - T : temperature (Kelvin) - * - a : temperature coefficient - * - b : offset -*/ -typedef struct -{ - int8_t coefficient; /**< Linear coefficient using first order approximation */ - int8_t offset; /**< Offset for linear equation */ -} thermistor_curve_linear_t; - -/** - * @brief represents a basic curve using samples -*/ -typedef struct -{ - thermistor_curve_type_t type; /**< curve type, either discrete or linear curve */ - union - { - struct - { - thermistor_curve_discrete_t const * hot_side; /**< hot side curve, where ratio is between 0 and 1 */ - thermistor_curve_discrete_t const * cold_side; /**< cold side curve, when ratio is above 1 */ - } discrete; - thermistor_curve_linear_t * linear; /**< Linear curve in case we need to use one */ - } data; -} thermistor_curve_t; - -/** - * @brief Describes a thermistor model using all calibration parameters and data coming from the thermistor. - * We do not rely on -*/ -typedef struct -{ - struct - { - uint8_t resistance; /**< Calibration resistance for the modelised thermistance (in kOhms) */ - int8_t temperature; /**< Calibration temperature for the modelised thermistance (in Kelvin) */ - } calibration; - - uint8_t bridge_res; /**< Divider bridge resistance (in kOhms) */ - adc_millivolts_t ref_voltage; /**< Reference voltage being used by ADC on MCU analog input pin (in millivolts) */ - thermistor_curve_t curve; /**< Thermistor modeled curve (either linear equation or discrete curve) */ -} thermistor_model_t; - -/** - * @brief configuration structure for thermistor driver -*/ -typedef struct -{ - adc_mux_t adc_index; /**< Index of adc input to be requested to adc driver */ - thermistor_model_t model; /**< Thermistor model used */ -} thermistor_config_t; - -/** - * @brief configures thermistor driver using static configuration (@see thermistor_driver_config symbol) - * @return thermistor driver errors -*/ -thermistor_error_t thermistor_init(void); - -/** - * @brief reads temperature for a given sensor (fetched data from internal data bank, thermistor_process function should be called to - * update stored data) - * @see thermistor_process - * @param[in] index : index of targeted thermistor driver - * @param[out] temp : computed temperature -*/ -thermistor_error_t thermistor_read(const uint8_t index, int8_t * temp); - -/** - * @brief scans all registered thermistors and compute temperatures -*/ -thermistor_error_t thermistor_process(void); - -/** - * @brief this extern symbol has to be implemented by application code. Usually, this is done in a config.c file - * whose symbols are linked when building the whole application -*/ -extern thermistor_config_t thermistor_driver_config[THERMISTOR_MAX_THERMISTORS]; - -#ifdef __cplusplus -} -#endif - -#endif /* THERMISTOR_HEADER */ \ No newline at end of file diff --git a/Sensors/Thermistor/private_inc/thermistor_private.h b/Sensors/Thermistor/private_inc/thermistor_private.h deleted file mode 100644 index c49e600..0000000 --- a/Sensors/Thermistor/private_inc/thermistor_private.h +++ /dev/null @@ -1,77 +0,0 @@ -#ifndef THERMISTOR_PRIVATE_HEADER -#define THERMISTOR_PRIVATE_HEADER - - -#ifdef __cplusplus -extern "C" -{ -#endif - -#include "thermistor.h" - -/** - * @brief this enum is used internally to correctly express the resistance ratio number - * without loosing too much of the precious digits, but fitting within a uint8_t enveloppe. - * Handling this kind of things adds a bit more code to the program (bigger flash footprint) but allows - * more fine-grain computation over a wide range of ratios number, ensuring ratios stay within the 0-255 range. -*/ -typedef enum -{ - RATIO_COEF_1 = 1U, /**< Ratio is expressed without scaling (1:1) scale */ - RATIO_COEF_10 = 10U, /**< Ratio is expressed as 10 times the actual effective ratio (10:1) scale */ - RATIO_COEF_100 = 100U, /**< Ratio is expressed as 100 times the actual ratio (100:1) scale */ -} ratio_coefficients_t; - -/** - * @brief implements a dichotomic search (binary search) to help finding the right - * data interval. Data is rejected whenever given ratio is out of bounds, and in case of exact match, - * both lower_bound and upper_bound points to the same data pair. - * @param[in] curve : input curve used to find the including interval - * @param[in] ratio : thermistor resistance ratio (nominal resistance over calibration resistance) - * @param[out] lower_bound : lower bound of the found interval - * @param[out] upper_bound : upper bound of the found interval - * @return true or false. false means input ratio is out of bounds ; this algorithm does not extrapolate. -*/ -bool find_segment(thermistor_curve_discrete_t const * const curve, - const uint8_t ratio, - const ratio_coefficients_t coef, - thermistor_curve_discrete_pair_t const ** lower_bound, - thermistor_curve_discrete_pair_t const ** upper_bound); - -/** - * @brief computes a temperature using sensor index and an appropriate sensor reading - * @param[in] index : sensor index - * @param[in] reading : analogic value read from the sensor's adc pin - * @return processed temperature value. - * INT8_MIN is returned in case of error (@see limits.h) -*/ -int8_t compute_temperature(const uint8_t index, adc_millivolts_t * reading); - - -/** - * @brief calculates thermistor ratio (Rth/R0) where : - * - Rth : current resistance of thermistance - * - R0 : calibration resistance of thermistance (usually 25°C / 298.15K) - * And returns a ratio_coefficient_t enum value to indicate the scale used by the ratio number to accurately represent it later. - * This is useful because we want to restrict data usage as much as possible to prevent bloating the flash with unecessary data, so a bit - * of code is sacrificed to help reduce the ram footprint. - * - * For instance, ratio could be encoded with : - * | scale | ratio values | - * | 1:1 | >= 10 | - * | 10:1 | [1,10] | - * | 100:1 | [0,1] | - * - * @param[in] model : thermistor model as configured in config.c - * @param[in] reading : adc reading in millivolt for a given sensor - * @param[out] ratio : output calculated ratio - * @return ratio coefficient used to depict ratio's current scaling -*/ -ratio_coefficients_t compute_ratio(thermistor_model_t const * const model, const adc_millivolts_t * reading, uint8_t * const ratio); - - -#ifdef __cplusplus -} -#endif - -#endif /* THERMISTOR_PRIVATE_HEADER */ \ No newline at end of file diff --git a/Sensors/Thermistor/src/thermistor.c b/Sensors/Thermistor/src/thermistor.c deleted file mode 100644 index 973ee21..0000000 --- a/Sensors/Thermistor/src/thermistor.c +++ /dev/null @@ -1,212 +0,0 @@ -#include "thermistor.h" -#include "thermistor_private.h" - -#define RATIO_POSITIVE_LOWER_LIMIT 10U - -static int8_t temperatures[THERMISTOR_MAX_THERMISTORS] = {0}; - -thermistor_error_t thermistor_read(const uint8_t index, int8_t * temp) -{ - if (index >= THERMISTOR_MAX_THERMISTORS) - { - return THERMISTOR_ERR_INDEX_OUT_OF_BOUNDS; - } - - *temp = temperatures[index]; - return THERMISTOR_ERR_OK; -} - -thermistor_error_t thermistor_init(void) -{ - return THERMISTOR_ERR_OK; -} - -thermistor_error_t thermistor_process(void) -{ - adc_millivolts_t reading = 0; - adc_error_t err = ADC_ERROR_OK; - for (uint8_t i = 0 ; i < THERMISTOR_MAX_THERMISTORS ; i++) - { - err = adc_read_millivolt(thermistor_driver_config[i].adc_index, &reading); - if (err != ADC_ERROR_OK) - { - return THERMISTOR_ERR_WRONG_CONFIG; - } - temperatures[i] = compute_temperature(i, &reading); - } - return THERMISTOR_ERR_OK; -} - - -bool find_segment(thermistor_curve_discrete_t const * const curve, - const uint8_t ratio, - const ratio_coefficients_t coef, - thermistor_curve_discrete_pair_t const ** lower_bound, - thermistor_curve_discrete_pair_t const ** upper_bound) -{ - uint8_t index = 0; - uint8_t upper_idx = curve->count - 1; - uint8_t lower_idx = 0; - uint8_t rcoef = 1; - - // Special case where ratio is expressed as 10x the real ratio, to prevent data loss - // This only works for that particular value, interpolation is the same for the 1x and 100x cases however because data is directly - // encoded like this in the curves. - if (RATIO_COEF_10 == coef) - { - rcoef = (uint8_t) coef; - } - - // Reject out of bounds values - if (ratio > (curve->data[0].ratio * rcoef)) - { - *lower_bound = &curve->data[0]; - *upper_bound = *lower_bound; - return false; - } - - if(ratio < (curve->data[curve->count - 1].ratio * rcoef)) - { - *lower_bound = &curve->data[curve->count - 1]; - *upper_bound = *lower_bound; - return false; - } - - while((upper_idx - lower_idx) > 1) - { - index = ((upper_idx - lower_idx) / 2) + lower_idx; - uint16_t current_ratio = curve->data[index].ratio * rcoef; - - // Exact match, we need to return the two exact same data pair then - if (ratio == current_ratio) - { - lower_idx = index; - upper_idx = index; - break; - } - - // Curves are written in reverse order (maximum ratio first, cold temperatures first) - if (ratio > current_ratio) - { - upper_idx = index; - } - else - { - lower_idx = index; - } - } - - *lower_bound = &curve->data[lower_idx]; - *upper_bound = &curve->data[upper_idx]; - - return true; -} - -/** - * Vcc - * ┌┴┐ - * │/│ } Thermistor - * └┬┘ - * ├───── > Vout - * ┌┴┐ - * │ │ } Bridge resistor (low side configuration) - * └┬┘ - * ╧ -*/ -int8_t compute_temperature(const uint8_t index, adc_millivolts_t * reading) -{ - int8_t temperature = 0; - uint8_t ratio = 0; - - ratio_coefficients_t coef = compute_ratio(&thermistor_driver_config[index].model, reading, &ratio); - - // Determine if we need to evaluate the cold side or not. - // "Hot" side is characterized by a resistance lower than the calibration resistance, with ratios lower than 1 so we need - // to scale up (multiply) the thermistor value by 100 in order to get the ratio from 0 to 100% - // - // On the cold side however, ratio always exceed 1 so we can perform a direct calculation with the parameters. - // This time, ratio is used to express a value which is always greater than 100%, so we save some memory space by only using the integer portion of the number. - if (thermistor_driver_config[index].model.curve.type == THERMISTOR_CURVE_DISCRETE) - { - thermistor_curve_discrete_pair_t const * lower_bound = NULL; - thermistor_curve_discrete_pair_t const * upper_bound = NULL; - bool found = true; - thermistor_curve_discrete_t const * curve = NULL; - if (coef == RATIO_COEF_100) - { - curve = thermistor_driver_config[index].model.curve.data.discrete.hot_side; - } - else - { - curve = thermistor_driver_config[index].model.curve.data.discrete.cold_side; - } - - found = find_segment(curve, ratio, coef, &lower_bound, &upper_bound); - - // If not found, take the lowest temperature of the range as a default value - if (!found) - { - temperature = lower_bound->temperature; - } - else - { - uint8_t rcoef = 1U; - - // Same remark as somewhere above : special case handling only for the 10x case where curves are not encoded with this 10x rule (because we'll lose data) - // This case is an in-between case ; RATIO_COEF_1 and RATIO_COEF_100 work just fine as curves already contain correctly encoded data - // Note: we would not have such an issue with uint16_t (up to ratios 65+), but this consumes a lot of FLASH/SRAM for nothing. - if (RATIO_COEF_10 == coef) - { - rcoef = (uint8_t) coef; - } - - uint16_t numerator_part = (ratio - (lower_bound->ratio * rcoef)) * (lower_bound->temperature - upper_bound->temperature); - uint16_t denominator_part = rcoef * (lower_bound->ratio - upper_bound->ratio); - temperature = (numerator_part / denominator_part) + lower_bound->temperature; - } - } - else - { - // Do nothing for now (not yet implemented) - } - - return temperature; -} - -ratio_coefficients_t compute_ratio(thermistor_model_t const * const model, const adc_millivolts_t * reading, uint8_t * const ratio) -{ - ratio_coefficients_t coef = RATIO_COEF_1; - - // Vcc - Vout - adc_millivolts_t potential_diff = (model->ref_voltage - *reading); - - // ((Vcc - Vout) * Rbridge) / Vout - uint16_t thermistor_value = (potential_diff * model->bridge_res) / *reading; - - if (thermistor_value >= model->calibration.resistance ) - { - // Perform upscaled calculation first, then decide if we need to downscale it or not - // This is done this way to get the maximum resolution first (thermistor value * 100) * 10 - // Then we can decimate the result if need be. - // Curves are still changing quite fast around ratios close to 1,2,3,4, so that's why we may use a bit more precise - // ratio evaluation. For instance, if computed ratio equals 1.7, integer arithmetic will truncate it to 1 and we won't be able to interpolate between 1 and 2 ratio values. - - uint16_t tmp_ratio = (thermistor_value * 1000) / (uint16_t) model->calibration.resistance; - if ((tmp_ratio / 1000U) < RATIO_POSITIVE_LOWER_LIMIT) - { - coef = RATIO_COEF_10; - *ratio = tmp_ratio / 100U; - } - else - { - *ratio = tmp_ratio / 1000U; - } - } - else - { - coef = RATIO_COEF_100; - *ratio = thermistor_value * 100U / model->calibration.resistance; - } - - return coef; -} \ No newline at end of file diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index f7d6a2d..e6bb9d6 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.20) -project(LabBenchPowerSupply_FirmwareTests C CXX) +project(AvrAsyncCoreLibs_Tests C CXX) find_package(GTest REQUIRED) enable_testing() @@ -12,12 +12,21 @@ add_definitions( -DI2C_IMPLEM_FULL_DRIVER ) +option(COVERAGE OFF) + + +if(COVERAGE) + set(COVERAGE_FLAGS "--coverage --ftest-coverage -fprofile-arcs") +else() + set(COVERAGE_FLAGS "") +endif() + if(UNIX) set(AVR_INCLUDES /usr/avr/include) - set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g3 -Wall -Wextra") - set(CMAKE_C_FLAGS_DEBUG "-O0 -g3 -Wall -Wextra") - set(CMAKE_CXX_FLAGS_RELEASE "-O3 -Wall -Wextra") - set(CMAKE_C_FLAGS_RELEASE "-O3 -Wall -Wextra") + set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g3 -Wall -Wextra ${COVERAGE_FLAGS}") + set(CMAKE_C_FLAGS_DEBUG "-O0 -g3 -Wall -Wextra ${COVERAGE_FLAGS}") + set(CMAKE_CXX_FLAGS_RELEASE "-O3 -Wall -Wextra ${COVERAGE_FLAGS}") + set(CMAKE_C_FLAGS_RELEASE "-O3 -Wall -Wextra ${COVERAGE_FLAGS}") else() if (NOT DEFINED AVR_INCLUDES) message(FATAL_ERROR "AVR_INCLUDES variable not set, please provide the path to avr includes") @@ -30,51 +39,51 @@ endif() -set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) -add_subdirectory( ${CMAKE_SOURCE_DIR}/../Drivers/Adc/Tests - ${CMAKE_BINARY_DIR}/Tests/Adc +set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) +add_subdirectory( ${CMAKE_CURRENT_SOURCE_DIR}/../Drivers/Adc/Tests + ${CMAKE_CURRENT_BINARY_DIR}/Tests/Adc ) # Memory utils library -add_subdirectory( ${CMAKE_SOURCE_DIR}/../Utils - ${CMAKE_BINARY_DIR}/Tests/Utils +add_subdirectory( ${CMAKE_CURRENT_SOURCE_DIR}/../Utils + ${CMAKE_CURRENT_BINARY_DIR}/Tests/Utils ) # Timer drivers -add_subdirectory( ${CMAKE_SOURCE_DIR}/../Drivers/Timers/Timer_generic/Tests - ${CMAKE_BINARY_DIR}/Tests/Drivers/Timers/Timer_generic +add_subdirectory( ${CMAKE_CURRENT_SOURCE_DIR}/../Drivers/Timers/Timer_generic/Tests + ${CMAKE_CURRENT_BINARY_DIR}/Tests/Drivers/Timers/Timer_generic ) -add_subdirectory( ${CMAKE_SOURCE_DIR}/../Drivers/Timers/Timer_8_bit/Tests - ${CMAKE_BINARY_DIR}/Tests/Drivers/Timers/Timer_8_bit +add_subdirectory( ${CMAKE_CURRENT_SOURCE_DIR}/../Drivers/Timers/Timer_8_bit/Tests + ${CMAKE_CURRENT_BINARY_DIR}/Tests/Drivers/Timers/Timer_8_bit ) -add_subdirectory( ${CMAKE_SOURCE_DIR}/../Drivers/Timers/Timer_8_bit_async/Tests - ${CMAKE_BINARY_DIR}/Tests/Drivers/Timers/Timer_8_bit_async +add_subdirectory( ${CMAKE_CURRENT_SOURCE_DIR}/../Drivers/Timers/Timer_8_bit_async/Tests + ${CMAKE_CURRENT_BINARY_DIR}/Tests/Drivers/Timers/Timer_8_bit_async ) -add_subdirectory( ${CMAKE_SOURCE_DIR}/../Drivers/Timers/Timer_16_bit/Tests - ${CMAKE_BINARY_DIR}/Tests/Drivers/Timers/Timer_16_bit +add_subdirectory( ${CMAKE_CURRENT_SOURCE_DIR}/../Drivers/Timers/Timer_16_bit/Tests + ${CMAKE_CURRENT_BINARY_DIR}/Tests/Drivers/Timers/Timer_16_bit ) # I2C driver -add_subdirectory( ${CMAKE_SOURCE_DIR}/../Drivers/I2c/Tests - ${CMAKE_BINARY_DIR}/Tests/Drivers/I2c +add_subdirectory( ${CMAKE_CURRENT_SOURCE_DIR}/../Drivers/I2c/Tests + ${CMAKE_CURRENT_BINARY_DIR}/Tests/Drivers/I2c ) # LCD Screen HD44780 driver -add_subdirectory( ${CMAKE_SOURCE_DIR}/../Drivers/Lcd_screen/Tests - ${CMAKE_BINARY_DIR}/Tests/Drivers/Lcd_screen +add_subdirectory( ${CMAKE_CURRENT_SOURCE_DIR}/../Drivers/Lcd_screen/Tests + ${CMAKE_CURRENT_BINARY_DIR}/Tests/Drivers/Lcd_screen ) # Io driver -add_subdirectory( ${CMAKE_SOURCE_DIR}/../Drivers/Io/Tests - ${CMAKE_BINARY_DIR}/Tests/Drivers/Io +add_subdirectory( ${CMAKE_CURRENT_SOURCE_DIR}/../Drivers/Io/Tests + ${CMAKE_CURRENT_BINARY_DIR}/Tests/Drivers/Io ) -# Thermistor driver -add_subdirectory( ${CMAKE_SOURCE_DIR}/../Sensors/Thermistor/Tests - ${CMAKE_BINARY_DIR}/Tests/Sensors/Thermistor +# Modules +add_subdirectory( ${CMAKE_CURRENT_SOURCE_DIR}/../Modules/Timebase/Tests + ${CMAKE_CURRENT_BINARY_DIR}/Tests/Modules/Timebase ) # Modules -add_subdirectory( ${CMAKE_SOURCE_DIR}/../Modules/Timebase/Tests - ${CMAKE_BINARY_DIR}/Tests/Modules/Timebase +add_subdirectory( ${CMAKE_CURRENT_SOURCE_DIR}/../Modules/Pwm/Tests + ${CMAKE_CURRENT_BINARY_DIR}/Tests/Modules/Pwm ) \ No newline at end of file diff --git a/Toolchain/avr8-gcc-toolchain.cmake b/Toolchain/avr8-gcc-toolchain.cmake index 4ea9928..aaf2643 100644 --- a/Toolchain/avr8-gcc-toolchain.cmake +++ b/Toolchain/avr8-gcc-toolchain.cmake @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.3) +cmake_minimum_required(VERSION 3.20) # see CMAKE_SYSTEM_NAME for cross compiling and Cmake system version if(WIN32 AND NOT CMAKE_GENERATOR STREQUAL "Unix Makefiles") @@ -142,10 +142,10 @@ endif( NOT ( (CMAKE_BUILD_TYPE MATCHES Release) OR -Wextra \ -Wno-main \ -Wundef \ - -pedantic \ -Wstrict-prototypes \ -Werror \ -Wfatal-errors ") + #-pedantic \ -> Disabled for now as it prevents using the #warning preproc macros. It can be added back when the C23 standard is out and supported by toolchains ! set(COMPILER_LINKER_FORWARD_OPTIONS "-Wl,--gc-sections -Wl,--relax") set(FULL_OPTIONS "${COMPILE_OPTIONS} ${COMPILER_WARNINGS} ${COMPILER_LINKER_FORWARD_OPTIONS}") diff --git a/Utils/inc/memutils.h b/Utils/inc/memutils.h index be4aaf4..feb950e 100644 --- a/Utils/inc/memutils.h +++ b/Utils/inc/memutils.h @@ -8,7 +8,7 @@ /** * @brief provides a volatile-ready memset kind-of interface * Sets all blocks of targeted memory to the given value, when memory is marked as volatile - * @param[in/out] ptr : memory block to be modified (volatile) + * @param[in,out] ptr : memory block to be modified (volatile) * @param[in] value : sets * @param[in] size : sizeof the block in bytes */