/* PICSEL Version 1.5                             */
/* (c) C.Schirp  released for non-commercial use  */
/* IR-routines copyright by Jos van Eijndhoven    */
/* debouncing copyright by Peter Dannegger        */
/*                                                */
/* V1.4->V1.5: Bei Tasten+/- auch sperrzeit       */
/*             Sperr- und Nachlaufzeit getrennt   */
/*                                                */
/* V1.3->V1.4: Tape-Relais ausblenden, wenn       */
/*             Tape-Gruppe inaktiv                */
/*                                                */
/* V1.2->V1.3: FB-Tasten 5&6 umrangiert,Nachlauf  */
/*             verkürzt                           */


#include <E:\projekte\picsel\cc5\16F628.h>
#include <E:\projekte\picsel\cc5\int16CXX.h>

#pragma config |= 0x3F18


/* Defines */
#define KEY_INPUT PORTA
#define RCLK	RB5
#define SCLK	RB2
#define SER 	RB1
#define M_DIR	RB4
#define M_PWM   RB3

#define INPUT 1
#define OUTPUT 0

#define NL_TIME 10;
#define SP_TIME 20;

unsigned char key_state;	 //debounced and inverted key state:
unsigned char key_press;
unsigned char key_collected;

uns16  ircode;
unsigned char irled; // 1 if light is on
unsigned char old_ircode;
unsigned char nachlauf;
unsigned char sperrzeit;

unsigned char tmr0cpy;


//bit Monitor; // Shadow fuer PB6

bit irledn     @ PORTB.0;

/*------------------------------------------------------------------*/
/*                                                                  */
/*                                                                  */
/*                                                                  */
/*                                                                  */
/*                                                                  */
/*------------------------------------------------------------------*/


#pragma origin 4
interrupt int_server( void)
{
    int_save_registers    // W, STATUS (and PCLATH)

    if ( INTF)
    {
        /* RB0/INT interrupt */
        irled.0 = !irledn;
        INTF = 0;
        if (irled)     // request interrupt on next edge (up or down)
           INTEDG = 1; // if 1, then interrupt on rising edge
        else
           INTEDG = 0; // is part of OPTION_REG, is 1 on reset

        /* finish and restart TMR0 */
        tmr0cpy = TMR0;
        TMR0 = 0;
        T0IF = 1; // end polling wait-loop, similar to TRM0 wraparound
    }

   /*
     NOTE: GIE is AUTOMATICALLY cleared on interrupt entry and set
           to 1 on exit (by RETFIE). Setting GIE to 1 inside the
           interrupt service routine will cause nested interrupts
           if an interrupt is pending. Too deep nesting may crash
           the program !
   */

    int_restore_registers // W, STATUS (and PCLATH)
}

/*------------------------------------------------------------------*/
/*   II  N   N  II TTTTT                                            */
/*   II  NN  N  II   T                                              */
/*   II  N N N  II   T                                              */
/*   II  N  NN  II   T                                              */
/*   II  N   N  II   T                                              */
/*------------------------------------------------------------------*/


void init ()
{
	/* PORT A Config */
	CMCON = 0x07;		// kein Komparator
	TRISA = 0xFF;		// alle input
    PORTA = 0;			// alle low

	/* PORT B Config */
	TRISB = 0b1000.0001;	// PB0,7 input, rest output
	T1CON = 0x00;			// Timer disabled
	RB6 =   0;				// Monitor-LED high=aus

	RCLK = 0;				// seriell reset state
	SCLK = 0;
	SER  = 0;

	M_DIR = 0;				// Motorsignale
	M_PWM = 0;

	/*sonstiges*/
	OPTION_REG = 0x04;/* pull-up portB, falling edge int, no WDT prescaler, prescale=32 */


	/* Variablen initialisieren */
	key_state = 0xFF;
	//Monitor = 0;
	irled = 	0;
	old_ircode= 0;
	nachlauf =  0;

	INTCON = 0; 			// reset and disable all interrupts by default
	INTE = 	1;
	GIE = 	 1;				// Enable Interrupts
}

