megaAVR Experiments - DS1302 RTC

DS1302 is a real time clock chip from Maxim Integrated™. It requires a 32.768 KHz crystal, takes up 3 IO pins and runs off Vcc or a back up battery (whichever is higher). More information can be found about this chip here.

The pin connections are:

Here is my driver code for DS1302:

/**************************
 * file name: rtc.c
 **************************/
 
#include <avr/io.h>
#include <stdint.h>
#define F_CPU 7372800UL //7.3728MHz
#include <util/delay.h>
#include "rtc.h"

//Strobe "pin" on "port" high
#define IO_PIN_STROBE_HIGH(port, pin)	\
		__asm__ __volatile__ (			\
		"sbi %0, %1"					\
		: /* no output */				\
		: "I" (_SFR_IO_ADDR(port)),		\
		  "I" (pin))

//Strobe "pin" on "port" low
#define IO_PIN_STROBE_LOW(port, pin)	\
		__asm__ __volatile__ (			\
		"cbi %0, %1"					\
		: /* no output */				\
		: "I" (_SFR_IO_ADDR(port)),		\
		  "I" (pin))

//Timing requirement between data output and clock stobe high
#define DATA_TO_CLK_SETUP()		\
		__asm__ __volatile__ (	\
		"nop\n\t"				\
		"nop\n\t"				\
		::)

//Strobe CE pin of DS1302 high and low
#define CE_STROBE_HIGH() IO_PIN_STROBE_HIGH(PORTE, 7)
#define CE_STROBE_LOW()	IO_PIN_STROBE_LOW(PORTE, 7)

//Strobe SCLK pin of DS1302 high and low
#define SCLK_STROBE_HIGH() IO_PIN_STROBE_HIGH(PORTE, 5)
#define SCLK_STROBE_LOW() IO_PIN_STROBE_LOW(PORTE, 5)

//Strobe IO pin of DS1302 high and low
#define IO_STROBE_HIGH() IO_PIN_STROBE_HIGH(PORTE, 6)
#define IO_STROBE_LOW() IO_PIN_STROBE_LOW(PORTE, 6)

//Calendar/Clock burst read and write command
#define DT_BURST_READ 0xbf
#define DT_BURST_WRITE 0xbe

//RTC register seconds byte read and write command
#define DT_SECONDS_READ 0x81
#define DT_SECONDS_WRITE 0x80

//RTC register hours byte read and write command
#define DT_HOURS_READ 0x85
#define DT_HOURS_WRITE 0x84

//RTC register WP write
#define DT_WP_WRITE 0x8e

//Configure port pin directions for read and write DS1302
#define WRITE_MODE() (DDRE |= 0xe0)
#define READ_MODE() (DDRE = (DDRE|0xe0)&0xbf)
//Disable io pull-up in read mode
#define DISABLE_IO_PULLUP() (PORTE &= 0xbf) 
//Read i/o value from DS1302
#define IO_READ() (PINE & 0x40)

//Prepare CE and SCLK for new operation
static void reset(void)
{
	//Pull both CE and SCLK low to start with	
	SCLK_STROBE_LOW();
	CE_STROBE_LOW();

	//Comms. begin with CE stobe high
	CE_STROBE_HIGH();
}

//Read one byte of Calendar/Clock data
static uint8_t read_byte(void)
{
	uint8_t byte = 0;
	uint8_t i;

	//Port pins in read mode for data read
	READ_MODE();
	//Disable internal I/O pull-up
	DISABLE_IO_PULLUP();

	//Read one byte of Calendar/Clock data
	for(i = 0; i != 8; ++i)
	{
		//Strobe SCLK low to read I/O
		SCLK_STROBE_LOW();
		_delay_us(1);

		if(IO_READ() != 0)
		{
			byte |= 1<<i;
		}
		//Strobe SCLK high for next I/O read
		SCLK_STROBE_HIGH();
		_delay_us(1);
	}

	return byte;
}

//Write one byte of control or Calendar/Clock data
static void write_byte(uint8_t byte)
{
	uint8_t i;

	//Port pins in write mode
	WRITE_MODE();

	//Write one byte of control or Calendar/Clock data
	for(i = 0; i != 8; ++i)
	{
		//Start clock cycle with SCLK low
		SCLK_STROBE_LOW();
		_delay_us(1);

		//Write bit value to I/O pin of DS1302			
		if(((1<<i)&byte) == 0)
		{
			IO_STROBE_LOW();
		}
		else
		{
			IO_STROBE_HIGH();
		}

		DATA_TO_CLK_SETUP(); //Data to clock setup

		//End clock cycle with SCLK high
		SCLK_STROBE_HIGH();
		_delay_us(1);		
	}
	
}

//Read 7 bytes of Calendar/Clock data
static dateTime read_dt_block(void)
{
	uint8_t dt_byte;
	uint8_t byte_pos;
	dateTime dt = {0};

	//Always do a reset before a new operation
	reset();

	//Write the clock burst read command into DS1302
	write_byte(DT_BURST_READ);

	//Read each of the 7 Calendar/Clock bytes from DS1302
	for(byte_pos = 0; byte_pos != 7; ++byte_pos)
	{
		//Read one byte of calendar/clock data
		dt_byte = read_byte();
		
		//Copy the read byte to the right place
		switch(byte_pos)
		{
			case 0:
						dt.second = dt_byte;
						break;
			case 1:
						dt.minute = dt_byte;
						break;
			case 2:
						dt.hour = dt_byte;
						break;
			case 3:
						dt.date = dt_byte;
						break;
			case 4:
						dt.month = dt_byte;
						break;
			case 5:
						dt.day = dt_byte;
						break;
			case 6:
						dt.year = dt_byte;
						break;
		}
	}

	//Always end an operation with a reset
	reset();
	return dt;
}

