/*

 Commandes Afficheur LCD     RS (Register Select) : 0 -> Instr. Reg. 
*                                                       1 -> Data Reg.

 	bitwise

  rtc.c realtime controller
  2-Channel Time Switch V1.0
  
  Motorola MC908QY4CP 16-pin MCU with external XTAL 4MHz for
  timebase realtime clock
  
  compiled with icc08
     
  Copyright (c) 2004 Wichit Sirichote, kswichit@kmitl.ac.th

  mode settings		 		state
  0	   set hour and min		  NA
  1    set day				  NA	
  2	   manual on/off		  NA
  3	   enter scheduler		  0 :enter time 1 :enter day 2: set output
  4	   display scheduler	  NA
  
*/

#include <io908QY4.H>

#define key0 (PTA&0x01)
#define key1 (PTA&0x02)
#define key2 (PTA&0x04)
#define key3 (PTA&0x08)

char temp,k,mode;
char i,n,timer1,timer2,flag;
unsigned char sec100,sec,min,hour,day,output;
unsigned char sec, warm_boot;

char *titre = "Horloge V1.0";
char buffer[8];

char *day_str[] = {"AA","MO","TU","WE","TH","FR","SA","SU","__"};

// "AA" for all days control
// "__" for end of program

char state;


struct pgm{
	   char set_day;
	   char set_hour;
	   char set_min;
	   char set_control;
};

struct pgm PGM[5];	   

/*   signal for lcd interface
   E       PTB1
   RS      PTB0
 
   D4      PTB4
   D5      PTB5
   D6      PTB6
   D7      PTB7
   
   D0-D3 and R/W# are tied to GND
*/


// function prototype declarations

void commande_LCD(char c);
void LCDWD(char c); 
void print_dec(char d,unsigned int n);

void print_time(char d);
void tick_display(void);

void rafraichir_horloge(void);
void scrute_clavier(void);
void print_output(char n);
void print_record(char d,char n);
void compare_time(void);
void fire_output(char N);
void initialisation_LCD(void);

// enter timer overflow every 1/100s or 10ms
#pragma interrupt_handler isr_TIM
void isr_TIM(void) 
{
 	  // fonction de gestion de l'interruption
 	  
 	  //PTB ^= 0x08; // toggle PTB.3 to check period
	  // found period was 20.012ms
	  
	  TSC &= ~0x80; // clear TOF
	  flag |= 2; // tick 1/100Hz 
	  COPCTL = 0; // clear COP 
	  
	  rafraichir_horloge();
	  tick_display(); // tick every 0.5 sec
	  scrute_clavier();
	  
	  
}

#pragma interrupt_handler isrDummy
void isrDummy(void) 
	{
	COPCTL = 0; // clear COP 
	}
	
	
#pragma abs_address:0xffde

void (* const _vectab[])(void) = {
	isrDummy,				/* ADC 	*/
	isrDummy,				/* KEYBOARD 	*/
	isrDummy,				/* NOT USED	*/
	isrDummy,				/* NOT USED	*/
	isrDummy,				/* NOT USED	*/
	isrDummy,				/* NOT USED	*/
	isrDummy,				/* NOT USED	*/
	isrDummy,				/* NOT USED	*/
	isrDummy,				/* NOT USED	*/
	isrDummy,				/* NOT USED	*/
	isr_TIM,				/* TIM OVERFLOW  	*/
	isrDummy,				/* TIM1  	*/
	isrDummy,				/* TIM0  	*/
	isrDummy,				/* NOT USED     	*/
	isrDummy,				/* IRQ          	*/
	isrDummy				/* SWI             	*/
	
	};
#pragma end_abs_address	

