Library for DS1305 Real Time Clock

Hi,

I've produced a library for the DS1305 RTC, I'd like to contribute it to the Arduino community, but thought I'd better get it code reviewed first!

All comments welcome; but please don't be too harsh - this is the first C++ I've done in years!

RTC/DS1305.h

#ifndef RTC_DS1305_h
#define RTC_DS1305_h

#include "WConstants.h"

class DS1305 {
      public:
            DS1305(int enable);
            DS1305(int enable, bool combineAlarms);

            bool init();
            
            bool getTrickleChargeSettings(int& diodes, int& resistance);
            bool setTrickleChargeSettings(const int diodes, const int resistance);

            void getDate(int& year, int& month, int& date);
            void setDate(const int year, const int month, const int date);

            void getTime(int& hour, int& minute, int& second);
            void setTime(const int hour, const int minute, const int second);

            bool setAlarm(const int alarm);
            bool setAlarm(const int alarm, const int seconds);
            bool setAlarm(const int alarm, const int seconds, const int minutes);
            bool setAlarm(const int alarm, const int seconds, const int minutes, const int hours);
            bool setAlarm(const int alarm, const int seconds, const int minutes, const int hours, const int day);

            bool setAlarmBcd(const int alarm, const byte seconds, const byte minutes, const byte hours, const byte day);
            bool getAlarmBcd(const int alarm, byte& seconds, byte& minutes, byte& hours, byte& day);

            bool alarmEnable(const int alarm, bool enable);
            bool alarmEnabled(const int alarm);
            
            bool alarmRaised(const int alarm);
            void alarmRaised(bool& alarm0, bool& alarm1);

            void alarmClear(const int alarm);

            bool write(const byte buffer[], const int size);
            bool write(const byte buffer[], const int size, const int offset);
            bool write(const byte data, const int offset);

            int read(byte buffer[], const int size);
            int read(byte buffer[], const int size, const int offset);
            byte read(const int offset);

      private:
            int Enable;
            int Clr;
            int CombineAlarms;

            void getBcdDate(byte& year, byte& month, byte& date);
            void setBcdDate(const byte year, const byte month, const byte date);

            void getBcdTime(byte& hour, byte& minute, byte& second);
            void setBcdTime(const byte hour, const byte minute, const byte second);

            void writeRtcRegister(const byte registerName, const byte data);
            byte readRtcRegister(const byte registerName);

            void beginSpiTransferRead(const byte registerName);
            void beginSpiTransferWrite(const byte registerName);
            void beginSpiTransfer(const byte registerName);
            char spiTransfer();
            char spiTransfer(volatile char data);
            void endSpiTransfer();
            
            byte decToBcd(const int dec);
            int bcdToDec(const byte bcd);
};

#endif

RTC/DS1305.cpp

#include "DS1305.h"

#define MOSI 11 //MOSI ds1305 SDI 12
#define MISO 12  //MISO ds1305 SDO 13
#define SPI_CLK 13 //sck ds1305 clk 11

#define DS1305_WRITE_OFFSET 0x80

#define DS1305_SECOND                   0x00
#define DS1305_MINUTE                   0x01
#define DS1305_HOUR                         0x02
#define DS1305_DAY                               0x03
#define DS1305_DATE                         0x04
#define DS1305_MONTH                         0x05
#define DS1305_YEAR                         0x06

// Alarm 0 register addresses
#define DS1305_ALARM0_SECOND             0x07
#define DS1305_ALARM0_MINUTE             0x08
#define DS1305_ALARM0_HOUR                  0x09
#define DS1305_ALARM0_DAY                        0x0A

// Alarm 1 register addresses
#define DS1305_ALARM1_SECOND             0x0B
#define DS1305_ALARM1_MINUTE             0x0C
#define DS1305_ALARM1_HOUR                  0x0D
#define DS1305_ALARM1_DAY                        0x0E

#define DS1305_ALARM_EVERY      B10000000

// Registers
#define DS1305_CONTROL_REGISTER 0x0F
#define DS1305_STATUS_REGISTER      0x10
#define DS1305_CHARGE_REGISTER      0x11

