/* 
**********************************************************************
***           DOCDUINO - INTERPOLATEUR POUR HOTCUT 1.60            ***
***              Philippe DESLOGES  11-2014 / 01-2017              ***
***           Compilation sous IDE version 1.6.13 (2016)           ***
**********************************************************************
   Gestion de moteurs pas à pas sur 4 axes basée sur ARDUINO UNO R3
            1 sortie ENABLE, 1 sortie RELAIS, 1 entée ATU
    Commande directe des drivers ou d'une interface déjà présente 
            ( niveau des sorties 5V en logique positive )
----------------------------------------------------------------------
FONCTIONS PRINCIPALES :
- Interpolation linéaire de coordonnées au format G-code (personnalisé).
- Gestion du maintien des couples moteurs.
- Gestion de la chauffe du fil.
- Arrêt d'urgence immédiat ou progressif en avance manuelle.
- Arrêt d'urgence autorisé durant la préchauffe initiale.
- Accélérations et décélérations en avance manuelle.
- Usinage à vitesse constante (sans accélération).
- Pause et reprise d'usinage.
- Simulation d'usinage pour validation du fichier G-code.
- Retour au point d'origine dans les deux modes d'avance.
CORRECTIONS D'USINAGE :
- Etalonnage systématique de la vitesse d'avance.
- Rattrapage automatique des pas résiduels.
AUTRES FONCTIONS :
- Remise aux valeurs par défaut de l'eeprom.
- Changement de vitesse de transmission série.
----------------------------------------------------------------------
*/

#include <math.h>                // sin, cos
#include <stdlib.h>              // atof
#include <EEPROM.h>              // eeprom

const byte ver(160), rev(0);     // version et révision

// sorties
const int hotPin(6);             // sortie chauffe (Port D)
const int enaPin(7);             // sortie enable  (Port D)

// atu
const int atuPin(3);             // entrée atu     (Port D)
bool atu(false);

// valeurs eeprom
word nPasMM; 
byte dutyCycle, burnTime, penteAccel;
float Vmin;
byte indexBr;                    // index baudrate
long baudRate;                   // baudrate
int waitAllReceipt;              // délai de réception  

// commandes
bool enableStart, enableStop, wireStart, wireStop, simuMode, stopMode, homeMode, infoMode;
bool progMode, manuMode, usinMode, burnMode, redoMode, backMode, testMode;

// vitesses et accélérations
word  tempo1, tempo2, Tcut1, Tcut2, Tmin1, Tmin2;
float Vcut, incmt1, incmt2, increment1, increment2;
unsigned long periode, numPas, numPasOld, nPas, nPasAccel;

// données série
byte messageOut[16], messageIn[48];
const byte sizeMessageIn(sizeof(messageIn));
const byte sizeMessageOut(sizeof(messageOut));

// g-code
char x1[9], y1[9], x2[9], y2[9];
long X1, Y1, X2, Y2;

// axes
byte byteOutPls, byteOutDir;
long posX1, posY1, posX2, posY2, holdPosX1, holdPosY1, holdPosX2, holdPosY2;
long sourceX1, sourceY1, sourceX2, sourceY2, targetX1, targetY1, targetX2, targetY2;
float fracX1, fracY1, fracX2, fracY2;

/************************  SETUP  ***********************/

void setup() {
  // effacement de l'eeprom
  // eepromInitConf();
  
  // rétablissement les valeurs eeprom par défaut (ATU + RESET)
  // (le circuit ATU doit être connecté, sinon reset perpétuel)
  if (!digitalRead(atuPin)) {
    eepromDefault();
    eepromWriteConf();
  }

  // initialisation
  eepromReadConf();
  razOrigine();
  Serial.begin(baudRate);
  
  // lecture eeprom via moniteur série + déblocage logiciel au reset
  eepromViewConf();

  // sens et mise à niveau des sorties vers cnc
  DDRB  = B00001111;
  DDRC  = B00001111;
  PORTB = B00000000;
  PORTC = B00000000;
  pinMode(enaPin, OUTPUT);
  pinMode(hotPin, OUTPUT);
  digitalWrite(enaPin, LOW);
  digitalWrite(hotPin, LOW);
}