/*------------------------------------------------------------------*/
/*    EEEE  EEEE  PPPP   RRRR    OOO   M     M                      */
/*    E     E     P   P  R   R  O   O  MM   MM                      */
/*    EEE   EEE   PPPP   RRRR   O   O  M M M M                      */
/*    E     E     P      R R    O   O  M  M  M                      */
/*    EEEE  EEEE  P      R  R    OOO   M     M                      */
/*------------------------------------------------------------------*/



void write_eeprom( unsigned char adr, unsigned char data)
{
	bit prev_gie = GIE;
	GIE = 0; 		// disable interrupts
	EEIF = 0; 		// clear eprom interrupt flag

	while (WR)
		; 			// wait if previous write is still busy

	EEADR = adr; 	// valid range is from 00h to 7Fh (128 bytes)
	EEADR &= 0x7f; 	// prevent bugs to overwrite program memory
	EEDATA = data;
	WREN = 1;
	EECON2 = 0x55;
	EECON2 = 0xAA;
	WR = 1; 		// start the actual write

	WREN = 0; 		// protect against unintended writes
	GIE = prev_gie; // restore interrupts
}

unsigned char read_eeprom( unsigned char adr)
{
	EEADR = adr; 	// valid range is from 00h to 7Fh (128 bytes)
	EEADR &= 0x7f; 	// prevent bugs to exceed program memory
	RD = 1;
	return EEDATA;
}

/*------------------------------------------------------------------*/
/*    K  K  EEEE  Y   Y   SSS                                       */
/*    K K   E      Y Y   S                                          */
/*    KK    EE      Y     SSS                                       */
/*    K K   E       Y        S                                      */
/*    K  K  EEEE    Y     SSS                                       */
/*------------------------------------------------------------------*/


void debounce(void)
{
  static unsigned char ct0, ct1;
  unsigned char i;

  i = key_state ^ ~KEY_INPUT;	// key changed ?
  ct0 = ~( ct0 & i );			// reset or count ct0
  ct1 = ct0 ^ (ct1 & i);		// reset or count ct1
  i &= ct0 & ct1;				// count until roll over
  key_state ^= i;				// then toggle debounced state
  key_press |= key_state & i;	// 0->1: key pressing detect
}


void do_keys(void)
{
	switch (key_press)
	{
	case 0x01:	RA0 = 0; TRISA0 = OUTPUT;
				TRISA |= 0x0E; // TRISA 1,2,3 setzen
				//TRISA1 = INPUT;
				//TRISA2 = INPUT;
				//TRISA3 = INPUT;
				break;
	case 0x02:	RA1 = 0; TRISA1 = OUTPUT;
				TRISA |= 0x0D; // TRISA 0,2,3 setzen
				//TRISA0 = INPUT;
				//TRISA2 = INPUT;
				//TRISA3 = INPUT;
				break;
	case 0x04:	RA2 = 0; TRISA2 = OUTPUT;
				TRISA |= 0x0B; // TRISA 0,1,3 setzen
				//TRISA0 = INPUT;
				//TRISA1 = INPUT;
				//TRISA3 = INPUT;
				break;
	case 0x08:	RA3 = 0; TRISA3 = OUTPUT;
				TRISA |= 0x07; // TRISA 0,1,2 setzen
				//TRISA0 = INPUT;
				//TRISA1 = INPUT;
				//TRISA2 = INPUT;
				break;
	case 0x10:	RA4 = 0; TRISA4 = OUTPUT;
				TRISA7 = INPUT;
				TRISA6 = INPUT;
				break;
	case 0x20:	RA5 = 0; TRISA5 = INPUT;
				if (RB6) // Monitor
				{
					RB6 = 0;
					//Monitor = 0;
				}
				else
				{
					RB6 = 1;
					//Monitor = 1;
				}
				break;
	case 0x40:	RA6 = 0; TRISA6 = OUTPUT;
				TRISA7 = INPUT;
				TRISA4 = INPUT;
				break;
	case 0x80:	RA7 = 0; TRISA7 = OUTPUT;
				TRISA6 = INPUT;
				TRISA4 = INPUT;
				break;
	default: break;
	}

}

