Skip to content

Commit

Permalink
STM32 RTC : bypass shadow registers
Browse files Browse the repository at this point in the history
- RTC_SSR for the subseconds
- RTC_TR for the time
- RTC_DR for the date

These registers were accessed through shadow registers which are synchronized with PCLK1 (APB1 clock).
They are now accessed directly in order to avoid waiting for the synchronization duration.
  • Loading branch information
jeromecoutant committed Jul 11, 2018
1 parent 61f3d8b commit 1052993
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 83 deletions.
179 changes: 108 additions & 71 deletions targets/TARGET_STM/rtc_api.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@
#include "rtc_api_hal.h"
#include "mbed_mktime.h"
#include "mbed_error.h"
#include "mbed_critical.h"

#if DEVICE_LPTICKER && !MBED_CONF_TARGET_LPTICKER_LPTIM
volatile uint32_t LP_continuous_time = 0;
volatile uint32_t LP_last_RTC_time = 0;
#endif

static int RTC_inited = 0;

static RTC_HandleTypeDef RtcHandle;

Expand All @@ -41,18 +49,15 @@ void rtc_init(void)
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInitStruct = {0};

if (RTC_inited) {
return;
}
RTC_inited = 1;

// Enable access to Backup domain
__HAL_RCC_PWR_CLK_ENABLE();
HAL_PWR_EnableBkUpAccess();

#if DEVICE_LPTICKER
if ((rtc_isenabled()) && ((RTC->PRER & RTC_PRER_PREDIV_S) == PREDIV_S_VALUE)) {
#else /* DEVICE_LPTICKER */
if (rtc_isenabled()) {
#endif /* DEVICE_LPTICKER */
return;
}

#if MBED_CONF_TARGET_LSE_AVAILABLE
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_LSE;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE; // Mandatory, otherwise the PLL is reconfigured!
Expand Down Expand Up @@ -114,48 +119,20 @@ void rtc_init(void)
error("RTC initialization failed");
}

rtc_synchronize(); // Wait for RSF

if (!rtc_isenabled()) {
rtc_write(0);
#if !(TARGET_STM32F1) && !(TARGET_STM32F2)
/* STM32F1 : there are no shadow registers */
/* STM32F2 : shadow registers can not be bypassed */
if (HAL_RTCEx_EnableBypassShadow(&RtcHandle) != HAL_OK) {
error("EnableBypassShadow error");
}
#endif /* TARGET_STM32F1 || TARGET_STM32F2 */
}

void rtc_free(void)
{
// Disable access to Backup domain
HAL_PWR_DisableBkUpAccess();
/* RTC clock can not be reset */
}

/*
ST RTC_DateTypeDef structure
WeekDay 1=monday, 2=tuesday, ..., 7=sunday
Month 0x1=january, 0x2=february, ..., 0x12=december
Date day of the month 1-31
Year year 0-99
ST RTC_TimeTypeDef structure
Hours 0-12 if the RTC_HourFormat_12 is selected during init
0-23 if the RTC_HourFormat_24 is selected during init
Minutes 0-59
Seconds 0-59
TimeFormat RTC_HOURFORMAT12_AM/RTC_HOURFORMAT12_PM
SubSeconds time unit range between [0-1] Second with [1 Sec / SecondFraction +1] granularity
SecondFraction range or granularity of Sub Second register content corresponding to Synchronous pre-scaler factor value (PREDIV_S)
DayLightSaving RTC_DAYLIGHTSAVING_SUB1H/RTC_DAYLIGHTSAVING_ADD1H/RTC_DAYLIGHTSAVING_NONE
StoreOperation RTC_STOREOPERATION_RESET/RTC_STOREOPERATION_SET
struct tm
tm_sec seconds after the minute 0-61
tm_min minutes after the hour 0-59
tm_hour hours since midnight 0-23
tm_mday day of the month 1-31
tm_mon months since January 0-11
tm_year years since 1900
tm_wday days since Sunday 0-6
tm_yday days since January 1 0-365
tm_isdst Daylight Saving Time flag
*/

