/* 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
 *
 * Version 3:
 *   Based on the original design with an improved fade method (hopefully)
 *
 *   Fade calculates the step, the error and distributes the error across
 *   the transfer period
 *
 *   It looks nice!
 */

#include<pic.h>

//#define STANDARD_TEST

#define RED		GPIO2
#define GREEN	GPIO4
#define BLUE	GPIO5

#define ON	0
#define OFF 1
	
#define PWM_FREQ	80
#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;

/* **********************************************
 * 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 50 ms
 * */
void delay50ms()
{
	unsigned int i;

	// formula is pwm_freq*pwm_steps/20 = 50ms
	// because pwm_freq*pwm_steps = 1sec

	for(i=0; i<PWM_FREQ; i++)
	{
		gDelay = PWM_STEPS/20;
		while(gDelay>0);	// wait
	}
}

/* **********************************************
 * Generates a delay of aproximately sec seconds
 * */
void delay(char sec)
{
	unsigned int i;

	for(i=0; i<20*sec; i++)
	{
		delay50ms();
	}
}

/* **********************************************
 * 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, signed int *error, unsigned char applyError)
{
	// apply step
	*cur += step;

	// if the error has a value, increment a unit...
	// ... and "decrement" the same unit in error
	if(applyError && *error)
	{
		if(*error>0) // if error is +
		{
			(*cur)++;
			(*error)--;	
		}
		else		// error is -
		{
			(*cur)--;
			(*error)++;
		}
	}
}

/* **********************************************
 * Determines the proper step to apply
 * */
signed int calcStep(unsigned char final, unsigned char current, unsigned int time)
{
	signed int step;

	step = (final-current)/(signed int)time;

	return step;
}

/* **********************************************
 * Determines the error of using this step 
 * */
signed int calcError(unsigned char final, unsigned char current, signed int step, unsigned int time)
{
	signed int error;
	signed int yf;

	yf = step*(signed int)time + current;	// equation of a line
	error = final - yf;			// difference because of integer math

	return error;
}

/* **********************************************
 * Fades from current (gRed, gGreen, gBlue) color
 * to (r, g, b) color in transferTime seconds
 * */
void fade(unsigned char r, unsigned char g, unsigned char b, unsigned int time)
{
	// fade in 20*time steps

	signed int rStep, gStep, bStep;
	signed int rErr, gErr, bErr;
	unsigned char rModulo, gModulo, bModulo;

	time *= 20;

	// calculate the step to apply in each cycle in each color
	rStep = calcStep(r, gRed, time);
	gStep = calcStep(g, gGreen, time);
	bStep = calcStep(b, gBlue, time);

	// calculate the error
	rErr = calcError(r, gRed, rStep, time);
	gErr = calcError(g, gGreen, gStep, time);
	bErr = calcError(b, gBlue, bStep, time);

	// Calculate the modulo period to distribute the error along the period
	if(rErr) rModulo = time/abs(rErr);
	else rModulo = 0;

	if(gErr) gModulo = time/abs(gErr);
	else gModulo = 0;

	if(bErr) bModulo = time/abs(bErr);
	else bModulo = 0;

	// Apply fade
	do
	{
		update(r, &gRed, rStep, &rErr, rModulo?time%rModulo==0:0);
		update(g, &gGreen, gStep, &gErr, gModulo?time%gModulo==0:0);
		update(b, &gBlue, bStep, &bErr, bModulo?time%bModulo==0:0);

		delay50ms();

		time--;
	}
	while(time);
}

/* **********************************************
 * Sets immediately this rgb triplet
 * */
void setRGB(unsigned char r, unsigned char g, unsigned char b)
{
	gRed = r;
	gGreen = g;
	gBlue = b;
}

/* **********************************************
 * 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);
	*/
	
#ifdef STANDARD_TEST
	// Red
	fade(255,0,0,2);
	fade(0,0,0,2);
	delay(1);

	// Green
	fade(0,255,0,2);
	fade(0,0,0,2);
	delay(1);

	//Blue
	fade(0,0,255,2);
	fade(0,0,0,2);
	delay(2);
#else
	// White test
	fade(255,255,255,2);
	delay(1);
	fade(0,0,0,2);
	delay(1);
	
	// Set colors using gray code

	// Red
	setRGB(255,0,0);
	delay(1);

	// Yellow
	setRGB(255,255,0);
	delay(1);

	// Green
	setRGB(0,255,0);
	delay(1);

	// Cyan
	setRGB(0,255,255);
	delay(1);

	// White
	setRGB(255, 255, 255);
	delay(1);

	// Magenta
	setRGB(255,0,255);
	delay(1);

	// Blue
	setRGB(0,0,255);
	delay(1);

	// Black
	setRGB(0,0,0);
	delay(1);

#endif
}

/* **********************************************
 * Main of PicRGB
 * */
void main(void)
{

//#define RANDOM_WAIT
#ifdef RANDOM_WAIT
	unsigned char waitTime;
#endif

	unsigned char red, green, blue;
	unsigned char transferTime;

	
	initPic();

	testLed();

	while(1)
	{
		// Get new random color
		// Get wait and transfer times
		red = getRandomChar();
#ifdef RANDOM_WAIT
			waitTime = getRandomChar();			// up to 4 minutes
#endif
		green = getRandomChar();
			transferTime = getRandomChar();		// from 4 to 25 seconds
		blue = getRandomChar();

		if(transferTime<4) transferTime = 4;
		if(transferTime>25) transferTime = 25;

		fade(red, green, blue, transferTime);

#ifdef RANDOM_WAIT
		delay(waitTime);
#else
		delay(20);
#endif
	}
}

