Microsecond Level Synced Clock

Status
Not open for further replies.

brtaylor

Well-known member
I'm considering trying to sync the Teensy clock on a Teensy 3.6 to an MPU-9250 IMU, which generates an interrupt at 50 Hz. My goal is to get microsecond level precision and not have the clock drift relative to the IMU. My naive approach is to compute the time between pulses on the Teensy using micros or elapsedMicros (or counting CPU cycles to get ~4 ns precision at 240 MHz), compare to the the expected period (20 ms) and compute a scale factor to apply to micros over the next period. I would probably use a moving average filter on the scale factor. I'm not able to add an RTC.

Is this a good approach? It seems better to me to use a scale factor rather than a bias. Perhaps if I had the crystal temperature I could use a more realistic error model.
 
T_3.6 has a built in RTC - I integrated that in prior code getting it's RTC_PPS - each second interrupt. it doesn't use a temp controlled crystal - but is adjustable given a true PPS clock like the GPS can provide.

Also I used the processor cycle counter for reference in that code - lots of notes with Mike on the other thread in your absence. It was sync'd on the MPU9250 interrupt and captured the running cycle counter then. That gives resolution of 1/180,000,000 [ 1/F_CPU ] instead of 1/1,000,000 for micros(). And has less overhead and is absolute count - not keyed on clocked crystal - but gives time diff in cycles since last MPU interrupt i.e. 180,000,000/50.

I found the cycle counter perhaps off by some fixed 500 to 2500 cycles of F_CPU - but that depends on the crystal and temp of the unit. I did do averaging in the code I did so as temp drifted the expected/average count would shift. I reported some of those rudimentary numbers on the other unav? thread - discussion was about calculating the dt for data.


Let me know if you find that and have questions or suggestions about how you like it presented - maybe I could get that done. Reading 'ARM_DWT_CYCCNT' seems to be a direct access value that is just a rolling uint32_t once enabled and incremented with each clock cycle through rollover.

Here is what I saw as good to capture that count on the IMU interrupt - not sure how much of this is in the current code:
Code:
void runFilter() {
  ccTimeNow_IMU = ARM_DWT_CYCCNT; // use with ccPartSec(); to get time offset
  newIMUData = 1;
  Cnt_IMU++;
}

This will give some search keywords - it looks like the last code I worked with was "...\9Apr18\uNavINS_CB_Ver6\uNavINS_CB_Ver6.ino"::