/*******************  BOUCLE PRINCIPALE  ******************/

void loop() {
  // écoute du message en provenance du port série
  if (Serial.available() >= sizeMessageIn) {
    Serial.readBytes((char*)messageIn, sizeMessageIn);
    
    // lecture du message : commandes sur octet 1
    enableStart = messageIn[0] & 1;    // bit 0 -> activation du couple (0=off, 1=on)
    enableStop  = messageIn[0] & 2;    // bit 1 -> maintien du couple (0=off, 1=on)
    wireStart   = messageIn[0] & 4;    // bit 2 -> marche chauffe (0=off, 1=on)
    wireStop    = messageIn[0] & 8;    // bit 3 -> arrêt chauffe (0=off, 1=on)
    simuMode    = messageIn[0] & 16;   // bit 4 -> mode simulation (0=off, 1=on)
    stopMode    = messageIn[0] & 32;   // bit 5 -> mode d'arrêt d'urgence (immédiat=0 ou progressif=1)
    homeMode    = messageIn[0] & 64;   // bit 6 -> mode remise à l'origine (0=off, 1=on)
    infoMode    = messageIn[0] & 128;  // bit 7 -> mode infos paramètres (0=off, 1=on)
    
    // lecture du message : commandes sur octet 2
    progMode    = messageIn[1] & 1;    // bit 0 -> mode programmation (0=off, 1=on)
    manuMode    = messageIn[1] & 2;    // bit 1 -> mode manuel (0=off, 1=on)
    usinMode    = messageIn[1] & 4;    // bit 2 -> mode usinage (0=off, 1=on)
    burnMode    = messageIn[1] & 8;    // bit 3 -> mode préchauffe (0=off, 1=on)
    redoMode    = messageIn[1] & 16;   // bit 4 -> mode reprise usinage (0=off, 1=on)
    backMode    = messageIn[1] & 32;   // bit 5 -> mode retour à l'origine (0=off, 1=on)
    testMode    = messageIn[1] & 64;   // bit 6 -> mode test de connexion (0=off, 1=on)

    // lecture du message : consigne g-code
    // sont sous-entendus : G21 (unité mm) et G90 (coordonnées absolues)
    if (usinMode || manuMode) {
      Serial.write(B00000000);                // appel de la consigne g-code suivante (rechargement du buffer)
      if ((char)messageIn[2] == 'G') {        // HEADER G
        for (int i=0; i<9; i++) {             // lecture des coordonnées :
          x1[i] = (char)messageIn[i+6];       // ..G0 X-0000.000 Y-0000.000 A-0000.000 B-0000.000
          y1[i] = (char)messageIn[i+17];
          x2[i] = (char)messageIn[i+28];
          y2[i] = (char)messageIn[i+39];
        }
        X1 = atof(x1) * nPasMM;               // conversion des coordonnées en pas
        Y1 = atof(y1) * nPasMM;
        X2 = atof(x2) * nPasMM;
        Y2 = atof(y2) * nPasMM;
        if ((char)messageIn[3] == '0') {      // HEADER G0
          targetX1 =  0;                      // coordonnées cible à zéro
          targetY1 =  0;
          targetX2 =  0;
          targetY2 =  0;
          sourceX1 = X1;                      // coordonnées de départ
          sourceY1 = Y1;
          sourceX2 = X2;
          sourceY2 = Y2;
          fracX1   = .0;                      // fractions de pas à zéro
          fracY1   = .0;
          fracX2   = .0;
          fracY2   = .0;
          holdPosX1 = posX1;                  // sauvegarde des positions
          holdPosY1 = posY1;
          holdPosX2 = posX2;
          holdPosY2 = posY2;
          return;
        } else {                              // HEADER G1
          targetX1 = X1 - sourceX1;           // coordonnées cible
          targetY1 = Y1 - sourceY1;
          targetX2 = X2 - sourceX2;
          targetY2 = Y2 - sourceY2;
          sourceX1 = X1;                      // coordonnées source pour consigne suivante
          sourceY1 = Y1;
          sourceX2 = X2;
          sourceY2 = Y2;
        }
      } else
      if ((char)messageIn[2] == 'F') {        // HEADER F
        char vitesse[5];                      // lecture de la vitesse en mm/mn (format xxxxx)
        for (int i=0; i<5; i++) {
          vitesse[i] = (char)messageIn[i+3];
        }
        Vcut = (float)atoi(vitesse) / 60;     // vitesse convertie en mm/s
        periode = 1.0 / nPasMM / Vcut * 1e6;  // calcul de la période théorique en µs
        periode -= calibration();             // prise en compte de la durée de traitement
        calcSpeeds();                         // application de la vitesse corrigée
        calcNoAccel();                        // aucune accélération par défaut 
        return;
      } else
      if ((char)messageIn[2] == 'M') {        // HEADER M2
        if (usinMode && !simuMode) {          // rattrapage des pas résiduels en fin d'usinage
          byte dMax = nPasMM / 40;            // préserve les formes volontairement ouvertes
          if (abs(holdPosX1 - posX1) < dMax && abs(holdPosY1 - posY1) < dMax && 
              abs(holdPosX2 - posX2) < dMax && abs(holdPosY2 - posY2) < dMax) {
              purgeSteps(holdPosX1, holdPosY1, holdPosX2, holdPosY2, !simuMode);
          }
        }
        if (backMode && !atu) {               // rattrapage des pas résiduels en retour au zéro absolu
          purgeSteps(0, 0, 0, 0, !simuMode);
        }
        endLoop();                            // fin d'usinage 
        return;        
      }
      
    // lecture du message : mode programmation eeprom
    } else if (progMode) {
      nPasMM     = ((word)messageIn[2] << 8) + (word)messageIn[3];                // pas par millimètre
      dutyCycle  = messageIn[4];                                                  // rapport cyclique en pourcentage
      Vmin       = (float)messageIn[5] + ((float)messageIn[6] / 100);             // vitesse de seuil en mm/s
      penteAccel = messageIn[7];                                                  // pente d'accélération en degrés
      burnTime   = messageIn[8];                                                  // duree de préchauffage en secondes
      if (messageIn[9] != indexBr)                                                // baudrate
        if ((messageIn[9] >= 0) && (messageIn[9] < 6)) {
          // changement de baudrate si différent
          indexBr = messageIn[9];
          baudRate = getBaudRate(indexBr);
          Serial.flush();
          Serial.end();
          Serial.begin(baudRate);
        }
      eepromWriteConf();
      return;
    }

    // -----  TRAITEMENT DES COMMANDES PAR PRIORITES  -----
    
    if (usinMode) {             // MODE USINAGE
      calcNpasMax();
      calcSensAxes();
      interpolation(true); 
    } else
    if (manuMode) {             // MODE MANUEL
      calcNpasMax();
      calcSensAxes();
      calcAccel();              // accélérations
      beginLoop();              // force l'activation des moteur  
      numPasOld = 0;            // sans reprise d'usinage
      interpolation(true); 
    } else     
    if (burnMode) {             // MODE PRECHAUFFE
      beginLoop();   
      burn();
      if (atu && !redoMode) {   // si atu durant la préchauffe initiale
        endLoop();              
      } else {
        if (redoMode) {         // si reprise d'usinage
          burnMode = false;
          usinMode = true;
          interpolation(true); 
        } else {                // si début d'usinage
          atu = false;
          numPasOld = 0;
        }
      }
    } else
    if (homeMode) {             // MODE DEFINITION ORIGINE
      razOrigine();     
    } else     
    if (infoMode) {             // MODE INFOS PARAMETRES
      beginLoop();   
      eepromSendConf();
    } else
    if (testMode) {             // MODE TEST DE CONNEXION AU DEMARRAGE DU LOGICIEL
      Serial.write(B10101010);   
      beginLoop();
    } else {
      endLoop();                // AUTRE CAS : SORTIE DU LOGICIEL
    }
  }
}

