/*
   Programme pc_rm1 : régleur de montre - chronocomparateur
   Auteur : P. Chour - 2018-12-31 - V1.4
   Matériel : carte Arduino pro micro, afficheur I2C 2 lignes 16 caractères, 2 LEDS, une entrée "signal", un contacteur rotatif avec poussoir
   Différences entre V1.0 et V1.1
    - amélioration de la gestion du bouton poussoir
    - correction bug sur signe avance/retard par heure
   Différences entre V1.1 et V1.2
    - passage en 64 bits
    - test durée d'affichage (53ms pour deux lignes). Alternance de l'affichage pour diminuer cette durée.
    - amélioration du comptage de la durée à long terme
    - Affichage avance-retard sur 24 heures
   Différences entre V1.2 et V1.3
    - Ajout battement 19800, 25200
   Différences entre V1.3 et V1.4 (30/3/2019)
    Portage de quelques améliorations de présentation du code de PC-RM3. 
    Mémorisation de la dernière valeur de battement choisie en EEPROM
    Portage et amélioration de l'algorithme de mesure depuis PC-RM3

*/


#include <limits.h>
#include <PinChangeInterrupt.h>
#include <PinChangeInterruptBoards.h>
#include <PinChangeInterruptPins.h>
#include <PinChangeInterruptSettings.h>
#include <EEPROM.h>
#include <LiquidCrystal_I2C.h>

//#define DEBUG_SERIAL
//#define DEBUG_TIME
//#define DEBUG_AFF

#define DureeSansSignal 50            // Pour la synchro. Délai durant lequel on ne veut pas de signal (en ms).

//
// Constantes, define et variables pour la mesure.
//
long     EcartDureeTicTac;    // Conservation de la moyenne court terme de la différence entre un TicTac et la période attendue
long     NbEcartDureeTicTac;

int64_t  EcartDureeTicTacLong;
int64_t  NbEcartDureeTicTacLong;

uint64_t DureeLongTerme;     // Pour mesure précise des périodes. En µS.
uint64_t NbDureeLongTerme;   // Nombre de mesures DureeLongTerme

uint64_t EcartTicTac;        // Pour moyenne de la différence de l'écart entre Tic et Tac
uint64_t NbEcartTicTac;      // pour calcul de la moyenne : nombre d'EcartTicTac 

#define MaxBattements   7             // Nombre de valeurs dans Battements[] (et idem pour Periode, demi periode et Aberrant)
// Les constantes qui suivent dépendent de Battements. Elles sont précalculées pour éviter de perdre du temps lors des traitements.
// Si on souhaite ajouter un nouveau battement, on l'ajoute dans le tableau Battement à la position i
// On calcule la période correspondante que l'on ajoute dans le tableau Periode à la position i
// On calcule la demi période que l'on ajoute dans le tableau DemiPeriode à la position i
// On calcule une valeur de durée de demi période que l'on considère comme aberrante. Pour ma part, c'est en général 1/4 de période. On l'ajoute dans
// le tableau Aberrant à la position i
// On met à jour la valeur MaxBattements qui correspond aux nombres de battements que l'on a défini.
const uint64_t Battements[MaxBattements] = {18000, 19800, 21600, 25200, 28800, 36000, 3600};             // en battements par heures
const uint64_t Periode[MaxBattements] = {400000, 363636, 333333, 285714, 250000, 200000, 1000000};       // periode en µS
const uint64_t DemiPeriode[MaxBattements] = {200000, 181818, 166666, 142857, 125000, 100000, 500000};    // demi période en µS
int IndexBattements = 0;                        // Valeur du battement courant dans le tableau Battements (et Periode et Aberrant).
const unsigned int AdIndexBattements = 0;       // Adresse de la sauvegarde de IndexBattement en EEPROM

// Mise à jours par le programme d'interruption ou le processus de synchronisation
unsigned long TempsTicTacPrec;            // Dernière valeur Timer d'un Tic ou d'un Tac mis à jour par le programme d'interruption
unsigned long TempsTicTac;                // Temps TicTac courant. TempsTictac-TempsTicTacPrec = durée entre Tic et Tac
unsigned long DiffTicTac;                 // Durée d'un Tic ou d'un Tac. Mis à jour par routine d'interruption.
unsigned long DiffTicTacPrec;             // Valeur précédente de DiffTicTac
bool ITTic = false;                       // Vrai si un Tic ou un Tac a été détecté (génération interruption). Doit être remis à faux par l'utilisateur




