#include <Arduino.h>
#include "USBDesc.h"
#include "Platform.h"

int main(void)
{
	init();
	USBDevice.attach();
	setup();
	for (;;) {
		loop();
	}
	return 0;
}


// definition des fonction cbi sbi idem assembleur
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif 

// définitions
#define TEMPDMXRX 8  			// Longueur de trame mini en reception
#define IOUTDMX_FIN -4
#define IOUTDMX_WAIT -5			// Attente du PC en mode synchrone
#define IOUTDMX_IDLE -1
#define IOUTDMX_BRK -2
#define IOUTDMX_MAB -3
#define IINUSB_IDLE 0
#define IINUSB_ESC 1
#define IINUSB_DATA 2
#define IINUSB_PARAM 10

#define TX1PIN 1 // pin DMX serial 1
#define LEDPIN 13 // led interne
#define RX_TX_LED_TIME 12


// tableaux de données
byte tab_input_pc[514];  // venant du pc
byte tab_input_dmx[518]; // données venant de l'extérieur : les 512 premiers DMX; les 4 suivant 4x8 bp ;
byte tab_temp_dmx[TEMPDMXRX];
byte inbuffer[7];

// Variables d'état et compteurs
volatile int index_input_pc=0; 		// entrée USB
volatile int index_output_pc=0; 	// sortie USB
volatile int index_input_dmx=513; 	// entrée dmx
volatile int index_output_dmx=0; 	// sortie dmx
volatile int etat_input_pc=IINUSB_IDLE; // etat entrée USB
volatile int emissionPc=0; 		// flag : emission USB en attente
unsigned int blkl = 0, blkcpt=0, rxledcpt=0 ,txledcpt=0;	// Gestion des leds
unsigned int blinkon, blinkp, interbrk=0;			// idem

// Paramètres
byte brk_timer_end=75; 			// Duree Break (par 2µs) def : 150us
byte mab_timer_end=25; 			// Duree MAB (par 2µs) def : 50us
int nb_circuits=512;			// Nombre de circuits DMX emis
int dmx_frame_interval=40; 		// Intervale entre trames DMX emises
bool flag_merge1=1;
bool syncflag=false;

// ********************************** USB ***************************** //                      
// lancé par interruption USB sur reception d'un char <- Cette fonction nécessite une modif de la lib arduino
// l'interet est de ne pas utiliser le buffer de la classe Serial, trop petit (64o)
void CDC_accept()
{
	char c;
	byte *pb, *pe;

	
	switch (etat_input_pc) {
	
	case IINUSB_IDLE:
		USB_Recv(CDC_RX,&c,1);
		// on attend un 'esc' pour commencer
		if (c==27) { 
			etat_input_pc=IINUSB_ESC;
			RXLED1; rxledcpt=RX_TX_LED_TIME; 
			break;
		}
		break;
		
	case IINUSB_ESC:
		USB_Recv(CDC_RX,&c,1);
		// on attend 'D' pour recevoir
		if (c==68) {
			etat_input_pc=IINUSB_DATA;
			index_input_pc=1;
			tab_input_dmx[517]=68;
			index_output_pc=0; // on init l'index d'emmission vers le pc
			emissionPc=1; 
			break;
		}
		// Esc 'C' réinit
		if (c==67) {
			etat_input_pc=IINUSB_IDLE;

			pe=tab_input_pc+514;
			for(pb=tab_input_pc;pb<pe;pb++) *pb=0;

			pe=tab_input_dmx+518;
			for(pb=tab_input_dmx;pb<pe;pb++) *pb=0;

			tab_input_dmx[517]=67;
			index_output_pc=517; // on init l'index d'emmission vers le pc ( uniquement etat : 67 'C' )
			emissionPc=1;
			break;

		}
		// Esc 'B' parametrage
		if (c==66) {
			etat_input_pc=IINUSB_PARAM;
			index_input_pc=0;
			break;
                }

		// Esc 'A' probe 
		if (c==65) {
			etat_input_pc=IINUSB_IDLE;
			tab_input_dmx[517]=65;
			index_output_pc=517; // on init l'index d'emmission vers le pc ( uniquement etat : 65 'A' )
			emissionPc=1;
			break;
		}

		// si aucune commande n'est trouvée (toujours à 1) on repart à 0
		etat_input_pc=IINUSB_IDLE;
		break;
		
	case IINUSB_DATA: // reception trame pc
		// on rempli le tableau
		index_input_pc+=USB_Recv(CDC_RX,tab_input_pc+index_input_pc,513-index_input_pc);
		if(index_input_pc<513) return;
		etat_input_pc=0; 
		break;
		
	case IINUSB_PARAM: 
		index_input_pc+=USB_Recv(CDC_RX,inbuffer+index_input_pc,5-index_input_pc);
		if(index_input_pc<5) return;

                nb_circuits= ((int)inbuffer[0])*2 + 2;
		brk_timer_end = (inbuffer[1]/2);
		mab_timer_end = (inbuffer[2]/2);
		flag_merge1 = inbuffer[3] & 1;
		syncflag = inbuffer[3] & 2;
		dmx_frame_interval = inbuffer[4];

		etat_input_pc=0;

		// on a tout recu, on reponds
		tab_input_dmx[517]=66;
		index_output_pc=517; // on init l'index d'emmission vers le pc ( uniquement etat : 66 'B' )
		emissionPc=1;

		break;
	default:
		// on fait rien
		break; 
	}

}

