GPS Clock Version 3 Part 1

This is not a "done it" blog post, it's a "doing it". The goal of this build is to preserve my Version 2 functionality while fixing a few issues, as documented in my last post.

It's always best to simplify. When doing something complex, the only way is to simplify. (There's got to be some awesome quote somewhere about that). Rather than try to tack on an RTC to my existing project I'm going to learn and develop the RTC separately. Then I should have the knowledge I need to integrate it into the existing project.

Real Time Clock (RTC)

There are any number of RTC modules available. The one I have is the Real Time Clock Module from Sparkfun Electronics. I suppose many are very similar implementations of the DS1307 module from Dallas Semiconductor.

Connection

First I need to know how to connect it. Go Google. An example circuit shows the pin connections. Another circuit shows pull-up resistors. Hmm. I wonder if I really need those? The schematic for the board tells me there aren't any pull-up resistors on the Sparkfun board, so I need to decide if I should add those externally. The DS1307 data sheet tells me this:

"SDA (Serial Data Input/Output) – SDA is the input/output pin for the 2-wire serial interface.  The SDA pin is open drain which requires an external pullup resistor."

Interesting the same is not said in the description for the SCL line:

SCL (Serial Clock Input) – SCL is used to synchronize data movement on the serial interface.

So the only proper thing to do is check the spec for I2C and see what it says. I found this link that shows both SDA and SCL lines tied to 5V with a pull-up resistor. So that settles it for me.

Here's my schematic:

And here's the real thing:

Coding

A quick hunt around for Arduino and RTC leads me to John Boxall's blog tronixstuff. Chapter Seven happens to use the DS1307 to teach I2C communications. I've used John's stuff a number of times, so when I see a Google hit for his site I go for it.

Reading, reading, reading. There's lots to learn and know. Ooh, a link to code. I wish I could link you to the part in his post that links to the code. But it's a good thing I can't because you really should read his whole post. Roughly half way through he says "Please examine this file". So do that. Grab his file, download it.

I had a bit of trouble getting his file to run. The Arduino environment kept complaining. To solve it, I downloaded the file example7p3.ino to my Downloads folder, then created a folder next to it called example7p3 and then moved the file into the folder. When I double clicked the folder it opened in the Arduino editor. If you haven't got .ino files configured to open automatically, just open Arduino first, then open the file from within the Arduino editor.

tronixstuff's Code

So let's upload John's code and see what happens. I've just set my COM port and Board type and hit Upload. I opened the Serial Monitor but nothing happened. Hmm. Oh, what baud rate did he use? 9,600. That's the problem. After setting the correct baud rate in the serial monitor, I got a meaningful output.

So we're good. The code (whatever it is, haven't looked yet) is doing something and obviously works with the RTC. So the connections are good and some basic code works. Great. Good start.

