GPIO output with GPS sync

Hello,

I have 2 breadboards with the following setup:

My goal is to synchronized the ds3231 using a time pulse of 1Hz/0.1s from a NEO6 gps and whenever the gps signal goes down to keep having that 1Hz/0.1s alive!
The code bellow doesn't behave as i was expecting and i don't know why!

#include <DS3231.h>
#include <Wire.h>

#define TRIGGER_LAMPS 10
#define GPS_SYNC 2        //int 0
#define PULSE_LENGTH 100  //100ms
#define SYNC_RATE 20      //20sec

DS3231 myRTC;

byte prevSecond = 0;
byte currSecond = 0;
bool timeTosync = 0;

unsigned long sec_ss = 0; 

void setup() {
  Serial.begin(57600);
  Wire.begin();
  pinMode(TRIGGER_LAMPS, OUTPUT);
  pinMode(GPS_SYNC, INPUT);
  attachInterrupt(digitalPinToInterrupt(GPS_SYNC), syncWithGps, RISING);
  myRTC.setSecond(0);
}
void loop() { 
  currSecond = myRTC.getSecond();
  if ( currSecond != prevSecond ) {
    delay(900);
    digitalWrite(TRIGGER_LAMPS, HIGH);
    delay(100);
    digitalWrite(TRIGGER_LAMPS, LOW);
    prevSecond = currSecond;
    sec_ss++;
  }
  if (timeTosync && (sec_ss > SYNC_RATE)) {
    sec_ss = 0;
    timeTosync = 0;
    myRTC.setSecond(0);
  }
}
void syncWithGps() {
  timeTosync = 1;
}

Maybe tell the forum what you were 'expecting' to happen and what actually happens ?

I uploaded the code on 2 breadboards and i notice that their output is not synchronized. I was expecting to have a synchronized output on both of them after 21 seconds from start!
My goal is to use these two devices to blink 2 lamps in the same time, the lamps are 600m apart. The gps signal in that location is not great that's why i went with rtc ds3231. From time to time i loose gps signal for a couple of hours!

I'm having trouble following your code, and that may be because I don't really understand what you want to do. The GPS interrupt occurs every second and sets the flag, but the flag only gets cleared every 20 seconds. Anyway, maybe you could explain exactly what you want to happen.

In any case, timeTosync needs to be a volatile variable.

I want to output a HIGH on pin d10 everytime the seconds of the rtc changes. I also want every 20 seconds to reset the rtc to zero based on the timepulse that i receive from the gps module. I want to do this because i need my two breadboards to output a HIGH on d10 in the same time.

Did your system work when they were side-by-side?

they are side by side as we speak and they don't work! I don't mind having the output of the 2 setups out of sync until both gps modules are fixed on sattelites. I need them to output in the same time after the gps timepulse arrives and continue to output in the same time in case the gps signal is lost!

Well, that is NOT going to happen. The two devices need to communicate with each other to stay in sync. Even two identical pendulums will not stay in sync when they are side by side.

Well you might try changing the declaration of timeTosync to volatile:

volatile bool timeTosync = 0;

So you don't really need to know the time of day at all. You're just looking at when the seconds register changes.

I would point out that if you set the DS3231 to output squarewave at 1Hz, that output will go low at exactly the beginning of each second. So instead of reading the seconds value from the RTC over and over until it changes, the RTC could generate an interrupt for you at the beginning of each second.

Also, the DS3231 has automatic temperature compensation, as well as an aging register that lets you calibrate it to keep time to within a few seconds a year. So I wonder if GPS gives you a meaningful benefit, particularly if you lose reception for hours at a time. Might the DS3231 be good enough on its own?

Try adding another line to that IF:

  if (timeTosync && (sec_ss > SYNC_RATE)) {
    sec_ss = 0;
    timeTosync = 0;
    myRTC.setSecond(0);
    prevSecond = 0;
  }

I will try with "volatile" but i don't think this is was is causing this not to work!
I don't thing you understand what i'm after, maybe i'm bad at explaining!

We have 2 independent devices, A and B, each device has and output, A_out and B_out. Device A is powered at 12:00:0, device B is powered at 12:00:5.3 (5 seconds and 300 ms). Both outputs A_out and B_out are outputting "1" every second. After 50 seconds the gps of device 1 is locked on sattelites and so it starts delivering the timepulse, the seconds register of device A is reset to 0. After 1 minute the gps of device B is ready to deliver timepulse and it reset the seconds reg of rtc and by doing this we get rid of those 300ms between blinks. Now both devices blink in the same time (well maybe a couple of ms that are not visible by eye) even though one has started earlier and the second one was resetted later.

Ok, so each device has its own GPS receiver, and their PPS signals should be in sync. Well, try the volatile declaration and the other change I suggested above, and see what happens. Also, with the delays totalling one second, you'll be getting output on D10 every time you go through the loop, so there's no chance for the GPS to reset the RTC asynchronously. You might try eliminating the first (900) delay altogether.

Will try tomorrow what you suggested and keep you informed! It's freezing outside to wait for the gps to pick up signal now! :grin:

Dont forget that the time put out by the GPSs could be 2-3 seconds out depending on whether they have both actually received the leap seconds update that goes out every 12.5 minutes.

The GPS/RTC idea seems like a very complex way to achieve this. Why not use a cable or RF connection between the two lamps?

I dont see that you need to read the actual time from the GPS at all.

But first, where are these lights located and why does the GPS apparently fail after some period ?

Second turn the light on when the GPS time sync pulse appears and then reset and start the RTC from time 0 and start the 1hz output. Turn on the light when the GPS time pulse OR the 1hz output goes active. No need for the RTC to know the actual time.

