GPS Clock Version 3 Part 6

Blinking the Colon


I wanted to get the colon on the clock display to blink. Instead of doing it by copying code from Version 2 I created a new solution. The main thing I was thinking about was how there's an interrupt signal every second. That's not enough. I'd like to have the colon on for half a second, then off for half a second.

A quick change in the LED class means the blink() method now allows a boolean argument and that determines if blink() is turning the colon on or off.


LED.h Extract

    void blink(boolean on);

LED.cpp Extract

void LED::blink(boolean on)
{
  port->write(0x77); // decimal point
  if (on) port->write(16);
  else port->write(64);
}

Main Sketch

#include "Wire.h"
#include "RTC.h"
#include "GPS.h"
#include "LED.h"

RTC rtc;
GPS gps;
LED led;

const int colonOnTime = 500;
boolean interruptComplete = false;
boolean colonOn = false;
unsigned long lastInterruptTime = 0;

void setup()
{
  Serial.begin(115200);
  Serial1.begin(4800); // for GPS
  Serial2.begin(4800); // for LED
  gps.setPort(Serial1);
  led.setPort(Serial2);
  led.write("GPS ");
  attachInterrupt(0, interruptHandler, FALLING);
}

void loop()
{
  if (interruptComplete)
  {
    if (gps.isSet())
    {
      rtc.setDateTime(gps.localDateTime());
    }
    DateTime now = rtc.getDateTime();
    led.write(now, LED::HoursMinutes);
    interruptComplete = false;
  }
  
  // control of blinking of the colon on the clock
  boolean colonOnNext = false;
  if ((millis() - lastInterruptTime) < colonOnTime) colonOnNext = true;
  if (colonOn != colonOnNext)
  {
    colonOn = colonOnNext;
    led.blink(colonOn);
  }
  gps.getData();
}

void interruptHandler()
{
  interruptComplete = true;
  lastInterruptTime = millis();
}


Blinking colon. Sweet. Now on to a big problem.

The problem is that the whole operation of this circuit is dependent on the SQW output (one pulse per second) from the RTC. The interesting thing is, this output doesn't exist under a particular condition. When the RTC has been powered-off (battery removed) it starts up in an 'un-set' condition. In this condition the date/time has not been set, the internal 'clock' is not running and the SQW output is absent.

With no SQW output, our interruptHandler() is not called, the current RTC time is never compared to the GPS time, the RTC time is never set and therefore the SQW signal never starts running. So what do we do about this? Well, we know that the interruptHandler() in the main sketch won't be called, but loop() always runs. So we know our code is not 'dead', it's just a matter of how to handle the situation where SQW is missing.

A present, when the clock is booted, it displays "GPS " on the screen. This will never go away. I'm thinking of using a simple delay() in the loop() function to trigger the clock-checking. This would operate in parallel with the SQW signal, however the two will be interlocked, so that once the SQW signal starts, the delay() loop is permanently deactivated.

While the delay() loop is operating, I want to display a spinning "wait cursor" next to the word GPS to show that it's working on something and not just totally dead. For this I'll need a new method in the LED class to start, spin and turn off the wait cursor. I'm adding this as the wait(boolean) method. This will require implementation of some of the 7-segment display's functionality listed under 3.5 Single Character Control in the manual.

Unhelpfully, the manual omits the segment diagram needed to understand this paragraph:

The following figure shows the bit representation for each decimal point; 0 is the least significant bit:
Example: To illuminate the B, E, and G segments of the third digit, first send the digit 3 single character control command (0x7D) followed by a data byte with
the 1st, 4th and 6th bits set (0x52). So, send: (0x7D) (0x52)

In the absence of this information we'll do a little experimentation. I've written a wait() method that outputs the special character 0x7E (digit 4 control) followed by a number that cycles through 1, 2, 4, 8, 1 ... etcetera to see which segments light up.

Added to LED.h

    void wait();

Added to LED.cpp

void LED::wait()
{
  static int segment = 0;
  switch (segment)
  {
    case 1:
      segment = 2;
      break;
    case 2:
      segment = 4;
      break;
    case 4:
      segment = 8;
      break;
    case 8:
      segment = 1;
      break;
    default:
      segment = 1;
      break;
  }
  port->write(0x7E); // decimal point
  port->write(segment);
}

In the main sketch I've added:
  • a boolean variable called rtcRunning
  • a small block of code at the beginning of loop() using if (!rtcRunning) as a condition
Testing this I found the segments I lit with the numbers 1, 2, 4, 8 were, according to the digram below, 1, 2, 3 and 4 - not quite what I want.


A little fiddling with numbers yields:
  • outputting the number 1 lights segment 1
  • outputting the number 2 lights segment 2
  • outputting the number 4 lights segment 3
  • outputting the number 8 lights segment 4
  • outputting the number 16 lights segment 5
  • outputting the number 32 lights segment 6
  • outputting the number 64 lights segment 7
I just want a top-half wait cursor, so I'll need the sequence 1, 2, 64, 32. Here's the code in my main sketch:

#include "Wire.h"
#include "RTC.h"
#include "GPS.h"
#include "LED.h"

RTC rtc;
GPS gps;
LED led;

const int colonOnTime = 500;
boolean interruptComplete = false;
boolean rtcRunning = false;
boolean colonOn = false;
unsigned long lastInterruptTime = 0;

