UNO R4 RTC accuracy

I was disappointed with the internal RTC accuracy.
It gained 20 seconds in 10 minutes.

I note that other people have found the RTC to be not highly accurate. Since there is no crystal-controlled oscillator on board, I expected some deviation, but not that much. I solved my problem by regularly updating via NTP. Polling the NTP servers constantly is not recommended, as they may consider it a denial of service attack or similar. Taking a guess that a 10 minute interlude is acceptable, I re-wrote the code for their example. Make sure you check the time zone offset for your location.

Changes:
Added variables NTPcheck, NTPdelay and NTPdiff
Extracted the NTP access code into its own routine void(NTP)
Changed and added code at the end to update every 10 minutes.

FULL CODE

// Arduino R4 WiFi Get time from NTP
// Include the RTC library
#include "RTC.h"

//Include the NTP library
#include <NTPClient.h>

#if defined(ARDUINO_PORTENTA_C33)
#include <WiFiC3.h>
#elif defined(ARDUINO_UNOWIFIR4)
#include <WiFiS3.h>
#endif

#include <WiFiUdp.h>
#include "arduino_secrets.h"

int NTPCheck;
int NTPdelay;
int NTPdiff = 0;

///////Enter login data in the Secret tab/arduino_secrets.h
char ssid[] = SECRET_SSID;  // network SSID
char pass[] = SECRET_PASS;  // network password

int wifiStatus = WL_IDLE_STATUS;
WiFiUDP Udp;  // A UDP instance to let us send and receive packets over UDP
NTPClient timeClient(Udp);

void printWifiStatus() {
  // print the SSID of the network you're attached to:
  Serial.print("SSID: ");
  Serial.println(WiFi.SSID());

  // print your board's IP address:
  IPAddress ip = WiFi.localIP();
  Serial.print("IP Address: ");
  Serial.println(ip);

  // print the received signal strength:
  long rssi = WiFi.RSSI();
  Serial.print("signal strength (RSSI):");
  Serial.print(rssi);
  Serial.println(" dBm");
}

void connectToWiFi() {
  // check for the WiFi module:
  if (WiFi.status() == WL_NO_MODULE) {
    Serial.println("Communication with WiFi module failed!");
    // don't continue
    while (true)
      ;
  }

  String fv = WiFi.firmwareVersion();
  if (fv < WIFI_FIRMWARE_LATEST_VERSION) {
    Serial.println("Please upgrade the firmware");
  }

  // attempt to connect to WiFi network:
  while (wifiStatus != WL_CONNECTED) {
    Serial.print("Attempting to connect to SSID: ");
    Serial.println(ssid);
    // Connect to WPA/WPA2 network.
    wifiStatus = WiFi.begin(ssid, pass);

    // wait 10 seconds for connection:
    delay(10000);
  }

  Serial.println("Connected to WiFi");
  printWifiStatus();
}

void NTP() {
  // Get the current date and time from an NTP server and convert
  // Time Zone offset for Cairns is 10 hours
  auto timeZoneOffsetHours = 10;
  auto unixTime = timeClient.getEpochTime() + (timeZoneOffsetHours * 3600);
  RTCTime timeToSet = RTCTime(unixTime);
  RTC.setTime(timeToSet);
}

void setup() {
  Serial.begin(9600);
  while (!Serial)
    ;
  connectToWiFi();
  RTC.begin();
  Serial.println("\nStarting connection to server...");
  timeClient.begin();
  timeClient.update();

  // Get the current time from NTP
  NTP();
}

void loop() {
  RTCTime currentTime;
  // Get current time from RTC
  RTC.getTime(currentTime);

  // Print out date (DD/MM//YYYY)
  Serial.print(currentTime.getDayOfMonth());
  Serial.print("/");
  Serial.print(Month2int(currentTime.getMonth()));
  Serial.print("/");
  Serial.print(currentTime.getYear());
  Serial.print(" - ");

  // Print time (HH/MM/SS)
  Serial.print(currentTime.getHour());
  Serial.print(":");
  Serial.print(currentTime.getMinutes());
  Serial.print(":");
  Serial.println(currentTime.getSeconds());

  delay(1000);

  // After 10 minutes, call NTP to set the RTC time again
  if (NTPdiff == 0) {
    NTPCheck = currentTime.getMinutes();
    NTPdelay = currentTime.getMinutes();
    NTPdiff = 1;
  } else {
    NTPdelay = currentTime.getMinutes();
    if (NTPdelay - NTPCheck <= 9) {
    } else {
      NTP();
      NTPdiff = 0;
      Serial.println("NTP Updated");
    }
  }
}
2 Likes

