#include <avr/io.h>		//I-O registers
#include <avr/interrupt.h>
#include <util/delay.h>		//_delay_ms

#define NB_TICK 104		//1563
#define CPU_FREQ 16000000L 	//Frequence du CPU
#define QUANTUM_ms 10

#define SAVE_CONTEXT()  \
asm volatile ( \
"push	r0				\n\t" \
"in		r0, __SREG__		\n\t" \
"cli					\n\t" \
"push	r0				\n\t" \
"push	r1				\n\t" \
"clr	r1				\n\t" \
"push	r2				\n\t" \
"push	r3				\n\t" \
"push	r4				\n\t" \
"push	r5				\n\t" \
"push	r6				\n\t" \
"push	r7				\n\t" \
"push	r8				\n\t" \
"push	r9				\n\t" \
"push	r10				\n\t" \
"push	r11				\n\t" \
"push	r12				\n\t" \
"push	r13				\n\t" \
"push	r14				\n\t" \
"push	r15				\n\t" \
"push	r16				\n\t" \
"push	r17				\n\t" \
"push	r18				\n\t" \
"push	r19				\n\t" \
"push	r20				\n\t" \
"push	r21				\n\t" \
"push	r22				\n\t" \
"push	r23				\n\t" \
"push	r24				\n\t" \
"push	r25				\n\t" \
"push	r26				\n\t" \
"push	r27				\n\t" \
"push	r28				\n\t" \
"push	r29				\n\t" \
"push	r30				\n\t" \
"push	r31				\n\t" \
);

#define RESTORE_CONTEXT() \
asm volatile ( \
"pop	r31				\n\t" \
"pop	r30				\n\t" \
"pop	r29				\n\t" \
"pop	r28				\n\t" \
"pop	r27				\n\t" \
"pop	r26				\n\t" \
"pop	r25				\n\t" \
"pop	r24				\n\t" \
"pop	r23				\n\t" \
"pop	r22				\n\t" \
"pop	r21				\n\t" \
"pop	r20				\n\t" \
"pop	r19				\n\t" \
"pop	r18				\n\t" \
"pop	r17				\n\t" \
"pop	r16				\n\t" \
"pop	r15				\n\t" \
"pop	r14				\n\t" \
"pop	r13				\n\t" \
"pop	r12				\n\t" \
"pop	r11				\n\t" \
"pop	r10				\n\t" \
"pop	r9				\n\t" \
"pop	r8				\n\t" \
"pop	r7				\n\t" \
"pop	r6				\n\t" \
"pop	r5				\n\t" \
"pop	r4				\n\t" \
"pop	r3				\n\t" \
"pop	r2				\n\t" \
"pop	r1				\n\t" \
"pop	r0				\n\t" \
"out 		__SREG__, r0		\n\t" \
"pop	r0				\n\t" \
);

/*
protocole de communication choisi
16u2 vers 328p (led) -> 1 bit = état d'une led

328p vers 16u2 (boutons + joysticks) -> enfonction du bit 7 :
à 0 : boutons puis un bit = état du bouton
à 1 : joysticks -> bit 6 = direction x ou y puis le reste, la valeur sur 6 bits
*/

struct task{
	uint16_t sp_vise;
	uint8_t state;
};

uint8_t cpt = 0;
uint8_t premier_lancement = 0;
struct task lecture_boutons = {0x300, 0};
struct task lecture_joystick = {0x0500, 0};
struct task affiche_led = {0x0700, 0};










//debug
void init_debug(void)//permet de detecter un reboot
{
	DDRB |= 0x1F;
	PORTB |= 0xFF;
	_delay_ms(300);
	PORTB &= 0x00;
}










//gestion de la liaison serie
void init_serial(int speed)
{
	UBRR0 = CPU_FREQ/(((unsigned long int)speed)<<4)-1;//Set baud rate
	UCSR0B = (1<<TXEN0 | 1<<RXEN0);//Enable transmitter & receiver
	UCSR0C = (1<<UCSZ01 | 1<<UCSZ00);//Set 8 bits character and 1 stop bit
	UCSR0A &= ~(1 << U2X0);//Set off UART baud doubler
}

void send_serial(unsigned char c)
{
	loop_until_bit_is_set(UCSR0A, UDRE0);
	UDR0 = c;
}

unsigned char get_serial(void)
{
	loop_until_bit_is_set(UCSR0A, RXC0);
	return UDR0;
}