// control register bit definitions
#define DS1305_CONTROL_EOSC                  7
#define      DS1305_CONTROL_WP                        6
#define DS1305_CONTROL_INTCN            2
#define DS1305_CONTROL_AIE1                  1
#define DS1305_CONTROL_AIE0                  0

// status register bit definitions
#define DS1305_STATUS_IRQF1                  1
#define DS1305_STATUS_IRQF0                  0

#define DS1305_USER_RAM                              0x20
#define DS1305_USER_RAM_BYTES            96

DS1305::DS1305(int enable)
      : Enable(enable),
            Clr(0),
            CombineAlarms(false) {
}

DS1305::DS1305(int enable, bool combineAlarms)
      : Enable(enable),
            Clr(0),
            CombineAlarms(combineAlarms) {
}

bool DS1305::init() {
      pinMode(MOSI, OUTPUT);
      pinMode(MISO, INPUT);
      pinMode(SPI_CLK, OUTPUT);
      
      pinMode(Enable, OUTPUT);
      digitalWrite(Enable, LOW);

      byte data = readRtcRegister(DS1305_CONTROL_REGISTER);
      writeRtcRegister(DS1305_CONTROL_REGISTER, data &= ~(1 << DS1305_CONTROL_WP)); // set write protect (low)

      data &= ~(1 << DS1305_CONTROL_EOSC); // Osc enable (low)

      if(CombineAlarms) // Interrupt control
            data &= ~(1 << DS1305_CONTROL_INTCN);
      else
            data |= (1 << DS1305_CONTROL_INTCN);

      writeRtcRegister(DS1305_CONTROL_REGISTER, data);

      return (readRtcRegister(DS1305_CONTROL_REGISTER) & (1 << DS1305_CONTROL_EOSC)) == 0;
}

bool DS1305::getTrickleChargeSettings(int& diodes, int& resistance) {
      byte data = readRtcRegister(DS1305_CHARGE_REGISTER);
      
      diodes = ((data & B00001100) >> 2);
      resistance = 2 * (data & B00000011);
      if(resistance == 6)
            resistance = 8;

      return (data & B11110000) == B10100000;
}

bool DS1305::setTrickleChargeSettings(const int diodes, const int resistance) {
      byte data = B10100000;

      if(diodes == 1 || diodes == 2)
            data |= diodes << 2;
      else
            data |= B11110000;

      if(resistance == 2 || resistance == 4)
            data |= (resistance / 2);
      else if(resistance == 8)
            data |= B11;
      else
            data |= B11110000;
      
      writeRtcRegister(DS1305_CHARGE_REGISTER, data);
      
      return (readRtcRegister(DS1305_CHARGE_REGISTER) & B11110000) == B10100000;
}

void DS1305::setBcdDate(const byte year, const byte month, const byte date) {
      beginSpiTransferWrite(DS1305_DATE);
      spiTransfer(date);
      spiTransfer(month);
      spiTransfer(year);
      endSpiTransfer();
}

void DS1305::setDate(const int year, const int month, const int date) {
      setBcdDate(decToBcd(year), decToBcd(month), decToBcd(date));
}

void DS1305::getBcdDate(byte& year, byte& month, byte& date) {
      beginSpiTransferWrite(DS1305_DATE);
      date = spiTransfer();
      month = spiTransfer();
      year = spiTransfer();
      endSpiTransfer();
}

void DS1305::getDate(int& year, int& month, int& date) {
      getBcdDate((byte&)year, (byte&)month, (byte&)date);
      year = bcdToDec((byte)year);
      month = bcdToDec((byte)month);
      date = bcdToDec((byte)date);
}

void DS1305::setBcdTime(const byte hour, const byte minute, const byte second) {
      beginSpiTransferWrite(DS1305_SECOND);
      spiTransfer(second);
      spiTransfer(minute);
      spiTransfer(hour);
      endSpiTransfer();
}

void DS1305::setTime(const int hour, const int minute, const int second) {
      setBcdTime(decToBcd(hour), decToBcd(minute), decToBcd(second));
}

void DS1305::getBcdTime(byte& hour, byte& minute, byte& second) {
      beginSpiTransferWrite(DS1305_SECOND);
      second = spiTransfer();
      minute = spiTransfer();
      hour = spiTransfer();
      endSpiTransfer();
}

