Pendulum clock accuracy - GPSDO 10Mhz + 1Hz pulse

Status
Not open for further replies.

Wez

Member
Hello,
I have a very particular type of Master clock (electro-mechanical pendulum, made by the company Gent and referred to as a pulsynetic) i am interested at looking at its time keeping ability, but i am actually more interested in discovering what makes it unstable. (atmospheric pressure, temperature etc)
but i need to construct some type of frequency standard and produce a time stamp for each pendulum swing so i can calculate its performance.

For reasonable money i can buy or build a GPSDO (global positioning system disciplined oscillator) to produce a long term stability 1Hz pulse from the satellites, and the oscillator will produce 10Mhz (hopefully square wave but possibly sinewave)
i am hoping that if a Teensy #? can work with this high frequency i should be able to count each second, and also subdivide it by 10 million. and produce a time stamp or every pendulum pulse.

i understand i am looking into a rabbit hole with this, but i am looking to build something that is reasonably good in term's of detecting drift or time error in seconds and rate error ideally in ppm not aiming for the ultimate system possible.

I would like to attach a small 'flag or marker' to the base of the pendulum, and this will passthrough a photodetector, i am thinking that if i can detect the rising and falling edge of a flag or marker of a known width, i can calculate pendulum velocity, and with some basic math looking at the time stamps of these edges for each swing i can see how the clock behaves.

So i guess my question for the experts would be :-
1. would i be expecting too much of a teensy to keep track of two interrupts (1Hz and 10Mhz) and count a 'unsigned long' to 10,000,000 before resetting to 0 and starting again.
2. for the data side i would like to write out the time stamp for each rising edge and falling edge from the flag. (two time stamps per second, as the pendulum is long and each swing is 1 second / the total oscillation is 2 seconds)
3. it would be ideal to write these time stamps to sd card, but i expect i would need carful management of when i need to write the data out ?

I am willing to try anything just for the fun of it, but i am hopeful of some guidance / discussion to point me in the right direction of the type of teensy and possible challenges. (i only have teensy 3.6 but i am expecting the 4.0 to perform better at this task ?

thanks
Wess
 
I'd feed a GPS 1 PPS into a teensy 4 and not use an external oscillator. The teensy will be able to measure the 1 PPS and pendulum signals with its 600 mhz clock.
 
Indeed - with a GPS 1PPS and the Teensy Cycle Counter alone the pendulum can be measured.

The Teensy 600 MHz cycles per second will vary over a day, hour, minute . But for any given second - recording the cycles elapsed each second each pendulum swing should "map" into that with reliable results.

Last I did this was using a T_3.6 - getting the 1PPS signal and it would vary some hundred (+/- 250?) counts (in 256M) across a day as it was sitting in a window with the GPS. The second to second variation ( not sure measured as they were averaged away ) or 'per second' of some cycle counts would be minimal per 600,000,000 and likely fall out in the math ( 1/60 ) even if the 10 MHz was wholly accurate with interrupt detection - and the 60 cycles between interrupts was enough time to even log the data and do anything with it.

With the pendulum interrupting at 1 Hz as the GPS is also feeding 1 HZ PPS interrupts - the best would be to keep the processor idle to accurately catch those points in time.

Once running - the port/pins could even be polled in advance of the next arrival for some short microseconds to eliminate any variation in detection by the interrupt response - or if they overlapped.
 
Edit: sorry, I misread your post. I thought you'd get a stable 10MHz signal from your GPSD0; so the following code probably won't help much....
...............

10MHz interrupts are not really feasible. You can however feed your external oscillator to one of the hardware counters. You'd then get your time stamp by simply reading out the counter value. I assume that you want to measure the long term stability of your pendulum? If so, it might be convenient to extend the 32counter which will overflow every ~7min to 64bit.

Here a simple prove of concept sketch which counts pulses on pin14. For testing the sketch generates the 10MHz pulses with a PWM signal on pin0 which is jumperd to pin14. (initialization code from here: https://github.com/manitou48/teensy4)

Code:
#include "Arduino.h"

void startGPT2Counter(void)
{
    CCM_CCGR0 |= CCM_CCGR0_GPT2_BUS(CCM_CCGR_ON);

    GPT2_CR = 0;
    GPT2_SR = 0x3F;             // clear all prior status
    GPT2_CR = GPT_CR_CLKSRC(3); // 3 external clock

    IOMUXC_SW_MUX_CTL_PAD_GPIO_AD_B1_02 = 8; // select input pin (14)
    IOMUXC_GPT2_IPP_IND_CLKIN_SELECT_INPUT = 1;

    GPT2_CR |= GPT_CR_EN;
}

uint64_t readCounter()
{
    static uint64_t curTime = 0;
    static uint32_t lastCnt = GPT2_CNT; // initialize on first call to current counter value

    uint32_t now = GPT2_CNT;            // extend 32bit counter to 64 bit (need to call readCounter at least once per 7min)
    curTime += (now - lastCnt);
    lastCnt = now;

    return curTime;
}

void setup()
{
    startGPT2Counter();
    pinMode(LED_BUILTIN, OUTPUT);

    analogWriteFrequency(0, 10'000'000); // generate 10Mhz on pin0, jumper pin0 to pin14 for testing
    analogWrite(0, 128);
}

elapsedMillis sw = 0;

void loop()
{
    if (sw > 500)
    {
        uint64_t cnt = readCounter();
        double timeInSeconds = cnt / 10E6;

        Serial.printf("timestamp: %fs  |  %ull cts \n", timeInSeconds, cnt);
        sw -= 500;
    }
}

Printout:
Code:
timestamp: 14.000000s  |  140000000ll cts 
timestamp: 14.500000s  |  145000000ll cts 
timestamp: 15.000000s  |  150000000ll cts 
timestamp: 15.500000s  |  155000000ll cts 
timestamp: 16.000000s  |  160000000ll cts 
timestamp: 16.500000s  |  165000000ll cts 
timestamp: 17.000000s  |  170000000ll cts 
timestamp: 17.500000s  |  175000000ll cts 
timestamp: 18.000000s  |  180000000ll cts 
timestamp: 18.500000s  |  185000000ll cts 
timestamp: 19.000000s  |  190000000ll cts 
timestamp: 19.500000s  |  195000000ll cts

In case the stability of the T4 clock is sufficient you could of course follow Jonr's suggestion and use the cycle counter for measurement. Here some info how to achieve this: https://github.com/TeensyUser/doc/wiki/Using-the-cycle-counter
 
Last edited:
I recently wrote some quite similar code (for exact audio clock matching). When you are ready, send me a message if you want the code.

Basic technique is to measure clock A with the teensy clock and clock B with the teensy clock. Then you can divide for an A/B ratio.

Decide if you just want "measure/match current rate" or "measure/eliminate all error since starting".

If you choose the latter, then:

a) be careful that a pulse is never missed (or is accounted for)
b) account for partial cycles

For just measuring/matching rate, filter the signals to average out noise. With enough averaging (or time), jitter in measurement isn't an issue.
 
FWIW, some GPSDO discussion and temperature issues in disciplining a T4 clock https://forum.pjrc.com/threads/61581-Teensy-4-1-NTP-server

