/*
 * Copyright 2008 Pavel Machek <pavel@ucw.cz>
 *
 * Distribute under GPLv3.
 */

#include <avr/io.h>
#include <avr/interrupt.h>
#define F_CPU 1000000UL   	/* 1MHz */
#include <util/delay.h>


/* ADC + LIGHT, no watchdog: 0x3c6 */
#if 0
#include <avr/wdt.h>
#else
#define wdt_enable(a) do {} while (0)
#define wdt_reset() do {} while (0)
#endif

typedef char bool;

inline void barrier(void)
{
	asm volatile("");
}

/* 0x36e bytes total */
#define ADC_SUPPORT
#define LIGHT
/* 0x40 bytes */
#undef INDICATOR 

static void __attribute__((noinline)) init_hw(void)
{
	//	MCUCR &= ~PUD;
	MCUCR |= PUD;
	DDRB = 1 << 1; //  | 1 << 3 | 1 << 4;
	/* ADC1 / B2 is input, voltage sensor is there */
 			/* B1 is output: green LED */
			/* B3 is output: powerful LED */
			/* B4 is output: surround LED */
	PORTB = 1 << 0 | 1 << 3 | 1 << 4;
			/* B0 is input: button, set it to high; 
			   turn off surround LED by default */
}

char mode;

struct led {
	char pin;

	char pwm_on;		/* cca 2000mA on beam  */
	char pwm_half;		/* cca 50mA on beam */
	unsigned char pwm_total;

	unsigned char time;
};

#ifdef INDICATOR
static struct led indicator = {
	.pin = 1,
	.pwm_on = 1,
	.pwm_total = 200,
};
#define IS_INDICATOR(led) (led == &indicator)
#else
#define IS_INDICATOR(led) 0
#endif
static struct led surround = {
	.pin = 4,
	.pwm_on = 1,
	.pwm_total = 200,
};
	
static struct led beam = {
	.pin = 3,
	.pwm_on = 1,
	.pwm_total = 200,
};

static void led_on(struct led *led)
{
	if (!IS_INDICATOR(led)) {
		DDRB |= (1<<led->pin);
		PORTB &= ~(1<<led->pin);

	} else {
		PORTB |= (1<<led->pin);
	}
}

static void led_off(struct led *led)
{
	if (!IS_INDICATOR(led)) {
		DDRB &= ~(1<<led->pin);
		PORTB |= 1<<led->pin;
	} else {
		PORTB &= ~(1<<led->pin);
	}		
}

static void fast_blink(struct led *led, char num)
{
	char j;
	for (j=0; j<num; j++) {
		unsigned short i;

		wdt_reset();
		for (i=0; i<10; i++)
			led_on(led);

		for (i=0; i<60000; i++)
			led_off(led);

	}
}

#ifdef INDICATOR
static void
fast_blink_indicator(char num)
{
	fast_blink(&indicator, num);
}
#endif



#ifdef ADC_SUPPORT
static volatile short adcval = -1;

/*
 * ADC conversion complete.
 */
ISR(ADC_vect)
{
	adcval = ADCW;
	ADCSRA &= ~_BV(ADIE);         /* disable ADC interrupt */
}
#endif


static void panic(void)
{
	while(1)
#ifdef INDICATOR			
		led_on(&indicator);
#else
	;
#endif
}

static void  __attribute__((noinline)) adc_read(void)
{
	init_hw();
	for (unsigned long i=0; i<10000; i++)
		led_on(&beam);

#ifdef ADC_SUPPORT
	ADCSRA = _BV(ADEN) | _BV(ADPS1) | _BV(ADPS0);
	ADMUX = _BV(REFS0) | 1; 	/* Internal reference 1.1V, ADC1 */
	sei();   

				/*
				 * Start one conversion.
				 */
	ADCSRA |= _BV(ADIE);
	ADCSRA |= _BV(ADSC);
#endif
	mode = 0;
	for (unsigned long i=0; i<1000; i++)
		led_on(&beam);
	init_hw();
	if (adcval == -1)
		panic();
}

static int button_down_time = 0;


