GPS Clock Version 3 Part 3

A few things must happen. Although I can't decide which to attack next. I'll get started on one and see what happens.

As at my last post we can now use the RTC's 1PPS output via an Arduino interrupt to trigger output to the serial monitor; the display isn't fitted yet on Version 3, see Version 2. We also have a workaround for the problem of using the Wire library from an interrupt handler. Would you go for the jugular now and try to wham it all together? I wouldn't.

Today I want to:
  • Move the RTC code out of the main sketch and create it's own library (RTC.h and RTC.cpp)
  • Add a DateTime library to the sketch, probably my own.
I'm going back to an empty project and building up from there. Ages ago I posted in Baby Steps how to create a class from scratch. Having a new device, the RTC, is a prime example of when you'd want to do this in Arduino development. So apologies to the gun programmers here, I'm going to do a walkthrough in simple terms again. I don't expect I'll cover everything a novice needs, so ask questions!
Start a new sketch in Arduino. I've got a blank sketch and I'm saving just that as GPS_Clock_Version_3_Part_2. Arduino doesn't like spaces in names and will replace those with underscores, so I just did it myself.

Fill in the 'shell' of a new sketch. I use the same starting code every time. This basic sketch compiles, uploads, runs and gives me some basic output to show everything is set up OK. From the (I don't know what to call it, it's on the top right) context menu, add a New Tab.

Call the new file RTC.h. Create another new tab called RTC.cpp. Paste in the 'shell' of code for a new header file into RTC.h:

#ifndef RTC_h
#define RTC_h

class RTC
{
  private:
  public:
};

#endif

And do the same for RTC.cpp, which at the moment is only one line:

#include "RTC.h"

I realise many Arduinians are of the 'one sketch per project' persuasion, so a brief bit of detail on classes is warranted. What's a class? Why did we create one? Well, a class is a template for a new thing, an object in object-oriented programming. An object in programming terms is a collection of code that represents a real or imaginary 'thing'. In our case, we want a code 'thing' to represent a physical RTC clock device.

