/* This Program is Developed by Akhil Gakhar, M.Tech Research Associate, Department of Electrical Engineering, IIT Bombay

// We set Date and Time in RTC IC and read the same from it and Display it on LCD
// You can change Date and Time according to your own need and observe auto-updation of date and time by RTC.
 
In this Program we interface DS1307 IC with PIC18F4550, we will use SWITCH-4 to indicate user wants to change time, SWITCH-3 to set the new time (Hardcoding of time is done here), We have to press reset to see the changes and the effect of SWITCH-4, refer the clock and calender registers of DS1307 to get a better picture of high_nibble and low_nibble utilisation.

One can change the delays and see the importance of setup and hold times, write time and read time etc.

DS1307 works in 100kHz mode by default. 

We are using PIC in MASTER Transmit/Recieve mode ONLY !, to use PIC in Slave mode refer the Datasheet.

Check the connections of SDA/SCL lines and Donot forget to give 5V to DS1307 apart from 3V battery back-up.

Notice the utilisation of RAM location at 0X80 to write a dummy flag variable. Program could have been done without the use dummy value.

Participants are encouraged to use this code fragment, modify, and craft out other possibilities beyond what they understand from this code. 

*/





#include <p18f4550.h>

#define RS PORTEbits.RE0
#define RW PORTEbits.RE1
#define EN PORTEbits.RE2
#define busy PORTDbits.RD7
#define data_lcd PORTD


unsigned char wval,rval,point_add,clock[7],high_nibble,low_nibble,check,store,temp;
int WAit,j,AM,PM,clk,flag=0,s=0;
unsigned char pm[]="PM",am[]="AM",hr[]="Hr",time[]="TIME-";
int flag_indicating_interrupt_has_occured = 0;

void start(void);
void write(unsigned char);
void I2C_Init(void);
void stop(void);
void rep_start(void);
void read(void);
void Ack(void);
void Nack(void);
void wait(void);
void check_switch(void);

//void write_time(unsigned char ,unsigned char unsigned char ,unsigned char , unsigned char ,unsigned char ,unsigned char ,unsigned char );
void read_time(void);
void BCD_ASCII(unsigned char);
void Disp_time(void);
void init_lcd(void);
void lcd_cmd(unsigned char cmd);
void lcd_data(unsigned char data);
void lcd_string(unsigned char *str);
void msDelay(unsigned char delay);

void interrupt_init(void);
void isr(void);
void starting_value(void);


/*The following lines of code perform interrupt vector relocation to work with the USB bootloader. These must be
used with every application program to run as a USB application.*/
extern void _startup (void);
#pragma code _RESET_INTERRUPT_VECTOR = 0x1000

void _reset (void)
{
	_asm goto _startup _endasm
}

#pragma code
#pragma code _HIGH_INTERRUPT_VECTOR = 0x1008
void high_ISR (void)
{
	_asm goto isr _endasm
}

#pragma code
#pragma code _LOW_INTERRUPT_VECTOR = 0x1018
void low_ISR (void)
{
}
#pragma code
/*End of interrupt vector relocation*/



/*Start of main program*/

#pragma interrupt isr       		// we need to explicitly write word "interrupt" for handling interrupts 
void isr(void)						// Good practice is to come out of the interrupt as early as possible
{									// As we donot want our routine task to show any lag in performance
	if(INTCON3bits.INT2IF == x)		// Refer Datasheet to see what is the value of INT2IF flag when I2C interrupt Occurs
	{
		flag_indicating_interrupt_has_occured = 1;
		INTCON3bits.INT2IF = x;		// Clear the Interrupt Flag Bit
	}
}
#pragma code

/* Delay subroutine */
void msDelay(unsigned char delay)
{
    int i=0,j=0;
    for(i=0;i<delay;i++)
    {
        for(j=0;j<50;j++);
    }
}

void start(void)
{
	SSPCON2bits.SEN = x;             // To initiate Start Condition using SEN bit of SSPCON2bits
	while(!PIR1bits.SSPIF);			// SSPIF flag is raised when the desired operation is completed
									// so we wait 'HERE' till the process gets completed
	PIR1bits.SSPIF = x;				// Clear the Interrupt flag
}

void stop(void)
{
	
	SSPCON2bits.PEN=x;            // To initiate Stop Condition using PSEN bit of SSPCON2bits
	while(SSPCON2bits.PEN);		  // The stop bit cleared automatically by hardware
								  // so we wait "UNTILL" the stop process is complete
	PIR1bits.SSPIF=x;
}

/* 
 * We perform write Operation to the slave device
 * If the slave acknowleges, then its fine
 * in case of not acknowledge
 * we need to stop the communication
 * Hence we need to monitor the Acknowledge flag bit also
 */