/* For some reason, inlining this makes code bigger */
void  __attribute__((noinline)) set_mode(int new_mode)
{
	/* For surround led from NiMH: 4 on 1 off -> 20mA
	                               1 on 25 off -> last usable that does not blink :-(
	*/

	mode = new_mode;

	surround.pwm_on = -1;
	surround.pwm_half = -1;
	beam.pwm_on = -1;
	beam.pwm_half = -1;
	surround.pwm_total = 200;
	beam.pwm_total = 200;

	switch (mode) {
	default:
	case -9 ... -1: break;
	case -10:
		adc_read();
		break;		
	case 9:
	case 0:		/* cca 1mA -- leaks on FETs? PUD seems irrelevant */
			/* cca 12mA if I turn green LED on?! */
		mode = 0;
		break;
	case 1:			/* 4mA ? */
		surround.pwm_half = 1;
		surround.pwm_total = 2;
		break;
	case 2:		/*  35mA on surround ?*/
		surround.pwm_on = 1;
		surround.pwm_total = 25;
		break;
	case 3:
		surround.pwm_on = 1;
		surround.pwm_total = 25;
		beam.pwm_half = 1;
		beam.pwm_total = 2;
		break;
	case 4:		/* should be 100mA on surround */
		surround.pwm_on = 1;
		surround.pwm_total = 11;
		break;
	case 5:		/* cca 150mA beam, 100mA surround */
		surround.pwm_on = 1;
		surround.pwm_total = 11;
		beam.pwm_on = 1;
		beam.pwm_total = 25;
		break;
	case 6:		/* should be ~450mA beam, 100mA surround */
		beam.pwm_on = 1;
		beam.pwm_total = 8;
		surround.pwm_on = 1;
		surround.pwm_total = 11;
		break;
	case 7:		/* cca 1500mA */
		beam.pwm_on = 1;
		beam.pwm_total = 3;
		break;
	case 8:		/* max on both */
		surround.pwm_on = 1;
		surround.pwm_total = 11;	/* used to be 6 */
		beam.pwm_on = 1;
		beam.pwm_total = 3;
		break;


	/* Full on can reach 2000mA with "penguin" battery */
	}
}

static void handle_led(struct led *led)
{
	led->time++;
	if (led->time >= led->pwm_total)
		led->time = 0;

	if (led->time < led->pwm_on)
		led_on(led);
	else if (led->time < led->pwm_half) {

		DDRB |= (1<<led->pin);
		DDRB &= ~(1<<led->pin);
	} else
		led_off(led);
}

static void  __attribute__((noinline)) adc_process(void)
{
	/* Voltage should be cca  2V - 8V => adcval cca 230 .. 930 */

	init_hw();
	if (adcval < 230)
		panic();

	/* 10000 led_on, measure, 1000 led_on */
	/* Fully charged penguin                     230 + 664 (?) */
	/* Fully charged penguin on charger          230 + 280 (?) */
	/* Mostly full battery on charger: adcval =  230 + 360..368 */ 
	/* Mostly full battery:	    2.5A   adcval =  230 + 320..352 */
	/* Half empty battery:      1.0A   adcval =  230 + 200..208 */
	/* Dangerously empty battery:.1A   adcval =  230 + 144..168 */


	/* If I try to measure and there's not enough power, numbers are _too high_ */

	adcval -= 230;

	int blink;

	if (adcval > 300)
		blink = 10;		/* 9 blinks, cca 1.2A on both */
	else if (adcval > 180)		/* 5 blinks, ~0.5A  on both */
		blink = 6;
	else if (adcval > 140)		/* 3 blinks, ~0.2A on both, <6V unloaded. */
		blink = 4;
	else 	blink = 2;		/* 1 blink, never seen */

	fast_blink(&beam, blink);
	adcval = -1;
}


void main(void)
{
	init_hw();

	wdt_enable(WDTO_2S);
	//	fast_blink_indicator(10);


	while (1) {
		int button = PINB & 1;

		wdt_reset();
		if (button == 0) {
			button_down_time++;

			if (button_down_time==2000) {
				set_mode(mode-1);
				button_down_time = 1001;
			}
		} else {
			if ((button_down_time>100) && (button_down_time < 1000))
				set_mode(mode+1);

			button_down_time = 0;
		}

#ifdef ADC_SUPPORT
		if (adcval != -1)
			adc_process();
#endif

		handle_led(&surround);
		handle_led(&beam);
	}
}