void ecritUSB()
{
	if(index_output_pc > 516){ 	// Emission d'un seul caractere (code etat)
		USB_Send(CDC_TX,tab_input_dmx+index_output_pc,1);
		emissionPc=0;
		return;
	}
	// Emission du tableau complet
	index_output_pc+= USB_Send(CDC_TX,tab_input_dmx+index_output_pc, 517-index_output_pc);
	if (index_output_pc>516) {
		if(index_output_dmx==IOUTDMX_WAIT)
			index_output_dmx=IOUTDMX_IDLE;
		emissionPc=0;
	}
}

// ********************************** RECEPTION DMX ********************************* //                      


// vecteur d'intéruption pour reception dmx
// sur reception d'un caractere / erreur
ISR(USART1_RX_vect)
{
	char c,r;
	r = UCSR1A;
	c = UDR1;

	if (r & (1<<FE1))  {index_input_dmx=0;return; }     		// Detection du BRK

	if ( index_input_dmx==0 && c!=0 ) {index_input_dmx=513; }	// Start code => doit etre 0

	if ( index_input_dmx<TEMPDMXRX) {  				// Les premiers codes sont stockés dans un tableau temporaire
		tab_temp_dmx[index_input_dmx]=c;
		index_input_dmx++;  
		return;
	}

	if ( index_input_dmx==TEMPDMXRX) {		// La tramme a depassé le minimum => on copie ce qu'on a déja recu
		for(int i=0;i<TEMPDMXRX;i++)
			tab_input_dmx[i]=tab_temp_dmx[i];
		RXLED1; rxledcpt=RX_TX_LED_TIME;
		tab_input_dmx[513]=0; 
	}

	if ( index_input_dmx<=512 ) 
	{     
		tab_input_dmx[index_input_dmx]=c;
		index_input_dmx++;
	}
}  

// ********************************** EMISSION DMX ********************************* //                      


// vecteur d'intéruption pour transmission DMX sur serial 1
// vecteur : USART 1 transmission buffer vide
// => Permet d'attendre que tout soit parti avant de déclancher le break
ISR(USART1_TX_vect)
{
	// si l'USART 1 est vide => break
	if(index_output_dmx==IOUTDMX_FIN) {
		if(syncflag)
			index_output_dmx=IOUTDMX_WAIT;
		else 
			index_output_dmx=IOUTDMX_IDLE;
	}
}

// vecteur : USART 1 transmission registre vide
ISR(USART1_UDRE_vect)
{
	if (flag_merge1==1) {
		UDR1 = max(tab_input_pc[index_output_dmx],tab_input_dmx[index_output_dmx]);
	}
	else {
		UDR1 = tab_input_pc[index_output_dmx];
	}
	index_output_dmx++;

	// si 512 transmits => mise en attente de fin de transmission
	// desactivation de l'interruption sur le registre
	if (index_output_dmx>nb_circuits) {
		index_output_dmx=IOUTDMX_FIN;
		cbi(UCSR1B, UDRIE1);
	}
}

// Timer 4 : gestion des temps de break et mab
ISR(TIMER4_COMPA_vect) {
	if (index_output_dmx==IOUTDMX_BRK ) {  // en cours de break
		index_output_dmx=IOUTDMX_MAB;
		digitalWrite(TX1PIN, HIGH); // on met la broche à 1
		// on relance le timer sur le temps du mab
		OCR4A = mab_timer_end ;
		TCNT4 = 0 ; // RAZ compteur timer
		TIFR4 = 0 ; //Clear Flags timer 
		return;
	}
	if (index_output_dmx==IOUTDMX_MAB ) {  // en cours de mab
		TXLED1; txledcpt=RX_TX_LED_TIME;
		sbi(UCSR1B, TXEN1);  // on reactive la transmission USART
		index_output_dmx=0; // on se prépare à émmettre à partir du stat code ( 513 octets )
		sbi(UCSR1B, UDRIE1); // on réactive l'intéruption du registre

		// Arret du timer
		TIMSK4= 0 ; //desactivation intéruption 
		TCCR4B = 0;  // arret du timer
		TIFR4 = 0 ; //Clear Flags timer 
	}
}

