Friday, October 17, 2025

GPS Clocks: Automatic Daylight Savings Time and Baudrate Configuration

Third K6JCA GPS Clock

Third GPS Clock:

For my third GPS clock (the second one to use the PCB I designed and had fab'd in China), I chose to build it into a small metal box I purchased on Amazon:

In fact, I designed the PCB to fit within this case, as you can see from the photo, below:


GPS Receiver Baudrate:

The previous two clocks (see here and here) used GPS receivers that I had configured to power-up with a serial-port baudrate of 38,400 baud.  Configuration was done using U-BLOX's "u-center" app, and I used this same app to store the new baudrate within the receivers so that they would power-up at the new baudrate.

But when I tried to configure a third GPS receiver for this new GPS clock, it would not store the new baudrate, but instead it would default back to 9600 baud at power-up.

Hmmm -- well, if I cannot store the new baudrate, why not have the Arduino Nano, at power up, always reprogram the receiver to the new baudrate?  Then it doesn't matter if I can or cannot store it.


Adding Automatic Baudrate Detection and Configuration:

I did some quick Googling and found the list of UBX commands used by the two types of GPS receiver's I had at hand.  Rather than write the code myself, I asked ChatGPT to do it.  Below is the code, shown in a simplified GPS Clock sketch.

// ChatGPT-generated code to detect and set a GPS receiver's
// baud rate using a U-BLOX UBX port-configuration command.

#include <SoftwareSerial.h>

#define GPS_RX 6   // Nano receives GPS data on pin 6
#define GPS_TX 11  // Nano transmits GPS data on pin 11

SoftwareSerial gpsSerial(GPS_RX, GPS_TX);

// Possible GPS baud rates (try these for detection)
const long supportedBauds[] = {4800, 9600, 19200, 38400};
const int numBauds = sizeof(supportedBauds) / sizeof(supportedBauds[0]);

// User-chosen Nano/GPS baud
const long desiredBaud = 38400;   // <-- change to what you want

// -----------------------------------------------------------------
// Build and send UBX-CFG-PRT message to set GPS baud dynamically
// -----------------------------------------------------------------
void sendUBXsetBaud(long baud) {
  uint8_t msg[] = {
    0xB5, 0x62,             // sync
    0x06, 0x00,             // CFG-PRT
    0x14, 0x00,             // payload length = 20

    // payload (20 bytes)
    0x01, 0x00, 0x00, 0x00, // portID=1, reserved, txReady=0
    0xD0, 0x08, 0x00, 0x00, // mode = 0x08D0 (8N1), little endian
    0x00, 0x00, 0x00, 0x00, // baud placeholder
    0x07, 0x00,             // inProtoMask: UBX+NMEA
    0x03, 0x00,             // outProtoMask: UBX+NMEA
    0x00, 0x00,             // flags
    0x00, 0x00              // reserved
  };

  // Fill baud (little-endian) at payload[8..11]
  msg[14] = (uint8_t)(baud & 0xFF);
  msg[15] = (uint8_t)((baud >> 8) & 0xFF);
  msg[16] = (uint8_t)((baud >> 16) & 0xFF);
  msg[17] = (uint8_t)((baud >> 24) & 0xFF);

  // Fletcher checksum
  uint8_t ckA = 0, ckB = 0;
  for (int i = 2; i < sizeof(msg); i++) {
    ckA += msg[i];
    ckB += ckA;
  }

  // Send message
  for (int i = 0; i < sizeof(msg); i++) gpsSerial.write(msg[i]);
  gpsSerial.write(ckA);
  gpsSerial.write(ckB);
}

// -----------------------------------------------------------------
// Try a baud rate, return true if NMEA sentences detected
// -----------------------------------------------------------------
bool detectBaud(long baud) {
  gpsSerial.begin(baud);
  unsigned long start = millis();
  String sentence = "";
  while (millis() - start < 1000) { // 1 second timeout
    while (gpsSerial.available()) {
      char c = gpsSerial.read();
      if (c == '\n') {
        if (sentence.startsWith("$GP") || sentence.startsWith("$GN")) {
          return true; // NMEA detected
        }
        sentence = "";
      } else {
        sentence += c;
      }
    }
  }
  return false;
}

// -----------------------------------------------------------------
void setup() {
  Serial.begin(115200);
  delay(2000);
  Serial.println(F("GPS Baud Auto-Detect & Configure"));

  long currentBaud = -1;

  // Detect current GPS baud
  for (int i = 0; i < numBauds; i++) {
    if (detectBaud(supportedBauds[i])) {
      currentBaud = supportedBauds[i];
      Serial.print(F("Detected GPS baud: "));
      Serial.println(currentBaud);
      break;
    }
  }

  if (currentBaud == -1) {
    Serial.println(F("No GPS detected at supported bauds!"));
    return;
  }

  // If GPS baud not what we want → change it
  if (currentBaud != desiredBaud) {
    Serial.print(F("Attempting to change GPS baud to "));
    Serial.println(desiredBaud);

    bool success = false;
    for (int attempt = 1; attempt <= 3; attempt++) {
      Serial.print(F("  Attempt "));
      Serial.println(attempt);

      gpsSerial.begin(currentBaud); // talk at old baud
      sendUBXsetBaud(desiredBaud);

      delay(500); // let GPS switch
      gpsSerial.begin(desiredBaud);

      if (detectBaud(desiredBaud)) {
        Serial.println(F("  ✔ Baud change verified OK!"));
        success = true;
        break;
      } else {
        Serial.println(F("  ✘ Verification failed"));
      }
    }

    if (!success) {
      Serial.println(F("❌ Could not change GPS baud after 3 tries."));
    }
  } else {
    Serial.println(F("GPS already at desired baud."));
  }
}

void loop() {
  // Pass GPS data to USB Serial for monitoring
  while (gpsSerial.available()) {
    Serial.write(gpsSerial.read());
  }
}

This worked great, and I incorporated it into the code for all my clocks.

But I still had a few more bare-PCB boards from the fab.  What to do with them?


My Fourth GPS Clock: 

For this clock I used an Adafruit 4-digit seven-segment display.  Besides being noticeably larger, the difference between this display and the TM1637 displays I had used previously is that this display uses an I2C interface.


Unfortunately, I hadn't thought to add an I2C interface to my PCB design.  But the modifications are simple -- two cuts, two jumpers, as I show on the schematic, below:


I also decided I wanted to minimize the number of round-holes-drilled and rectangular-holes-cut in whatever case I used.  

To this end, I found a box on Amazon with a clear top.  Great!  No rectangular holes required!


And here's the clock:


Unlike my earlier GPS clocks, there is no toggle-switch on this clock to select between Daylight-Savings-Time (DST), Standard Time, or GMT.  I decided to forego GMT time and also to program the Nano to automatically adjust between DST and Standard-Time, depending upon the date and time it received from the GPS receiver.  So no switch needed.

Auto-DST mode is selected by shorting both the nGMT and the nDST signals to ground.  Note that if the DST/Standard-Time/GMT toggle switch is installed, these two signals are never grounded simultaneously, so the same revision of Nano code can be used for either version: DST/Standard-Time/GMT toggle switch installed or no switch but with auto-DST.

For more on the Automatic Daylight Savings Time code, see my previous post...


My Fifth GPS Clock: 

The displays of my previous clocks were all limited to four seven-segment digits.  Why not make a multi-line clock and display the GPS-received date, along with the derived weekday, too?  (Date and day modified to account for local time offset from GMT time).

Here is the clock, using a 4-line by 20 character display:

I had purchased the display a few years ago when I was designing my 500 Watt HF Amplifier project.

The bottom line of the display's four lines is used to display various parameters and menus for setting up the receiver (selected using the two push-buttons on top of the case).  The image above shows this 4th line displaying GMT time.  And the image below shows it displaying the number of satellites being used by the receiver:


The display itself has a parallel interface, but I converted it to I2C with an I2C adapter (purchased from Amazon) that solders onto the display's PCB:

Note that the 4-pin connector's GND and VCC pins are swapped compared to their assignment for the Adafruit I2C interface.  I handle this difference by swapping the VCC and GND wires in the cable connecting the PCB to the LCD Display Adapter.

The LCD backlight can only be turned on or off -- it's brightness cannot be varied.  So I use the "ambient-light" photoresistor to turn off the backlight at night, when the room has turned dark (this feature can be disabled via a menu selection).


That's it!  I have one more Clock PCB left.  Will it become a clock, or...?


Standard Caveat:

As always, I might have made a mistake in my equations, assumptions, drawings, or interpretations.  If you see anything you believe to be in error or if anything is confusing, please feel free to contact me or comment below.

And so I should add -- this information is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

No comments: