Skip to content
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

support both struct timespec and struct tm variants of the aon timer APIs #2079

Merged
merged 7 commits into from
Nov 22, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 38 additions & 16 deletions src/common/pico_util/datetime.c
Original file line number Diff line number Diff line change
@@ -1,8 +1,23 @@
#include "pico/util/datetime.h"

#include <stdio.h>
#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 ayway
#define __datetime_weak
#else
#define __datetime_weak __weak
#endif

__datetime_weak struct tm * pico_localtime_r(const time_t *time, struct tm *tm) {
kilograham marked this conversation as resolved.
Show resolved Hide resolved
return localtime_r(time, tm);
}

__datetime_weak time_t pico_mktime(struct tm *tm) {
return mktime(tm);
}

#if PICO_INCLUDE_RTC_DATETIME
#include <stdio.h>

static const char *DATETIME_MONTHS[12] = {
"January",
"February",
Expand Down Expand Up @@ -41,31 +56,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);
kilograham marked this conversation as resolved.
Show resolved Hide resolved
#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
kilograham marked this conversation as resolved.
Show resolved Hide resolved
* 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