/*** rgb.c 
 * 
 * Basic RGB colourwash module for STM8.
 * Requires Timer1 and Timer4 hardware resources
 * and three Timer1 GPIO outputs.
 * 
 * Created December 2010 by James Fowkes
 */ 

/* Standard Library Includes */
#include <stdlib.h>

/* Other Includes */
#include "stm8s.h"

#include "rgb.h"

#include "stm8s_tim1.h"
#include "stm8s_tim4.h"
#include "stm8s_type.h"

/* This module implements an random RGB colour creator
 * it will generate a 10 complete RGB "cycles"
 * In each cycle, each sepreate colour will go through the LOW (off), RISING, HIGH (fully on), FALLING pattern,
 * starting in a randomly determined position 
 */

/* Possible brightness states for a single colour to be in */
typedef enum {
	LOW,
	RISING,
	HIGH,
	FALLING
} state_enum;

/* A colour can be in a particular state, with a brightness value if rising or falling. */
typedef struct {
	state_enum state; // Current state of the colour
	u8 value; // Current brightness value
	u8 wait; // If in a waiting state, time the wait so we know when to stop.
} colour_t;

/* There are three colours */
colour_t rgb[] = {
	{LOW, 0},
	{LOW, 0},
	{LOW, 0},
};
/* ...which we can reference individually as well */
colour_t * const r = &rgb[0];
colour_t * const g = &rgb[1];
colour_t * const b = &rgb[2];

/* The LEDs may be common anode or common cathode */
led_type _ledType = CC; // Default to common cathode LEDs

/* Local Function Prototypes */
static void rgbSetTimerValues(void);
static bool rgbTestStateCondition(void);

void rgbInit(led_type ledType) {
	
	/* Timer1 will handle the PWM output */
	TIM1_DeInit();
	
	/* Count up to 4095 before reset */
	TIM1_TimeBaseInit(0, TIM1_COUNTERMODE_UP, 255, 0);
	
	/* Start with zero pulse width */
	TIM1_OC1Init(TIM1_OCMODE_PWM2, TIM1_OUTPUTSTATE_ENABLE, TIM1_OUTPUTNSTATE_ENABLE, 0x00, TIM1_OCPOLARITY_LOW, TIM1_OCNPOLARITY_HIGH, TIM1_OCIDLESTATE_SET, TIM1_OCNIDLESTATE_RESET);  
	TIM1_OC2Init(TIM1_OCMODE_PWM2, TIM1_OUTPUTSTATE_ENABLE, TIM1_OUTPUTNSTATE_ENABLE, 0x00, TIM1_OCPOLARITY_LOW, TIM1_OCNPOLARITY_HIGH, TIM1_OCIDLESTATE_SET, TIM1_OCNIDLESTATE_RESET);
	TIM1_OC3Init(TIM1_OCMODE_PWM2, TIM1_OUTPUTSTATE_ENABLE, TIM1_OUTPUTNSTATE_ENABLE, 0x00, TIM1_OCPOLARITY_LOW, TIM1_OCNPOLARITY_HIGH, TIM1_OCIDLESTATE_SET, TIM1_OCNIDLESTATE_RESET);

	TIM1_Cmd(ENABLE);

	TIM1_CtrlPWMOutputs(ENABLE);
	
	/* Timer 4 will handle stepping through colour levels */
	TIM4_DeInit();
	TIM4_TimeBaseInit(TIM4_PRESCALER_128, 125); // (16MHz / 128) / 1000Hz = 125 counts
	TIM4_ITConfig(TIM4_IT_UPDATE, ENABLE);
	enableInterrupts();
	TIM4_Cmd(ENABLE);
	
	// Set the LED type  for timer function
	_ledType = ledType;
	rgbNewCycle();
}

void rgbNewCycle(void) {
	
	int cs = 0; // Colour select
	
	/* Randomise the 'phase' of each colour
	 * and ensure at least one color moves */
	do {
		r->state = ( rand() % 4 );
		g->state = ( rand() % 4 );
		b->state = ( rand() % 4 );
	} while (!rgbTestStateCondition());
	
	/* Ensure each colour starts with the right value for the phase */
	for (cs = 0; cs < 3; cs++) {
		switch ( rgb[cs].state ) {
			case LOW:
				rgb[cs].value = 0;
				break;
			case RISING:
			case FALLING:
				rgb[cs].value = ( rand() % 255 );
				break;
			case HIGH:
				rgb[cs].value = 255;
		}
	}
}

void rgbStep(void) {

	static u8 step = 0;
	static u8 q = 0;
	u8 cs = 0; // Colour select
	
	if (++step == 255) {
		step = 0;
		// Count quarter cycles, do 10 full cycles = 40 quarter cycles before changing color
		if (++q == 40) {
			rgbNewCycle();
		}
	}
	
	for (cs = 0; cs < 3; cs++) { // Process each colour
		switch ( rgb[cs].state ) {
			case LOW:
				// Stay low for one cycle, then start to rise
				if (++rgb[cs].wait == 255) {
					rgb[cs].state = RISING;
					rgb[cs].value = 0;
				}
				break;
			case RISING:
				// Rise to max value and then stay high for one cycle
				if (++rgb[cs].value == 255) {
					rgb[cs].state = HIGH;
					rgb[cs].wait = 0;
				}
				break;
			case FALLING:
				// Fall to zero and then stay low for one cycle
				if (--rgb[cs].value == 0) {
					rgb[cs].state = LOW;
					rgb[cs].wait = 0;
				}
				break;
			case HIGH:
				// Stay high for one cycle, then start to fall
				if (++rgb[cs].wait == 255) {
					rgb[cs].state = FALLING;
					rgb[cs].value = 255;
				}
				break;
		}
	}
	rgbSetTimerValues();
}

static bool rgbTestStateCondition(void) {
	
	/* Return false if none of the colours are
	 * in a "moving" state */
	
	bool rtn;
	
	rtn = (r->state == LOW) || (r->state == HIGH);
	rtn &= (g->state == LOW) || (g->state == HIGH);
	rtn &= (b->state == LOW) || (b->state == HIGH);
	
	return !rtn;
}

void rgbSetColour(u8 rval, u8 gval, u8 bval) {
	
	/* Immediately overrides colour settings.
	Note: state values remain unaffected. */
	r->value = rval;
	g->value = gval;
	b->value = bval;
	rgbSetTimerValues();
}

static void rgbSetTimerValues(void) {
	
	if (_ledType == CC) {
		// Common cathode - do not invert
		TIM1_SetCompare1(b->value);
		TIM1_SetCompare2(g->value);
		TIM1_SetCompare3(r->value);
	} else {
		// Common anode - invert the output
		TIM1_SetCompare1(255 - b->value);
		TIM1_SetCompare2(255 - g->value);
		TIM1_SetCompare3(255 - r->value);
	}
	
}