void DS1305::getTime(int& hour, int& minute, int& second) {
      getBcdTime((byte&)hour, (byte&)minute, (byte&)second);
      hour = bcdToDec((byte)hour);
      minute = bcdToDec((byte)minute);
      second = bcdToDec((byte)second);
}

bool DS1305::setAlarm(const int alarm) {
      return setAlarmBcd(alarm, DS1305_ALARM_EVERY, DS1305_ALARM_EVERY, DS1305_ALARM_EVERY, DS1305_ALARM_EVERY);
}

bool DS1305::setAlarm(const int alarm, const int seconds) {
      return setAlarmBcd(alarm, decToBcd(seconds), DS1305_ALARM_EVERY, DS1305_ALARM_EVERY, DS1305_ALARM_EVERY);
}

bool DS1305::setAlarm(const int alarm, const int seconds, const int minutes) {
      return setAlarmBcd(alarm, decToBcd(seconds), decToBcd(minutes), DS1305_ALARM_EVERY, DS1305_ALARM_EVERY);
}

bool DS1305::setAlarm(const int alarm, const int seconds, const int minutes, const int hours) {
      return setAlarmBcd(alarm, decToBcd(seconds), decToBcd(minutes), decToBcd(hours), DS1305_ALARM_EVERY);
}

bool DS1305::setAlarm(const int alarm, const int seconds, const int minutes, const int hours, const int day) {
      return setAlarmBcd(alarm, decToBcd(seconds), decToBcd(minutes), decToBcd(hours), decToBcd(day));
}

continued…

continued…

bool DS1305::setAlarmBcd(const int alarm, const byte seconds, const byte minutes, const byte hours, const byte day) {
      alarmEnable(alarm, false);

      if(alarm == 0)
            beginSpiTransferWrite(DS1305_ALARM0_SECOND);
      else if(alarm == 1)
            beginSpiTransferWrite(DS1305_ALARM1_SECOND);
      else
            return false;
      
      spiTransfer(seconds);
      spiTransfer(minutes);
      spiTransfer(hours);
      spiTransfer(day);
      
      endSpiTransfer();

      return alarmEnable(alarm, true);
}

bool DS1305::getAlarmBcd(const int alarm, byte& seconds, byte& minutes, byte& hours, byte& day) {
      if(alarm == 0)
            beginSpiTransferWrite(DS1305_ALARM0_SECOND);
      else if(alarm == 1)
            beginSpiTransferWrite(DS1305_ALARM1_SECOND);
      else
            return false;

      seconds = spiTransfer();
      minutes = spiTransfer();
      hours = spiTransfer();
      day = spiTransfer();

      endSpiTransfer();
      
      return true;
}

bool DS1305::alarmEnable(const int alarm, bool enable) {
      int aie;
      if(alarm == 0)
            aie = DS1305_CONTROL_AIE0;
      else if(alarm == 1)
            aie = DS1305_CONTROL_AIE1;
      else
            return false;

      byte data = readRtcRegister(DS1305_CONTROL_REGISTER);
      
      if(enable)
            data |= (1 << aie);
      else
            data &= ~(1 << aie);

      writeRtcRegister(DS1305_CONTROL_REGISTER, data);

      return alarmEnabled(alarm);
}

bool DS1305::alarmEnabled(const int alarm) {
      int aie;
      if(alarm == 0)
            aie = DS1305_CONTROL_AIE0;
      else if(alarm == 1)
            aie = DS1305_CONTROL_AIE1;
      else
            return false;

      return (readRtcRegister(DS1305_CONTROL_REGISTER) & (1 << aie)) == (1 << aie);
}

bool DS1305::alarmRaised(const int alarm) {
      bool alarm0;
      bool alarm1;

      alarmRaised(alarm0, alarm1);

      if(alarm == 0)
            return alarm0;
      
      if(alarm == 1)
            return alarm1;

      return false;
}

void DS1305::alarmRaised(bool& alarm0, bool& alarm1) {
      byte data = readRtcRegister(DS1305_STATUS_REGISTER);

      alarm0 = data & (1 << DS1305_STATUS_IRQF0) == (1 << DS1305_STATUS_IRQF0);
      alarm1 = data & (1 << DS1305_STATUS_IRQF1) == (1 << DS1305_STATUS_IRQF1);
}