/*******************  SEQUENCES DE DEBUT ET DE FIN  ******************/

void beginLoop() {
  digitalWrite(enaPin, enableStart);          // mise à niveau de l'état couple
  digitalWrite(hotPin, wireStart);            // mise à niveau de l'état chauffe
}

void burn() {
  atu = false;
  unsigned long endBurn = millis() + (burnTime * 1000);
  while ( millis() < endBurn && !atu)         // séquence de préchauffage
    if (!redoMode) atu = !digitalRead(atuPin);
  Serial.write(atu << 6);                     // signal de fin
}

void endLoop() {
  delay(waitAllReceipt);                      // attente de réception de toutes les données série
  while (Serial.read() != -1);                // vidage du buffer entrant
  digitalWrite(enaPin, enableStop);           // mise à niveau de l'état couple
  digitalWrite(hotPin, wireStop);             // mise à niveau de l'état chauffe
  majOrigine();                               // envoi des nouvelles positions
}

void majOrigine() {
  // mise à jour des positions sur 4 entiers longs signés
  // octet de poids fort pour signe et atu, et 3 octets pour la valeur en pas 
  messageOut[0]  = ((posX1 >> 24) & 128) | (atu << 6);
  messageOut[1]  = abs(posX1) >> 16;
  messageOut[2]  = abs(posX1) >> 8;
  messageOut[3]  = abs(posX1) >> 0;
  messageOut[4]  = ((posY1 >> 24) & 128) | (atu << 6);
  messageOut[5]  = abs(posY1) >> 16;
  messageOut[6]  = abs(posY1) >> 8;
  messageOut[7]  = abs(posY1) >> 0;
  messageOut[8]  = ((posX2 >> 24) & 128) | (atu << 6);
  messageOut[9]  = abs(posX2) >> 16;
  messageOut[10] = abs(posX2) >> 8;
  messageOut[11] = abs(posX2) >> 0;
  messageOut[12] = ((posY2 >> 24) & 128) | (atu << 6);
  messageOut[13] = abs(posY2) >> 16;
  messageOut[14] = abs(posY2) >> 8;
  messageOut[15] = abs(posY2) >> 0;
  // envoi du message
  Serial.write(messageOut, sizeMessageOut);
}

