Skip to content

Commit ca01200

Browse files
authored
Merge pull request #7965 from dhalbert/stm-rtc-monotonic
STM: monotonic time even when RTC is changed
2 parents 40f1ac1 + e806785 commit ca01200

File tree

4 files changed

+95
-50
lines changed

4 files changed

+95
-50
lines changed

ports/stm/peripherals/rtc.c

Lines changed: 91 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@
3232
#include "shared/timeutils/timeutils.h"
3333

3434
// Default period for ticks is 1/1024 second
35-
#define TICK_DIVISOR 1024
35+
#define TICKS_PER_SECOND 1024
36+
// Based on a 32768 kHz clock
37+
#define SUBTICKS_PER_TICK 32
3638

3739
STATIC RTC_HandleTypeDef hrtc;
3840

@@ -47,6 +49,13 @@ volatile uint32_t cached_date = 0;
4749
volatile uint32_t seconds_to_minute = 0;
4850
volatile uint32_t cached_hours_minutes = 0;
4951

52+
// The RTC starts at 2000-01-01 when it comes up.
53+
// If the RTC is set to a later time, the ticks the RTC returns will be offset by the new time.
54+
// Remember that offset so it can be removed when returning a monotonic tick count.
55+
static int64_t rtc_ticks_offset;
56+
// Normalized to be 0-31 inclusive, so always positive.
57+
static uint8_t rtc_subticks_offset;
58+
5059
volatile bool alarmed_already[2];
5160

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

