I chose this model because it has a ublox GPS module and a header with signals marked "GP" and "EX". EX should be external interrupt, and it opens the possibility of a simple GPS-disciplined frequency counter.
It comes with a small antenna, short cable. One of those godawful laptop style connectors, and no provision for directly soldering a better one. Sigh.
Initially, I connected directly to my PC without a microcontroller involved, by jumpering a 5V FTDI dongle to +5V, GND, TX and RX pins. In this condition, it gets a fix after a few minutes on a windowsill with a decent view of the sky
NMEA identification says:
$GPTXT,01,01,02,u-blox ag - www.u-blox.com*50 $GPTXT,01,01,02,HW UBX-G60xx 00040007 FF7FFFFFp*53 $GPTXT,01,01,02,ROM CORE 7.03 (45969) Mar 17 2011 16:18:34*59 $GPTXT,01,01,02,ANTSUPERV=AC SD PDoS SR*20 $GPTXT,01,01,02,ANTSTATUS=DONTKNOW*33and it works with u-center so it might even be a genuine ublox module or at least a genuine UBX-G60xx chip (but who can say for sure).
Next, I soldered pins to the GP/EX header JP5 and seated it on an Arduino form-factor Metro M4 with CircuitPython.
After a bit of hacking, the M4 puts the GPS in frequency counter mode, and I've also connected my ublox-based RF generator to the GPS shield to the EX input. (Note that the RF generator's signal is referenced to 3.0V and has a nominal 50 ohm impedance; the GPS shield lacks level conversion for the EX pin, so connecting 5V could cause damage)
Placing the GPS in counter mode means sending a CFG-MSG packet to turn on TIM-TM2 reports. Both of these messages are in UBX binary format.
The main hitch I discovered is that in the ublox TIM-TM2 message, the number of counts is modulo 2^16 and the message is delivered once a second, so only frequencies of up to 65535Hz can be counted directly.
However, when you're willing to allow the assumption that the original clock is close to a specific value, the lost high bits of the count can be restored with some arithmetic. As you know the delta time accurately, you know how many counts are expected within that delta time. You simply choose k such that k*65536+lo_counts is closest to the expected counts over the given timespan.
The program in its current form compares the most recently received TIM-TM2 packet to the first received one, so you have to drive the module with a fixed frequency from powerup (or, rather, from when the python code starts running).
After a few minutes of accumulated data, the input frequency remains highly consistent with being 10MHz (or that they both have the same systematic error due to both being based on ublox GPS modules and receiving the same GPS signal):
Frequency estimate = 9999999.999537499 15999996466 counts in 1599.999646674s estimated error [start,end]=62,66ns 9999999.998737499 .. 10000000.000337500
While it seems initially that you could go to infinite gate time, in practice I don't think that's possible while considering only the first and last packets. That's because the error between the nominal and actual frequency will accumulate and eventually exceed 2^15 counts. When this happens, the code to restore the count high bits will restore it to a value closer than the true value. However, by doing the high bit restoration based on the two most recent TIM-TM2 packets, even this restriction should be lifted.
(For a nominal 10MHz clock with a 1ppm systematic error, aka 10Hz, this would become a problem at around 3276s if I understand properly)
In order to ensure accuracy, my code works with nanoseconds and nanoHz as Python longs, so floating point precision (and specifically micropython floats which are just 32-bit values!) does not affect the accuracy of the arithmetic.
My code is too un-polished to be published at this time, and due to the necessity of hard-coding the nominal frequency I don't plan to publish it at this time and may not develop it any further.
Minor update: After running overnight, the two GPSes agree to within a very tight margin (on the order of 1e-11 or a microsecond per day):
Frequency estimate = 10000000.000036165 334569999530 counts in 33456.999952879s estimated error [start,end]=62,62ns 9999999.999999103 .. 10000000.000073228However, some estimates are seen which exclude 10MHz exactly:
Frequency estimate = 10000000.000039292 335940000414 counts in 33594.000041268s estimated error [start,end]=62,62ns 10000000.000002381 .. 10000000.000076204In infinite accumulation mode there doesn't seem to be any point to letting the program run for longer than a day or so, as you have to run 10x as long to get an additional digit of error to disappear.