diff --git a/src/rp2_common/hardware_rtc/include/hardware/rtc.h b/src/rp2_common/hardware_rtc/include/hardware/rtc.h index 8757e36e0..4a335384e 100644 --- a/src/rp2_common/hardware_rtc/include/hardware/rtc.h +++ b/src/rp2_common/hardware_rtc/include/hardware/rtc.h @@ -32,6 +32,11 @@ extern "C" { #endif +// PICO_CONFIG: PARAM_ASSERTIONS_ENABLED_RTC, Enable/disable assertions in the RTC module, type=bool, default=0, group=hardware_rtc +#ifndef PARAM_ASSERTIONS_ENABLED_RTC +#define PARAM_ASSERTIONS_ENABLED_RTC 0 +#endif + /*! Callback function type for RTC alarms * \ingroup hardware_rtc * @@ -50,7 +55,7 @@ void rtc_init(void); * \param t Pointer to a \ref datetime_t structure contains time to set * \return true if set, false if the passed in datetime was invalid. */ -bool rtc_set_datetime(datetime_t *t); +bool rtc_set_datetime(const datetime_t *t); /*! \brief Get the current time from the RTC * \ingroup hardware_rtc @@ -69,10 +74,12 @@ bool rtc_running(void); /*! \brief Set a time in the future for the RTC to call a user provided callback * \ingroup hardware_rtc * - * \param t Pointer to a \ref datetime_t structure containing a time in the future to fire the alarm. Any values set to -1 will not be matched on. + * \param t Pointer to a \ref datetime_t structure containing a time in the future to fire the alarm. Any values set to a negative value will not be matched on. + * With one exception: If all values are negative, it will be matched on every step of abs(datetime_t::sec). * \param user_callback pointer to a \ref rtc_callback_t to call when the alarm fires + * \return false if parameters aren't valid */ -void rtc_set_alarm(datetime_t *t, rtc_callback_t user_callback); +bool rtc_set_alarm(const datetime_t *t, rtc_callback_t user_callback); /*! \brief Enable the RTC alarm (if inactive) * \ingroup hardware_rtc @@ -84,6 +91,11 @@ void rtc_enable_alarm(void); */ void rtc_disable_alarm(void); +/*! \brief Deletes the alarm previously set with \see rtc_set_alarm + * \ingroup hardware_rtc + */ +void rtc_delete_alarm(void); + #ifdef __cplusplus } #endif diff --git a/src/rp2_common/hardware_rtc/rtc.c b/src/rp2_common/hardware_rtc/rtc.c index 5429acd4b..0e6a3ac9c 100644 --- a/src/rp2_common/hardware_rtc/rtc.c +++ b/src/rp2_common/hardware_rtc/rtc.c @@ -11,9 +11,28 @@ #include "hardware/resets.h" #include "hardware/clocks.h" +#define RANGE_CHECK_YEAR(t) (t->year >= 0 && t->year <= 4095) +#define RANGE_CHECK_MONTH(t) (t->month >= 1 && t->month <= 12) +#define RANGE_CHECK_DAY(t) (t->day >= 1 && t->day <= 31) +#define RANGE_CHECK_DOTW(t) (t->dotw >= 0 && t->dotw <= 6) +#define RANGE_CHECK_HOUR(t) (t->hour >= 0 && t->hour <= 23) +#define RANGE_CHECK_MIN(t) (t->min >= 0 && t->min <= 59) +#define RANGE_CHECK_SEC(t) (t->sec >= 0 && t->sec <= 59) + + // Set this when setting an alarm static rtc_callback_t _callback = NULL; -static bool _alarm_repeats = false; +static uint8_t _seconds_increment = 1; + +#define ADD_AND_ENABLE_REPEATABLE_SECOND(s) (RTC_IRQ_SETUP_1_SEC_ENA_BITS | ((((uint)s + _seconds_increment) % 60) << RTC_IRQ_SETUP_1_SEC_LSB)) + +typedef enum { + NO_REPEAT = 0, + CONTINUOUS_REPEAT = 1, + CONTINUOUS_REPEAT_EVERY_SEC = 2, +} repeat_type; + +static repeat_type _alarm_repeats = NO_REPEAT; bool rtc_running(void) { return (rtc_hw->ctrl & RTC_CTRL_RTC_ACTIVE_BITS); @@ -39,21 +58,38 @@ void rtc_init(void) { rtc_hw->clkdiv_m1 = rtc_freq; } -static bool valid_datetime(datetime_t *t) { +static bool is_valid_datetime(const datetime_t *t) { // Valid ranges taken from RTC doc. Note when setting an RTC alarm // these values are allowed to be -1 to say "don't match this value" - if (!(t->year >= 0 && t->year <= 4095)) return false; - if (!(t->month >= 1 && t->month <= 12)) return false; - if (!(t->day >= 1 && t->day <= 31)) return false; - if (!(t->dotw >= 0 && t->dotw <= 6)) return false; - if (!(t->hour >= 0 && t->hour <= 23)) return false; - if (!(t->min >= 0 && t->min <= 59)) return false; - if (!(t->sec >= 0 && t->sec <= 59)) return false; - return true; + return RANGE_CHECK_YEAR(t) + && RANGE_CHECK_MONTH(t) + && RANGE_CHECK_DAY(t) + && RANGE_CHECK_DOTW(t) + && RANGE_CHECK_HOUR(t) + && RANGE_CHECK_MIN(t) + && RANGE_CHECK_SEC(t); } -bool rtc_set_datetime(datetime_t *t) { - if (!valid_datetime(t)) { +// small helper without check for running rtc +static inline datetime_t _rtc_get_datetime(datetime_t *t) { + // Note: RTC_0 should be read before RTC_1 + uint32_t rtc_val = rtc_hw->rtc_0; + t->dotw = (rtc_val & RTC_RTC_0_DOTW_BITS) >> RTC_RTC_0_DOTW_LSB; + t->hour = (rtc_val & RTC_RTC_0_HOUR_BITS) >> RTC_RTC_0_HOUR_LSB; + t->min = (rtc_val & RTC_RTC_0_MIN_BITS) >> RTC_RTC_0_MIN_LSB; + t->sec = (rtc_val & RTC_RTC_0_SEC_BITS) >> RTC_RTC_0_SEC_LSB; + + rtc_val = rtc_hw->rtc_1; + t->year = (rtc_val & RTC_RTC_1_YEAR_BITS) >> RTC_RTC_1_YEAR_LSB; + t->month = (rtc_val & RTC_RTC_1_MONTH_BITS) >> RTC_RTC_1_MONTH_LSB; + t->day = (rtc_val & RTC_RTC_1_DAY_BITS) >> RTC_RTC_1_DAY_LSB; +} + +bool rtc_set_datetime(const datetime_t *t) { + bool check_params = is_valid_datetime(t); + valid_params_if(RTC, check_params); + + if (!check_params) { return false; } @@ -91,18 +127,7 @@ bool rtc_get_datetime(datetime_t *t) { return false; } - // Note: RTC_0 should be read before RTC_1 - uint32_t rtc_0 = rtc_hw->rtc_0; - uint32_t rtc_1 = rtc_hw->rtc_1; - - t->dotw = (rtc_0 & RTC_RTC_0_DOTW_BITS ) >> RTC_RTC_0_DOTW_LSB; - t->hour = (rtc_0 & RTC_RTC_0_HOUR_BITS ) >> RTC_RTC_0_HOUR_LSB; - t->min = (rtc_0 & RTC_RTC_0_MIN_BITS ) >> RTC_RTC_0_MIN_LSB; - t->sec = (rtc_0 & RTC_RTC_0_SEC_BITS ) >> RTC_RTC_0_SEC_LSB; - t->year = (rtc_1 & RTC_RTC_1_YEAR_BITS ) >> RTC_RTC_1_YEAR_LSB; - t->month = (rtc_1 & RTC_RTC_1_MONTH_BITS) >> RTC_RTC_1_MONTH_LSB; - t->day = (rtc_1 & RTC_RTC_1_DAY_BITS ) >> RTC_RTC_1_DAY_LSB; - + _rtc_get_datetime(t); return true; } @@ -114,60 +139,89 @@ void rtc_enable_alarm(void) { } } -static void rtc_irq_handler(void) { +void rtc_disable_alarm(void) { + // Disable matching and wait for it to stop being active + hw_clear_bits(&rtc_hw->irq_setup_0, RTC_IRQ_SETUP_0_MATCH_ENA_BITS); + while (rtc_hw->irq_setup_0 & RTC_IRQ_SETUP_0_MATCH_ACTIVE_BITS) { + tight_loop_contents(); + } +} + +static void __no_inline_not_in_flash_func(rtc_irq_handler)(void) { // Always disable the alarm to clear the current IRQ. // Even if it is a repeatable alarm, we don't want it to keep firing. // If it matches on a second it can keep firing for that second. rtc_disable_alarm(); if (_alarm_repeats) { - // If it is a repeatable alarm, re enable the alarm. - rtc_enable_alarm(); + if(_alarm_repeats == CONTINUOUS_REPEAT_EVERY_SEC) { + // we need to modify the sec entry with the next valid RTC change and store it into irq_setup_1 + datetime_t t; + rtc_get_datetime(&t); + rtc_hw->irq_setup_1 = ADD_AND_ENABLE_REPEATABLE_SECOND(t.sec); + } } // Call user callback function - if (_callback) { + if (_callback) _callback(); + + // If it is a repeatable alarm, re enable the alarm. + if(_alarm_repeats) { + rtc_enable_alarm(); } } -static bool rtc_alarm_repeats(datetime_t *t) { - // If any value is set to -1 then we don't match on that value - // hence the alarm will eventually repeat - if (t->year < 0) return true; - if (t->month < 0) return true; - if (t->day < 0) return true; - if (t->dotw < 0) return true; - if (t->hour < 0) return true; - if (t->min < 0) return true; - if (t->sec < 0) return true; - return false; +static repeat_type rtc_alarm_repeats(const datetime_t *t) { + // If any value is set to -1 then we don't match on that value + // hence the alarm will eventually repeat + if (t->year < 0 && t->month < 0 && t->day < 0 && t->dotw < 0 + && t->hour < 0 && t->min < 0 && t->sec < 0) return CONTINUOUS_REPEAT_EVERY_SEC; + + return (t->year < 0 || t->month < 0 || t->day < 0 || t->dotw < 0 + || t->hour < 0 || t->min < 0 || t->sec < 0) + ? CONTINUOUS_REPEAT : NO_REPEAT; } -void rtc_set_alarm(datetime_t *t, rtc_callback_t user_callback) { - rtc_disable_alarm(); +bool rtc_set_alarm(const datetime_t *t, rtc_callback_t user_callback) { + if (!rtc_running()) + return false; - // Only add to setup if it isn't -1 - rtc_hw->irq_setup_0 = ((t->year < 0) ? 0 : (((uint)t->year) << RTC_IRQ_SETUP_0_YEAR_LSB )) | - ((t->month < 0) ? 0 : (((uint)t->month) << RTC_IRQ_SETUP_0_MONTH_LSB)) | - ((t->day < 0) ? 0 : (((uint)t->day) << RTC_IRQ_SETUP_0_DAY_LSB )); - rtc_hw->irq_setup_1 = ((t->dotw < 0) ? 0 : (((uint)t->dotw) << RTC_IRQ_SETUP_1_DOTW_LSB)) | - ((t->hour < 0) ? 0 : (((uint)t->hour) << RTC_IRQ_SETUP_1_HOUR_LSB)) | - ((t->min < 0) ? 0 : (((uint)t->min) << RTC_IRQ_SETUP_1_MIN_LSB )) | - ((t->sec < 0) ? 0 : (((uint)t->sec) << RTC_IRQ_SETUP_1_SEC_LSB )); + rtc_disable_alarm(); - // Set the match enable bits for things we care about - if (t->year >= 0) hw_set_bits(&rtc_hw->irq_setup_0, RTC_IRQ_SETUP_0_YEAR_ENA_BITS); - if (t->month >= 0) hw_set_bits(&rtc_hw->irq_setup_0, RTC_IRQ_SETUP_0_MONTH_ENA_BITS); - if (t->day >= 0) hw_set_bits(&rtc_hw->irq_setup_0, RTC_IRQ_SETUP_0_DAY_ENA_BITS); - if (t->dotw >= 0) hw_set_bits(&rtc_hw->irq_setup_1, RTC_IRQ_SETUP_1_DOTW_ENA_BITS); - if (t->hour >= 0) hw_set_bits(&rtc_hw->irq_setup_1, RTC_IRQ_SETUP_1_HOUR_ENA_BITS); - if (t->min >= 0) hw_set_bits(&rtc_hw->irq_setup_1, RTC_IRQ_SETUP_1_MIN_ENA_BITS); - if (t->sec >= 0) hw_set_bits(&rtc_hw->irq_setup_1, RTC_IRQ_SETUP_1_SEC_ENA_BITS); + uint32_t s0 = 0, s1 = 0; // Does it repeat? I.e. do we not match on any of the bits _alarm_repeats = rtc_alarm_repeats(t); + bool check_params = (is_valid_datetime(t) || _alarm_repeats != NO_REPEAT); + valid_params_if(RTC, check_params); + if(!check_params) // none of the parameters is valid + return false; + + // Set the match enable bits for things we care about + if(_alarm_repeats == CONTINUOUS_REPEAT_EVERY_SEC) { + // repeatable every second! All entries are -1 + datetime_t new_dt; + _rtc_get_datetime(&new_dt); + _seconds_increment = (-t->sec) % 60; + s1 = ADD_AND_ENABLE_REPEATABLE_SECOND(new_dt.sec); + } + else { + if (RANGE_CHECK_YEAR(t)) s0 |= RTC_IRQ_SETUP_0_YEAR_ENA_BITS | (((uint)t->year) << RTC_IRQ_SETUP_0_YEAR_LSB); + if (RANGE_CHECK_MONTH(t)) s0 |= RTC_IRQ_SETUP_0_MONTH_ENA_BITS | (((uint)t->month) << RTC_IRQ_SETUP_0_MONTH_LSB); + if (RANGE_CHECK_DAY(t)) s0 |= RTC_IRQ_SETUP_0_DAY_ENA_BITS | (((uint)t->day) << RTC_IRQ_SETUP_0_DAY_LSB); + if (RANGE_CHECK_DOTW(t)) s1 |= RTC_IRQ_SETUP_1_DOTW_ENA_BITS | (((uint)t->dotw) << RTC_IRQ_SETUP_1_DOTW_LSB); + if (RANGE_CHECK_HOUR(t)) s1 |= RTC_IRQ_SETUP_1_HOUR_ENA_BITS | (((uint)t->hour) << RTC_IRQ_SETUP_1_HOUR_LSB); + if (RANGE_CHECK_MIN(t)) s1 |= RTC_IRQ_SETUP_1_MIN_ENA_BITS | (((uint)t->min) << RTC_IRQ_SETUP_1_MIN_LSB); + if (RANGE_CHECK_SEC(t)) s1 |= RTC_IRQ_SETUP_1_SEC_ENA_BITS | (((uint)t->sec) << RTC_IRQ_SETUP_1_SEC_LSB); + + if(!s0 && !s1) return false; // out of range datetime_t input + } + + rtc_hw->irq_setup_0 = s0; + rtc_hw->irq_setup_1 = s1; + // Store function pointer we can call later _callback = user_callback; @@ -180,12 +234,19 @@ void rtc_set_alarm(datetime_t *t, rtc_callback_t user_callback) { irq_set_enabled(RTC_IRQ, true); rtc_enable_alarm(); + return true; } -void rtc_disable_alarm(void) { - // Disable matching and wait for it to stop being active - hw_clear_bits(&rtc_hw->irq_setup_0, RTC_IRQ_SETUP_0_MATCH_ENA_BITS); - while(rtc_hw->irq_setup_0 & RTC_IRQ_SETUP_0_MATCH_ACTIVE_BITS) { - tight_loop_contents(); - } +void rtc_delete_alarm(void) +{ + // first disable the alarm + rtc_disable_alarm(); + + // don't receive interrupts anymore + rtc_hw->inte = RTC_INTE_RESET; + + // disable IRQ and remove handler + irq_remove_handler(RTC_IRQ, rtc_irq_handler); + + _callback = NULL; }