/*
 * TinyRC2 I/O using Sum PPM Signal
 *
 * Attiny 26
 *
 * set fuses for 4MHz internal RC Osc
 * avrdude -c USBasp -p t26 -U lfuse:w:0xE3:m -U hfuse:w:0x17:m
 *
 * program flash with
 * avrdude -c USBasp -p t26 -U flash:w:TinyRC2.hex
 *
 * Author: Heinz & Florian Bruederlin
 * 16.02.09 First implemented
 * 01.04.09 Warn implemented
 * 10.04.09 Wink reversed
 *
 * Channels of "webra nano S6"
 *	1 = Lift
 *  2 = Motor
 *  3 = Tip
 *  4 = Steering
 *  5 = Light / Blink    / Both
 *  6 = Horn  / Warnwink / Both
 */
#ifndef F_CPU
#define F_CPU 4000000     /* Clock in HZ */
#endif
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

/* PORTA defines                                        */
/* all anodes of the LEDs at PORTA are connected to VCC */
/* they are switched on with logic 0 value (inverted)   */
#define RC_NOK_OUT  PA0
#define BLINK0_OUT  PA1
#define BLINK1_OUT  PA2
#define BLINK2_OUT  PA3
#define BLINK3_OUT  PA4
#define BLINK4_OUT  PA5
#define BLINK5_OUT  PA6
#define BLINK6_OUT  PA7
#define BLINKX_OUT (_BV(BLINK0_OUT) | _BV(BLINK1_OUT) | \
			        _BV(BLINK2_OUT) | _BV(BLINK3_OUT) | \
			        _BV(BLINK4_OUT) | _BV(BLINK5_OUT) | \
			        _BV(BLINK6_OUT))


/* PORTB defines */
/* all outputs at PORTB are connected to driver transistors */
/* they are switched on with logic 1 value 					*/
#define LIGHT_OUT   PB0
#define LOUDSP_OUT  PB3
#define WINKL_OUT   PB4
#define WINKR_OUT   PB5
#define REC_IN	    PB6

/* RC range defines */
#define RC_MIN_VALUE  64
#define RC_MID_VALUE  98
#define RC_MAX_VALUE 128
#define RC_TOL_VALUE  14	// tolerance for bad value
#define RC_MIN_GAP   160	// GAP must be at least 3msec


/* RC states */
#define RC_WAIT_FOR_START 0
#define RC_TIME_CHANNEL	  1
#define RC_GOT_LONG_GAP	  2
static volatile unsigned char rc_state = RC_WAIT_FOR_START;

/* valid rc values received */
static volatile unsigned char rc_valid = 0;

/* RC channels */
#define RC_CHANNEL_CNT 6
static volatile unsigned char rc_channel = 0;
static volatile unsigned char rc_value[RC_CHANNEL_CNT];

/* RC switch levels */
#define RC_MID_TOL			10 // tolerance around RC_MID_VALUE, which is ignored
#define RC_IS_WINKL		(rc_value[3] > RC_MID_VALUE+RC_MID_TOL)
#define RC_IS_WINKR		(rc_value[3] < RC_MID_VALUE-RC_MID_TOL)

#define RC_IS_BEEP		(rc_value[1] < RC_MID_VALUE-RC_MID_TOL)

#define RC_SWITCH(v)	(((v)-RC_MIN_VALUE)/((RC_MAX_VALUE-RC_MIN_VALUE)/4)) 
												// divide channel into 4 ranges
#define RC_IS_LIGHT		(RC_SWITCH(rc_value[4])==1 || RC_SWITCH(rc_value[4])>=3)
#define RC_IS_BLINK		(RC_SWITCH(rc_value[4])==2 || RC_SWITCH(rc_value[4])>=3)



#define RC_IS_HORN		(RC_SWITCH(rc_value[5])==1 || RC_SWITCH(rc_value[5])>=3)
#define RC_IS_WARN		(RC_SWITCH(rc_value[5])==2 || RC_SWITCH(rc_value[5])>=3)



