/*
   _===_ _           _                  ______ _     _             _ _ _
  |  ___| |         | |                 | ___ (_)   | |           (_) | |
  | |__ | | ___  ___| |_ _ __ ___ ______| |_/ /_  __| | ___  _   _ _| | | ___ _   _ _ __
  |  __|| |/ _ \/ __| __| '__/ _ \______| ___ \ |/ _` |/ _ \| | | | | | |/ _ \ | | | '__|
  | |___| |  __/ (__| |_| | | (_) |     | |_/ / | (_| | (_) | |_| | | | |  __/ |_| | |
  \____/|_|\___|\___|\__|_|  \___/      \____/|_|\__,_|\___/ \__,_|_|_|_|\___|\__,_|_|

  Contrôle de référence 10 MHz par GPS: Projet avec contrôle de LTC1661 (DAC 10 bits double) 
  Électro-Bidouilleur, Mars 2022. https://www.youtube.com/Électro-Bidouilleur
  Licence de droits: Attribution 4.0 International (CC BY 4.0)  Lisez https://creativecommons.org/licenses/by/4.0/deed.fr  
*/

// DIRECTIVES AU COMPILATEUR
//#include <timer.h>
#include <SPI.h>    

#define BROCHE_PPS              PA1                   // Timer2, entrée de capture 0
#define BROCHE_REF_IN           PA0                   // Timer2, entrée d'incrémentation du compteur.
#define BROCHE_SPI_SS           PB12                  // La broche de sortie vers le Slave Select du DAC
#define BROCHE_SPI_MOSI         PB15                  // La broche de données SPI vers le DAC
#define BROCHE_SPI_MISO         PB14                  // La broche de données SPI venant du DAC (pas utilisée)
#define BROCHE_SPI_SCLK         PB13                  // La broche de sortie SPI vers l'horloge du DAC
#define NOMB_PPS_PAR_ECHANT     10                    // Le nombre d'impulsions PPS dans un échantillon
#define REPONSE_OCXO_HZ_PAR_V   2.6385                // Réponse mesurée de l'OCXO utilisé. Doit etre un nombre avec decimale(s)
#define DAC_TENSION_MIN         0.0                   // Tension minimale du DAC. Doit etre un nombre avec decimale(s)
#define DAC_TENSION_MAX         5.0                   // Tension maximale du DAC. Doit etre un nombre avec decimale(s)
#define DAC_POST_GAIN           1.0                   // Gain de l'ampli op en sortie du DAC. Doit etre un nombre avec decimale(s)
#define DAC_NOMB_BITS_EFFECTIFS 16                    // Nombre de bits effectifs du DAC (combinaison des deux DACs)    
#define FREQ_OCXO               10000000.0000         // Fréquence de l'OCXO. Exprimer avec 4 décimales pour forcer la précision des calculs
#define vrai                    true
#define faux                    false
#define oui                     true
#define non                     false
#define vt_cls                  "\x1B[2J"            // Code VT-100 clear screen

HardwareTimer RefTimer(TIM2);                         // Instanciation de la minuterie TIM2