void DS1305::alarmClear(const int alarm) {
      if(alarm == 0)
            beginSpiTransferWrite(DS1305_ALARM0_SECOND);
      else if(alarm == 1)
            beginSpiTransferWrite(DS1305_ALARM1_SECOND);
      else
            return;

      spiTransfer(); // Should reset the alarm flag

      endSpiTransfer();
}

bool DS1305::write(const byte buffer[], const int size) {
      return write(buffer, size, 0);
}

bool DS1305::write(const byte buffer[], const int size, const int offset) {
      if(size + offset > DS1305_USER_RAM_BYTES)
            return false;

      beginSpiTransferWrite(DS1305_USER_RAM + offset);

      for(int i = 0 ; i < size; ++i)
            spiTransfer(buffer[i]);
      
      endSpiTransfer();
      
      return true;
}

bool DS1305::write(const byte data, const int offset) {
      if(offset > DS1305_USER_RAM_BYTES)
            return false;

      writeRtcRegister(DS1305_USER_RAM + offset, data);

      return true;
}

int DS1305::read(byte buffer[], const int size) {
      return read(buffer, size, 0);
}

int DS1305::read(byte buffer[], const int size, const int offset) {
      if(offset > DS1305_USER_RAM_BYTES)
            return 0;

      beginSpiTransferWrite(DS1305_USER_RAM + offset);

      int i = 0;
      do {
            buffer[i++] = spiTransfer();
      }      while(i < DS1305_USER_RAM_BYTES);
      
      endSpiTransfer();
      
      return i;
}

byte DS1305::read(const int offset) {
      if(offset > DS1305_USER_RAM_BYTES)
            return 0;
      
      return readRtcRegister(DS1305_USER_RAM + offset);
}

void DS1305::writeRtcRegister(const byte registerName, const byte data) {
      beginSpiTransferWrite(registerName);
      spiTransfer(data);
      endSpiTransfer();
}

byte DS1305::readRtcRegister(const byte registerName) {
      beginSpiTransferWrite(registerName);
      byte data = spiTransfer(0xFF);
      endSpiTransfer();
      return data;
}

void DS1305::beginSpiTransferWrite(const byte registerName) {
      beginSpiTransfer(DS1305_WRITE_OFFSET | registerName);
}

void DS1305::beginSpiTransferRead(const byte registerName) {
      beginSpiTransferRead(registerName);
}

/*
      SPCR
      | 7    | 6    | 5    | 4    | 3    | 2    | 1    | 0    |
      | SPIE | SPE  | DORD | MSTR | CPOL | CPHA | SPR1 | SPR0 |

      SPIE - Enables the SPI interrupt when 1
      SPE - Enables the SPI when 1
      DORD - Sends data least Significant Bit First when 1, most Significant Bit first when 0
      MSTR - Sets the Arduino in master mode when 1, slave mode when 0
      CPOL - Sets the data clock to be idle when high if set to 1, idle when low if set to 0
      CPHA - Samples data on the falling edge of the data clock when 1, rising edge when 0
      SPR1 and SPR0 - Sets the SPI speed, 00 is fastest (4MHz) 11 is slowest (250KHz)

      0 0 CK/4
      0 1 CK/16
      1 0 CK/64
      1 1 CK/128
*/
void DS1305::beginSpiTransfer(const byte registerName) {
      SPCR = (1 << SPE) | (1 << MSTR) | (1 << CPHA);

      Clr = SPCR;
      Clr = SPDR;

      digitalWrite(Enable, HIGH);

      // send the address of the register we want to read first
      spiTransfer(registerName);
}

char DS1305::spiTransfer() {
      return spiTransfer(0x00);
}

char DS1305::spiTransfer(volatile char data) {
      // Writing to the SPDR register begins an SPI transaction
      SPDR = data;
      /*
      Loop right here until the transaction is complete. the SPIF bit is
      the SPI Interrupt Flag. When interrupts are enabled, and the
      SPIE bit is set enabling SPI interrupts, this bit will set when
      the transaction is finished.
      */
      while (!(SPSR & (1<<SPIF)))
      {
      };
      
      // received data appears in the SPDR register
      return SPDR;
}