As a proof of concept, here is a sketch that uses an external 8mhz GNSS GPS pulse frequency as an external timer source to Teensy 4 GPT2 timer. In lieu of a pendulum PPS I used another GPS to provide a 1 Hz (PPS) to GPT2 capture pin. The sketch reports the number of 8mhz ticks between PPS ticks.
Code:
// GPT2 external clock (GPS 8 mhz) into pin 14,   PPS captured to pin 15
//https://forum.pjrc.com/threads/54265-Teensy-4-testing-mbed-NXP-MXRT1050-EVKB-(600-Mhz-M7)?p=193217&viewfull=1#post193217
// CLKSRC 1 24mhz or 150mhz  3 external  4 32khz
// 1062 capture pin 15  Serial3 Rx    test with GPS pps or pwm on pin 14
// GPT2 capture 1 GPIO_AD_B1_03   Alt 8
// external pin is 14 GPIO_AD_B1_02 ALT8  (Frontside - A0) Serial3 Tx

void setup() {
  Serial.begin(9600);
  while (!Serial);
  delay(2000);
  Serial.println("ok");

  //  analogWriteFrequency(14, 100);  // test with PWM
  //  analogWrite(14, 128); // jumper pwm 14  to pin 15  Serial3 on T4B2 breakout
  // Connect GPS 1PPS signal to pin 15 (GPIO_AD_B1_03)
  IOMUXC_GPT2_IPP_IND_CAPIN1_SELECT_INPUT = 1;  // remap GPT2 capture 1
  IOMUXC_SW_MUX_CTL_PAD_GPIO_AD_B1_03 = 8; // GPT2 Capture1
  IOMUXC_SW_PAD_CTL_PAD_GPIO_AD_B1_03 = 0x13000; //Pulldown & Hyst
  // external clock pin 14
  IOMUXC_SW_MUX_CTL_PAD_GPIO_AD_B1_02 = 8;
  IOMUXC_GPT2_IPP_IND_CLKIN_SELECT_INPUT = 1;
  // uncomment following for 150mhz
  //CCM_CSCMR1 &= ~CCM_CSCMR1_PERCLK_CLK_SEL; // turn off 24mhz mode
  CCM_CCGR0 |= CCM_CCGR0_GPT2_BUS(CCM_CCGR_ON) |
               CCM_CCGR0_GPT2_SERIAL(CCM_CCGR_ON);  // enable clock
  GPT2_CR = 0;
  GPT2_PR = 0;
  GPT2_SR = 0x3F; // clear all prior status
  GPT2_IR = GPT_IR_IF1IE;
  GPT2_CR = GPT_CR_EN | GPT_CR_CLKSRC(3) |   // SRC 1 24 or 150mhz  3 external clock
            GPT_CR_FRR | GPT_CR_IM1(1);
  attachInterruptVector(IRQ_GPT2, capture);
  NVIC_ENABLE_IRQ(IRQ_GPT2);
}


volatile uint32_t delta, captured;

void capture() {
  static uint32_t prior = 0;
  static int index = 0, clockhz = 150 * 1000000; // or 24 mhz
  uint32_t ticks = GPT2_ICR1;
  GPT2_SR = GPT_SR_IF1;
  delta = ticks - prior;
  prior = ticks;
  asm("dsb");
  captured = 1;
}

void loop() {
  if (captured) {
    Serial.println(delta);
    captured = 0;
  }
}

The GNSS GPS PPS with external antenna fires about 356 ns before the sparkfun gps
gpsgps4.png
 
Last edited:
Indeed - with a GPS 1PPS and the Teensy Cycle Counter alone the pendulum can be measured.

The Teensy 600 MHz cycles per second will vary over a day, hour, minute . But for any given second - recording the cycles elapsed each second each pendulum swing should "map" into that with reliable results.

Last I did this was using a T_3.6 - getting the 1PPS signal and it would vary some hundred (+/- 250?) counts (in 256M) across a day as it was sitting in a window with the GPS. The second to second variation ( not sure measured as they were averaged away ) or 'per second' of some cycle counts would be minimal per 600,000,000 and likely fall out in the math ( 1/60 ) even if the 10 MHz was wholly accurate with interrupt detection - and the 60 cycles between interrupts was enough time to even log the data and do anything with it.

With the pendulum interrupting at 1 Hz as the GPS is also feeding 1 HZ PPS interrupts - the best would be to keep the processor idle to accurately catch those points in time.

Once running - the port/pins could even be polled in advance of the next arrival for some short microseconds to eliminate any variation in detection by the interrupt response - or if they overlapped.

Thank you for this reply, its probably the most practical solution to get my project started. As i advance i can consider an external 10Mhz OCXO and how to deal with that as in the past several days my clock pendulum has not even varied (visually / audible method) from an internet time page.
so i guess i need to start researching the teensy cycle counter and how to use it effectively + interrupts.
i have only just received my Teensy 4.1 today and unfortunately i will be out of town from Thursday until the 28th ! but that should give me time to read about these functions, as i see a difference in the coding examples in this post compared to the Arduino IDE i have used in the past. i assume this is probably the more advanced method to make the code run faster ?
thank you for your time replying to this.
 
Edit: sorry, I misread your post. I thought you'd get a stable 10MHz signal from your GPSD0; so the following code probably won't help much....
...............

10MHz interrupts are not really feasible. You can however feed your external oscillator to one of the hardware counters. You'd then get your time stamp by simply reading out the counter value. I assume that you want to measure the long term stability of your pendulum? If so, it might be convenient to extend the 32counter which will overflow every ~7min to 64bit.