// VARIABLES GLOBALES
unsigned short int  compte_nominal;                   // Le résultat de l'échantillon de TIM2 si la fréquence est d'exactement 10 MHz
unsigned short int  nouv_val_capture;                 // Nouvelle valeur de l'échantillon de TIM2
unsigned short int  preced_val_Capture;               // Ancienne valeur de l'échantillon de TIM2
unsigned short      compteur_captures = 0;            // Le compteur du nombre de PPS durant l'échantillonnage
unsigned int        compte_osc;                       // L'échantillon de TIM2 à analyser
short int           delta_compte_osc;                 // La différence entre l'échantillon et le compte nominal
short int           somme_delta_compte_osc = 0;       // La somme des delta_compte_osc pour fins de calcul
unsigned short int  compteur_echantillons = 0;        // Le compteur du nombre d'échantillons 
unsigned short int  nomb_echantillons_par_cycle = 1;  // Le nombre d'échantillons dans la période d'analyse     
float               moyenne_delta_compte_osc;         // La moyenne accumulée des différences échantillon-nominal
float               moyenne_decalage_hz = 0;          // La moyenne du décalage des échantillons, exprimé en Hz 
double              frequence_mesuree_hz;             // La fréquence 10 MHz mesurée
double              reponse_lsb_par_hz;               // La variation du DAC correspondant à un changement de 1 Hz de l'oscillateur. Valeur calculée.
bool                echantillon_pret = non;           // Signal annonçant qu'un échantillon est prêt
bool                sauter_echantillon;               // Signal annonçant de sauter l'échantillon courant
bool                detection_pps = non;              // Signal indiquant qu'une pulsation PPS a été détectée
int                 delta_dac;                        // La correction à appliquer à la valeur du DAC 
int                 dac_val_h;                        // La valeur des 10 bits supérieurs du DAC, appliquée sur le DAC A du LTC1661
int                 dac_val_l;                        // La valeur des 6 bits supérieurs du DAC, appliquée sur le DAC A du LTC1661
char                signe_delta_dac;                  // Le caractère (+ ou -) à afficher sur le résultat delta_dac
unsigned short int  duree_cycle_court = 1;            // Le nombre d'échantillons d'un cycle court
unsigned short int  duree_cycle_moyen = 10;           // Le nombre d'échantillons d'un cycle moyen
unsigned short int  duree_cycle_long = 100;           // Le nombre d'échantillons d'un cycle long
float               seuil_cycle_moyen = 0.101;          // Le seuil de changement de cycle moyen -> court
float               seuil_cycle_long = 0.0101;          // Le seuil de changement de cycle long -> moyen
unsigned long int   debut_led_millis = 0;             // La valeur conservée de millis (pour fins d'extinction de la LED)

// Fonction gérant l'interruption de la minuterie T2 générée par le signal PPS
void interrupt_TIM2_PPS()
{
  if (++compteur_captures >= NOMB_PPS_PAR_ECHANT)     // Attendu la durée spécifiée en secondes?
  { // Oui
    preced_val_Capture = nouv_val_capture;            // Conserver la précédente valeur capturée de TIM2 
    nouv_val_capture = TIM2->CCR2;                    // Lire la nouvelle valeur capturée de TIM2
    if (nouv_val_capture > preced_val_Capture) compte_osc = nouv_val_capture - preced_val_Capture;    // Gérer le débordement des valeurs nouvelle et précédente
    else compte_osc = pow(2,DAC_NOMB_BITS_EFFECTIFS) + nouv_val_capture - preced_val_Capture;                                  //     "
    compteur_captures = 0;                            // Ré-initialiser le compteur des captures
    echantillon_pret = oui;                           // Aviser qu'un nouvel échantillon est prêt
  }
  // Non
  detection_pps = oui;                                // Aviser de la détection d'une impulsion PPS
}

// La fonction envoyant une nouvelle valeur à un des deux DACs du LTC1661
void Envoi_Val_Dac(unsigned short dac, unsigned short int valeur)
{
  SPI.beginTransaction(SPISettings(100000, MSBFIRST, SPI_MODE0)); // Début d'une écriture d'un des DACs via le port SPI (débit, ordre des bits, mode du bus SPI)
  valeur = ((dac == 1 ? 0b1001 : 0b1010)<<12) | (valeur<<2);      // Choix de l'adresse du registre de DAC en fonction du paramètre reçu
  digitalWrite(BROCHE_SPI_SS, LOW);                               // Abaissement du signal Slave Select du DAC  
  SPI.transfer(valeur>>8);                                        // Transfert des 8 bits MSB
  SPI.transfer(valeur&255);                                       // Transfert des 8 bits LSB
  digitalWrite(BROCHE_SPI_SS, HIGH);                              // Relèvement du signal Slave Select du DAC  
  SPI.endTransaction();                                           // Fin de l'écriture du DAC, libération du bus.
}