void write(unsigned char wval)
{
	
	SSPBUF = wval;					// what ever we have to write to RTC, we pass that value to SSPBUF register
	while(!PIR1bits.SSPIF);			// SSPIF is indicative of the process being complete, so we monitor that flag bit
	PIR1bits.SSPIF=x;				// We need to manually clear this flag
	
	if(SSPCON2bits.ACKSTAT)          // Keep record of Acknowledge sent by Slave Device
	{
		SSPCON2bits.PEN=x;       // If no acknowledge is recieved then Initiate Stop condition
								 // check data sheet what value is of PEN when Not ack is recieved
		while(SSPCON2bits.PEN);
		PORTBbits.RB3=1;		// Glowing an LED in case Nack is recieved
		
	}
	
}

/* 
 * To send NAck we need to load the value to ACKDT, 
 * and then send this value over the channel by giving adequate value to ACKEN
*/
void Nack(void)
{
	SSPCON2bits.ACKDT=x;            // value of ACKDT decides Acknowledge or Not-Acknowledge that will be transmitted 
	SSPCON2bits.ACKEN=x;		   // ACKEN transmits value ACKDT over I2C Bus. 
	
	while(SSPCON2bits.ACKEN);

}

/* 
 * To send Ack we need to load the value to ACKDT, 
 * and then send this value over the channel by giving adequate value to ACKEN
*/
void Ack(void)
{
	SSPCON2bits.ACKDT=x;
	SSPCON2bits.ACKEN=x;
	
	while(SSPCON2bits.ACKEN);
}
	
/* 
 * When in Read Mode, we need to explicitly mention it to Our controller
 * This program performs single read at a time
*/
void read(void)
{
	SSPCON2bits.RCEN=X;      // Configures I2C in Recieve mode 

	while(!PIR1bits.SSPIF);	 // Refer Datasheet 
	PIR1bits.SSPIF=X;		// clear the SSPIF flag
	rval=SSPBUF;			 // SSPBUF holds received/transmitted value
	
}

/*
 * For Repeated start we need to write adequate value to RSEN bit
*/
void rep_start(void)
{
	SSPCON2bits.RSEN=X;            /*send repeated start pulse*/
    while(SSPCON2bits.RSEN);       /*wait for completion of repeated start pulse*/
    PIR1bits.SSPIF=X;			   // Clear the flag
}


/*
 * Set-up our controller for I2C mode of communication
*/
void I2C_Init(void)
{
    DDRBbits.RB0 = x;		/* Set up I2C lines(SDA/SCL) by setting them as input */
    DDRBbits.RB1 = x;		// RBO -> SDA (Data Line), RB1 -> SCL(Clock Line)
	
    SSPSTAT=0x80;		/* Slew rate disabled for standard mode of operation(100khz), other bits are cleared */
    SSPCON1=0x28;	    /* Enabling SSPEN and Master Mode clock = FOSC / (4 * (SSPADD+1))*/ 
    SSPCON2=0x00;       /* To monitor Acknowledge/ Start/ Stop/ Repeated Start condition */
    SSPADD=0xXX;	    /* Clock 100 kHz */  
    PIE1bits.SSPIE=x;		/* Enable SSPIF interrupt */
    PIR1bits.SSPIF=x;	// Clear the Interrupt flag
}
	
void wait(void)
{	
	WAit=0;
	while((WAit++)<10);
}

void write_time(unsigned char sec,unsigned char min,unsigned char hour,unsigned char day,
				 unsigned char date,unsigned char month,unsigned char year,unsigned char SQWE)
{
	wait();                 // Just to provide Delay before Start ( It might be possible that a stop condition is encountered before 					// calling this function, there must be some delay between a Start and Stop )
	start();
	write(0xXX);
	write(0x00);

	write(sec);
	write(min);
	write(hour);
	write(day);
	write(date);
	write(month);
	write(year);
	write(SQWE);		// Configuring Square Wave Output for 1Hz
	stop();
}


void read_time(void)
{
	point_add=0x00;
	j=0;
	while(j<3)
	{
		wait();		
		start();
		write(0xD0);		// Eight bit = 0, Indicates Master wants to Write to Slave.
		write(point_add);        // To Read  from the slave, we first need to write to it the location from where to read.
		rep_start();
		write(0xD1);		// Eight bit = 1, Indicates Master wants to Read from Slave.

		read();
		Nack();
		stop();
		clock[j]=rval;
		point_add++;
		j++;
	}

}



void BCD_ASCII(unsigned char value)   // Since we read from DS1307 and display it to LCD (which accepts only ASCII Value)
{
	high_nibble=value&0xXX; // Mask the Lower Nibble and extract the higher nibble
	low_nibble=value&0xXX; // Mask the Higher Nibble and extract the lower nibble
	
	low_nibble|=0x30;
	high_nibble=(high_nibble>>4);
	high_nibble|=0x30;
	
}
void init_lcd(void)
{
    
    lcd_cmd(0x38);// initialize it in 8-bit mode
    msDelay(300);       // You Should Initialize LCD with sufficient amount of delay and Once the LCD is 
				       // initalized you can use very less Delays for Displaying one Data after another
				       // using LCD_cmd and LCD_data.    																	
				       // Moreover this has to be done only once in the begining, so this amount of delay
                       // wont affect your performance.
    
    lcd_cmd(0x01);// clear LCD
    msDelay(300);

    
    lcd_cmd(0x0C);// display ON cursor off
    msDelay(300); 
	
    
    lcd_cmd(0x80);// go to Starting location of first line of lcd
     msDelay(300);


}