// Pin du processeur et usage
//
#define SignalEntree        9             // PIN Signal d'entrée de la mesure
#define LED_tic     4                     // PIN LED rouge (Tic)
#define LED_tac     5                     // PIN LED verte (Tac)
#define poussoir    8                     // PIN bouton poussoir
#define droite      7                     // PIN contact "droite" du contacteur
#define gauche      6                     // PIN contact "gauche du contacteur

// Etats de l'automate
//
enum TEtat {E_selection,E_mesure,E_selection_trans,E_mesure_trans,E_pause,E_pause_in_trans,E_pause_out_trans,E_mesure_redemarre_trans,E_selection_change_trans};
enum TEtatSynchro {E_Synchro_En_Cours,E_Synchro_En_Cours_Trans, E_Synchro_Ok, E_Synchro_Ok_Trans};
TEtat EtatAutomate = E_selection_trans;  // Etat courant de l'automate

const char Depassement[4] = "***\0";

// gestion du contacteur rotatif
//
#define AntiRebondPoussoir 50             // durée en millisecondes de l'anti rebond
unsigned long TimerPoussoir = 0;          // Timer pour antirebond
#define DureeDouble 700                   // Durée max d'un double appui en ms
unsigned long TimerDouble = 0;            // Timer pour double appui
boolean Gauche = false;                   // Contacteur va à gauche
boolean Droite = false;                   // contacteur va à droite
byte GaucheSeq = 0;                       // séquence courante de lecture du contacteur rotatif
byte DroiteSeq = 0;
boolean GauchePrec = false;               // Ancienne valeur du contacteur rotatif
boolean DroitePrec = false;
#define AntiRebondRotation 5              // durée en millisecondes de l'anti rebond
unsigned long TimerRotation = 0;          // timer pour anti rebond de la rotation


LiquidCrystal_I2C lcd(0x3f, 16, 2); // Initialisation de l'afficheur : adresse 0x27, 16 caractères, 2 lignes



/*
   Initialisation du programme
*/
void setup()
{
  int i;
  bool Etat = false;
  lcd.init();                       // Initialisation de l'afficheur
  lcd.clear();
  lcd.print("Initialisation");      // Un petit texte pour faire patienter
  lcd.setCursor(0, 1);
  lcd.print("P. Chour V1.4");       // C'est moi, c'est moi, c'est moi !

  pinMode(poussoir, INPUT);         // La pin où est connecté le bouton poussoir en entrée.
  pinMode(droite, INPUT);           // La pin où est connecté le contact "droite"
  pinMode(gauche, INPUT);           // La pin où est connecté le contact "gauche"
  digitalWrite(poussoir, HIGH);     // active résistance pull up
  digitalWrite(droite, HIGH);       // active résistance pull up
  digitalWrite(gauche, HIGH);       // active résistance pull down
  pinMode(SignalEntree, INPUT);     // La pin où est connecté le signal à mesurer
  digitalWrite(SignalEntree, LOW);  // active résistance pull up
  pinMode(LED_tic, OUTPUT);         // La pin où est connecté la LED rouge
  pinMode(LED_tac, OUTPUT);         // La pin où est connecté la LED verte
  for (i = 0; i < 5; i++) {
    lcd.noBacklight();              // Allumage rétro éclairage afficheur
    FlipLed(&Etat); 
    delay(200);
    lcd.backlight();                // Allumage rétro éclairage afficheur
    FlipLed(&Etat); 
    delay(200);
  }
  
#if defined(DEBUG_SERIAL)
  Serial.begin(9600);  
#endif  

  attachPinChangeInterrupt(digitalPinToPinChangeInterrupt(SignalEntree), TicTacIT, RISING); // Signal d'entrée sous interruption.
  enablePinChangeInterrupt(digitalPinToPinChangeInterrupt(SignalEntree)); // On active l'interruption du signal d'entrée
  attachPinChangeInterrupt(digitalPinToPinChangeInterrupt(poussoir), PoussoirIT, FALLING); // Signal poussoir sous interruption.
  enablePinChangeInterrupt(digitalPinToPinChangeInterrupt(poussoir)); // On active l'interruption du poussoir
  sei();
}

/*
   Procédure sous interruption
   Déclenchée si signal en entrée (tic-tac)

*/