void razOrigine() {
  // raz des positions
  posX1 = 0;
  posY1 = 0;
  posX2 = 0;
  posY2 = 0;
}

long calibration() {
  // retourne la durée de traitement du code en µs
  calcSpeeds();              // selon valeurs <periode> et <Vcut>
  calcNoAccel();             // aucune accélération
  nPas = nPasMM / 4;         // nombre de pas à effectuer pour la mesure
  numPasOld = 0;             // aucune reprise d'usinage
  targetX1 =  nPas;          // déplacement identique sur les 4 axes
  targetY1 =  nPas;
  targetX2 =  nPas;
  targetY2 =  nPas;
  fracX1   = .0;             // fractions de pas à zéro
  fracY1   = .0;
  fracX2   = .0;
  fracY2   = .0;
  long startTest(micros());
  interpolation(false);      // interpolation avec moteurs désactivés
  long stopTest(micros());
  return ((stopTest - startTest) / nPas) - periode;
}

/*******************  CALCULS PRELIMINAIRES  ******************/

void calcSpeeds() {
  // précalcul des délais selon le rapport cyclique (en début de tout programme g-code)
  Tcut1 = (periode * dutyCycle) / 100;
  Tcut2 = periode - Tcut1;
  Tmin1 = Tcut1 / (Vmin / Vcut);
  Tmin2 = Tcut2 / (Vmin / Vcut);
}

void calcNpasMax() {
  // recherche du plus grand déplacement en pas (à chaque consigne g-code)
  long maxPas1 = max(abs(targetX1), abs(targetY1));
  long maxPas2 = max(abs(targetX2), abs(targetY2));
  nPas = max(maxPas1, maxPas2);
}  
  
void calcSensAxes() {
  // application des sens de rotation (à chaque consigne g-code)
  byteOutDir = B00000000;
  byteOutDir |= (targetY2 < 0) << 3;
  byteOutDir |= (targetX2 < 0) << 2;
  byteOutDir |= (targetY1 < 0) << 1;
  byteOutDir |= (targetX1 < 0) << 0;
  PORTB = byteOutDir;
}