/********************************************************************/
void rafraichir_horloge()
{
   if(++sec100 >=100)
	{
	  sec100 = 0;
	if(mode !=3 && mode !=4)
	  {
	  print_time(0x80);
	  print_output(output);
	  }
	if(++sec > 59)
     {
      sec = 0;
	    compare_time(); 
	  if( ++min >59)
       {
        min = 0;
		 if(++hour >23)
          {
           hour = 0;
		   if(++day > 7)
		    day = 1;
			}
        }
     }
}
}
/********************************************************************/
void tick_display()
{
   if(mode !=3 && mode !=4)
   {
   if(++timer1> 50)
   {
    timer1 = 0;
    commande_LCD(0x85); // swap display
	   k^=1;
	   if (k) LCDWD(' ');
	   else LCDWD(':');
    }
  }		   
}	   
/********************************************************************/
void reglage_heure()
{   
		++heure;
	  if(heure>23)
	    { heure=0; }
	  affiche_heure(0x80);
}
/********************************************************************/
void reglage_minute()
{
		++min;
		sec = 0;
		compare_time();
		if(min>59)
	    { min =0; }
		affiche_heure(0x80);
}	 	  
/********************************************************************/
void reglage_jour()
{ 
		++jour;
		if(jour>7) 
		  { day = 1; } 
		affiche_heure(0x80);
}	 
/********************************************************************/
void output2_setting()
{
   output^=2;		// XOR	 si x=6 alors x^=3 -> x=x^3		
   							// 0110
   							// 0011
   							// 0101							
	 print_output(output);
}	 
/********************************************************************/
void output1_setting()
{
     output^=1;
	 print_output(output);
}	 	
/********************************************************************/
void pgm_setting1()
{
   switch(state)
   {
    case 0:
	temp =PGM[n].set_hour;
	++temp;
	if(temp>23) temp = 0;
	PGM[n].set_hour = temp;
	print_record(0x80,n);
	break;
	case 1:
	temp = PGM[n].set_day;
	++temp;
	if(temp>8) temp = 0;
	PGM[n].set_day = temp;
	print_record(0x80,n);
	break;
	case 2:
	PGM[n].set_control ^= 2;
	print_record(0x80,n);
	break;
	}
}	
/********************************************************************/
void pgm_setting2()
{
   switch(state)
   {
    case 0:
	temp =PGM[n].set_min;
	++temp;
	if(temp>59) temp = 0;
	PGM[n].set_min = temp;
	print_record(0x80,n);
	break;
	case 2:
	PGM[n].set_control ^= 1;
	print_record(0x80,n);
	break;
	}
}	
/********************************************************************/
void traite_touche2()
{
    switch(mode)
	{
	case 0: hour_setting(); break;
	case 1: day_setting(); break;	
	case 2: output2_setting();break;
	case 3: pgm_setting1(); break; 		
	case 4: ++n; if(n>4) n=0; print_record(0x80,n);
	commande_LCD(0xc7); // mode 4 shows record number
	LCDWD(n+48); break;
	 }
}
/********************************************************************/
void servicekey3()
{
    switch(mode)
	{
	case 0: min_setting(); break;
	case 2: output1_setting();break;
	case 3: pgm_setting2(); break;
	}
}	 
/********************************************************************/
void servicekey0()
{
  switch(mode)
  {
   case 0:i_LCD(); // reinit lcd manually
   break;
   case 3: 
   ++state;
   if(state == 3)
   {
      state = 0; // back to initial sequence 
      ++n; // next record
	}
	  if(n>5) n = 0; // loop back to record 0
	  print_record(0x80,n);
    commande_LCD(0xc7); LCDWD(state+48);
	break;
  }
}   
/********************************************************************/
// use bit 0 and bit 1 for two bits output at port B

void print_output(char m)
{  
   commande_LCD(0xc1);
   if(m&2) LCDWD('*');
   else LCDWD('-');
   commande_LCD(0xc3);
   if(m&1) LCDWD('*');
   else LCDWD('-');
   fire_output(m); // send to PTB also
}    
/********************************************************************/
void servicekey1()
{
   ++mode;
   if(mode>4) mode = 0;
    commande_LCD(0xc5);
	LCDWD(mode+48);
	LCDWD('.');	
	LCDWD(state+48); // mode 3 has more state
    switch(mode)
	{
	case 3:
	n=0;
	print_record(0x80,n);
	state = 0; 
	break; 	 
	case 4:
	n=0;
	state = 0;
	print_record(0x80,n); // print record 0
	commande_LCD(0xc7); // mode 4 shows record number
	LCDWD(n+48); 
	break; 
	}
}

