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

STM: monotonic time even when RTC is changed #7965

Merged
merged 2 commits into from
May 12, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
139 changes: 91 additions & 48 deletions ports/stm/peripherals/rtc.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@
#include "shared/timeutils/timeutils.h"

// Default period for ticks is 1/1024 second
#define TICK_DIVISOR 1024
#define TICKS_PER_SECOND 1024
// Based on a 32768 kHz clock
#define SUBTICKS_PER_TICK 32

STATIC RTC_HandleTypeDef hrtc;

Expand All @@ -47,6 +49,13 @@ volatile uint32_t cached_date = 0;
volatile uint32_t seconds_to_minute = 0;
volatile uint32_t cached_hours_minutes = 0;

// The RTC starts at 2000-01-01 when it comes up.
// If the RTC is set to a later time, the ticks the RTC returns will be offset by the new time.
// Remember that offset so it can be removed when returning a monotonic tick count.
static int64_t rtc_ticks_offset;
// Normalized to be 0-31 inclusive, so always positive.
static uint8_t rtc_subticks_offset;

volatile bool alarmed_already[2];

bool peripherals_wkup_on = false;
Expand All @@ -59,6 +68,9 @@ uint32_t stm32_peripherals_get_rtc_freq(void) {
}

void stm32_peripherals_rtc_init(void) {
rtc_ticks_offset = 0;
rtc_subticks_offset = 0;

// RTC oscillator selection is handled in peripherals/<family>/<line>/clocks.c
__HAL_RCC_RTC_ENABLE();
hrtc.Instance = RTC;
Expand All @@ -74,49 +86,9 @@ void stm32_peripherals_rtc_init(void) {
HAL_NVIC_EnableIRQ(RTC_Alarm_IRQn);
}

#if CIRCUITPY_RTC
void stm32_peripherals_rtc_get_time(timeutils_struct_time_t *tm) {
RTC_DateTypeDef date = {0};
RTC_TimeTypeDef time = {0};

int code;
if ((code = HAL_RTC_GetTime(&hrtc, &time, RTC_FORMAT_BIN)) == HAL_OK &&
(code = HAL_RTC_GetDate(&hrtc, &date, RTC_FORMAT_BIN)) == HAL_OK) {
tm->tm_hour = time.Hours;
tm->tm_min = time.Minutes;
tm->tm_sec = time.Seconds;
tm->tm_wday = date.WeekDay - 1;
tm->tm_mday = date.Date;
tm->tm_mon = date.Month;
tm->tm_year = date.Year + 2000;
tm->tm_yday = -1;
}
}

void stm32_peripherals_rtc_set_time(timeutils_struct_time_t *tm) {
RTC_DateTypeDef date = {0};
RTC_TimeTypeDef time = {0};

time.Hours = tm->tm_hour;
time.Minutes = tm->tm_min;
time.Seconds = tm->tm_sec;
date.WeekDay = tm->tm_wday + 1;
date.Date = tm->tm_mday;
date.Month = tm->tm_mon;
date.Year = tm->tm_year - 2000;
time.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
time.StoreOperation = RTC_STOREOPERATION_RESET;

if (HAL_RTC_SetTime(&hrtc, &time, RTC_FORMAT_BIN) != HAL_OK ||
HAL_RTC_SetDate(&hrtc, &date, RTC_FORMAT_BIN) != HAL_OK) {
// todo - throw an exception
}
}
#endif

// This function is called often for timing so we cache the seconds elapsed computation based on the
// register value. The STM HAL always does shifts and conversion if we use it directly.
uint64_t stm32_peripherals_rtc_raw_ticks(uint8_t *subticks) {
STATIC uint64_t stm32_peripherals_rtc_raw_ticks(uint8_t *subticks) {
// Disable IRQs to ensure we read all of the RTC registers as close in time as possible. Read
// SSR twice to make sure we didn't read across a tick.
__disable_irq();
Expand Down Expand Up @@ -157,13 +129,84 @@ uint64_t stm32_peripherals_rtc_raw_ticks(uint8_t *subticks) {
uint8_t seconds = (uint8_t)(time & (RTC_TR_ST | RTC_TR_SU));
seconds = (uint8_t)RTC_Bcd2ToByte(seconds);
if (subticks != NULL) {
*subticks = subseconds % 32;
*subticks = subseconds % SUBTICKS_PER_TICK;
}

uint64_t raw_ticks = ((uint64_t)TICK_DIVISOR) * (seconds_to_date + seconds_to_minute + seconds) + subseconds / 32;
uint64_t raw_ticks = ((uint64_t)TICKS_PER_SECOND) * (seconds_to_date + seconds_to_minute + seconds) + subseconds / SUBTICKS_PER_TICK;
return raw_ticks;
}

// This function returns monotonically increasing ticks by adjusting away the RTC tick offset
// from the last time the date was set.
uint64_t stm32_peripherals_rtc_monotonic_ticks(uint8_t *subticks) {
uint8_t raw_subticks;
uint64_t monotonic_ticks = stm32_peripherals_rtc_raw_ticks(&raw_subticks) - rtc_ticks_offset;
int8_t monotonic_subticks = raw_subticks - rtc_subticks_offset;
// Difference might be negative. Normalize to 0-31.
// `while` not really necessary; should only loop 0 or 1 times.
while (monotonic_subticks < 0) {
monotonic_ticks--;
monotonic_subticks += SUBTICKS_PER_TICK;
}
*subticks = (uint8_t)monotonic_subticks;
return monotonic_ticks;
}

#if CIRCUITPY_RTC
void stm32_peripherals_rtc_get_time(timeutils_struct_time_t *tm) {
RTC_DateTypeDef date = {0};
RTC_TimeTypeDef time = {0};

int code;
if ((code = HAL_RTC_GetTime(&hrtc, &time, RTC_FORMAT_BIN)) == HAL_OK &&
(code = HAL_RTC_GetDate(&hrtc, &date, RTC_FORMAT_BIN)) == HAL_OK) {
tm->tm_hour = time.Hours;
tm->tm_min = time.Minutes;
tm->tm_sec = time.Seconds;
tm->tm_wday = date.WeekDay - 1;
tm->tm_mday = date.Date;
tm->tm_mon = date.Month;
tm->tm_year = date.Year + 2000;
tm->tm_yday = -1;
}
}

void stm32_peripherals_rtc_set_time(timeutils_struct_time_t *tm) {
RTC_DateTypeDef date = {0};
RTC_TimeTypeDef time = {0};

uint8_t current_monotonic_subticks;
uint64_t current_monotonic_ticks = stm32_peripherals_rtc_monotonic_ticks(&current_monotonic_subticks);

// SubSeconds will always be set to zero.
time.Hours = tm->tm_hour;
time.Minutes = tm->tm_min;
time.Seconds = tm->tm_sec;
date.WeekDay = tm->tm_wday + 1;
date.Date = tm->tm_mday;
date.Month = tm->tm_mon;
date.Year = tm->tm_year - 2000;
time.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
time.StoreOperation = RTC_STOREOPERATION_RESET;

if (HAL_RTC_SetTime(&hrtc, &time, RTC_FORMAT_BIN) != HAL_OK ||
HAL_RTC_SetDate(&hrtc, &date, RTC_FORMAT_BIN) != HAL_OK) {
// todo - throw an exception
}

uint8_t raw_subticks;
rtc_ticks_offset = stm32_peripherals_rtc_raw_ticks(&raw_subticks) - current_monotonic_ticks;
int8_t rtc_subticks_offset_signed = raw_subticks - current_monotonic_subticks;
// Difference might be negative. Normalize subticks to 0-31.
// `while` not really necessary; should only loop 0 or 1 times.
while (rtc_subticks_offset_signed < 0) {
rtc_ticks_offset--;
rtc_subticks_offset_signed += SUBTICKS_PER_TICK;
}
rtc_subticks_offset = (uint8_t)rtc_subticks_offset_signed;
}
#endif

void stm32_peripherals_rtc_assign_wkup_callback(void (*callback)(void)) {
wkup_callback = callback;
}
Expand All @@ -177,7 +220,7 @@ void stm32_peripherals_rtc_set_wakeup_mode_seconds(uint32_t seconds) {
}

void stm32_peripherals_rtc_set_wakeup_mode_tick(void) {
HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, (rtc_clock_frequency / 16) / TICK_DIVISOR, RTC_WAKEUPCLOCK_RTCCLK_DIV2);
HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, (rtc_clock_frequency / 16) / TICKS_PER_SECOND, RTC_WAKEUPCLOCK_RTCCLK_DIV2);
}

void stm32_peripherals_rtc_enable_wakeup_timer(void) {
Expand Down Expand Up @@ -205,9 +248,9 @@ void stm32_peripherals_rtc_set_alarm(uint8_t alarm_idx, uint32_t ticks) {
uint64_t raw_ticks = stm32_peripherals_rtc_raw_ticks(NULL) + ticks;

RTC_AlarmTypeDef alarm;
if (ticks > TICK_DIVISOR) {
if (ticks > TICKS_PER_SECOND) {
timeutils_struct_time_t tm;
timeutils_seconds_since_2000_to_struct_time(raw_ticks / TICK_DIVISOR, &tm);
timeutils_seconds_since_2000_to_struct_time(raw_ticks / TICKS_PER_SECOND, &tm);
alarm.AlarmTime.Hours = tm.tm_hour;
alarm.AlarmTime.Minutes = tm.tm_min;
alarm.AlarmTime.Seconds = tm.tm_sec;
Expand All @@ -221,7 +264,7 @@ void stm32_peripherals_rtc_set_alarm(uint8_t alarm_idx, uint32_t ticks) {
}

alarm.AlarmTime.SubSeconds = rtc_clock_frequency - 1 -
((raw_ticks % TICK_DIVISOR) * 32);
((raw_ticks % TICKS_PER_SECOND) * SUBTICKS_PER_TICK);
if (alarm.AlarmTime.SubSeconds > rtc_clock_frequency) {
alarm.AlarmTime.SubSeconds = alarm.AlarmTime.SubSeconds +
rtc_clock_frequency;
Expand Down
2 changes: 1 addition & 1 deletion ports/stm/peripherals/rtc.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@

uint32_t stm32_peripherals_get_rtc_freq(void);
void stm32_peripherals_rtc_init(void);
uint64_t stm32_peripherals_rtc_raw_ticks(uint8_t *subticks);
uint64_t stm32_peripherals_rtc_monotonic_ticks(uint8_t *subticks);

void stm32_peripherals_rtc_assign_wkup_callback(void (*callback)(void));
void stm32_peripherals_rtc_set_wakeup_mode_seconds(uint32_t seconds);
Expand Down
2 changes: 1 addition & 1 deletion ports/stm/supervisor/port.c
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ __attribute__((used)) void HardFault_Handler(void) {
}

uint64_t port_get_raw_ticks(uint8_t *subticks) {
return stm32_peripherals_rtc_raw_ticks(subticks);
return stm32_peripherals_rtc_monotonic_ticks(subticks);
}

// Enable 1/1024 second tick.
Expand Down
2 changes: 2 additions & 0 deletions shared-module/time/__init__.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ uint64_t common_hal_time_monotonic_ns(void) {
uint64_t ticks = port_get_raw_ticks(&subticks);
// A tick is 976562.5 nanoseconds so multiply it by the base and add half instead of doing float
// math.
// A subtick is 1/32 of a tick.
// 30518 is 1e9 / 32768
return 976562 * ticks + ticks / 2 + 30518 * subticks;
}

Expand Down