// ********************************** INIT ********************************* //                      


void setup() {
	// Le startcode n'est jamais recu. On l'initialise ici
	tab_input_pc[0]=0; 
   
	// init pin led interne en sortie
	pinMode(LEDPIN, OUTPUT); 
  
	// init USART
	// baudrate à 250k
	UCSR1A = 1 << U2X1;
	UBRR1H=0;
	UBRR1L = 7;
 
	// 2 bit de stop; pas de parité; 8 bits de données
	UCSR1C = 14;

	// activation reception et intéruptions serial 1
	sbi(UCSR1B, RXEN1);  //Reception
	cbi(UCSR1B, TXEN1);  //Pas de transmission avant 1er Break
	sbi(UCSR1B, RXCIE1); //Interruption sur reception
	sbi(UCSR1B, TXCIE1); //Interruption pour fin de transmission

	// init timer 4 (break) 
	TIMSK4 = 0;
	TCCR4C = 0;
	TCCR4B = 0;
	TCCR4A = 0; //  00000000
	TCNT4 = 0; // compteur 
	TC4H=0;

	sei();
 
	// préparation du premier break
	index_output_dmx=IOUTDMX_IDLE;
}

// ********************************** BOUCLE ********************************* //                      

void loop() {
 	// en mode synchrone, dmx_frame_interval est un maximum
	if(index_output_dmx==IOUTDMX_WAIT && interbrk >=dmx_frame_interval)
			index_output_dmx=IOUTDMX_IDLE;

	// Lancement du Break : après fin de transmisson et ecoulement de "dmx_frame_interval" ms depuis le dernier break
	//  ou immediatement au passage en IDLE si mode synchrone
	if (index_output_dmx==IOUTDMX_IDLE && (interbrk >= dmx_frame_interval||syncflag)) {
		index_output_dmx=IOUTDMX_BRK;
		tab_input_dmx[514]= (byte)interbrk ;
		interbrk=0;
		pinMode(TX1PIN, OUTPUT); // on met la broche en mode sortie
		cbi(UCSR1B, TXEN1);  //on stop la transmission 
		digitalWrite(TX1PIN, LOW); // on met la broche à 0
		OCR4A = brk_timer_end ; // val 120 µs
		TCNT4 = 0 ; // RAZ compteur timer
		TIFR4 = 0 ; //Clear Flags timer 
		TIMSK4 = 64 ; //activation intéruption A &B 
		TCCR4B = 6; // 00000110 division par 32 de l'horloge base => 2µs
	}

	// octets d'état. Pour le moment, tous à 0
	//tab_input_dmx[513]= 0;
	//tab_input_dmx[514]= 0;
	//tab_input_dmx[515]= 0;
	//tab_input_dmx[516]= 0; 

	if(_usbLineInfo.lineState) { // Si port serie USB ouvert
		blinkp=100, blinkon=70; // la led clignote plus vite
		if(emissionPc!=0)
		{
			// en mode synchrone emission uniquement entre les transmissions DMX
			if (!syncflag || index_output_dmx==IOUTDMX_WAIT || index_output_dmx==IOUTDMX_IDLE)
				ecritUSB();
		}
	} else {
		blinkon = 3500, blinkp=4000; 
		// dans le doute, on force l'etat d'entree
		etat_input_pc=IINUSB_IDLE;
	}
 
	int m = millis();
	if (m-blkl) {  // Toutes les ms
		int ival = (m-blkl) ; 
	        interbrk+=ival ; 
		blkcpt+=ival ; 
		if(tab_input_dmx[513]<200) tab_input_dmx[513]+=ival ;  // "age" de la trame recue

		blkl = m;

	 	if(rxledcpt) rxledcpt--;
		else RXLED0; 
	 	if(txledcpt) txledcpt--;
		else TXLED0; 
	 	if(blkcpt>=blinkon) { digitalWrite(LEDPIN, HIGH); }
		if(blkcpt>=blinkp){ digitalWrite(LEDPIN, LOW); blkcpt=0; }
	}
}