/********************************************************************/

void scrute_clavier()
{	// controle l'appui sur une touche toutes les 250ms	

  if(++timer2 > 20)
  { 
   timer2 = 0;
   if(touche0 == 0) traite_touche0();
   if(touche1 == 0) traite_touche1();  
   if(touche2 == 0) traite_touche2();
   if(touche3 == 0) traite_touche3();
   }
}	  	  

/********************************************************************/
void tempo(int j)
{ 
   unsigned int i; 
   for (i = 0; i < j; i++) 
   COPCTL = 0;   // clear COP otherwise the cpu itself will be reset 	  
} 
/********************************************************************/
void selection_LCD()
{ 
    // on utilise la pin ENABLE de l'afficheur                  
    PORTB |= 2; // mettre le bit1=1 
    tempo(1);
		PORTB &= ~2;   // mettre le bit1=0
} 
/********************************************************************/
void commande_LCD(char c)      
{ 
		/* écrire dans le registre d'instruction */ 

		char temp;
    temp = c;
		PTB &= ~3;			// clear RS and E
		PTB &= 0x0f;		// clear high nibbel to low
		c &= 0xf0;			// prepare only high nibble
		PTB |= c;
		pulseE();
		c = temp;
		c <<=4;
		PTB &= 0x0f;
		PTB |= c;
		pulseE();
		delay(5);
}	 
/********************************************************************/
void data_LCD(char c)     
{ 
/* écrire dans le registre de données */	
    char temp;
    temp = c;
	PTB |= 1; // set bit RS
	PTB &= ~2; // clear bit E
    PTB &= 0x0f; // clear high nibbel to low
	c &= 0xf0; // prepare only high nibble
	PTB |= c; // mearge high nibble first
	pulseE();
	c = temp;
	c <<=4;
	PTB &= 0x0f;
	PTB |= c;
	pulseE();
	delay(5);
}	
/********************************************************************/
void initalisation_LCD() 
/* circuit standard Hitachi HD44780 mode 4 bits */ 
{
  PORTB &= ~3;	// clear RS and E
	PORTB |= 0x30;
	pulseE(); 
  delay(200); 
  pulseE(); 
  delay(50); 
  pulseE(); 
  delay(50); 
  PORTB &= 0x0f;	// et logique	
	PORTB |= 0x20;	// ou logique
	pulseE(); 
  pulseE(); 
  pulseE(); 
  commande_LCD(0x28); // passe le bus en mode 4bits, 1ligne- 16caractéres, 5*7 points  
  commande_LCD(0x0c); // display on/off on display,off cursor, no blink
  commande_LCD(0x06); // entry mode DDRAM auto address increment 
  commande_LCD(1);    // clear display  
  delay(50); 
  
  0x28 		mode 4 bits, 2 lignes, caractère 5x8
		
		LDAA	#%00001110		Display ON, Cursor ON, Blink OFF
		BSR	LCD_CMD

		LDAA	#%00000110		Auto Inc, Cur. shift R, no Disp shift
 		BSR	LCD_CMD


  
  
} 
/********************************************************************/
// print LCD text >8
// the 16x1 line LCD has two blocks start address!
void affiche_chaine(char *chaine)
{
	  char i;
		commande_LCD(0x01);		// efface LCD
		commande_LCD(0x80);		// fixe la position de depart du curseur
		for(i=0; i<8; i++)
	  LCDWD(*s++);
	  commande_LCD(0xc0); // new address for position 9
	  for(i=0; i<8; i++)
	  LCDWD(*s++);
}		
/********************************************************************/
void affiche_horloge(char d)
{
// affiche l'heure sur l'afficheur en  0x80 or 0xc0

   buffer[0] = *day_str[day];
   buffer[1] = *(day_str[day]+1);
   buffer[2] = ' ';
   buffer[3] = hour/10+48;
   if(buffer[3] =='0') buffer[3] = ' ';
   buffer[4] = hour%10+48;
  
  // buffer[5] = ':'; left for tick display
  
   buffer[6] = min/10+48;
   buffer[7] = min%10+48;
   commande_LCD(d);
   for(i=0; i<5; i++)
     LCDWD(buffer[i]);
	 
	 commande_LCD(0x86);
	 LCDWD(buffer[6]);
	 LCDWD(buffer[7]);
}   