// La fonction d'initialisation, style Arduino
void setup()
{
  Serial.begin(115200);                               // Le port série pour lire les rapports et résultats
  pinMode(BROCHE_PPS, INPUT_PULLUP);                  // Broche PPS en entrée avec pullup
  pinMode(BROCHE_REF_IN, INPUT_PULLUP);               // Broche de la référence 10 MHz en entrée avec pullup
  pinMode(LED_BUILTIN, OUTPUT);                       // Broche de la LED intégrée en sortie
  pinMode(BROCHE_SPI_SS, OUTPUT);                     // Broche Slave Select vers le DAC LTC1661
  digitalWrite(BROCHE_SPI_SS, HIGH);                  // Désactiver le SS du DAC
  SPI.setMOSI(BROCHE_SPI_MOSI);
  SPI.setMISO(BROCHE_SPI_MISO);
  SPI.setSCLK(BROCHE_SPI_SCLK);
  SPI.begin();                                        // Préparer le bus SPI.

  // Calcul du compte nominal d'un échantillon pour une référence d'exactement 10 MHz
  compte_nominal = round((NOMB_PPS_PAR_ECHANT * FREQ_OCXO / pow(2,DAC_NOMB_BITS_EFFECTIFS) - trunc(NOMB_PPS_PAR_ECHANT * FREQ_OCXO / pow(2,DAC_NOMB_BITS_EFFECTIFS))) * pow(2,DAC_NOMB_BITS_EFFECTIFS)); 

  // Calcul des paramètres du système en fonction des composants et de la topologie
  reponse_lsb_par_hz = 1 / float((DAC_TENSION_MAX - DAC_TENSION_MIN) * DAC_POST_GAIN / pow(2,DAC_NOMB_BITS_EFFECTIFS) * REPONSE_OCXO_HZ_PAR_V);
  
  RefTimer.attachInterrupt(2,interrupt_TIM2_PPS);     // Associer la fonction d'interruption au compteur TIM2

  // Configuration du compteur TIM2 du STM32 pour faire la "capture" de la référence 10 MHz
  TIM2->CR1 =   0b0000'0000'0000'0000;                // TIM2 désactivé au départ
  TIM2->CR2 =   0b0000'0000'0000'0000;                // TIM2 Canal 1 est l'entrée 
  TIM2->SMCR =  0b0100'0000'1111'0111;                // ETR non-inversé, External Mode 2 sactivé, Prescaler éteint, pas de filtre. master-slave synchro,  entrée ETR va incrémenter TIM2
  TIM2->DIER =  0b0000'0000'0000'0100;                // Capture/Compare 2 interrupt activé, 
  TIM2->EGR =   0b0000'0000'0000'0100;                // Capture/compare 2 generation activé
  TIM2->CCMR1 = 0b0000'0001'0000'0000;                // Canal 2 en entrée pour le déclenchement PPS, pas de filtre, pas de préselecteur
  TIM2->CCMR2 = 0b0000'0000'0000'0000;
  TIM2->CCER =  0b0000'0000'0001'0000;                // Front montant de CC2P, Capture activée
  TIM2->PSC =   0b0000'0000'0000'0000;                // Prescaler à 1
  TIM2->ARR =   0b1111'1111'1111'1111;                // Valeur Auto-Reload au maximum.
  TIM2->CR1 =   0b0000'0000'0000'0001;                // TIM2 activé et incrémente

  delay(2000);                                        // Faire une pause pour permettre au port série de s'ouvrir sur l'ordi
  Serial.println("Controle de reference 10 MHz par GPS sur STM32, Version 1");    // Message d'accueuil
  Serial.println("Electro-Bidouilleur, Mars 2022");   
  sauter_echantillon = oui;                           // Aviser de sauter le premier échantillon après la mise sous tension
  Serial.println("Pause de stabilisation...");   

  // Intialiser avec les valeurs du système par défaut
  dac_val_h = 511;                    // 1024 valeurs possibles (10 bits)
  dac_val_l = 0;                      // 64 valeurs possibles (6 bits)
  Envoi_Val_Dac(1,dac_val_h);         // Configurer les DACs à mi-chemin (par défaut)
  Envoi_Val_Dac(2,dac_val_l);         // Configurer les DACs à mi-chemin (par défaut)

  // À venir ici: Récupérer les paramètres du système de la mémoire EEPROM ici
  
}

