The Mystery of the Non-Matching Clocks

A friend of mine, David Sparks, is using a SAMD based board (a Seeedstudio XIAO) to be a real time clock. You can see his project here: https://github.com/IowaDave/SAMD21-RTC-Clock. The SAMD has a Real Time Counter which can be configured as a Real Time Clock (RTC=RTC!) eliminating the need for a second component. However when run the time seems to slip when compared with calls to print the time using millis() to print every five seconds. Here is my loop function:

void loop() {
  if (millis() - lastReading >= 5000) {
    Serial.print("Current time:");
    readAndDisplayDTStamp();
    lastReading += 5000;  // advance time of next reading
  }
}

Here is what happens every few minutes:

Current time:10/16/22 07:34:30
Current time:10/16/22 07:34:35
Current time:10/16/22 07:34:39
Current time:10/16/22 07:34:44

So I needed to hunt this down. After all, there is only one clock source! It turns out there are two findings to this issue, depending on if the Arduino board has a crystal (most boards and the XIAO) or not (the Arduino Nano 33 IoT). In neither case is there a real solution, other than not using the millis() function but instead creating a timer that uses the 32.768 kHz clock source.

The Crystal Case

These boards have an external 32.768 kHz crystal oscillator. The RTC divides this by 32,768 to get a 1 Hz clock for the time display. This will be as accurate as the crystal. The problem comes with the 48 MHz system clock that is generated from the 32.768 kHz clock with a phase-locked loop. This high speed system clock is what provides the timing for the millis() function among basically everything else in the Microcontroller. Unfortunately 48,000,000 is not an integer multiple of 32768. A multiplier of 1465 is used and the actual frequency is 48,005,120 Hz. This causes the millis() function to run fast. In most applications the small error won’t be observable, but in is in this case since we can easily detect small differences between two frequencies in this program (hint — look up “vernier scales”).

The problem can be minimized but not entirely eliminated. If the defined clock speed in the Arduino library is changed from 48000000 to 48005000 (the closest it can be resolved) the difference is cut by a factor of about 40x.

The Crystal-less Case

The Arduino Nano 33 IoT has an internal 32.768 kHz clock which runs the RTC with somewhat less accuracy. However this board does a bit of “funny business” in the generation of the high speed clock. If the USB port is active then it uses the 1 kHz Start Of Frame signal from the bus to lock the 48 MHz oscillator using a multiplier of 48,000. Now the RTC and the millis() function are using two distinct clock sources so there is no way they will be synchronized! At least the 48 MHz clock is exactly 48 MHz now, to within the accuracy of the USB host clock.

If the USB port is not being used, the 48 MHz oscillator switches to open-loop mode without the phase-locked loop. The clock will be about 10x less accurate.