void lcd_cmd(unsigned char cmd)
{	
    data_lcd = cmd;
    RS = 0;
    RW = 0;
    EN = 1;
    msDelay(1);
	
    EN = 0;
    msDelay(1);
	
    
}

void lcd_data(unsigned char data)
{	
    data_lcd = data;
    RS = 1;
    RW = 0;
    EN = 1;
    msDelay(1);                   // Find how much time is required for "Enable" signal to remain at particular logic level
			          // This can be even smaller all the way down till minimum Read/Write time of an LCD
				  // If you would have used same amount of delay for initalizing LCD in order to 
                                  // make things really quick, then nothing shall work  
	
    EN = 0;
   	msDelay(1);
	
    
}
void lcd_string(unsigned char *str)
{
    int i=0;
    while(str[i]!=0)
    {
        lcd_data(str[i]);
        i++;
    }
    return;
}

void Disp_time(void)
{
		
		lcd_cmd(0xXX);                     // Relocation LCD pointer to the location where we intend to print TIME.
		for(clk=2;clk>=0;clk--)
		{
			
			
			BCD_ASCII(clock[clk]);
			if(clk==2)
			{
				check=high_nibble&0x06;     // Refer datasheet of DS1307 and see the "Time" registers format.  
				switch (check)
				{
					case 0x06 :
								PM=1;
								AM=0;
								high_nibble=(high_nibble&0x01)|0x30;
								break;
					case 0x04 :
								AM=1;
								PM=0;
								high_nibble=(high_nibble&0x01)|0x30;
								break;
					default   :
								high_nibble=(high_nibble&0x03)|0x30;
								
				}
			}

			lcd_data(high_nibble);
			lcd_data(low_nibble);
			lcd_data(' ');
		}

	if(PM==1)
	{
		lcd_string(pm);
	}
	else if(AM==1)
	{
		lcd_string(am);
	}
	else
	{	
		lcd_string(hr);
	}

}


void interrupt_init(void)
{
	INTCON=0X80;
	INTCON2=0X00;
	INTCON3=0X80;
}


void main()
{
	
	TRISD = 0xXX; //make portD is output as it is connected to data pins of LCD.
    TRISE = 0xXX; //make portE is output as it is connected to control signals of LCD.
    
    init_lcd(); // first initialize LCD 

	INTCON2bits.NOT_RBPU=X;  // Enabling pull-ups for Switches
	OSCCON|=0x72;            // Refer Datasheet
	
	ADCON1 = 0x0F;
	DDRBbits.RB7=X;		// configuring them as input
	DDRBbits.RB6=X;
		
	PORTBbits.RB3=0;
		
		
	I2C_Init();

// You can put this code in while(1) ***


			write_time(0x20,0x42,0x51,0x06,0x13,0x01,0x18,0X10);//for AM
			// 00h --> 0 0 1 0 0 0 0 0 -> '0' 'Tens_place_of_second' 'Units_place_of_second'
			// 01h --> 0 1 0 0 0 0 1 0 -> '0' 'Tens_place_of_minute' 'Units_place_of_minute'
			// 02h --> 0 1 0 1 0 0 0 1 -> '0' '12hr_format' 'AM/PM' '10th_place_value' 'unit_place_value'
			// 03h --> 0 0 0 0 0 1 1 0 -> '0 0 0 0 0' 'Day_value'
			// 04h --> 0 0 0 1 0 0 1 1 -> '0 0' 'Tens_place_of_Date' 'Units_place_of_Date'
			// 05h --> 0 0 0 0 0 0 0 1 -> '0 0 0' 'Units_place_of_Month' 'Tens_place_of_month'
			// 06h --> 0 0 0 1 1 0 0 0 -> 'Units_place_of_year' 'Tens_place_of_year'
			// 07h --> 0 0 0 1 0 0 0 0 -> 'Output = 0, when SQWE = 0 (active_low)' '0 0' 'SQWE_enable' '0 0' 'Frequency_of_SQWE'  


		
		lcd_cmd(0x80);
		lcd_string(time);


	interrupt_init();
	INTCON3bits.INT2IE=X; // Enable the I2C interrupt
	while(1)
	{
		if(flag_indicating_interrupt_has_occured)
		{
			read_time();
			Disp_time();
			flag_indicating_interrupt_has_occured = 0;
		}

	}
	
}