/*
Information about STM32F0, STM32F2, STM32F3, STM32F4, STM32F7, STM32L0, STM32L1, STM32L4:
Expand All @@ -172,7 +149,7 @@ Information about STM32F1:
For date, there is no specific register, only a software structure.
It is then not a problem to not use shifts.
*/

#if TARGET_STM32F1
time_t rtc_read(void)
{
RTC_DateTypeDef dateStruct = {0};
Expand All @@ -186,15 +163,13 @@ time_t rtc_read(void)
HAL_RTC_GetTime(&RtcHandle, &timeStruct, RTC_FORMAT_BIN);
HAL_RTC_GetDate(&RtcHandle, &dateStruct, RTC_FORMAT_BIN);

#if TARGET_STM32F1
/* date information is null before first write procedure */
/* set 01/01/1970 as default values */
if (dateStruct.Year == 0) {
dateStruct.Year = 2 ;
dateStruct.Month = 1 ;
dateStruct.Date = 1 ;
}
#endif

// Setup a tm structure based on the RTC
/* tm_wday information is ignored by _rtc_maketime */
Expand All @@ -215,11 +190,57 @@ time_t rtc_read(void)
return t;
}

#else /* TARGET_STM32F1 */

time_t rtc_read(void)
{
struct tm timeinfo;

/* Since the shadow registers are bypassed we have to read the time twice and compare them until both times are the same */
uint32_t Read_time = RTC->TR & RTC_TR_RESERVED_MASK;
uint32_t Read_date = RTC->DR & RTC_DR_RESERVED_MASK;

while ((Read_time != (RTC->TR & RTC_TR_RESERVED_MASK)) || (Read_date != (RTC->DR & RTC_DR_RESERVED_MASK))) {
Read_time = RTC->TR & RTC_TR_RESERVED_MASK;
Read_date = RTC->DR & RTC_DR_RESERVED_MASK;
}

/* Setup a tm structure based on the RTC
struct tm :
tm_sec seconds after the minute 0-61
tm_min minutes after the hour 0-59
tm_hour hours since midnight 0-23
tm_mday day of the month 1-31
tm_mon months since January 0-11
tm_year years since 1900
tm_yday information is ignored by _rtc_maketime
tm_wday information is ignored by _rtc_maketime
tm_isdst information is ignored by _rtc_maketime
*/
timeinfo.tm_mday = RTC_Bcd2ToByte((uint8_t)(Read_date & (RTC_DR_DT | RTC_DR_DU)));
timeinfo.tm_mon = RTC_Bcd2ToByte((uint8_t)((Read_date & (RTC_DR_MT | RTC_DR_MU)) >> 8)) - 1;
timeinfo.tm_year = RTC_Bcd2ToByte((uint8_t)((Read_date & (RTC_DR_YT | RTC_DR_YU)) >> 16)) + 68;
timeinfo.tm_hour = RTC_Bcd2ToByte((uint8_t)((Read_time & (RTC_TR_HT | RTC_TR_HU)) >> 16));
timeinfo.tm_min = RTC_Bcd2ToByte((uint8_t)((Read_time & (RTC_TR_MNT | RTC_TR_MNU)) >> 8));
timeinfo.tm_sec = RTC_Bcd2ToByte((uint8_t)((Read_time & (RTC_TR_ST | RTC_TR_SU)) >> 0));

// Convert to timestamp
time_t t;
if (_rtc_maketime(&timeinfo, &t, RTC_4_YEAR_LEAP_YEAR_SUPPORT) == false) {
return 0;
}

return t;
}

#endif /* TARGET_STM32F1 */

