-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Feature - RTC alarm on every second #370
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
b3fa3e6
61309a6
f945ab8
66ceabd
eddfce4
3deda98
82a4681
27cb80e
ecedcbe
1a6884b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not sure why this is state; it seems like it should be a macro parameter as the usage on line 161 should always be 1 (not the last value) |
||
|
||
#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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should all the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. a question of coding guidelines. Technically it's the same There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fair enough, that's a Graham-decision then 🙂 |
||
// 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; | ||
Comment on lines
+176
to
+179
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably worth adding a comment here saying that if all values are -1 then we repeat every second? |
||
|
||
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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. even more confused, because t->sec is -1 here always There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe best described with an example: If sec value is negative, it is interpreted as a relative value. Means if it is -1, _seconds_increment is set to 1, and the alarm appears whenever the seconds change. If now input sec is -2, the increment will be 2 and therefore the ISR is called every 2 seconds. As this input value is just local within rtc_set_alarm(), it must be remembered as a state, so that ISR can access it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. E.g. like snooze function on a clock. Set alarm to -30sec repeat, alarm appears, you press button to switch off the alarm, and after 30s it appears again. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh maybe you're confused because _seconds_increment is processed within the macro ADD_AND_ENABLE_REPEATABLE_SECOND? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think @Reneg973 was intending that you could set e.g. "repeat every 5 seconds" by setting EDIT: That's weird, I was replying to @kilograham 's comment and for some reason GH didn't show me @Reneg973 's comments until after I posted mine! 😕 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From SW side you are right, this functionality could be implemented in a level above.
So both methods produce more overhead than really required. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 3rd approach: repeat every second -> query datetime_t -> if As I mentioned before, with RP2040 running with a CPU clock of 125MHz I don't think you really need to worry about "slow ISR"? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You do need to worry! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 3rd approach = 1st :) - I meant the same! And t->sec % 5 is not correct, you need include the start seconds. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
@kilograham:
What do you think? |
||
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 | ||
Reneg973 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
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; | ||
} |
Uh oh!
There was an error while loading. Please reload this page.