DS1305 RTC using 3-wire mode

Hello,

I'm doing a project with the Arduino Mega2560, and I’m looking for a way to communicate with the DS1305 real time clock, but I need to use the so-called 3-wire mode, not the 4-wire “true” SPI everyone else seem to be using. The 1305 chip supports both modes.

I had previously used the DS1307 which uses I2C protocol (connected to SDA/CLK pins), and it was relatively easy thanks to some example code I found that used the Wire.h library for communication.

Now I want to use the DS1305 instead, but I can’t seem to find any good source or library on the net that directly solves my problem, and I’m not experienced enough to know how easy it would be to write a low-level library using 3-wire communication myself.

This is what I have:

The DS1305 chip is connected as on the first page of the datasheet “Typical operating circuit”, with its SCLK pin connected to SCL and the SDI/SDO pins both connected to SDA. Also the CE (chip enable) is connected to pin 41. With the SERMODE pin connected to GND, the chip should be in 3-wire mode.

I have found a library for the DS1305 which uses the SPI mode, so that one should have the logic down for controlling time and alarm registers. I would need to convert that library to use 3-wire.

I have also found a library for another chip, the related DS1302, which also uses 3-wire protocol, but of course the functionality and meaning of the internal registers are somewhat different.

Questions:

Combining these two libraries should give the desired result?

Has someone implemented this already, and I just couldn't find it?

I have connected the DS1305 to the microcontrollers SCL and SDA pins, as I had the previous 1307, but the DS1302 3-wire library I have uses common digital pins 5, 6 and 7. From the 1302 library linked below it looks like I have to manually control these pins, including changing the pin mode of the data i/o pin, so I guess I have to rewire those pins first?

I basically need to know if this is a feasible way to go, before I go waste a lot of hours trying to get it to not work. But I'm learning here, so any help would be appreciated!

The two libraries that could be combined to get what I need is linked here:

DS1305 library (SPI): http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1220985468

DS1302 library (3-wire): https://github.com/msparks/arduino-ds1302

-David

For anyone interested, the datasheets for the DS1305 and DS1302 are found here:

http://datasheets.maxim-ic.com/en/ds/DS1305.pdf

http://datasheets.maxim-ic.com/en/ds/DS1302.pdf

From a quick overlook of the code I'd guess that changing the defines:

#define SEC_REG 0
#define MIN_REG 1
#define HR_REG 2
#define DATE_REG 3
#define MON_REG 4
#define DAY_REG 5
#define YR_REG 6
#define WP_REG 7

to this:

#define SEC_REG 0
#define MIN_REG 1
#define HR_REG 2
#define DATE_REG 4
#define MON_REG 5
#define DAY_REG 3
#define YR_REG 6
#define WP_REG 15

should help to use the DS1302 library with the DS1305. For the write protect feature to work you have to additionally change the code to use bit 6 instead of bit 7 of the WP_REG, but I guess you don't need that.

Thanks for looking at the code and replying. Yes I definitely need to change the register address defines.

pylon:
For the write protect feature to work you have to additionally change the code to use bit 6 instead of bit 7 of the WP_REG, but I guess you don’t need that.

Why wouldn’t I need it? The 1302 setup code starts by clearing the WP_REG, so I think I need to do that before I can write anything to it, like setting a valid date and time.

(The machine I’m building has display of date/time as well as interface for setting it. Also setting of alarms is needed (using interrupt pins), to signal user and run procedure at a certain time.

I noticed that the 1302 has a clock halt register that must be cleared before anything runs, but I don’t think the 1305 has anything like that.)

Is there anything important I am missing, or should I be good to go in using the 1302 library with few modifications?

My biggest hurdle perhaps is inexperience with writing low-level protocol stuff, but I would like to learn it.

For example this is how the 1302 3-wire library does reads and writes of a single byte:

void DS1302::_write_out(uint8_t value)
{
  pinMode(_io_pin, OUTPUT);
  shiftOut(_io_pin, _sclk_pin, LSBFIRST, value);
}


uint8_t DS1302::_read_in()
{
  uint8_t input_value = 0;
  uint8_t bit = 0;
  pinMode(_io_pin, INPUT);

  for (int i = 0; i < 8; ++i) {
    bit = digitalRead(_io_pin);
    input_value |= (bit << i);

    digitalWrite(_sclk_pin, HIGH);
    delayMicroseconds(1);
    digitalWrite(_sclk_pin, LOW);
  }

  return input_value;
}

I guess nothing would be changed here, but I don’t know if that’s even the correct way of implementing this.
If this is elementary, then I apologize, but how would I know that you need excactly a 1 micro second delay between taking the shift clock pin high and low? Reading some embedded programming books maybe…

I will see if anyone else responds that it should be ok to continue this way, and then just get on with it. When I eventually get some working code, I’ll post it and results. And probably have some more questions along the way :slight_smile:

Why wouldn’t I need it? The 1302 setup code starts by clearing the WP_REG, so I think I need to do that before I can write anything to it, like setting a valid date and time.

You’re right, so you need to change this routine too:

void DS1302::write_protect(bool enable)
{
  write_register(WP_REG, (enable << 6));
}

Don’t use the halt() method (you can remove it from your library).

By looking into the details a bit more, I found that the 3-wire protocol of the two chips is quite different. You have to change it additionally:

uint8_t DS1302::read_register(reg_t reg)
{
  uint8_t cmd_byte = reg;

  digitalWrite(_sclk_pin, LOW);
  digitalWrite(_ce_pin, HIGH);

  _write_out(cmd_byte);
  reg_value = _read_in();

  digitalWrite(_ce_pin, LOW);

  return reg_value;
}