6170
void stm32_peripherals_rtc_init(void) {
71+
rtc_ticks_offset = 0;
72+
rtc_subticks_offset = 0;
73+
6274
// RTC oscillator selection is handled in peripherals/<family>/<line>/clocks.c
6375
__HAL_RCC_RTC_ENABLE();
6476
hrtc.Instance = RTC;
@@ -74,49 +86,9 @@ void stm32_peripherals_rtc_init(void) {
7486
HAL_NVIC_EnableIRQ(RTC_Alarm_IRQn);
7587
}
7688

77-
#if CIRCUITPY_RTC
78-
void stm32_peripherals_rtc_get_time(timeutils_struct_time_t *tm) {
79-
RTC_DateTypeDef date = {0};
80-
RTC_TimeTypeDef time = {0};
81-
82-
int code;
83-
if ((code = HAL_RTC_GetTime(&hrtc, &time, RTC_FORMAT_BIN)) == HAL_OK &&
84-
(code = HAL_RTC_GetDate(&hrtc, &date, RTC_FORMAT_BIN)) == HAL_OK) {
85-
tm->tm_hour = time.Hours;
86-
tm->tm_min = time.Minutes;
87-
tm->tm_sec = time.Seconds;
88-
tm->tm_wday = date.WeekDay - 1;
89-
tm->tm_mday = date.Date;
90-
tm->tm_mon = date.Month;
91-
tm->tm_year = date.Year + 2000;
92-
tm->tm_yday = -1;
93-
}
94-
}
95-
96-
void stm32_peripherals_rtc_set_time(timeutils_struct_time_t *tm) {
97-
RTC_DateTypeDef date = {0};
98-
RTC_TimeTypeDef time = {0};
99-
100-
time.Hours = tm->tm_hour;
101-
time.Minutes = tm->tm_min;
102-
time.Seconds = tm->tm_sec;
103-
date.WeekDay = tm->tm_wday + 1;
104-
date.Date = tm->tm_mday;
105-
date.Month = tm->tm_mon;
106-
date.Year = tm->tm_year - 2000;
107-
time.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
108-
time.StoreOperation = RTC_STOREOPERATION_RESET;
109-
110-
if (HAL_RTC_SetTime(&hrtc, &time, RTC_FORMAT_BIN) != HAL_OK ||
111-
HAL_RTC_SetDate(&hrtc, &date, RTC_FORMAT_BIN) != HAL_OK) {
112-
// todo - throw an exception
113-
}
114-
}
115-
#endif
116-
11789
// This function is called often for timing so we cache the seconds elapsed computation based on the
11890
// register value. The STM HAL always does shifts and conversion if we use it directly.
119-
uint64_t stm32_peripherals_rtc_raw_ticks(uint8_t *subticks) {
91+
STATIC uint64_t stm32_peripherals_rtc_raw_ticks(uint8_t *subticks) {
12092
// Disable IRQs to ensure we read all of the RTC registers as close in time as possible. Read
12193
// SSR twice to make sure we didn't read across a tick.
12294
__disable_irq();
@@ -157,13 +129,84 @@ uint64_t stm32_peripherals_rtc_raw_ticks(uint8_t *subticks) {
157129
uint8_t seconds = (uint8_t)(time & (RTC_TR_ST | RTC_TR_SU));
158130
seconds = (uint8_t)RTC_Bcd2ToByte(seconds);
159131
if (subticks != NULL) {
160-
*subticks = subseconds % 32;
132+
*subticks = subseconds % SUBTICKS_PER_TICK;
161133
}
162134

163-
uint64_t raw_ticks = ((uint64_t)TICK_DIVISOR) * (seconds_to_date + seconds_to_minute + seconds) + subseconds / 32;
135+
uint64_t raw_ticks = ((uint64_t)TICKS_PER_SECOND) * (seconds_to_date + seconds_to_minute + seconds) + subseconds / SUBTICKS_PER_TICK;
164136
return raw_ticks;
165137
}
166138

139+
// This function returns monotonically increasing ticks by adjusting away the RTC tick offset
140+
// from the last time the date was set.
141+
uint64_t stm32_peripherals_rtc_monotonic_ticks(uint8_t *subticks) {
142+
uint8_t raw_subticks;
143+
uint64_t monotonic_ticks = stm32_peripherals_rtc_raw_ticks(&raw_subticks) - rtc_ticks_offset;
144+
int8_t monotonic_subticks = raw_subticks - rtc_subticks_offset;
145+
// Difference might be negative. Normalize to 0-31.
146+
// `while` not really necessary; should only loop 0 or 1 times.
147+
while (monotonic_subticks < 0) {
148+
monotonic_ticks--;
149+
monotonic_subticks += SUBTICKS_PER_TICK;
150+
}
151+
*subticks = (uint8_t)monotonic_subticks;
152+
return monotonic_ticks;
153+
}
154+
155+
#if CIRCUITPY_RTC
156+
void stm32_peripherals_rtc_get_time(timeutils_struct_time_t *tm) {
157+
RTC_DateTypeDef date = {0};
158+
RTC_TimeTypeDef time = {0};
159+
160+
int code;
161+
if ((code = HAL_RTC_GetTime(&hrtc, &time, RTC_FORMAT_BIN)) == HAL_OK &&
162+
(code = HAL_RTC_GetDate(&hrtc, &date, RTC_FORMAT_BIN)) == HAL_OK) {
163+
tm->tm_hour = time.Hours;
164+
tm->tm_min = time.Minutes;
165+
tm->tm_sec = time.Seconds;
166+
tm->tm_wday = date.WeekDay - 1;
167+
tm->tm_mday = date.Date;
168+
tm->tm_mon = date.Month;
169+
tm->tm_year = date.Year + 2000;
170+
tm->tm_yday = -1;
171+
}
172+
}
173+
174+
void stm32_peripherals_rtc_set_time(timeutils_struct_time_t *tm) {
175+
RTC_DateTypeDef date = {0};
176+
RTC_TimeTypeDef time = {0};
177+
178+
uint8_t current_monotonic_subticks;
179+
uint64_t current_monotonic_ticks = stm32_peripherals_rtc_monotonic_ticks(&current_monotonic_subticks);
180+
181+
// SubSeconds will always be set to zero.
182+
time.Hours = tm->tm_hour;
183+
time.Minutes = tm->tm_min;
184+
time.Seconds = tm->tm_sec;
185+
date.WeekDay = tm->tm_wday + 1;
186+
date.Date = tm->tm_mday;
187+
date.Month = tm->tm_mon;
188+
date.Year = tm->tm_year - 2000;
189+
time.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
190+
time.StoreOperation = RTC_STOREOPERATION_RESET;
191+
192+
if (HAL_RTC_SetTime(&hrtc, &time, RTC_FORMAT_BIN) != HAL_OK ||
193+
HAL_RTC_SetDate(&hrtc, &date, RTC_FORMAT_BIN) != HAL_OK) {
194+
// todo - throw an exception
195+
}
196+
197+
uint8_t raw_subticks;
198+
rtc_ticks_offset = stm32_peripherals_rtc_raw_ticks(&raw_subticks) - current_monotonic_ticks;
199+
int8_t rtc_subticks_offset_signed = raw_subticks - current_monotonic_subticks;
200+
// Difference might be negative. Normalize subticks to 0-31.
201+
// `while` not really necessary; should only loop 0 or 1 times.
202+
while (rtc_subticks_offset_signed < 0) {
203+
rtc_ticks_offset--;
204+
rtc_subticks_offset_signed += SUBTICKS_PER_TICK;
205+
}
206+
rtc_subticks_offset = (uint8_t)rtc_subticks_offset_signed;
207+
}
208+
#endif
209+
167210
void stm32_peripherals_rtc_assign_wkup_callback(void (*callback)(void)) {
168211
wkup_callback = callback;
169212
}
@@ -177,7 +220,7 @@ void stm32_peripherals_rtc_set_wakeup_mode_seconds(uint32_t seconds) {
177220
}
178221

179222
void stm32_peripherals_rtc_set_wakeup_mode_tick(void) {
180-
HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, (rtc_clock_frequency / 16) / TICK_DIVISOR, RTC_WAKEUPCLOCK_RTCCLK_DIV2);
223+
HAL_RTCEx_SetWakeUpTimer_IT(&hrtc, (rtc_clock_frequency / 16) / TICKS_PER_SECOND, RTC_WAKEUPCLOCK_RTCCLK_DIV2);
181224
}
182225

183226
void stm32_peripherals_rtc_enable_wakeup_timer(void) {
@@ -205,9 +248,9 @@ void stm32_peripherals_rtc_set_alarm(uint8_t alarm_idx, uint32_t ticks) {
205248
uint64_t raw_ticks = stm32_peripherals_rtc_raw_ticks(NULL) + ticks;
206249

207250
RTC_AlarmTypeDef alarm;
208-
if (ticks > TICK_DIVISOR) {
251+
if (ticks > TICKS_PER_SECOND) {
209252
timeutils_struct_time_t tm;
210-
timeutils_seconds_since_2000_to_struct_time(raw_ticks / TICK_DIVISOR, &tm);
253+
timeutils_seconds_since_2000_to_struct_time(raw_ticks / TICKS_PER_SECOND, &tm);
211254
alarm.AlarmTime.Hours = tm.tm_hour;
212255
alarm.AlarmTime.Minutes = tm.tm_min;
213256
alarm.AlarmTime.Seconds = tm.tm_sec;
@@ -221,7 +264,7 @@ void stm32_peripherals_rtc_set_alarm(uint8_t alarm_idx, uint32_t ticks) {
221264
}
222265

223266
alarm.AlarmTime.SubSeconds = rtc_clock_frequency - 1 -
224-
((raw_ticks % TICK_DIVISOR) * 32);
267+
((raw_ticks % TICKS_PER_SECOND) * SUBTICKS_PER_TICK);
225268
if (alarm.AlarmTime.SubSeconds > rtc_clock_frequency) {
226269
alarm.AlarmTime.SubSeconds = alarm.AlarmTime.SubSeconds +
227270
rtc_clock_frequency;

ports/stm/peripherals/rtc.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636

3737
uint32_t stm32_peripherals_get_rtc_freq(void);
3838
void stm32_peripherals_rtc_init(void);
39-
uint64_t stm32_peripherals_rtc_raw_ticks(uint8_t *subticks);
39+
uint64_t stm32_peripherals_rtc_monotonic_ticks(uint8_t *subticks);
4040

4141
void stm32_peripherals_rtc_assign_wkup_callback(void (*callback)(void));
4242
void stm32_peripherals_rtc_set_wakeup_mode_seconds(uint32_t seconds);

ports/stm/supervisor/port.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -385,7 +385,7 @@ __attribute__((used)) void HardFault_Handler(void) {
385385
}
386386

387387
uint64_t port_get_raw_ticks(uint8_t *subticks) {
388-
return stm32_peripherals_rtc_raw_ticks(subticks);
388+
return stm32_peripherals_rtc_monotonic_ticks(subticks);
389389
}
390390

391391
// Enable 1/1024 second tick.

shared-module/time/__init__.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ uint64_t common_hal_time_monotonic_ns(void) {
3838
uint64_t ticks = port_get_raw_ticks(&subticks);
3939
// A tick is 976562.5 nanoseconds so multiply it by the base and add half instead of doing float
4040
// math.
41+
// A subtick is 1/32 of a tick.
42+
// 30518 is 1e9 / 32768
4143
return 976562 * ticks + ticks / 2 + 30518 * subticks;
4244
}
4345

0 commit comments

Comments
 (0)