void TicTacIT()
{
  if (!ITTic) { // Si IT en cours de traitement, on l'ignore
    TempsTicTac = micros(); // Sauve la valeur du compteur courant
    ITTic = true;
  }
}

/*
   Procédure sous interruption
   Déclenchée lorsque l'on appuie sur le poussoir

*/

void PoussoirIT()
{
  unsigned long courante;
  courante = millis();            
  // valeur courante de l'horloge systeme
  if (DureeVrai(TimerPoussoir, courante) >= AntiRebondPoussoir) { // Prend-on en compte l'impulsion (antireboond) ?
    TimerPoussoir = courante;
    switch (EtatAutomate) {
      case E_mesure:                                              // On est dans l'état E_mesure. On passe en pause
        EtatAutomate = E_pause_in_trans;
        TimerDouble = courante;
        break;
      case E_pause:
        EtatAutomate = E_pause_out_trans;
        TimerDouble = courante;
      break;
      case E_pause_in_trans:                                      // On est en transition vers la pause : si deux appui rapide, on passe en sélection
      case E_pause_out_trans:
        if (DureeVrai(TimerDouble, courante) <= DureeDouble) {
          EtatAutomate = E_selection_trans;
        }
      break;
      case E_selection:                                           // On est dans l'état E_selection. On passe dans l'état E_mesure
        EtatAutomate = E_mesure_trans;
        break;
      default:                                                    // On peut être dans l'exécution d'une transition. Dans ce cas, on ne fait rien
      break;
    }
  }

}

//
// La fonction ci-dessous mesure le temps en prenant en compte un éventuel bouclage de la fonction millis ou micro
// On lui donne la valeur de départ de la valeur à mesurer et la valeur courante de millis ou micro. Elle rend la durée écoulée en millisecondes ou microseconde.
// DureeVrai = courante-dernière
//
unsigned long DureeVrai(unsigned long derniere, unsigned long courante) {
  if (courante < derniere) {  // on est passé par zéro
    return (0xffffffffu - derniere) + courante;
  } else {
    return courante - derniere;
  }
}

// La fonction ci-dessous convertit un nombre de 64 bits en string
// En entrée : N = nombre de 64 bits
// En sortie : string contenant le nombre
String Uint64ToString(uint64_t N) {
  String S;  
  if (N <= ULONG_MAX) {
    return String((unsigned long)N);
  } else {
    S = "";
    while (N > ULONG_MAX) {
      S = String((unsigned long)(N%10))+S;
      N = N / 10;
    }
    if (N > 0) {
      S = String(((unsigned long)N)) + S;
    }
    if (S == "") {S = "0";}
    return S;
  }
}

/*
 * Routine conservée (mais simplifiée) pour ne pas trop différer du logiciel PC-RM3.
 * Initialise la fenêtre d'occultation à une valeur fixe d'1/2 de la demi période
 * En entrée : index du battement choisi. Pas de contrôle
 * En sortie : Fenêtre basse d'occultation et fenêtre haute d'occultation initialisés (en ms)
*/
void InitFenetre(int IndexBattements, unsigned long *FenetreBasseOccultation, unsigned long *FenetreHauteOccultation)
{
  *FenetreBasseOccultation = DemiPeriode[IndexBattements]/2000; 
  *FenetreHauteOccultation = (Periode[IndexBattements]/1000)-*FenetreBasseOccultation;
}  

/*
 * Fait Basculer les LED en fonction d'un booléen en entrée 
 * Ce booléen est inversé en sortie
*/
void FlipLed(bool *Etat)  
{
  if (*Etat) { // Clignotement des LED
    digitalWrite(LED_tic, HIGH);
    digitalWrite(LED_tac, LOW);
  } else {
    digitalWrite(LED_tic, LOW);
    digitalWrite(LED_tac, HIGH);
  }
  *Etat  = !(*Etat);   // Pour affichage alterné des LED
}