Here a simple prove of concept sketch which counts pulses on pin14. For testing the sketch generates the 10MHz pulses with a PWM signal on pin0 which is jumperd to pin14. (initialization code from here: https://github.com/manitou48/teensy4)

Code:
#include "Arduino.h"

void startGPT2Counter(void)
{
    CCM_CCGR0 |= CCM_CCGR0_GPT2_BUS(CCM_CCGR_ON);

    GPT2_CR = 0;
    GPT2_SR = 0x3F;             // clear all prior status
    GPT2_CR = GPT_CR_CLKSRC(3); // 3 external clock

    IOMUXC_SW_MUX_CTL_PAD_GPIO_AD_B1_02 = 8; // select input pin (14)
    IOMUXC_GPT2_IPP_IND_CLKIN_SELECT_INPUT = 1;

    GPT2_CR |= GPT_CR_EN;
}

uint64_t readCounter()
{
    static uint64_t curTime = 0;
    static uint32_t lastCnt = GPT2_CNT; // initialize on first call to current counter value

    uint32_t now = GPT2_CNT;            // extend 32bit counter to 64 bit (need to call readCounter at least once per 7min)
    curTime += (now - lastCnt);
    lastCnt = now;

    return curTime;
}

void setup()
{
    startGPT2Counter();
    pinMode(LED_BUILTIN, OUTPUT);

    analogWriteFrequency(0, 10'000'000); // generate 10Mhz on pin0, jumper pin0 to pin14 for testing
    analogWrite(0, 128);
}

elapsedMillis sw = 0;

void loop()
{
    if (sw > 500)
    {
        uint64_t cnt = readCounter();
        double timeInSeconds = cnt / 10E6;

        Serial.printf("timestamp: %fs  |  %ull cts \n", timeInSeconds, cnt);
        sw -= 500;
    }
}

Printout:
Code:
timestamp: 14.000000s  |  140000000ll cts 
timestamp: 14.500000s  |  145000000ll cts 
timestamp: 15.000000s  |  150000000ll cts 
timestamp: 15.500000s  |  155000000ll cts 
timestamp: 16.000000s  |  160000000ll cts 
timestamp: 16.500000s  |  165000000ll cts 
timestamp: 17.000000s  |  170000000ll cts 
timestamp: 17.500000s  |  175000000ll cts 
timestamp: 18.000000s  |  180000000ll cts 
timestamp: 18.500000s  |  185000000ll cts 
timestamp: 19.000000s  |  190000000ll cts 
timestamp: 19.500000s  |  195000000ll cts

In case the stability of the T4 clock is sufficient you could of course follow Jonr's suggestion and use the cycle counter for measurement. Here some info how to achieve this: https://github.com/TeensyUser/doc/wiki/Using-the-cycle-counter


thank you for this concept and idea, i will firstly try and use the teensy clock only then i hope to build upto using a more stable OCXO 10Mhz reference.
great feedback and given me a good appreciation of what's possible ! i mentioned in another reply that the coding looks to be somewhat different from the typical Arduino i am use to, so i also need to do some reading.
best regards
Wess
 
Coding is easy like reading microseconds() - when Cycle Counter is tracked with uint32_t the match for rollover just falls out.

uint32_t SeeTime = micros();
// SOME TIME
SeeTime = micros() - SeeTime;

And SeeTime will always show how many micros() have elapsed in //SOME TIME.

Only a read of the Cycle Counter takes about 3 CPU cycles instead of 36 or so for Micros();

And F_CPU_ACTUAL is the number of expected clock cycles per second on T_4.x, even if the speed is changed at runtime.

I plugged in one of the two GPS units I have - it have been off a year or so - and the PPS has reverted to not 1Hz it seems. Will have to power up the other old unit and see how it runs.
 
FWIW, some GPSDO discussion and temperature issues in disciplining a T4 clock https://forum.pjrc.com/threads/61581-Teensy-4-1-NTP-server

As a proof of concept, here is a sketch that uses an external 8mhz GNSS GPS pulse frequency as an external timer source to Teensy 4 GPT2 timer. Another GPS provides a 1 Hz (PPS) to GPT2 capture pin. The sketch reports the number of 8mhz ticks between PPS ticks.
Code:
// GPT2 external clock (GPS 8 mhz) into pin 14,   PPS captured to pin 15
//https://forum.pjrc.com/threads/54265-Teensy-4-testing-mbed-NXP-MXRT1050-EVKB-(600-Mhz-M7)?p=193217&viewfull=1#post193217
// CLKSRC 1 24mhz or 150mhz  3 external  4 32khz
// 1062 capture pin 15  Serial3 Rx    test with GPS pps or pwm on pin 14
// GPT2 capture 1 GPIO_AD_B1_03   Alt 8
// external pin is 14 GPIO_AD_B1_02 ALT8  (Frontside - A0) Serial3 Tx

void setup() {
  Serial.begin(9600);
  while (!Serial);
  delay(2000);
  Serial.println("ok");

  //  analogWriteFrequency(14, 100);  // test with PWM
  //  analogWrite(14, 128); // jumper pwm 14  to pin 15  Serial3 on T4B2 breakout
  // Connect GPS 1PPS signal to pin 15 (GPIO_AD_B1_03)
  IOMUXC_GPT2_IPP_IND_CAPIN1_SELECT_INPUT = 1;  // remap GPT2 capture 1
  IOMUXC_SW_MUX_CTL_PAD_GPIO_AD_B1_03 = 8; // GPT2 Capture1
  IOMUXC_SW_PAD_CTL_PAD_GPIO_AD_B1_03 = 0x13000; //Pulldown & Hyst
  // external clock pin 14
  IOMUXC_SW_MUX_CTL_PAD_GPIO_AD_B1_02 = 8;
  IOMUXC_GPT2_IPP_IND_CLKIN_SELECT_INPUT = 1;
  // uncomment following for 150mhz
  //CCM_CSCMR1 &= ~CCM_CSCMR1_PERCLK_CLK_SEL; // turn off 24mhz mode
  CCM_CCGR0 |= CCM_CCGR0_GPT2_BUS(CCM_CCGR_ON) |
               CCM_CCGR0_GPT2_SERIAL(CCM_CCGR_ON);  // enable clock
  GPT2_CR = 0;
  GPT2_PR = 0;
  GPT2_SR = 0x3F; // clear all prior status
  GPT2_IR = GPT_IR_IF1IE;
  GPT2_CR = GPT_CR_EN | GPT_CR_CLKSRC(3) |   // SRC 1 24 or 150mhz  3 external clock
            GPT_CR_FRR | GPT_CR_IM1(1);
  attachInterruptVector(IRQ_GPT2, capture);
  NVIC_ENABLE_IRQ(IRQ_GPT2);
}


volatile uint32_t delta, captured;

void capture() {
  static uint32_t prior = 0;
  static int index = 0, clockhz = 150 * 1000000; // or 24 mhz
  uint32_t ticks = GPT2_ICR1;
  GPT2_SR = GPT_SR_IF1;
  delta = ticks - prior;
  prior = ticks;
  asm("dsb");
  captured = 1;
}

void loop() {
  if (captured) {
    Serial.println(delta);
    captured = 0;
  }
}

The GNSS GPS PPS with external antenna fires about 356 ns before the sparkfun gps
View attachment 22822

Thank you for taking the time to look at this, this is extremely useful and i hope to take a look at this when i progress from using the teensy counter and i build upto the 10mhz OCXO external.
i hope to post some results for this project as i move forward as i feel the fundamental topics in this post will be useful for other newbies like me to this area of clocks and timing.
 
Coding is easy like reading microseconds() - when Cycle Counter is tracked with uint32_t the match for rollover just falls out.

uint32_t SeeTime = micros();
// SOME TIME
SeeTime = micros() - SeeTime;

And SeeTime will always show how many micros() have elapsed in //SOME TIME.

Only a read of the Cycle Counter takes about 3 CPU cycles instead of 36 or so for Micros();

And F_CPU_ACTUAL is the number of expected clock cycles per second on T_4.x, even if the speed is changed at runtime.

I plugged in one of the two GPS units I have - it have been off a year or so - and the PPS has reverted to not 1Hz it seems. Will have to power up the other old unit and see how it runs.


My lack of experience is with this type of code below, as it looks to be very different to what i am used to (standard Arduino) :
IOMUXC_SW_MUX_CTL_PAD_GPIO_AD_B1_03 = 8; // GPT2 Capture1
IOMUXC_SW_PAD_CTL_PAD_GPIO_AD_B1_03 = 0x13000; //Pulldown & Hyst

now this is perhaps a stupid question, what is the best resource or location to find out what this refers to + other functions. is there a .pdf or online descriptions. as i would really like to learn more.
 
My lack of experience is with this type of code below, as it looks to be very different to what i am used to (standard Arduino) :
IOMUXC_SW_MUX_CTL_PAD_GPIO_AD_B1_03 = 8; // GPT2 Capture1
IOMUXC_SW_PAD_CTL_PAD_GPIO_AD_B1_03 = 0x13000; //Pulldown & Hyst

now this is perhaps a stupid question, what is the best resource or location to find out what this refers to + other functions. is there a .pdf or online descriptions. as i would really like to learn more.

Most of those things when useful have been captured in library or CORES code. So searching TeensyDuino installed sources can give examples to use - or code to call.

Those were all written based on the microprocessor manuals with links to PDF's on the PJRC site.

Others have evolved in forum posts during Hardware Beta as well with some samples published on github by involved users.
 
For the code indicated - nothing like that is needed to start for sure.

I was using another GPS primarily - an AdaF Ultimate GPS - but the one hooked up is another one I had to wire from a RED PPS LED to a pin as the PPS was not brought out.

I'm finding this to have BOUNCE so been playing with a few working iterations of the code to get this:
Code:
[B]132	599995267	8954625	(11)[/B]
133	599995202	8954624	(11)
134	599995240	8954623	(11)
135	599995241	8954623	(11)
136	599995228	8954623	(11)
137	599995241	8954622	(11)
138	599995240	8954621	(11)
139	599995228	8954621	(11)
140	599995241	8954621	(11)
141	599995240	8954620	(11)
142	599995228	8954619	(11)

Output columns from first line: 132 599995267 8954625 (11)
132 : index of the second
599995267 : ARM_DWT_CYCCNT between PPS interrupts
8954625 : Loops counts on 600 MHz T_4.0
(11) : Amount in Micros() in advance an elapsedMicros time value will be called to wait and not miss the second count after enabling the interrupt
Because of the bouncing value on this PPS the code is probably not worth sharing, but the output gives an idea of the variation in Cycles between PPS interrupts, although that is abnormally clean - a later sample with larger jumps - likely from interrupt timing?

While writing this post I refined it again removing some of the initial debounce code.

The _isr is simple and looks like this - and it disable itself to stop bouncing - this wasn't needed to work with the AdaFruit GPS module with proper PPS pin.
Code:
void gpsISR() {
  gpsEdge = ARM_DWT_CYCCNT;
  detachInterrupt(gpsPin);
  gpsFlag++;
}

Here is a later output segment - the (11) means it hasn't adjusted the number of us's needed to assure it hits the coming PPS.
The jumps are perhaps interrupt jitter - which is why some short term averaging was done [though 14 ticks in 600M is 1/42857142 of a second]:
Code:
1189	599995281	8822821	(11)
1190	[B]599995280	[/B]8822829	(11)
1191	[U]599995266	[/U]8822828	(11)
1192	[B]599995280	[/B]8822827	(11)
1193	[U]599995269	[/U]8822825	(11)
1194	[B]599995281	[/B]8822825	(11)
1195	599995279	8822823	(11)
1196	599995268	8822822	(11)
1197	599995281	8822821	(11)
1198	599995280	8822829	(11)
1199	599995267	8822829	(11)
1200	599995280	8822826	(11)
1201	599995281	8822826	(11)
1202	599995269	8822824	(11)
1203	599995279	8822823	(11)
1204	599995280	8822822	(11)
1205	599995268	8822830	(11)
1206	599995280	8822829	(11)

After 11 minutes - then 40 minutes - the current code shows:
> Higher loop() count with void yield(){}
> [Min - Max] values seen
> Diff from prior second
> Diff from Max-Min
Code:
660	599995475	9374239	(11)	[599995453 - 599995479]	13	{26
...
2400	599995495	9374242	(11)	[599995453 - 599995509]	-11	{56

So even over 40 minutes the drift in cycles is 56/600,000,000 - but with the GPS it can be re-centered every 1 PPS.
 
Last edited:
A fine but important point. When you use the teensy clock to measure a ratio between two external clocks, drift in the teensy clock is mostly irrelevant. Because it effects both measurements equally. Jitter goes away with averaging.

Using only the teensy clock to measure some other clock is a different matter - drift matters and it might be off by 1 second per day.
 
Pendulum time keeping is mainly about choosing a material that doesn't expand with temperature for the pendulum so
its length doesn't vary with the seasons and time of day.

Having a good escapement helps too, electomechanical ones can be close to ideal if designed carefully.

And lastly good control of swing-amplitude is important - period does depend on this more and more with larger
swings, so a small and accurately controlled amplitude is best.

And a final point - don't put a pendulum clock up high in a high-rise building - they can really move in the wind.
 
A fine but important point. When you use the teensy clock to measure a ratio between two external clocks, drift in the teensy clock is mostly irrelevant. Because it effects both measurements equally. Jitter goes away with averaging.

Using only the teensy clock to measure some other clock is a different matter - drift matters and it might be off by 1 second per day.

Indeed a worthy point - as noted on T_3.6 when playing with this an average across a few second was done to hide the jitter - and indeed if using two interrupt inputs - both will suffer the same effect against the same base clock for ref against the GPS PPS.

The note was that even if both suffer jitter in opposite direction it might be 30 ticks on that second? And 30/600,000,000 is a small number, smaller than the overhead and same or worse error using a "10mhz OCXO external". The T_4.x won't process 10M interrupt per second well, if at all. And even if tied to a perfect clock and perfectly read and synchronized - it is no better than the small number above when the GPS PPS signal is used over the long term.

Wanted to show results - but explain that those jumps occurred - they will average out over the short term as the clock drifts over the longer term by some hundreds out of the 600M cycles with daily temp or other changes.

Started a long run last night - but didn't notice the first 22 seconds had a few disturbed hack reading of the debouncing - exploded Min-Max diff to 85K right away. This seems intermittent where one upload it perfect, the next has these anomolies - the 1064 MCU has logic to determine expected path or something that. So having a good PPS signal like on the other Ultimate GPS would not show this problem, perhaps after 2 years sitting the wire soldered to an LED and connector is suspect as I moved it. The onboard GPS battery is certainly dead.

started the code over and it started and synchronized perfectly - and after 6 minutes:
Code:
T:\tCode\_GPSimuDK\Teensy_Cycle_CountingT4\Teensy_Cycle_CountingT4.ino Dec 16 2020 00:20:28
2	599995637	9373188	(151)	[999999999 - 0]	599995637	{3294967297
3	599995657	9372921	(131)	[599995637 - 599995637]	20	{0
4	599995648	9373130	(111)	[599995637 - 599995657]	-9	{20
5	599995633	9373320	(91)	[599995637 - 599995657]	-15	{20
6	599995648	9373508	(71)	[599995633 - 599995657]	15	{24
7	599995646	9373690	(51)	[599995633 - 599995657]	-2	{24
8	599995632	9373883	(31)	[599995633 - 599995657]	-14	{24
9	599995641	9374068	(11)	[599995632 - 599995657]	9	{25
10	599995627	9374255	(11)	[599995632 - 599995657]	-14	{25
11	599995638	9374244	(11)	[599995627 - 599995657]	11	{30
12	599995637	9374247	(11)	[599995627 - 599995657]	-1	{30
13	599995625	9374253	(11)	[599995627 - 599995657]	-12	{30
14	599995638	9374242	(11)	[599995625 - 599995657]	13	{32
15	599995622	9374246	(11)	[599995625 - 599995657]	-16	{32
16	599995632	9374247	(11)	[599995622 - 599995657]	10	{35
17	599995632	9374242	(11)	[599995622 - 599995657]	0	{35
18	599995634	9374249	(11)	[599995622 - 599995657]	2	{35
19	599995620	9374251	(11)	[599995622 - 599995657]	-14	{35
20	599995635	9374248	(11)	[599995620 - 599995657]	15	{37
21	599995635	9374243	(11)	[599995620 - 599995657]	0	{37
...
360	599995610	9374240	(11)	[599995591 - 599995657]	16	{66
...
6600	599995585	9374245	(11)	[599995568 - 599995657]	1	{89
...
8528	599995538	9374238	(11)	[599995522 - 599995657]	1	{135

Updated for 110 minutes running. Max and Min have changed to a total diff of 89 cycles ( last {# ) over 396,000 seconds.
The 2nd number "599995585" is cycles per PPS second and is trending down from the start "599995637"
The 3rd number is count of calls to loop() - 9M/sec is very high and shows the CPU is basically free, and again this is with kludge code handle a noisy PPS signal requiring extra overhead - like turning off the interrupt when received and then enabling it again before it is expected to appear because it chatters horribly for some 50ms after appearing, maybe it is PWM'd - I never used this GPS (with hand wired PPS to LED) like this before.

Just added current output line at 8528 seconds - the cycle drop has continued - but second to second net change in DIFF from 89 to 135 is at most 1-2 clock ticks per second. So changes are miniscule versus the PPS, and even over 140 minutes the 135 tick diff would only be a +/-1 tick rounding error using the "10mhz OCXO external"

I should set up 2nd GPS and the code to compare those two GPS PPS readings. The other GPS has better PPS wiring as used before with driect output - so on a second Teensy with timing code it could connect to this Teensy with a PIN this First Teensy changes on its reported PPS - filling in for the Pendulum in User code.
 
Last edited:
The way clock performance is often compared is using a plot of Allan Deviation - are you planning to process the data you
get into this?
 
For the code indicated - nothing like that is needed to start for sure.

I was using another GPS primarily - an AdaF Ultimate GPS - but the one hooked up is another one I had to wire from a RED PPS LED to a pin as the PPS was not brought out.

I'm finding this to have BOUNCE so been playing with a few working iterations of the code to get this:
Code:
[B]132	599995267	8954625	(11)[/B]
133	599995202	8954624	(11)
134	599995240	8954623	(11)
135	599995241	8954623	(11)
136	599995228	8954623	(11)
137	599995241	8954622	(11)
138	599995240	8954621	(11)
139	599995228	8954621	(11)
140	599995241	8954621	(11)
141	599995240	8954620	(11)
142	599995228	8954619	(11)

Output columns from first line: 132 599995267 8954625 (11)

Because of the bouncing value on this PPS the code is probably not worth sharing, but the output gives an idea of the variation in Cycles between PPS interrupts, although that is abnormally clean - a later sample with larger jumps - likely from interrupt timing?

While writing this post I refined it again removing some of the initial debounce code.

The _isr is simple and looks like this - and it disable itself to stop bouncing - this wasn't needed to work with the AdaFruit GPS module with proper PPS pin.
Code:
void gpsISR() {
  gpsEdge = ARM_DWT_CYCCNT;
  detachInterrupt(gpsPin);
  gpsFlag++;
}

Here is a later output segment - the (11) means it hasn't adjusted the number of us's needed to assure it hits the coming PPS.
The jumps are perhaps interrupt jitter - which is why some short term averaging was done [though 14 ticks in 600M is 1/42857142 of a second]:
Code:
1189	599995281	8822821	(11)
1190	[B]599995280	[/B]8822829	(11)
1191	[U]599995266	[/U]8822828	(11)
1192	[B]599995280	[/B]8822827	(11)
1193	[U]599995269	[/U]8822825	(11)
1194	[B]599995281	[/B]8822825	(11)
1195	599995279	8822823	(11)
1196	599995268	8822822	(11)
1197	599995281	8822821	(11)
1198	599995280	8822829	(11)
1199	599995267	8822829	(11)
1200	599995280	8822826	(11)
1201	599995281	8822826	(11)
1202	599995269	8822824	(11)
1203	599995279	8822823	(11)
1204	599995280	8822822	(11)
1205	599995268	8822830	(11)
1206	599995280	8822829	(11)

After 11 minutes - then 40 minutes - the current code shows:
> Higher loop() count with void yield(){}
> [Min - Max] values seen
> Diff from prior second
> Diff from Max-Min
Code:
660	599995475	9374239	(11)	[599995453 - 599995479]	13	{26
...
2400	599995495	9374242	(11)	[599995453 - 599995509]	-11	{56

So even over 40 minutes the drift in cycles is 56/600,000,000 - but with the GPS it can be re-centered every 1 PPS.

That really is very impressive considering 'nothing too special about the crystal'
While i should be packing for going away i had to make time today to try out the new Teensy 4.1
Ive attempted to get going with some code (i actually found while reading more about counters and the issue of rollover, as my first attempts seamed to rollover after 7 seconds / seven serial outputs)

for the most part i see the counter bouncing around by say -50 to +50 counts per delay(1000), but i do see some portions of the output where this value is much more and in the order of hundreds, but with a cancelling value immediately after, for example the difference might rise to +563 then the following result will be -550. then continue in the low tens of units after 1666 seconds. also this didn't happen until 800 seconds into the run.
I am sure this is obvious for the experienced, and completely down to my own doing but i would like to understand what's happening..
i would like to include the code used below and an example of this output. i performed the difference calculations in Excel to attempt to keep any processing down but that had no effect.


Code:
uint64_t cycles64()
{
    static uint32_t oldCycles = ARM_DWT_CYCCNT;
    static uint32_t highDWORD = 0;

    uint32_t newCycles = ARM_DWT_CYCCNT;
    if (newCycles < oldCycles)
    {
        ++highDWORD;
    }
    oldCycles = newCycles;
    return (((uint64_t)highDWORD << 32) | newCycles);
}

void setup()
{
    ARM_DEMCR    |= ARM_DEMCR_TRCENA;         // enable debug/trace
    ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA;   // enable cycle counter
}

void loop()
{
    uint64_t cnt = cycles64();
    double   sec = cnt * (double)1.0/F_CPU_ACTUAL;       //F_CPU;
    Serial.printf("CYCCNT:%11" PRIu64" ->%13.9f s\n",cnt, sec);

    delay(1000);
}


Code:
CPU counts	seconds	counts per 1s	difference
460982953232	768.304922053	#VALUE!	#VALUE!
461582957456	769.304929093	600004224	#VALUE!
462182961656	770.304936093	600004200	-24
462782965856	771.304943093	600004200	0
463382970619	772.304951032	600004763	563
463982974832	773.304958053	600004213	-550
464582979056	774.304965093	600004224	11
465182983856	775.304973093	600004800	576
465782988032	776.304980053	600004176	-624
466382992232	777.304987053	600004200	24
466982996432	778.304994053	600004200	0
467583000619	779.305001032	600004187	-13
468183004819	780.305008032	600004200	13
468783009032	781.305015053	600004213	13
469383013232	782.305022053	600004200	-13
469983017456	783.305029093	600004224	24
470583022243	784.305037072	600004787	563
471183026419	785.305044032	600004176	-611
471783030632	786.305051053	600004213	37
472383034819	787.305058032	600004187	-26
 
The way clock performance is often compared is using a plot of Allan Deviation - are you planning to process the data you
get into this?

Yes is the short answer, but perhaps not at this early stage? (if you can see issues then i would be very happy to hear them, other than the accuracy of the teensy clock as i understand i can only go so far with that)
i am trying to build up knowledge of both Teensy and Horology at the same time. i am hoping the project builds my knowledge and understand of the subjects with the outcome i understand more about the real effects on these clocks (Temp, Pressure, possibly the moon etc etc etc)
It is fascinating subject, and i will see at what point it either becomes : to expensive, complicated or frustrating to progress ! so i am looking at fundamentals first.
With some light reading and experimentation i have been able to get my Master Clock over the past seven days without alteration to not loose or gain (by visually looking at an internet time page. the final 'clunk' of the gravity arm is still visually in perfect sequence.) this provides nothing more than, in perhaps weeks or months time i can say I've lost a second.
but data could support why, and then it becomes interesting for me.

Thank you for taking the time to respond
 
I didn't look closely at what you are doing, but in general, for low jitter input, you can disable interrupts while waiting for the next pulse. Or use an interrupt of the highest priority to capture pulses.
 
Yes, as huge as 32 bit integers are - they only last about 7 seconds counting ticks at 600MHz. But as shown in the code below Cyc Cnt values work well in uint32_t's as long as that 7 sec limit isn't passed.

The big problem in that sketch was delay(1000); It would always be late coming back around by the amount if time already used.

Look up elapsedMillis and Micros for skipping code not needed to run until some time. They can be used often and well.

In place of that here - since this will be interrupt triggered from input the sketch was redone with an intervalTimer.

Other typical tweaks applied - wait for SerMon to be sure output isn't lost when desired. And as noted clock is already running - code edited would work though on T_3.x's where it is not currently.

Not sure the cycles64() has any value here - but left it to see it is running better without the delay().
Code:
CYCCNT: 6822942839 -> 11.371571398 s	CYCCNT Diff:600000000	15788430
CYCCNT: 7422942839 -> 12.371571398 s	CYCCNT Diff:600000000	15788431
CYCCNT: 8022942839 -> 13.371571398 s	CYCCNT Diff:600000000	15788431
CYCCNT: 8622942839 -> 14.371571398 s	CYCCNT Diff:600000000	15788431
CYCCNT: 9222942839 -> 15.371571398 s	CYCCNT Diff:600000000	15788431

Just added Loop Cnt /sec as the last value - that shows this code running faster than the prior GPS hacked sketch. Also important again to say with delay(1000) prior code was only doing less than 1 loop per second.
Also added gratuitous void yield() - and loop count went from 13.3M/sec to 15.7M/sec.
Code:
IntervalTimer clockedSecond;

void setup() {
	// put your setup code here, to run once:
	Serial.begin(115200);
	while (!Serial && millis() < 4000 );
	Serial.println("\n" __FILE__ " " __DATE__ " " __TIME__);
	if ( ARM_DWT_CYCCNT == ARM_DWT_CYCCNT ) { // T_4.x has the counter started
		ARM_DEMCR    |= ARM_DEMCR_TRCENA;         // enable debug/trace
		ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA;   // enable cycle counter
	}
	clockedSecond.begin(clock_isr, 1000000);
}

volatile uint32_t secTick;
void clock_isr() {
	secTick = ARM_DWT_CYCCNT;
}

void yield() {}
uint64_t cycles64()
{
	static uint32_t oldCycles = ARM_DWT_CYCCNT;
	static uint32_t highDWORD = 0;

	uint32_t newCycles = ARM_DWT_CYCCNT;
	if (newCycles < oldCycles)
	{
		++highDWORD;
	}
	oldCycles = newCycles;
	return (((uint64_t)highDWORD << 32) | newCycles);
}

void loop()
{
	static uint32_t lCnt = 0; // loop/sec cnt
	static uint32_t LastCCnt = 0;
	static uint32_t LastSecTick = 0;

	lCnt++;
	if ( LastSecTick != secTick ) {
		uint64_t cnt = cycles64();
		uint32_t thisCCnt = secTick;
		double   sec = cnt * (double)1.0 / F_CPU_ACTUAL;     //F_CPU;
		Serial.printf("CYCCNT:%11" PRIu64" ->%13.9f s\t", cnt, sec);
		Serial.printf("CYCCNT Diff:%lu\t%lu\n", thisCCnt - LastCCnt, lCnt);
		LastCCnt = thisCCnt;
		LastSecTick = secTick;
		lCnt=0;
	}
}
 
Powered up the 2nd GPS with reliable PPS.

Got the code working with the bouncy PPS that starts like this counting cycles between transition to HIGH in setup:
Code:
6151
1025
2337
Requires coding around that and taking the first _isr on pin RISING and having that _ISR do this to itself :: detachInterrupt(gpsPin); Then it has to be turned back on before next PPS pulse.

On the AdaFruit Ultimate GPS the PPS is clean and that same loop reads like this:
Code:
599997583
599997636

T:\tCode\TIME\Teensy_Cycle_CountingT4\Teensy_Cycle_CountingT4.ino Dec 18 2020 21:11:23
Proper PPS Found!
Then the code knows it has the 'Proper PPS'. And the same Sketch does BOTH given that detectable difference in setup().

Interesting thing is both GPS's seem to trigger the PPS at the same time!

Here are TWO T_4.0's with the two diff GPS units - with the code in a single sketch where::
>> 1st T_4 is the bouncy one - that a stable reading has been managed, then it drives a pin HIGH ( like the Pendulum swing detector will I suppose? ) It writes it LOW 600ms later at the same time it re Attaches the PPS interrupt detection.

>> 2nd T_4 in 'Proper PPS' mode has an easier time detecting the stable PPS with _isr, and also has a second _isr on the pin coming from 1st T_4

Those two signals seems to be splitting an I/0 BUS cycle as sometimes the 1st is first pushing it's PPS report by 26 CPU cycles and other times the 2nd gets it first by about 125 CPU cycles.

Output from the 1st as before looks like - now with 4 sample averaging :
Code:
9	599995423	12764862	[599995411 - 599995428]	0	{17	av=599995421
10	599995409	12764872	[599995409 - 599995428]	-14	{19	av=599995416
11	599995422	12764857	[599995409 - 599995428]	13	{19	av=599995419
12	599995413	12764859	[599995409 - 599995428]	-9	{19	av=599995416

And the 2nd with PPS and PIN (row starts with ^ ) timing combined from setup() detecting the PPS rate of change into looping building the 4 point average. The PIN print puts '?' when it can't read well - and the Proper PPS just prints a newline:
Code:
T:\tCode\TIME\Teensy_Cycle_CountingT4\Teensy_Cycle_CountingT4.ino Dec 18 2020 23:11:06
683744084
599997654
599997687

T:\tCode\TIME\Teensy_Cycle_CountingT4\Teensy_Cycle_CountingT4.ino Dec 18 2020 23:11:06
Proper PPS Found!

PPS MISS RANGE
?1	3023737178	___	[999999999 - 0]	-1271230118	{3294967297	__<??	3023737178


^2	599998204	___	[599998204 - 599998204]	1871228322	{0	av=149999551__<<
2	599998204	38706794	[599998204 - 599998204]	1871228451	{0	av=149999551

3	599998207	38706123	[599998204 - 599998207]	3	{3	av=299999102
^3	599998101	___	[599998101 - 599998204]	-103	{103	av=299999076__<<

4	599998197	38706265	[599998197 - 599998207]	-10	{10	av=449998652
^4	599998303	___	[599998101 - 599998303]	202	{202	av=449998652__<<

5	599998201	38706218	[599998197 - 599998207]	4	{10	av=599998202
^5	599998201	___	[599998101 - 599998303]	-102	{202	av=599998202__<<

6	599998198	38706237	[599998197 - 599998207]	-3	{10	av=599998200
^6	599998092	___	[599998092 - 599998303]	-109	{211	av=599998174__<<

^7	599998292	___	[599998092 - 599998303]	200	{211	av=599998222__<<
7	599998186	38706237	[599998186 - 599998207]	-12	{21	av=599998195

^8	599998190	___	[599998092 - 599998303]	-102	{211	av=599998193__<<
8	599998190	38706219	[599998186 - 599998207]	4	{21	av=599998193

...

426	599998108	38706202	[599998093 - 599998151]	8	{58	av=599998104
^426	599998108	___	[599998093 - 599998151]	8	{58	av=599998104__<<

^427	599998101	___	[599998093 - 599998151]	-7	{58	av=599998103__<<
427	599998101	38706209	[599998093 - 599998151]	-7	{58	av=599998103

...

1407	599998117	38706183	[599998093 - 599998151]	-4	{58	av=599998119
^1407	599998117	___	[599998078 - 599998151]	-4	{73	av=599998119__<<

^1408	599998123	___	[599998078 - 599998151]	6	{73	av=599998121__<<
1408	599998123	38706183	[599998093 - 599998151]	6	{58	av=599998121

... // Some time later the one GPS lost lock it seems and some missed data points - when it was noted with PPS MISS RANGE code triggered and did a reSync:
^5810	599998241	___	[599998028 - 599998355]	-108	{327	av=599998267__<<
5810	599998241	39996425	[599998119 - 599998375]	-2	{256	av=599998241

^5811	599998130	___	[599998028 - 599998355]	-111	{327	av=599998212__<<

^5812	599998244	___	[599998028 - 599998355]	114	{327	av=599998241__<<

^5813	599998344	___	[599998028 - 599998355]	100	{327	av=599998239__<<

PPS MISS RANGE

^5812	599998244	___	[599998028 - 599998355]	-100	{327	av=599998240__<<
5812	599998244	39997130	[599998119 - 599998375]	-1199996474	{256	av=599998241

...

7404	599998259	39996449	[599998119 - 599998375]	4	{256	av=599998255
^7404	599998259	___	[599998028 - 599998366]	4	{338	av=599998255__<<

7405	599998250	39996447	[599998119 - 599998375]	-9	{256	av=599998253
^7405	599998250	___	[599998028 - 599998366]	-9	{338	av=599998253__<<

The two T_4's have their own clock per GPS PPS - but as received on the 2nd T_4 - both signals arriving within a few cycles of each other shows the same AVG CycCnt value even with the 1st Teensy interrupt being independent it seems ... at 6.75 hours.

Here's the code - it might work well using the with a single T_4.x Proper PPS (on #17) code path and look for PIN (on #16) input from however the Pendulum swing point is read - if both go HIGH then LOW ONCE each second.
The PROPER PPS code path uses GPS GOOD versus GPS BOUNCE function sets. And GPS GOOD here is tied to the PIN ISR XFER functions that run in parallel emulating the Pendulum input on "2nd T_4" here.
It took some tweaking to get here - with the two units on hand (where the 1st simulates the Pendulum) - there may be some kludges left - but it generally looks as expected (gets out of sync it seems if 1st not online before 2nd - and can get out of sync with signal loss)- even with BOTH timing events happening within some hundred CPU cycles.
Here it is - maybe just a start for monitoring - I have made TWO edits to the code as I wrote this post ... first was shared vars in 'good' and 'pin' code for averages, second when it reports PPS MISS RANGE in PIN code - the left side seconds counts are reset:
Code:
// FB: https://forum.pjrc.com/threads/46652-Teensy-3-6-millis()-problem-amp-fastled?p=155323&viewfull=1#post155323
#define qBlink() {digitalToggleFast(LED_BUILTIN);}

uint32_t perMax = 0;
uint32_t perMin = 999999999;
uint32_t perPinMax = 0;
uint32_t perPinMin = 999999999;

const int gpsPin = 17;
const int gpsPinIO = 16;
volatile uint32_t gpsEdge = 0;
volatile uint32_t pinEdge = 0;
uint32_t lastEdge = 0;
uint32_t lastPinEdge = 0;
unsigned long longCount = 0;
unsigned long longPinCount = 0;
long test;

// void yield() {}
elapsedMicros debounceWait;
void setup() {
  if ( ARM_DWT_CYCCNT == ARM_DWT_CYCCNT ) {
    ARM_DEMCR |= ARM_DEMCR_TRCENA;  // info from teensy forum, access cycle counter
    ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA;
  }
  pinMode(gpsPin, INPUT);
  pinMode(LED_BUILTIN, OUTPUT);
  Serial.begin(9600);
  while (!Serial && (millis() <= 6000));
  Serial.println("\n" __FILE__ " " __DATE__ " " __TIME__);
  uint32_t tt = 0, ss = 0;
  for ( int ii = 0; ii < 7; ii++) { // this shows GPS PPS jitter
    int yy = digitalReadFast( gpsPin );
    while ( yy == digitalReadFast( gpsPin ));
    if ( yy) {
      Serial.println( ss = ARM_DWT_CYCCNT - tt );
      tt = ARM_DWT_CYCCNT;
    }
  }
  Serial.println("\n" __FILE__ " " __DATE__ " " __TIME__);
  if ( ss > 500'000'000) {
    Serial.printf("Proper PPS Found!\n");
    pinMode(gpsPinIO, INPUT);
    attachInterrupt(gpsPinIO, pinISR, RISING); // set flag high on rising edge
    attachInterrupt(gpsPin, gpsISR_PPS, RISING); // set flag high on rising edge
    while ( 1 ) {
      loopPPS();
    }
  }
  else {
    pinMode(gpsPinIO, OUTPUT);
    attachInterrupt(gpsPin, gpsISR, RISING); // set flag high on rising edge
    while ( 1 ) {
      loop();
    }
  }
}

//-------------------------------------   GPS BOUNCE
uint32_t lCnt = 0;
void loop() {
  lCnt++;
  if (gpsEdge != lastEdge) {
    gpsTest();
  }
  if ( debounceWait > 600000 ) {
    digitalWriteFast(gpsPinIO, LOW);
    debounceWait = 0;
    attachInterrupt(gpsPin, gpsISR, RISING);
  }
}

//-------------------------------------   GPS BOUNCE
void gpsTest() {
  static uint32_t lastPeriod = 0;
  uint32_t period = F_CPU_ACTUAL;
  uint32_t thisEdge = 0;
  thisEdge = gpsEdge;  // free running time in cycle counts
  period = thisEdge - lastEdge;
  lastEdge = thisEdge;
  longCount++;
  debounceWait = 0;
  //qBlink();
  digitalWriteFast(LED_BUILTIN, LOW);
  if ( longCount > 5 ) {
    if ( period < perMin ) perMin = period;
    if ( period > perMax  && period < F_CPU_ACTUAL + 1000 ) perMax = period;
    Serial.printf("%lu\t%lu\t%lu\t[%lu - %lu]\t%d\t{%lu\tav=%lu\n", longCount, period, lCnt, perMin, perMax, period - lastPeriod, perMax - perMin, fourAvg(period) );
  }
  else
    Serial.println();
  lCnt = 0;
  lastPeriod = period;
}

//-------------------------------------   GPS BOUNCE
void gpsISR() {
  gpsEdge = ARM_DWT_CYCCNT;
  digitalWriteFast(LED_BUILTIN, HIGH);
  digitalWriteFast(gpsPinIO, HIGH);
  detachInterrupt(gpsPin);
}

//-------------------------------------  GPS GOOD
void loopPPS() {
  lCnt++;
  if (gpsEdge != lastEdge) {
    gpsTestPPS();
    // Serial.printf("\t\tPIvGPS diff=%lu\n", pinEdge - gpsEdge); // 129, 126 or 23 cycles diff : either makles the same IO bus cycle or the next w/ diff ~150 cycles 
  }
  if (pinEdge != lastPinEdge) {
    pinTest();
  }
}

//-------------------------------------  GPS GOOD
void gpsTestPPS() {
  static uint32_t lastPeriod = 0;
  uint32_t period = F_CPU_ACTUAL;
  uint32_t thisEdge = gpsEdge;
  qBlink();
  period = thisEdge - lastEdge;
  lastEdge = thisEdge;
  longCount++;
  debounceWait = 0;
  if ( longCount != longPinCount)
    Serial.print('\n');
  if ( period < F_CPU_ACTUAL + 3000 )   {
    if ( period < perMin ) perMin = period;
    if ( period > perMax ) perMax = period;
    Serial.printf("%lu\t%lu\t%lu\t[%lu - %lu]\t%d\t{%lu\tav=%lu\n", longCount, period, lCnt, perMin, perMax, period - lastPeriod, perMax - perMin, fourAvg(period) );
  }
  else {
    Serial.println( "PPS MISS RANGE");
    longPinCount = longCount; // Assume signal loss and get backin sync ?
  }
  lCnt = 0;
  lastPeriod = period;
}

//-------------------------------------  GPS GOOD
void gpsISR_PPS() {
  gpsEdge = ARM_DWT_CYCCNT;
}

//-------------------------------------  PIN ISR XFER
void pinTest() {
  static uint32_t lastPeriod = 0;
  uint32_t period = F_CPU_ACTUAL;
  uint32_t thisEdge = pinEdge;
  qBlink();
  period = thisEdge - lastPinEdge;
  lastPinEdge = thisEdge;
  longPinCount++;
  if ( longCount != longPinCount)
    Serial.print('\n');
  if ( period < F_CPU_ACTUAL + 3000 && period > F_CPU_ACTUAL - 3000 )   {
    if ( period < perPinMin ) perPinMin = period;
    if ( period > perPinMax ) perPinMax = period;
    Serial.printf("^%lu\t%lu\t___\t[%lu - %lu]\t%d\t{%lu\tav=%lu__<<\n", longPinCount, period, perPinMin, perPinMax, period - lastPeriod, perPinMax - perPinMin, fourAvgPin(period) );
  }
  else
    Serial.printf("?%lu\t%lu\t___\t[%lu - %lu]\t%d\t{%lu\t__<??\t%lu\n\n", longPinCount, period, perPinMin, perPinMax, period - lastPeriod, perPinMax - perPinMin, period );
  lastPeriod = period;
}

//-------------------------------------  PIN ISR XFER
void pinISR() {
  pinEdge = ARM_DWT_CYCCNT;
}


//-------------------------------------  GPS AVERAGES
uint32_t fourAvg( uint32_t inVal ) {
  uint32_t retVal;
  static int ii = 0;
  static uint32_t vals[4];
  vals[ii++] = inVal;
  ii = (ii) & 3;
  retVal = (vals[0] + vals[1] + vals[2] + vals[3]) / 4;
  return retVal;
}

//-------------------------------------  PIN ISR XFER
uint32_t fourAvgPin( uint32_t inVal ) {
  uint32_t retVal;
  static int ii = 0;
  static uint32_t vals[4];
  vals[ii++] = inVal;
  ii = (ii) & 3;
  retVal = (vals[0] + vals[1] + vals[2] + vals[3]) / 4;
  return retVal;
}

You can see this code came from an old T_3.6 thread by the intro comment.
If the 2nd isn't started first the sync may be off as the two expect to clock together.
 
Last edited:
Been running some long time.

Here is the debounced GPS - was good then a couple spasms the other day? Now over 6 days running:
Code:
...
459873	599995534	13332290	[599995421 - 599996046]	13	{625	av=599995527
459874	599995522	13332293	[599995421 - 599996046]	-12	{625	av=599995527
459875	599995534	13332290	[599995421 - 599996046]	12	{625	av=599995527
459876	599995521	13332292	[599995421 - 599996046]	-13	{625	av=599995527
459877	599995521	13332291	[599995421 - 599996046]	0	{625	av=599995524
459878	599995533	13332298	[599995421 - 599996046]	12	{625	av=599995527
459879	599995521	13332292	[599995421 - 599996046]	-12	{625	av=599995524
459880	599994109	13332258	[599994109 - 599996046]	-1412	{1937	av=599995171
459881	599995483	13332281	[599994109 - 599996046]	1374	{1937	av=599995161
459882	599995533	13332283	[599994109 - 599996046]	50	{1937	av=599995161
459883	599995534	13332289	[599994109 - 599996046]	1	{1937	av=599995164
459884	599995547	13332295	[599994109 - 599996046]	13	{1937	av=599995524
459885	599995546	13332290	[599994109 - 599996046]	-1	{1937	av=599995540
459886	599995534	13332293	[599994109 - 599996046]	-12	{1937	av=599995540
459887	599995547	13332287	[599994109 - 599996046]	13	{1937	av=599995543
459888	599995533	13332290	[599994109 - 599996046]	-14	{1937	av=599995540
459889	599995533	13332287	[599994109 - 599996046]	0	{1937	av=599995536
459890	599995533	13332295	[599994109 - 599996046]	0	{1937	av=599995536
459891	599995521	13332295	[599994109 - 599996046]	-12	{1937	av=599995530
459892	599995533	13332286	[599994109 - 599996046]	12	{1937	av=599995530
459893	599995533	13332290	[599994109 - 599996046]	0	{1937	av=599995530
459894	599995533	13332295	[599994109 - 599996046]	0	{1937	av=599995530
459895	599995521	13332294	[599994109 - 599996046]	-12	{1937	av=599995530
459896	599995534	13332287	[599994109 - 599996046]	13	{1937	av=599995530
459897	599995534	13332289	[599994109 - 599996046]	0	{1937	av=599995530
459898	599995521	13332295	[599994109 - 599996046]	-13	{1937	av=599995527
459899	599995534	13332287	[599994109 - 599996046]	13	{1937	av=599995530
459900	599995534	13332289	[599994109 - 599996046]	0	{1937	av=599995530
459901	599996795	13332323	[599994109 - 599996795]	1261	{2686	av=599995846
459902	599995546	13332284	[599994109 - 599996795]	-1249	{2686	av=599995852
459903	599995535	13332281	[599994109 - 599996795]	-11	{2686	av=599995852
459904	599995525	13332286	[599994109 - 599996795]	-10	{2686	av=599995850

...
529884	599995486	13332285	[599994109 - 599996795]	-1	{2686	av=599995490
529885	599995488	13332293	[599994109 - 599996795]	2	{2686	av=599995490
529886	599995498	13332293	[599994109 - 599996795]	10	{2686	av=599995489

And a couple of points from the Teensy with better GPS/PPS and monitoring the other GPS above by pin _isr.
>> Pin lines start '^' - tracks the above
>> GPS got a LOW time "28633610" that wasn't excluded as out of bounds for MIN value
Code:
528072	599998269	39996343	[28633610 - 599998979]	7	{571365369	av=599998265

^528074	599998269	___	[599997326 - 599999532]	7	{2206	av=599998265__<<

^528075	599998264	___	[599997326 - 599999532]	-5	{2206	av=599998266__<<

528073	599998264	39996352	[28633610 - 599998979]	-5	{571365369	av=599998266

528074	599998267	39996342	[28633610 - 599998979]	3	{571365369	av=599998265

^528076	599998267	___	[599997326 - 599999532]	3	{2206	av=599998265__<<
 
Been running some long time.

Hello back from my travels and managed to get through the first week of work, and looking forward to picking this project up again.
The Photointerrupters i want are now back in stock and are ordered.

such a large amount of information has been disclosed in this thread i would like to summarize and please correct any errors.

1.) It is indeed possible to use the teensy to measure the pendulum of a clock.
2.) short term stability is excellent in proportion to the variations of a pendulum that's being impulsed every 30 seconds.
3.) long term stability is also very good with the observation some variations might randomly occur.
4.) The 1PPS from a GPSDO would address the issue of long-term timekeeping stability ?

i am planning on pulling apart an old unused printer/scanner as i am betting a photointerrupter is inside and might get me going sooner.
it will probably take me awhile to work out how to manage the pulses coming in (as i will get the rising edge and falling edge, velocity can be calculated) but thinking i might need to alternate what side of the flag/marker is the 1 second point, while the pendulum swings left to right.

I would like to say that I'm very grateful for all the time and experience you have passed on in this topic, and i will be sure to keep this page updated as i get results (and possibly problems)
 
Good luck. Will be interested to see if the dual PPS code above as written relates well to your PPS and Pendulum case.
 
Status
Not open for further replies.
Back
Top