MKR1400 timing accuracy vs. GPS PPS

Hello,

I made some tests with the millis() function against a GPS PPS source and am surprised, that there is a permanent drift of one millisec every 2...4 sec. I expected better accuracy because of the separate 32 kHz crystal on the MKR 1400. In previous projects with Mega2560 I used a extra DS3234 for the 32 kHz clock and was impressed about the accuracy of 1 sec/day.

Here my code:

#include <Arduino_MKRGPS.h>


const byte ppsPin = 6;
volatile bool newSec = false;
volatile int16_t abwPPS = 0;

extern volatile uint32_t _ulTickCount; // from delay.c, static removed


void ISR_pps() {
	newSec = true;
	
	abwPPS = _ulTickCount % 1000;
	if ( abwPPS > 500 ) abwPPS -= 1000;

	_ulTickCount -= abwPPS;
}


void setup() {
	Serial.begin(115200);
	while (!Serial);

	Serial.println("Starting");

	if (!GPS.begin(GPS_MODE_SHIELD)) {
		Serial.println("Failed to initialize GPS!");
		while (1);
	}

	pinMode(ppsPin, INPUT_PULLUP);
	attachInterrupt(digitalPinToInterrupt(ppsPin), ISR_pps, RISING);

	Serial.println("Setup finished");
}

void loop() {

	if ( newSec ) {
		newSec = false;
		Serial.print("millis=");Serial.print(millis());Serial.print(", abwPPS=");Serial.println(abwPPS);
	}
}

I also removed the static in the _ulTickCount definition in delay.c to make _ulTickCount available in the main scope:

/** Tick Counter united by ms */
//static volatile uint32_t _ulTickCount=0 ;
volatile uint32_t _ulTickCount=0;

And I deactivated all GPS NMEA messages expect RMC in the begin method of GPS.c from the Arduino_GPS library to reduce the serial interrupt load:

  _stream->write("$PUBX,40,GLL,0,0,0,0,0,0*5C\r\n"); _stream->flush();
  _stream->write("$PUBX,40,GSV,0,0,0,0,0,0*59\r\n"); _stream->flush();
  _stream->write("$PUBX,40,GSA,0,0,0,0,0,0*4E\r\n"); _stream->flush();
  _stream->write("$PUBX,40,GGA,0,0,0,0,0,0*5A\r\n"); _stream->flush();
  _stream->write("$PUBX,40,ZDA,0,0,0,0,0,0*44\r\n"); _stream->flush();
  _stream->write("$PUBX,40,VTG,0,0,0,0,0,0*5E\r\n"); _stream->flush();

Hardware is MKR 1400 with MKR GPS shield with PPS jumper connected.

millis=2086000, abwPPS=0
millis=2087000, abwPPS=1 correction was necessary
millis=2088000, abwPPS=0
millis=2089000, abwPPS=0
millis=2090000, abwPPS=0
millis=2091000, abwPPS=1 correction was necessary
millis=2092000, abwPPS=0
millis=2093000, abwPPS=0
millis=2094000, abwPPS=1 correction was necessary
millis=2095000, abwPPS=0
millis=2096000, abwPPS=0
millis=2097000, abwPPS=0
millis=2098000, abwPPS=1 correction was necessary
millis=2099000, abwPPS=0
millis=2100000, abwPPS=0
millis=2101000, abwPPS=0

Is the MKR1400 crystal realy so much worse the DR3234 clock?

Well GPS is the gold standard when it comes to timing accuracy (outside of a Cesium atomic clock). If you have access to a GPS PPS, then you should be able to calibrate the CPU clock on the MKR and not worry about the 32 kHz crystal

If i understand Arduino MKRs startup.c right, then the 48 MHz CPU clock is generated by Generic Clock Multiplexer 0 using the external 32 kHz OSC as reference. So the deviations on 32 kHz and CPU clock should be the same.

The task for me is to keep the clock accuracy high even if PPS is missing for a while.

What do you mean by "calibrate CPU clock"? Add ticks manually every e.g. 3 millis or is there any special function for this?

You can use the PPS from a GPS as a reference to calibrate the DFLL48M. The basic idea is

  1. create a timer, lets say a counter that counts from 0 to 2^16 (16 bit counter) with a 32768 Hz frequency

  2. create a pin interrupt using the GPS signal as trigger

  3. every time this interrupt is triggered, stop the timer, get the current value of the counter register, reset the counter, and restart the timer

  4. in a perfect world, you would expect the counter value to be 32768 (since its a 32768 Hz timer)

  5. if its lower than that, then you know your clock is "slower" than it should be. If it's higher, then the CPU clock is "faster" than it should be

  6. depending on the value of the counter, you can increase/decrease the calibration value of the DFLL48M (page 165 of the datasheet tells you about changing these calibration registers)

  7. Do note that this require significant changes to the code as this can only work in "open-loop" mode of the DFLL48M, but the Arduino bootloader sets up in "close-loop" mode with the 32K crystal as the input.

  8. Ideally, it would be easiest to use an accurate 32K source in the first place (instead of a quartz crystal), but most hobby GPS module only has a PPS output

  9. The other option is use a DS3231 module with a 32K output and feed that to the SAMD21, then configure this as the input to the DFLL48M

Thank you hzrnbgy. After a few hours the deviation changed from plus to minus, possibly because of decreased temperature. Thats why the DS32xx chips are temperature compensated. So calibration is not an option.

millis=32423000, abwPPS=0
millis=32424000, abwPPS=-1
millis=32425000, abwPPS=0
millis=32426000, abwPPS=-1
millis=32427000, abwPPS=-1
millis=32428000, abwPPS=0
millis=32429000, abwPPS=-1
millis=32430000, abwPPS=0
millis=32431000, abwPPS=-1
millis=32432001, abwPPS=-1
millis=32433000, abwPPS=0
millis=32434000, abwPPS=-1
millis=32435000, abwPPS=0
millis=32436000, abwPPS=-1
millis=32437000, abwPPS=-1
millis=32438000, abwPPS=0
millis=32439000, abwPPS=-1
millis=32440000, abwPPS=-1
millis=32441000, abwPPS=0
millis=32442000, abwPPS=-1

So my way seems to be your #9. I have already gone this way on Mega2560 and I hoped to simplify that with the MKR with the 32 kHz.

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.