void collect (void)
{
	key_collected = TRISA;

	if (!RB6) // Monitor
		key_collected &= 0xDF;
}

void restore_state(void)
{
	unsigned char tester;

	key_collected = read_eeprom(0);
	tester = read_eeprom(1);
	key_press = read_eeprom(2);

	// 2 aus 3
	if (key_collected==tester)
	{
	}
	else if (key_collected==key_press)
	{
	}
	else if (tester == key_press)
	{
		key_collected = tester;
	}

	key_press = 0; // Variable initialisieren

	// gueltige Bitkombinationen fuer TAPE testen
	tester = key_collected;
	tester &= 0b1101.0000;

	switch (tester)
	{
		case 0b0101.0000:
		case 0b1001.0000:
		case 0b1100.0000: break;
		default: key_collected |= 0b.1101.0000; break;
	}

	// gueltige Bitkombinationen fuer Phono/CD/Tuner/AUX testen
	tester = key_collected;
	tester &= 0b0000.1111;

	switch (tester)
	{
		case 0b0000.0111:
		case 0b0000.1011:
		case 0b0000.1101:
		case 0b0000.1110: break;
		default: key_collected |= 0b.0000.1111; break;
	}

	// Tape/Monitor Port und Shadow setzen
	if (key_collected & 0x20)
	{
		RB6 = 1;
		//Monitor = 1;
	}
	else
	{
		RB6 = 0;
		//Monitor = 0;
	}

	key_state = ~key_collected;

	// Tape/Monitor-Bit setzen (immer Eingang!)
	key_collected |= 0x20;

	TRISA = key_collected;
}

void set_relais(void)
{

	unsigned char temp;
	unsigned char i;

	temp = ~key_collected;

	// kleiner Stromspartrick:
	// Wenn Tape/Monitor nicht aktiv ist, Tape-Relais-Bits ausblenden
	if (key_collected & 0x20)
	{
		temp &= 0b0010.1111;
	}


	// Bits per SW austoggeln
	for ( i = 0; i>= 1;
	}

	// alles wieder auf Idle zurueck
	SCLK = 0;
	SER  = 0;

	// D-Latch clock betaetigen
	RCLK = 1;
	RCLK = 1;
	RCLK = 0;
}

/*
void pause(unsigned char ms)	// ms Milisekunden warten (fosc=4MHz)
{
	while(ms)				// Schleife verlassen wenn ms=0 ist
	{
    	OPTION = 2; 		// Vorteiler auf 8 einstellen
    	TMR0 = 131; 		//  [256-131=125]: 125 * 8 = 1000 (=1ms)
    	while (TMR0);		// abwarten einer Milisekunde
    	ms--;				// "ms" mit jeder Milisekunde ernidrigen
    }

    OPTION = 4; 			// Zustand für TMRWAIT herstellen
}
*/

/*------------------------------------------------------------------*/
/*    RRR    CCC    55555                                           */
/*    R  R  C   C   5                                               */
/*    RRR   C       5555                                            */
/*    R R   C   C       5                                           */
/*    R  R   CCC    5555                                            */
/*------------------------------------------------------------------*/

// 4MHz crystal, 1MHz cycle time, TMR0 prescale=32: timer units of 32 usec.
// TRM0 expires at 8 msec.

extern uns8 tmr0wait( void)
{
	tmr0cpy = 0xff; // potential longest waiting time, reduced by irled
	while (!T0IF) // wait for TMR0 wrap-around or IRled
		;

	T0IF = 0;
	return tmr0cpy;
}

void ir_bogus( void)
{
	// just eat irled input, as long as we have fast pulses
	uns8 t;

	t = 0;
	while (t < 200)
	   t = tmr0wait();

	ircode = 0xffffL; // bogus code;
}