void setup()
{
  Serial.begin(115200);
  Serial1.begin(4800); // for GPS
  Serial2.begin(4800); // for LED
  gps.setPort(Serial1);
  led.setPort(Serial2);
  led.write("GPS ");
  attachInterrupt(0, interruptHandler, FALLING);
}

void loop()
{
  if (!rtcRunning)
  {
    led.wait();
    delay(100);
    gps.utcDateTime().print();
    Serial.println();
    if (gps.isSet()) rtc.setDateTime(gps.localDateTime());
  }
  if (interruptComplete)
  {
    if (gps.isSet()) rtc.setDateTime(gps.localDateTime());
    DateTime now = rtc.getDateTime();
    led.write(now, LED::HoursMinutes);
    interruptComplete = false;
  }
  
  // control of blinking of the colon on the clock
  boolean colonOnNext = false;
  if ((millis() - lastInterruptTime) < colonOnTime) colonOnNext = true;
  if (colonOn != colonOnNext)
  {
    colonOn = colonOnNext;
    led.blink(colonOn);
  }
  gps.getData();
}

void interruptHandler()
{
  rtcRunning = true;
  interruptComplete = true;
  lastInterruptTime = millis();
}

Something's not quite right. If I take out the RTC battery then reinsert it, put the RTC back on the breadboard and then start the circuit I get the GPS wait cursor and the time never displays. If I reset the circuit, the time appears. It's like the RTC is getting set, but the SQW output doesn't start until after a reboot. Strange. It must be a simple logic fail.

Well after half an hour of removing and reinserting the RTC battery and changing code I still haven't fixed it. It seems the RTC needs a physical push to get the SQW output to function. If I pull out the 5V supply momentarily, off she goes. Weird. I can imagine powering the RTC from an Arduino output pin would fix it. Set the time, drop the power momentarily, and the SQW output starts running. But why the stuffing around?

Ah! It seems to be an issue with starting the SQW output in software. If the SQW register is forced on-off-on, it works. I bodged this code in the RTC::setDateTime() to try this out:

void RTC::setDateTime(DateTime value)
{
  Wire.beginTransmission(DS1307_I2C_ADDRESS);
  Wire.write(0);
  Wire.write(decToBcd(value.second()));    // 0 to bit 7 starts the clock
  Wire.write(decToBcd(value.minute()));
  Wire.write(decToBcd(value.hour()));     
  Wire.write(decToBcd(value.dayOfWeek()));
  Wire.write(decToBcd(value.day()));
  Wire.write(decToBcd(value.month()));
  Wire.write(decToBcd(value.year()));
  Wire.write(B00010000); // sends 0x10 (hex) 00010000 (binary) to control register - turns on square wave
  Wire.endTransmission();
  
  //Testing junk code starts here
  delay(500);
  Wire.beginTransmission(DS1307_I2C_ADDRESS);
  Wire.write(0);
  Wire.write(decToBcd(value.second()));    // 0 to bit 7 starts the clock
  Wire.write(decToBcd(value.minute()));
  Wire.write(decToBcd(value.hour()));     
  Wire.write(decToBcd(value.dayOfWeek()));
  Wire.write(decToBcd(value.day()));
  Wire.write(decToBcd(value.month()));
  Wire.write(decToBcd(value.year()));
  Wire.write(B00000000); // sends 0x10 (hex) 00010000 (binary) to control register - turns on square wave
  Wire.endTransmission();
  delay(500);
  Wire.beginTransmission(DS1307_I2C_ADDRESS);
  Wire.write(0);
  Wire.write(decToBcd(value.second()));    // 0 to bit 7 starts the clock
  Wire.write(decToBcd(value.minute()));
  Wire.write(decToBcd(value.hour()));     
  Wire.write(decToBcd(value.dayOfWeek()));
  Wire.write(decToBcd(value.day()));
  Wire.write(decToBcd(value.month()));
  Wire.write(decToBcd(value.year()));
  Wire.write(B00010000); // sends 0x10 (hex) 00010000 (binary) to control register - turns on square wave
  Wire.endTransmission();
  //Testing junk code ends here
}

After a little play I've come up with a simpler fix. When the time is initially set using if (!rtcRunning) I then set the time twice. This kicks off the RTC's SQW output every time. It's a kludge because I don't know why it works. I don't like implementing a solution without the understanding of why it works, but this one's got me. Full code here.

That is all.




Comments

  1. Hi..Great articles, great job.!!! Folowing your posts,I made the same setup and run the code from the 6-th part..I made some changes for the localtime,printed on serial monitor (now.print)..The time is ok, but the year is 61 instead 13..What I'm doing wrong ? Forgot to mention that I'm totally newbie in C++..:)).assembler beeing my "native" language..:)Furthermore, I want to pick the time variables to compare them with some alarms...The code beeing so compressed,I'm wondering where from to pick them?..Thanks again for this "Arduino Handbook"..

    ReplyDelete
  2. Hi again...Sorry to say but the DateTime library gives weird results concerning year and day of week calculations..The same things as above...

    ReplyDelete
  3. Hi, do you have any solution for me? I like very much those libs, and I wanna use them to get full date on a display..

    ReplyDelete

Post a Comment