//
// La fonction qui suit teste le contacteur rotatif et détermine s'il y a eu
// rotation ou pas. Elle fonctionne par test d'état (donc pas très performante).
// Positionne les variables d'état Gauche, Droite
// La fonction rend vrai si il y a eu rotation et que le sens a pu être déterminé
//
void TestRotation() {
  unsigned long courante;
  courante = millis();                   // valeur courante de l'horloge systeme
  // Lit les signaux présents sur les pins droite et gauche du contacteur
  boolean GaucheVal = digitalRead(gauche);
  boolean DroiteVal = digitalRead(droite);
  if ((GauchePrec != GaucheVal) || (DroitePrec != DroiteVal)) {     // il y a eu changement d'état
    if (DureeVrai(TimerRotation, courante) >= AntiRebondRotation) { // Prend-on en compte l'impulsion (antirebond) ?
      TimerRotation = courante;
      GaucheSeq <<= 1;
      GaucheSeq |= GaucheVal;
      GaucheSeq &= 0b00001111;
      GauchePrec = GaucheVal;
      DroiteSeq <<= 1;
      DroiteSeq |= DroiteVal;
      DroiteSeq &= 0b00001111;
      DroitePrec = DroiteVal;

      // Mask the MSB four bits
      // Compare the recorded sequence with the expected sequence
      if ((GaucheSeq == 0b00001001) && (DroiteSeq == 0b00000011)) {
        Gauche = true;
      }
      if ((GaucheSeq == 0b00000011) && (DroiteSeq == 0b00001001)) {
        Droite = true;
      }
      if (Droite || Gauche) {EtatAutomate = E_selection_change_trans;}
    }
  }
}

//
// Formatte une valeur pour affichage en microsecondes, millisecondes, secondes ou minutes dans un tableau de char terminé par null
// V1 = valeur en microsecondes
// L = longueur max affichage. Le formatage implique au moins 2 caractères + éventuellement un signe => jusqu'à 3 caractères sont pris. L Max = 19
// Signe = Signe à afficher. Espace => pas de signe. On gagne un caractère sur l'affichage.
// Rend "***" si dépassement capacité ou affichage impossible
// Affichage => unité sur deux caractères + signe sur 1 (+ ou -) ou (pas de signe) caractère + 1 chiffre => L >= 3 si pas de signe et L >= 4 si signe
// Pas de vérification de débordement !!! L'appelant doit faire attention que L et le buffer soient de la bonne taille.
//
void FormatTemps(uint64_t V1, int L, char Signe, char buf[]) {
uint64_t VTempPF;
uint64_t VTemppf;
char bufPF[20];
int iPF, k, i, j;
    if (L > 20) {L=19;}  // Une précaution !
    buf[L] = '\0';
    for (i=0; i<L;i++){bufPF[i]=' ';}
    
    VTempPF = V1/60000000;
    if (VTempPF > 0) {
      buf[L-1] = 'n';
      buf[L-2] = 'm';
      VTemppf = (V1-(VTempPF*60000000))/1000000;
    } else {
      VTempPF = V1/1000000;
      if (VTempPF > 0) {
        VTemppf = V1-(VTempPF*1000000)/1000;
        buf[L-1] = 's';
        buf[L-2] = ' ';
      } else {
        VTempPF = V1/1000;
        if (VTempPF > 0) {
          VTemppf = (V1-VTempPF*1000);
          buf[L-1] = 's';
          buf[L-2] = 'm';
        } else {
          VTemppf = V1;
          buf[L-1] = 's';
          buf[L-2] = 'u';
        }
      }
    }
    if (((VTempPF > 10) && ((L-2) <= 1)) || ((VTempPF > 100) && ((L-2) <= 2)) || (VTempPF > 1000)) {
        memcpy(buf,Depassement,sizeof(Depassement));
        return;
    }    
    iPF = 0;
    while (VTemppf > 0) {
      bufPF[iPF] = (char)(48+VTemppf-(VTemppf/10)*10); // attention aux optimisations du compilo !
      VTemppf = VTemppf/10;
      iPF = iPF+1;
    }
    if (iPF == 0) {
      bufPF[0] = '0';
      iPF = 1;
    }

    bufPF[iPF] = '.';
    iPF = iPF+1;
    if (VTempPF == 0) {
      bufPF[iPF] = '0';
      iPF = iPF+1;
    } else {
      while (VTempPF > 0) {
        bufPF[iPF] = (char)(48+VTempPF-(VTempPF/10)*10); // attention aux optimisations du compilo !
        VTempPF = VTempPF/10;
        iPF = iPF+1;
      }
    }
    if (Signe != ' ') {
      bufPF[iPF] = Signe;
      iPF = iPF+1;
    };   

    i = L-3;
    j = iPF-L+2;
    if (j < 0) {j = 0;}
    k = 0;
    for (k=j; k < iPF; k++) {
      buf[i] = bufPF[k];
      i = i-1;
    }
    while (i >= 0) {
      buf[i] = ' ';
      i = i-1;
    }
#if defined(DEBUG_AFF)      
    Serial.println(String((unsigned long)V1) + " / " + buf);
#endif    
}

