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

int main(void)
{
	init();

#if defined(USBCON)
	USBDevice.attach();
#endif
	
	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 

#define TEMPDMXRX 8


// déclaration des 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[6];

// reception
volatile int index_input_pc=0; // entrée serial 0
volatile int index_output_pc=0; // sortie serial 0
volatile int index_input_dmx=513; // entrée dmx
volatile int index_output_dmx=0; //sortie dmx

#define IOUTDMX_FIN -4
#define IOUTDMX_IDLE -1
#define IOUTDMX_BRK -2
#define IOUTDMX_MAB -3

volatile int etat_input_pc=0; // 0 raz ; 1 esc reçu ; 2 D reçu : pret à recevoir ; 3 prèt à emmetre

int tx1pin = 1; // pin DMX serial 1

int ledPin = 13; // led interne
unsigned int blkl = 0, blkcpt=0, rxledcpt=0 ,txledcpt=0;
#define RX_TX_LED_TIME 3

byte brk_timer_end=75; // def : 150us
byte mab_timer_end=25; //def : 50us
int nb_circuits=512;
int flag_merge1=1;
volatile bool emissionPc=0; 

// Fonctions 

void ecritUSB();
void CDC_accept();

// 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 0:
		USB_Recv(CDC_RX,&c,1);
		// on attend un 'esc' pour commencer
		if (c==27) { etat_input_pc=1; break;}
		break;
		
	case 1:
		USB_Recv(CDC_RX,&c,1);
		// on attend 'D' pour recevoir
		if (c==68) {
			etat_input_pc=2;
			index_input_pc=1;
			tab_input_dmx[517]=68;
			index_output_pc=1; // on init l'index d'emmission vers le pc ( le 0 n'est pas emit => start code)
			emissionPc=1; // activation de l'intéruption registre emmission 
		}
		// Esc 'C' réinit
		if (c==67) {
			etat_input_pc=0;

			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=10;index_input_pc=0;
                }

		// Esc 'A' probe 
		if (c==65) {etat_input_pc=0;
			tab_input_dmx[517]=65;
			index_output_pc=517; // on init l'index d'emmission vers le pc ( uniquement etat : 65 'A' )
			emissionPc=1;
		}
		
		// si aucune commande n'est trouvée (toujours à 1) on repart à 0
		if (etat_input_pc==1) {etat_input_pc=0;}
		break;
		
	case 2: // reception trame pc
		// on rempli le tableau
		index_input_pc+=USB_Recv(CDC_RX,tab_input_pc+index_input_pc,513-index_input_pc);
		// si on arrive à 512
		if (index_input_pc > 512) {
		  etat_input_pc=0;
 		}
		break;
		
	case 10: 
		index_input_pc+=USB_Recv(CDC_RX,inbuffer+index_input_pc,4-index_input_pc);
		if(index_input_pc<4) 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;

		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()
{
  TXLED1; txledcpt=RX_TX_LED_TIME;
  if(index_output_pc > 516){
    USB_Send(CDC_TX,tab_input_dmx+index_output_pc,1);
    emissionPc=0;
    return;
  }
  index_output_pc+= USB_Send(CDC_TX,tab_input_dmx+index_output_pc, 517-index_output_pc);
  if (index_output_pc>516) {
    emissionPc=0;
  }
}


// vecteur d'intéruption pour reception dmx
ISR(USART1_RX_vect)
{
  char c,r;
  r = UCSR1A;
  c = UDR1;
  if (r & (1<<FE1))  {index_input_dmx=0;return; }    
  if ( index_input_dmx==0 && c!=0 ) {index_input_dmx=513;  }
  if ( index_input_dmx<TEMPDMXRX) {  
      tab_temp_dmx[index_input_dmx]=c;
      index_input_dmx++;  
      return;
  }
  if ( index_input_dmx==TEMPDMXRX) {
     for(int i=0;i<TEMPDMXRX;i++)
        tab_input_dmx[i]=tab_temp_dmx[i];
     RXLED1; rxledcpt=RX_TX_LED_TIME;
  }
  if ( index_input_dmx<=512 ) 
  {
     
      tab_input_dmx[index_input_dmx]=c;
      index_input_dmx++;
  }
}  



// vecteur d'intéruption pour transmission DMX sur serial 1
// vecteur : USART 1 transmission buffer vide
ISR(USART1_TX_vect)
{
  // si l'USART 1 est vide => break
  if(index_output_dmx==IOUTDMX_FIN){
    index_output_dmx=IOUTDMX_IDLE;
  }  
}
// vecteur : USART 1 transmission registre vide
ISR(USART1_UDRE_vect)
{
  if (index_output_dmx>=0) { //si index >=0 caratère suivant
    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'intéruption sur le registre
  if (index_output_dmx>nb_circuits) {index_output_dmx=IOUTDMX_FIN;cbi(UCSR1B, UDRIE1);}
  
}

ISR(TIMER4_COMPA_vect) {
  if (index_output_dmx==IOUTDMX_BRK ) {
    digitalWrite(tx1pin, HIGH); // on met la broche à 1
    
    index_output_dmx=IOUTDMX_MAB;
    OCR4A = mab_timer_end ;
    TCNT4 = 0 ;// RAZ compteur timer
    TIFR4 = 0 ; //Clear Flags timer 
    return; 
  }
  if (index_output_dmx==IOUTDMX_MAB ) {
    sbi(UCSR1B, TXEN1);  // on redémarre la transmission
    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  
    TIMSK4= 0 ; //desactivation intéruption A &B
    TCCR4B = 0;
  }
}


void setup() {
 // initialisation du stat code à 0
 etat_input_pc=0;
 tab_input_pc[0]=0; 
   
 // init pin led interne en sortie
 pinMode(ledPin, OUTPUT); 
 
 // initialisation à 250k de serial 1
   // baudrate
   UCSR1A = 1 << U2X1;
   UBRR1H=0;
   UBRR1L = 7;
 
   // 2 bit de stop; pas de parité; 8 bits de données
   UCSR1C = 14;
 
 
 
   // activation transmission et intéruption serial 1
  sbi(UCSR1B, RXEN1);  //Reception
  sbi(UCSR1B, TXEN1);  //Transmission
  sbi(UCSR1B, RXCIE1); //Interruption sur reception
  sbi(UCSR1B, TXCIE1); //Interruption pour fin de transmission

 // init timer 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;
 
}

int blinkon = 350, blinkp=400;
void loop() {
 // lancement du break 


 if (index_output_dmx==IOUTDMX_IDLE ) {
  index_output_dmx=IOUTDMX_BRK;
  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=10, blinkon=6; // led clignote plus vite
   if(emissionPc!=0) {
      ecritUSB();
   }
 } else {
   blinkon = 350, blinkp=400; 
   etat_input_pc=0;
 }
 
 int m = millis()>>3;
 if (m-blkl) { 
	blkcpt++; 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; }
 }
}



