diff --git a/README.md b/README.md index fc4f82adb6..9fedddfe46 100644 --- a/README.md +++ b/README.md @@ -445,6 +445,30 @@ Please [discover modm's peripheral drivers for your specific device][discover]. ✕ ✕ +RTC +○ +○ +○ +○ +○ +○ +○ +✅ +○ +○ +○ +○ +○ +○ +○ +○ +○ +○ +○ +✕ +✕ +✕ + SPI ✅ ✅ diff --git a/examples/nucleo_g474re/rtc/main.cpp b/examples/nucleo_g474re/rtc/main.cpp new file mode 100644 index 0000000000..c23ad2cd2a --- /dev/null +++ b/examples/nucleo_g474re/rtc/main.cpp @@ -0,0 +1,63 @@ +// coding: utf-8 +/* + * Copyright (c) 2023, Rasmus Kleist Hørlyck Sørensen + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#include +#include +#include +#include + +using namespace modm::literals; + +// Set the log level +#undef MODM_LOG_LEVEL +#define MODM_LOG_LEVEL modm::log::INFO + +static constexpr Rtc::DateTime datetime = { + .year = 0, + .month = 12, + .date = 31, + .weekday = 1, + .hour = 23, + .minute = 59, + .second = 30, +}; + +int +main() +{ + Board::initialize(); + + modm::PeriodicTimer timer(std::chrono::milliseconds(1000)); + + Rtc::initialize(); + while (true) + { + if (timer.execute()) + { + float ss; + Rtc::DateTime dt; + if (Rtc::getSubSecond(ss) && Rtc::getDateTime(dt)) + { + MODM_LOG_INFO << "Date: " << dt.date << "-" << dt.month << "-" << dt.year << modm::endl; + MODM_LOG_INFO << "Weekday: " << dt.weekday << modm::endl; + MODM_LOG_INFO << "Time: " << dt.hour << ":" << dt.minute << ":" << dt.second << modm::endl; + MODM_LOG_INFO << "Sub Second: " << ss << modm::endl; + } + else + { + MODM_LOG_INFO << "Error: Unable to get Date Time and Sub Second" << modm::endl; + } + } + } + + return 0; +} diff --git a/examples/nucleo_g474re/rtc/project.xml b/examples/nucleo_g474re/rtc/project.xml new file mode 100644 index 0000000000..f2a8865c21 --- /dev/null +++ b/examples/nucleo_g474re/rtc/project.xml @@ -0,0 +1,12 @@ + + modm:nucleo-g474re + + + + + modm:debug + modm:platform:rtc + modm:processing:timer + modm:build:scons + + diff --git a/src/modm/board/nucleo_g474re/board.hpp b/src/modm/board/nucleo_g474re/board.hpp index 870a8c178e..fc8541cccf 100644 --- a/src/modm/board/nucleo_g474re/board.hpp +++ b/src/modm/board/nucleo_g474re/board.hpp @@ -114,6 +114,9 @@ struct SystemClock // update frequencies for busy-wait delay functions Rcc::updateCoreFrequency(); + Rcc::enableLowSpeedExternalCrystal(); + Rcc::enableRealTimeClock(Rcc::RealTimeClockSource::LowSpeedExternalCrystal); + Rcc::setCanClockSource(Rcc::CanClockSource::Pclk); return true; } diff --git a/src/modm/math/utils.hpp b/src/modm/math/utils.hpp index 74fd7ef5d7..5f45b315d8 100644 --- a/src/modm/math/utils.hpp +++ b/src/modm/math/utils.hpp @@ -22,5 +22,6 @@ #include "utils/operator.hpp" #include "utils/endianness.hpp" #include "utils/crc.hpp" +#include "utils/bcd.hpp" #endif // MODM_MATH_UTILS_HPP diff --git a/src/modm/math/utils/bcd.hpp b/src/modm/math/utils/bcd.hpp new file mode 100644 index 0000000000..fe1c341d8f --- /dev/null +++ b/src/modm/math/utils/bcd.hpp @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2023, Rasmus Kleist Hørlyck Sørensen + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#ifndef MODM_MATH_BCD_HPP +#define MODM_MATH_BCD_HPP + +#include + +namespace modm +{ + +/// @ingroup modm_math_utils +/// @{ + +constexpr uint32_t +fromBcd(uint32_t bcd) +{ + uint32_t decimal = 0; + for (uint16_t multiplier = 1; bcd != 0; multiplier *= 10) + { + decimal += (bcd & 0b1111) * multiplier; + bcd >>= 4; + } + return decimal; +} + +constexpr uint32_t +toBcd(uint32_t decimal) +{ + uint32_t bcd = 0; + uint16_t remainder = decimal % 10; + for (uint16_t shift = 0; decimal != 0; shift += 4) + { + bcd |= remainder << shift; + decimal = (decimal - remainder) / 10; + remainder = decimal % 10; + } + return bcd; +} + +/// @} + +} // namespace modm + +#endif // MODM_MATH_BCD_HPP diff --git a/src/modm/platform/clock/stm32/module.lb b/src/modm/platform/clock/stm32/module.lb index 6dff923a6f..c81af65a2b 100644 --- a/src/modm/platform/clock/stm32/module.lb +++ b/src/modm/platform/clock/stm32/module.lb @@ -179,6 +179,9 @@ def build(env): nper = "DSI" if "Eth" in all_peripherals and per == "ETHMAC": per = "Eth" + if "Rtc" in all_peripherals and per == "RTCAPB": + per = "RTC" + nper = "RTCAPB" # Fix USBOTG OTG if target.family == "u5" and per == "OTG": per = "Usbotgfs" @@ -193,7 +196,9 @@ def build(env): if per.capitalize() not in all_peripherals: continue if "EN" in mode: - rcc_enable[per.capitalize()] = (nper, mode["EN"]) + kw = per.capitalize() + if kw not in rcc_enable: + rcc_enable[kw] = (nper, mode["EN"]) if "RST" in mode: rcc_reset[nper] = mode["RST"] diff --git a/src/modm/platform/clock/stm32/rcc_impl.hpp.in b/src/modm/platform/clock/stm32/rcc_impl.hpp.in index 1ed38f1485..5e94f23d88 100644 --- a/src/modm/platform/clock/stm32/rcc_impl.hpp.in +++ b/src/modm/platform/clock/stm32/rcc_impl.hpp.in @@ -167,4 +167,4 @@ Rcc::isEnabled() %% endfor } -} // namespace modm::platform +} // namespace modm::platform \ No newline at end of file diff --git a/src/modm/platform/core/stm32/startup_platform.c.in b/src/modm/platform/core/stm32/startup_platform.c.in index 5764524b36..408a57856f 100644 --- a/src/modm/platform/core/stm32/startup_platform.c.in +++ b/src/modm/platform/core/stm32/startup_platform.c.in @@ -61,6 +61,8 @@ __modm_initialize_platform(void) // Enable Data Tighly Coupled Memory (DTCM) and backup SRAM (BKPSRAM) RCC->AHB1ENR |= RCC_AHB1ENR_DTCMRAMEN | RCC_AHB1ENR_BKPSRAMEN; %% elif target.family in ["g0", "g4", "l4", "l5"] + // Enable access to RTC and Backup registers + PWR->CR1 |= PWR_CR1_DBP; %% if target.family in ["l4", "g4"] RCC->APB1ENR1 |= RCC_APB1ENR1_PWREN; %% elif target.family != "g0" diff --git a/src/modm/platform/rtc/stm32/module.lb b/src/modm/platform/rtc/stm32/module.lb new file mode 100644 index 0000000000..92f29e6b1b --- /dev/null +++ b/src/modm/platform/rtc/stm32/module.lb @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (c) 2023, Rasmus Kleist Hørlyck Sørensen +# +# This file is part of the modm project. +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# ----------------------------------------------------------------------------- + +def init(module): + module.name = ":platform:rtc" + module.description = "Real Time Clock (RTC)" + +def prepare(module, options): + device = options[":target"] + if device.identifier.family not in ["g4"]: + return False + if not device.has_driver("rtc:stm32*"): + return False + + module.depends( + ":cmsis:device", + ":platform:rcc", + ":architecture:register", + ":math:utils", + ":math:units",) + + return True + +def build(env): + target = env[":target"].identifier + + env.outbasepath = "modm/src/modm/platform/rtc" + env.template("rtc_impl.hpp.in") + env.template("rtc.cpp.in") + env.template("rtc.hpp.in") diff --git a/src/modm/platform/rtc/stm32/rtc.cpp.in b/src/modm/platform/rtc/stm32/rtc.cpp.in new file mode 100644 index 0000000000..78dcbebf00 --- /dev/null +++ b/src/modm/platform/rtc/stm32/rtc.cpp.in @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2023, Rasmus Kleist Hørlyck Sørensen + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#include "rtc.hpp" + +#include + +namespace modm::platform +{ + +// ---------------------------------------------------------------------------- + +bool +Rtc::getDateTime(DateTime &dateTime, uint32_t waitCycles) +{ + /// TODO: Determine if ABP1 frequency is less than seven times RTC frequency + /// TODO: Determine if BYPSHAD control bit is set or cleared in the RTC_CR register + + // After initialization, synchronization software must wait until RSF is set + while (!(RTC->ICSR & RTC_ICSR_RSF)) + if (--waitCycles == 0) return false; + + dateTime.hour = fromBcd((RTC->TR & (RTC_TR_HT_Msk | RTC_TR_HU_Msk)) >> RTC_TR_HU_Pos); + dateTime.minute = fromBcd((RTC->TR & (RTC_TR_MNT_Msk | RTC_TR_MNU_Msk)) >> RTC_TR_MNU_Pos); + dateTime.second = fromBcd((RTC->TR & (RTC_TR_ST_Msk | RTC_TR_SU_Msk)) >> RTC_TR_SU_Pos); + + dateTime.year = fromBcd((RTC->DR & (RTC_DR_YT_Msk | RTC_DR_YU_Msk)) >> RTC_DR_YU_Pos); + dateTime.month = fromBcd((RTC->DR & (RTC_DR_MT_Msk | RTC_DR_MU_Msk)) >> RTC_DR_MU_Pos); + dateTime.date = fromBcd((RTC->DR & (RTC_DR_DT_Msk | RTC_DR_DU_Msk)) >> RTC_DR_DU_Pos); + dateTime.weekday = ((RTC->DR & RTC_DR_WDU_Msk) >> RTC_DR_WDU_Pos); + + return true; +} + +// ---------------------------------------------------------------------------- + +bool +Rtc::getSubSecond(float &subsecond, uint32_t waitCycles) +{ + // After initialization, synchronization software must wait until RSF is set + while (!(RTC->ICSR & RTC_ICSR_RSF)) + if (--waitCycles == 0) return false; + + /* + uint32_t predivS = getSynchronousPrescaler(); + uint32_t ss = (RTC->SSR & RTC_SSR_SS_Msk) >> RTC_SSR_SS_Pos; + subsecond = float(predivS - ss) / float(predivS + 1); + /// TODO: Handle case when ss > predivS + */ + return true; +} + +} diff --git a/src/modm/platform/rtc/stm32/rtc.hpp.in b/src/modm/platform/rtc/stm32/rtc.hpp.in new file mode 100644 index 0000000000..8387f74b1b --- /dev/null +++ b/src/modm/platform/rtc/stm32/rtc.hpp.in @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2023, Rasmus Kleist Hørlyck Sørensen + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#ifndef MODM_STM32_RTC_HPP +#define MODM_STM32_RTC_HPP + +#include + +#include +#include +#include +#include + +namespace modm::platform +{ + +/** + * Real Time Clock (RTC) control for STM32 devices + * + * @author Rasmus Kleist Hørlyck Sørensen + * @ingroup modm_platform_rtc + */ +class Rtc +{ + using duration = std::chrono::duration; + using rep = duration::rep; + using period = duration::period; + using time_point = std::chrono::time_point; + +public: + + /// TODO: Use std::tm instead + struct modm_packed + DateTime + { + uint8_t year; + uint8_t month; + uint8_t date; + uint8_t weekday; + uint8_t hour; + uint8_t minute; + uint8_t second; + }; + +public: + static void + enable(); + + static void + disable(); + + /** + * Initializes the Real Time Clock (RTC) to provide a timer to `modm::Clock` and + * `modm::PreciseClock`. + * + * @tparam SystemClock + * the currently active system clock + * @tparam rtcclk + * the RTC clock frequency + * @tparam tolerance + * the allowed tolerance for the resulting clock rate + * + * @param waitCycle Number of cycles to wait for the INITF bit to be set. (default = 2048) + * + * @attention The APB1 clock frequency must never be lower than the RTC clock frequency + * @return True on success + */ + template< class SystemClock, frequency_t rtcclk = 32.768_kHz, percent_t tolerance=pct(0) > + static bool + initialize(uint32_t waitCycles = 2048); + + /** + * Synchronized to a remote clock with a high degree of precision + * + * @tparam Rep + * an arithmetic type representing the number of ticks + * @tparam Period + * a std::ratio representing the tick period (i.e. the number of second's fractions per tick) + * + * @param delay The amount of time to delay (or advance) the + * @param waitCycle Number of cycles to wait for the INITF bit to be set. (default = 2048) + * + * @return True on success + */ + template< typename Rep, typename Period > + static bool + synchronize(std::chrono::duration delay, uint32_t waitCycles = 2048); + + /** + * @brief + * + */ + static bool + getDateTime(DateTime &dateTime, uint32_t waitCycles = 2048); + + /** + * @brief Get the subsecond second used for synchronization + * + * @return float + */ + static bool + getSubSecond(float &subsecond, uint32_t waitCycles = 2048); + +public: + + enum class + Interrupt : uint32_t + { + InternalTimestamp = RTC_CR_ITSE, + Timestamp = RTC_CR_TSIE, + WakeupTimer = RTC_CR_WUTIE, + AlarmB = RTC_CR_ALRBIE, + AlarmA = RTC_CR_ALRAIE, + }; + MODM_FLAGS32(Interrupt); + + enum class + InterruptFlag : uint32_t + { + InternalTimestamp = RTC_SR_ITSF, + TimestampOverflow = RTC_SR_TSOVF, + Timestamp = RTC_SR_TSF, + WakeupTimer = RTC_SR_WUTF, + AlarmB = RTC_SR_ALRBF, + AlarmA = RTC_SR_ALRAF, + }; + MODM_FLAGS32(InterruptFlag); + + static void + enableInterruptVector(bool enable, uint32_t priority); + + static void + enableInterrupt(Interrupt_t interrupt); + + static void + disableInterrupt(Interrupt_t interrupt); + + static InterruptFlag_t + getInterruptFlags(); + + /** + * + * + * @warning Not all InterruptFlags can be cleared this way. + */ + static void + acknowledgeInterruptFlag(InterruptFlag_t flags); + +private: + /// Unlock RTC register write protection + static void + unlock(); + + /// Lock RTC register write protection + static void + lock(); + + static inline frequency_t rtc_ker_ck{0}; /// RTCCLK frequency + static inline frequency_t ck_apre{0}; /// RTCCLK frequency / (PREDIV_A + 1) + static inline frequency_t ck_spre{0}; /// ck_apre frequency / (PREDIV_S + 1) + + friend class modm::chrono::milli_clock; + friend class modm::chrono::micro_clock; +}; + +} // namespace modm::platform + +#include "rtc_impl.hpp" + +#endif // MODM_STM32_RTC_HPP diff --git a/src/modm/platform/rtc/stm32/rtc_impl.hpp.in b/src/modm/platform/rtc/stm32/rtc_impl.hpp.in new file mode 100644 index 0000000000..42396a4a6d --- /dev/null +++ b/src/modm/platform/rtc/stm32/rtc_impl.hpp.in @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2023, Rasmus Kleist Hørlyck Sørensen + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#include + +namespace modm::platform +{ + +// ---------------------------------------------------------------------------- + +void inline +Rtc::enable() +{ + Rcc::enable(); +} + +// ---------------------------------------------------------------------------- + +void inline +Rtc::disable() +{ + Rcc::disable(); +} + +// ---------------------------------------------------------------------------- + +template< class SystemClock, frequency_t rtcclk, percent_t tolerance > +bool +Rtc::initialize(uint32_t waitCycles) +{ + static_assert(rtcclk < SystemClock::Apb1, + "The APB1 clock frequency must never be lower than the RTC clock frequency!"); + + /// TODO: Devise a novel algorithm for chosing prescalers + constexpr uint32_t prediv_a = 0x007F; + constexpr uint32_t prediv_s = 0x00FF; + //PeripheralDriver::assertBaudrateInTolerance< result.frequency, 1, tolerance >(); + + rtc_ker_ck = rtcclk; + ck_apre = rtcclk / (prediv_a + 1); + ck_spre = ck_apre / (prediv_s + 1); + + enable(); + unlock(); + + // Enter initialization mode + RTC->ICSR |= RTC_ICSR_INIT; + + // Wait until initialization phase mode is entered when INITF bit is set + while (!(RTC->ICSR & RTC_ICSR_INITF)) + if (--waitCycles == 0) return false; + + // To generate a 1 Hz clock for the calendar counter, program both the prescaler factors + RTC->PRER = ((prediv_a << RTC_PRER_PREDIV_A_Pos) & RTC_PRER_PREDIV_A_Msk) | + ((prediv_s << RTC_PRER_PREDIV_S_Pos) & RTC_PRER_PREDIV_S_Msk); + + // Configure 24 hour format + RTC->CR &= ~RTC_CR_FMT; + + /* + // Load the time in 24 hour format into the shadow registers + RTC->TR = ((toBcd(dateTime.hour) << RTC_TR_HU_Pos) & (RTC_TR_HT_Msk | RTC_TR_HU_Msk)) | + ((toBcd(dateTime.minute) << RTC_TR_MNU_Pos) & (RTC_TR_MNT_Msk | RTC_TR_MNU_Msk)) | + ((toBcd(dateTime.second) << RTC_TR_SU_Pos) & (RTC_TR_ST_Msk | RTC_TR_SU_Msk)); + + // Load the date in the shadow registers + RTC->DR = ((toBcd(dateTime.year) << RTC_DR_YU_Pos) & (RTC_DR_YT_Msk | RTC_DR_YU_Msk)) | + ((toBcd(dateTime.month) << RTC_DR_MU_Pos) & (RTC_DR_MT_Msk | RTC_DR_MU_Msk)) | + ((toBcd(dateTime.date) << RTC_DR_DU_Pos) & (RTC_DR_DT_Msk | RTC_DR_DU_Msk)) | + ((dateTime.weekday << RTC_DR_WDU_Pos) & RTC_DR_WDU_Msk); + */ + + // Exit the initialization mode by clearing the INIT bit + RTC->ICSR &= ~RTC_ICSR_INIT; + + lock(); + + return true; +} + +// ---------------------------------------------------------------------------- + +template< typename Rep, typename Period > +bool +Rtc::synchronize(std::chrono::duration delay, uint32_t waitCycles) +{ + // Check that SS[15] = 0 in order to ensure that no overflow will occur, before initiating a shift operation. + if (((RTC->SSR & RTC_SSR_SS_Msk) & (Bit15 << RTC_SSR_SS_Pos)) == 0 && RTC->CR & RTC_CR_REFCKON == 0) { + unlock(); + + if (0 <= delay.count()) { + uint32_t subfs = delay.count() * Period::num * ck_spre / Period::den; + RTC->SHIFTR = (subfs << RTC_SHIFTR_SUBFS_Pos) & RTC_SHIFTR_SUBFS_Msk; + } + else { + uint32_t subfs = ck_spre + delay.count() * Period::num * ck_spre / Period::den; + RTC->SHIFTR = RTC_SHIFTR_ADD1S | ((subfs << RTC_SHIFTR_SUBFS_Pos) & RTC_SHIFTR_SUBFS_Msk); + } + + lock(); + + // Wait until pending shift operation is performed before returning + while (RTC->ICSR & RTC_ICSR_SHPF) + if (--waitCycles == 0) return false; + + return true; + } + return false; +} + +// ---------------------------------------------------------------------------- + +void inline +Rtc::enableInterruptVector(bool enable, uint32_t priority) +{ + /// TODO: Some devices only have a single RTC interrupt register + if (enable) { + // Set priority for the interrupt vector + NVIC_SetPriority(RTC_TAMP_LSECSS_IRQn, priority); + NVIC_SetPriority(RTC_WKUP_IRQn, priority); + // register IRQ at the NVIC + NVIC_EnableIRQ(RTC_TAMP_LSECSS_IRQn); + NVIC_EnableIRQ(RTC_WKUP_IRQn); + } + else { + NVIC_DisableIRQ(RTC_TAMP_LSECSS_IRQn); + NVIC_DisableIRQ(RTC_WKUP_IRQn); + } +} + +// ---------------------------------------------------------------------------- + +void inline +Rtc::enableInterrupt(Interrupt_t interrupt) +{ + RTC->CR |= interrupt.value; +} + +// ---------------------------------------------------------------------------- + +void inline +Rtc::disableInterrupt(Interrupt_t interrupt) +{ + RTC->CR &= ~interrupt.value; +} + +// ---------------------------------------------------------------------------- + +Rtc::InterruptFlag_t inline +Rtc::getInterruptFlags() +{ + return InterruptFlag_t(RTC->SR); +} + +// ---------------------------------------------------------------------------- + +void inline +Rtc::acknowledgeInterruptFlag(InterruptFlag_t flags) +{ + RTC->SCR |= flags.value; +} + +// ---------------------------------------------------------------------------- + +void inline +Rtc::unlock() +{ + // DBP bit must be set in order to enable RTC registers write access. + PWR->CR1 |= PWR_CR1_DBP; + + // Unlock the write protection on the protected RTC registers. + RTC->WPR = 0xCA; + RTC->WPR = 0x53; +} + +// ---------------------------------------------------------------------------- + +void inline +Rtc::lock() +{ + // Lock the write protection on the protected RTC registers. + RTC->WPR = 0xFF; + + // DBP bit must be reset in order to disable RTC registers write access. + PWR->CR1 &= ~PWR_CR1_DBP; +} + +} // namespace modm::platform diff --git a/test/modm/math/utils/bcd_test.cpp b/test/modm/math/utils/bcd_test.cpp new file mode 100644 index 0000000000..7c20adbba5 --- /dev/null +++ b/test/modm/math/utils/bcd_test.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2023, Rasmus Kleist Hørlyck Sørensen + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#include + +#include "bcd_test.hpp" + +void +BcdTest::testFromBcd() +{ + TEST_ASSERT_EQUALS(modm::fromBcd(0x01), 0x01U); + TEST_ASSERT_EQUALS(modm::fromBcd(0x02), 0x02U); + TEST_ASSERT_EQUALS(modm::fromBcd(0x03), 0x03U); + TEST_ASSERT_EQUALS(modm::fromBcd(0x04), 0x04U); + TEST_ASSERT_EQUALS(modm::fromBcd(0x05), 0x05U); + TEST_ASSERT_EQUALS(modm::fromBcd(0x06), 0x06U); + TEST_ASSERT_EQUALS(modm::fromBcd(0x07), 0x07U); + TEST_ASSERT_EQUALS(modm::fromBcd(0x08), 0x08U); + TEST_ASSERT_EQUALS(modm::fromBcd(0x09), 0x09U); +} + +void +BcdTest::testToBcd() +{ + TEST_ASSERT_EQUALS(modm::toBcd(0x01), 0x01U); + TEST_ASSERT_EQUALS(modm::toBcd(0x02), 0x02U); + TEST_ASSERT_EQUALS(modm::toBcd(0x03), 0x03U); + TEST_ASSERT_EQUALS(modm::toBcd(0x04), 0x04U); + TEST_ASSERT_EQUALS(modm::toBcd(0x05), 0x05U); + TEST_ASSERT_EQUALS(modm::toBcd(0x06), 0x06U); + TEST_ASSERT_EQUALS(modm::toBcd(0x07), 0x07U); + TEST_ASSERT_EQUALS(modm::toBcd(0x08), 0x08U); + TEST_ASSERT_EQUALS(modm::toBcd(0x09), 0x09U); +} \ No newline at end of file diff --git a/test/modm/math/utils/bcd_test.hpp b/test/modm/math/utils/bcd_test.hpp new file mode 100644 index 0000000000..24f313d293 --- /dev/null +++ b/test/modm/math/utils/bcd_test.hpp @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2023, Rasmus Kleist Hørlyck Sørensen + * + * This file is part of the modm project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +// ---------------------------------------------------------------------------- + +#include + +/// @ingroup modm_test_test_math +class BcdTest : public unittest::TestSuite +{ +public: + void + testFromBcd(); + + void + testToBcd(); +};