//
// Affichage des mesures
// AAfficher² : 0 = Différence TicTac, 1=Différence période, 2= moyenne période, 3=avance retard par jour
//
void AffMesure(int IndexBattements, byte AAfficher)
{
  int i;
  uint64_t MoyenneTic;
  uint64_t MoyenneTac;
  long VTemp;
  uint64_t VTempU;
  int64_t  VTemp64;
  char buf[21];
  char Signe ;


#if defined(DEBUG_TIME)  
// Test durée exécution affichage  
  unsigned long ENTREE;
  ENTREE =  millis();
// Fin test durée exécution affichage 
#endif
#if defined(DEBUG_AFF)
for (i=0;i<20;i++) {
  buf[i]='A';
}
#endif
  
// Affichage période. On n'affiche que si l'on a au moins deux mesures
    switch (AAfficher) {
      case 1 :  // Moyenne des différences entre Tic et Tac 
            if (NbEcartTicTac > 0) {
              FormatTemps(EcartTicTac/NbEcartTicTac,7,' ',buf);
              lcd.setCursor(9,0);
              lcd.print(buf);
            }
         break;
      case 0 : // Ecart Tic Tac par rapport à période    
          if (NbEcartDureeTicTac > 0) {
            EcartDureeTicTacLong = EcartDureeTicTacLong+EcartDureeTicTac;
            NbEcartDureeTicTacLong = NbEcartDureeTicTacLong+NbEcartDureeTicTac;
            if (EcartDureeTicTac > 0) {
              VTempU = EcartDureeTicTac/NbEcartDureeTicTac;
              Signe = '+';
            } else {
              VTempU = (-EcartDureeTicTac)/NbEcartDureeTicTac;
              Signe = '-';
            }
            FormatTemps(VTempU,8,Signe,buf);
            lcd.setCursor(0,0);
            lcd.print(buf);
            EcartDureeTicTac = 0;
            NbEcartDureeTicTac = 0;
          }
      break;
      case 2 : //Moyenne de la période
          if (NbEcartDureeTicTacLong > 0) {
            if (EcartDureeTicTacLong > 0) {
              VTempU = EcartDureeTicTacLong/NbEcartDureeTicTacLong;
              Signe = '+';
            } else {
              VTempU = (-EcartDureeTicTacLong)/NbEcartDureeTicTacLong;
              Signe = '-';
            }
            FormatTemps(VTempU,8,Signe,buf);
            lcd.setCursor(0,1);
            lcd.print(buf);

          }
       break;
      case 3 : // Ecart
        VTempU = Periode[IndexBattements]*NbDureeLongTerme; 
        if (VTempU > DureeLongTerme) {                          // Si durée attendue est inférieure à durée mesurée
          VTempU = (VTempU-DureeLongTerme)/NbDureeLongTerme;    // La montre avance
          Signe = '+';
        } else {
          VTempU = (DureeLongTerme-VTempU)/NbDureeLongTerme;    // Sinon, elle retarde   
          Signe = '-';
        }
        FormatTemps(VTempU*Battements[IndexBattements]*(uint64_t)12,7,Signe,buf);
        lcd.setCursor(9,1);
        lcd.print(buf);
      break;
      default:;
    }

#if defined(DEBUG_TIME)    
// Test durée exécution affichage   : mesure = 62 ms et 71 ms  
  Serial.println("Duree affichage (ms) :"+String(millis()-ENTREE));
// Fin Test durée exécution affichage   
#endif

}


/**********************************
   APPLICATION
**********************************/

