Teensy3.1 as a precision Frequency Standard

Status
Not open for further replies.

TelephoneBill

Well-known member
Here is some top level comment on an interesting experiment I have been working on... to create a precision Frequency Standard using Teensy3.1, derived from a radio time signal. If there is wider interest, then I can add more detail (program code etc) to this post.

Other posts have described/measured the accuracy of the Teensy3.1 clock rate (https://forum.pjrc.com/threads/28098-Crystal-pull-range-on-3-1?goto=newpost). It was noted that a register called OSC0_CR gives digital control of the internal chip capacitors that "fine tune" the 16 MHz crystal module to bring it very close to an exact value. For example, binary "0010" was measured (by JBeale) as giving -3ppm below 16.000000 MHz and "1100" as +10ppm above. It was also suggested that "dithering" between these two values could be used as a method to "home in" on an exact average value for the Teensy clock rate. It was this suggestion that has prompted my current experiment.

It should be appreciated that there is no temperature control of the crystal module, and that internal heating or heat from surround components will inevitably cause the crystal to drift away from an exact value - even if a measurement could be found for the required amount of dither at a specific moment. But if this dither could be adjusted in real time to compensate for the drift, then it might be possible to create a precision Frequency Standard that would track any changes to give a reference signal accurate in the short term to better than 1 Hz - and longer term to even better than that.

A key factor in convincing myself that this could be done was in realising that the frequency of the USA and UK radio time signals (WWVB and MSF respectively) transmitted on 60 KHz was an exact sub multiple of 120 MHz. And that Teensy's default clock rate of 96 MHz could be increased reliably to 120 MHz and above (many have shown up to 144 MHz "overclocking"). The overclocked internal clock rate is generated by the chip itself using a phase locked loop from the base 16 MHz. A six times PLL gives the default 96 MHz. You enable these higher overclock values by "uncommenting" statements in the "boards.txt" file (part of the Teensyduino/Arduino environment). 120 MHz is one of the available values.

By overclocking to 120 MHz, this then provides an internal Peripheral Clock Rate of 60 MHz (cpu rate divided by two). And you can divide this Peripheral Rate by 1000 inside a Flex Timer (e.g. FTM1) to generate a very clean and precise 60 KHz square wave. This in turn can be compared against the carrier wave from the transmitted radio time signal, such as to synchronise these two together in a phase locked loop (PLL). The control for this PLL would be to programmatically adjust the dither rate of OSC0_CR to maintain the phase lock.

Once you have synchronised the Teensy clock rate to the radio time signal, then that Peripheral Clock Rate is a precision 60 MHz. So you can then use a second FTM Flex Timer to create a whole variety of precise frequencies. For example, divide by 6 gives a precision 10 MHz, or divide by 60 gives a precise 1 MHz.

My experiment has demonstrated that this actually works. I used a simple 3 inch ferrite loopstick to pick up the UK MSF radio transmission on 60 KHz. This was first amplified by a simple JFET using a 1K load resistor, and then amplified again by two stages of a TL082 (dual op amp chip). I then filtered this by a mini 60 KHz crystal in the input stage of a INA126 instrumentation op amp. That crystal had to be carefully selected from a bunch of ten bought off eBay in order to find one that was a very close match in frequency to 60.000 KHz.

The bandwidth of that last instrumentation op amp stage was very narrow (less than 3 Hz) in order to do two jobs: (i) To remove extraneous signal noise cause by local switched mode power supplies (computers, LED lights etc), and (ii) To overcome the interruptions in the UK MSF signal - the 1 Second data is encoded as on/off carrier modulation (up to 50/50 mark/space on the minute). The output from the INA126 is a beautiful 60 KHz sine wave of about 1 volt amplitude peak to peak. It does have some amplitude variations (due to the interrupted carrier), but the narrow bandwidth of the mini 60 KHz crystal "rings on" for a number of milliSeconds after carrier switch off, such as to decay exponentially, but never reaches zero amplitude. In the USA, the WWVB signal is not interrupted, so should be an even better sine wave.

The 60 KHz output signal from the radio receiver was then fed to the Teensy3.1 breadboard via a simple twisted pair length of wire, about 3 feet long (chopped from one of the twisted pairs inside a standard ethernet distribution cable). You need to provide some isolation distance between the radio receiver and Teensy, or else the 60 KHz sq wave electric radiation from Teensy will be picked up directly and swamp the radio. I ought to use some degree of electrostatic shielding around the ferrite loop, but found it worked fine without it in practice.

On board the Teensy breadboard, the radio signal was first fed to a "digital inverter" input (one sixth part of 74HCT14B chip), biased into a "linear analogue amp" by a 100K resistor from output back to input. This "squared up" the signal from sine wave to a fast edged square wave, which was then fed as a digital input into Teensy Pin 17 (CH1 for FTM1).

FlexTimer FTM1 was the Teensy 60 KHz sq wave timer, used in "output Toggle mode". A "modulus value" of 499 then provided a counter (0 to 499 = divide 60 MHz by 500) for each "half-cycle", giving a divide by 1000 factor overall for a complete cycle. The rising edge of the Radio 60 KHz signal (FTM1 Channel1) would then trap the instantaneous value of the counter at some point midway between 0 and 499. By reading this CH1 count value in the Teensy main program loop, I could then decide to adjust the "dither rate" of OSC0_CR such as to always push this CH1 value back towards the midpoint of 250. If the count was above 250, then slow down the 16 MHz clock, and if below it then speed it up. In this way, a control loop was organised to adjust the dither rate accordingly and consequently nudge the 60 MHz peripheral clock rate to "lock" the cycles of the FTM1 counter to that of the incoming radio 60 KHz sq wave on pin 17.

The method of controlling the dither rate was another simple piece of code in the Teensy program. I created a program global variable (FreqCounter) to act as a "half cycle counter" for the cycling of the output from FTM1. This would increment inside an "Interrupt Service Routine", called whenever the FTM1 toggled on "overflow" of the modulus. On reaching a value of 1000, this would be reset automatically (in code) to zero - and start incrementing again. For a number of cycles while this count was less than, say, 500, then the value of the OSC0_CR register would be set to "0010" (-3ppm). Once the global counter "FreqCounter" had exceeded that value, then the OSC0_CR register would be set to "1100" (+10 ppm). In this way, part of the time for 1000 toggles of FMT1, the OSC0_CR would have one value - and the rest of the time for 1000 cycles it would have another value. Thus the overall frequency of the 16 MHz external crystal module could be adjusted up and down, and consequently, the Peripheral Clock Rate derived from it would vary in sympathy. All that was needed was to change the static value (in my code its called "FreqAdjust") and this would "fine tune" the dither rate.

Now here is an interesting point. If the Peripheral Clock Rate at 60 MHz is locked as a multiple of the precise radio 60 KHz frequency, then the cpu clock rate of 120 MHz will also be locked to it. This means that Teensy is now locked in time to UTC (Universal Co-ordinated Time) as defined by NIST to an accuracy of 1 part in 10e12 (or 2 parts in 10e12 by NPL in the UK).

Proof that this all works as described is shown in the picture attached below...

Phase Locked Loop.jpg

Light Blue Trace 2 is the Radio 60 KHz sine wave going into the twisted pair length of wire. The slight sine wave distortion is caused by the slew rate of INA126 (amplitude is above 1 volt peak to peak). Yellow Trace 1 is the Teensy generated 60 KHz sq wave output from FlexTimer FMT1. Purple Trace 3 is the radio signal square wave conversion from the 74HCT14B going in on Teensy pin 17. The Darker Blue Trace 4 is an independant 10 KHz signal derived from a GPS module in satellite lock.

This picture shows that all the traces for Teensy are now locked in time to UTC. With the Peripheral Clock Rate now accurately controlled, other output standard frequencies can be created. For example, FlexTimer FTM0 could be configured for 10 MHz whilst FTM2 was configured for 1 MHz. And any internal Teensy global variable that counted 120,000 FTM1 "overflows" (inside the ISR for example) would be an extremly accurate 1 Second timer. And 86,400 counts of such a variable would denote a very accurate 1 Day !!!
 
Last edited:
This sounds like a very interesting project. Radio amateurs should find it quite useful as an alternative to GPS-disciplined methods, and less expensive. Do you plan on making the code and schematic available? I'm interested in giving it a try in the US with WWVB.
 
This is a great project! Thanks for the writeup. I'm curious about the analog 60 kHz front end and the noise level. What does your crystal filter circuit look like? The scope trace looks very clean, how much instantaneous jitter do you see? I assume that in some cases, like local thunderstorms, etc. there must be a great deal of noise in the 60 kHz band.
 
Thanks for showing interest. I will not claim this to be optimum design - possibly can be improved with some expert guidance. But it does lock my Teensy3.1 to MSF in the UK.

Here is my radio receiver and its schematic:

SixtyKHzRadio.jpg
SixtyKHzReceiver003.jpg

And here is the sine to sq wave converter, providing output directly into Teensy Pin 17:
SineToSquareConverter.jpg

I'll send my code in another post to this thread shortly.

Looking at the receiver, the ferrite loopstick is a standard one for "Long Wave/Medium Wave" and I use just the LW coil (plain/black leads, red/blue leads unused). The two 1000pF caps tune this to 60 KHz, but its not particularly critical so I didn't bother with a trimmer. Anyone building this may have to adjust caps to suit. The real selectivity comes later in the "SuperQ" stage of IC3 (INA126). My ferrite is even shorter than normal at 3 inches (see photo). I am 180 Kilometres away from the UK Anthorn MSF transmitter on the eastern side of the Pennine Mountain range and just south of the city of Leeds (Anthorn is on the Western side).

The two stages, IC1A and IC1B simply boost the signal level to around 100 - 200 milliVolts. You may be surprised that there is no direct coupling from stage IC1B to IC3 - it uses only the stray capacitance between two "six hole" rows on the breadboard (spaced 2 holes apart - see photo) plus two short vertical wire lengths (around 1 inch long - again see photo). My estimate is that the stray capacitance is somewhere from 5pF to 10pF (maybe slightly more). It did work with 1 hole spacing on the breadboard (no vertical wires), but that seemed to overload IC3.

I call IC3 a "SuperQ" filter because it uses the natural frequency of the "mini 60 KHz crystal" (see photo, a little metal cylinder just above the orange trimmer). This crystal has to be selected by testing the accuracy to 60.000 KHz. It needs to be within a few Hz because the bandwidth of the filter is only about 3 Hz altogether. The 0 to 60 pF trimmer offsets the internal C of the crystal to "peak" the output from IC3 at 60.000 Khz. I actually found the trimmer has to be at minimum, but I could not omit it.

Be careful with buying a bunch of 60 KHz crystals on eBay for "testing". I bought 50 and none were close enough - they had obviously been "picked over" during manufacture, and the good ones taken out. I did buy 10 direct from a Hong Kong manufacturer and 2 were excellent, 3 were fairly close.

There is very little "loading" placed on this "mini 60 KHz" crystal, and this is the reason why no direct coupling from IC1B. The MSF transmitter uses on/off modulation in the UK, and this is the true benefit obtained from the "SuperQ" stage - it keeps on "ringing" at the crystal natural frequency (exponential decay) and bridges the gap caused by the "off" modulation. This will not be perfectly correct, but the "frequency forcing" (which is perfect frequency) during the "on" carrier stage more than compensates, and a little tuning from the "vertical wires" seem to bring the crystal frequency back to near the perfect value during the decay period.

Here are two pictures of the "modulation gap" issue with MSF in the UK (you should not experience this with WWVB). Notice how the amplitude varies every second depending on the coded data. (These pictures were taken without the "tuning" from the vertical wires):

Start of "minute" decay period:
Freq Check Start of Minute Decay.jpg

End of "minute" decay period:
Freq Check End of Minute Decay.jpg

The blue trace in the above two pictures is 10 KHz from an OCXO (carefully adjusted to GPS timing) for comparison. You can see that the "natural frequency" of the crystal does drift some phase away from perfect (even though its frequency has been chosen to be close). The pictures are the worst case at minute intervals, but this suggests that the best strategy is to sample the receiver output just before the carrier switches off. That way, the phase will have been forced to "very near (if not) perfect" by the driving force of the carrier. I do this in my code... I only check the Teensy 60 KHz against the Radio 60 KHz once per second, and I nudge the selection point to minimse the amount of jitter experienced. This should position my selection point to be in the carrier "build up" portions and away from the "decay portions".

The sine wave that comes out of IC3 is a beauty to behold, providing that I don't over drive the crystal and the amp itself (keep the amplitude to around 1 to 1.5 volts peak to peak). It is a consequence of using a crystal with very high Q, and it shows the selectivity of the SuperQ stage is exactly what is needed to remove all the atmospheric noise in the 60 KHz band. I use a gain resistor (IC3 pins 1,8) of 68K so that the final amplitude is large enough into the sine/square converter.

Be careful NOT TO OVERDRIVE these mini 60 KHz crystals... they can only stand a small amount of applied power. I learnt the hard way and lost my best one. Also, the sine wave distorts as the amplitude increases. This I think is the effect of a slow slew rate with the INA126. It is cheaper but lower gain-bandwidth than some of the other instrumentation op amps.

I had a little tinkering to get a good sine/square converter working. Maybe someone can improve on my design. The issue is getting snappy switching of the square wave without two much hysteresis in the input on/off comparator levels. I also found that the 100K to ground on the input helped make the output mark/space much more evenly timed.

This is a fascinating experiment to play with. The end result is not quite GPS quality and my design ended up with about 200 nanoSecs jittery wandering. But it does have the advantage of portability by not needing a clear view of the overhead sky. I only use a crude control circuit for phase lock, so perhaps some improvements there could reduce that 200 nS.
 

Attachments

  • SixtyKHzReceiver003.jpg
    SixtyKHzReceiver003.jpg
    99.5 KB · Views: 392
Last edited:
Here is my code:

//Teensy31 - 60 KHz Oscillator (SixtyKHz007)
//==========================================
//Date: 21 JUL 2016 - Experiments with Flextimer 1 (FTM1) using Peripheral Clock (60 MHz)
//Uses COM4 as serial port. CPU Overclock Speed = 120 MHz.
//120 MHz CPU speed = 60 MHz Peripheral Clock speed. Divide this by 1000 gives 60 KHz. So each FTM1 half cycle count is 500 (0 - 499).
//FTM1 output set to toggle, so need two FTM1 cycles for every 60 KHz output cycle - Teensy Pin 3.
//Program will phase lock the 60 KHz from FTM1 to MSF via sine to sq wave converter (4046 amplifier) - input Teensy Pin 17.
//Program allows user to modify the CPU speed using digital capacitor values. Keybd cmds q = Raise Freq, a = Lower Freq, h = 5q, g = 5a.
//f123 will set modulus of FTM0 to value 123 (change frequency generated from 60 MHz peripheral clock). Output on Teensy Pin 9.
//For each FTM1 timer interrupt, FreqCounter is incremented - wraps around from 1000 back to 0. If FreqCounter >= Freq Adjust
//then digi cap = 0x02 (slower) else 0x0C (faster). Higher Freq Adjust value means more time at faster rate than slower.
//FTM1Ch1CountValue is a measure of PLL lock point (value = 250 mid way in half cycle). This is averaged over eight readings.
//So duty cycle for digital capacitor is varied from 0 to 100% altering digital capacitor between capacitor values.
//Need to connect 60 KHz output from Teensy Pin 3 to Teensy Pin 21 (D1) to detect polarity of output used for PLL point.
//Teensy Pin 22 is used as a 1 Second digital output signal. The phase of Pin 22 can be varied with n012345 (max is n119997).

//Note - Adjust using n000000 to n119000 to get OneSec pulse on pin 22 aligned just before GPS 1PPS. This is the optimum position
//for stable MSF reference signal. This minimises frequency wobble of SuperQ filter crystal.

//Note - FTM0 can be set to generate any frequency with integer division of Peripheral Clock rate (nominal = 1 MHz 50% duty cycle).

//declarations
#include <stdint.h>
#define LED_ON GPIOC_PSOR=(1<<5)
#define LED_OFF GPIOC_PCOR=(1<<5)
const float pi = 3.14;
const unsigned int OneSecLoDiff = 1;
bool ControlOn, FineControlOn, D1, D3;
int Byte1, Byte2, Byte3, Byte4, Byte5, Byte6, Byte7; // for Keyboard input via incoming serial data
volatile unsigned int MODValue, MOD0Value, DeltaMODValue, FTM0CountOVFlow=0, FTM1CountOVFlow=0;
volatile unsigned int FreqCounter, FreqAdjust, FreqNominal, FreqMax;
volatile unsigned int OneSecCount, OneSecMaxVal, OneSecHiVal, OneSecLoVal, Seconds;
volatile unsigned int Counter, PhaseCount, LastPhaseCount, LoopCount = 0;
volatile unsigned int dCh1CountValue, FTM1Ch1CountValue, FTM1Ch1[8], FTM1Ch1CV[8], dCh1OldCountValue;
volatile int dCh1Differential, FreqAverage;
unsigned int dLoopCount, dFTM1CountOVFlow, dFreqAdjust, dLastPhaseCount, dOneSecCount, dOneSecHiVal, dControlOn, dFineControlOn, dD1, dD3, dCounter;

void setup() {
//initialise general variables
FreqNominal = 450; //starting value of FreqAdjust change point for digital capacitors
FreqAdjust = FreqNominal; //set adjustment in freq adjustment scale to starting value
FreqMax = 1000; //max value, wraps around to zero on overflow
OneSecMaxVal = 120000; //set OneSec count maximum value (number of 60 KHz half cycles for 1 Sec)
OneSecHiVal = 0; //set OneSec high value starting value (pin22 goes high)
OneSecLoVal = OneSecHiVal + OneSecLoDiff; //set OneSec low starting value (pin 22 goes low)
if (OneSecLoVal>=120000) { //ensure OneSec low value is in range 0 to 119,999
OneSecLoVal = OneSecLoVal - 120000;
}

//initialise general hardware
PORTC_PCR5 = PORT_PCR_MUX(0x1); //LED PC5 pin 13, config GPIO alt = 1
GPIOC_PDDR = (1<<5); //make this an output pin
LED_OFF; //start with LED off
pinMode(21, INPUT); //pin 21 as digital input
pinMode(22, OUTPUT); //pin 22 as digital output
pinMode(23, INPUT); //pin 23 as digital input
digitalWriteFast(22, 0); //set pin 22 low
Serial.begin(115200); //setup serial port
OSC0_CR = 0x02; //starting value for digital capacitors

//initialise Flextimer 1
MODValue = 499; //modulus = 499 (gives count = 500 on roll-over each half cycle = 60 KHz)
FTM1_MODE = 0x05; //set write-protect disable (WPDIS) bit to modify other registers
FTM1_SC = 0x00; //set status/control to zero = disabled (enabled in main loop)
FTM1_CNT = 0x0000; //reset count to zero
FTM1_MOD = MODValue; //set modulus value
FTM1_C0SC = 0x14; // CHF=0, CHIE=0 (disable interrupt, use software polling), MSB=0 MSA=1, ELSB=0 ELSA=1 (output compare - toggle), 0, DMA=0
FTM1_C1SC = 0x04; // CHF=0, CHIE=0 (disable interrupt, use software polling), MSB=0 MSA=0, ELSB=0 ELSA=1 (input capture - rising edge), 0, DMA=0

//enable FTM1 interrupt within NVIC table
NVIC_ENABLE_IRQ(IRQ_FTM1);

//configure Teensy FTM1 output compare channels
PORTA_PCR12 |= 0x300; //MUX = alternative function 3 on Chip Pin 28 (FTM1_CH0) = Teensy Pin 3
//(Note - This makes TEENSY Pin 3 the output square wave signal)

//configure Teensy input compare channels
PORTB_PCR1 |= 0x300; //MUX = alternative function 3 on Chip Pin 36 (FTM1_CH1) = Teensy Pin 17
//(Note - This makes TEENSY Pin 17 an input signal to capture the value of the FTM1 Timer on a rising edge)

//enable system clock (60 MHz), no prescale
FTM1_C0V = 0; //compare value = 0 for CH0
FTM1_SC = 0x48; // (Note - FTM1_SC [TOF=0 TOIE=1 CPWMS=0 CLKS=01 (System Clock 60 MHz) PS=000 [no prescale divide])

//initialise Flextimer 0
MOD0Value = 29; //modulus = 29 (gives count = 30 on roll-over each half cycle = divide by 60 = 1 MHz). Peripheral clock = 60 MHz.
FTM0_MODE = 0x05; //set write-protect disable (WPDIS) bit to modify other registers
FTM0_SC = 0x00; //set status/control to zero = disabled (enabled in main loop)
FTM0_CNT = 0x0000; //reset count to zero
FTM0_MOD = MOD0Value; //set modulus value
FTM0_C2SC = 0x14; // CHF=0, CHIE=0 (disable interrupt, use software polling), MSB=0 MSA=1, ELSB=0 ELSA=1 (output compare - toggle), 0, DMA=0

//configure Teensy FTM0 output compare channels
PORTC_PCR3 |= 0x400; //MUX = alternative function 4 on Chip Pin 46 (FTM0_CH2) = Teensy Pin 9
//(Note - This makes TEENSY Pin 3 the output square wave signal

//enable system clock (60 MHz), no prescale
FTM0_C2V = 0; //compare value = 0 for CH2
FTM0_SC = 0x08; // (Note - FTM1_SC [TOF=0 TOIE=0 CPWMS=0 CLKS=01 (System Clock 60 MHz) PS=000 [no prescale divide])

//blink 4 times
Blink();
delay(400);
Blink();
delay(400);
Blink();
delay(400);
Blink();
delay(400);

//initialise counters and controls
FTM1CountOVFlow = 0;
FreqCounter = 0;
LoopCount = 0;
OneSecCount = 0; //used to determine when one second has passed (120,000 half cycles of 60 KHz)
Counter = 0; //general counter - available for use in freq control
ControlOn = true; //if ControlOn then output is synch'd to MSF
FineControlOn = false; //if FineControlOn then take average of last 8 FTM1Ch1CountValue readings
} //end of setup

// ISR routine for FlexTimer1
extern "C" void ftm1_isr(void) {
//ISR latency time = 300 nanoSecs after interrupt flag set by FTM1 Timer Overflow
//This ISR is called on every half cycle - FTM1 toggles the output so ISR called twice for every 60KHz cycle
if ((FTM1_SC & FTM_SC_TOF) != 0) { //read the timer overflow flag (TOF in FTM1_SC)
FTM1_SC &= ~FTM_SC_TOF; //if set, clear overflow flag
}
FTM1CountOVFlow++; //increment overflow counter (shows total number of interrupts)
//adjust peripheral clock freq via digital capacitor if reached adjustment point
if ((FreqCounter>=FreqAdjust)) { //HalfCycle counter reached point to change digital capacitor
OSC0_CR = 0x02; //slower digital capacitor setting (0x02 = -3.109)
}
else {
OSC0_CR = 0x0C; //faster digital capacitor setting (0x0C = +10.422)
}
FreqCounter++; //increment half cycle counter
if (FreqCounter>=FreqMax) { //HalfCycle counter reached max value
FreqCounter = 0; //reset HalfCycle counter
}
//ISR time = +1 microSec at this point in code

//read the input captured value of FTM1 CH1 (this is captured on a rising edge of pin 17)
if ((FTM1_C1SC&0x80) != 0) { //look for CH1 event flag
FTM1Ch1[7] = FTM1Ch1[6]; //store last 8 edge readings - main loop uses average
FTM1Ch1[6] = FTM1Ch1[5];
FTM1Ch1[5] = FTM1Ch1[4];
FTM1Ch1[4] = FTM1Ch1[3];
FTM1Ch1[3] = FTM1Ch1[2];
FTM1Ch1[2] = FTM1Ch1[1];
FTM1Ch1[1] = FTM1Ch1[0];
FTM1Ch1[0] = FTM1_C1V; //read current CH1 value
FTM1_C1SC &= ~0x80; //clear CH1 event flag
}

//adjust ONE SECOND pulse
OneSecCount++;
if (OneSecCount>=OneSecMaxVal) { //OneSec counter reached max value
OneSecCount = 0; //reset OneSec counter
}
if (OneSecCount==OneSecHiVal) { //OneSec counter reached pulse ON phase
digitalWriteFast(22, 1); //set pin 22 high
}
if (OneSecCount==OneSecLoVal) { //OneSec counter reached pulse OFF phase
digitalWriteFast(22, 0); //set pin 22 low
}
//ISR time = +1.5 microSec at this point in code

}


void loop() {
//read key input (if any)
KeyInput(); //read key input (if any)
LoopCount++; //keep tally of loop counts
if (OneSecCount==OneSecHiVal) { //check for 1 second interval to slow display of data
CopyData(); //freeze a copy in case data changes before being displayed
DisplayData(); //display data in serial monitor
if (ControlOn) {
CalcAverageCV(); //calculate average value of FTM1Ch1CountValue
FreqControl(); //if control cactive then call freq control routine
}
}
} //end of main loop


//SUBROUTINES
//===========

//Blink Routine
void Blink() {
//blink the LED
LED_ON;
delay(10);
LED_OFF;
delay(10);
}

//Calculate Average CH1 Count Value Routine
void CalcAverageCV() {
//Calculate average FTM1Ch1CountValue
FTM1Ch1CountValue = (FTM1Ch1[0] + FTM1Ch1[1] + FTM1Ch1[2] + FTM1Ch1[3] + FTM1Ch1[4] + FTM1Ch1[5] + FTM1Ch1[6] + FTM1Ch1[7])/8;
}

void FreqControl() {
//FreqControl Steps
//=================
//adjust frequency - positive Ch1Differential means going too fast
if (dCh1Differential>0) {
FreqAdjust--;
}
if (dCh1Differential<0) {
FreqAdjust++;
}

//adjust phase - nudge towards 250 value
if ((dCh1CountValue>250)&&(dCh1Differential>-2)&&(dCh1Differential<2)) {
FreqAdjust--;
}
if ((dCh1CountValue<250)&&(dCh1Differential>-2)&&(dCh1Differential<2)) {
FreqAdjust++;
}
}

//Copy Data Routine
void CopyData() {
D1 = digitalReadFast(21);
Seconds++;
dLoopCount = LoopCount;
dFTM1CountOVFlow = FTM1CountOVFlow;
dCh1OldCountValue = dCh1CountValue;
dCh1CountValue = FTM1Ch1CountValue;
dCh1Differential = dCh1CountValue - dCh1OldCountValue;
dFreqAdjust = FreqAdjust;
dLastPhaseCount = LastPhaseCount;
dOneSecCount = OneSecCount;
dOneSecHiVal = OneSecHiVal;
dControlOn = ControlOn;
dFineControlOn = FineControlOn;
dD1 = D1;
dD3 = D3;
dCounter = Counter;
}

//Display Routine
void DisplayData() {
//print to serial monitor
Serial.print("Seconds = ");
Serial.print(Seconds);
Serial.print(", FreqAdjust = ");
Serial.println(dFreqAdjust);
Serial.print("LoopCount = ");
Serial.print(dLoopCount);
Serial.print(", FTM1CountOVFlow = ");
Serial.println(dFTM1CountOVFlow);
Serial.print("Ch1CountValue = ");
Serial.print(dCh1CountValue);
Serial.print(", Ch1Diff = ");
Serial.println(dCh1Differential);
Serial.print("OneSecCount = ");
Serial.print(dOneSecCount);
Serial.print(", OneSecHiVal = ");
Serial.println(dOneSecHiVal);
Serial.print("ControlOn = ");
Serial.print(dControlOn);
Serial.print(", FineControlOn = ");
Serial.println(dFineControlOn);
Serial.print("D1 = ");
Serial.print(dD1);
Serial.print(", D3 = ");
Serial.println(dD3);
Serial.println(" ");
Blink();
}

//KeyInput Routine
void KeyInput() {
//blink the LED
if (Serial.available() > 0) {
//read the incoming byte
Byte1 = Serial.read();
if (Byte1>0x60) {
switch (Byte1) {
case 0x71: //q - Increase Frequency
//task goes here...
FreqAdjust++;
if (FreqAdjust>FreqMax) {
FreqAdjust = FreqMax;
}
Blink();
break;
case 0x61: //a - Decrease Frequency
//task goes here...
FreqAdjust--;
if (FreqAdjust<0) {
FreqAdjust = 0;
}
Blink();
break;
case 0x63: //c - Toggle Control
//task goes here...
if (ControlOn) {
ControlOn = false;
}
else {
ControlOn = true;
Blink();
delay(100);
}
Blink();
break;
case 0x7A: //z - Set Nominal FreqAdjust Value
//task goes here...
FreqAdjust = FreqNominal;
Blink();
break;
case 0x70: //p1234 - set FreqAdjust = 1234
//task goes here...
Byte2 = Serial.read();
Byte3 = Serial.read();
Byte4 = Serial.read();
FreqAdjust = ((Byte2-0x30) * 100) + ((Byte3-0x30) * 10) + ((Byte4-0x30));
Blink();
break;
case 0x6E: //n123456
//task goes here...
Byte2 = Serial.read();
Byte3 = Serial.read();
Byte4 = Serial.read();
Byte5 = Serial.read();
Byte6 = Serial.read();
Byte7 = Serial.read();
OneSecHiVal = ((Byte2-0x30) * 100000) + ((Byte3-0x30) * 10000) + ((Byte4-0x30) * 1000) + ((Byte5-0x30) * 100) + ((Byte6-0x30) * 10) + ((Byte7-0x30) * 1);
OneSecLoVal = OneSecHiVal + OneSecLoDiff;
if (OneSecLoVal>=120000) { //ensure CycleCount low value is in range 0 to 119,999
OneSecLoVal = OneSecLoVal - 120000;
}
Blink();
break;
case 0x66: //f123
//task goes here...
Blink();
Byte2 = Serial.read();
Byte3 = Serial.read();
Byte4 = Serial.read();
FTM0_MODE = 0x05; //set write-protect disable (WPDIS) bit to modify other registers
FTM0_SC = 0x00; //set status/control to zero = disabled (enabled in main loop)
MOD0Value = ((Byte2-0x30) * 100) + ((Byte3-0x30) * 10) + (Byte4-0x30);
FTM0_MOD = MOD0Value;
FTM0_SC = 0x08; // (Note - FTM1_SC [TOF=0 TOIE=0 CPWMS=0 CLKS=01 (System Clock 60 MHz) PS=000 [no prescale divide])
break;
case 0x64: //d
//task goes here...
if (D3) {
D3 = false;
}
else {
D3 = true;
}
break;
case 0x74: //t
//task goes here...
FTM1Ch1CV[7] = 250;
FTM1Ch1CV[6] = 250;
FTM1Ch1CV[5] = 250;
FTM1Ch1CV[4] = 250;
FTM1Ch1CV[3] = 250;
FTM1Ch1CV[2] = 250;
FTM1Ch1CV[1] = 250;
FTM1Ch1CV[0] = 250;
if (FineControlOn) {
FineControlOn = false;
}
else {
FineControlOn = true;
Blink();
delay(100);
}
Blink();
break;
case 0x67: //g
//task goes here...
FreqAdjust--;
FreqAdjust--;
FreqAdjust--;
FreqAdjust--;
FreqAdjust--;
if (FreqAdjust<0) {
FreqAdjust = 0;
}
Blink();
break;
case 0x68: //h
//task goes here...
FreqAdjust++;
FreqAdjust++;
FreqAdjust++;
FreqAdjust++;
FreqAdjust++;
if (FreqAdjust>FreqMax) {
FreqAdjust = FreqMax;
}
Blink();
break;
} //end of switch statement
} //end of Byte1 > 0x60
} //end of if Serial Available
}
 
Last edited:
This is amazing work and belongs in the showcase! Great job documenting what you did, how it works, and what it achieved.
 
Thanks for sharing, I learned a lot! Do you have data of the control loop in action, like a plot or list of the control register values over time?
 
The data I can collect of the control loop action would not really tell you much. In response to your query, I went and had a look at the Teensy/Arduino serial monitor where I display each "seconds" incremental value of FreqAdjust. But what I see is not very informative - and really depends on the crude way I perform this control (it only changes once each second). What I see (every second) looks like 380, 381, 380, 379, 379, 380, 382, 379, 380... ( these values relate to 0 = all one OSC0_CR cap value and 999 = all the other OSC0_CR cap value) and so on. I take your point on measuring the control loop performance and will look at this in more detail when I have a better design. It requires further experimenting to get something worth measuring.

If you look at the code (above) you can see that I average the current error with three previous values (at -1 sec, -2 sec, -3 sec) in order to reduce the jitter, and because the time constant is each second to make a new measurement, then this stabilises the control loop down to a minor tweak. Even more averaging might actually improve things - I shall have to test it.

I took a short video of the scope display today which demonstrates the relationship between the Radio 60 KHz sine wave and the Teensy 60 KHz square wave. It is quite interesting to see how "calm/stable" the square wave is compared to the phase wobbles of the Radio 60 KHz. These wobbles are caused by the fact that the mini 60 KHz is "free-running" (making a slight phase error) in the decay periods, and the periods of "frequency forcing" from MSF have to "catch up" that phase error to give zero shift overall (or else there would not be lock). It looks like a small amount of frequency modulation on the display, with the sine wave waving back and forth each second. I use an OCXO to trigger the scope so that there is no bias in the outcome (and compare against GPS just to prove that it is phase locked on UTC). I will see if I can organise a YouTube video (never done that before) to demonstrate. Sadly, I can't upload a video clip here like I can do on images.

But I can add a new still image, which I will do shortly...

If I compare this Teensy 60 KHz sq wave "edge" against a GPS signal at high resolution (200 nS per cm), the normal jitter is about the same as that of GPS (caused by the OSC0_CR dithering), but there is also a slow wandering of the Teensy edge which might take a few seconds to wander say, 200 nSec. But this wandering always comes back again (as it must if phase locked). There are periods too when it doesn't hardly wander at all. And there are periods when it could wander 400 nS. Not quite sure why this is inconsistent. It is like a random walk. The good news is that when not wandering, then the accuracy is very impressive. Over a whole 24 hours, any accuracy better than 1 microSec is equivalent to 1 part in 10e11.
 
Last edited:
OK. Here is an image from today.

MSF60 PLL 07172016.jpg

The Yellow Trace 1 is taken from the end of the twisted pair going into the sine/square converter. The "blips" you can see are an artefact from the converter logic gate switching - note this to be in the centre of Light Blue Trace 2 which is the Teensy Output (value of count = 250 - that's where the control loop maintains phase lock). Purple Trace 3 is a GPS 120 KHz - I doubled the comparison frequency so that it would not be picked up by the Radio. The Dark Blue Trace 4 is a 20 KHz signal from an OCXO and is used as the synch trace for the picture.

I think the reason for the "wandering" is now becoming apparent. Its a shortcoming of the sine/square converter. In the video for this same display, I note that the blips keep shuffling towards the right every second, in sympathy with the amplitude changing (the switching voltage level obviously does not change). And the data changes too (with the time encoded) such that the blip horizontal modulation is not constant. I can see I need a better sine/square converter... back to the drawing board...
 
Thanks you. I was afraid the control might oscillate, but it seems OK.
Regarding the sine/square converter, did I understand correctly that the problem is the switching threshold not being in the middle of the sine? If so, maybe try omitting the feedback and adjusting the input DC-level with one resistor to ground and one to the rail? That wouldn't require much change in the circuit.
 
From the last picture, you can see that the switching levels are not far from the middle of the sine. The positive threshold is about +350 mV and the negative one is -300 mV. I think the problem is more about amplitude variation causing those threshold levels to happen at later times when the amplitude decays. Much depends on which time point in the "one second" period is choosen as the reference starting point for zero in the FTM1 counter. By default, this happens at power up and can be anywhere in the true UTC "one second" period. My "n012345" keystroke command allows me to manually change this to be anywhere in the 120,000 half-cycles that make up "one second". This would then put the sampling point for the "250" mark anywhere in the period of a MSF 60 KHz cycle, and it could be in the "buildup" period, or in the "decay" period. Trying to guess where the optimum point is - is difficult. Today I cheat by using GPS to tell me where it is, but that's not acceptable obviously as an independent solution.

Even when the zero reference point is chosen at the end of the "buildup" phase of "frequency forcing", the variation in the "encoded data" "buildup" period (either a '0' or a '1') might be enough to cause a random walk of a few hundred nanoSeconds.

I have had another idea (in fact two similar ideas)... Inside a 4046 PLL chip, there is a "signal to square" converter with input on Pin 14 and output on Pin 2. I don't know the exact details of how this works, but diagramatically it indicates a high gain amplifier prior to the XOR gate. Later this evening, I will try this method of using this just as an amplifier chip, to square off the sine wave. It should give me switching edges much nearer to the zero crossing point of the sine wave. In fact, there is a good argument to use the whole 4046 chip as a PLL on to the sine wave, as an "intermediate" stabilising circuit, and then lock Teensy to the output of the 4046 chip. The 4046 integration time constant can even remove some of the "wobbling" of the sine wave. Hmm, interesting.

That leads to the second idea. The old SIGNETICS/LINEAR chip NE567 is a TONE DECODER (I think NXP now make this). This is effectively a PLL chip. It has a very sensitive input which works down to 25 mV rms input. That too might be a good way to engineer a more stable switching edge by using it as an intermediate PLL.
 
Last edited:
@JBeale - you asked how much instantaneous jitter is present on the Teensy output square wave.

I switched off the control loop and carefully adjusted the frequency manually using my "q" and "a" keystroke commands to get the Teensy frequency as close to a GPS equivalent as possible. Here is a picture of Teensy (Trace 2), GPS (Trace 3) and Double Oven OCXO (Trace 4 and trigger) which clearly shows the amount of jitter due to the "dithering nature" of OSC0_CR. I read Teensy to be 25 nS and GPS to be 20 nS.

JitterMeasure.jpg

I wonder if there are better algorithms to engineer this "dithering"? I did attempt to get finer control of frequency by expanding the total count from 1000 to 4000, but that seemed to increase the amount of jitter proportionately. What if I could "dither" the "dither rate" itself? Or is this whistling in the wind?
 
I have completed the new sine/square converter using the XOR component of a 4046 chip. It works very well - better than my original design with the 74HCT14B. Here the signal from the twisted pair goes into a 0.1 microFd capacitor and then to 4046 chip Pin 14. Pin 5 (inhibit internal oscillator) is held high (+5v) and Pin 3 (other XOR input) is held low (Gnd). Supply pins are as normal (Pin 16 = +5v, Pin 8 = Gnd). The output square wave is on Pin 2 and is fed directly to Teensy's Pin 17.

I have also modified my code earlier to correct a few mistakes and make improvements (edited above dated 20 JUL 2016).

The scope display for this new design/code is shown below:

SixtyKHz007.jpg

Purple Trace 3 is GPS 10 KHz (trigger and reference). Yellow Trace 1 is MSF radio sine wave (artefact blips now gone). Dark Blue Trace 4 is 4046 output square wave into Teensy FTM1 CH1 Pin 17. Light Blue Trace 2 is Teensy FTM1 CH0 output 60 KHz square wave on Teensy Pin 3.

With this design/code, the Teensy 60 KHz positive going edge is about 500 nS behind the GPS "One Second" mark (used as reference). It has a few tens of nS jitter (as described before) and also suffers a very slow "wandering" of about +/- 250 nS RMS from the central point at 500 nS. This wandering can stop for several seconds, then happens over about four seconds to max deviation, and then returns again to the 500 nS point over a similar number of seconds. This "wandering" is not cumulative (that is it does not increase with time). Neither is it an oscillation of the control loop. It will be the same after one minute as it would be after one hour. It is an interesting phenomena and is worthy of more description in a moment.

So the accuracy of this design as a precision frequency reference will depend on the time interval that you use it. 250 nS over four seconds equates to a frequency error of 250/4 = 62.5 parts per Billion. But it will still be the same amount of error over an hour - this equates to 250/3600 = 0.0694 parts per Billion (6.9 x 10e11). This frequency error is deviation from a precise 60.000000000... KHz square wave. Most often, the output will return to somewhere near the 500 nS point, so is likely to be even less.

I believe the "wandering" is a phase change due to atmospherics (GPS also suffers same to a lesser degree). MSF is propagated throughout Europe (yes, even after Brexit !!) as a ground wave. There also exists a skywave, but in daylight hours the D layer ionisation caused by UV is a good absorber and most is absorbed by it. However, such is the precision we are talking about, the tiny amount that does get through alters the overall radio received phase (it modulates the phase back and forth in a somewhat erratic manner). The most stable time is around 2pm local time just after the sun has reached local zenith (max UV). There is a relative stable period for about six hours beforehand and six hours after. The most distruptive phase changes happen when the D layer ionisation is recombining (about 10 pm local time in summer). Throughout the night, the skywave is erratic and the vector addition with the groundwave can be a hundred times worse than during daylight.

But if an experiment is conducted over a 24 hour period, from daylight to daylight, then this phase change averages to zero, and the full extent of the transmitted accuracy can be realised.
 
Last edited:
All very interesting, thanks for the additional writeup! The 25ns jitter is better than I expected given the Teensy wasn't designed to be a high-stability source; not sure if that could be improved with software changes. I was going to ask what value resistor connected the preamp JFET drain and +12V, but I see https://forum.pjrc.com/attachment.php?attachmentid=7671&d=1468755368 has been updated and it is labelled 1k.

You aren't by any chance a member of the "time-nuts" mailing list? http://www.leapsecond.com/time-nuts.htm This project seems worth a mention there.
 
Silly error corrected in code today (21 JUL 2016). Had this version running all day so works good. Better comments in some places. Still need to find a working solution to the issue of adjusting the sampling to being at the peak of the "buildup frequency forcing" and just before the "decay starts". Some ideas on watching for the "minute marker" and precede this by a fixed time. I ought to try several phases to see if this is an important issue, or just nice to have.

A dual gate FET might be even better than the J113 - I think they are less noisy. This might be important for those users at greater distances. My MSF signal is quite good being only 180 Km away. The wavelength is 5 Km, so I'm 36 wavelengths away. Other front-end designs could be implemented - screened/shielded electric loop, for example, rather than my magnetic loopstick. The SuperQ stage is what makes this particular project interesting (and sensitive). I have tried re-generative receivers with lesser success.

I'm also keen to improve the control loop with a more mathematical algorithm. The current version works well and always locks, but sometimes locks on the opposite phase of the 60 KHz, so that needs looking at.

I have read the "time-nuts" comments in the past, but I am not a member. Happy for you to pass the word forward if you can. Could increase Teensy sales for Paul :). He puts a lot of effort in and is very good value for money.

By the way, my GPS is a UBlox NEO7 module with delicate wire soldered to the LED for a signal output - see https://sites.google.com/site/g4zfqradio/u-blox_neo-6-7 for more details.
 
Last edited:
The Linear Systems LSK170 single and LSK389, LSK489 dual JFETs claim good noise performance (0.9 and 1.8 nV/sqrt.Hz), although these are apparently specialty parts, sold only by vendors I've never heard of. Probably in many cases, ambient RF noise dominates in-circuit noise(?)
http://www.linearsystems.com/assets/media/file/datasheets/LSK170.pdf
http://www.linearsystems.com/assets/media/file/LSK389.pdf
http://www.linearsystems.com/assets/media/file/datasheets/LSK489.pdf

However the existing circuit shows a 10k resistor in series with the JFET gate. I believe the Johnson noise of that resistor at 20 C is 12.7 nV/sqrt(Hz) so reducing the JFET noise much below that just makes the Johnson noise dominate (?)
 
Last edited:
I should also mention, I'm not sure how this receiver would work with WWVB here in the US because since 2012, it uses phase-shift keying. From https://en.wikipedia.org/wiki/WWVB :
An independent time code is transmitted by binary phase-shift keying of the WWVB carrier. A 1 bit is encoded by inverting the phase (a 180° phase shift) of the carrier for one second. A 0 bit is transmitted with normal carrier phase. [...] Added in late 2012, this phase modulation has no effect on popular radio-controlled clocks which consider only the carrier's amplitude, but will cripple (rare) receivers that track the carrier phase.
 
Existing circuit has 10K resistor, but works equally well without it. It is a useful "limiting" component to protect the gate against adverse local transients that might be induced, but not essential.

It would be a very interesting challenge to create a front end design to cater for WWVB and phase-switching. Many commutating ideas spring to mind (such as centre tapped windings, dual tuned circuits, etc).

What my experiments have shown to date is that Teensy can create a beautiful 60 KHz square wave, and the "dithering" method" can adjust the 16 MHz module to a very fine degree. The drift from "perfect" timing appears to be quite slow, if handled manually (multiple seconds). Anyone can check this out with my code. My user command "c" toggles the control on/off. Other commands such as "q" and "a" can increment/decrement the dither ratio manually. If you have a stable reference, then you can see how fine the control is on a scope.

I was looking into "Dither Theory" today and found it is a subject in itself. Analysis went something like this. I use a rollover loop counter of 1000. "Perfect" dithering today was a 450 setting for "FreqAdjust". This means that there are 450 "half-cycles" of 60 KHz (8.3 uS each half-cycle) at the faster rate. So overall correction is... 450 of +10 ppb = +4500 ppbs total per loop, and 1000 - 450 = 550 "half-cycles" of correction -3 ppb = -1650 ppbs in total per loop. The overall ppbs per loop is therefore the sum = +4500 + (-1650) = +2850 ppbs per loop, and that's effective correction of 2850/1000 = +2.85 ppb. So "dithering" has produced this new intermediate setting between OSC0_CR = "0010" (-3 ppb) and "1100" (+10 ppb). I think this means adjustment increments are each 0.013 ppb (???).

In answering my own previous question of "dithering" the "Dither Rate", a mental bell rang and I realised that was exactly what the control loop was doing - dithering the FreqAdjust figure... 450, 449, 450, 451, 452, 450, 451 etc.

If a sequences of "Dither Rates" were pre-programmed, then this could produce a method of EVEN FINER manual control (but at the expense of jitter?).
 
Last edited:
Could the analogue 60 KHz crystal be replaced by a digital filter? The ARM chip has the right instruction set. I don't know enough about writing digital filtering.

There is a "small chance" that the current design might actually work on WWVB. The reasoning goes like this... The phase change happens on a fixed time boundary (at second intervals). The phase change of exactly 180 deg would force the mini crystal to decay much faster than at the "natural" rate - and would actually reverse its phase to some other maximum. On a scope, that signal might appear as a sine wave that drops its amplitude to zero and then goes "negative amplitude" - but maintaining its phase crossing points. Such a signal into the twisted pair would cause the 4046 sine/square converter to also switch (more abruptly) its phase. But the code inside Teensy only looks at the "positive going edge". And now that occurs 180 deg later too... so the value trapped inside CH1 Count Value will not alter where in the "half-cycle" it reads it - it only alters which "half-cycle" it is actually synchronised to. And remember that the value is only used once per second to adjust the dither rate. I manually cheat to push this "instant" to just before the second mark (to get maximum stable amplitude) when using MSF.

This sychronising on arbitrary phases was an issue I had to still to consider, but it may turn out to be what saves the design for WWVB. There is only one way to find out... volunteers? (I obviously can't reproduce WWVB very easily).
 
Last edited:
I have some parts on order and I might try it out although I'm about 1500 km distant from WWVB. However with phase reversal at 1 second intervals, expecting the filter to pass through 0 amplitude seems to throw away some of the benefit of a narrow bandpass filter. It seems to me a different design that is looking at the difference between 0 and 180 degrees phase should do better (the true signal will always be +1 or -1 at any moment when the LO is locked, while random noise will average near 0). And you can do better still if you already know which phase is coming, which for most of the bits you do, if you already know the current time of day to better than 1 second.
 
Drive distance is 2000 km - most of the clocks I have that sync to this have to move to an east facing window at night to get a setting for DST updates. Is the simple heartbeat easier to pickup than a full time steing?
 
I will await your experiments with great interest. Very difficult to predict exactly what will happen.

What was a big surprise to me was just how sensitive the SuperQ principle is. As a consequence, I think even at 2000 Km you are going to get far better results.

Prior to stumbling on this, I had used an extra stage of gain and a larger ferrite front end. My earlier designs all suffered from local switched power supply noise. But as soon as I started with the SuperQ, then even the small 3 inch ferrite in the picture was more than good enough. And you have witnessed the quality of the sine wave into the twisted pair.

One interesting precursor design had the tuned circuit you see in the picture, and on the same axis, I had a seperate ferrite tuned to 60 KHz which was only coupled by its magnetic field. This could boost the receiver output (presumably increasing the front end Q, as in the old double tuned cicruits used in IF stages) by optimising the distance between the two pieces of ferrite (about 30 mm seemed optimum). Too close and it became over-coupled, with a reduction in output. Zeta = 0.7 theory rings a bell here...

I have learned a lot about crystal behaviour from this work. I now think about this as "energy management" within a resonating crystal. The anti-phase "forcing drive" from a 180 deg phase reversal will knock out "specific energy packets" from each oscillating cycle far quicker than natural decay - its like a "braking force" - but has to be precisely delivered (which it will be). And knowing how these oscillations "build up" (as seen in the "Start of Decay" and "End of Decay" pictures earlier) then this is why I think it will go through zero and establish a "negative amplitude". Not sure the bandwidth will suffer, but ready to be shown wrong.

The problem I do see with WWVB is that your amplitude modulation is at a disadvantage to MSF. WWVB has 0.2 secs and 0.5 secs gaps, whereas MSF as 0.1 secs and 0.2 secs (plus some odd gaps on DUT1) so MSF has more "carrier on" time. Whether this is enough time for a mini crystal to "build up" as stable is the question (and that depends on Q and drive level). Only practical experiment will answer these questions.

Good luck. And enjoy :) !!
 
While awaiting components... a bit more info on why a gap of 0.5 secs might be problematic...

AmplitudeProfile01.jpg
MSFEncoding01.jpg

This 1st picture shows a bit more detail of the receiver output around the "minute mark". The 2nd picture shows the encoding for MSF.

I am sampling this waveform inside Teensy within the FTM1 ISR routine. I use the FTM1Ch1[8] array to read consecutive values continuously, but most readings are discarded. The CalcAverageCV() subroutine is called in the main loop but ONLY when OneSecCount==OneSecHiVal is true. So this means the 8 array values immediately before, and at, the OneSecond mark are used to make the final single reading for the control variable FTM1Ch1CountValue.

By manually altering the value of OneSecHiVal, I can push the sampling point to any of those 120,000 half cycles (of Teensy 60 KHz) which exist within a 1 second period. My "cheat" today is to use the GPS "second" as a reference point - and using the scope, I observe the fast single output pulse on Teensy Pin22 (one second intervals). I input new values via the "n012345" keyboard command until that Pin22 pulse occurs a few cycles just before the GPS "second". This way I know that I'm sampling at the optimum "stable" time of the waveform which is the peak of the amplitude. This is shown in the 1st picture as points A, C, E, G, I.

You can clearly see the data encoding in the picture waveform. Interval AB is the "natural decay" of the mini crystal's oscillations and BC is the next "forcing frequency build up" period. It is ONLY when the crystal is being forced that its frequency is exactly equal to that of the transmitted MSF signal. And by pushing the sampling point to the end of that forcing period, that gives me the best reading possible. By calculating 8 values at that sampling point, and using the average value, then this minimises the chance of "extraneous noise" affecting the result.

The period AB is 300 mS long. It consists of 100 mS carrier off, plus two "data bit periods" for Bit A and Bit B (2nd picture) - both these are of value "1" - which means that the carrier is also off during those periods. So the total carrier off time is 300 mS. Bit 57A will always be a "1". Bit 57B is a parity bit for some other data bits earlier in the encoding.

You can also see that in the 58th second, the period CD is identical to that of AB, so this too must have Bits 58A and 58B set at "1". Bit 58B is a very interesting bit - when set "1" it indicates that the UK is on "Daylight Saving Time"/Summer Time and the MSF data will now be 1 hour advanced on UTC (as indeed we are). Bit 58A is always set to "1".

Bits 57A and 58A are part of an eight bit sequence "01111110" set in 52A, 53A, 54A, 55A, 56A, 57A, 58A, 59A. This sequence is identical to the "Flag Sequence" used in Ethernet packet data encoding, which signals the start and stop of an Ethernet packet. Here, it signifies that the next bit is the start of the MINUTE MARKER interval.

Bit 59B is always a "0", so together with 59A being "0", the carrier off interval at the start of "Second 59" is now only 100 mS long. Again you can see this as EF in the picture.

====

The shortest carrier on period for MSF is therefore not less than 700 mS throughout the 59 seconds which make up a minute. The exception to this rule is that MINUTE MARKER interval of 500 mS carrier off then on. This is shown as GH and HI respectively.

Now you can see why 0.5 secs might be problematic. If you look closely at HI, you can see that "point I" does not quite reach the same amplitude as that at A, C, E and G. It is not until the next maximum (what would be K) that the amplitude stability is reached again.

Because the sine to square converter is (to a very fine degree) dependant on amplitude for its "crossing points" in the output, then this could affect the stability overall.

====

The intervals GH and HI are very dependant on the Q of the SuperQ receiver. You can actually compute the Q by examining the cycles of decay in GH using the "Ring Down method". Have a look at http://www.giangrandi.ch/electronics/ringdownq/ringdownq.shtml.

I computed the Q of my circuit by first measuring the bandwidth in a test circuit. This test circuit was identical to the receiver design IC3 stage, but driving from a sig gen. A suitable "value for money" sig gen is available at http://www.ebay.co.uk/itm/MHS-5200A...tor-Source-Frequency-Meter-12S6-/351479922752. This is a synthesiser capable of setting to 0.01 Hz.

My bandwidth measurement was 3 Hz, so using the formula Q = Freq/Bandwidth, then 60,000/3 = 20,000. This figure is quite unusual in practical circuits.

====

Going back to the 1st picture, notice that periods BC and DE are an exponential growth. But they have a "funny wobble" superimposed on it. You don't see any similar "funny wobble" on the decay period of GH (its a fairly smooth exponential decay). My interpretation of this "funny wobble" is that it is experiencing a "hit with a sledge-hammer" - this is the "force" coming from the re-established carrier on period, and it's that carrier bashing the mini crystal into submission and forcing it against its will to oscillate at the carrier frequency, not at its natural frequency.

This "bashing with a sledge-hammer" is what I am trying to say when I talk about "energy management" in my last post. Its fine to analyse this design using conventional "static state" FILTER theory. But there is a lot of "dynamic activity" going on here. An alternative approach is to view this not so much as a FILTER, but more as a FORCED OSCILLATOR which is centred on the stage of IC3 in the schematic. This forced oscillator is being "disciplined" by that carrier sledge-hammer which is "looming into the stage" via a misty cloud of small stray capacitance that is coupling this to the FORCED OSCILLATOR to the prior radio amplifier stages of JFET and IC1.

This alternative viewpoint is why I think that the sine wave output of the receiver is "so pure" (forgive the slew rate shortcomings of INA126). The high Q would not be as high as 20,000 if the IC3 stage was coupled more directly to IC1B - say through a higher value fixed capacitor. The previous stage would load the Q factor and thereby reduce it with the output impedance of IC1B (which will be very low).

I also think that this viewpoint might explain why the receiver is so sensitive. An oscillating crystal which is very "loosely coupled", and almost in its natural Q environment, will be very sensitive to "pulsations" that happen near to its own natural resonant frequency. I am reminded of the tale of that great electical engineer NIKOLA TESLA in his early days wandering the streets of New York in the early 1900's. The rumour has it that he experimented with attaching "very slow mechanical tapping devices" to the sides of buildings. Inhabitants of the building did not notice anything unusual for several days. But after a few weeks, the building started to sway quite violently. He obviously understood the great power of a very small tap on an object at its natural resonant frequency :) !!
 
Last edited:
An improved Frequency Control routine and a small change to the main loop below...

Putting CalcAverageCV outside the ControlOn test in the main loop makes it display correctly when control is off.

The change to the Frequency Control routine makes for smoother action and less wandering. The idea now on adjusting the phase is to have a small amount of "dead band" (248 to 252) and control outside these values is slower to react, taking four seconds before any control action happens (using the Counter variable to count 0,1,2,3 and back to 0).

void loop() {
//read key input (if any)
KeyInput(); //read key input (if any)
LoopCount++; //keep tally of loop counts
if (OneSecCount==OneSecHiVal) { //check for 1 second interval to slow display of data
CopyData(); //freeze a copy in case data changes before being displayed
DisplayData(); //display data in serial monitor
CalcAverageCV(); //calculate average value of FTM1Ch1CountValue
if (ControlOn) {
FreqControl(); //if control active then call freq control routine
}
}
} //end of main loop


//Frequency/Phase Control Routine
void FreqControl() {
//FreqControl Steps
//=================
//adjust frequency - positive Ch1Differential means going too fast
if (dCh1Differential>0) {
FreqAdjust--;
}
if (dCh1Differential<0) {
FreqAdjust++;
}

//adjust phase - nudge towards 250 value (action every 4th second)
if ((dCh1CountValue>252)&&(Counter == 0)) {
FreqAdjust--;
}
if ((dCh1CountValue<248)&&(Counter == 0)) {
FreqAdjust++;
}
Counter++;
if (Counter>3) {
Counter = 0;
}
}
 
Last edited:
Status
Not open for further replies.
Back
Top