In order for the returned value to accurately reflect a single moment in time, ensure the registers are read just once and in the datasheet order. Before this change, the RTC registers would each be read multiple times, leading (infrequently) to the returned fields not all reflecting the same moment in time. The rp2040 datasheet has what I believe is an incorrect example (embedding the source of this function); will the datasheet be updated if this function is fixed? This problem is only a speculative one; I did not actually observe it in the wild.
192 lines
6.5 KiB
C
192 lines
6.5 KiB
C
/*
|
|
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
|
|
*
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*/
|
|
|
|
#include "pico.h"
|
|
|
|
#include "hardware/irq.h"
|
|
#include "hardware/rtc.h"
|
|
#include "hardware/resets.h"
|
|
#include "hardware/clocks.h"
|
|
|
|
// Set this when setting an alarm
|
|
static rtc_callback_t _callback = NULL;
|
|
static bool _alarm_repeats = false;
|
|
|
|
bool rtc_running(void) {
|
|
return (rtc_hw->ctrl & RTC_CTRL_RTC_ACTIVE_BITS);
|
|
}
|
|
|
|
void rtc_init(void) {
|
|
// Get clk_rtc freq and make sure it is running
|
|
uint rtc_freq = clock_get_hz(clk_rtc);
|
|
assert(rtc_freq != 0);
|
|
|
|
// Take rtc out of reset now that we know clk_rtc is running
|
|
reset_block(RESETS_RESET_RTC_BITS);
|
|
unreset_block_wait(RESETS_RESET_RTC_BITS);
|
|
|
|
// Set up the 1 second divider.
|
|
// If rtc_freq is 400 then clkdiv_m1 should be 399
|
|
rtc_freq -= 1;
|
|
|
|
// Check the freq is not too big to divide
|
|
assert(rtc_freq <= RTC_CLKDIV_M1_BITS);
|
|
|
|
// Write divide value
|
|
rtc_hw->clkdiv_m1 = rtc_freq;
|
|
}
|
|
|
|
static bool valid_datetime(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;
|
|
}
|
|
|
|
bool rtc_set_datetime(datetime_t *t) {
|
|
if (!valid_datetime(t)) {
|
|
return false;
|
|
}
|
|
|
|
// Disable RTC
|
|
rtc_hw->ctrl = 0;
|
|
// Wait while it is still active
|
|
while (rtc_running()) {
|
|
tight_loop_contents();
|
|
}
|
|
|
|
// Write to setup registers
|
|
rtc_hw->setup_0 = (((uint)t->year) << RTC_SETUP_0_YEAR_LSB ) |
|
|
(((uint)t->month) << RTC_SETUP_0_MONTH_LSB) |
|
|
(((uint)t->day) << RTC_SETUP_0_DAY_LSB);
|
|
rtc_hw->setup_1 = (((uint)t->dotw) << RTC_SETUP_1_DOTW_LSB) |
|
|
(((uint)t->hour) << RTC_SETUP_1_HOUR_LSB) |
|
|
(((uint)t->min) << RTC_SETUP_1_MIN_LSB) |
|
|
(((uint)t->sec) << RTC_SETUP_1_SEC_LSB);
|
|
|
|
// Load setup values into rtc clock domain
|
|
rtc_hw->ctrl = RTC_CTRL_LOAD_BITS;
|
|
|
|
// Enable RTC and wait for it to be running
|
|
rtc_hw->ctrl = RTC_CTRL_RTC_ENABLE_BITS;
|
|
while (!rtc_running()) {
|
|
tight_loop_contents();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool rtc_get_datetime(datetime_t *t) {
|
|
// Make sure RTC is running
|
|
if (!rtc_running()) {
|
|
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;
|
|
|
|
return true;
|
|
}
|
|
|
|
void rtc_enable_alarm(void) {
|
|
// Set matching and wait for it to be enabled
|
|
hw_set_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 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();
|
|
}
|
|
|
|
// Call user callback function
|
|
if (_callback) {
|
|
_callback();
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
void rtc_set_alarm(datetime_t *t, rtc_callback_t user_callback) {
|
|
rtc_disable_alarm();
|
|
|
|
// 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 ));
|
|
|
|
// 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);
|
|
|
|
// Does it repeat? I.e. do we not match on any of the bits
|
|
_alarm_repeats = rtc_alarm_repeats(t);
|
|
|
|
// Store function pointer we can call later
|
|
_callback = user_callback;
|
|
|
|
irq_set_exclusive_handler(RTC_IRQ, rtc_irq_handler);
|
|
|
|
// Enable the IRQ at the peri
|
|
rtc_hw->inte = RTC_INTE_RTC_BITS;
|
|
|
|
// Enable the IRQ at the proc
|
|
irq_set_enabled(RTC_IRQ, true);
|
|
|
|
rtc_enable_alarm();
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|