void calcAccel() {
  // calcul des accélérations (à chaque consigne g-code en mode manuel)
  nPasAccel = (Vcut - Vmin) / sin(penteAccel * M_PI / 180) * cos(penteAccel * M_PI / 180) * nPasMM;
  
  if (nPasAccel != 0) {
    increment1 = (float)(Tmin1 - Tcut1) / nPasAccel;
    increment2 = (float)(Tmin2 - Tcut2) / nPasAccel;
  } else {
    nPasAccel  = 0;
    increment1 = 0;
    increment2 = 0;
    Tmin1 = Tcut1;
    Tmin2 = Tcut2;
  }
  // limitation qui ne doit pas influer sur les incréments
  if (nPasAccel > nPas / 2) { nPasAccel = nPas / 2; }

  // tempos et incréments
  tempo1 = Tmin1;
  tempo2 = Tmin2;
  incmt1 = Tmin1;
  incmt2 = Tmin2;
}

void calcNoAccel() {
  // usinage sans accélérations (par défaut en début de tout programme g-code)
  nPasAccel  = 0;
  increment1 = 0;
  increment2 = 0;
  // tempos et incréments
  tempo1 = Tcut1;
  tempo2 = Tcut2;
  incmt1 = Tcut1;
  incmt2 = Tcut2;
}

/**********************  INTERPOLATION  *********************/

void interpolation(bool motorsOn) {
  atu = false;
  // rapports de déplacements des axes
  float rateX1 = (float)abs(targetX1) / nPas;
  float rateY1 = (float)abs(targetY1) / nPas;
  float rateX2 = (float)abs(targetX2) / nPas;
  float rateY2 = (float)abs(targetY2) / nPas;
  // restauration de la position dans la consigne g-code si reprise d'usinage
  numPas = numPasOld;
  numPasOld = 0;
  // valeur d'incrément des positions (positions inchangées si étalonnage ou simulation)
  byte ic;
  motorsOn && !simuMode ? ic = 1 : ic = 0 ;
  // exécution synchrone de la consigne g-code
  while (numPas < nPas) {
    byteOutPls = B00000000;
    fracX1 += rateX1;
    if (fracX1 >= 1) {
        byteOutPls |= 1;
        byteOutDir & 1 ? posX1 += ic : posX1 -= ic ;
        fracX1--;
    }
    fracY1 += rateY1;
    if (fracY1 >= 1) {
        byteOutPls |= 2;
        byteOutDir & 2 ? posY1 += ic : posY1 -= ic ;
        fracY1--;
    }
    fracX2 += rateX2;
    if (fracX2 >= 1) {
        byteOutPls |= 4;
        byteOutDir & 4 ? posX2 += ic : posX2 -= ic ;
        fracX2--;
    }
    fracY2 += rateY2;
    if (fracY2 >= 1) {
        byteOutPls |= 8;
        byteOutDir & 8 ? posY2 += ic : posY2 -= ic ;
        fracY2--;
    }
    if (!motorsOn || simuMode) {              // désactivation des moteurs si étalonnage ou simulation
      byteOutPls = B00000000; 
    }       
    sendOneStep();                            // envoi du pas
    numPas++;
    atu = !digitalRead(atuPin);
    if (atu) {                                // si arrêt d'urgence
      // atu en usinage (immédiat)
      if (usinMode) {
        delay(waitAllReceipt);                // attente de réception de toutes les données série
        while (Serial.read() != -1);          // vidage du buffer entrant (efface la consigne g-code en attente)
        numPasOld = numPas;                   // sauvegarde de la position dans la consigne g-code
        numPas = nPas;                        // sortie de boucle
        Serial.write(atu << 6);               // envoi du signal atu
      } else
      // atu en manuel
      if (manuMode) {
        if (stopMode) {                       // arrêt progressif
          if (numPas < nPasAccel) 
            { nPasAccel = numPas; }
          if (numPas < nPas - nPasAccel) 
            { nPas = numPas + nPasAccel; }
        } else {
          numPas = nPas;                      // arrêt immédiat                   
        }
        endLoop;                              // envoi du signal atu + positions
      }
    }
  }
}
        