The first thing I want to pursue is not so much the setting and reading of the correct time (the RTC's main function) but I want to get at the "one pulse per second" (1PPS) output. The GPS I've been using (EM-406A) has a 1PPS output and I've based my code around using that to update the time display on my clock.  Since I need to incorporate the RTC, I need some way of synchronising my clock updates with the RTC clock. If worst came to worst I could still use the 1PPS signal from the GPS.

Having set up my test circuit with an LED connected to the 1PPS output from the RTC, I'm disappointed to find nothing is happening on that output. Reading through John's code, I see a comment in void setDateDs1307 stating that it's turning on the square wave output.

  Wire.write(00010000); // sends 0x10 (hex) 00010000 (binary) to control register - turns on square wave

So he thinks it's doing it. But I can't see it working. Hmm, if I were going to send a binary value to a function, I would think it needs to be marked as a binary value. Otherwise 00010000 is interpreted as the decimal value 10,000. Checking the Arduino documentation for constants shows the binary value should be preceded with a 'B'. A one-character code change, an upload, and bargain, it works! My LED is now flashing a one pulse per second signal.

Going For Broke

If everything worked like my dreams, I'd just add an interrupt to the code and print out the date and time when the interrupt signal is received from the RTC's 1PPS output. Go for it!

I go through my code library looking for some code I've written to work with interrupt handlers. I found InterruptTest. Here it is:

const int led = 13;

void setup()
{
  attachInterrupt(0, blink, RISING);
  pinMode(led, OUTPUT);
}

void loop()
{
}

void blink()
{
  static boolean state;
  state = !state;
  digitalWrite(led, state);
}

So, armed with a working example of how to handle an interrupt, I splice the two code sources together. The variable pin is defined at the top of the sketch as a constant, and blink() has been added. In setup() there are two new lines to set up the interrupt handler that will call blink() when the 1PPS signal changes and to configure pin 13 for output.

After uploading the new sketch, bingo, the on-board LED on the Arduino is blinking in synchronicity with the LED near the RTC! I can open the serial monitor and see date/time info is still being output from the loop() function. So far so good.

Code:

/*
 Example 7.3
 reading and writing to the Maxim DS1307 real time clock IC
 tronixstuff.com/tutorials
 based on code by Maurice Ribble
 17-4-2008 - http://www.glacialwanderer.com/hobbyrobotics
*/

#include "Wire.h"
#define DS1307_I2C_ADDRESS 0x68

const int led = 13;

void blink()
{
  static boolean state;
  state = !state;
  digitalWrite(led, state);
}

// Convert normal decimal numbers to binary coded decimal
byte decToBcd(byte val)
{
  return ( (val/10*16) + (val%10) );
}

// Convert binary coded decimal to normal decimal numbers
byte bcdToDec(byte val)
{
  return ( (val/16*10) + (val%16) );
}

// 1) Sets the date and time on the ds1307
// 2) Starts the clock
// 3) Sets hour mode to 24 hour clock

// Assumes you're passing in valid numbers

void setDateDs1307(byte second,        // 0-59
byte minute,        // 0-59
byte hour,          // 1-23
byte dayOfWeek,     // 1-7
byte dayOfMonth,    // 1-28/29/30/31
byte month,         // 1-12
byte year)          // 0-99
{
  Wire.beginTransmission(DS1307_I2C_ADDRESS);
  Wire.write(0);
  Wire.write(decToBcd(second));    // 0 to bit 7 starts the clock
  Wire.write(decToBcd(minute));
  Wire.write(decToBcd(hour));     
  Wire.write(decToBcd(dayOfWeek));
  Wire.write(decToBcd(dayOfMonth));
  Wire.write(decToBcd(month));
  Wire.write(decToBcd(year));
  Wire.write(B00010000); // sends 0x10 (hex) 00010000 (binary) to control register - turns on square wave
  Wire.endTransmission();
}

// Gets the date and time from the ds1307
void getDateDs1307(byte *second,
byte *minute,
byte *hour,
byte *dayOfWeek,
byte *dayOfMonth,
byte *month,
byte *year)
{
  // Reset the register pointer
  Wire.beginTransmission(DS1307_I2C_ADDRESS);
  Wire.write(0);
  Wire.endTransmission();

  Wire.requestFrom(DS1307_I2C_ADDRESS, 7);

  // A few of these need masks because certain bits are control bits
  *second     = bcdToDec(Wire.read() & 0x7f);
  *minute     = bcdToDec(Wire.read());
  *hour       = bcdToDec(Wire.read() & 0x3f);  // Need to change this if 12 hour am/pm
  *dayOfWeek  = bcdToDec(Wire.read());
  *dayOfMonth = bcdToDec(Wire.read());
  *month      = bcdToDec(Wire.read());
  *year       = bcdToDec(Wire.read());
}

void setup()
{
  byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;
  Wire.begin();
  Serial.begin(9600);

  attachInterrupt(0, blink, RISING);
  pinMode(led, OUTPUT);

  // Change these values to what you want to set your clock to.
  // You probably only want to set your clock once and then remove
  // the setDateDs1307 call.

  second = 0;
  minute = 47;
  hour = 0;
  dayOfWeek = 3;
  dayOfMonth = 31;
  month = 3;
  year = 12;
  setDateDs1307(second, minute, hour, dayOfWeek, dayOfMonth, month, year);
}

void loop()
{
  byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;

  getDateDs1307(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month, &year);
  Serial.print(hour, DEC);// convert the byte variable to a decimal number when being displayed
  Serial.print(":");
  if (minute<10)
  {
      Serial.print("0");
  }
  Serial.print(minute, DEC);
  Serial.print(":");
  if (second<10)
  {
      Serial.print("0");
  }
  Serial.print(second, DEC);
  Serial.print("  ");
  Serial.print(dayOfMonth, DEC);
  Serial.print("/");
  Serial.print(month, DEC);
  Serial.print("/");
  Serial.print(year, DEC);
  Serial.print("  Day of week:");
  switch(dayOfWeek){
  case 1: 
    Serial.println("Sunday");
    break;
  case 2: 
    Serial.println("Monday");
    break;
  case 3: 
    Serial.println("Tuesday");
    break;
  case 4: 
    Serial.println("Wednesday");
    break;
  case 5: 
    Serial.println("Thursday");
    break;
  case 6: 
    Serial.println("Friday");
    break;
  case 7: 
    Serial.println("Saturday");
    break;
  }
  //  Serial.println(dayOfWeek, DEC);
  delay(1000);
}

Let's take it to the next level. The interrupt handler blink() needs to do the job of the loop() function. That is, the interrupt signal (1PPS from RTC) should come in to the Arduino causing Interrupt 0 to fire. This calls blink() and code executes to output the date/time to the serial monitor. As a quick hack, just take all the code out of loop() except the delay(1000) line at the end, and moved that into blink(). And the result? Nothing. No blinking and no data in the serial monitor. Bugger.

An hour or so of research and experimentation went into the next step. The outcome was that I learned the Wire library (used with the RTC) uses interrupts to control reads and writes on the I2C bus. From previous experience I've found the use of interrupts to be somewhat limited, there are a number of side effects of using them. I wondered if the interrupts used by the Wire library could interfere with the interrupt I was setting up? I commented the following line that's now in the blink() function:

  //getDateDs1307(&second, &minute, &hour, &dayOfWeek, &dayOfMonth, &month, &year);

This time the blinking LED returned. So there's definitely a problem with initiating an I2C communique during an interrupt handler. Now people with some assembler chops or better understanding of AVR microcontrollers might instantly know what to do next, but I don't. I tried re-enabling the interrupt handler each time the time is read from the RTC to no avail.

After a bit more experimentation I decided just to concede at this point that getting the interrupt handler to initiate a read from the RTC can't be made to work. I'm going to work around it. Perhaps I can use the interrupt handler to push a value out to the display and that's all. In the second in-between display updates, when the interrupt handler is definitely not in action, I can read back the date/time from the RTC and cache it ready for the next display. I'll put that in play next.

That is all.

Comments