void loop()
{
  int i;
  bool LEDFlip = true;   // Pour alterner l'allumage des LED
  bool CalculPossible = false; 
  byte AAfficher = 0;                     // pour affichage des mesures. Pour des raisons de temps d'affichage, on n'affiche qu'une mesure à la fois
  unsigned long AffTimer;                 // On ne fait un affichage des mesures que toutes les 1000ms (Arduino, c'est lent !)
  unsigned long SynchroTimer;             // Timer pour la synchronisation et pour la détection d'inactivité
  unsigned long FenetreBasseOccultation;  // Durée basse à partir de laquelle on accepte une interruption
  unsigned long FenetreHauteOccultation;  // Durée haute au delà de laquelle on refuse accepte une interruption. On doit resynchroniser la mesure
  long TempsTic;                          // pour affichages, temporaire
  long TempsTac;                          // pour affichages, temporaire
  TEtatSynchro EtatSynchro = E_Synchro_En_Cours_Trans;  // Etat courant de l'automate de synchronisation 
  int IndexBattements;                                  // Valeur du battement courant dans le tableau Battements, Periode, demi-periode...  
  
  const String NonInitialise = "------    ------";
  
  AffTimer = millis();
  EEPROM.get(AdIndexBattements,IndexBattements); // Récupération des valeurs par défaut de IndexBattements et IndexOccultation
  if ((IndexBattements < 0) || (IndexBattements >= MaxBattements)) {IndexBattements = 0;}
  InitFenetre(IndexBattements,&FenetreBasseOccultation,&FenetreHauteOccultation);
  AffTimer = millis();  
  
  while (1) {

    switch (EtatAutomate) {

      //
      // Passage en mode sélection, on efface l'écran et on passe en changement de battement
      //
      case E_selection_trans:
        lcd.clear();
        lcd.print("Selection");


      //
      // Transition de changement de battement
      //
      case E_selection_change_trans:
        if (Gauche) {
          Gauche=false;
          IndexBattements--;
          if (IndexBattements < 0) {IndexBattements = MaxBattements-1;}
        }
        if (Droite) {
          Droite=false;
          IndexBattements++;
          if (IndexBattements >= MaxBattements) {IndexBattements = 0;}
        }
        lcd.setCursor(0, 1);
        lcd.print(Uint64ToString(Battements[IndexBattements]) + " Bat/h   ");
        InitFenetre(IndexBattements,&FenetreBasseOccultation,&FenetreHauteOccultation);        
        EtatAutomate = E_selection;
      break;

      //
      // Passage en mode mesure. On affiche les valeurs courantes de la mesure
      //
      case E_mesure_trans:
        lcd.clear();
        lcd.print(NonInitialise);
        lcd.setCursor(0, 1);
        lcd.print(NonInitialise);
        EEPROM.put(AdIndexBattements,IndexBattements);            // Sauvegarde valeur par défaut du battement en EEPROM. N'écrit que s'il y a eu changement
        EcartDureeTicTac = 0;
        NbEcartDureeTicTac = 0;
        EcartDureeTicTacLong = 0;
        NbEcartDureeTicTacLong = 0;
        EcartTicTac = 0;
        NbEcartTicTac = 0;
        DureeLongTerme = 0;
        NbDureeLongTerme = 0; 
        EtatAutomate = E_mesure;        
      break;

      //
      // passage en pause
      //
      case E_pause_in_trans: // on n'était pas dans l'état E_pause
        if (DureeVrai(TimerDouble,  millis()) > DureeDouble) {
          EtatAutomate = E_pause;
          lcd.setCursor(9, 1);
          lcd.print(" PAUSE  ");
        }
      break;

      case E_pause_out_trans: // on était dans l'état E_pause
        if (DureeVrai(TimerDouble, millis()) > DureeDouble) {
          EtatSynchro = E_Synchro_En_Cours_Trans;          
          EtatAutomate = E_mesure_redemarre_trans;
          lcd.setCursor(9, 1);
          lcd.print("        ");
        }
      break;

      //
      // En pause, on attend un événement (bouton poussoir)
      //
      case E_pause:
        delay(1);
      break;

      //
      // Si on n'a pas eu de double appui, alors, on repars en mesure
      //
      case E_mesure_redemarre_trans:

        lcd.setCursor(10, 1);
        lcd.print("     ");
        EtatAutomate = E_mesure;
      break;


      case E_mesure:

        if (CalculPossible) { // ITTic positionné par routine d'interruption
          // Pour Calcul de la moyenne de la différence entre un TicTac et la période attendue
          EcartDureeTicTac = EcartDureeTicTac+(DiffTicTac+DiffTicTacPrec-Periode[IndexBattements]);
          TempsTic = DiffTicTacPrec-DemiPeriode[IndexBattements];
          TempsTac = DiffTicTac-DemiPeriode[IndexBattements];
          NbEcartDureeTicTac = NbEcartDureeTicTac+1;            

          // Pour calcul de la moyenne de la différence entre un Tic et un Tac
          if (DiffTicTacPrec > DiffTicTac) {EcartTicTac = EcartTicTac+DiffTicTacPrec-DiffTicTac;} else {EcartTicTac = EcartTicTac+DiffTicTac-DiffTicTacPrec;}
          NbEcartTicTac = NbEcartTicTac+1; 
          // Pour calcul de l'avance/retard
          if (DureeLongTerme < 0x0FFFFFFFFFFFFFFF) {
            DureeLongTerme = DureeLongTerme + DiffTicTac + DiffTicTacPrec;
            NbDureeLongTerme = NbDureeLongTerme+1;
          }
          CalculPossible = false;
        } else {
          if (DureeVrai(AffTimer, millis()) > 1000)  { // on affiche que toutes les 1000 ms 
            AffTimer = millis();
            AffMesure(IndexBattements, AAfficher);
            AAfficher = (AAfficher+1) %4;             // Et on affiche qu'une valeur à la fois car ça prend du temps ! La valeur à afficher change à chaque appel
          }             
        }
      break;
//
// Dans l'état E_selection, on ne fait que choisir le battement de la montre.
// La gestion du contacteur rotatif est fait par test d'état car on n'a que ça à faire.
// On sort de l'état E_selection par appui sur le bouton poussoir du contacteur. Cet appui est
// détecté par interruption
//
      case E_selection:
        TestRotation();
      break;


//
// L'état default correspond à une erreur d'automate (pas d'état connu).
// En cas d'erreur, on retourne dans l'état E_Selection
//
      default:
        EtatAutomate = E_selection_trans;
        lcd.clear();
        lcd.print("Erreur 1");
        delay(1000);
       break;
    }


    //-------------------------------------------------
    // Automate de synchronisation
    //-------------------------------------------------
    switch (EtatSynchro) {
      case E_Synchro_En_Cours_Trans: // Passage en synchronisation. On recherche une période de silence suffisante pour que la prochaine IT soit le début d'un Tic ou d'un Tac
          SynchroTimer = millis();
          ITTic = false;
          CalculPossible = false;
          EtatSynchro = E_Synchro_En_Cours;
      break;
      
      case E_Synchro_En_Cours: // 
        if (ITTic) { // Il y a eu une IT en dessous de la période attendue. Pas bon, on recommence à attendre un silence.
          FlipLed(&LEDFlip);
          SynchroTimer = millis();  // On réinitialise le timer
          ITTic = false;
        } else {
          if (DureeVrai(SynchroTimer,millis()) > DureeSansSignal) { //On n'a pas eu d'IT durant un certain temps. 
            EtatSynchro = E_Synchro_Ok_Trans; // Donc, on a une période de silence suffisante pour que la prochaine IT soit le début d'un Tic ou d'un Tac.
          }
        }
      break;
      
      case E_Synchro_Ok_Trans: // On attend un début de Tic ou de Tac pour démarrer la mesure. 
        if (ITTic) {
          SynchroTimer = millis();                    // On réinitialise le timer
          EtatSynchro = E_Synchro_Ok;                 // Etat synchronisé
          TempsTicTacPrec = TempsTicTac;
          LEDFlip = false;
          ITTic = false;
        }
      break;
      
      case E_Synchro_Ok:
        if (DureeVrai(SynchroTimer,millis()) > FenetreHauteOccultation) {        // Si la différence entre le dernier Tic valide et le temps courant > à la fenêtre d'occultation haute, on a un pb. Il faut resynchroniser
            EtatSynchro = E_Synchro_En_Cours_Trans;
        } else {
          if (DureeVrai(SynchroTimer,millis()) > FenetreBasseOccultation) { // Si la différence est supérieure à la fenetre basse d'occultation, on réactive les IT
            if (ITTic) {
              if (LEDFlip) {
                DiffTicTac = DureeVrai(TempsTicTacPrec,TempsTicTac);
                CalculPossible = true;
              } else {
                DiffTicTacPrec = DureeVrai(TempsTicTacPrec,TempsTicTac);
              }
              TempsTicTacPrec = TempsTicTac;
              FlipLed(&LEDFlip);
              SynchroTimer = millis();
            } 
          }
        }
        ITTic = false;
      break;
      default: 
        lcd.clear();
        lcd.print("Erreur 2");
        delay(1000);
        EtatSynchro = E_Synchro_En_Cours_Trans;      
      break;
    }

  }
}