I had a quick look at your circuit and notice that you are attempting to directly measure the time between the GNSS PPS and the next 10MHz OCXO rising clock edge using the TDC7200. Unfortunately this will not work as the TDC7200 has a minimum measurement interval of 12ns (according to the data sheet). In practise the time difference between the GNSS PPS and the next 10MHz clock edge will be in the range of 0ns through 100ns so you could be in trouble up to 12% of the time.My idea to circumvent the TDC7200's dead time is to exploit the 10 MHz signal's periodicity. When it doesn't measure 0 to 12 ns, it should measure 100 to 112 ns. I haven't read anything in the datasheet that indicates the TDC7200 would be negatively affected from a stop pulse within its dead time (other than not measuring that stop pulse, of course). The way I understand the data sheet, early stop pulses are simply ignored (https://www.ti.com/lit/ds/symlink/tdc7200.pdf#page=20):
7. After reaching the Clock Counter STOP Mask value, the STOP pin waits to receive a single or multiple STOPThinkfat also didn't have any issues with feeding continous 10 MHz stop pulses to the TDC. (https://www.eevblog.com/forum/projects/diy-gpsdo-project-w-stm32-tdc7200/msg4122208/#msg4122208) Even if the first stop pulse falls into the dead time and/or the first measurement is flawed, using the TDC's feature to measure multiple consecutive stop pulses could be used to filter out nonsensical measurement values.
trigger signal from the analog-front-end (for example, detected echo signal of the ultrasonic burst signal)
One method that I have personally used is to feed the GNSS PPS into a chain of D flip flops clocked by the 10MHz clock to create a synchroniser and use the TDC7200 to measure the difference between the GNSS PPS and the output of the synchroniser and then use this synchronised output with your microcontroller counter (assuming your microcontroller counter is also synchronous to the same 10MHz clock used by the TDC7200).Thanks for the suggestion. Using the TDC's CLOCK_CNTR_STOP_MASK to wait one 10 MHz clock period after the start pulse before processing stop pulses should have the same effect without relying on external circuitry. That's my plan B if above plan A fails.
I remember I shared a little circuit with a 74HC74 to delay the stop pulse by 100ns (one 10MHz clock cycle). That circumvents the problem with the dead time in mode 1. Also, if you search the time-nuts archive, you'll find a similar design by member Tobias Pluess. He shared his full schematics on the mailing list as well. It is sure worth a look.
PS: I'd add an air pressure sensor as well. It is something I missed in my design, the next revision will have it.
I did have some issues with the TDC7200 which might have been due to the STOP signal coming too quickly after the START signal. The topic came up in this thread but could not be resolved satisfactorily. However, in praxi I have not seen any issues with sending STOP without a START before, according to the data sheet, the TDC7200 should be fine with this.
[...]
Feeding the 10MHz into a counter and directly to the TDC7200 works, but it is better to delay the 10MHz STOP signal edge with regards to the 1PPS pulse by e.g. a flip-flop because the TDC7200 cannot measure down to 0ns.
..I would do it..
As there's obivously some doubt about my approach, should I first verify that the TDC7200 works the way I intend to use it before having the GNSSDO manufactured?
I do not want to add a synchronizer, unless there's a absolutely solid technical reason for it.
Originally, I wanted to measure and average 5 stop pulses,
I just curious how You measure the performance of GPSDO in Your application.We conducted an automotive measurement with the Jackson Labs LC_XO GPSDOs (part of USRP X310) about 3 years ago. Boils down to packing them in a car, using separate antennas for each one (preferably with different orientation) to maximize the difference between each signal, and directly comparing the output signals of each GPSDO while driving. Results are published behind an IEEE paywall, sorry (https://ieeexplore.ieee.org/document/9234915). Measuring between different vehicles is difficult. We only did that in a stationary scenario between buildings. Doing it on the move is complex and error prone as it requires a radio link with ideal line-of-sight conditions between participating nodes. While probably feasible, not worth the effort.
You can leave any delay circuit if you are sure your time difference is always >= 12 ns all of the time.It isn't. But time differences 0 < t < 12 ns should just wrap to 100 < t < 112 ns due to the periodic 10 MHz stop signal.
I wouldn't do so.
I wonder how five stop pulses could improve your results. Pulsdifferences 2 - 5 should be exactly 100 ns.Averaging to reduce noise. No idea whether that would have a positive effect with the TDC7200.
TDC7200 compared to AS6500 is very noisy.Thanks for the hint with the AS6500. I think I have looked at it before, because I distinctly recall the color scheme of the ScioSense website, but I must have discarded it because the QFN package scared me off or sth.
There's quite a nice set of experiments documented here:That's an informative paper, indeed. I already cited it in my initial post ;)
https://hamsci.org/sites/default/files/publications/2020_TAPR_DCC/N8UR_GPS_Evaluation_August2020.pdf
Also there is a possibility to "misuse" mode 2 if you are measuring phase offset to a clock edge. You just discard the "TIME2" measure, and only count clocks and use the TIME1 fractional measure. This avoids the additive noise which comes from summing what is effectively 2 measures. This is how I did it in my TDC7200 based GNSSDO.Haven't thought of that. That's a nice idea.
What's the advantage of delaying the stop pulse compared to relying on the 10 MHz stop signal's periodicity as I intend to do it? Values within the dead time (0 to 12 ns) should simply map to 100 to 112 ns. Do you see any reason that's not gonna work? The systematic error should be non-measurable, as the difference between sucessive 10 MHz edges is given by the phase noise floor (offset >1 MHz).
I have two reasons against using a synchronizer based on 74-type logic:
- Temperature dependency: The 74HC74 adds too much uncertainty in terms of potential temperature dependence of propagation delay (https://assets.nexperia.com/documents/data-sheet/74HC_HCT74.pdf#page=6) (14/~40 ns typ./max. @ 5V). Even the high-speed CMOS variant 74AHC74 (https://assets.nexperia.com/documents/data-sheet/74AHC_AHCT74.pdf#page=6), which I would recommend over the regular 74HC74, still fields a propagation delay of typically 3.7 ns at 25°C and min/max from 1.0 to 8.5 ns over the -40°C to +85°C temperature range. That's simply too much uncertainty (i.e., potential temperature dependence) between the edge measured by the TDC7200 and the edge output on the SMA connectors. The LMK1C1103 I'm using as a fanout buffer has max. 50 ps skew between channels (https://www.ti.com/lit/ds/symlink/lmk1c1103.pdf#page=6) over its entire -40°C to 125°C temperature range.
- Rise/fall times: The TDC7200 datasheet (https://www.ti.com/lit/ds/symlink/tdc7200.pdf#page=6) specifies the Maximum rise, fall time for START, STOP signals and Maximum rise, fall time for external CLOCK as nominally 1 ns. The 74HC74's transition time is substantially higher (7/19 ns typ./max.). Unfortunately, no rise time is given in Nexperia's 74AHC74 datasheet. The LMK1C110x specifies max. 0.7 ns rise/fall time.
PS: I'd add an air pressure sensor as well. It is something I missed in my design, the next revision will have it.
Do you also recommend that for OCXOs or just for your LPRO? AFAIK, OCXOs should be insenstive to air pressure due to hermetically sealed cases.
Regarding the AS6500 - it's an expensive part and probably not worth the expense.
Regarding the AS6500 - it's an expensive part and probably not worth the expense.
Did you ever use it?
I think: no.
Regarding delaying the stop signal, I really like your idea of using the mask function to ignore early stop events. I hadn't thought of that, I'll have to consider that.
Alright. Looks like you cannot use the stop mask in mode 1 because the clock counter is not running. That was what you intended to do, right?
; Timer 2 determines the length of each PWM output. The duty cycle of
; each pulse is dithered each interrupt. In software, the PWM value is
; held as 24 bits. The most significant 10 bits are loaded into the PWM
; duty cycle register. The least significant 14 bits of the 3 byte PWM
; value are repeatedly added to the value Dithr. If Dithr overflows to
; the 15th bit then the duty cycle is increased by 1 bit for the next
; PWM pulse.
BANKSEL PWM
MOVF PWM,W ; Add the least significant
ADDWF Dithr,F ; 14 bits of PWM to Dithr
MOVF PWM+1,W
ANDLW 0x3F ; truncate to top 6 of 14 bits
ADDWFC Dithr+1,W
MOVWF Dithr+1
ANDLW 0x40 ; isolate possible carry out
XORWF Dithr+1,F ; remove it from Dithr
ADDWF PWM+1,W ; now the most significant 10 bits
MOVWF PWM2DCL ; bits 0-5 ignored
CLRW
ADDWFC PWM+2,W
MOVWF PWM2DCH ; and store it as top 8 bits of next pulse width
This is followed by a passive filter, most OCXOs have a high input impedance, the filter is effectively 12kohm in series so usually provides better than 90% of the full voltage swing (which even old OCXOs don't seem to go near).Have you considered the noise contribution of the resistor? At 300 Kelvin it would generate 14 nV/√Hz of thermal noise, which is significantly above the output noise of the OPA189 I chose for active filtering my DAC output. While the OCXO control voltage inputs are high impedance, I'd assume their input noise cannot be modeled resistively. I haven't calculated/simulated which control voltage noise level is negligible compared to the OCXO's uncontrolled phase noise, but to err on the side of caution I picked a 100 Ohm resistor for my second, passive filter stage.
Thank you for the suggestion. I believe sth. similar could be realized using the STM32G4's (high-resolution) timers and DMA requests triggered automatically by roll-over events to synchronously update the compare registers. For the first iteration I wanted to keep the hardware straightforward, so I went for a low-noise 16-bit DAC. If more resolution becomes necessary, I can similarly use circular DMA with the DAC to implement PAM/PWM.This is followed by a passive filter, most OCXOs have a high input impedance, the filter is effectively 12kohm in series so usually provides better than 90% of the full voltage swing (which even old OCXOs don't seem to go near).Have you considered the noise contribution of the resistor? At 300 Kelvin it would generate 14 nV/√Hz of thermal noise, which is significantly above the output noise of the OPA189 I chose for active filtering my DAC output. While the OCXO control voltage inputs are high impedance, I'd assume their input noise cannot be modeled resistively. I haven't calculated/simulated which control voltage noise level is negligible compared to the OCXO's uncontrolled phase noise, but to err on the side of caution I picked a 100 Ohm resistor for my second, passive filter stage.
A noise density of 14 nV/√Hz wont't be your enemy, but the 20ns jitter from the gps.
The actual jitter is in the order of 500 ps rms vs. a Cesium (https://hamsci.org/sites/default/files/publications/2020_TAPR_DCC/N8UR_GPS_Evaluation_August2020.pdf#page=25).A noise density of 14 nV/√Hz wont't be your enemy, but the 20ns jitter from the gps.
Manufacturer's figure Time pulse jitter ±4 ns. I believe the internal TCXO is steered to be synchronous with the generated timing pulses. Any disciplining algorithm will average over a period of seconds, random jitter disappears.
I don't understand the need for such precise timing. If your vehicles are moving at 300kph, then in 1µs they move less than 0.1mm. That is a lot less than the accuracy of the GPS location. If your aim was value for money, don't bother with an OCXO, just use the output of the ZED-F9T directly. Messing with nanoseconds is messing with dollars.The relevant speed is not the speed of the vehicles, but the speed of light. While with RTK you can get positions down to a few cm or even mm, inaccurate time dilutes this position accuracy substantially (30 cm per 1 ns) wrt the radio wave, which is what I want to measure.
I didn't use 24 bits because I needed it. You could say it comes for free (2 instructions in the interrupt routine, 1 byte of memory). I committed to the idea of using dithering to eliminate the need for a DAC. Because the control voltage doesn't vary much, a long bit stream can be used - in this case 16M bits, generated at a 40MHz rate, so the pattern repeats more than twice a second. Granularity is no longer an issue, one less variable to deal with.What's your filter's cutoff frequency? With such a long period, don't you get some residual wobble at the filter output even with 1 Hz 3 dB cutoff?
Resistor noise is essentially negligible, indeed, unless you have an oscillator with a particularly large tuning range, you need only minimal filtering of the DAC output.
Using narrow band modulation theory, it is straightforward to calculate the phase noise attributable to Vc input noise. Conveniently in the narrow band regime, the infinite sum of Bessel J functions simplifies considerably and gives the following approximation.
Np(f) ~= 20 log10 ( S * Nv(f) / 2f ).
Where Np(f) is the phase noise at offset frequency f in dbc/√Hz, Nv(f) is voltage input noise at frequency f (V/√Hz) and S is tuning sensitivity (Hz/V).
As a worked example, I used a cheap AliX 10 MHz OCXO with pull range of +/-2 ppm over a 0-4V range, giving S=10 Hz/V. A 0.5 Hz square wave with 1 LSB p-p is used to modulate the OCXO. Considering the fundamental only for now, the amplitude is 0.5 LSB = 30 uV. This gives an expression:
20 log (10 * 3e-5 / 1) = -70 dbc/√Hz at 0.5 Hz offset.
Taking into account the harmonic sequence of the square wave, and frequency dependence of phase noise, it can be seen that the phase noise attributable to harmonics decreases at a rate of 40dB/decade. So, to estimate phase noise in the region of 10 Hz offset, we apply the factor of 40dB/decade, to reach a phase noise @ 10 Hz (approx) of -122 dbc/√Hz, which is far below the specified phase noise of many budget OCXOs - so you could reasonably omit filtering all together.
What's your filter's cutoff frequency? With such a long period, don't you get some residual wobble at the filter output even with 1 Hz 3 dB cutoff?I don't know if there is some misunderstanding about PWM and dithering. WatchfulEye talks about an 0.5Hz wave of 30µV. Not sure where that comes from. The basic PWM is 0V and 5V alternating at a 40kHz rate. The filter has to get rid of the 40kHz to produce an average voltage. The processor I work with has a 10-bit PWM so can produce a duration of the 0V (and consequently the 5V) that is varied to produce averaged voltages in steps of 5mV. Dithering is simply increasing the length of some pulses by 1 bit so some pulses are 25ns longer than others. If all the longer pulses were lumped at the start of the 16,000 pulses required to get a pseudo 24-bit then yes there would be 2.4Hz wave riding on top of the 40kHz wave, about 5mV p-p. But the method of dithering doesn't do that, it intersperses the longer and shorter pulses optimally to give the filter the least amount of work to do.
What's your filter's cutoff frequency? With such a long period, don't you get some residual wobble at the filter output even with 1 Hz 3 dB cutoff?I don't know if there is some misunderstanding about PWM and dithering. WatchfulEye talks about an 0.5Hz wave of 30µV. Not sure where that comes from.
..The PIC processors are reputed to have low jitter output, I have not tested the assertion..Double-check that as it could easily be the assertion is not valid for an internal clock made by its PLL..
As there's obivously some doubt about my approach, should I first verify that the TDC7200 works the way I intend to use it before having the GNSSDO manufactured?I would do it..
I will spin a breakout board for the TDC7200 and try out whether my approach works. See attached quick'n'dirty schematic. Using the (high-resolution) timers of the STM32G474 to dynamically generate start and stop signals should faciliate rapid generation of a large dataset to derive stochastically significant conclusions from.
for (uint32_t n = 0; n < config.iterations; n++) {
// start measurement
tdc_start_measurement(1);
// measured 92 TIM2 clock cycles (~541 ns) from start command to TRIGG
//while (!tdc_read_trigg()) {}
// configure TIM2 to generate rising edges in in ~1000 TIM2 clock cycles
uint32_t tim2 = TIM2->CNT;
TIM2->CCMR2 = 0x3030;
TIM2->CCR4 = tim2 + 1000;
TIM2->CCR3 = tim2 + 1000 + config.pulse_delay;
// wait for measurement to complete, then reset start and stop signals
while (tdc_read_intb()) {}
TIM2->CCMR2 = 0x4040;
// read measurement results from TDC7200
uint8_t int_status = tdc_read8(TDC_REG_INT_STATUS);
uint32_t time1 = tdc_read24(TDC_REG_TIME1);
uint32_t calib1 = tdc_read24(TDC_REG_CALIB1);
uint32_t calib2 = tdc_read24(TDC_REG_CALIB2);
tdc_clear_interrupts();
// write results to LPUART
len = sprintf((char *) buf, "0x%02x,%lu,%lu,%lu\n",
int_status, time1, calib1, calib2);
if (HAL_UART_Transmit(&hlpuart1, buf, len, 100) != HAL_OK)
Error_Handler();
}START STOP | TDC7200 MODE 1 TOF | VALID |
TIMER DELAY | AVERAGE +- STDEV | TOFs | ERRORS
------------|----------------------|--------|-------
-117.65 ns | nan +- nan ns | 0 | 10000
-88.24 ns | nan +- nan ns | 0 | 10000
-58.82 ns | nan +- nan ns | 0 | 10000
-29.41 ns | nan +- nan ns | 0 | 10000
-23.53 ns | nan +- nan ns | 0 | 10000
-17.65 ns | nan +- nan ns | 0 | 10000
-11.76 ns | nan +- nan ns | 0 | 10000
-5.88 ns | nan +- nan ns | 0 | 10000
0.00 ns | nan +- nan ns | 0 | 10000
5.88 ns | 6.71 +- 0.010 ns | 99833 | 167
11.76 ns | 12.54 +- 0.013 ns | 100000 | 0
17.65 ns | 18.47 +- 0.028 ns | 100000 | 0
23.53 ns | 24.31 +- 0.022 ns | 100000 | 0
29.41 ns | 30.14 +- 0.026 ns | 100000 | 0
35.29 ns | 36.05 +- 0.032 ns | 100000 | 0
41.18 ns | 41.92 +- 0.035 ns | 100000 | 0
47.06 ns | 47.77 +- 0.040 ns | 100000 | 0
52.94 ns | 53.68 +- 0.043 ns | 100000 | 0
58.82 ns | 59.57 +- 0.047 ns | 100000 | 0
70.59 ns | 71.34 +- 0.053 ns | 100000 | 0
82.35 ns | 83.08 +- 0.057 ns | 100000 | 0
94.12 ns | 94.84 +- 0.064 ns | 100000 | 0
105.88 ns | 106.59 +- 0.071 ns | 100000 | 0
117.65 ns | 118.37 +- 0.080 ns | 100000 | 0
147.06 ns | 147.86 +- 0.096 ns | 100000 | 0
176.47 ns | 177.31 +- 0.117 ns | 100000 | 0
205.88 ns | 206.68 +- 0.136 ns | 100000 | 0
235.29 ns | 236.14 +- 0.159 ns | 100000 | 0
264.71 ns | 265.53 +- 0.178 ns | 100000 | 0
294.12 ns | 294.94 +- 0.205 ns | 100000 | 0
352.94 ns | 353.80 +- 0.260 ns | 100000 | 0
411.76 ns | 412.64 +- 0.311 ns | 100000 | 0
470.59 ns | 471.39 +- 0.370 ns | 100000 | 0
529.41 ns | 530.29 +- 0.425 ns | 100000 | 0
588.24 ns | 589.12 +- 0.481 ns | 100000 | 0
647.06 ns | 647.95 +- 0.537 ns | 100000 | 0
705.88 ns | 706.81 +- 0.588 ns | 100000 | 0
764.71 ns | 765.66 +- 0.648 ns | 100000 | 0
823.53 ns | 824.37 +- 0.703 ns | 100000 | 0
882.35 ns | 883.24 +- 0.761 ns | 100000 | 0
941.18 ns | 942.11 +- 0.826 ns | 100000 | 0
1000.00 ns | 1000.94 +- 0.872 ns | 100000 | 0
1058.82 ns | 1059.80 +- 0.917 ns | 100000 | 0
1117.65 ns | 1118.67 +- 0.967 ns | 100000 | 0
1176.47 ns | 1177.35 +- 1.013 ns | 100000 | 0
Nice board! Btw, why your stddev is rising proportionally with the TOF?
Interestingly, my stdev is substantially higher than the one in Fig. 17. Unfortunately, the number of calibration 2 periods (https://www.ti.com/lit/ds/symlink/tdc7200.pdf#page=26) is not given for that figure. I used the default of 10 (added that info to my previous post).Nice board! Btw, why your stddev is rising proportionally with the TOF?Figure 17 in the TDC7200 data sheet might partly explain the increasing stddev. This is probably why they decided to put mode 2 in the TDC7200 for measuring longer TOF.
You may also just be observing the noise of the microcontroller 170MHz PLL ...The STM32G474 datasheet puts the PLL jitter at approx. +- 25 ps. Assuming that also applies to the MCU timers, the impact on the TDC measurement should be negligible. Also, since MCU and TDC effectively rely on the same clock, I wouldn't expect a quasi-linear dependency.
For a complete characterization, what results to you get if you send two 100ns spaced stop edges? This is your intended use case, right? The linearity of the region across the crossover point would be interesting to see.I'll have a look at that when I use the continuous 10 MHz stop signal. Examining the cross-over point in detail is a good idea. I'll put that on the list. The STM32G4x4's high-resolution timers (HRTIM) should prove useful for that.
It's worth mentioning that MCUs may demonstrate some jitter on their outputs, not just from their PLLs.Thanks for the hint. I already played around with the PLL and MCO configs, using 160 MHz as main PLL frequency, which is divided by 16 for the MCO pin that drives the TDC clock input, but the measured delay stdev didn't change substantially.
[...]
It you have the option to try to characterise the jitter of the MCU with various PLL configurations, it might prove informative.
* START, STOP, and CLOCK signals generated by STM32G474 (on Nucleo-G474RE board)
* TDC7200 mounted on Nucleo-G474RE via minimal breakout board
* STM32 PLL clock (160 MHz) generated from external 24 MHz crystal (via HSE input)
* TDC7200 CLOCK frequency is 10 MHz (160 MHz PLL clock divided by 16 and output on MCO pin)
* START and STOP generated by 32-bit timer (TIM2) running at 160 MHz, i.e., 6.25 ns resolution
* measured 100000 iterations for each combinations of configured TIM2 delay between start and stop signals (see file names; delay in multiples of TIM2 clock cycle)
delay(ns), gpio_ospeed, mu(ns), sigma(ns), num_errors, num_valid, num_3sigma, num_5sigma
12.50, 0, 12.81, 0.217, 0, 100000, 0, 0
12.50, 1, 13.30, 0.075, 0, 100000, 5746, 0
12.50, 2, 13.27, 0.054, 0, 100000, 5228, 1
12.50, 3, 13.26, 0.042, 0, 100000, 3660, 0
18.75, 0, 19.16, 0.208, 0, 100000, 0, 0
18.75, 1, 19.55, 0.054, 0, 100000, 3886, 29
18.75, 2, 19.55, 0.059, 0, 100000, 3491, 0
18.75, 3, 19.54, 0.045, 0, 100000, 3438, 0
50.00, 0, 50.37, 0.227, 0, 100000, 0, 0
50.00, 1, 50.72, 0.064, 0, 100000, 2821, 1
50.00, 2, 50.71, 0.055, 0, 100000, 1896, 0
50.00, 3, 50.71, 0.047, 0, 100000, 359, 0
150.00, 0, 150.47, 0.233, 0, 100000, 22, 0
150.00, 1, 150.77, 0.115, 0, 100000, 549, 0
150.00, 2, 150.75, 0.101, 0, 100000, 572, 0
150.00, 3, 150.78, 0.114, 0, 100000, 29, 0
250.00, 0, 250.40, 0.242, 0, 100000, 131, 0
250.00, 1, 250.74, 0.132, 0, 100000, 929, 3
250.00, 2, 250.73, 0.130, 0, 100000, 998, 0
250.00, 3, 250.81, 0.192, 0, 100000, 0, 0
500.00, 0, 500.16, 0.258, 0, 100000, 1793, 16
500.00, 1, 500.52, 0.240, 0, 100000, 2278, 16
500.00, 2, 500.49, 0.241, 0, 100000, 2287, 10
500.00, 3, 500.74, 0.426, 0, 100000, 0, 0
750.00, 0, 750.14, 0.398, 0, 100000, 1596, 11
750.00, 1, 750.48, 0.326, 0, 100000, 2480, 141
750.00, 2, 750.46, 0.324, 0, 100000, 2477, 126
750.00, 3, 750.82, 0.655, 0, 100000, 0, 0
2000.00, 0, 1999.79, 0.547, 0, 100000, 586, 162
2000.00, 1, 2000.11, 0.520, 0, 100000, 561, 196
2000.00, 2, 2000.09, 0.520, 0, 100000, 594, 190
2000.00, 3, 2000.36, 1.209, 0, 100000, 3406, 1