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

RTC date set incorrectly for years before 2000 #321

Open
NathanielJS1541 opened this issue May 20, 2024 · 0 comments
Open

RTC date set incorrectly for years before 2000 #321

NathanielJS1541 opened this issue May 20, 2024 · 0 comments
Labels
type: imperfection Perceived defect in any part of project

Comments

@NathanielJS1541
Copy link

NathanielJS1541 commented May 20, 2024

The Bug 🐛

Using an Arduino Uno R4 WiFi, if the RTC date is set to before 2000-01-01, it will incorrectly report the date when it is read back from the RTC. For an easy repro, see the sketch I have provided in the Repro Sketch section.

Contents

This has gotten pretty long, so I'll leave this here as an easy way to jump between sections...

Debugging Attempt...

During my search for answers, I have spent a long time trying to work out where it's all going wrong. I will do my best to impart that knowledge onto others here.

My Findings

The RTC in the RA4M1 has a 100 year calendar, which in itself is not a problem, but it seems to be bodged into working with the C standard for tm_year (defined as the number of years since 1900 in the UNIX specification).

In the Arduino RTC library, the years of RTCTime get 1900 subtracted from them, so 1970 becomes 70 and 2017 becomes 117 etc:

#define TM_YEAR_OFFSET   (1900)

...

bool RTCTime::setYear(int _y) {
    if (_y >= TM_YEAR_OFFSET) {
        _y -= TM_YEAR_OFFSET;
    }
    year = _y;
    stime.tm_year = _y;
    //stime.tm_yday = day + yday(year, Month2tm(month));
    return true;
}

I am unable to debug what happens within the fsp, but according to the comments validation of the year should fail for 70 since the expected range is 100-199. This is detailed in r_rtc.c of Renesas/fsp. It looks like the validation is disabled, i.e. RTC_CFG_PARAM_CHECKING_ENABLE is not defined, since the result of RTC.setTime for these invalid dates returns True.

Since RTC_CFG_PARAM_CHECKING_ENABLE isn't defined, a year such as 70 (from 1970) can be passed to R_RTC_CalendarTimeSet without causing an error. When this happens, the year (i.e. 70) will have 100 subtracted from it, and then be cast to a uint8_t:

/* Subtract 100 to match with HW register */
R_RTC->RYRCNT = rtc_dec_to_bcd((uint8_t) (p_time->tm_year - RTC_C_TIME_OFFSET));

This results in (int) 70 becoming (int) -30. When cast to a uint8_t this becomes 226. This is then converted using the following function (rtc_dec_to_bcd) into a binary-coded decimal:

/*******************************************************************************************************************//**
 * Convert decimal to BCD
 *
 * @param[in] to_convert   Decimal Value to be converted
 **********************************************************************************************************************/
static uint8_t rtc_dec_to_bcd (uint8_t to_convert)
{
    return (uint8_t) ((((to_convert / (uint8_t) 10) << 4) & (uint8_t) RTC_MASK_LSB) | (to_convert % (uint8_t) 10));
}

Breaking this down in to the three parts:

  1. 226 is divided by 10 (22) and then bit-shifted by 4 (352).
  2. RTC_MASK_LSB is defined elsewhere as 0xF0.
  3. The modulo of 226 and 10 is 6.

Bringing this together, 352 & 0xF0 is 96. 96 | 6 is 102. 102 (0b01100110) is the binary-coded representation of 66, which will then be turned into 66 + 100 + 1900 = 2066 when the time is taken back from the RTC.

Issue Summary

It seems like the Arduino RTC library isn't validating whether the value has been constrained properly, which leads to issues when a value pre-2000 is passed in to R_RTC_CalendarTimeGet. It also seems like the fsp is being built without this validation in place by defining RTC_CFG_PARAM_CHECKING_ENABLE.