void rtc_write(time_t t)
{
RTC_DateTypeDef dateStruct = {0};
RTC_TimeTypeDef timeStruct = {0};

core_util_critical_section_enter();
RtcHandle.Instance = RTC;

// Convert the time into a tm
Expand Down Expand Up @@ -247,40 +268,49 @@ void rtc_write(time_t t)
timeStruct.StoreOperation = RTC_STOREOPERATION_RESET;
#endif /* TARGET_STM32F1 */

#if DEVICE_LPTICKER && !MBED_CONF_TARGET_LPTICKER_LPTIM
/* Need to update LP_continuous_time value before new RTC time */
uint32_t current_lp_time = rtc_read_lp();

This comment has been minimized.

Copy link
@mattbrown015

mattbrown015 Jul 31, 2018

Contributor

@jeromecoutant This generates an unused variable warning when building with "target.lpticker_lptim": 0.

It appears you're calling rtc_read_lp() because of its side effect of updating LP_continuous_time.

I don't understand what's going on here, and don't need to, but the warning worried me and made my build output look untidy.

Is it definitely necessary to call rtc_read_lp()? And, if so, can we remove the unused variable or add MBED_UNUSED? My preference would be the former, why have source code that doesn't do anything??


/* LP_last_RTC_time value is updated with the new RTC time */
LP_last_RTC_time = timeStruct.Seconds + timeStruct.Minutes * 60 + timeStruct.Hours * 60 * 60;

/* Save current SSR */
uint32_t Read_SubSeconds = (uint32_t)(RTC->SSR);
#endif /* DEVICE_LPTICKER && !MBED_CONF_TARGET_LPTICKER_LPTIM */

// Change the RTC current date/time
if (HAL_RTC_SetDate(&RtcHandle, &dateStruct, RTC_FORMAT_BIN) != HAL_OK) {
error("HAL_RTC_SetDate error\n");
}
if (HAL_RTC_SetTime(&RtcHandle, &timeStruct, RTC_FORMAT_BIN) != HAL_OK) {
error("HAL_RTC_SetTime error\n");
}

#if DEVICE_LPTICKER && !MBED_CONF_TARGET_LPTICKER_LPTIM
while (Read_SubSeconds != (RTC->SSR)) {
}
#endif /* DEVICE_LPTICKER && !MBED_CONF_TARGET_LPTICKER_LPTIM */

core_util_critical_section_exit();
}

int rtc_isenabled(void)
{
#if !(TARGET_STM32F1)
return (((RTC->ISR & RTC_ISR_INITS) == RTC_ISR_INITS) && ((RTC->ISR & RTC_ISR_RSF) == RTC_ISR_RSF));
return ((RTC->ISR & RTC_ISR_INITS) == RTC_ISR_INITS);
#else /* TARGET_STM32F1 */
return ((RTC->CRL & RTC_CRL_RSF) == RTC_CRL_RSF);
#endif /* TARGET_STM32F1 */
}

void rtc_synchronize(void)
{
RtcHandle.Instance = RTC;
if (HAL_RTC_WaitForSynchro(&RtcHandle) != HAL_OK) {
error("rtc_synchronize error\n");
}
}

#if DEVICE_LPTICKER && !MBED_CONF_TARGET_LPTICKER_LPTIM

static void RTC_IRQHandler(void);
static void (*irq_handler)(void);

volatile uint8_t lp_Fired = 0;
volatile uint32_t LP_continuous_time = 0;
volatile uint32_t LP_last_RTC_time = 0;