void DS1305::endSpiTransfer() {
      // deselect the device...
      digitalWrite(Enable, LOW);
}

continued!

(last bit!)

byte DS1305::decToBcd(const int dec) {
      return ((dec / 10 * 16) + (dec % 10));
}

int DS1305::bcdToDec(const byte bcd) {
      return ((bcd / 16 * 10) + (bcd % 16));
}

and the example…

RTC/examples/DS1305/DS1305.pde

#include <DS1305.h>

//#define BATTERYBACKUP
//#define TESTMEMORY

DS1305 rtc = DS1305(10, true);

char buffer[17];
int timeDate[6];

void setup() {
      Serial.begin(9600);

      pinMode(3, INPUT);
      digitalWrite(3, HIGH);

      if(!rtc.init()) {
            // RTC failed to initialise; set a default time/date

            // Set date and time.
            rtc.setDate(0, 1, 1);
            rtc.setTime(0, 0, 0);

#ifdef BATTERYBACKUP
            int dummy;
            if(!rtc.getTrickleChargeSettings(dummy, dummy)) {
                  // Trickle charge wasn't initiated; setting it to two diodes and 8ohms resistance (slowest)
                  rtc.setTrickleChargeSettings(2, 8);
            }
#endif
      }
      
#ifdef BATTERYBACKUP
      // See what the trickle charge settings are
      int diodes;
      int resistance;
      if(rtc.getTrickleChargeSettings(diodes, resistance)) {
            Serial.print("Trickle charge set to ");
            Serial.print(diodes);
            Serial.print(" diodes and ");
            Serial.print(resistance);
            Serial.println("ohms resistance");
      }
      else
            Serial.println("Trickle charge wasn't initiated");
#endif

#ifdef TESTMEMORY
      // Check we can write and read from the RTC onboard memory
      rtc.write((byte*)"Memory test", 12);
      rtc.read((byte*)buffer, 12);
      Serial.println(buffer);
#endif
      
      // Setup an alarm
      if(rtc.setAlarm(0, 0)) {
            Serial.println("Alarm 0 set");
            if(rtc.setAlarm(1, 30)) {
                  Serial.println("Alarm 1 set");
                  attachInterrupt(1, alarmInterrupt, FALLING);
            } else
                  Serial.println("Alarm 1 error!");
      }
      else
            Serial.println("Alarm 0 error!");
}

void alarmInterrupt() {
      bool alarm0;
      bool alarm1;
      rtc.alarmRaised(alarm0, alarm1);

      if(alarm0) {
            Serial.print("Alarm 0 ");
            rtc.alarmClear(0);
      }
      
      if(alarm1) {
            if(alarm0)
                  Serial.print(" and ");
            else
                  Serial.print("Alarm ");
            Serial.print("1 ");
            rtc.alarmClear(1);
      }
      
      Serial.println("raised!");
}

void loop() {
      // Get date and time
      rtc.getDate(timeDate[0], timeDate[1], timeDate[2]);
      rtc.getTime(timeDate[3], timeDate[4], timeDate[5]);
      
      if(timeDate[3] < 10)
            Serial.print(" ");
      Serial.print(itoa(timeDate[3], buffer, 10));
      Serial.print(":");
      if(timeDate[4] < 10)
            Serial.print("0");
      Serial.print(itoa(timeDate[4], buffer, 10));
      Serial.print(":");
      if(timeDate[5] < 10)
            Serial.print("0");
      Serial.print(itoa(timeDate[5], buffer, 10));

      Serial.print(" ");

      if(timeDate[2] < 10)
            Serial.print("0");
      Serial.print(itoa(timeDate[2], buffer, 10));
      Serial.print("/");
      if(timeDate[1] < 10)
            Serial.print("0");
      Serial.print(itoa(timeDate[1], buffer, 10));
      Serial.print("/20");
      if(timeDate[0] < 10)
            Serial.print("0");
      Serial.println(itoa(timeDate[0], buffer, 10));
      
      delay(750);
}

Hopefully that’ll all make sense!

John.

PS, I know it needs documentation and the formatting has messed up :’(