Arduino RTClib explained

p1403360560_ds1307_small

One of the main functionalities of the clock will be… suprise surprise, keep track of time!

For this task we’ll interface with a DS1307 RTC using the RTClib. You can just include that library and forget how it works. But that’s not the way this blog works. Let’s dive a little deeper.

RTC Library

This nice RTC library will let us “talk” to the RTC without problem. I’ll split this post in three parts:

  • First a check on the header.
  • Second, a quick check on the DS1307 itself. (You can’t implement if you don’t know what to implement)
  • Last, the implementation of the library.

Disclaimer and small print: It seems I’ve been using an old RTClib, which works but has less functionality that the one linked. I’m not even sorry.

RTCLib Header

This section is a nice stroll through the header of the RTClib.

class DateTime {

Ok, a class DateTime. Makes sense. What does this class provide?

public:
  DateTime (uint32_t t =0);
  DateTime (uint16_t year, uint8_t month, uint8_t day,
     uint8_t hour =0, uint8_t min =0, uint8_t sec =0);
  DateTime (const char* date, const char* time);
  uint16_t year() const     { return 2000 + yOff; }
  uint8_t month() const     { return m; }
  uint8_t day() const       { return d; }
  uint8_t hour() const      { return hh; }
  uint8_t minute() const    { return mm; }
  uint8_t second() const    { return ss; }
  uint8_t dayOfWeek() const;
  // 32-bit times as seconds since 1/1/2000
  long secondstime() const;   
  // 32-bit times as seconds since 1/1/1970
  uint32_t unixtime(void) const;

A Bunch of functions related to recorded time (and public). Those are clearly the functions that we are meant to use. Create a DateTime from an uint32_t or a bunch of: year, month,… etc. For this library, probably, create a datetime from the RTC itself.

After the creation, we can check the value of the time with the “getters” (year(), month(),…), I personally like that there’s also a dayOfTheWeek() function :-D, even if I don’t use it.

It has a separation of time since 1/1/2000 and the more standard time, the epoch time.

protected:
    uint8_t yOff, m, d, hh, mm, ss;

As protected, we have the data storage. Nothing too fancy.

Finally, there are two other classes:

class RTC_DS1307 {
public:
  static uint8_t begin(void);
    static void adjust(const DateTime& dt);
    uint8_t isrunning(void);
    static DateTime now();
}

and

class RTC_Millis {

RTC_DS1307 and RTC_Millis. Both classes seem to provide the same functionality via the functions: adjust() begin() now(). The now() function returns the date with the previously defined DateTime instance.

For our case, we don’t care about the Millis class, we are interested in the RTC_DS1307, that will give us the functionality to connect with the TinyRTC module.

DS1307 Datasheet(before implementing)

Before going into the functions, let’s see what are we talking to, the DS1307. For that, we’ll bring in a corresponding datasheet and look up how it works. From there, we gather the following:

  • It is indeed an I2C serial device.
  • It stores and counts the time. (Duh, an RTC)
  • It has 8 registers. (7 for time, 1 for control)
  • The time is stored in BCD format.
  • Day of the week is user defined.
  • When reset (or first power) the time is 01/01/00 01 00:00:00 (MM/DD/YY DOW HH:MM:SS)

Of course it has a plethora of information, but for us, the most important thing is the defined registers:

DS1307 RTC registers

DS1307 RTC registers

I’ll explain specifics of the registers when (if) they are needed. It is also the first time in my life I stumble upon the BCD format. It won’t be discussed in depth here, suffice to say that each digit is stored separately (24 is stored as two digits, 0010 and 0100 instead of 0001 1000).

And even more important, the ID to address the device:

(…)the slave address byte contains the 7-bit DS1307 address, which is 1101000(…)
DS1307 Datasheet

RTCLib Implementation

We’ll skip the implementation of DateTime. You can check if yourself in github. But Let’s see how the RTC_DS1307 class manages.

First off, the basic defines and includes

#include <Wire.h>
#include <avr/pgmspace.h>
#include "RTClib.h"

#define DS1307_ADDRESS 0x68
#define SECONDS_PER_DAY 86400L

#define SECONDS_FROM_1970_TO_2000 946684800

#if (ARDUINO >= 100)
 #include <Arduino.h>
#else
 #include <WProgram.h>
#endif
  • Wire.h: Arduino provided library to connect to I2C devices.
  • avr/pgmspace.h: Program space utilities library for AVR architecture. Not necessary for the following explanation, but I’ll use it in the future.
  • RTClib.h: The defined RTCLib
  • DS1307_ADDRESS: Self explained. The I2C address to DS1307 RTC
  • ARDUINO>100: Select Arduino library to load accordingly to IDE

The ARDUINO>100 define selects which version of the RTC_DS1307 class to compile. Here we’ll discuss the ARDUINO>100 = TRUE version of later Arduinos.

The list of functions used for this implementation is:

static uint8_t bcd2bin (uint8_t val);
static uint8_t bin2bcd (uint8_t val);
uint8_t RTC_DS1307::begin(void);
uint8_t RTC_DS1307::isrunning(void);
void RTC_DS1307::adjust(const DateTime& dt);
DateTime RTC_DS1307::now();

Let’s go step by step:

>>> bcd2bin and bin2bcd
static uint8_t bcd2bin (uint8_t val) { 
    return val - 6 * (val >> 4); 
}
static uint8_t bin2bcd (uint8_t val) { 
    return val + 6 * (val / 10); 
}

This two functions move the bits up and down to translate between BCD format and more common base-2 binary. We won’t enter into specifics, but those are used to write/read registers.

>>> begin
uint8_t RTC_DS1307::begin(void) {
  return 1;
}

Okay. Begin is a dummy function. NEXT!

>>> isrunning
uint8_t RTC_DS1307::isrunning(void) {
  Wire.beginTransmission(DS1307_ADDRESS);
  Wire.write(i);	
  Wire.endTransmission();

  Wire.requestFrom(DS1307_ADDRESS, 1);
  uint8_t ss = Wire.read();
  return !(ss>>7);
}

Two parts. A signal tramsission to the DS1307 and a request of information. The respective function descriptions can be read from the Arduino Wire library documentation.