/* timing stuff */
#define TONE_HIGH_CMP 125						// 4MHz/64/125 = 500Hz
#define TONE_LOW_CMP  250						// 4Mhz/64/250 = 250Hz
#define TONE_HIGH_DIV 50-1 						// 500Hz/50 = 10Hz
#define TONE_LOW_DIV  25-1 						// 250Hz/25 = 10Hz
static volatile unsigned char tone_div = 0;		// current divider
static volatile unsigned char tone_cnt = 0;		// current divider value

#define WINK_FREQ 3-1							// 10/3 = 3.3Hz
static volatile unsigned char wink_cnt = 0;		// current wink value

#define BLINK_FREQ 1-1							// 10/1 = 10Hz
static volatile unsigned char blink_cnt   = 0;	// current blink value
static volatile unsigned char blink_state = 0;	// current blink value

static volatile unsigned char beep_cnt = 0;		// current blink value

/*
 * io_init -
 *	init io ports
 */
void io_init(void) {
	/* switch off LEDs */
	PORTA = _BV(BLINK0_OUT) | _BV(BLINK1_OUT) | _BV(BLINK2_OUT) |
	        _BV(BLINK3_OUT) | _BV(BLINK4_OUT) | _BV(BLINK5_OUT) |
		    _BV(BLINK6_OUT) | _BV(RC_NOK_OUT);
	/* Set PORT A outputs */
	DDRA  = _BV(BLINK0_OUT) | _BV(BLINK1_OUT) | _BV(BLINK2_OUT) |
	        _BV(BLINK3_OUT) | _BV(BLINK4_OUT) | _BV(BLINK5_OUT) |
		    _BV(BLINK6_OUT) | _BV(RC_NOK_OUT);
    
	/* clear outputs and set pullups */
	PORTB = _BV(REC_IN);						// enable pullup for input
												// and switch off LEDs
	/* Set PORT B to outputs */
	DDRB  = _BV(LIGHT_OUT) | _BV(LOUDSP_OUT) |
			_BV(WINKL_OUT) | _BV(WINKR_OUT);
}

/*
 * timer_init -
 *	init timer stuff
 *
 *	prescaler for timer0 is set to 64, which will result in an 
 *	interrupt every 16usec.
 *		 64 ticks are equal to approx. 1msec
 *		128 ticks are equal to approx. 2msec
 *		255 ticks are equal to approx. 4msec 
 *	The absolute maximum range with the FC-16 is 61..134
 *
 *	prescaler for timer1 is set to 64.
 *	in a frequency of 4MHz/64/OCR1C or
 *     OCR1C |   Freq
 *     ------|-------
 *      250  |  250Hz
 *      125  |  500Hz
 *      104  |  601Hz
 *       64  |  976Hz
 *       62  | 1042Hz
 *	     31  | 2016Hz
 */
void timer_init(void) {
    TCCR0 |= _BV(CS01) | _BV(CS00);     // set timer0 prescaler to 64
    TCNT0  = 0;							// clear timer0
	TIFR  |= _BV(TOV0);					// clear pending overflow 

    TCCR1B |= _BV(CS12) | _BV(CS11) | _BV(CS10) | // set timer1 prescaler to 64
			  _BV(CTC1);						  // clear timer1 on cmp match
}

/*
 * tone_set -
 *	set high/low frequency for sound (and timing).
 *	we avoid glitches, by checking if we have already the right settings
 */
void tone_set(unsigned char high) {
	if (high) {
		if (tone_div != TONE_HIGH_DIV) {
			OCR1C    = TONE_HIGH_CMP;
			tone_div = TONE_HIGH_DIV;
		}
	} else {
		if (tone_div != TONE_LOW_DIV) {
			OCR1C    = TONE_LOW_CMP;
			tone_div = TONE_LOW_DIV;
		}
	}
}

/*
 * sound_set -
 *	enable/disable sound output
 *	we check wether the TCCR1A has already the right settings
 *	to avoid gliches, when set/reset multiple times.
 */
