DS1302 communication with an AVR micro

DS1302 is designed to be connected, usually serially, with a microcontroller. It exchanges data based on a specially designed protocol such as the one described above. Below we will present the basic communication steps of DS1302 with an AVR microcontroller.

For our convenience, we define the following directives and we place them at the beginning of the program of AVR. They describe the way of interconnection and they help in the communication of the two chips. Also, they are defined as labels for the addresses of registers of DS1302 which make the source code even more understandable

#define CLOCK_PORT   PORTD
#define CLOCK_PIN    PIND
#define CLOCK_DDR    DDRD
#define SET_CLOCK_CE()    CLOCK_PORT|=(1<<CLOCK_CE)
#define CLR_CLOCK_CE()    CLOCK_PORT&=~(1<<CLOCK_CE)
#define SET_CLOCK_IO()    CLOCK_PORT|=(1<<CLOCK_IO)
#define CLR_CLOCK_IO()    CLOCK_PORT&=~(1<<CLOCK_IO)
#define SET_CLOCK_SCLK()  CLOCK_PORT|=(1<<CLOCK_SCLK)
#define CLR_CLOCK_SCLK()  CLOCK_PORT&=~(1<<CLOCK_SCLK)
#define SET_CLOCK_DDRIO() CLOCK_DDR|=(1<<CLOCK_IO)
#define CLR_CLOCK_DDRIO() CLOCK_DDR&=~(1<<CLOCK_IO)
#define CLOCK_SEC_REG       0x80
#define CLOCK_MIN_REG       0x82
#define CLOCK_HR_REG        0x84
#define CLOCK_DATE_REG      0x86
#define CLOCK_MONTH_REG     0x88
#define CLOCK_DAY_REG       0x8a
#define CLOCK_YEAR_REG      0x8c
#define CLOCK_CONTROL_REG   0x8e
#define CLOCK_CHARGER_REG   0x90
#define CLOCK_CLKBURST_REG  0xbe

We introduce the void-type function:

void clock_write(uint8_t reg, uint8_t data) { //source code }

which takes two arguments “reg” and “data” which have data type “uint8_t”. The variable “reg” takes the command byte of the register we want to pass by and it sends it first serially to DS1302 and then it sends to DS1302 itself the data of the variable “data” representing the content of the register we pass by. Also, in some cases, it is needed to deactivate interrupts at first and we activate them again at the end through the suitable functions. Also, if we haven’t done it, we define the pin’s direction of port communication. Thereafter, we introduce the function:

The implementation in C of this function is:

void clock_write(uint8_t reg, uint8_t data) {
    uint8_t i;
    cli();
    CLR_CLOCK_CE();
    SET_CLOCK_DDRIO();
    CLR_CLOCK_CE();
    clock_delay();
    CLR_CLOCK_SCLK();
    clock_delay();
    SET_CLOCK_CE();
    clock_delay();
    
    for(i=8; i>0; i-- ) {
        if( reg & 0x01) SET_CLOCK_IO(); else CLR_CLOCK_IO();
        clock_delay();
        SET_CLOCK_SCLK();
        clock_delay();
        CLR_CLOCK_SCLK();
        clock_delay();
        reg >>= 1;
    }
    
    for(i=8; i>0; i-- ) {
        if( (data & 0x01) ==1 ) SET_CLOCK_IO(); else CLR_CLOCK_IO();
        clock_delay();
        SET_CLOCK_SCLK();
        clock_delay();
        CLR_CLOCK_SCLK();
        clock_delay();
        data>>=1;
    }
    
    CLR_CLOCK_CE();
    clock_delay();
    CLR_CLOCK_DDRIO();
    sei();
}

Thereafter, we introduce the function:

uint8_t clock_read(uint8_t reg){ //source code }

in which the returned values are data type “uint8_t”. The variable “reg” takes the command byte of the register we want to read. The data of the variable “reg” are first sent to DS1302 and then DS1302 replies with the data of the corresponding register which are the returned value of the function. Note: The bit0 for RD=1 of the command byte is predefined set to 1 inside the function’s body and, thus, the value of “reg” is the same for the writing and the reading of the same register.

The implemantation in C of these two function is:

int8_t clock_read(uint8_t reg) {
    uint8_t data=0, i;
    cli();
    reg|=0x01;
     CLR_CLOCK_CE();
    SET_CLOCK_DDRIO();
    CLR_CLOCK_CE();
    clock_delay();
    CLR_CLOCK_SCLK();
    clock_delay();
    SET_CLOCK_CE();
    clock_delay();
    
    for(i=8; i>0; i-- ) {
        if( (reg & 0x01)==1) SET_CLOCK_IO(); else CLR_CLOCK_IO();
        clock_delay();
        SET_CLOCK_SCLK();
        clock_delay();
        CLR_CLOCK_SCLK();
        clock_delay();
        reg >>= 1;
    }
    CLR_CLOCK_DDRIO();
    
    for(i=8; i>0; i-- ) {
        data>>=1;
        if(CLOCK_PIN &(1 << CLOCK_IO )) data|= 0x80;
        SET_CLOCK_SCLK();
        clock_delay();
        CLR_CLOCK_SCLK();
        clock_delay();
    }        
    CLR_CLOCK_CE();
    clock_delay();
    sei();
    return data;

In these two functions, an additional function is used for the creation of a short delay as supplemented below:

void clock_delay(void){
    asm("nop");
    asm("nop");
    asm("nop");
    asm("nop");
}

Programming examples of AVR for communication with DS1302. In the source code below, we initialize DS1302 with the time 21:45:52 and date November 27, 2015.

int main(void)
{
         CLOCK_DDR|= ( (1 << CLOCK_CE) | (1 << CLOCK_SCLK ));
         uint8_t h=21, m=45, s=52;
         uint8_t d=27, mo=11, y=2015;
         uint8_t hdata=0,  mdata=0, sdata=0, ydata=0,  ddata=0,  modata=0, ydata=0;
         sdata = ((s / 10) << 4) | (s % 10);
          mdata= ((m / 10) << 4) | (m % 10);
          if(h-12<=0)
           {
               hdata |=0x80;
               hdata |= 0x0f & h;
               if( h>=10 ) hdata |= 0x10;
           }
           else
           {
                h-=12;
                hdata |= 0x80;
                hdata |= 0x20;
                hdata |= 0x0f & h;
                if(h >= 10) hdata |= 0x10;
            }
            ddate = (( d / 10 ) << 4) | (d % 10);
            modata = (( mo / 10) << 4) | (mo % 10);
            y-=2000;
            ydata = ((y / 10) << 4) | (y % 10);
            clock_write(CLOCK_CONTROL_REG, 0x00);
            clock_write(CLOCK_SEC_REG, sdata);  // Writes seconds register
            clock_write(CLOCK_MIN_REG, mdata); //Writes minutes register
            clock_write(CLOCK_HR_REG, hdata);  // Writes hours register
            clock_write(CLOCK_DATE_REG, ddate); //Write date’s register
            clock_write(CLOCK_MONTH_REG, modata); // Write month’s register
            clock_write(CLOCK_YEAR_REG, ydata);  // Writes years register
            clock_write(CLOCK_CONTROL_REG, 0x80);
}

In the source code below, we read from DS1302: time hh:mm:ss and date: mm, dd, yyyy

int main(void)
{
         CLOCK_DDR|=( (1 << CLOCK_CE) | (1 << CLOCK_SCLK) );
         uint8_t h, m, s;
         uint8_t d, mo, y;
         uint8_t hdata,  mdata, sdata, ydata,  ddata,  modata, ydata;
            sdata =  clock_read(CLOCK_SEC_REG);  //Reads seconds register
            mdata = clock_read(CLOCK_MIN_REG); //Reads minutes register
            hdata = clock_read(CLOCK_HR_REG);    //Reads hours register
            ddate = clock_read(CLOCK_DATE_REG);  //Reads  date register
            modata = clock_read(CLOCK_MONTH_REG);  //Reads month register
            ydata = clock_read(CLOCK_YEAR_REG);    //Reads year register
            
           sdata &= 0x7f;
           s = ((sdata & 0x70) >> 4) * 10 + (sdata & 0x0f);  //We take seconds
           m = ((mdata & 0x70) >> 4) * 10 + (mdata & 0x0f); //We take minutes
           if(h & 0x9x)  h = ((hdata & 0x10) >> 4) * 10 + (hdata & 0x0f);
            else h = ((hdata & 0x30) >> 4) * 10 + (hdata & 0x0f); //We take hours
           d = ((ddata & 0x30) >> 4) *10 +(ddata & 0x0f); //We take the date
           mo = ((modata & 0x10) >> 4) * 10 + (modata & 0x0f); //We take the month
           y = 2000 + (ydata & 0xf0) >> 4 )* 10 + (ydata & 0x0f);  //We take the year
}