We've created RTC.h and RTC.cpp and these two files together define a class called RTC. In our main sketch we want to instantiate (create) an object of the RTC type and this will be the representation in code of the physical device. We'll also have a GPS class and an LED class for those devices. Our main sketch will hopefully boil down to a few lines of well-designed code that make everything run, like (I'm imagining):

// check the clocks are synchronised
// if not, set the RTC from the GPS
if (rtc.now() != gps.now()) rtc.set(gps.now());
// display the current RTC time on the LED display
led.display(rtc.now());

The header file (RTC.h) is kind of the 'public face' of the class. It shows you all the bits of the class you as a programmer might want to use; what methods (functions) it has, what inputs (parameters) and outputs (return types) each method has. There's no code in the header file, just declarations of methods. The implementation file (RTC.cpp) is where all the code lives. If a class has been properly created, you shouldn't need to look in the implementation file - ever! Only the person who wrote the class needs to see that.

So we have a new class for RTC. This will handle the RTC hardware and contain all the code we need to interact with it. The project will also have classes for GPS and LED hardware as well as a DateTime class that does all the critical work with the date and time information that every other class uses. Interestingly three out of four classes we need represent physical objects. Only the DateTime class is abstract. So when we're finished, the project will be 9 files: the main sketch and two files each for four classes. You can see from my Version 2 code that it uses 3 classes and has 7 files.

So what's the stuff we pasted into RTC.h? The top two lines and the bottom one work together and is called a macro guard.

#ifndef RTC_h
#define RTC_h
#endif

If, as we will do with the DateTime class later, you call a class into your project more than once (you reference it with an #include statement) you'll create nasty errors. The class that's been defined gets defined again and everything breaks. The macro guard sets up a nickname for the class "RTC_h" in the line #define RTC_h. Each time the header file is read (by the precompiler, don't worry) it checks to see if the nickname has been set up before #ifndef RTC_h (ifndef = 'if not defined'). If it hasn't seen RTC_h before, it reads the entire file. If RTC_h has been seen before, it jumps to #endif and the whole file is skipped.

The bit in the middle of the file is the good stuff. This is where we name the class. class RTC should match the names of the files you put it in (RTC.h and RTC.cpp) or you risk confusing the hell out of yourself.

class RTC
{
  private:
  public:
};

Everything within that set of curly braces belongs to the class and falls into two main categories. The private: stuff stays within an RTC object and can't be accessed from other code. The public: stuff will be accessed from other objects or from the main sketch. Usually a class will have some data that it wants to control, that's private. If the the outside world wants to interact with an object's private data, it will have to ask nicely by using a method that's been declared publicly accessible.

At this stage the RTC.cpp file only contains an #include directive and that says "read the header file first".

Now we'll get the last post's code and cannibalise it. Let's grab the following functions from the sketch and copy them into RTC.cpp under the #include line: decToBcd, bcdToDec, setDateDs1307, getDateDs1307. Each 'function' is now a 'method' and needs some changes before it can work.

Copy the declaration of the methods, the bits before the opening curly braces, into the header file RTC.h, put them under the public: heading and terminate them with a semicolon. The completed header file should look like this:

#ifndef RTC_h
#define RTC_h

class RTC
{
  private:
  public:
    byte decToBcd(byte val);
    byte bcdToDec(byte val);
    void setDateDs1307(byte second, 
                       byte minute, 
                       byte hour, 
                       byte dayOfWeek, 
                       byte dayOfMonth, 
                       byte month, 
                       byte year);
    void getDateDs1307(byte *second,
                       byte *minute, 
                       byte *hour,
                       byte *dayOfWeek,
                       byte *dayOfMonth,
                       byte *month,
                       byte *year);
};
#endif


Go back to the implementation file (RTC.cpp) and change the names of the methods so that RTC:: (yes, that's two colons) is put in front of the method name, for example...

byte decToBcd(byte val)

... is changed to ...

byte RTC::decToBcd(byte val)

... and so on for the other three methods. So far you've made these changes because I've told you to. Let's change the tactics here and start trying to get Arduino to verify (compile) our sketch. I'm on Windows at the moment, so I'm hitting Ctrl+R to compile.

The first error I got is in RTC.h 'byte' does not name a type. Ah, I didn't include Arduino.h in the new RTC class. Once you've chased this particular error for a day you remember forever! I added #include "Arduino.h" above the line class RTC. I compiled again and got a new error, so the previous one was fixed. The next error is in RTC.cpp: 'Wire' was not declared in this scope. Again, I've missed an include statement that was in my previous sketch. I added #include "Wire.h" to the top of RTC.h. Compile again, new problem, old one's fixed.

RTC.cpp: 'DS1307_I2C_ADDRESS' was not declared in this scope. Yup, similar issue. I copied the #define for this from the previous sketch to the RTC.h file, next to the #include lines. RTC.cpp: 'decToBcd' was not declared in this scope. Ah, now we have a problem. This function was put in the RTC class and we're calling it from the RTC class, so why can't it find it? It seems I've missed adding RTC:: to the front of the method name! Fixed, compiled, done.

So the sketch now compiles. Great. But it doesn't do anything! Let's get something running, in the main sketch:
  • Add #include "RTC.h" to the top of the file
  • Add #include "Wire.h" to the top of the file
  • At the bottom of setup(), past in the code from the function getDateTime() from my previous post
  • Immediately above what you just posted in, add the lines Wire.begin(); and RTC rtc;
  • Two lines down from there is the call to getDateDs1307(), put rtc. in front of that function name.
The main sketch now looks like this:

#include "RTC.h"
#include "Wire.h"

void setup()
{
  Serial.begin(115200);
  Serial.println("setup()");
  
  // BEGIN ADDED
  Wire.begin();
  RTC rtc;

  byte second, minute, hour, dayOfWeek, dayOfMonth, month, year;

  rtc.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;
  }
  // END ADDED
}

void loop()
{
  Serial.println("loop()");
  delay(1000);
}

Upload and run. You should get output that looks something like this:


setup()
2:55:12  31/3/12  Day of week:Tuesday
loop()
loop()
loop()
loop()


Whew. That wasn't all that I aimed at covering today. But that'll do. Full code for today is here.

That is all.

Comments