void purgeSteps(long px1, long py1, long px2, long py2, bool motorsOn) {
  // définition des sens de rotation
  byteOutDir = B00000000;
  byteOutDir |= (posY2 < py2) << 3;
  byteOutDir |= (posX2 < px2) << 2;
  byteOutDir |= (posY1 < py1) << 1;
  byteOutDir |= (posX1 < px1) << 0;
  PORTB = byteOutDir;
  // purge asynchrone des pas résiduels
  while (posX1 != px1 || posY1 != py1 || posX2 != px2 || posY2 != py2) {
    byteOutPls = B00000000;
    if (posX1 != px1) {
        byteOutPls |= 1;
        byteOutDir & 1 ? posX1++ : posX1-- ;
    }
    if (posY1 != py1) {
        byteOutPls |= 2;
        byteOutDir & 2 ? posY1++ : posY1-- ;
    }
    if (posX2 != px2) {
        byteOutPls |= 4;
        byteOutDir & 4 ? posX2++ : posX2-- ;
    }
    if (posY2 != py2) {
        byteOutPls |= 8;
        byteOutDir & 8 ? posY2++ : posY2-- ;
    }
    sendOneStep(); 
  }
}

void sendOneStep() {
  // envoi de 1 pas
  PORTC = byteOutPls;
  delayMicroseconds(tempo1);
  PORTC = B00000000;
  delayMicroseconds(tempo2);
  // mise à jour accélération
  if (numPas <= nPasAccel) {
     incmt1 -= increment1;
     incmt2 -= increment2;
     tempo1  = incmt1;
     tempo2  = incmt2;
  }
  // mise à jour décélération
  if (numPas >= nPas - nPasAccel) {
     incmt1 += increment1;
     incmt2 += increment2;
     tempo1  = incmt1;
     tempo2  = incmt2;
  }
}

/*****************  LECTURE/ECRITURE EEPROM  ******************/

long getBaudRate(byte b) {
  // conversion index -> baudrate
  switch (b) {
    case 0:
      return   4800; break;
    case 1:
      return   9600; break;
    case 2:
      return  19200; break;
    case 3:
      return  38400; break;
    case 4:
      return  57600; break;
    case 5:
      return 115200; break;
    default :
      indexBr = 1;
      eepromWriteConf();
      return 9600;
  } 
}

void eepromWriteConf() {
  // écriture des paramètres dans l'eeprom
  EEPROM.write(0,  nPasMM >> 8);
  EEPROM.write(1,  nPasMM >> 0);
  EEPROM.write(2,  dutyCycle);
  EEPROM.write(3,  Vmin);
  EEPROM.write(4,  (byte)((Vmin - int(Vmin)) * 100));
  EEPROM.write(5,  penteAccel);
  EEPROM.write(6,  burnTime);
  EEPROM.write(7,  indexBr);
  // incrémentation du compteur d'écriture eeprom
  unsigned long v = eepromWriteCount() + 1;
  EEPROM.write(1000, v >> 24);
  EEPROM.write(1001, v >> 16);
  EEPROM.write(1002, v >>  8);
  EEPROM.write(1003, v >>  0);
}

void eepromReadConf() {
  // lecture des paramètres depuis l'eeprom
  nPasMM     = ((word)EEPROM.read(0) << 8) + (word)EEPROM.read(1);
  dutyCycle  = EEPROM.read(2);
  Vmin       = (float)EEPROM.read(3) + ((float)EEPROM.read(4) / 100);
  penteAccel = EEPROM.read(5);
  burnTime   = EEPROM.read(6);
  indexBr    = EEPROM.read(7);
  baudRate   = getBaudRate(indexBr);
  // délai de réception pour 3 chaines 
  // soit 50ms par chaine de 48octets (480 bits) à 9600bps
  waitAllReceipt = (float)((1000 / (baudRate / 480)) * 3);   
}

unsigned long eepromWriteCount() {
  // retourne ne nombre d'enregistrements eeprom effectués
  return(((long)EEPROM.read(1000) << 24) + ((long)EEPROM.read(1001) << 16) +
         ((long)EEPROM.read(1002) <<  8) + ((long)EEPROM.read(1003) <<  0));
}

