stm32duino / STM32RTC

Arduino RTC library for STM32.
131 stars 48 forks source link

STM32F1: RTC does not advance date when running from VBAT for long periods of time #87

Closed ArrestedLightning closed 1 year ago

ArrestedLightning commented 1 year ago

I am working on a project based on the STM32F1 and have found an issue with this library. I am using a custom board which should be equivalent to a blue pill board for the purposes of this issue. (32.768KHz RTC oscillator, 8MHz main oscillator, 3V CR2032 attached to the VBAT pin, USB-TTL serial adapter on pins PA9 and PA10.) If the board is powered down (i.e. only VBAT is present) when the clock rolls over from 23:59 to 00:00, the time will be correct when main power is reapplied to the board, but the date will not advance if the power is off for more than hour.

I have created a short Arduino sketch which demonstrates the issue:

#include <STM32RTC.h>
STM32RTC& rtc = STM32RTC::getInstance();

HardwareSerial Serial1(PA10, PA9);

const byte seconds = 0;
const byte minutes = 58;
const byte hours = 23;
const byte weekDay = 1;
const byte day = 1;
const byte month = 1;
const byte year = 23;

void setup()
{
  pinMode(PB0, INPUT_PULLUP);
  Serial1.begin(115200);
  rtc.setClockSource(STM32RTC::LSE_CLOCK);
  rtc.begin(STM32RTC::HOUR_24); // initialize RTC 24H format

  Serial1.println("---------------------------------------------------------------"); 

  if (digitalRead(PB0) == 0) {
    rtc.setTime(hours, minutes, seconds);
    rtc.setDate(weekDay, day, month, year);
    Serial1.printf("RTC reset to %d:%d:%d %d-%d-%d\n", hours, minutes, seconds, year, month, day);
  }
}

void loop()
{
  Serial1.printf("%02d-%02d-%02d ", rtc.getYear(), rtc.getMonth(), rtc.getDay());
  Serial1.printf("%02d:%02d:%02d.%03d\n", rtc.getHours(), rtc.getMinutes(), rtc.getSeconds(), rtc.getSubSeconds());
  delay(1000);
}

To demonstrate the issue, power on the board with pin B0 shorted to ground, then power it off and wait 10 minutes. When it is powered on again, it will print out the following:

---------------------------------------------------------------
23-01-02 00:10:01.000
23-01-02 00:10:02.000

Note that both the date and time have advanced, as expected.

Then repeat the process: power on the board with pin B0 shorted to ground, then power it off and wait 70 minutes. When it is powered on again, it will print out the following:

---------------------------------------------------------------
23-01-01 01:10:48.000
23-01-01 01:10:49.000

In this case, the time has advanced as expected, but the date has not. Leaving the device powered off for longer periods of time will produce similar results.

As I understand it, the STM32F1 does not have a hardware calendar, so the RTC is just used as a flat seconds counter that (when the CPU is running) is reset every 86400 seconds. Since it is a 32-bit register, there is no technical reason that it couldn't handle more than one day's worth of time, assuming the logic was implemented to support this.

Looking into how the library is implemented, I see that RTC_SetDate is used to set the date from backup RAM when the RTC is initialized: https://github.com/stm32duino/STM32RTC/blob/58da1ed07f7864a5668d057ffe50ef53b69158fe/src/rtc.c#L487-L490

RTC_SetDate does not contain any logic to handle advancing the date, though, and in fact intentionally subtracts out any accumulated days: https://github.com/STMicroelectronics/STM32CubeF1/blob/2976d3b5b5cd1178ece18546e697c497c29d9c14/Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_rtc.c#L962-L969

However, the logic to advance the date based on accumulated counter time does in fact already exist, in the HAL_RTC_GetTime and RTC_DateUpdate functions: https://github.com/STMicroelectronics/STM32CubeF1/blob/2976d3b5b5cd1178ece18546e697c497c29d9c14/Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_rtc.c#L825-L832 https://github.com/STMicroelectronics/STM32CubeF1/blob/2976d3b5b5cd1178ece18546e697c497c29d9c14/Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_rtc.c#L882

This function even pulls in the backup date from the RtcHandle structure. It seems a little strange to use the GetTime function to actually set the date, but it does not seem to have any other side effects.

I have experimented with replacing the RTC_SetDate call with HAL_RTC_GetTime in the RTC_Init function, and in my testing have found that it appears to resolve the issue, at least for my application. The date is advanced correctly even after being powered off for several days.

https://github.com/ArrestedLightning/STM32RTC/blob/7243392c9bd14c40b534ab96ac5cb333a4cf2909/src/rtc.c#L487-L490

However, I am not sure if there may be other side effects due to this change, or additional changes required for other code paths that I'm not using; someone who is more familiar with the code base would have to comment on that.

fpistm commented 1 year ago

Hi, I will deep into this and get you informed. My first guess is that it misses one step after date restoration. https://github.com/STMicroelectronics/STM32CubeF1/blob/2976d3b5b5cd1178ece18546e697c497c29d9c14/Drivers/STM32F1xx_HAL_Driver/Src/stm32f1xx_hal_rtc.c#L61C7-L63

fpistm commented 1 year ago

Hi @ArrestedLightning,

Thanks for the detailed description, I've reviewed the code and agreed with you. I've made a PR to fix this. Let me know if it is ok for you.

fpistm commented 1 year ago

I've made a test with a Nucleo F103RB and it works as expected, wait 110min :

---------------------------------------------------------------
RTC reset to 23:58:0 23-1-1
23-01-01 23:58:00.000
23-01-01 23:58:00.000
23-01-01 23:58:01.000
23-01-01 23:58:02.000
23-01-01 23:58:03.000
23-01-01 23:58:04.000
23-01-01 23:58:05.000
23-01-01 23:58:06.000
23-01-01 23:58:07.000
23-01-02 01:48:27.000
23-01-02 01:48:28.000
23-01-02 01:48:29.000
23-01-02 01:48:30.000
23-01-02 01:48:31.000