If the RTC cannot store the year correctly, it should be converted to a compatible value before being written to the RTC to avoid inconsistent behavior between getting and setting the RTC time. Even if the RTC cannot store a specified value, it should at least be constrained so that the conversion works the same in both directions.

I am aware that some of the code mentioned here is not maintained by Arduino, but I feel it important to highlight the restrictions in both hardware and software that must be taken into account if a fix is to be developed.

The comments within the RTC library are also a bit misleading about the valid range of years for RTCTime. One of the comments in the RTC library suggests that setting the date to a year 1989 is possible (See line 91 of RTC.h):

/* setters */
bool setDayOfMonth(int day); /* day from 1 to 31 */
bool setMonthOfYear(Month m); /* month from 1 (January) to 12 (December) */
bool setYear(int year); /* the year 1989 or 2022 */

It should either be better documented that the valid range of years is only 2000-2099, and perhaps even constrain the year and have an error state returned when an invalid date is used to explain why the year has been constrained. Each of the setters already returns a bool, make use of it!

This also needs commenting or accounting for in RTCTime::setUnixTime, as 0 is a valid for a UTC, but cannot be stored by the RTC itself!

Repro Sketch

I have created a test sketch to demonstrate the issue:

// Include the RTC library
#include "RTC.h"

// The number of seconds between the Epoch and 01/01/2000 12:00 am.
#define SECONDS_FROM_1970_TO_2000 946684800

// Function to print an RTCTime to the serial monitor.
// A newline will be printed after the tme.
void print_time(RTCTime time) {
  Serial.print(time.getYear());
  Serial.print("-");
  Serial.print(Month2int(time.getMonth()));
  Serial.print("-");
  Serial.print(time.getDayOfMonth());
  Serial.print(" ");
  Serial.print(time.getHour());
  Serial.print(":");
  Serial.print(time.getMinutes());
  Serial.print(":");
  Serial.println(time.getSeconds());
}

// Function to set the time to a specified value.
// Also prints debug infomation while setting the time.
void set_rtc_time(RTCTime time) {
  // Print the time the RTC will be set to.
  Serial.print("Setting time to: ");
  print_time(time);

  // Set the time on the RTC.
  bool result = RTC.setTime(time);

  // Check whether the RTC was set successfully.
  Serial.print("RTC Set Result: ");
  Serial.println(result ? "OK!" : "Failed.");

  // To debug, fetch the time from the RTC and print it.
  Serial.println("RTC now set to: ");
  RTCTime currentTime;
  RTC.getTime(currentTime);
  print_time(currentTime);
}

void setup() {
  // Start the serial monitor at 115200 baud.
  Serial.begin(115200);

  // Initialize the RTC.
  RTC.begin();

  // Create an RTCTime set to the epoch (UNIX time 0).
  RTCTime epochTime = RTCTime(0);

  // Create an RTCTime set to the 01/01/2000 12 am.
  RTCTime milleniumTime = RTCTime(SECONDS_FROM_1970_TO_2000);

  // Try and set the time to the epoch. Print debug info...
  set_rtc_time(epochTime);

  // Now try and set the time to the millenium.
  set_rtc_time(milleniumTime);
}

void loop() {
}

The output to the serial monitor will be as follows:

Setting time to: 1970-1-1 0:0:0
RTC Set Result: OK!
RTC now set to: 
2066-1-1 0:0:0
Setting time to: 2000-1-1 0:0:0
RTC Set Result: OK!
RTC now set to: 
2000-1-1 0:0:0
@NathanielJS1541 NathanielJS1541 changed the title RTC cannot be set to dates before the year 2000 RTC time set incorrectly for years before 2000 May 20, 2024
@NathanielJS1541 NathanielJS1541 changed the title RTC time set incorrectly for years before 2000 RTC date set incorrectly for years before 2000 May 20, 2024
@per1234 per1234 added the type: imperfection Perceived defect in any part of project label May 23, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: imperfection Perceived defect in any part of project
Projects
None yet
Development

No branches or pull requests

2 participants