// La boucle principale, style Arduino
void loop()
{
  if (echantillon_pret)                               // Est-ce qu'un échantilon est prêt?                                           
  {  // Oui 
    if (!sauter_echantillon)                          // Doit-on sauter le nouvel échantillon ?
    {   // Non
      // Calcul cumulatif des résultats de l'échantillonnage
      delta_compte_osc = compte_osc - compte_nominal;                                       // Calculer la différence entre le compte de l'échantillon et le compte nominal
      somme_delta_compte_osc += delta_compte_osc;                                           // Accumuler ces différences pour fins de calcul
      moyenne_delta_compte_osc = float(somme_delta_compte_osc) / ++compteur_echantillons;   // Faire la moyenne de ces différences
      moyenne_decalage_hz = float(moyenne_delta_compte_osc) / NOMB_PPS_PAR_ECHANT;          // Exprimer la moyenne des différences en Hz
      frequence_mesuree_hz = FREQ_OCXO + moyenne_decalage_hz;
 
      // Imprimer les différents résultats de calculs.
      Serial.print(vt_cls);                           // Un effacement de l'écran en mode terminal VT-100
      Serial.println("\n--------------------------------------");              
      Serial.print("Echantillon: ");
      Serial.print(compteur_echantillons);
      Serial.print(" / ");
      Serial.println(nomb_echantillons_par_cycle);
      Serial.print("Compteur de l'oscillateur: ");
      Serial.print(compte_osc);
      Serial.print("     Nominal: ");
      Serial.println(compte_nominal);      
      Serial.print("Ecart vs. compte nominal: ");
      Serial.println(delta_compte_osc);
      Serial.print("Moyenne des ecarts: ");
      Serial.println(moyenne_delta_compte_osc,4);
      Serial.print("Moyenne des ecarts (Hz): ");
      Serial.println(moyenne_decalage_hz,4);
      Serial.print("Frequence calculee de la reference (Hz): ");
      Serial.println(frequence_mesuree_hz,4);
        
      // Ici le traitement de la correction de l'OCXO est fait si le nombre d'échantillons accumulés est rencontré.
      if (compteur_echantillons >= nomb_echantillons_par_cycle) // Est-ce le temps de calculer la correction d'OCXO?
      {   // Oui
        compteur_echantillons = 0;                        // Ré-initialiser les différentes valeurs pour démarrer un neauveau cycle d'échantillonnage.
        moyenne_delta_compte_osc = 0;                     //            "
        somme_delta_compte_osc = 0;
        sauter_echantillon = oui;
        delta_dac = int(round(abs(moyenne_decalage_hz) * reponse_lsb_par_hz));     // Correction opposée à la valeur du décalage mesurée. 
        if (delta_dac > (pow(2,DAC_NOMB_BITS_EFFECTIFS)-1)) delta_dac = (pow(2,DAC_NOMB_BITS_EFFECTIFS)-1);                      // 2598.8 LSB par Volt de DAC (mesuré/calculé), incluant gain de 2 sur l'ampli op de sortie.

        // Calcul de la valeur des DACs A et B (h et l)
        // Le DAC A a un effet de 1023/1024 de ta tension de sortie. Le DAC B a un effet 1/1024 de la tension de sortie.
        if (moyenne_decalage_hz >0)   // moyenne de décalage positive?
        { // Oui, faire une correction négative
          dac_val_l -= ((delta_dac & 63)<<4);             // Ajouter l'effet du delta_dac à la valeur de dac_val_l. 64 valeurs possibles (6 bits LSB du DAC effectif)          
          if (dac_val_l < 0)                              // Dépassement négatif de dac_val_l?
          { // Oui
            dac_val_l += 1024;                            // Ajouter 1024 à dac_val_l
            dac_val_h--;                                  // Décrémenter dac_val_h
          }
          dac_val_h -= (delta_dac>>6);                    // Soustraire l'effet du delta_dac de la valeur de dac_val_h. (1024 valeurs possibles (10 bits MSB du DAC effectif))
          signe_delta_dac = '-';                          // delta_dac négatif, assigner un - pour l'affichage
        }
        else if (moyenne_decalage_hz < 0)                 // moyenne de décalage négative
        { // Faire une correction positive
          dac_val_l += ((delta_dac & 63)<<4);             // Ajouter l'effet du delta_dac à la valeur de dac_val_l (64 valeurs possibles (6 bits))
          if (dac_val_l > 1023)                           // Dépassement de dac_val_l?
          { // Oui, gérer le dépassement
            dac_val_l -= 1024;                            // Soustraire 1024 de la valeur
            dac_val_h++;                                  // incrémenter dac_val_h             
          }
          dac_val_h += (delta_dac>>6);                    // Ajouter l'effet du delta_dac à la valeur de dac_val_h 
          signe_delta_dac = '+';                          // delta_dac positif, assigner un + pour l'affichage
        }
        else signe_delta_dac = ' ';                       // Cas de décalage zéro: assigner un espace pour l'affichage
        if (dac_val_h > 1023) dac_val_h = 1023;           // S'assurer qu'il n'y ait pas de débordement du DAC rendu ici
        else if (dac_val_h < 0) dac_val_h = 0;

        // Envoi des valeurs aux DACs
        Envoi_Val_Dac(1,dac_val_h);                       // Envoi de la valeur du DAC A du LTC1661   
        Envoi_Val_Dac(2,dac_val_l);                       // Envoi de la valeur du DAC B du LTC1661

        // Affichage des résultats
        unsigned short int dac_val = (dac_val_h<<6) + (dac_val_l>>4);  // Valeur du DAC sur 16 bits calculee pour affichage
        Serial.println("CYCLE DE MOYENNE COMPLETE");      // Impression des résultats
        Serial.print("Ajustement fait au DAC: ");         //        "
        Serial.print(signe_delta_dac);     
        Serial.println(delta_dac);     
        Serial.print("Nouvelle valeur du DAC (16 bits): ");
        Serial.println(dac_val);     
        Serial.println("Pause de stabilisation...");   
        
        // Sélection de la longueur du prochain cycle d'échantillonnage en fonction des résultats du cycle complété.
        nomb_echantillons_par_cycle = duree_cycle_long;                                                     
        if (abs(moyenne_decalage_hz) >= seuil_cycle_long) nomb_echantillons_par_cycle = duree_cycle_moyen;
        if (abs(moyenne_decalage_hz) >= seuil_cycle_moyen) nomb_echantillons_par_cycle = duree_cycle_court;        
      }
    // Pas le moment pour une correction d'OCXO
    Serial.println("--------------------------------------");
    }  
    else sauter_echantillon = non;                        // Sinon il fait sauter cet échantillon.
    echantillon_pret = non;                               // Ré-initialiser le marqueur de nouvel échantillon prêt
  }

  // Traitement des pulsations PPS (partie non-critique)?
  if (detection_pps)                                      // PPS détecté par la fonction d'interruption?
  {   // Oui
      debut_led_millis = millis();
      detection_pps = non;                                // Ré-initialiser le marqueur de PPS reçu   
      digitalWrite(LED_BUILTIN, LOW);   // Allumer la LED pour indiquer la réception d'un PPS
      if (!sauter_echantillon)
      {
        Serial.print("_");                                // Afficher le numéro de l'impulsion PPS durant l'échantillonnage
        Serial.print(compteur_captures + 1);              //            "       
      }
  }
  else  // Pas de pulsations PPS de recue; on complète les travaux non critiques.
  {
    if (millis() - debut_led_millis >= 50) digitalWrite(LED_BUILTIN,HIGH);    // Extinction de la LED après un court délai
  }
}
