Skip to content

Commit

Permalink
support both struct timespec and struct tm variants of the aon timer …
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
kilograham committed Nov 21, 2024
1 parent e17a82d commit 0df36d9
Show file tree
Hide file tree
Showing 4 changed files with 180 additions and 47 deletions.
45 changes: 30 additions & 15 deletions src/common/pico_util/datetime.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

#include <stdio.h>

__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",
Expand Down Expand Up @@ -41,31 +49,38 @@ 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;
}

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;
}

Expand Down
25 changes: 23 additions & 2 deletions src/common/pico_util/include/pico/util/datetime.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ extern "C" {
* \ingroup pico_util
*/

#if PICO_INCLUDE_RTC_DATETIME
#include <time.h>
#include <sys/time.h>

#if PICO_INCLUDE_RTC_DATETIME

/*! \brief Convert a datetime_t structure to a string
* \ingroup util_datetime
Expand All @@ -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 <sys/time.h>
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
Expand Down
121 changes: 97 additions & 24 deletions src/rp2_common/pico_aon_timer/aon_timer.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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
Expand All @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down
36 changes: 30 additions & 6 deletions src/rp2_common/pico_aon_timer/include/pico/aon_timer.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down

0 comments on commit 0df36d9

Please sign in to comment.