When the RTC _isr is activated - this captures the cycle counter at that moment
Code:
void rtc_seconds_isr(void) {
  cctCntDif = ARM_DWT_CYCCNT;

That was also used to capture the START_BIT of the GPS Serial transfer:
Code:
void GPS_serialrx_isr() {
  ccTimeNow_GPS = ARM_DWT_CYCCNT;
 
Running sketch below on T_3.6 shows cycle counter usage - and a quick compare to micros() and elapsedMicros.

Getting 10 copies of current micros() or elapsedMicros values takes at least 2 microseconds +/- error would be another microsecond.

Getting the first Cycle Count in the loop can take ~10 Clock Cycles ( 1/F_CPU @ 180 MHz ), then each iteration is 6 clock cycles.

At 180 MHz, Increasing 'ArrLen' to 40 there are either 5 or 6 reads of micros() or elapsedMicros per microsecond, where CycleCount at 6 cycles would be 30

Then for 10 microseconds I read all the Cycle Counts and micros() possible in unrolled sets of 10 and it is 36 sets versus 5 sets showing the added overhead in checking the clock state to determine the current microsecond - to the nearest microsecond ... that would be +/- 180 cycle counts. I just recompiled at 240 MHz and the same 7 to 1 ratio appeared with counts of 49 sets versus 7 sets of 10 for micros().

Code:
void setup() {
  Serial.begin(19200);
  pinMode(LED_BUILTIN, OUTPUT);
  while ( !Serial && millis() < 4000 );
  Serial.println("\n" __FILE__ " " __DATE__ " " __TIME__);

  if ( ARM_DWT_CYCCNT == ARM_DWT_CYCCNT ) { // // Enable CPU Cycle Counter, avoid restart which may be harmless
    ARM_DEMCR |= ARM_DEMCR_TRCENA;
    ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA;
  }


  elapsedMicros emWait;

#define ArrLen 10
  uint32_t Tcatch[3][ArrLen];
  uint32_t ii;

  for ( ii = 0; ii < ArrLen; ii++ ) {
    Tcatch[0][ii] = micros();
  }

  for ( ii = 0; ii < ArrLen; ii++ ) {
    Tcatch[1][ii] = ARM_DWT_CYCCNT;
  }

  for ( ii = 0; ii < ArrLen; ii++ ) {
    Tcatch[2][ii] = emWait;
  }

  uint32_t jj;
  for ( jj = 0; jj < 3; jj++ ) {
    for ( ii = 1; ii < ArrLen; ii++ ) {
      Serial.print( jj );
      Serial.print( "\t" );
      Serial.println( Tcatch[jj][ii] - Tcatch[jj][ii - 1] );
    }
    Serial.println(  );
  }

  ii = 0;
  emWait = 0;
  while ( emWait < 10 ) {
    jj += ARM_DWT_CYCCNT;
    jj += ARM_DWT_CYCCNT;
    jj += ARM_DWT_CYCCNT;
    jj += ARM_DWT_CYCCNT;
    jj += ARM_DWT_CYCCNT;
    jj += ARM_DWT_CYCCNT;
    jj += ARM_DWT_CYCCNT;
    jj += ARM_DWT_CYCCNT;
    jj += ARM_DWT_CYCCNT;
    jj += ARM_DWT_CYCCNT;
    ii++;
  }
  Serial.print( "CyCnt loops:: " );
  Serial.println( ii );

  ii = 0;
  emWait = 0;
  while ( emWait < 10 ) {
    jj += micros();
    jj += micros();
    jj += micros();
    jj += micros();
    jj += micros();
    jj += micros();
    jj += micros();
    jj += micros();
    jj += micros();
    jj += micros();
    ii++;
  }
  Serial.print( "Micros() loops:: " );
  Serial.println( ii );

}

void loop(void)
{

}
 
Thanks! Interesting to look through your approach. Are you updating the RTC from the GPS PPS? I'm not seeing it int T_timeBase.ino or uNavINS_CB_Ver6.ino.
 
At one point I compared the RTC .vs. PPS - but I never implemented updates to the RTC timing. I assumed if the PPS was there then it would be used against cycle counter - voiding the use of RTC. If no PPS - then no way to adjust the RTC.

And it seemed many of the GPS units at hand were not emitting PPS in a generally usable fashion. Mine had an LED to hand wire to, others not so - or not so easily.

With GPS you have incoming RTC/UTC clock data - but not tied to an absolute time of arrival. Also 'time is relative' - if the IMU is driving the data at 50 Hz everything is based off of its timing ... as suggested by your OP that seemed to be what was critical as the basis for the time that matters.

I could plot the IMU time of interrupt against Cycle Counter (and PPS) - but I had not done that. There is some delay and jitter in interrupt response - maybe 12 -50 cycles depending on what else is going on - but that is an accurate (unknown) part of a microsecond's 180 cycles. My unit is still on a cable in the window where it was abandoned after the SPI_MSTransfer work completed ...

The IMU math is not the 'space math' the GPS has to do integrating 5 or 15 satellites - so it probably represents a fixed amount of time ( note in data sheets by device ? ) from the time of sample. The only slippage would be the clocking it uses where crystal influences the rate as it changes over time - so a proper average of dt differences over time seems like can adjust for temp influences over time - but be a valid reference between 'this and the last' measurement.

When I let that code run near a window to see GPS the drifting Clock Cycle (subject to daily temp on Teensy crystal) average seemed to have a range of =/- 250 cycles over a day against PPS - IIRC what I posted months back on the other thread. The change against RTC was greater as it involved the other uncompensated crystal.
 
Last edited:
Here's a morphed version that shows the IMU return times based on cycle counts expressed as microseconds [ CycleCount / (F_CPU / 1000000) ].

Math overall seems to be close showing Hz based on SRD - nothing normalized to PPS or clock - the running elapsedMillis is shown after running IMU data rcv cnt between pairs of '-----' before the '<-----', it shows some oddity in the elapsedMillis as used.

Running this starts at SRD 49 and drops by '4' after each 4 seconds - where a second is counted based on the expected IMU Hz. It ends on SRD==9 showing 100 Hz IMU reads - so that works.

Code:
#include "MPU9250.h"

// IMU Declares :: Adjust for your device
#define IMU_BUS      Wire  //Wire // SPI
#define IMU_ADDR     0x69  //0x69 // 0x68 // SPI 9
#define IMU_SCL       47  //47 // 0x255
#define IMU_SDA       48  //48 // 0x255
#define IMU_SPD   1200000  //1000000 // 0==NULL or other

#define IMU_INT_PIN   50  //50 // 1 // 14

MPU9250 Imu(IMU_BUS, IMU_ADDR, IMU_SCL, IMU_SDA, IMU_SPD);
int status;

uint16_t vvIMU_SRD = 49;
uint16_t IMU_HZ = (1000 / (1 + vvIMU_SRD));

volatile int newIMUData;
volatile uint32_t ccTimeNow_IMU = 0;
volatile uint32_t Cnt_IMU = 0;
uint32_t CntIMU = 0; // debug

elapsedMillis emSec;
void setup() {
  Serial.begin(19200);
  while ( !Serial && millis() < 4000 );
  Serial.println("\n" __FILE__ " " __DATE__ " " __TIME__);

  ARM_DEMCR |= ARM_DEMCR_TRCENA;
  ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA;

  // start communication with IMU
  status = Imu.begin();
  if (status < 0) {
    Serial.println("IMU initialization unsuccessful");
    Serial.println("Check IMU wiring or try cycling power");
    Serial.print("Status: ");
    Serial.println(status);
    while (1) {}
  }

  Imu.setDlpfBandwidth(MPU9250::DLPF_BANDWIDTH_20HZ);
  //Imu.setDlpfBandwidth(MPU9250::DLPF_BANDWIDTH_41HZ);
  Imu.setSrd(vvIMU_SRD);

  Imu.enableDataReadyInterrupt();
  pinMode(IMU_INT_PIN, INPUT);
  attachInterrupt(IMU_INT_PIN, runFilter, RISING);
  Imu.setSrd(vvIMU_SRD);
  Serial.print("\n-----______ IMU_SRD >>\t");
  Serial.print(vvIMU_SRD);
  Serial.println("\t-----______\t");
  emSec = 0;
}

void loop(void)
{
  static uint32_t ccTimeLast_IMU;

  if (newIMUData == 1) {
    uint32_t Tmp_ccTimeNow_IMU = ccTimeNow_IMU;
    if ( !( CntIMU % IMU_HZ ) )     {
      Serial.print("\n\n-----\t");
      Serial.print(CntIMU);
      Serial.print("\t");
      Serial.print(emSec);
      Serial.print(" <-----\t");
      emSec = 0;
    }
    Serial.print(( Tmp_ccTimeNow_IMU - ccTimeLast_IMU) / (F_CPU / 1000000));
    ccTimeLast_IMU = Tmp_ccTimeNow_IMU;
    Serial.print("\t");
    if ( !( CntIMU % 10 ) )         Serial.println();

    newIMUData = 0;
    CntIMU++;

    float ax; float ay; float az; float gx; float gy; float gz; float hx; float hy; float hz; bool MagD;
    Imu.readSensorPMD( &ax, &ay, &az, &gx, &gy, &gz, &hx, &hy, &hz, &MagD);
  }
  if ( (CntIMU > (4 * IMU_HZ)) && (vvIMU_SRD > 10) ) {
    vvIMU_SRD -= 4;
    emSec = 0;
    Imu.setSrd(vvIMU_SRD);
    CntIMU = 0;
    Serial.print("\n\n------------------------------______ IMU_SRD >>\t");
    Serial.print(vvIMU_SRD);
    Serial.println("\t-----______\t");
    IMU_HZ = (1000 / (1 + vvIMU_SRD));
  }
  if ( CntIMU > 1000 ) while ( 1 ) ;
}

void runFilter() {
  ccTimeNow_IMU = ARM_DWT_CYCCNT;
  newIMUData = 1;
  Cnt_IMU++;
}

Here's the output for SRD==9 - showing 100 IMU interrupt times:
Code:
-----	900	999 <-----	9989	
9989	9989	9989	9989	9989	9989	9989	9989	9989	9989	
9989	9989	9989	9990	9989	9989	9989	9989	9989	9989	
9989	9989	9989	9989	9989	9989	9989	9989	9989	9989	
9989	9989	9989	9989	9989	9989	9989	9989	9989	9989	
9989	9990	9989	9989	9989	9989	9989	9989	9989	9989	
9989	9989	9989	9989	9989	9989	9989	9990	9989	9990	
9989	9990	9989	9989	9989	9989	9989	9989	9989	9989	
9990	9989	9989	9989	9989	9989	9989	9989	9990	9989	
9989	9989	9989	9989	9989	9989	9989	9990	9989	9989	
9989	9989	9989	9990	9989	9989	9989	9989	9989	

-----	1000	999 <-----
 
Here is that sketch with PPS added for reference. Using Pin 6 for PPS interrupt from GPS.

It runs as before starting at 17 then drops 8 SRD's to 9 to try to synch. Ends with 30 seconds of cycles on SRD==9, then continues showing the most recent count of CPU cycles between PPS reports.

In those 30 seconds my MPU9250 ends up slipping in 1, 2, and then 3 extra IMU data sets past the PPS signal.

This 180 Mhz Teensy only getting about this many between PPS's right now :: >>>>>>>>> ----- PPS !!!! >>>>>> 179997501

NOTE: While IMU reads are active it goes over "PPS !!!! >>>>>> 179997519" and when IMU data stops being pulled it drops as low as "PPS !!!! >>>>>> 179997471". Of course 50 parts in 179997501 is only about 0.2778 millisecond. And as noted using the system micros() clock would - slip based on the same crystal change over time, add 5-7 times more overhead to establish each time, and then could be high or low a full microsecond any time.

I quickly set up synchronization on the IMU reports as SRD is changed - the IMU crystal doesn't report on 'Space Seconds' ... i.e. as reported by the GPS you can see that in the end with SRD==9 the IMU reports that should be 10 ms apart are showing as arriving in 9.990 ms. NOTE: using 'measured' cycles between the PPS did make the IMU timed readings about 99% 9.990 ms rather than a higher percentage of 9.989 as with prior post code - where sample periods like the one shown are dominated by 9989 ( 9.989 ms ) .


Seems to show that using the cycle counter to track incoming IMU _isr() hits looks good. But trying to constrain those to the ticks of a clock would be chasing your tail. Best to know when they arrive and know how long between.

In this sketch I did adjust the cycle counts expected per second from F_CPU to those observed between PPS _isr()'s, not any average - just last observed updated each second as printed.

Let me know if this fails to properly show on your system, or if I made some faults in the process/analysis.

I should really tweak the crystal on this Teensy - as I'm getting cheated 2,500 cycles every second! I lose a whole CPU second every 20 hours! That number I noted 2,500 (post #2) above seems to be the right number I saw months back on this T_3.6 when it was powered down - so if I adjusted ( assuming it is an internal sticky adjustment ) - it should stay in that range.


Code:
#include "MPU9250.h"
// https://forum.pjrc.com/threads/53372-Microsecond-Level-Synced-Clock?p=184835&viewfull=1#post184835

// IMU Declares :: Adjust for your device
#define IMU_BUS      Wire  //Wire // SPI
#define IMU_ADDR     0x69  //0x69 // 0x68 // SPI 9
#define IMU_SCL       47  //47 // 0x255
#define IMU_SDA       48  //48 // 0x255
#define IMU_SPD   1200000  //1000000 // 0==NULL or other

#define IMU_INT_PIN   50  //50 // 1 // 14

const byte PPSinterruptPin = 6;
int pps = 0;
uint32_t CntPPS = 0; // debug
uint32_t CurrPPSsec = F_CPU; // debug
volatile bool PPS_set = false;

MPU9250 Imu(IMU_BUS, IMU_ADDR, IMU_SCL, IMU_SDA, IMU_SPD);
int status;

uint16_t vvIMU_SRD = 17;
uint16_t IMU_HZ = (1000 / (1 + vvIMU_SRD));

volatile int newIMUData;
volatile uint32_t ccTimeNow_IMU = 0;
volatile uint32_t Cnt_IMU = 0;
uint32_t CntIMU = 0; // debug

elapsedMillis emSec;
void setup() {
  Serial.begin(19200);
  while ( !Serial && millis() < 4000 );
  Serial.println("\n" __FILE__ " " __DATE__ " " __TIME__);

  ARM_DEMCR |= ARM_DEMCR_TRCENA;
  ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA;

  // start communication with IMU
  status = Imu.begin();
  if (status < 0) {
    Serial.println("IMU initialization unsuccessful");
    Serial.println("Check IMU wiring or try cycling power");
    Serial.print("Status: ");
    Serial.println(status);
    while (1) {}
  }

  Imu.setDlpfBandwidth(MPU9250::DLPF_BANDWIDTH_20HZ);
  //Imu.setDlpfBandwidth(MPU9250::DLPF_BANDWIDTH_41HZ);
  Imu.setSrd(vvIMU_SRD);

  pinMode(PPSinterruptPin, INPUT);
  attachInterrupt(PPSinterruptPin, PPSInterruptCall, RISING);


  Imu.enableDataReadyInterrupt();
  pinMode(IMU_INT_PIN, INPUT);
  attachInterrupt(IMU_INT_PIN, runFilter, RISING);
  Imu.setSrd(vvIMU_SRD);
  Serial.print("\n-----______ IMU_SRD >>\t");
  Serial.print(vvIMU_SRD);
  Serial.println("\t-----______\t");
  emSec = 0;
}

volatile bool IMU_enable = false;

void loop(void)
{
  static uint32_t ccTimeLast_IMU;
  static uint32_t ccTimeLast_PPS = 0;

  if ( true == PPS_set ) {
    PPS_set = false;
    IMU_enable = true;
    Serial.print("\n\n >>>>>>>>>   ----- PPS !!!! >>>>>>\t");
    Serial.println( CntPPS - ccTimeLast_PPS );
    if ( 0 == ccTimeLast_PPS ) {
      CntIMU = 0;
    }
    ccTimeLast_PPS = CntPPS;
  }

  if (newIMUData == 1) {
    uint32_t Tmp_ccTimeNow_IMU = ccTimeNow_IMU;
    if ( !( CntIMU % IMU_HZ ) )     {
      Serial.print("\n\n-----\t");
      Serial.print(CntIMU);
      Serial.print("\t");
      Serial.print(emSec);
      Serial.print(" <-----\t");
      emSec = 0;
    }
    Serial.print(( Tmp_ccTimeNow_IMU - ccTimeLast_IMU) / (CurrPPSsec / 1000000));
    ccTimeLast_IMU = Tmp_ccTimeNow_IMU;
    Serial.print("\t");
    if ( !( CntIMU % 10 ) )         Serial.println();

    newIMUData = 0;
    CntIMU++;

    float ax; float ay; float az; float gx; float gy; float gz; float hx; float hy; float hz; bool MagD;
    Imu.readSensorPMD( &ax, &ay, &az, &gx, &gy, &gz, &hx, &hy, &hz, &MagD);
  }
  if ( (CntIMU > (4 * IMU_HZ)) && (vvIMU_SRD > 10) ) {
    vvIMU_SRD -= 8;
    emSec = 0;
    Imu.setSrd(vvIMU_SRD);
    CntIMU = 0;
    Serial.print("\n------------------------------______ IMU_SRD >>\t");
    Serial.print(vvIMU_SRD);
    Serial.println("\t-----______\t");
    IMU_HZ = (1000 / (1 + vvIMU_SRD));
    ccTimeLast_PPS = 0;
    IMU_enable = false;
  }
  if ( CntIMU > 3000 ) {
    while ( 1 ) {
      if ( true == PPS_set ) {
        PPS_set = false;
        IMU_enable = true;
        Serial.print("\n >>>>>>>>>   ----- PPS !!!! >>>>>>\t");
        CurrPPSsec = CntPPS - ccTimeLast_PPS;
        Serial.println( CurrPPSsec );
        ccTimeLast_PPS = CntPPS;
      }
    }
  }
}

void runFilter() {
  ccTimeNow_IMU = ARM_DWT_CYCCNT;
  newIMUData = IMU_enable;
  Cnt_IMU++;
}

void PPSInterruptCall() {
  CntPPS = ARM_DWT_CYCCNT;
  PPS_set = true;
  pps += 1;
}
 
Here's a morphed version that shows the IMU return times based on cycle counts expressed as microseconds [ CycleCount / (F_CPU / 1000000) ].

Math overall seems to be close showing Hz based on SRD - nothing normalized to PPS or clock - the running elapsedMillis is shown after running IMU data rcv cnt between pairs of '-----' before the '<-----', it shows some oddity in the elapsedMillis as used.

Running this starts at SRD 49 and drops by '4' after each 4 seconds - where a second is counted based on the expected IMU Hz. It ends on SRD==9 showing 100 Hz IMU reads - so that works.

Code:
#include "MPU9250.h"

// IMU Declares :: Adjust for your device
#define IMU_BUS      Wire  //Wire // SPI
#define IMU_ADDR     0x69  //0x69 // 0x68 // SPI 9
#define IMU_SCL       47  //47 // 0x255
#define IMU_SDA       48  //48 // 0x255
#define IMU_SPD   1200000  //1000000 // 0==NULL or other

#define IMU_INT_PIN   50  //50 // 1 // 14

MPU9250 Imu(IMU_BUS, IMU_ADDR, IMU_SCL, IMU_SDA, IMU_SPD);
int status;

uint16_t vvIMU_SRD = 49;
uint16_t IMU_HZ = (1000 / (1 + vvIMU_SRD));

volatile int newIMUData;
volatile uint32_t ccTimeNow_IMU = 0;
volatile uint32_t Cnt_IMU = 0;
uint32_t CntIMU = 0; // debug

elapsedMillis emSec;
void setup() {
  Serial.begin(19200);
  while ( !Serial && millis() < 4000 );
  Serial.println("\n" __FILE__ " " __DATE__ " " __TIME__);

  ARM_DEMCR |= ARM_DEMCR_TRCENA;
  ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA;

  // start communication with IMU
  status = Imu.begin();
  if (status < 0) {
    Serial.println("IMU initialization unsuccessful");
    Serial.println("Check IMU wiring or try cycling power");
    Serial.print("Status: ");
    Serial.println(status);
    while (1) {}
  }

  Imu.setDlpfBandwidth(MPU9250::DLPF_BANDWIDTH_20HZ);
  //Imu.setDlpfBandwidth(MPU9250::DLPF_BANDWIDTH_41HZ);
  Imu.setSrd(vvIMU_SRD);

  Imu.enableDataReadyInterrupt();
  pinMode(IMU_INT_PIN, INPUT);
  attachInterrupt(IMU_INT_PIN, runFilter, RISING);
  Imu.setSrd(vvIMU_SRD);
  Serial.print("\n-----______ IMU_SRD >>\t");
  Serial.print(vvIMU_SRD);
  Serial.println("\t-----______\t");
  emSec = 0;
}

void loop(void)
{
  static uint32_t ccTimeLast_IMU;

  if (newIMUData == 1) {
    uint32_t Tmp_ccTimeNow_IMU = ccTimeNow_IMU;
    if ( !( CntIMU % IMU_HZ ) )     {
      Serial.print("\n\n-----\t");
      Serial.print(CntIMU);
      Serial.print("\t");
      Serial.print(emSec);
      Serial.print(" <-----\t");
      emSec = 0;
    }
    Serial.print(( Tmp_ccTimeNow_IMU - ccTimeLast_IMU) / (F_CPU / 1000000));
    ccTimeLast_IMU = Tmp_ccTimeNow_IMU;
    Serial.print("\t");
    if ( !( CntIMU % 10 ) )         Serial.println();

    newIMUData = 0;
    CntIMU++;

    float ax; float ay; float az; float gx; float gy; float gz; float hx; float hy; float hz; bool MagD;
    Imu.readSensorPMD( &ax, &ay, &az, &gx, &gy, &gz, &hx, &hy, &hz, &MagD);
  }
  if ( (CntIMU > (4 * IMU_HZ)) && (vvIMU_SRD > 10) ) {
    vvIMU_SRD -= 4;
    emSec = 0;
    Imu.setSrd(vvIMU_SRD);
    CntIMU = 0;
    Serial.print("\n\n------------------------------______ IMU_SRD >>\t");
    Serial.print(vvIMU_SRD);
    Serial.println("\t-----______\t");
    IMU_HZ = (1000 / (1 + vvIMU_SRD));
  }
  if ( CntIMU > 1000 ) while ( 1 ) ;
}

void runFilter() {
  ccTimeNow_IMU = ARM_DWT_CYCCNT;
  newIMUData = 1;
  Cnt_IMU++;
}

Here's the output for SRD==9 - showing 100 IMU interrupt times:
Code:
-----	900	999 <-----	9989	
9989	9989	9989	9989	9989	9989	9989	9989	9989	9989	
9989	9989	9989	9990	9989	9989	9989	9989	9989	9989	
9989	9989	9989	9989	9989	9989	9989	9989	9989	9989	
9989	9989	9989	9989	9989	9989	9989	9989	9989	9989	
9989	9990	9989	9989	9989	9989	9989	9989	9989	9989	
9989	9989	9989	9989	9989	9989	9989	9990	9989	9990	
9989	9990	9989	9989	9989	9989	9989	9989	9989	9989	
9990	9989	9989	9989	9989	9989	9989	9989	9990	9989	
9989	9989	9989	9989	9989	9989	9989	9990	9989	9989	
9989	9989	9989	9990	9989	9989	9989	9989	9989	

-----	1000	999 <-----

Yes, I get similar results as this. So for a 10000 us frame from the MPU-9250, we're about 10 to 11 us short on the Teensy 3.6. My thought process was to compute a scale factor to apply to correct that difference. For a 10000 us frame where the Teensy measures 9990 us, the scale factor would be about 1.001. I would apply the scale factor to all of the times I read from either micros or the cycle count. This way we would be using the MPU-9250 as a "true" time reference while using the Teensy clock to do the intermediate timing.
 
Here is that sketch with PPS added for reference. Using Pin 6 for PPS interrupt from GPS.

It runs as before starting at 17 then drops 8 SRD's to 9 to try to synch. Ends with 30 seconds of cycles on SRD==9, then continues showing the most recent count of CPU cycles between PPS reports.

In those 30 seconds my MPU9250 ends up slipping in 1, 2, and then 3 extra IMU data sets past the PPS signal.

This 180 Mhz Teensy only getting about this many between PPS's right now :: >>>>>>>>> ----- PPS !!!! >>>>>> 179997501

NOTE: While IMU reads are active it goes over "PPS !!!! >>>>>> 179997519" and when IMU data stops being pulled it drops as low as "PPS !!!! >>>>>> 179997471". Of course 50 parts in 179997501 is only about 0.2778 millisecond. And as noted using the system micros() clock would - slip based on the same crystal change over time, add 5-7 times more overhead to establish each time, and then could be high or low a full microsecond any time.

I quickly set up synchronization on the IMU reports as SRD is changed - the IMU crystal doesn't report on 'Space Seconds' ... i.e. as reported by the GPS you can see that in the end with SRD==9 the IMU reports that should be 10 ms apart are showing as arriving in 9.990 ms. NOTE: using 'measured' cycles between the PPS did make the IMU timed readings about 99% 9.990 ms rather than a higher percentage of 9.989 as with prior post code - where sample periods like the one shown are dominated by 9989 ( 9.989 ms ) .


Seems to show that using the cycle counter to track incoming IMU _isr() hits looks good. But trying to constrain those to the ticks of a clock would be chasing your tail. Best to know when they arrive and know how long between.

In this sketch I did adjust the cycle counts expected per second from F_CPU to those observed between PPS _isr()'s, not any average - just last observed updated each second as printed.

Let me know if this fails to properly show on your system, or if I made some faults in the process/analysis.

I should really tweak the crystal on this Teensy - as I'm getting cheated 2,500 cycles every second! I lose a whole CPU second every 20 hours! That number I noted 2,500 (post #2) above seems to be the right number I saw months back on this T_3.6 when it was powered down - so if I adjusted ( assuming it is an internal sticky adjustment ) - it should stay in that range.


Code:
#include "MPU9250.h"
// https://forum.pjrc.com/threads/53372-Microsecond-Level-Synced-Clock?p=184835&viewfull=1#post184835

// IMU Declares :: Adjust for your device
#define IMU_BUS      Wire  //Wire // SPI
#define IMU_ADDR     0x69  //0x69 // 0x68 // SPI 9
#define IMU_SCL       47  //47 // 0x255
#define IMU_SDA       48  //48 // 0x255
#define IMU_SPD   1200000  //1000000 // 0==NULL or other

#define IMU_INT_PIN   50  //50 // 1 // 14

const byte PPSinterruptPin = 6;
int pps = 0;
uint32_t CntPPS = 0; // debug
uint32_t CurrPPSsec = F_CPU; // debug
volatile bool PPS_set = false;

MPU9250 Imu(IMU_BUS, IMU_ADDR, IMU_SCL, IMU_SDA, IMU_SPD);
int status;

uint16_t vvIMU_SRD = 17;
uint16_t IMU_HZ = (1000 / (1 + vvIMU_SRD));

volatile int newIMUData;
volatile uint32_t ccTimeNow_IMU = 0;
volatile uint32_t Cnt_IMU = 0;
uint32_t CntIMU = 0; // debug

elapsedMillis emSec;
void setup() {
  Serial.begin(19200);
  while ( !Serial && millis() < 4000 );
  Serial.println("\n" __FILE__ " " __DATE__ " " __TIME__);

  ARM_DEMCR |= ARM_DEMCR_TRCENA;
  ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA;

  // start communication with IMU
  status = Imu.begin();
  if (status < 0) {
    Serial.println("IMU initialization unsuccessful");
    Serial.println("Check IMU wiring or try cycling power");
    Serial.print("Status: ");
    Serial.println(status);
    while (1) {}
  }

  Imu.setDlpfBandwidth(MPU9250::DLPF_BANDWIDTH_20HZ);
  //Imu.setDlpfBandwidth(MPU9250::DLPF_BANDWIDTH_41HZ);
  Imu.setSrd(vvIMU_SRD);

  pinMode(PPSinterruptPin, INPUT);
  attachInterrupt(PPSinterruptPin, PPSInterruptCall, RISING);


  Imu.enableDataReadyInterrupt();
  pinMode(IMU_INT_PIN, INPUT);
  attachInterrupt(IMU_INT_PIN, runFilter, RISING);
  Imu.setSrd(vvIMU_SRD);
  Serial.print("\n-----______ IMU_SRD >>\t");
  Serial.print(vvIMU_SRD);
  Serial.println("\t-----______\t");
  emSec = 0;
}

volatile bool IMU_enable = false;

void loop(void)
{
  static uint32_t ccTimeLast_IMU;
  static uint32_t ccTimeLast_PPS = 0;

  if ( true == PPS_set ) {
    PPS_set = false;
    IMU_enable = true;
    Serial.print("\n\n >>>>>>>>>   ----- PPS !!!! >>>>>>\t");
    Serial.println( CntPPS - ccTimeLast_PPS );
    if ( 0 == ccTimeLast_PPS ) {
      CntIMU = 0;
    }
    ccTimeLast_PPS = CntPPS;
  }

  if (newIMUData == 1) {
    uint32_t Tmp_ccTimeNow_IMU = ccTimeNow_IMU;
    if ( !( CntIMU % IMU_HZ ) )     {
      Serial.print("\n\n-----\t");
      Serial.print(CntIMU);
      Serial.print("\t");
      Serial.print(emSec);
      Serial.print(" <-----\t");
      emSec = 0;
    }
    Serial.print(( Tmp_ccTimeNow_IMU - ccTimeLast_IMU) / (CurrPPSsec / 1000000));
    ccTimeLast_IMU = Tmp_ccTimeNow_IMU;
    Serial.print("\t");
    if ( !( CntIMU % 10 ) )         Serial.println();

    newIMUData = 0;
    CntIMU++;

    float ax; float ay; float az; float gx; float gy; float gz; float hx; float hy; float hz; bool MagD;
    Imu.readSensorPMD( &ax, &ay, &az, &gx, &gy, &gz, &hx, &hy, &hz, &MagD);
  }
  if ( (CntIMU > (4 * IMU_HZ)) && (vvIMU_SRD > 10) ) {
    vvIMU_SRD -= 8;
    emSec = 0;
    Imu.setSrd(vvIMU_SRD);
    CntIMU = 0;
    Serial.print("\n------------------------------______ IMU_SRD >>\t");
    Serial.print(vvIMU_SRD);
    Serial.println("\t-----______\t");
    IMU_HZ = (1000 / (1 + vvIMU_SRD));
    ccTimeLast_PPS = 0;
    IMU_enable = false;
  }
  if ( CntIMU > 3000 ) {
    while ( 1 ) {
      if ( true == PPS_set ) {
        PPS_set = false;
        IMU_enable = true;
        Serial.print("\n >>>>>>>>>   ----- PPS !!!! >>>>>>\t");
        CurrPPSsec = CntPPS - ccTimeLast_PPS;
        Serial.println( CurrPPSsec );
        ccTimeLast_PPS = CntPPS;
      }
    }
  }
}

void runFilter() {
  ccTimeNow_IMU = ARM_DWT_CYCCNT;
  newIMUData = IMU_enable;
  Cnt_IMU++;
}

void PPSInterruptCall() {
  CntPPS = ARM_DWT_CYCCNT;
  PPS_set = true;
  pps += 1;
}

I have an old Adafruit breakout with PPS, let me set that up and try this code...
 
This illustrates what I was thinking, just using the GPS PPS, no MPU-9250 at the moment (so we're using the GPS PPS as a "true" time source).

Code:
const unsigned int GPS_PPS_PIN = 1;
const double period = 1e6;
unsigned long ccTimeLast_PPS, pps = 0, CntPPS = 0;
double scale = 1, filtered_scale = 1, coeff = 10;
double time, scaled_time;
bool PPS_set = false;


void setup() 
{
  /* serial mon */
  Serial.begin(115200);

  /* start CPU counter */
  ARM_DEMCR |= ARM_DEMCR_TRCENA;
  ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA;

  /* GPS PPS interrupt */
  pinMode(GPS_PPS_PIN,INPUT);
  attachInterrupt(GPS_PPS_PIN,gps_pps,RISING);
}

void loop()
{
  if (PPS_set) {
    PPS_set = false;
    time = (double)(CntPPS - ccTimeLast_PPS) / F_CPU * 1e6;
    if (pps > 0) {
      scale = period/time;
      filtered_scale = (filtered_scale*(coeff - 1) + scale) / coeff;
    }
    scaled_time = time * filtered_scale;
    Serial.print(scale,10);
    Serial.print("\t");
    Serial.print(filtered_scale,10);
    Serial.print("\t");
    Serial.print(time);
    Serial.print("\t");
    Serial.print(scaled_time);
    Serial.print("\n");
    ccTimeLast_PPS = CntPPS;
    pps++;
  }
}

void gps_pps()
{
  CntPPS = ARM_DWT_CYCCNT;
  PPS_set = true;
}
 
Attached is a preliminary library for a synced microsecond and nanosecond timer along with some example code comparing it to an unsynced timer.

Brian
 

Attachments

  • sync_time.zip
    1.9 KB · Views: 151
Looks interesting Brian - only thing odd is using F_CPU throughout - not adjusted to local cycles/sec.

Also can these both be right with same "freq * 1e9":
Code:
synced_micros_64::synced_micros_64(double freq)
{ // ...
  _period = 1.0/freq * 1e9;


synced_nanos_64::synced_nanos_64(double freq)
{ // ...
  _period = 1.0/freq * 1e9;
 
I'm using the _scale variable to scale the cycles per second so the duration between periods should match the "true" value. Is there a better approach? I'm also using ns for both classes so that I can have a little more accuracy computing the scale factor and then rounding to us for the microsecond version.
 
Yes it is there among the glossed over complexity.

This seems to run the same with the complexity reduced directly using the observed PPS Period, not sure if I removed any future value intent?

I edited in a parallel set of classes just for side by side testing:
synced_micros_64 tm(1);
synced_nanos_64 tn(1);

ynced_micros_64 ttm(1);
ynced_nanos_64 ttn(1);


attachment Not alright ...


Looks like it rolls over in 72 minutes:
4293000030 4293000022 4292938491
4293000028 4293000031 xxx

4294000028 4294000022 4293938475
4294000028 4294000031 xxx

32734 32726 4294938464
32732 32736 xxx

1032732 1032726 971151
1032731 1032735 xxx

2032732 2032726 1971136
2032731 2032735 xxx
 
Last edited:
The ROLLOVER was in the Sketch as it was using .print( uint32_t ). Added a func to sprint those values - in the zip below.

I pulled zip above because I reduced the _period a bit too far and was updating each iteration before use ...

This version removed some of the math overhead [ about 65% less overhead ] - it takes 2 PPS seconds instead of 1 to determine the _period and begin ticking up.
View attachment sync_time_b.zip

Brian: Hopefully it serves the same purpose? It never uses F_CPU, and after the 2 startup seconds uses the prior _period to determine the dt in current one?
 
Just noticed I used int64_t not uint64_t in posted code in the header file in a couple spots - a few million seconds and BOOM it would be Year 2K allover again..

Been ticking away - way past the int 32 rollover now. Both (nano) methods staying in sync - just 1 sec apart - for some reason the micros version shows drift on the second line going +/- across the second boundary? But the 2nd column both nano version's are staying in sync for over 27K seconds. The calc's are simpler on nano version ( no round ) - so would be best to drop micros - or just map to the same class with a micro return element.

With the _period update on EACH new GPS PPS - a single aberrant reading would not get averaged away - also I may have made an edit error between the two added classes.


Output for above zip code running some time::
27146000041 27146000035 1376196172
27144999997 27145000033 xxx

27147000041 27147000036 1377196159
27145999998 27146000033 xxx

27148000044 27148000036 1378196147
27147000000 27147000035 xxx

27149000041 27149000035 1379196131
27147999995 27148000030 xxx

27150000036 27150000033 1380196111
27148999993 27149000027 xxx

27151000043 27151000035 1381196105
27150000006 27150000040 xxx
 
Here it is running some more time ... 1.8235 days. The two methods diverged in the error tracking.

157555000041 157555000036 2933436217
157554831731 157554831717 xxx

157556000042 157556000036 2934436204
157555831732 157555831718 xxx

I saw my code above as busted because each PPS interval used its own time as the length of an interval, rather than against the prior interval.

Apparently that same behavior was in the initial code and I copied it because it has not drifted from the startup offset so the dt calculations are based off of difference from self same interval, that is NONE?

The last posted code has accounted for some 831K cycles of drift it seems?

I'm not sure which was intended - my original code was 300% faster rather than a mere 65% because the if() was removed.

The problem with last posted code is that delta from prior shifts when pushed to a second boundary can result in the second value going +/- on those updates as seen in post #16. So that seems wrong - which means the scaling work is not helpful as all that is needed is an ever increasing second - with a cycle counter base for the start of that second and accepting that some few cycles ( tens of nanoseconds ) will be added or lost in each second.
 
Here it is running some more time ... 1.8235 days. The two methods diverged in the error tracking.



I saw my code above as busted because each PPS interval used its own time as the length of an interval, rather than against the prior interval.

Apparently that same behavior was in the initial code and I copied it because it has not drifted from the startup offset so the dt calculations are based off of difference from self same interval, that is NONE?

The last posted code has accounted for some 831K cycles of drift it seems?

I'm not sure which was intended - my original code was 300% faster rather than a mere 65% because the if() was removed.

The problem with last posted code is that delta from prior shifts when pushed to a second boundary can result in the second value going +/- on those updates as seen in post #16. So that seems wrong - which means the scaling work is not helpful as all that is needed is an ever increasing second - with a cycle counter base for the start of that second and accepting that some few cycles ( tens of nanoseconds ) will be added or lost in each second.

My intention with the code in post #11 is to give the "true" period and then scale the Teensy clock so that it's measuring the same period. It's sort of like integrating an IMU to get position estimates at a high rate (i.e. 100 Hz) and then using GPS to correct with true data at a lower rate (i.e. 1 Hz). I'm trying to use the Teensy to provide a high rate clock (1 us or better) while using a lower rate source to keep the clock from drifting relative to it.

Syncing the clock at the start of each pulse and just counting up is similar to applying a bias correction, instead of a scale factor. It arrests the drift, but the intermediate measurements are inaccurate.
 
I have memory, bad as it is, that the actual clock of the arm can be moved using a setting in the chip,
not a lot, but sufficient to account for differences in the resonators, for just this sort of thing.
 
Indeed - with a true sense of what a second is - there is a means to adjust the crystal to match that closely at any given time. Temp and other will still have drift - I noted above that my T_3.6 is getting shorted about 2,500 clocks per second based on the GPS signal and I should attempt corrention. And noted that the GPS's in use may or may not avail the user of a usable PPS signal.

Brian - seems I should put my alternate classes back to the code like yours as I first did it. I understood you wanted the gained/lost cycles to 'dt' into the clock. Getting that is much simpler processing wise as I had it, I'll have to recreate that code as I dumped the ZIP.

I should add my GPS_Rx_isr() code and see how close that rings true to the same timing of the PPS to see if watching the GPS message start is a good approximation of true PPS - but again the 'variable?' time for the GPS to finish it's 'space math' may add more variability.
 
These are the interrupt service routines I use to quickly slew the Teensy RTC to the PPS from the GPS's PPS output, then gather the data for calculating fine grained rtc adjustments for various temperatures. I included the arrays I use for storing the captured time & temperature data. You can provide your own interrupt setup code. I haven't written code to create fine tuned RTC time adjustment values yet. That is the next step. For better accuracy it may be good to keep the Teensy RTC set a certain amount of instruction cycles off of the GPS PPS output. That way, after fine tuning, the two PPS interrupt service routines aren't trying to run at the same time.

My ISRs are a bit longer code wise. With the faster MCUs that are around today I don't worry about doing a small bit of processing in them.

Code:
#define tcrCal(__r__,__a__) (uint32_t)(((uint32_t)__r__<<8) | ((uint32_t)0x000000ff & ((int8_t) __a__)))

// lastTemp is the raw value from analogRead(70) for the internal temperature sensor. 
//    It is set a few times a second by a low priority thread.
// gfti, tfti are the pointers to where the ISRs last stored data into 
//    the arrays. They should be used to initialize another index 
//    pointer that is then used for processing. That is because 
//    they can change at any time. The buffers are ring buffers 
//    with the old data being clobbered even if it hasn't been 
//    processed.

// time adjustment data collection
// time adjustment data collection
uint32_t gfti, tfti; // = last index into array set.
uint32_t lastTemp = 0;
uint8_t fineTuneModeSecSkip = 0;
int8_t fineTuneModePrescaleAdjust = 0;
int8_t fineTuneMode = false;
struct fineTuneStruct {
struct fineTuneStruct {
  int32_t s;  // seconds
  uint32_t p, icc, t; // prescale, instruction cycle counter, temperature
} gfineTune[256] = {{0,0,0,0}},
  tfineTune[256] = {{0,0,0,0}};

#define tcrCal(__r__,__a__) (uint32_t)(((uint32_t)__r__<<8) | ((uint32_t)0x000000ff & ((int8_t) __a__)))

// This ISR drives the fast slewing of the Teensy RTC to reasonably 
//    closely match it. I had it before I found out about the instruction
//    cycle counter. It will keep the Teensy RTC clock within +/-3 
//    prescale ticks of the GPS PPS signal without fine scale 
//    adjustments. If that is all you need, the time data saving code, 
//    and rtcPps_interrupt() code could be deleted.
// When it goes into fine tune adjustment most it will get the current 
//    fine tune adjustment if available and set the RTC_TCR register 
//    and sets a flag saying the system is in fine tune  
//    adjustment mode. My thread that processes the data in  
//    the array will then take over setting the RTC_TCR register  
//    adjustment values as the temperature changes.
// Because of the way RTC_TCR is updated, it is possible to 
//    overshoot by a full adjustment. Therefore I slowly slew into 
//    agreement by 1 tick at a time at the end. The fine tuning 
//    adjustment setting code may still need to slew by a few ticks.
void gpsPps_interrupt() {
  uint32_t s, p, pOr, icc;
  static uint8_t fti = 0;
  
  // get the current time
  icc = ARM_DWT_CYCCNT;
  p = RTC_TPR;
  s = RTC_TSR;
  while((p != RTC_TPR) || (s != RTC_TSR)) {s = RTC_TSR; p = RTC_TPR;}

  // save data into the array
  fti++;
  gfineTune[fti].s = s;
  gfineTune[fti].p = p;
  gfineTune[fti].icc = icc;
  gfineTune[fti].t = lastTemp;
  gfti = fti;
  
  intCount++;
  if((p <= 1) || (p >= (uint32_t)0x00007ffe)) { // fine tune zone
    if(fineTuneMode == true) return;
    fineTuneMode = true;
    if(fineTuneModePrescaleAdjust > 0) {
      RTC_TCR = tcrCal(fineTuneModeSecSkip,fineTuneModePrescaleAdjust);
    }
    return;
  } 
  // calculate new fast slew adjustment
  fineTuneMode = false;
  if (p >= (uint32_t)16384) {
    // high value ones
    if(p >= (uint32_t)(0x00007ffb)) {
      RTC_TCR = tcrCal(0,+1);
      return;
    } else if (p >= (uint32_t)(0x00007f00)) {
      RTC_TCR = tcrCal(0,+((0x00000100-(p-(0x00007f00)))/3 + 1));
      return;
    } else {
      RTC_TCR = tcrCal(0,+127);
      return;
    }
  } else {
    // low value ones
    if(p <= (uint32_t)4) {
      RTC_TCR = tcrCal(0,-1);
      return;
    } else if (p <= (uint32_t)0x000000ff) {
      RTC_TCR = tcrCal(0,-(p/3 + 1));
      return;
    } else {
      RTC_TCR = tcrCal(0,-127);
      return;
    }
  }
  // should not get here.
}

// Teensy RTC PPS interrupt service routine
void rtcPps_interrupt() {
  uint32_t s, p, icc;
  static uint8_t fti;
  // get the current time
  icc = ARM_DWT_CYCCNT;
  s = RTC_TSR;
  p = RTC_TPR;
  while((p != RTC_TPR) || (s != RTC_TSR)) {s = RTC_TSR; p = RTC_TPR;}
  
  // save data into the array
  fti++;
  tfineTune[fti].s = s;
  tfineTune[fti].p = p;
  tfineTune[fti].icc = icc;
  tfineTune[fti].t = lastTemp;
  tfti = fti;
}
 
That looks useful - is there a tested compliable version? with RTC start and such ?

These are the interrupt service routines I use to quickly slew the Teensy RTC to the PPS from the GPS's PPS output ... My ISRs are a bit longer code wise. With the faster MCUs that are around today I don't worry about doing a small bit of processing in them.

Code:
// ...

struct fineTuneStruct { [B]// This is duplicated in above post[/B]
struct fineTuneStruct {

// ...
  
  intCount++; [B]//  This is undefined - unused?[/B]

// ...
 
@Eka - I made it a program, but the _isr()'s collect data - but I don't see code to compare the icc's and make any changes included?

Using code I had that showed the T_3.6 at hand is about 2,500 cycles short per second that difference persists.

I added a counter for entering fineTuneMode = true; mode and it hit 50 so it is cycling the _isr's. And added RTCpps LED on and GPSpps LED off so I see both are firing in turn.

Code:
#define tcrCal(__r__,__a__) (uint32_t)(((uint32_t)__r__<<8) | ((uint32_t)0x000000ff & ((int8_t) __a__)))

// https://forum.pjrc.com/threads/53372-Microsecond-Level-Synced-Clock?p=185235&viewfull=1#post185235

// >> https://forum.pjrc.com/threads/24563-Question-re-RTC-compensation-function?highlight=RTC_TCR

/*
 * These are the interrupt service routines I use to quickly slew the Teensy RTC to the PPS from the GPS's PPS output, then gather the data for calculating fine grained rtc adjustments for various temperatures. I included the arrays I use for storing the captured time & temperature data. You can provide your own interrupt setup code. I haven't written code to create fine tuned RTC time adjustment values yet. That is the next step. For better accuracy it may be good to keep the Teensy RTC set a certain amount of instruction cycles off of the GPS PPS output. That way, after fine tuning, the two PPS interrupt service routines aren't trying to run at the same time.
 * My ISRs are a bit longer code wise. With the faster MCUs that are around today I don't worry about doing a small bit of processing in them.
 */

// lastTemp is the raw value from analogRead(70) for the internal temperature sensor. 
//    It is set a few times a second by a low priority thread.
// gfti, tfti are the pointers to where the ISRs last stored data into 
//    the arrays. They should be used to initialize another index 
//    pointer that is then used for processing. That is because 
//    they can change at any time. The buffers are ring buffers 
//    with the old data being clobbered even if it hasn't been 
//    processed.


const byte PPSinterruptPin = 6;
static uint32_t cntLFTM = 0;
static uint32_t cntFTM = 0;



// time adjustment data collection
// time adjustment data collection
uint32_t gfti, tfti; // = last index into array set.
uint32_t lastTemp = 0;
uint8_t fineTuneModeSecSkip = 0;
int8_t fineTuneModePrescaleAdjust = 0;
int8_t fineTuneMode = false;
struct fineTuneStruct {
  int32_t s;  // seconds
  uint32_t p, icc, t; // prescale, instruction cycle counter, temperature
} gfineTune[256] = {{0,0,0,0}},
  tfineTune[256] = {{0,0,0,0}};

#define tcrCal(__r__,__a__) (uint32_t)(((uint32_t)__r__<<8) | ((uint32_t)0x000000ff & ((int8_t) __a__)))

// This ISR drives the fast slewing of the Teensy RTC to reasonably 
//    closely match it. I had it before I found out about the instruction
//    cycle counter. It will keep the Teensy RTC clock within +/-3 
//    prescale ticks of the GPS PPS signal without fine scale 
//    adjustments. If that is all you need, the time data saving code, 
//    and rtcPps_interrupt() code could be deleted.
// When it goes into fine tune adjustment most it will get the current 
//    fine tune adjustment if available and set the RTC_TCR register 
//    and sets a flag saying the system is in fine tune  
//    adjustment mode. My thread that processes the data in  
//    the array will then take over setting the RTC_TCR register  
//    adjustment values as the temperature changes.
// Because of the way RTC_TCR is updated, it is possible to 
//    overshoot by a full adjustment. Therefore I slowly slew into 
//    agreement by 1 tick at a time at the end. The fine tuning 
//    adjustment setting code may still need to slew by a few ticks.
void gpsPps_interrupt() {
  uint32_t s, p, pOr, icc;
  static uint8_t fti = 0;

  if ( !(fti%4) ) digitalWriteFast(LED_BUILTIN, LOW);
  
  // get the current time
  icc = ARM_DWT_CYCCNT;
  p = RTC_TPR;
  s = RTC_TSR;
  while((p != RTC_TPR) || (s != RTC_TSR)) {s = RTC_TSR; p = RTC_TPR;}

  // save data into the array
  fti++;
  gfineTune[fti].s = s;
  gfineTune[fti].p = p;
  gfineTune[fti].icc = icc;
  gfineTune[fti].t = lastTemp;
  gfti = fti;
  
  if((p <= 1) || (p >= (uint32_t)0x00007ffe)) { // fine tune zone
    if(fineTuneMode == true) return;
    fineTuneMode = true;
    cntFTM++;
    if(fineTuneModePrescaleAdjust > 0) {
      RTC_TCR = tcrCal(fineTuneModeSecSkip,fineTuneModePrescaleAdjust);
    }
    return;
  } 
  // calculate new fast slew adjustment
  fineTuneMode = false;
  if (p >= (uint32_t)16384) {
    // high value ones
    if(p >= (uint32_t)(0x00007ffb)) {
      RTC_TCR = tcrCal(0,+1);
      return;
    } else if (p >= (uint32_t)(0x00007f00)) {
      RTC_TCR = tcrCal(0,+((0x00000100-(p-(0x00007f00)))/3 + 1));
      return;
    } else {
      RTC_TCR = tcrCal(0,+127);
      return;
    }
  } else {
    // low value ones
    if(p <= (uint32_t)4) {
      RTC_TCR = tcrCal(0,-1);
      return;
    } else if (p <= (uint32_t)0x000000ff) {
      RTC_TCR = tcrCal(0,-(p/3 + 1));
      return;
    } else {
      RTC_TCR = tcrCal(0,-127);
      return;
    }
  }
  // should not get here.
}

// Teensy RTC PPS interrupt service routine
//void rtcPps_interrupt() {
void rtc_seconds_isr () {
  uint32_t s, p, icc;
  static uint8_t fti;
  // get the current time
  icc = ARM_DWT_CYCCNT;
  s = RTC_TSR;
  p = RTC_TPR;
  while((p != RTC_TPR) || (s != RTC_TSR)) {s = RTC_TSR; p = RTC_TPR;}

  if ( !(fti%4) ) digitalWriteFast(LED_BUILTIN, HIGH);

  // save data into the array
  fti++;
  tfineTune[fti].s = s;
  tfineTune[fti].p = p;
  tfineTune[fti].icc = icc;
  tfineTune[fti].t = lastTemp;
  tfti = fti;
}


void setup() {
  Serial.begin(19200);
  while ( !Serial && millis() < 4000 );
  Serial.println("\n" __FILE__ " " __DATE__ " " __TIME__);

  ARM_DEMCR |= ARM_DEMCR_TRCENA;  // enable cycle counter
  ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA;

  // Enable RTC second isr
  RTC_IER = 0x10 ;    //TSIE
  NVIC_ENABLE_IRQ(IRQ_RTC_SECOND);

  pinMode(LED_BUILTIN, OUTPUT);

  pinMode(PPSinterruptPin, INPUT);
  attachInterrupt(PPSinterruptPin, gpsPps_interrupt, RISING);

  Serial.println("\t-----______\t");
}

void loop() {
  // put your main code here, to run repeatedly:
  if ( cntFTM != cntLFTM ) {
    cntLFTM = cntFTM;
    Serial.print( "New Fine Tune #" );
    Serial.println( cntFTM );
  }
}
 
That looks useful - is there a tested compliable version? with RTC start and such ?
I'm currently away from my development computer and the code which they are part of. So just some notes that elaborate on the interrupt routines and how they are tied into my code.

As said, this is a work in progress. I didn't mention that it is a new part of the code I use to animate sculptures. There are thousands of lines unrelated code. I should turn it into a library but I haven't taken the time to do that yet.

intCount is just a counter tracking executions of the interrupt routine. It could be deleted.

For the Teensy hardware RTC, just set and start it like normal. For some 3.x Teensys some additional hardware like a crystal and capacitors are needed. For the 3.6 I'm using I added a 1F super capacitor charged from the 3.3 VDC rail via a resistor and diode in series as the RTC backup battery. It will ride through multi hour power outages. It won't handle multi-day power outages.

The GPS PPS interrupt is just a normal interrupt routine tied to an IO pin. I use code based on the GPS RTC example to get the time from the GPS for initially setting the Teensy RTC time.

The name of the Teensy PPS interrupt routine will need to be changed to the one expected as described in a 2015 thread on setting up a PPS interrupt routine for the Teensy RTC. And obviously it also needs to be otherwise set up like described in the thread. if I remember correctly two registers need to be modified, and the interrupt routine needs a specific name which is mentioned in that thread. Once that was done the routine is called each time the Teensy real time clock increments the seconds.

The GPS PPS interrupt routine alone will get the Teensy RTC to within +/- a few clock ticks of the GPS PPS. With fine tune trimming I have only been able to get the Teensy RTC within +/- 1 clock tick most of the time with a worst case of +/- 2 ticks. Theoretically it should be able to be tuned within plus or minus .5 clock ticks. The way the Teensy RTC is adjusted it just isn't able to be tuned any more accurately. for what I need I don't think I needed any more accurate than +/- 5 clock ticks. I need to write the code to save the plus and minus ticks applied to the clock over a long period of time at stable temperatures. From that data I can then calculate RTC adjustments for specific temperatures.

I did change the length of the Ring buffers from 256 to 16. I didn't feel I needed any more than that. I write my data tracking info to a buffer where it can be written to a file or the serial output.

I still need to work on gathering clock correction data for tuning based on temperature. I don't have an oven large enough to fit the prototype sculpture I have been testing this code on. I will need to set up a Teensy specially for doing the temperature calibration data capture.
 
Looking forward to any updates when possible. (cross) Posted above is base code working with provided _isr()'s and it seems a piece or so is missing as noted - and another clue is that ' fti++ ' grows unbounded and never checked back to 0 as posted.
 
Status
Not open for further replies.
Back
Top