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:
- SCLK - PE5
- IO - PE6
- REST - PE7
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