void DS1302::write_register(reg_t reg, uint8_t value)
{
  uint8_t cmd_byte = (0x80 | reg);

  digitalWrite(_sclk_pin, LOW);
  digitalWrite(_ce_pin, HIGH);

  _write_out(cmd_byte);
  _write_out(value);

  digitalWrite(_ce_pin, LOW);
}

but how would I know that you need excactly a 1 micro second delay between taking the shift clock pin high and low?

The datasheet specifies that. For 5V it should be sufficient to have it up for 250ns but you cannot time such small bits on the Arduino. Theoretically the digitalWrite() consumes more than enough time to have the 250ns gone by but it’s more reliable if it’s not at the edge.

Could you try the changes I proposed (I don’t have to hardware to try) and post any problems you have?

Many thanks, you've helped me straighten certain things out. I will try the modified code in the next few hours, and post my results.

Some differences from DS1302 to DS1305:

Write protection and clock halt (enable oscillator) are bit 6 and 7 respectively in the control register.

Bit 7 in the command/address byte indicates a read(0) or a write(1).

Time registers are read from address 0x00 through 0x08, and written from address 0x80 through 0x88.

It writes and reads the date and time!

12 hour mode is not supported.

Still some fixing up remaining, to ensure that it always calculates the time and date fields correctly conforming to the datasheet.

Then there’s the alarm and related interrupt stuff, which I’ll soon get to.

Changes to code:

DS1305.h:

...
#define SEC_REG 	0x00
#define MIN_REG 	0x01
#define HR_REG 		0x02
#define DAY_REG 	0x03
#define DATE_REG 	0x04
#define MON_REG 	0x05
#define YR_REG 		0x06
// alarm later

#define CONTROL_REG 0x0F

// control register bit definitions
#define CTRL_EOSC		7
#define	CTRL_WP			6
...

DS1305.cpp:

...
uint8_t DS1305::read_register(reg_t reg)
{
  uint8_t cmd_byte = reg;
  uint8_t reg_value;

  digitalWrite(_sclk_pin, LOW);
  digitalWrite(_ce_pin, HIGH);

  _write_out(cmd_byte);
  reg_value = _read_in();

  digitalWrite(_ce_pin, LOW);

  return reg_value;
}

void DS1305::write_register(reg_t reg, uint8_t value)
{
  uint8_t cmd_byte = (0x80 | reg);

  digitalWrite(_sclk_pin, LOW);
  digitalWrite(_ce_pin, HIGH);

  _write_out(cmd_byte);
  _write_out(value);

  digitalWrite(_ce_pin, LOW);
}

void DS1305::write_protect(bool enable)
{
  uint8_t reg = read_register(CONTROL_REG);
  reg &= ~(1 << CTRL_WP);
  reg |= (enable << CTRL_WP);
  write_register(CONTROL_REG, reg);
}

void DS1305::halt(bool enable)
{
  uint8_t reg = read_register(CONTROL_REG);
  reg &= ~(1 << CTRL_EOSC);
  reg |= (enable << CTRL_EOSC);
  write_register(CONTROL_REG, reg);
}
...

Example code:
DS1305_test.ino:

#include <stdio.h>
#include <string.h>
#include <DS1305.h>
#include <LiquidCrystal.h>

LiquidCrystal lcd(12, 11, 5, 4, 3, 2);

/* Set the appropriate digital I/O pin connections */
uint8_t CE_PIN   = 41;
uint8_t IO_PIN   = 31;
uint8_t SCLK_PIN = 33;

/* Create buffers */
char buf[50];
char day[10];

/* Create a DS1302 object */
DS1305 rtc(CE_PIN, IO_PIN, SCLK_PIN);

void print_time()
{
  /* Get the current time and date from the chip */
  Time t = rtc.time();

  /* Name the day of the week */
  memset(day, 0, sizeof(day));  /* clear day buffer */
  switch (t.day) {
    case 1:
      strcpy(day, "Sunday");
      break;
    case 2:
      strcpy(day, "Monday");
      break;
    case 3:
      strcpy(day, "Tuesday");
      break;
    case 4:
      strcpy(day, "Wednesday");
      break;
    case 5:
      strcpy(day, "Thursday");
      break;
    case 6:
      strcpy(day, "Friday");
      break;
    case 7:
      strcpy(day, "Saturday");
      break;
  }

  /* Format the time and date and insert into the temporary buffer */
  snprintf(buf, sizeof(buf), "%s %04d-%02d-%02d %02d:%02d:%02d",
           day,
           t.yr, t.mon, t.date,
           t.hr, t.min, t.sec);

  /* Print the formatted string to serial so we can see the time */
  Serial.println(buf);
}

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

  Serial.println("Clock test starting...");
  delay(2000);
  /* Initialize a new chip by turning off write protection and clearing the
     clock halt flag. These methods needn't always be called. See the DS1305
     datasheet for details. */
  
  rtc.write_protect(false);
  rtc.halt(false);

  /* Make a new time object to set the date and time */
  /*   Tuesday, May 19, 2009 at 21:16:37.            */
  Time t(2009, 5, 19, 21, 16, 37, 3);

  /* Set the time and date on the chip */
  rtc.time(t);
}

/* Loop and print the time every second */
void loop()
{
  print_time();
  delay(1000);
}

It prints the following:

Clock test starting...
Wednesday 2009-05-19 21:16:37
Wednesday 2009-05-19 21:16:38
Wednesday 2009-05-19 21:16:39
...