//gestion du convertisseur analogique vers numerique
void init_ADC(void)
{
	ADCSRA |= ((1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0));//Clock prescaler at 128
	ADMUX |= (1<<REFS0);
	ADMUX &= ~(1<<REFS1);//Avcc(+5v) as voltage reference
	ADMUX &= ~(1<<ADLAR);
	ADCSRB &= ~((1<<ADTS2)|(1<<ADTS1)|(1<<ADTS0));//ADC in free-running mode
	//ADCSRA |= (1<<ADATE);//Signal source, in this case is the free-running
	ADCSRA |= (1<<ADEN);//Power up the ADC
	ADCSRA |= (1<<ADSC);//Start converting
}

uint16_t ADC_read(uint8_t adcx)
{
	ADMUX |= adcx;//ADC selection
	ADCSRA |= _BV(ADSC);//ADC start conversion
	while ( (ADCSRA & _BV(ADSC)) );//Wait until the conversion is finished
	ADMUX &= ~adcx;//ADC deselection
	return ADC;
}










//gestion du timer
void init_timer()
{
	TCCR1B |= _BV(WGM12); // CTC mode with value in OCR1A 
	TCCR1B |= _BV(CS12);  // CS12 = 1; CS11 = 1; CS10 =1 => CLK/1024 prescaler
	TCCR1B |= _BV(CS10);
	OCR1A   = NB_TICK;
	TIMSK1 |= _BV(OCIE1A);
}










//gestion de l'initialisation des differentes taches
void init_tasks(void)
{
	DDRD |= 0x00;
	PORTD |= 0xFF;
	DDRB |= 0b00111111;
}










//gestion de l'execution des differentes taches
void task_lecture_boutons(void)
{
  uint8_t tmp = 0x00;
  uint8_t tmp2;
	while(1){
	  tmp2 = 0x00;
	  if((PIND&(1<<2)) == 0) tmp2 |= 0b00000001;
	  if((PIND&(1<<3)) == 0) tmp2 |= 0b00000010;
	  if((PIND&(1<<4)) == 0) tmp2 |= 0b00000100;
	  if((PIND&(1<<5)) == 0) tmp2 |= 0b00001000;
	  if((PIND&(1<<6)) == 0) tmp2 |= 0b00010000;
	  if(tmp2 != tmp) { send_serial(tmp2); tmp = tmp2; }
	_delay_ms(QUANTUM_ms);
	}
}

void task_lecture_joystick(void)
{
	uint8_t tmp = ADC_read(0)>>4;//code sur 6 bits (2 bits de descriptions necessaires)
	uint8_t tmp2 = ADC_read(1)>>4;
	uint8_t test = 0;
	uint8_t tmp3;
	uint8_t tmp4;
	while(1){
	tmp3 = ADC_read(0)>>4;
	tmp4 = ADC_read(1)>>4;
	if(test == 0 && tmp3 != tmp)
		{
		  send_serial(tmp3 | 0b10000000);
		tmp = tmp3;
		}
	else if(test == 1 && tmp4 != tmp2)
		{
		  send_serial(tmp4 | 0b11000000);
		tmp2 = tmp4;
		}
	if(test == 0) test = 1; else test = 0;
	_delay_ms(QUANTUM_ms);
	}
}

void task_affiche_led(void)
{
	while(1){
	PORTB = get_serial();
	_delay_ms(QUANTUM_ms);
	}
}










//gestion du contexte
ISR(TIMER1_COMPA_vect)
{
	if(premier_lancement==0)
	{
		premier_lancement+= 1;
		sei();
		SP = affiche_led.sp_vise;
		task_affiche_led();
	}
	else if(premier_lancement==1)
	{
		SAVE_CONTEXT();
		affiche_led.sp_vise = SP;
		premier_lancement+=1;
		sei();
		SP = lecture_joystick.sp_vise;
		task_lecture_joystick();
	}
	else if(premier_lancement==2)
	{
		SAVE_CONTEXT();
		lecture_joystick.sp_vise = SP;
		premier_lancement+=1;
		sei();
        	SP = lecture_boutons.sp_vise;
		task_lecture_boutons();
	}


	else
	{
		if(cpt==0)
		{
			SAVE_CONTEXT();
			lecture_boutons.sp_vise = SP;
			SP = affiche_led.sp_vise;
			RESTORE_CONTEXT();
			cpt++;
		}
		else if(cpt==1)
		{
			SAVE_CONTEXT();
			affiche_led.sp_vise = SP;
			SP = lecture_joystick.sp_vise;
			RESTORE_CONTEXT();
			cpt++;
		}
		else if(cpt==2)
		{
			SAVE_CONTEXT();
			lecture_joystick.sp_vise = SP;
			SP = lecture_boutons.sp_vise;
			RESTORE_CONTEXT();
			cpt = 0;
		}
	}
	sei();
}










int main(void)
{
	init_debug();//allume toutes les leds au reset
	init_serial(9600);
	init_ADC();
	init_timer();

	init_tasks();
	sei();

	while(1)
	{}
	return 0;
}