void ir_philips_rc5(void) // now assuming no 'extended' RC5, so S2==1
{
	// 1 is sequence dark,light:  2x 900us == 2x28
	// 0 is sequence light,dark:  2x 900us == 2x28
	// frame is: one,one,toggle,11xdata
	unsigned char t;
	unsigned char i;

	t = 0;
	i = 0;
	ircode = 0L;
	// starting now in the 2nd (light) half S2
	while (t < 80)
	{
	   // last half of current bit still to be measured, its timer is running
	   ircode <<=1; // insert current value
	   if (irled) // irled input is active low
		   ircode.0 = 1;
	   i++;

	   t = tmr0wait();

	   if (t < 38) // new codebit is same as current, edge at bit boundary
	   {
	   	  t=tmr0wait(); // skip first half of new bit
	   }
	}

	ircode.high8 &= 0x07; // clear start bits S2 and T

	if (i < 13)
	{
		ircode = 0xffffL; // bogus
	}
}


void get_ir( void)
{
	unsigned char ton;
	unsigned char toff;

    ton = tmr0wait(); // measure first light
	toff = tmr0wait(); // following first dark

    if (ton  > 20 && ton  < 36 &&  // nominal 900/32 = 28
        toff > 20 && toff < 36)  // nominal 900/32 = 28
    {
		ir_philips_rc5();
	}
	else
	{
		ir_bogus();
	}
}

void do_remote(void)
{
	unsigned char h;



	// systemadresse testen
	h = ircode.high8 & 0b.0000.0111;
	if (h != 4)
	{
		return;
	}

	h = ircode.low8 & 0b.1100.0000;
	if (h != 0)
	{
		return;
	}

	// okay, kommando gilt uns: Commando extrahieren
	h= ircode.low8 & 0b.0011.1111;

	// Quellenumschaltung nur bei neuem IR-Code
	if (h != old_ircode)
	{
		// Quellenwahltasten = 1..8
		if ((0<h) && (h<9))
		{
			// Tasten 5 und 6 tauschen
			if (5==h)
			{
				h=6;
			}
			else if (6==h)
			{
				h=5;
			}

			h-=1;	// Taste 1 entspricht Bit 0
			key_press = 1;
			key_press <<= h;
			h+=1;	// code wiederherstellen

			// Tasten 5 und 6 tauschen
			if (5==h)
			{
				h=6;
			}
			else if (6==h)
			{
				h=5;
			}

		}

		if (h == 16) // Volume +
		{
			M_DIR = 1;
			M_PWM = 0;
			nachlauf = NL_TIME;
			sperrzeit = SP_TIME;
		}
		else if (h == 17) // Volume -
		{
			M_DIR = 0;
			M_PWM = 1;
			nachlauf = NL_TIME;
			sperrzeit = SP_TIME;
		}

	}



	old_ircode = h;

	// bei Taste
	if (h == 5)
		sperrzeit = SP_TIME;

}


/*------------------------------------------------------------------*/
/*     M     M    A    II  N   N                                    */
/*     MM   MM   A A   II  NN  N                                    */
/*     M M M M  A   A  II  N N N                                    */
/*     M  M  M  AAAAA  II  N  NN                                    */
/*     M     M  A   A  II  N   N                                    */
/*------------------------------------------------------------------*/

void main (void)
{
	unsigned char tmp;

	init();

	restore_state();

	collect();

	set_relais();

	while (1)
	{

		// Schalter lesen und entprellen
		debounce();

		// wenn Taste, dann EEPROM und Relais schreiben
		if (key_press)
		{
			// Auswerten, LEDs setzen
			do_keys();

			collect();
			write_eeprom(0,key_collected);
			write_eeprom(1,key_collected);
			write_eeprom(2,key_collected);
			set_relais();
		}
		// zustand zuruecksetzen
		key_press = 0;

		tmp = tmr0wait(); // wrap-around or interrupt, wrap-around takes 8ms

		//pause(10);

	    if (irled) //came out of wait by interrupt: light on, start of new code
		{
			get_ir();
			do_remote();
		}

		// Motornachlauf?
		if (!nachlauf)
		{
			M_DIR = 0;	// Motor stoppen
			M_PWM = 0;
		}
		else
		{
			nachlauf--;
		}

		// Sperrzeit fuer Taste 5 (tape/Monitor)
		if (!sperrzeit)
		{
			old_ircode = 0; // Fuer Tape/Monitor
		}
		else
		{
			sperrzeit--;
		}



	} /* while (1) */
}