//Write 8 bytes of Calendar/Clock data
static void write_dt_block(dateTime dt)
{
	uint8_t dt_byte;
	uint8_t byte_pos;

	//Always do a reset before a new operation
	reset();

	//Write burst write command byte to DS1302
	write_byte(DT_BURST_WRITE);

	//Write each of the 7 Calendar/Clock byte to DS1302
	for(byte_pos = 0; byte_pos != 7; ++byte_pos)
	{
		//Copy the right byte to write
		switch(byte_pos)
		{
			case 0:
						dt_byte = dt.second;
						break;
			case 1:
						dt_byte = dt.minute;
						break;
			case 2:
						dt_byte = dt.hour;
						break;
			case 3:
						dt_byte = dt.date;
						break;
			case 4:
						dt_byte = dt.month;
						break;
			case 5:
						dt_byte = dt.day;
						break;
			case 6:
						dt_byte = dt.year;
						break;
		}

		//Write one byte of Calendar/Clock data
		write_byte(dt_byte);
	}

	//Must write the 8th byte of the Calendar/Clock register
	write_byte(0);

	//Always end an operation with a reset
	reset();
}

/*******************************************************************
  Interface function to initialize RTC: 1. Disable Clock Halt
                  						2. Set to 24 hour mode
                  						3. Disable Write Protection
  No Calendar/Clock will be changed
********************************************************************/
void rtc_init(void)
{
	uint8_t byte_second;
	uint8_t byte_hour;

	//Disable Clock Halt
	reset();
	write_byte(DT_SECONDS_READ);
	byte_second = read_byte();
	reset();
	write_byte(DT_SECONDS_WRITE);
	write_byte(byte_second & 0x7f);
	reset();

	//Set to 24 hour mode
	write_byte(DT_HOURS_READ);
	byte_hour = read_byte();
	reset();
	write_byte(DT_HOURS_WRITE);
	write_byte(byte_hour & 0x7f);
	reset();

	//Disable Write Protection
	write_byte(DT_WP_WRITE);
	write_byte(0);
	reset();
}

//Interface function to read Calendar/Clock value
dateTime get_date_time(void)
{
	dateTime dt;
	
	//Read raw calendar/clock block from DS1302
	dt = read_dt_block();

	/*************************************************************
	 Convert from the raw BCD Calendar/Clock data to normal decimal
	 values. Hour is treated differently in 24 and AM/PM mode.
	 Also the day of week is left as is.
	**************************************************************/	
	dt.second = (((dt.second&0x70)>>4)*10) + (dt.second&0x0f);
	dt.minute = (((dt.minute&0x70)>>4)*10) + (dt.minute&0x0f);
	if((dt.hour&0x80) == 0)
	{
		dt.hour = (((dt.hour&0x30)>>4)*10) + (dt.hour&0x0f);
	}	
	dt.date = (((dt.date&0x30)>>4)*10) + (dt.date&0x0f);
	dt.month = (((dt.month&0x10)>>4)*10) + (dt.month&0x0f);
	dt.year = (((dt.year&0xf0)>>4)*10) + (dt.year&0x0f);

	return dt;
}

//Interface function to set Calendar/Clock value
void set_date_time(dateTime dt)
{
	/**************************************************************
	 Convert from normal decimal Calendar/Clock value to BCD. Hour
	 is treated differently in 24 and AM/PM mode. Also the day of
	 week is left as is.
	***************************************************************/	
	dt.second = ((dt.second/10)<<4) | (dt.second%10);
	dt.minute = ((dt.minute/10)<<4) | (dt.minute%10);
	if((dt.hour&0x80) == 0)
	{
		dt.hour = ((dt.hour/10)<<4) | (dt.hour%10);
	}
	dt.date = ((dt.date/10)<<4) | (dt.date%10);
	dt.month = ((dt.month/10)<<4) | (dt.month%10);
	dt.year = ((dt.year/10)<<4) | (dt.year%10);

	write_dt_block(dt);
}

And a header that goes with it:

/******************************
 * file name: rtc.h
 ******************************/
#ifndef RTC_H
#define RTC_H

//Data type to hold calendar/clock data
typedef struct 
{
	uint8_t second;
	uint8_t minute;
	uint8_t hour;
	uint8_t date;
	uint8_t month;
	uint8_t day;
	uint8_t year;
} dateTime;

/*******************************************************************
  Interface function to initialize RTC: 1. Disable Clock Halt
                  						2. Set to 24 hour mode
                  						3. Disable Write Protection
  No Calendar/Clock will be changed
********************************************************************/
void rtc_init(void);

//Interface function to read Calendar/Clock value
dateTime get_date_time(void);

//Interface function to set Calendar/Clock value
void set_date_time(dateTime dt);

#endif