Hi Nigel,

Cheers for posting your software fix! Perhaps the Arduino designers figured the R4 WiFi would have an NTP connection and thus accuracy wasn’t critical (allowing them to reduce board cost without a second crystal)?

James. Yes, but methinks that if there is one crystal onboard, it can be used for several jobs. Never mind, perhaps in a future version.

Hi Nigel,

I’ve worked at a low level with STM32s (similar to the Renesas micro used on the R4 in scope), and the core expects a crystal in the MHz (say, 8, then sent through a PLL to get it up to 96MHz for the core clock. On the other hand the RTC expects a 32.786kHz clock that corresponds to one second per rollover of a 15-bit timer (2^15 = 32,768). Usually there are a ton of dividers and routing option for the core and other peripherals, but the RTC doesn’t have as much flexibility. This difference in ability to divide and vast difference in crystal speeds means if you have one crystal, you have to pick between the two.

James. Yes, obviously not enough dividers on board at the base crystal level to accommodate. I guess it comes down to cost. Still a software solution is probably good enough unless you want microsecond timing.
:slight_smile:

1 Like

I changed the code:
// Arduino R4 WiFi Get time from NTP
// Include the RTC library
#include “RTC.h”

//Include the NTP library
#include <NTPClient.h>

#if defined(ARDUINO_PORTENTA_C33)
#include <WiFiC3.h>
#elif defined(ARDUINO_UNOWIFIR4)
#include <WiFiS3.h>
#endif

#include <WiFiUdp.h>
#include “arduino_secrets.h”

int interval = 600000; // 10 minute delay before calling NTP update
unsigned long previousMillis = 0;

///////Enter login data in the Secret tab/arduino_secrets.h
char ssid = SECRET_SSID; // network SSID
char pass = SECRET_PASS; // network password

int wifiStatus = WL_IDLE_STATUS;
WiFiUDP Udp; // A UDP instance to let us send and receive packets over UDP
NTPClient timeClient(Udp);

void printWifiStatus() {
// print the SSID of the network you’re attached to:
Serial.print("SSID: ");
Serial.println(WiFi.SSID());

// print your board’s IP address:
IPAddress ip = WiFi.localIP();
Serial.print("IP Address: ");
Serial.println(ip);

// print the received signal strength:
long rssi = WiFi.RSSI();
Serial.print(“signal strength (RSSI):”);
Serial.print(rssi);
Serial.println(" dBm");
}

void connectToWiFi() {
// check for the WiFi module:
if (WiFi.status() == WL_NO_MODULE) {
Serial.println(“Communication with WiFi module failed!”);
// don’t continue
while (true)
;
}

String fv = WiFi.firmwareVersion();
if (fv < WIFI_FIRMWARE_LATEST_VERSION) {
Serial.println(“Please upgrade the firmware”);
}

// attempt to connect to WiFi network:
while (wifiStatus != WL_CONNECTED) {
Serial.print("Attempting to connect to SSID: ");
Serial.println(ssid);
// Connect to WPA/WPA2 network.
wifiStatus = WiFi.begin(ssid, pass);

// wait 10 seconds for connection:
delay(10000);

}

Serial.println(“Connected to WiFi”);
printWifiStatus();
}

void NTP() {
// Get the current date and time from an NTP server and convert
// Time Zone offset for Cairns is 10 hours
auto timeZoneOffsetHours = 10;
auto unixTime = timeClient.getEpochTime() + (timeZoneOffsetHours * 3600);
RTCTime timeToSet = RTCTime(unixTime);
RTC.setTime(timeToSet);
}

void setup() {
Serial.begin(9600);
while (!Serial)
;
connectToWiFi();
RTC.begin();
Serial.println(“\nStarting connection to server…”);
timeClient.begin();
timeClient.update();

// Get the current time from NTP
NTP();
}

void loop() {
RTCTime currentTime;
// Get current time from RTC
RTC.getTime(currentTime);

// Print out date (DD/MM//YYYY)
Serial.print(currentTime.getDayOfMonth());
Serial.print(“/”);
Serial.print(Month2int(currentTime.getMonth()));
Serial.print(“/”);
Serial.print(currentTime.getYear());
Serial.print(" - ");

// Print time (HH/MM/SS)
Serial.print(currentTime.getHour());
Serial.print(“:”);
Serial.print(currentTime.getMinutes());
Serial.print(“:”);
Serial.println(currentTime.getSeconds());

delay(1000);

// After 10 minutes, call NTP to set the RTC time again
unsigned long currentMillis = millis();
if ((unsigned long)(currentMillis - previousMillis) >= interval) {
NTP();
Serial.print(“RTC Updated at: “);
Serial.print(currentTime.getHour());
Serial.print(”:”);
Serial.println(currentTime.getMinutes());
previousMillis = currentMillis;
}
}

Thanks for documenting your work @Nigel1 - i agree that it’s disappointing the product is marketed as having an RTC. While technically true in this case (it keeps time and date registers which is convenient) it’s unfortunate there seems to be no way to BYO crystal to the party and so the clock source is an inaccurate internal oscillator.

A bit of an oversight :confused:

It looks like a lot of people are affected, there’s a fair bit of discussion as I’m sure you’ve already found.
forum topic 1
forum topic 2

1 Like

Is it better than using DS1307 etc?

It’s different. The DS1307 will keep the time during a power down, but it’s accuracy is not always good - depends on the crystal and manufacturer. However, it doesn’t require network access.
The software method relies on NTP accuracy, which is good. Obviously after a power down, it must reconnect with a network for NTP access. Make sure you use the second version I posted.

1 Like

Agree with @Nigel1 - the advantage comes from the convenience NTP offers, but relies heavily on NTP to keep accurate time long-term.

A DS1307 is super-good-enough for hobby projects without NTP / other time correction.

If you need decent timekeeping, I would really recommend the DS3234. I also have the Uno R4 WiFi, and I’m losing about the same accuracy as you are. If my calculations are correct, the RTC on this board has an accuracy of about ± 33,333 ppm, which is absolutely horrendous. The DS3234 has an accuracy of ± 2 ppm, several orders of magnitude more accurate. Sparkfun sells one on a breakout board that also includes a coin cell holder so it will continue to keep time even if power is disconnected. It isn’t cheap, but if you need something that only loses 0.173 seconds of accuracy every 24 hours, the $25 price tag might be worth it.

I read the datasheet for the microcontroller on this board and found that you can actually adjust the RTC’s accuracy by setting a plus or minus offset in a control register, but unfortunately you can only adjust the offset if the RTC is using an external 32.768KHz crystal, and as far as I know, there’s no way to modify the RTC clock control register in the Arduino software, besides the fact that there aren’t any pads exposed to solder in an external crystal anyways. I really wish Arduino had chosen a different microcontroller for this board. It just seems like some of the cost could’ve been diverted from something like the LED matrix and put into a better microcontroller. I was really looking forward to using this board as a datalogger with accurate timestamps without having to rely on external devices to provide the functionality I need. Oh well, maybe in the next revision.

I agree with you. It was bad design or perhaps cost was a motivating factor. For the project I had at the time, NTP was always available, so a software solution was fine. I have used the DS1307 in the past. I shall keep the DS3234 in mind. Thanks.

2 Likes

The last IF stmt is buggy and doesnt update in the first 30 minutes of each hour. Rather than:

if (NTPdelay - NTPCheck <= 9) {
} else {
NTP();
NTPdiff = 0;
Serial.println(“NTP Updated”);
}

I found this works every 10 minutes:

if (abs(NTPdelay - NTPCheck) > 9) {
NTP();
NTPdiff = 0;
Serial.println(“NTP Updated”);
}

1 Like

Yes, I realised the error and posted updated code on the 6th Jan (I think).

I have several clocks running on that code and they work perfectly. I hope that helps.

2 Likes