void sound_set(unsigned char on) {
	if (on) {
		if (!(TCCR1A & _BV(COM1B0))) {
			TCCR1A |= _BV(COM1B0);
		}
	} else {
		if (TCCR1A & _BV(COM1B0)) {
			TCCR1A &= ~_BV(COM1B0);
			PORTB  &= ~_BV(LOUDSP_OUT); // switch off loudspeaker output
		}
	}
}
   
/*
 * intr_init -
 *	init interrupts
 *	timer1 overflow is used to recognize the end of a PPM frame after 3+n*4msec
 *	the external interrupt INT0 will be used to recognize the
 *	falling edge of the PPM channels:
 *		    _     _      _             _
 *         | |   | |    | |           | |
 *   ______| |___| |____| |___________| |_
 *           <-----><-----><------------><--..
 *	         CH1    CH2     Gap          CH1
 *           1..2ms 1..2ms  ~4..12ms
 */
void intr_init(void) {
    
    TIMSK |= _BV(TOIE0) | // enable timer0 overflow interrupt
			 _BV(OCIE1B); // enable timer1 output cmp interrupt

    //falling edge generates an interrupt
    MCUCR |= _BV(ISC01);

    // enable external interrupt on PB6 (REC_IN)
    GIMSK |= _BV(INT0);

	// enable interupts
	sei();
}

/*
 * TIMER0_OVF0_vect -
 *	timer 0 overflow interrupt handler
 *	if the timer overflows while measuring a channel we wait for a new start.
 *	if we are waiting for start, the gap is longer than 4msec.
 */
ISR(TIMER0_OVF0_vect) {
	switch (rc_state) {
		case RC_WAIT_FOR_START:
			rc_state = RC_GOT_LONG_GAP;
			break;
		case RC_GOT_LONG_GAP:
			/* long gap gets even longer */;
			break;
		case RC_TIME_CHANNEL:
			if (rc_channel < RC_CHANNEL_CNT) {
				rc_valid = 0;
				rc_state = RC_WAIT_FOR_START;
			} else {
				rc_state = RC_GOT_LONG_GAP;
			}
			break;
	}
}

/*
 * INT0_vect -
 *	external interrupt 0
 *	called on each falling edge of PB6 (REC_IN)
 *	because we have only a 8bit timer, we recognize
 *  the start of a frame, if there was an overflow, or 
 *	the start gap is at least RC_MIN_GAP long.
 *  A valid channel value must be between RC_MIN_VALUE and RC_MAX_VALUE.
 *  In all other cases (too short start, or invalid channel value)
 *	we go to the RC_WAIT_FOR_START state.
 *  A valid frame is signaled by setting rc_value to 1, if we have
 *	received RC_CHANNEL_CNT valid values and got a long enough start gap.
 */
ISR(INT0_vect) {
	unsigned char value = TCNT0; // store width of this channel

    TCNT0 = 0;		   // start measurement of next channel
	TIFR |= _BV(TOV0); // clear Overflow which occured while we are in 
					   // in this interrupt with higher prio.
	switch (rc_state) {
		case RC_WAIT_FOR_START:
		case RC_GOT_LONG_GAP:
			if (value > RC_MIN_GAP || rc_state==RC_GOT_LONG_GAP) {
				if (rc_channel>=RC_CHANNEL_CNT) rc_valid = 1;
				rc_channel = 0;
				rc_state   = RC_TIME_CHANNEL;
			}
			break;
		case RC_TIME_CHANNEL:
			if (value < RC_MIN_VALUE-RC_TOL_VALUE ||
				value > RC_MAX_VALUE+RC_TOL_VALUE) {
				rc_valid = 0;
				rc_state = RC_WAIT_FOR_START;
			} else if (rc_channel < RC_CHANNEL_CNT) {
				rc_value[rc_channel] = value;
			}
			rc_channel++;
			break;
	}
}

