From 3dce58f21b2829bbe8827ee86a7b12501ee1cc1b Mon Sep 17 00:00:00 2001 From: Graham Sanderson Date: Fri, 22 Nov 2024 12:11:02 -0600 Subject: [PATCH] support both struct timespec and struct tm variants of the aon timer APIs (#2079) * support both struct timespec and struct tm variants of the aon timer APIs since use of one type on RP2040 and the other on RP2350 require pulling in C library code. Provide weak pico_ wrappers for localtime_r and mktime so that the user can override the conversion functions --- src/common/pico_util/datetime.c | 54 +++++-- .../pico_util/include/pico/util/datetime.h | 25 ++- src/rp2_common/pico_aon_timer/aon_timer.c | 136 ++++++++++++---- .../pico_aon_timer/include/pico/aon_timer.h | 146 ++++++++++++++++-- 4 files changed, 306 insertions(+), 55 deletions(-) diff --git a/src/common/pico_util/datetime.c b/src/common/pico_util/datetime.c index c113ce466..130e2d5a6 100644 --- a/src/common/pico_util/datetime.c +++ b/src/common/pico_util/datetime.c @@ -1,8 +1,23 @@ #include "pico/util/datetime.h" -#include +#if !PICO_ON_DEVICE && __APPLE__ +// if we're compiling with LLVM on Apple, __weak does something else, but we don't care about overriding these anyway on host builds +#define __datetime_weak +#else +#define __datetime_weak __weak +#endif + +__datetime_weak struct tm * pico_localtime_r(const time_t *time, struct tm *tm) { + return localtime_r(time, tm); +} + +__datetime_weak time_t pico_mktime(struct tm *tm) { + return mktime(tm); +} #if PICO_INCLUDE_RTC_DATETIME +#include + static const char *DATETIME_MONTHS[12] = { "January", "February", @@ -41,17 +56,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 +86,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..a4d594f63 100644 --- a/src/rp2_common/pico_aon_timer/aon_timer.c +++ b/src/rp2_common/pico_aon_timer/aon_timer.c @@ -13,6 +13,7 @@ static aon_timer_alarm_handler_t aon_timer_alarm_handler; #if HAS_RP2040_RTC #include "hardware/rtc.h" #include "pico/util/datetime.h" + #elif HAS_POWMAN_TIMER #include "hardware/powman.h" @@ -23,56 +24,92 @@ 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(); } + +static bool ts_to_tm(const struct timespec *ts, struct tm *tm) { + return pico_localtime_r(&ts->tv_sec, tm) != NULL; +} #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; + aon_timer_get_time(&ts); + return ts_to_tm(&ts, tm); +#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) { @@ -120,15 +179,36 @@ void aon_timer_start_with_timeofday(void) { aon_timer_start(&ts); } -void aon_timer_start(const struct timespec *ts) { +bool aon_timer_start(const struct timespec *ts) { #if HAS_RP2040_RTC rtc_init(); - aon_timer_set_time(ts); + return aon_timer_set_time(ts); #elif HAS_POWMAN_TIMER // todo how best to allow different configurations; this should just be the default powman_timer_set_1khz_tick_source_xosc(); - powman_timer_set_ms(timespec_to_ms(ts)); - powman_timer_start(); + bool ok = aon_timer_set_time(ts); + if (ok) { + powman_timer_set_ms(timespec_to_ms(ts)); + powman_timer_start(); + } + return ok; +#else + panic_unsupported(); +#endif +} + +bool aon_timer_start_calendar(const struct tm *tm) { +#if HAS_RP2040_RTC + rtc_init(); + return 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(); + bool ok = aon_timer_set_time_calendar(tm); + if (ok) { + powman_timer_start(); + } + return ok; #else panic_unsupported(); #endif 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..768c79827 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,38 @@ * \if rp2350_specific * This library uses the Powman Timer on RP2350. * \endif + * + * This library supports both `aon_timer_xxx_calendar()` methods which use a calendar date/time (as struct tm), + * and `aon_timer_xxx()` methods which use a linear time value relative an internal reference time (via struct timespec). + * + * \if rp2040_specific + * \anchor rp2040_caveats + * On RP2040 the non 'calendar date/time' methods must convert the linear time value to a calendar date/time internally; these methods are: + * + * * \ref aon_timer_start_with_timeofday + * * \ref aon_timer_start + * * \ref aon_timer_set_time + * * \ref aon_timer_get_time + * * \ref aon_timer_enable_alarm + * + * This conversion is handled by 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. The default implementation of `pico_localtime_r` is weak, so it can be overridden + * if a better/smaller alternative is available, otherwise you might consider the method variants ending in `_calendar()` instead on RP2040. + * \endif + * + * \if rp2350_specific + * \anchor rp2350_caveats + * On RP2350 the 'calendar date/time' methods must convert the calendar date/time to a linear time value internally; these methods are: + * + * * \ref aon_timer_start_calendar + * * \ref aon_timer_set_time_calendar + * * \ref aon_timer_get_time_calendar + * * \ref aon_timer_enable_alarm_calendar + * + * This conversion is handled by 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. The default implementation of `pico_mktime` is weak, so it can be overridden + * if a better/smaller alternative is available, otherwise you might consider the method variants not ending in `_calendar()` instead on RP2350. + * \endif */ #ifdef __cplusplus @@ -49,6 +81,11 @@ typedef void (*aon_timer_alarm_handler_t)(void); /** * \brief Start the AON timer running using the result from the gettimeofday() function as the current time + * + * \if rp2040_specific + * See \ref rp2040_caveats "caveats" for using this method on RP2040 + * \endif + * * \ingroup pico_aon_timer */ void aon_timer_start_with_timeofday(void); @@ -56,9 +93,30 @@ void aon_timer_start_with_timeofday(void); /** * \brief Start the AON timer running using the specified timespec as the current time * \ingroup pico_aon_timer - * \param ts the current time + * + * \if rp2040_specific + * See \ref rp2040_caveats "caveats" for using this method on RP2040 + * \endif + * + * \param ts the time to set as 'now' + * \return true on success, false if internal time format conversion failed + * \sa aon_timer_start_calendar + */ +bool aon_timer_start(const struct timespec *ts); + +/** + * \brief Start the AON timer running using the specified calendar date/time as the current time + * + * \if rp2350_specific + * See \ref rp2350_caveats "caveats" for using this method on RP2350 + * \endif + * + * \ingroup pico_aon_timer + * \param tm the calendar date/time to set as 'now' + * \return true on success, false if internal time format conversion failed + * \sa aon_timer_start */ -void aon_timer_start(const struct timespec *ts); +bool aon_timer_start_calendar(const struct tm *tm); /** * \brief Stop the AON timer @@ -67,18 +125,60 @@ void aon_timer_start(const struct timespec *ts); void aon_timer_stop(void); /** - * \brief Update the current time of the AON timer + * \brief Set the current time of the AON timer * \ingroup pico_aon_timer + * + * \if rp2040_specific + * See \ref rp2040_caveats "caveats" for using this method on RP2040 + * \endif + * * \param ts the new current time + * \return true on success, false if internal time format conversion failed + * \sa aon_timer_set_time_calendar */ -void aon_timer_set_time(const struct timespec *ts); +bool aon_timer_set_time(const struct timespec *ts); + +/** + * \brief Set the current time of the AON timer to the given calendar date/time + * \ingroup pico_aon_timer + * + * \if rp2350_specific + * See \ref rp2350_caveats "caveats" for using this method on RP2350 + * \endif + * + * \param tm the new current time + * \return true on success, false if internal time format conversion failed + * \sa aon_timer_set_time + */ +bool aon_timer_set_time_calendar(const struct tm *tm); /** * \brief Get the current time of the AON timer * \ingroup pico_aon_timer + * + * \if rp2040_specific + * See \ref rp2040_caveats "caveats" for using this method on RP2040 + * \endif + * * \param ts out value for the current time + * \return true on success, false if internal time format conversion failed + * \sa aon_timer_get_time_calendar */ -void aon_timer_get_time(struct timespec *ts); +bool aon_timer_get_time(struct timespec *ts); + + /** + * \brief Get the current time of the AON timer as a calendar date/time + * \ingroup pico_aon_timer + * + * \if rp2350_specific + * See \ref rp2350_caveats "caveats" for using this method on RP2350 + * \endif + * + * \param tm out value for the current calendar date/time + * \return true on success, false if internal time format conversion failed + * \sa aon_timer_get_time + */ +bool aon_timer_get_time_calendar(struct tm *tm); /** * \brief Get the resolution of the AON timer @@ -91,19 +191,47 @@ 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. + * + * See \ref rp2040_caveats "caveats" for using this method on RP2040 + * \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) + * or PICO_ERROR_INVALID_ARG if internal time format conversion failed + * \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); +/** + * \brief Enable an AON timer alarm for a specified calendar date/time + * \ingroup pico_aon_timer + * + * \if rp2350_specific + * On RP2350 the alarm will fire if it is in the past + * + * See \ref rp2350_caveats "caveats" for using this method on RP2350 + * \endif + * + * \if rp2040_specific + * On RP2040 the alarm will not fire if it is in the past. + * \endif + * + * \param tm the alarm calendar date/time + * \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) + * or PICO_ERROR_INVALID_ARG if internal time format conversion failed + * \sa pico_localtime_r + */ +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 * \ingroup pico_aon_timer