void eepromSendConf() {
  byte message[sizeMessageOut];
  // paramètres
  for (int i=0; i<7; i++)
    message[i] = EEPROM.read(i);
  // version et révision
  message[7] = ver;
  message[8] = rev;
  // nombre d'écritures eeprom
  for (int i=0; i<4; i++)
    message[i + 9] = EEPROM.read(i + 1000);
  // envoi du message (réponse à la requête de test du logiciel)
  Serial.write(message, sizeMessageOut);
}

void eepromViewConf() {
  // lecture des valeurs eeprom via moniteur série
  Serial.print("Version ");
  Serial.print(ver);
  Serial.print(".");
  Serial.println(rev);
  Serial.println();
  // valeurs définies
  Serial.print("Pas par mm       : ");
  Serial.println(nPasMM);
  Serial.print("Rap cyclique   % : ");
  Serial.println(dutyCycle);
  Serial.print("Vit seuil   mm/s : ");
  Serial.println(Vmin);
  Serial.print("Pente accel  deg : ");
  Serial.println(penteAccel);
  Serial.print("Prechauffe   sec : ");
  Serial.println(burnTime);
  Serial.print("Baudrate     bps : ");
  Serial.println(getBaudRate(indexBr));
  Serial.println();
  // valeurs calculées
  periode = 2500;
  Vcut = 1.00;
  Serial.print("Etalonnage mic-s : ");
  Serial.println(calibration());
  Serial.print("Delai port mic-s : ");
  Serial.println(waitAllReceipt);
  // valeurs système
  Serial.print("SRAM disponible  : ");
  Serial.println(freeRam());
  Serial.print("Ecritures EEPROM : ");
  Serial.println(eepromWriteCount());
}

void eepromDefault() {
  // valeurs eeprom par défaut
  nPasMM     = 200;     // pas par mm
  dutyCycle  = 50;      // rapport cyclique en %
  Vmin       = 1.00;    // vitesse de seuil en mm/s
  penteAccel = 75;      // pente d'accélération en degrés
  burnTime   = 5;       // duree de préchauffage
  indexBr    = 1;       // index baudrate
  baudRate   = getBaudRate(indexBr);
}

void eepromInitConf() {
  // effacement de l'eeprom
  for (int i=0; i<1023; i++) {
    EEPROM.write(i, 0);
  }
  // paramètres par défaut
  eepromDefault();
  eepromWriteConf();
}

/****************** FONCTIONS SYSTEME ************************/

int freeRam() {
  // retourne la quantité de SRAM disponible
  extern int __heap_start, *__brkval;
  int v;
  return (int)&v - (__brkval == 0 ? (int)&__heap_start : (int)__brkval);
}

/********************* INFORMATIONS ***************************
ARDUINO UNO R3 ATmega 328
FLASH 32k (32 256 octets + 512 octets bootloader) / SRAM 2k / EEPROM 1k
FLASH  :  10.000 cycles d'écriture
EEPROM : 100.000 cycles d'écriture
---------------------------------------------------------------
Câblage du switch ATU (composants au plus proche de la carte) :
pin 3 -> R 10k  <- Vcc
pin 3 -> 100nF  <- GND
pin 3 -> switch <- GND
---------------------------------------------------------------
Câblage du switch RESET :
reset -> switch <- GND
---------------------------------------------------------------
Câblage des sorties (DB25 à adapter selon modèle d'interface) :
  CNC            ARDUINO           DB 15         DB 25
step X1   =>   A0 (port C)    =>   pin  1   =>   pin 2
step Y1   =>   A1 (port C)    =>   pin  2   =>   pin 16
step X2   =>   A2 (port C)    =>   pin  3   =>   pin 7
step Y2   =>   A3 (port C)    =>   pin  4   =>   pin 6
dir  X1   =>    8 (port B)    =>   pin  5   =>   pin 14
dir  Y1   =>    9 (port B)    =>   pin  6   =>   pin 3
dir  X2   =>   10 (port B)    =>   pin  7   =>   pin 8
dir  Y2   =>   11 (port B)    =>   pin  8   =>   pin 5
enable    =>    7 (port D)    =>   pin  9   =>   pin 1
chauffe   =>    6 (port D)    =>   pin 10   =>   pin 9
masse     =>  GND             =>   pin 15   =>   pin 18~25
---------------------------------------------------------------
*/

