/* Program PIC RGB
 * 
 * This program generates random colors with a RGB LED
 *
 *   LED pins should be connected to pins GPIO2, GPIO4 and GPIO5 in RGB order
 *   PIC12 should be configured to work with IntOSC + PWRT + BODEN 
 *   MCLR has to be turned off if you want to leave the pin floating and thus
 * save a resistor
 *
 * 2007/08 (c) Joao Figueiredo
 * Based on TinyRGB from www.floery.net - Elektor 2007/08
 *
 */

#include<pic.h>

#define RED		GPIO2
#define GREEN	GPIO4
#define BLUE	GPIO5

#define ON	1
#define OFF 0
	
#define PWM_FREQ	100
#define PWM_STEPS	256

#define FOSC		(4000000UL)
#define TMR_FREQ	(PWM_FREQ*PWM_STEPS)	// 12800
#define TMR_COUNT	(FOSC/4/TMR_FREQ)	// value to count 78.125

#define TMR1V	(65535-(TMR_COUNT)+1)	// value to load to timer

// ----- Fuses -----
__CONFIG(MCLRDIS & PWRTEN & WDTDIS & INTIO);

// ----- Globals -----
unsigned char gRed = 0;
unsigned char gGreen = 0;
unsigned char gBlue = 0;
volatile unsigned char gT1Tick = 0;
volatile unsigned char gDelay = 0;

unsigned char r, g, b;
unsigned char transferTime;

/* **********************************************
 * Interrupt service routine
 * */
void interrupt isr(void)
{
	if(TMR1IF)
	{
		TMR1H = (TMR1V>>8)&0xFF;		
		TMR1L = (TMR1V&0xFF);	// reload timer

		gT1Tick++;

		if(gT1Tick==0)	// if 256 ticks have elapsed, reset and turn ON
		{
			if(gRed) RED = ON;
			if(gGreen) GREEN = ON;
			if(gBlue) BLUE = ON;
		}
		else			// turn OFF as values remain less than current tick
		{
			if(gT1Tick>gRed)
				RED = OFF;

			if(gT1Tick>gGreen)
				GREEN = OFF;

			if(gT1Tick>gBlue)
				BLUE = OFF;
		}

		// this is the counter of the delay function
		if(gDelay>0) gDelay--;

		TMR1IF = 0;
	}
}

/* **********************************************
 * PIC initialization
 * */
void initPic()
{
	GIE = 0;
	PEIE = 0;

	// LED OFF
	RED=0;
	GREEN=0;
	BLUE=0;

	// IO
	TRISIO = 0x0B;		// GP2, GP4 and GP5 as outputs 0b 0000 1011
	
	OPTION = 0xD0;		// 0b 1101 0000  T0CS=0, PSA=0, 1:1

	// Timer 1
	TMR1H = (TMR1V>>8)&0xFF;		
	TMR1L = (TMR1V&0xFF);		// (Fosc/4)/(78) = 12800Hz
	TMR1IF = 0;
	TMR1IE = 1;
	TMR1ON = 1;

	PEIE = 1;
	GIE  = 1;
}

/* **********************************************
 * Random char generator
 * */
char getRandomChar(void)
{
	static unsigned short shiftregister = 0x9500;

	unsigned short value;

	/* feedback based on 1+x10+x11 polynomial function */
	value = (shiftregister&0x0200)>>9 ^ (shiftregister&0x0400)>>10;

	/* feedback */
	shiftregister <<= 1;
	shiftregister |= value;

	return (char)shiftregister;
}

/* **********************************************
 * Generates a delay of aproximately 100 ms
 * */
void delay100ms()
{
	unsigned int i;

	for(i=0; i<PWM_FREQ; i++)
	{
		gDelay = PWM_STEPS/10;
		while(gDelay>0){};	// wait
	}
}

/* **********************************************
 * Generates a delay of aproximately sec seconds
 * */
void delay(char sec)
{
	unsigned int i;

	for(i=0; i<10*sec; i++)
	{
		delay100ms();
	}
}

/* **********************************************
 * Returns the absolute value of X
 * */
int abs(int x)
{
	return (x<0?-x:x);
}

/* **********************************************
 * Updates the color to its new value
 * The new value is usualy cur+step!
 * */
void update(unsigned char final, unsigned char *cur, signed int step)
{
	if( abs(final-*cur) > abs(step) )
		*cur += step;
	else if( abs(final-*cur) < abs(step) )
	{
		/*
		if(final<*cur)
			*cur -= 1;
		else
			*cur += 1;
		*/
		*cur=final;
	}
}

/* **********************************************
 * Determines the proper step to apply
 * */
signed int properStep(unsigned char final, unsigned char current, unsigned char time)
{
	signed int step;

	step = (final-current)/(signed int)time;

	if( ((final-current)%(time)) != 0 )
	{
		if(final-current > 0)
			step++;

		if(final-current < 0)
			step--;
	}

	return step;
}

/* **********************************************
 * Fades from current (gRed, gGreen, gBlue) color
 * to (r, g, b) color in transferTime seconds
 * */
void fade()
{
	// fade in 10*(number of seconds) steps

	signed int rStep, gStep, bStep;

	transferTime*=10;

	// calculate the step to apply in each cycle in each color
	rStep = properStep(r, gRed, transferTime);
	gStep = properStep(g, gGreen, transferTime);
	bStep = properStep(b, gBlue, transferTime);

	// Apply fade
	do
	{
		update(r, &gRed, rStep);
		update(g, &gGreen, gStep);
		update(b, &gBlue, bStep);

		delay100ms();

		transferTime--;
	}
	while(transferTime);
}

/* **********************************************
 * Lights up Red, then Green and finally Blue
 * before exiting turns them off
 * */
void testLed()
{
	/*
	gRed=255;gGreen=255;gBlue=255;
	delay(5);
	gRed=0;gGreen=0;gBlue=0;
	delay(5);
	*/
	
	transferTime=2;
	r=255;g=0;b=0;
	fade();
	transferTime=2;
	r=0;g=0;b=0;
	fade();
	delay(1);

	transferTime=2;
	r=0;g=255;b=0;
	fade();
	transferTime=2;
	r=0;g=0;b=0;
	fade();
	delay(1);

	transferTime=2;
	r=0;g=0;b=255;
	fade();
	transferTime=2;
	r=0;g=0;b=0;
	fade();
	delay(3);
}

/* **********************************************
 * Main of PicRGB
 * */
void main(void)
{
	unsigned char waitTime;
	
	initPic();

	testLed();

	while(1)
	{
		// Get new random color
		// Get wait and transfer times
		r = getRandomChar();
			waitTime = getRandomChar();			// up to 4 minutes
		g = getRandomChar();
			transferTime = getRandomChar();		// from 4 to 25 seconds
		b = getRandomChar();

		if(transferTime<4) transferTime = 4;
		if(transferTime>25) transferTime = 25;

		fade();

		delay(waitTime);
	}
}

