[SOLVED] Problem Synchronizing micros() Between Two Arduinos

Hey there -

I have a setup with three (3) Arduino Dues where I’m trying to synchronize the microsecond timestamps between two of them. In my current setup, I have a central “time server”, which is booted up before all other Arduinos. This Arduino just responds with its own timestamp via I2C. The code is below.

The remaining two Arduinos are booted up sequentially (as not to crash on the I2C bus). Upon boot, each Arduino pings (via I2C) the time server Arduino and records the timestamp that the server reports. Then when a button is pressed on pin 10, the Arduinos will print out via Serial the [time server timestamp + micros()] (the timestamp in the reference frame of the time server…or close enough). The code is below.

In my current test rig, I have both Arduinos connected to the same button. However, the timestamps are off. Here are a few timestamps after a lot of button presses:

ARDUINO 1	ARDUINO 2
SYNCING…	SYNCING…
97987306	97987360
105037345	105037442
105196726	105196824
120266032	120266224
120357427	120357620
128765414	128765659
128918271	128918517
136696763	136697058
136950139	136950435
136960594	136960890
147941940	147942305
148092120	148092486
158248603	158249032
158454726	158455157
158464636	158465067
163082746	163083206
163189754	163190214
164674937	164675406
164811519	164811989
167329300	167329786
167424599	167425085
168005684	168006174
168155157	168155623
168581157	168581652
168687951	168688445
183834746	183835336
183981555	183982146
187934177	187934792
188088331	188088948
188949054	188949676
188958589	188959870
189129449	189130072
189617876	189618502
189807592	189808219

Notice how they’re kinda close at the beginning, but as time goes on they’re way off (in terms of microseconds… :wink: )

I’m not really sure how to tackle this and would appreciate any advice.

Thanks a lot!
Aakash

Time Server code:

#include <Wire.h>

String timeValue; // Variable to hold time

void setup()
{
  timeValue.reserve(10); // Reserve 10 bytes for unsigned long time
  Wire.begin(0x50); // Setup I2C slave address as 0x50     
  Wire.onRequest(requestEvent); // Register event
}

void loop() {
 // Do nothing
}

void requestEvent()
{
  timeValue = String(micros());  // convert timestamp to string
  volatile int timeLength = timeValue.length();  // find out how many digits are in the current time
  // If not 10 digits (max digit length for unsigned long), add leading zeros
  // This is to keep the time of I2C transfers consistent between subsequent read/writes
  if(timeLength == 1){
    timeValue = "000000000" + timeValue;
  }
  else if(timeLength == 2){
    timeValue = "00000000" + timeValue;
  }
  else if(timeLength == 3){
    timeValue = "0000000" + timeValue;
  }
  else if(timeLength == 4){
    timeValue = "000000" + timeValue;
  }
  else if(timeLength == 5){
    timeValue = "00000" + timeValue;
  }
  else if(timeLength == 6){
    timeValue = "0000" + timeValue;
  }
  else if(timeLength == 7){
    timeValue = "000" + timeValue;
  }
  else if(timeLength == 8){
    timeValue = "00" + timeValue;
  }
  else if(timeLength == 9){
    timeValue = "0" + timeValue;
  }
  Wire.print(timeValue); // respond with padded timestamp
}

Timer Sync code:

#include <Wire.h>

char dataFromTimeServer[10]; // char array to hold I2C data from TimeServer
unsigned long timeFromTimeServer = 0;  // unsigned long to hold time from TimeServer

unsigned long currentSyncedTime = 0;

const int buttonPin = 10;

void setup() {
  Serial.begin(9600);
  Serial.print("SYNCING...");
  syncTime(); // Sync time with TimeServer
  
  digitalWrite(buttonPin,HIGH);  // enable pull up resitor
  attachInterrupt(buttonPin, buttonPress, FALLING);  // register interrup on FALLING state
}


void loop() {
 if(currentSyncedTime!=0){
   Serial.println(currentSyncedTime);
   currentSyncedTime=0;
 }
}
// Run when falling
void buttonPress(){
  // Record timestamp in reference frame of TimeServer
  currentSyncedTime = timeFromTimeServer + micros();
}

// Method to read timestamp from central TimeServer over I2C
void syncTime(){
  Wire.begin();  // Initialize I2c
  
  Wire.requestFrom(0x50, 10);  // Request 10 bytes
  int index = 0;  // index of char array
  while(Wire.available())   // while more bytes avail
  { 
    char c = Wire.read();    // receive a byte as character
    dataFromTimeServer[index++] = c;  // store byte in char array
    
  }
  timeFromTimeServer = atol(dataFromTimeServer);  // convert char array to unsigned long
  
}

Millis() won’t run at exactly the same speed on every device. Crystals will have slightly different tolerances, temperature will introduce errors etc.

If you want to keep them in sync then you will need to periodically re-sync with your ‘master’. How often will depend on the rate of drift and what tolerance you place on total drift before resync.

Would it be possible to wire up multiple AVR chips to the same oscillator? In which case, wouldn't their clocks remain synced? I'm not sure if that would be as simple as wiring up the appropriate pins to the same crystal, or whether you would need to invoke the "external clock" option (section 8.8 of the AVR328 datasheet). It seems like maybe the CLKO pin on the master device could be used to drive the "external clock" pin on the slave devices.

Tack - yep! That was it. The clocks were drifting. I got it to stay within 26 microseconds by syncing every 5 seconds.

For any future strugglers, here's a quick write up with code included: http://aaka.sh/patel/2013/12/23/synchronizing-micros-millis-arduino/

joshuabardwell - It seems possible! A quick search came up with promising results. I might try it one day!

Thanks all!
Aakash

joshuabardwell:
Would it be possible to wire up multiple AVR chips to the same oscillator?

Yes.

In which case, wouldn't their clocks remain synced?

Perfectly.

I'm not sure if that would be as simple as wiring up the appropriate pins to the same crystal, or whether you would need to invoke the "external clock" option (section 8.8 of the AVR328 datasheet).

Latter.

It seems like maybe the CLKO pin on the master device could be used to drive the "external clock" pin on the slave devices.

Exactly.

You can probably reduce the drift rate by making sure you are using crystal, rather than oscillators.

If all units are in close proximity then daisy chaining the clock signal, as stated above, is an option. If they are physically separated then your reync code could be easily modified to work over RF, although you might need to factor in an adjustment for the send/receive times.