  • beginTransmission: Starts an I2C transmission.
  • write: writes something to the bus
  • endTransmission: Writes the End transmission to the bus.
  • requestFrom: Request N bytes from the device
  • receive: Read bytes

The initial trasmission writes i, with value 0, always. Why name it i is beyond my comprehension.

Second, we request 1 byte. That will be the first register, and returns the value of the most significant bit. From the datasheet this bit is CH (Clock Halt). If this bit is set to 1, means that the oscillator is disabled, otherwise (zero) it is enabled.

>>> now
DateTime RTC_DS1307::now() {
  Wire.beginTransmission(DS1307_ADDRESS);
  Wire.write(i);	
  Wire.endTransmission();
  
  Wire.requestFrom(DS1307_ADDRESS, 7);
  uint8_t ss = bcd2bin(Wire.read() & 0x7F);
  uint8_t mm = bcd2bin(Wire.read());
  uint8_t hh = bcd2bin(Wire.read());
  Wire.read();
  uint8_t d = bcd2bin(Wire.read());
  uint8_t m = bcd2bin(Wire.read());
  uint16_t y = bcd2bin(Wire.read()) + 2000;
  
  return DateTime (y, m, d, hh, mm, ss);
}

As the function name suggests. It will retrieve the current time, and return it formatted as a DateTime instance (we discussed it in earlier sections).

This time we’ll request 7 bytes (registers). As seen on the datasheet, the bytes are recieved in the following order: seconds, minutes, hours, day, date, month, year. This function simply reads and process the data using the bcd functions, that do some magic.

Between hour and day, the function skips one byte. This byte is the day of the week byte. This implementation of RTClib skips this value. The latest and greatest version probably uses it.

>>> adjust
void RTC_DS1307::adjust(const DateTime& dt) {
    Wire.beginTransmission(DS1307_ADDRESS);
    Wire.write(i);
    Wire.write(bin2bcd(dt.second()));
    Wire.write(bin2bcd(dt.minute()));
    Wire.write(bin2bcd(dt.hour()));
    Wire.write(bin2bcd(0));
    Wire.write(bin2bcd(dt.day()));
    Wire.write(bin2bcd(dt.month()));
    Wire.write(bin2bcd(dt.year() - 2000));
    Wire.write(i);
    Wire.endTransmission();
}

This is exactly the same as the now function but inverted. Write the registers instead of reading

Conclusion

Know your libraries, it may save you some time in the future.

To be fair, when I wrote the clock application I simply used the library without diving into it. Of course I had a look into the header file, but since it simply worked, there was no reason the check.

While writing this blog post, I realized it was getting quite boring, and the basics of how to use this RTC were already covered in so many other posts. So I decided to focus in the RTClibrary (for those who want to know). And maybe (just maybe) I’ll write a post on how to use the lib, but I’m sure that the linked pages are enough for everyone to wire an RTC to an Arduino.

References

RTCLib on github ⇒GO
DS1307 datasheet ⇒GO

Advertisement

4 Comments

Filed under code

4 responses to “Arduino RTClib explained

  1. Dr. Eric L. LePage

    I looked for a simple method of getting my RTC to give the exact local time. rtc.adjust certainly doesn’t work the way one thinks it is supposed to; I cannot even give it the time with an offset. “Knowing my libraries” means I end up working for computers, instead of them working for me.

    • kxtells

      I understand the frustration when a library does not work as one expects, we’ve all been there. That is why it is so important to “know your libraries”, even more when the library is “provided as is” just out of good will, and without any kind of documentation except the source code.

      I believe you do not end up working for computers, you end up working to realize what another fellow human meant by “adjust” in this particular case.

      For the clock, I remember calling adjust once to set it up, and giving a user flow to change the time whenever was necessary.

  2. Carlos Sanchez

    Hi, is there a way I can call a function that returns current time since epoch?
    I’ve tried
    “uint32_t t=unixtime() ;
    Serial.println(t);”
    But an error message appears saying I have to declare it:
    ” exit status 1
    ‘unixtime’ was not declared in this scope”
    Thanks.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.