diff --git a/src/common/pico_util/datetime.c b/src/common/pico_util/datetime.c index c113ce466..857053944 100644 --- a/src/common/pico_util/datetime.c +++ b/src/common/pico_util/datetime.c @@ -2,6 +2,14 @@ #include +__weak struct tm *pico_localtime_r(const time_t *time, struct tm *tm) { + return localtime_r(time, tm); +} + +__weak time_t pico_mktime(struct tm *tm) { + return mktime(tm); +} + #if PICO_INCLUDE_RTC_DATETIME static const char *DATETIME_MONTHS[12] = { "January", @@ -41,17 +49,29 @@ void datetime_to_str(char *buf, uint buf_size, const datetime_t *t) { t->year); }; +void datetime_to_tm(const datetime_t *dt, struct tm *tm) { + tm->tm_year = dt->year - 1900; + tm->tm_mon = dt->month - 1; + tm->tm_mday = dt->day; + tm->tm_hour = dt->hour; + tm->tm_min = dt->min; + tm->tm_sec = dt->sec; +} + +void tm_to_datetime(const struct tm *tm, datetime_t *dt) { + dt->year = (int16_t) (tm->tm_year + 1900); // 0..4095 + dt->month = (int8_t) (tm->tm_mon + 1); // 1..12, 1 is January + dt->day = (int8_t) tm->tm_mday; // 1..28,29,30,31 depending on month + dt->dotw = (int8_t) tm->tm_wday; // 0..6, 0 is Sunday + dt->hour = (int8_t) tm->tm_hour; // 0..23 + dt->min = (int8_t) tm->tm_min; // 0..59 + dt->sec = (int8_t) tm->tm_sec; // 0..59 +} bool time_to_datetime(time_t time, datetime_t *dt) { struct tm local; - if (localtime_r(&time, &local)) { - dt->year = (int16_t) (local.tm_year + 1900); // 0..4095 - dt->month = (int8_t) (local.tm_mon + 1); // 1..12, 1 is January - dt->day = (int8_t) local.tm_mday; // 1..28,29,30,31 depending on month - dt->dotw = (int8_t) local.tm_wday; // 0..6, 0 is Sunday - dt->hour = (int8_t) local.tm_hour; // 0..23 - dt->min = (int8_t) local.tm_min; // 0..59 - dt->sec = (int8_t) local.tm_sec; // 0..59 + if (pico_localtime_r(&time, &local)) { + tm_to_datetime(&local, dt); return true; } return false; @@ -59,13 +79,8 @@ bool time_to_datetime(time_t time, datetime_t *dt) { bool datetime_to_time(const datetime_t *dt, time_t *time) { struct tm local; - local.tm_year = dt->year - 1900; - local.tm_mon = dt->month - 1; - local.tm_mday = dt->day; - local.tm_hour = dt->hour; - local.tm_min = dt->min; - local.tm_sec = dt->sec; - *time = mktime(&local); + datetime_to_tm(dt, &local); + *time = pico_mktime(&local); return *time >= 0; } diff --git a/src/common/pico_util/include/pico/util/datetime.h b/src/common/pico_util/include/pico/util/datetime.h index 92e6d6460..05dea5633 100644 --- a/src/common/pico_util/include/pico/util/datetime.h +++ b/src/common/pico_util/include/pico/util/datetime.h @@ -19,8 +19,10 @@ extern "C" { * \ingroup pico_util */ -#if PICO_INCLUDE_RTC_DATETIME #include +#include + +#if PICO_INCLUDE_RTC_DATETIME /*! \brief Convert a datetime_t structure to a string * \ingroup util_datetime @@ -33,14 +35,33 @@ void datetime_to_str(char *buf, uint buf_size, const datetime_t *t); bool time_to_datetime(time_t time, datetime_t *dt); bool datetime_to_time(const datetime_t *dt, time_t *time); + +void datetime_to_tm(const datetime_t *dt, struct tm *tm); +void tm_to_datetime(const struct tm *tm, datetime_t *dt); + #endif -#include uint64_t timespec_to_ms(const struct timespec *ts); uint64_t timespec_to_us(const struct timespec *ts); void ms_to_timespec(uint64_t ms, struct timespec *ts); void us_to_timespec(uint64_t ms, struct timespec *ts); +/*! \brief localtime_r implementation for use by the pico_util datetime functions + * \ingroup util_datetime + * + * This method calls localtime_r from the C library by default, + * but is declared as a weak implementation to allow user code to override it + */ +struct tm *pico_localtime_r(const time_t *time, struct tm *tm); + +/*! \brief mktime implementation for use by the pico_util datetime functions +* \ingroup util_datetime +* +* This method calls mktime from the C library by default, +* but is declared as a weak implementation to allow user code to override it +*/ +time_t pico_mktime(struct tm *tm); + #ifdef __cplusplus } #endif diff --git a/src/rp2_common/pico_aon_timer/aon_timer.c b/src/rp2_common/pico_aon_timer/aon_timer.c index 35173cf89..01f8ee09c 100644 --- a/src/rp2_common/pico_aon_timer/aon_timer.c +++ b/src/rp2_common/pico_aon_timer/aon_timer.c @@ -13,6 +13,10 @@ static aon_timer_alarm_handler_t aon_timer_alarm_handler; #if HAS_RP2040_RTC #include "hardware/rtc.h" #include "pico/util/datetime.h" + +static __force_inline bool ts_to_tm(const struct timespec *ts, struct tm *tm) { + return pico_localtime_r(&ts->tv_sec, tm) != NULL; +} #elif HAS_POWMAN_TIMER #include "hardware/powman.h" @@ -23,56 +27,89 @@ static void powman_timer_irq_handler(void) { irq_remove_handler(irq_num, powman_timer_irq_handler); if (aon_timer_alarm_handler) aon_timer_alarm_handler(); } + #endif -void aon_timer_set_time(const struct timespec *ts) { +static bool tm_to_ts(const struct tm *tm, struct timespec *ts) { + struct tm tm_clone = *tm; + ts->tv_sec = pico_mktime(&tm_clone); + ts->tv_nsec = 0; + return ts->tv_sec != -1; +} + +bool aon_timer_set_time(const struct timespec *ts) { #if HAS_RP2040_RTC - datetime_t dt; - bool ok = time_to_datetime(ts->tv_sec, &dt); - assert(ok); - if (ok) rtc_set_datetime(&dt); + struct tm tm; + bool ok = pico_localtime_r(&ts->tv_sec, &tm); + if (ok) aon_timer_set_time_calendar(&tm); + return ok; #elif HAS_POWMAN_TIMER powman_timer_set_ms(timespec_to_ms(ts)); + return true; #else panic_unsupported(); #endif } -void aon_timer_get_time(struct timespec *ts) { +bool aon_timer_set_time_calendar(const struct tm *tm) { #if HAS_RP2040_RTC datetime_t dt; - rtc_get_datetime(&dt); - time_t t; - bool ok = datetime_to_time(&dt, &t); - assert(ok); - ts->tv_nsec = 0; - if (ok) { - ts->tv_sec = t; - } else { - ts->tv_sec = -1; + tm_to_datetime(tm, &dt); + rtc_set_datetime(&dt); + return true; +#elif HAS_POWMAN_TIMER + struct timespec ts; + if (tm_to_ts(tm, &ts)) { + return aon_timer_set_time(&ts); } + return false; +#else + panic_unsupported(); +#endif +} + +bool aon_timer_get_time(struct timespec *ts) { +#if HAS_RP2040_RTC + struct tm tm; + bool ok = aon_timer_get_time_calendar(&tm); + return ok && tm_to_ts(&tm, ts); #elif HAS_POWMAN_TIMER ms_to_timespec(powman_timer_get_ms(), ts); + return true; #else panic_unsupported(); #endif } -aon_timer_alarm_handler_t aon_timer_enable_alarm(const struct timespec *ts, aon_timer_alarm_handler_t handler, bool wakeup_from_low_power) { - uint32_t save = save_and_disable_interrupts(); - aon_timer_alarm_handler_t old_handler = aon_timer_alarm_handler; - struct timespec ts_adjusted = *ts; +bool aon_timer_get_time_calendar(struct tm *tm) { #if HAS_RP2040_RTC - ((void)wakeup_from_low_power); // don't have a choice datetime_t dt; + rtc_get_datetime(&dt); + datetime_to_tm(&dt, tm); + return true; +#elif HAS_POWMAN_TIMER + struct timespec ts; + bool ok = tm_to_ts(tm, &ts); + return ok && aon_timer_get_time(&ts); +#else + panic_unsupported(); +#endif +} + +aon_timer_alarm_handler_t aon_timer_enable_alarm(const struct timespec *ts, aon_timer_alarm_handler_t handler, bool wakeup_from_low_power) { +#if HAS_RP2040_RTC + struct tm tm; // adjust to after the target time + struct timespec ts_adjusted = *ts; if (ts_adjusted.tv_nsec) ts_adjusted.tv_sec++; - bool ok = time_to_datetime(ts_adjusted.tv_sec, &dt); - assert(ok); - if (ok) { - rtc_set_alarm(&dt, handler); + if (!pico_localtime_r(&ts_adjusted.tv_sec, &tm)) { + return (aon_timer_alarm_handler_t)PICO_ERROR_INVALID_ARG; } + return aon_timer_enable_alarm_calendar(&tm, handler, wakeup_from_low_power); #elif HAS_POWMAN_TIMER + uint32_t save = save_and_disable_interrupts(); + aon_timer_alarm_handler_t old_handler = aon_timer_alarm_handler; + struct timespec ts_adjusted = *ts; uint irq_num = aon_timer_get_irq_num(); powman_timer_disable_alarm(); // adjust to after the target time @@ -92,12 +129,34 @@ aon_timer_alarm_handler_t aon_timer_enable_alarm(const struct timespec *ts, aon_ irq_set_exclusive_handler(irq_num, powman_timer_irq_handler); irq_set_enabled(irq_num, true); } + aon_timer_alarm_handler = handler; + restore_interrupts_from_disabled(save); + return old_handler; #else panic_unsupported(); #endif +} + +aon_timer_alarm_handler_t aon_timer_enable_alarm_calendar(const struct tm *tm, aon_timer_alarm_handler_t handler, bool wakeup_from_low_power) { +#if HAS_RP2040_RTC + ((void)wakeup_from_low_power); // don't have a choice + uint32_t save = save_and_disable_interrupts(); + aon_timer_alarm_handler_t old_handler = aon_timer_alarm_handler; + datetime_t dt; + tm_to_datetime(tm, &dt); + rtc_set_alarm(&dt, handler); aon_timer_alarm_handler = handler; restore_interrupts_from_disabled(save); return old_handler; +#elif HAS_POWMAN_TIMER + struct timespec ts; + if (!tm_to_ts(tm, &ts)) { + return (aon_timer_alarm_handler_t)PICO_ERROR_INVALID_ARG; + } + return aon_timer_enable_alarm(&ts, handler, wakeup_from_low_power); +#else + panic_unsupported(); +#endif } void aon_timer_disable_alarm(void) { @@ -134,6 +193,20 @@ void aon_timer_start(const struct timespec *ts) { #endif } +void aon_timer_start_calendar(const struct tm *tm) { +#if HAS_RP2040_RTC + rtc_init(); + aon_timer_set_time_calendar(tm); +#elif HAS_POWMAN_TIMER + // todo how best to allow different configurations; this should just be the default + powman_timer_set_1khz_tick_source_xosc(); + aon_timer_set_time_calendar(tm); + powman_timer_start(); +#else + panic_unsupported(); +#endif +} + void aon_timer_stop(void) { #if HAS_RP2040_RTC hw_clear_bits(&rtc_hw->ctrl, RTC_CTRL_RTC_ENABLE_BITS); diff --git a/src/rp2_common/pico_aon_timer/include/pico/aon_timer.h b/src/rp2_common/pico_aon_timer/include/pico/aon_timer.h index 49a7c2a5d..ebe55ebb7 100644 --- a/src/rp2_common/pico_aon_timer/include/pico/aon_timer.h +++ b/src/rp2_common/pico_aon_timer/include/pico/aon_timer.h @@ -23,6 +23,23 @@ * \if rp2350_specific * This library uses the Powman Timer on RP2350. * \endif + * + * This library supports both `aon_timer_xxx_calendar()` methods which take a calendar date/time (as struct tm), + * and `aon_timer_xxx()` methods which take a time since epoch (as struct timespec) + * + * \if rp2040_specific + * NOTE: On RP2040 the non date/time methods must convert the struct timespec to a date/time value which is handled via + * the \ref pico_localtime_r method. By default, this pulls in the C library local_time_r method which can lead to a big increase in binary size. + * `pico_localtime_r` is weak, so can be overridden if a better/smaller alternative is available, otherwise you might consider + * using the `aon_timer_xxx_calendar()` variants on RP2040 + * \endif + * + * \if rp2350_specific + * NOTE: On RP2350 the date/time methods must convert the struct tm to a struct timespec value which is handled via + * the \ref pico_mktime method. By default, this pulls in the C library mktime method which can lead to a big increase in binary size. + * `pico_mktime` is weak, so can be overridden if a better/smaller alternative is available, otherwise you might consider + * using the `aon_timer_xxx()` variants on RP2350 + * \endif */ #ifdef __cplusplus @@ -59,6 +76,7 @@ void aon_timer_start_with_timeofday(void); * \param ts the current time */ void aon_timer_start(const struct timespec *ts); +void aon_timer_start_calendar(const struct tm *tm); /** * \brief Stop the AON timer @@ -71,14 +89,16 @@ void aon_timer_stop(void); * \ingroup pico_aon_timer * \param ts the new current time */ -void aon_timer_set_time(const struct timespec *ts); +bool aon_timer_set_time(const struct timespec *ts); +bool aon_timer_set_time_calendar(const struct tm *tm); /** * \brief Get the current time of the AON timer * \ingroup pico_aon_timer * \param ts out value for the current time */ -void aon_timer_get_time(struct timespec *ts); +bool aon_timer_get_time(struct timespec *ts); +bool aon_timer_get_time_calendar(struct tm *tm); /** * \brief Get the resolution of the AON timer @@ -91,18 +111,22 @@ void aon_timer_get_resolution(struct timespec *ts); * \brief Enable an AON timer alarm for a specified time * \ingroup pico_aon_timer * - * \if rp2040_specific - * On RP2040 the alarm will not fire if it is in the past - * \endif * \if rp2350_specific * On RP2350 the alarm will fire if it is in the past * \endif + * \if rp2040_specific + * On RP2040 the alarm will not fire if it is in the past. + * \endif * * \param ts the alarm time - * \param handler a callback to call when the timer fires (may be NULL for wakeup_from_low_power = true) + * \param handler a callback to call when the timer fires (can be NULL for wakeup_from_low_power = true) * \param wakeup_from_low_power true if the AON timer is to be used to wake up from a DORMANT state + * \return on success the old handler (or NULL if there was none) + * on failure, PICO_ERROR_INVALID_ARG + * \sa pico_localtime_r */ aon_timer_alarm_handler_t aon_timer_enable_alarm(const struct timespec *ts, aon_timer_alarm_handler_t handler, bool wakeup_from_low_power); +aon_timer_alarm_handler_t aon_timer_enable_alarm_calendar(const struct tm *tm, aon_timer_alarm_handler_t handler, bool wakeup_from_low_power); /** * \brief Disable the currently enabled AON timer alarm if any