The cable option is out of discussion, there's no way i can run that cable between the 2 devices.
RF could be an option but i have buildings between the devices and that might be an issue!
I only use the timepulse pin of the GPS, when the gps is locked on sattelites it outputs a 1Hz signal on a pin and i use that to reset the RTC seconds register to zero! Also the system could expand in the future and i might have devices that are located much far away from each other.

I think the problem with your original code is that there's no connection between the time the GPS interrupt takes place and the time the seconds value is reset to zero. So if the interrupt takes place in the middle of the 900ms delay, there's nothing that resets seconds immediately, or exactly one second later. It just resets about a second after it last changed value.

The code below records the millis() time of the interrupt in the ISR when it takes place. Then the seconds reset takes place one second later. Also, I thought it made sense to move the reset IF up into the main IF. And the variable declarations have changed. Anyway, see what you think.

#include <DS3231.h>
#include <Wire.h>

#define TRIGGER_LAMPS 10
#define GPS_SYNC 2        //int 0
#define PULSE_LENGTH 100  //100ms
#define SYNC_RATE 20      //20sec

DS3231 myRTC;

byte prevSecond = 0;
byte currSecond = 0;
volatile long GPSmillis = 0;
volatile int sec_ss = 0;

void setup() {
  Serial.begin(57600);
  Wire.begin();
  pinMode(TRIGGER_LAMPS, OUTPUT);
  pinMode(GPS_SYNC, INPUT);
  attachInterrupt(digitalPinToInterrupt(GPS_SYNC), syncWithGps, RISING);
  myRTC.setSecond(0);
}
void loop() {
  currSecond = myRTC.getSecond();
  if ( currSecond != prevSecond ) {
    digitalWrite(TRIGGER_LAMPS, HIGH);
    delay(100);
    digitalWrite(TRIGGER_LAMPS, LOW);
    prevSecond = currSecond;

    if (sec_ss == SYNC_RATE) {
      sec_ss = 0;
      prevSecond = 61;                    //make sure won't match next loop
      delay(GPSmillis + 999 - millis());  //delay until almost 1 sec after GPS
      myRTC.setSecond(0);
    }
    else delay(895);                      //if not resetting, normal delay
  }
}
void syncWithGps() {
  GPSmillis = millis();
  sec_ss++;
}

I got it working with my initial code + "volatile" attribute for timeTosync variable, i also added a pull down resistor on pin d2 (GPS_SYNC) since that one was floating.
Also tested your last version which is working great, i initially thought about alligning gps pulse with driver output but i was too focused on getting it working!

Thank you for support!

Is it critical that the TRIGGER_LAMPS output be a 900mS LOW followed by a 100mS HIGH, or is there some flexibility in one or both of those times? Trying to maintain an exact 1000mS cycle using delay() or millis(), while at the same time trying to stay synced with the RTC/GPS second, is not going to work unless you can guarantee the millis() counter is running slightly fast. Also, which is the important part of the TRIGGER_LAMPS output, the 900mS LOW, or the 100mS HIGH?

I like the suggestion of using the SQW output of the DS3231. Use that to trigger an interrupt, instead of spending the time to actually read the RTC.

Now that you actually have your code working, this is my idea of how to do it:

#include <DS3231.h>
DS3231 myRTC;

#define RTC_1HZ_PIN 3      // RTC 1Hz interrupt
#define TRIGGER_LAMPS 10
#define GPS_SYNC 2        //int 0
#define PULSE_LENGTH 100  //100ms
#define SYNC_RATE 20      //20sec

enum _lampStatus : uint8_t {
  lampIdle,
  lampStartDelay,
  lampPulseDelay
} lampStatus = lampIdle;

unsigned long lampTimer = 0;
unsigned long currentMillis;

byte timeTosync = SYNC_RATE;
volatile bool lampTrigger = false;
volatile bool syncSeconds = false;

void setup() {
  //Serial.begin(57600);
  Wire.begin();

  pinMode(TRIGGER_LAMPS, OUTPUT);
  pinMode(GPS_SYNC, INPUT_PULLUP);
  pinMode(RTC_1HZ_PIN, INPUT_PULLUP);
  
  digitalWrite(TRIGGER_LAMPS, LOW);
  
  myRTC.enableOscillator(true, false, 0); //enable 1Hz square wave output

  attachInterrupt(digitalPinToInterrupt(RTC_1HZ_PIN), incrementTime, FALLING);

  attachInterrupt(digitalPinToInterrupt(GPS_SYNC), syncWithGps, RISING);
}

void loop() {
  currentMillis = millis();
  if (syncSeconds) {
    syncSeconds = false;
    myRTC.setSecond(0);
  }
  switch (lampStatus) {
    case lampIdle:
      if (lampTrigger) {
        lampTrigger = false;
        lampTimer = currentMillis;
        lampStatus = lampStartDelay;
      }
      break;
    case lampStartDelay:
      //early abort if GPS sync caused an early lamp trigger
      if (lampTrigger) {
        lampStatus = lampIdle;
      }
      if ((currentMillis - lampTimer) >= 900ul) {
        digitalWrite(TRIGGER_LAMPS, HIGH);
        lampTimer = currentMillis;
        lampStatus = lampPulseDelay;
      }
      break;
    case lampPulseDelay:
      if (lampTrigger || ((currentMillis - lampTimer) >= 100ul)) {
        digitalWrite(TRIGGER_LAMPS, LOW);
        lampStatus = lampIdle;
      }
      break;
  }
}

void incrementTime() {
  lampTrigger = true;
}

void syncWithGps() {
  timeTosync--;
  if (timeTosync == 0) {
    syncSeconds = true;
    timeTosync = SYNC_RATE;
  }
}
1 Like