// print record number #n on LCD at location d
/********************************************************************/
void print_record(char d,char n)
{
   buffer[0] = *day_str[PGM[n].set_day];
   buffer[1] = *(day_str[PGM[n].set_day]+1);
   buffer[2] = ' ';
   buffer[3] = PGM[n].set_hour/10+48;
   if(buffer[3] =='0') buffer[3] = ' ';
   buffer[4] = PGM[n].set_hour%10+48;
  
   buffer[5] = ':';
   buffer[6] = PGM[n].set_min/10+48;
   buffer[7] = PGM[n].set_min%10+48;
   commande_LCD(d);
   for(i=0; i<8; i++)
     LCDWD(buffer[i]);
   print_output(PGM[n].set_control);	 
}
/********************************************************************/
// sink current driving, fire with logic '0'
void fire_output(char N)
{
  	if(N&1) PTB &= ~0x08; // clear PTB.3
	else PTB |= 0x08; // set PTB.3
	
	if(N&2) PTB &= ~0x04; // clear PTB.2
	else PTB |= 0x04; // set PTB.2
}	
/********************************************************************/
 
 
// scan 5 records, compare
// if matched, fires output
void compare_time()
{
   char i;
   for(i=0; i<5; i++)
    {
   if(PGM[i].set_day != 8) // skip comapre time if day = 8
   {  
       if(PGM[i].set_day == 0) // daily control
        {
		if(PGM[i].set_hour == hour && PGM[i].set_min == min)
	     {
		 output=PGM[i].set_control;
		 print_output(output);
		 }
   		}
   	  else
	  {	   	 		// check day also			
      
 if(PGM[i].set_day == day && PGM[i].set_hour == hour && PGM[i].set_min == min)
	   {
	   output = PGM[i].set_control;
	   print_output(output);
       }
	   }	
     } 
	}
}   				   

/********************************************************************/ 
void main()
{
 	 
 	 // attend la fin du reset materiel : par exemple 100ms

	
 	 delay(1000);				// tempo démarage du  LCD
	 DDRB = 0xff;				// configuration port B en output 
	 PORTB = 0x30;			// clear port B
	 delay(1000);
	 n = k=0;
 		 
	 TMODH = 0x04;
	 TMODL = 0xe0; //0xe2; // for 100Hz generation! 
	 // value in TMODL can be adjusted for precise timing
	 TSC = 0x43; // run timer with interrupt 
	 // 4Mhz/4 = 1Mhz/8 = 125000Hz
	 DDRA = ~0xf; // PA0-PA3 are input
	 PTA = 0xff; // drive oscillator high
	// DDRB = 0xff; // port B is output port
	// PTB = 0; // clear port B
	// PTAPUE = ~0x81; // osc2 pin is PTA4 I/O 
	 //ADCLK = 0x40; // ADC clock= fbus/4
	 
	 initialisation_LCD();
	 PTAPUE = 0x0f; // enable internal pullup
	 
	 CONFIG2 = 0x18; // external XTAL osc!
	 delay(10);
	 OCSTAT = 0x02; // enable external clock!
	 
	 affiche_chaine(titre);
	 delay(20000);
	 commande_LCD(1); // clear lcd
	 
	 // skip initial load if warm boot == '%'
	 if(warm_boot != '%')
	 {
	 warm_boot = '%';
	 day = 1;
	 hour = 10;
	 min = 40;
	 sec = 0;
	 }
	 
	 asm cli; // active les irqs 
	 	 
	for(;;)
	{
	 COPCTL = 0; // clear COP 	 
	}
}	  
/********************************************************************/