static void RTC_IRQHandler(void)
{
Expand Down Expand Up @@ -311,31 +341,34 @@ static void RTC_IRQHandler(void)

uint32_t rtc_read_lp(void)
{
RTC_TimeTypeDef timeStruct = {0};
RTC_DateTypeDef dateStruct = {0};

RtcHandle.Instance = RTC;
HAL_RTC_GetTime(&RtcHandle, &timeStruct, RTC_FORMAT_BIN);
struct tm timeinfo;

/* Reading RTC current time locks the values in calendar shadow registers until Current date is read
to ensure consistency between the time and date values */
HAL_RTC_GetDate(&RtcHandle, &dateStruct, RTC_FORMAT_BIN);
/* Since the shadow registers are bypassed we have to read the time twice and compare them until both times are the same */
/* We don't have to read date as we bypass shadow registers */
uint32_t Read_SecondFraction = (uint32_t)(RTC->PRER & RTC_PRER_PREDIV_S);
uint32_t Read_time = (uint32_t)(RTC->TR & RTC_TR_RESERVED_MASK);
uint32_t Read_SubSeconds = (uint32_t)(RTC->SSR);

if (timeStruct.SubSeconds > timeStruct.SecondFraction) {
/* SS can be larger than PREDIV_S only after a shift operation. In that case, the correct
time/date is one second less than as indicated by RTC_TR/RTC_DR. */
timeStruct.Seconds -= 1;
while ((Read_time != (RTC->TR & RTC_TR_RESERVED_MASK)) || (Read_SubSeconds != (RTC->SSR))) {
Read_time = (uint32_t)(RTC->TR & RTC_TR_RESERVED_MASK);
Read_SubSeconds = (uint32_t)(RTC->SSR);
}
uint32_t RTC_time_s = timeStruct.Seconds + timeStruct.Minutes * 60 + timeStruct.Hours * 60 * 60; // Max 0x0001-517F => * 8191 + 8191 = 0x2A2E-AE80

timeinfo.tm_hour = RTC_Bcd2ToByte((uint8_t)((Read_time & (RTC_TR_HT | RTC_TR_HU)) >> 16));
timeinfo.tm_min = RTC_Bcd2ToByte((uint8_t)((Read_time & (RTC_TR_MNT | RTC_TR_MNU)) >> 8));
timeinfo.tm_sec = RTC_Bcd2ToByte((uint8_t)((Read_time & (RTC_TR_ST | RTC_TR_SU)) >> 0));

uint32_t RTC_time_s = timeinfo.tm_sec + timeinfo.tm_min * 60 + timeinfo.tm_hour * 60 * 60; // Max 0x0001-517F => * 8191 + 8191 = 0x2A2E-AE80

if (LP_last_RTC_time <= RTC_time_s) {
LP_continuous_time += (RTC_time_s - LP_last_RTC_time);
} else {
/* Add 24h */
LP_continuous_time += (24 * 60 * 60 + RTC_time_s - LP_last_RTC_time);
}
LP_last_RTC_time = RTC_time_s;

return LP_continuous_time * PREDIV_S_VALUE + timeStruct.SecondFraction - timeStruct.SubSeconds;
return LP_continuous_time * PREDIV_S_VALUE + Read_SecondFraction - Read_SubSeconds;
}

void rtc_set_wake_up_timer(timestamp_t timestamp)
Expand Down Expand Up @@ -377,7 +410,11 @@ void rtc_fire_interrupt(void)
void rtc_deactivate_wake_up_timer(void)
{
RtcHandle.Instance = RTC;
HAL_RTCEx_DeactivateWakeUpTimer(&RtcHandle);
__HAL_RTC_WRITEPROTECTION_DISABLE(&RtcHandle);
__HAL_RTC_WAKEUPTIMER_DISABLE(&RtcHandle);
__HAL_RTC_WAKEUPTIMER_DISABLE_IT(&RtcHandle, RTC_IT_WUT);
__HAL_RTC_WRITEPROTECTION_ENABLE(&RtcHandle);
NVIC_DisableIRQ(RTC_WKUP_IRQn);
}

#endif /* DEVICE_LPTICKER && !MBED_CONF_TARGET_LPTICKER_LPTIM */
Expand Down
12 changes: 0 additions & 12 deletions targets/TARGET_STM/sleep.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
#include "mbed_critical.h"
#include "mbed_error.h"

extern void rtc_synchronize(void);
extern void save_timer_ctx(void);
extern void restore_timer_ctx(void);

Expand Down Expand Up @@ -203,17 +202,6 @@ void hal_deepsleep(void)

restore_timer_ctx();

#if DEVICE_RTC
/* Wait for RTC RSF bit synchro if RTC is configured */
#if (TARGET_STM32F2) || (TARGET_STM32F4) || (TARGET_STM32F7)
if (READ_BIT(RCC->BDCR, RCC_BDCR_RTCSEL)) {
#else /* (TARGET_STM32F2) || (TARGET_STM32F4) || (TARGET_STM32F7) */
if (__HAL_RCC_GET_RTC_SOURCE()) {
#endif /* (TARGET_STM32F2) || (TARGET_STM32F4) || (TARGET_STM32F7) */
rtc_synchronize();
}
#endif

// Enable IRQs
core_util_critical_section_exit();
}
Expand Down

0 comments on commit 1052993

Please sign in to comment.