/*
 * TIMER1_CMPB_vect -
 *	timer1 controls the loudspeaker
 *	with one of two OCR settings: TONE_HIGH_CMP and TONE_LOW_CMP
 *	is runs with 4MHz/64/TONE_HIGH_CMP or 4Mhz/64/TONE_LOW_CMP.
 *	This interrupt divides this by TONE_HIGH_DIV or
 *	TONE_LOW_DIV depending on variable tone_high,
 * 	to get a fixed 10Hz clock.
 *
 *	This 10Hz is used for the timing of Blinking and other stuff.
 */
ISR(TIMER1_CMPB_vect) {
	if (tone_cnt) {
		tone_cnt--;
		return;
	}
	tone_cnt = tone_div;
	// the following code will run with 10Hz

	if (!rc_valid) return;

	// Winker Code
	if (wink_cnt) {
		wink_cnt--;
	} else {
		wink_cnt = WINK_FREQ;
		if (RC_IS_WINKR || RC_IS_WARN) {
			PORTB ^= _BV(WINKR_OUT);
		} else {
			PORTB &= ~_BV(WINKR_OUT);
		}
		if (RC_IS_WINKL || RC_IS_WARN) {
			PORTB ^= _BV(WINKL_OUT);
		} else {
			PORTB &= ~_BV(WINKL_OUT);
		}
	}

	// Blinker code
	if (blink_cnt) {
		blink_cnt--;
	} else {
		blink_cnt = BLINK_FREQ;
		if (RC_IS_BLINK) {
			unsigned char b;
			blink_state++;
			if (blink_state>6) blink_state=0;
			PORTA |= BLINKX_OUT; // all off
			switch (blink_state) {
				case 0:  b = (unsigned char)(~_BV(BLINK0_OUT)); break;
				case 1:  b = (unsigned char)(~_BV(BLINK1_OUT)); break;
				case 2:  b = (unsigned char)(~_BV(BLINK2_OUT)); break;
				case 3:  b = (unsigned char)(~_BV(BLINK3_OUT)); break;
				case 4:  b = (unsigned char)(~_BV(BLINK4_OUT)); break;
				case 5:  b = (unsigned char)(~_BV(BLINK5_OUT)); break;
				default: b = (unsigned char)(~_BV(BLINK6_OUT)); break;
			}
			PORTA = (PORTA | BLINKX_OUT) & b;
		} else {
			PORTA |= BLINKX_OUT; // all off
		}
	}

	// beeper code
	if (RC_IS_BEEP && !RC_IS_HORN) {
		switch (beep_cnt) {
			case 0: sound_set(1);
					beep_cnt++;
					break;
			case 1: 
			case 2: beep_cnt++;
					break;
			case 3: sound_set(0);
					beep_cnt++;
					break;
			case 4: beep_cnt=0;
					break;
		}
	} else {
		sound_set(0);
	}
}

/*
 * main -
 *	init everthing and go to endlees loop
 */
int main(void) {
	io_init();
	timer_init();
	intr_init();

	for(;;) {
		if (rc_valid) {
			PORTA |= _BV(RC_NOK_OUT);	// rc not ok LED off
			if (RC_IS_HORN) {
				tone_set(0);	// low frequence sound
				sound_set(1);	// enable loudspeaker
			} else {
				tone_set(1);	// high frequence sound for beep and timing
				if (!RC_IS_BEEP) {
					sound_set(0); // disable loudspeaker if not backward beeping
				}
			}
			if (RC_IS_LIGHT) {
				PORTB |= _BV(LIGHT_OUT);	// light LEDs on
			} else {
				PORTB &= ~_BV(LIGHT_OUT);	// light LEDs off
			}
		} else {
			PORTA &= ~_BV(RC_NOK_OUT);	// rc not ok LED on
			PORTA |= BLINKX_OUT;		// blink LEDs off
			PORTB &= ~_BV(LIGHT_OUT) &	// light LED off
					 ~_BV(WINKR_OUT) &  // right winker off
					 ~_BV(WINKL_OUT);   // left winker off
			sound_set(0);				// disable loudspeaker
		}
	}
}
