/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//                                                 EFIS_Avionicsduino
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

 Copyright (c) 2021 AvionicsDuino - [email protected]
 Contributors : Benjamin Frémond - Gabriel Consigny - Michel Riazuelo
 
    EFIS_Avionicsduino is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *****************************************************************************************************************************/ 
  
// --------------------------------------------------------------------------------------------------------------------------------------------------------------
//                                                Connexions des composants avec la carte Teensy 4.1
// --------------------------------------------------------------------------------------------------------------------------------------------------------------
// L'écran est une dalle TFT LCD de 4.3", résolution 480x272, de marque RIVERDI (ref. RVT43HLTFWN00), piloté par un contrôleur RA8875 Adafruit(ref. Product ID: 1590), connecté en SPI1
// Library utilisée pour l'affichage : https://github.com/mjs513/RA8875/tree/RA8875_t4 (version de Teensyduino)
// --------------------RA8875-------------------Teensy 4.1------------------------
//                   GND           --------->      GND 
//                Alim +5 volts    --------->   + 5 Volts
//                JP1 Pin 5  CS    --------->   Pin 0 CS1
//                JP1 Pin 6 MISO   --------->   Pin 1 MISO1
//                JP1 Pin 7 MOSI   --------->   Pin 26 MOSI1
//                JP1 Pin 8 SCLK   --------->   Pin 27 SCK1
//                JP3 Pin 11 Reset --------->   Non connecté
//                JP3 Pin 10 Int   --------->   Non connecté
//---------------------------------------------------------------------------
// Encodeur rotatif KY-40 
// ---------------KY-40------------------------Teensy 4.1------------------------
//                GND  ---------------------->   GND
//                VCC  ---------------------->   3.3 v (courant : qq mA)
//                 C   ---------------------->   Pin 35 Bouton central
//                 B   ---------------------->   Pin 36 Phase B
//                 A   ---------------------->   Pin 37 Phase A
//---------------------------------------------------------------------------
// Module NAVEOL
// -------------NAVEOL------------------------Teensy 4.1------------------------
//                GND     ------------------->   GND
//             Alim +5v   ------------------->   + 5 volts
//                TX   ---------------------->   RX5 (pin 21)
//                RX   ---------------------->   TX5 (pin 20)
//---------------------------------------------------------------------------
// Capteur de température intérieure oneWire DS18B20
// -------------DS18B20------------------------Teensy 4.1------------------------
//                GND     ------------------->   GND
//             Alim +3.3v ------------------->   + 3.3 volts (courant : 7 mA)
//              Signal   -------------------->   pin 2 + résistance pull up de 4,7k vers 3.3 volts
//---------------------------------------------------------------------------
// Capteurs de pression AMS5915 (1500A pour la statique, 050D pour la dynamique) 
// -------------1500A------------------------Teensy 4.1------------------------
//                GND     ------------------->   GND
//            Alim +3.3v  ------------------->   + 3.3 volts (courant : 4 mA)
//                SDA   --------------------->   SDA (pin 18) + résistance pull up de 4,7k vers 3.3 volts 
//                SCL   --------------------->   SCL (pin 19) + résistance pull up de 4,7k vers 3.3 volts
//           Adresse : 0x28
//---------------------------------------------------------------------------

// -------------0050D------------------------Teensy 4.1------------------------
//                GND     ------------------->   GND
//            Alim +3.3v   ------------------>   + 3.3 volts (courant : 4 mA)
//                SDA   --------------------->   SDA1 (pin 17) + résistance pull up de 4,7k vers 3.3 volts
//                SCL   --------------------->   SCL1 (pin 16) + résistance pull up de 4,7k vers 3.3 volts
//           Adresse : 0x28
//---------------------------------------------------------------------------

// GNSS SparkFun GPS Breakout - NEO-M9N, U.FL
// -------------NEO-M9N------------------------Teensy 4.1------------------------
//                GND     ------------------->   GND
//             Alim +3.3v ------------------->   + 3.3 volts (courant : 31 mA)
//                SDA ----------------------->   pin 25 (SDA2, résistance pull up sur la carte GPS)
//                SCL ----------------------->   pin 24 (SCL2, résistance pull up sur la carte GPS)
// 
// Transceiver CAN MCP 2562 EP (CAN 2.0)
//-------------- MCP 2562 ------------------------ Teensy 4.1 ------------------------
//             Pin1 TXD ------------------------> Pin 31 CTX3
//             Pin2 GND ------------------------> GND
//             Pin3 VDD ------------------------> + 5V
//             Pin4 RXD ------------------------> Pin 30 CRX3
//             Pin5 Vio ------------------------> + 3.3V
//             Pin6 CAN LOW --------------------> sortie CanBus
//             Pin7 CAN HIGH -------------------> Sortie CanBus
//             Pin8 Standby --------------------> GND


// L'heure est mémorisée dans la SRTC de la carte Teensy 4.1 par une pile lithium 3v CR2032 entre GND et VBAT.

// --------------------------------------------------------------------------------------------------------------------------------------------------------------
//                                                                  Inclusions des bibliothèques et fichiers externes
// --------------------------------------------------------------------------------------------------------------------------------------------------------------

#include <EEPROM.h>   
#include <SPI.h>      
// #include <RA8875.h> 
#include <TFT_eSPI.h>  
#include <OneWire.h>  
#include <TimeLib.h>  
#include <SD.h>       
// #include <QuadEncoder.h> 
// #include <Adafruit_GFX.h> // Pour les fontes, il faut télécharger cette library https://github.com/mjs513/ILI9341_fonts et l'installer dans le dossier des libraries Teensy.
// #include <font_Arial.h>
// #include <font_ArialBold.h>
// #include <TeensyTimerTool.h> /
// using namespace TeensyTimerTool; 
#include "HorizArt.h"
#include "HorizArtParamGen.h"
// #include "AMS5915_simplified.h"
// #include <FlexCAN_T4.h> 
// #include <SparkFun_u-blox_GNSS_Arduino_Library.h> 

// --------------------------------------------------------------------------------------------------------------------------------------------------------------
//                                                                             Création des objets
// --------------------------------------------------------------------------------------------------------------------------------------------------------------

// RA8875 tft = RA8875(RA8875_CS1, 255, RA8875_MOSI1, RA8875_SCLK1, RA8875_MISO1); 
// https://github.com/sumotoy/RA8875/blob/0.70/RA8875.h
TFT_eSPI tft = TFT_eSPI();// создаем объект для работы с экраном
OneWire  ds(2);  
// AMS5915_simplified AMS5915_050D(Wire1,0x28,AMS5915_simplified::AMS5915_0050_D);  
// AMS5915_simplified AMS5915_1500A(Wire,0x28, AMS5915_simplified::AMS5915_1500_A); 
HorizArt Horizon(&tft); 
// QuadEncoder encodeur(2, 37, 36, 0);
// PeriodicTimer TimerSendF; 
// OneShotTimer TimerStopReading;
// FlexCAN_T4<CAN3, RX_SIZE_256, TX_SIZE_16> CAN_Module_EFIS; 
// SFE_UBLOX_GNSS myGNSS; 
File fichier; // Crée un objet fichier pour le data logging

// --------------------------------------------------------------------------------------------------------------------------------------------------------------
//                                                                             Déclarations des variables et constantes globales
// --------------------------------------------------------------------------------------------------------------------------------------------------------------

// ******************************************* Déclaration du typedef d'une structure, puis d'un tableau d'éléments de cette structure, définissant l'arborescence des menus ********************************************************
/*
   Voir le schéma du système de menus : https://i.imgur.com/oD18FKH.jpg.
   Définitions :
   - Option de menu : tout texte pouvant être sélectionné avec l'encodeur rotatif, puis validé avec le bouton de cet encodeur, en vue d'agir sur le système, ou d'ouvrir une autre option.
   - Niveaux de menus : le menu principal est le niveau 1, chaque option du niveau 1 peut elle même ouvrir un sous-menu de niveau 2 qui lui-même...etc.
   - Numéro d'option : chaque option des menus et sous-menus se voit affecter un numéro unique avec la logique suivante : la première option du niveau 1 a le numéro 10, et on ajoute une unité pour les numéros des options suivantes
   du même niveau. Donc les numéros des options du niveau 1 sont : 10, 11, 12, 13...etc sans dépasser 19. Chaque option peut ouvrir un sous menu de niveau 2, dont le premier numéro est obtenu en multipliant par 10 le numéro 
   de l'option "mère". On rajoute des unités pour les sous-options suivantes. Par exemple l'option n° 12 de niveau 1 peut ouvrir les sous-options filles de numéros 120, 121, 122...etc. de niveau 2, sans dépasser 129. 
   On multiplie encore par 10 pour le niveau suivant.
   A chaque niveau, le numéro se terminant par zéro est affecté à l'option qui permet de retourner au niveau précédent (Back), simplement en divisant son numéro par 10. La dernière option de chaque sous-menu permet 
   de quitter complètement le menu (Quit). On ne peut pas, avec des nombres entiers, avoir plus de 10 options par menu/sous-menu. Mais pour une application simple, c'est suffisant. 
   Pour éviter d'avoir plus de 10 options, qui de plus ne pourraient pas s'afficher en bandeau horizontal en bas de l'écran, il faut créer des sous-menus.
   - Index : les options sont stockées dans un tableau où chaque option est repérable par son index

   La structure des options de menus et l'algorithme de traitement de ces options ont été conçus de telle façon qu'il soit très simple de faire évoluer ces menus, de modifier, de supprimer ou de rajouter des options.
   Voir le schéma de l'algorithme : https://i.imgur.com/eA0QNdb.jpg
   */
     //Le tableau va jouer le rôle d'une table de base de données pour contenir tous les éléments (options) des menus/sous-menus.
     // Chaque ligne du tableau est l'équivalent d'un enregistrement (ou entrée) de cette table. Chaque variable de la structure est un champ de cette table.
typedef struct     
{
  char* label;              // Libellé de l'option sous forme d'une C string. On évite d'utiliser un objet String.
  uint32_t numero;          // Numéro permettant de l'identifier, il est unique (équivalent d'une clé primaire)
  char action;              // Type d'action attendue si cette option est validée : Q->Quitte, S->Sous-menu à ouvrir, B->Back retour option mère, P->Procédure d'ajustement d'une valeur, A->Autre Action
  uint8_t nbOptionsSoeurs;  // Nombre d'options soeurs, c'est à dire toutes les options d'un même niveau de menu/sous-menu. Ce champ est calculé et initialisé pendant le setup.
  uint8_t indexOfOm;        // Index dans le tableau menu (APRES tri) de l'option fille (si action='S') ou de l'option mère (si action = 'B'). Champ calculé et initialisé pendant le setup. Voir le rôle des index : https://i.imgur.com/c1LsPR6.jpg
}OptionMenu;                // Nom de la structure (toujours à la fin en cas de déclaration de typedef struct)


// Déclaration d'un tableau sans dimension, nommé "menu", de type "OptionMenu". Chaque ligne du tableau caractérise une option de menu/sous-menu.
// Initialisation des trois premiers champs, "label", "numero" et "action", de toutes les lignes de ce tableau. Les 2 autres champs seront initialisés dans le setup.
// La présentation indentée ci-dessous, triée pour bien exposer l'arborescence des menus, permet une gestion facile des options de menus (modification, ajout ou suppression d'options).
// Mais avant utilisation, ce tableau sera trié différement dans le setup, par ordre croissant du champ "numero". Ce nouveau tri permettra l'exploitation des menus par l'algorithme.
// Le setup montre les résultats avant et après tri sur la console de l'IDE Arduino. 

OptionMenu menu[]=                              // Les champs "numéro" d'un même menu/sous-menu doivent OBLIGATOIREMENT se suivre toutes les unités, sans aucun trou.
{
  {"FLIGHT",10,'S'},                              
           {" BACK",  100,'B'},
           {"  QNH",  101,'P'},
           {"  ------", 102,'A'},
           {"ResetG", 103,'A'},
           {"  QUIT", 104,'Q'},
  
  {"   GEN",11,'S'},

           {" BACK",  110,'B'},
           {"  Uvit",  111,'S'},

                       {" BACK", 1110,'B'},
                       {" Km/h", 1111,'A'},
                       {"Noeuds",1112,'A'},
                       {"  QUIT",1113,'Q'},
           
           {"MgDev", 112,'P'},
           {"  Time", 113,'S'},

                       {" BACK", 1130,'B'},
                       {" Year", 1131,'P'},
                       {"Month", 1132,'P'},
                       {" Day",  1133,'P'},
                       {"Hours", 1134,'P'},
                       {" Min",  1135,'P'},
                       {" Sec",  1136,'P'},
                       {"Valid", 1137,'A'},
                       {"  QUIT",1138,'Q'},

           {"PrsCor", 114,'P'},
           {"  Lum",  115,'P'},
           {" DatLog",116,'S'},

                       {" BACK",  1160,'B'},
                       {"   Del", 1161,'A'},
                       {"  Start",1162,'A'},
                       {"  Stop", 1163,'A'},
                       {" Export",1164,'A'},
                       {"  QUIT", 1165,'Q'},
           
           {"  QUIT", 117,'Q'},

  {"Timers",12,'S'}, 

           {" BACK",  120,'B'},
           {"Depart", 121,'A'},
           {"Chrono", 122,'A'},
           {" Down",  123,'P'},
           {"  QUIT", 124,'Q'},   
  
  {"  QUIT",13,'Q'},   
  
  {" ",1000000,'\0'} // Marqueur de fin de tableau, permettant d'en compter les éléments
};

//******************************************************************** Variables Encodeur ***********************************************************************
int ValeurPositionEncodeur; 
int ValeurPositionEncodeurOld = 0; 
int ValeurPositionEncodeurOldSetVal = 0; 
byte Pin_Bouton = -1; 
const byte debounceTime = 200; 
volatile unsigned long lastButtonPress; 
bool Bouton_Clic = false; 

//********************************************************************* Variables Menus **************************************************************************
bool Menu_Ouvert = false; 
bool SetVal = false; 
uint8_t indexOptionMenuEnCours = 0;   
int16_t * ptrGen = NULL; 
uint16_t abscisseMenu=0, ordonneeMenu = 254; 
uint8_t hauteurCase = 18, largeurCase = 53; 

// ************************************************************** Variables EFIS - horizon artificiel ***************************************************************
int16_t QNH; 
int16_t YY,MM,DD,HH,Mn,Sec;
int16_t correctionPression; 
float correctionAltitude = 0.995; 
float pression = 101300; 
float pressionNonF; 
float valPressionFiltreePrecedente; 
float pressionDiff = 0; 
float pressionDiffNonF; 
float valPressionDiffFiltreePrecedente=0;
float tempAMS5915_050D = 0;
float tempAMS5915_1500A = 0;
float altitudeQNH=0; 
float altitudePression=0; 
float altitudeDensite=0.0;  
float iat = 5.0; 
volatile float oat = 5.0; 
volatile float humiditeRelative = 0.5; 
volatile uint32_t capMagnetique = 0; 
float dewPoint=0;
int16_t vitesseIndiquee, vitessePropre; 
int16_t offsetBille=0;
float altitudeVario, altitudeVarioOld; 
float vario = 0; 
uint32_t topchronoVario; 
uint16_t tempsIntegrationVario = 250; 
float valVarioFiltreePrecedente = 0; 
float accG,accGmax=1.0, accGmin=1.0; 
float valAccGFiltreePrecedente = 1; 
float facteurConversionVitesse = 1.0; 
int16_t magDev=0; 
float windSpeed, windDirection;

//******************************************************* Déclaration de 2 variables pour la gestion de l'EEPROM **********************************************
#define EFISeepromAdresse 22  
uint16_t  OffsetEFISeepromAdresse = 0;    

// ************************************************************ Variables utilisées pour le chronométrage de la boucle principale **********************************************************
int nombreBoucleLoop = 0; 
int dureeNdbBoucles = 0;
float Ndb = 1000.0;
unsigned long topHoraire; 

// ****************************************************************** Variables utilisées pour l'exploitation des trames reçues du boîtier Naveol **************************************************
bool dataReady=false; 
byte tabTrame[74];
float roll=0, pitch=0, psi=0, Q0, Q1, Q2, Q3, P, Q, R, AccX, AccY, AccZ, Vz, constante, trk, indexmenu, nbSat; 
char a; byte b; // les 2 octets d'entête de la trame Naveol
float valRollFiltreePrecedente=0, valPitchFiltreePrecedente=0, valAccYFiltreePrecedente=0, valVzFiltreePrecedente=0, valTrkFiltreePrecedente;
int16_t Vzint; 
uint16_t trkEntierArrondi;
bool flagPsiFiable = false; 
bool flagDataLogging = false; 
byte ndex = 0;

// ************************************************************************* Variables GNSS UBLOX *******************************************************************************************************************
float altitudeGNSS;
byte nbSatGNSS;
uint8_t heureGNSS;
uint8_t minuteGNSS;
uint8_t secondeGNSS;
uint16_t millisecondeGNSS;
float verticalSpeedGNSS, verticalSpeedGNSSOld;
float groundSpeedGNSS, groundSpeedGNSSOld;
float trackGNSS;

// ******************************************************************** Variables pour les envois de données sur le CAN Bus **************************************************************************************
// CAN_message_t msg;
// CAN_message_t msgRoll;
// CAN_message_t msgPitch;

//******************************************************* Variables et constantes liées à l'horloge et à la gestion de l'heure *****************************************************************************
#define Chy 192 
#define Chx 35  
#define Rh 28 
byte LaigH, LaigM, LaigS; 
time_t zt; 
time_t t_chrono1;  
time_t t_chrono2;  
time_t t_minuteur; 
bool flag_chrono1 = false;  
bool flag_chrono2 = false;  
bool flag_minuteur = false;  
int16_t dureeMinuteur = 0;   
// IntervalTimer TimerDataLog; 
bool flagLog=false; 
// IntervalTimer TimerHorloge; 
bool flagAfficheHeure=true; 
uint16_t hs, ms; 
uint8_t ss; 
#define nbs12h 43200 
uint16_t angleH, angleM, angleS; 
uint16_t angleHold = 0; 
uint16_t angleMold = 0;
uint16_t angleSold = 0;

unsigned long topClignote; 
const uint16_t periodeClignote = 500; 
byte inverseVoyant = 1; 
byte divFreqDataLog = 4; 
byte cptDataLog = 1; 

// ************************************************************** Variables nécessaires au fonctionnement de la sonde de température intérieure********************************************************************************************
byte dataDS18B20[12]; 
byte addrDS18B20[8];  
float temperatureDS18B20; 
uint32_t chronoDS18B20 = 0; 
bool flagDelaiAcquisitionDS18B20Termine = true; 


// ************************************************************** Variables diverses **********************************************************************************************
int16_t luminosite; 
char buf[25]; 
#define largeurPhoto 470 
#define hauteurPhoto 262

//------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
//                                                                                                           SETUP
//------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
void setup() 
{
  
// *************************************************************************************** Initialisation de la carte microSD **************************************************************************************************** 
  // bool result = (SD.begin(BUILTIN_SDCARD)); 
  bool result = false; 

// *********************************************************************************** Initialisation du bus SPI1 et de l'écran  *******************************************************************************************  
  // SPI1.setMOSI(26);
  // SPI1.setSCK(27);
  // SPI1.setMISO(1);
  // SPI1.setCS(0);

  // SPI1.begin();

  // tft.begin(Adafruit_480x272); 
  tft.begin(); 
  tft.setRotation(1);
  // tft.useLayers(true);
  // tft.layerEffect(LAYER1);
  // tft.writeTo(L1);
  tft.fillRect(0,0,480,272,TFT_BLACK);
  sablier(tft.width()/2, tft.height()/2); 
  // tft.writeTo(L2);
  tft.fillRect(0,0,480,272,TFT_BLACK);
  photo(); 
  // tft.layerEffect(LAYER2); 
  tft.setCursor(151,8); 
  // tft.setFont(Arial_24_Bold); 
  tft.setTextFont(1); 
  tft.setTextColor(TFT_WHITE); 
  tft.println("EFIS-DUINO v 1.0");
  tft.setCursor(150,7); 
  // tft.setFont(Arial_24_Bold); 
  tft.setTextFont(1); 
  tft.setTextColor(TFT_BLACK); 
  tft.println("EFIS-DUINO v 1.0");
  // tft.setFontDefault(); 
  // tft.setFontScale (0);
  tft.setTextSize(1);
  delay(28);
  tft.setTextColor(TFT_RED);
  if (result) tft.println("Carte micro SD        : OK");
  else {tft.setTextColor(TFT_GREEN); tft.println("Carte micro SD non disponible");}
  delay(28);
  tft.setTextColor(TFT_RED);
  tft.println("LCD                   : OK");
  delay(28);

// ***************************************************************************************** Initialisation du CAN bus  *****************************************************************************************************
  // CAN_Module_EFIS.begin();
  // CAN_Module_EFIS.setBaudRate(500000);
  // CAN_Module_EFIS.setMaxMB(16);
  // CAN_Module_EFIS.enableFIFO();
  // CAN_Module_EFIS.enableFIFOInterrupt();
  // CAN_Module_EFIS.onReceive(FIFO,canSniff);
  // CAN_Module_EFIS.mailboxStatus();
  // delay(1000);
  
// ********************************************************************************* Initialisation des deux capteurs de pression  *******************************************************************************************
  // if (AMS5915_050D.begin() < 0) tft.println("Erreur de communication avec le capteur AMS5915-50D");
  // else tft.println("Capteur AMS5915-50D   : OK");
  // delay(28);
  // if (AMS5915_1500A.begin() < 0) tft.println("Erreur de communication avec le capteur AMS5915-1500A");
  // else tft.println("Capteur AMS5915-1500A : OK");
  // delay(28);
  if (1 < 0) tft.println("Erreur de communication avec le capteur AMS5915-50D");
  else tft.println("Capteur AMS5915-50D   : OK");
  delay(28);
  if (1 < 0) tft.println("Erreur de communication avec le capteur AMS5915-1500A");
  else tft.println("Capteur AMS5915-1500A : OK");
  delay(28);
// ********************************************************************************* Initialisation des voies série  *******************************************************************************************
  Serial.begin(115200);
  // Serial5.begin(115200); 
  tft.println("Serial                : OK");
  delay(28);
// ********************************************************************************* Initialisations des timers *******************************************************************************************
  // TimerSendF.begin(SendF, 50ms); 
  // TimerStopReading.begin(StopReading); 
  // TimerDataLog.begin (setFlagLog, 1000000); 
  tft.println("Timers                : NOT OK");
  delay(28);
// *************************************************************************** Initialisation de l'encodeur rotatif ********************************************************************************************
    pinMode(Pin_Bouton, INPUT);
  attachInterrupt(digitalPinToInterrupt(Pin_Bouton), Bouton_ISR, RISING); 
  // encodeur.setInitConfig();  
  // encodeur.EncConfig.positionInitialValue = 0;
  // encodeur.EncConfig.filterCount = 7;           
  // encodeur.EncConfig.filterSamplePeriod = 255;
  // encodeur.init();
  tft.println("Encodeur              : NOT OK");
  delay(28);
// *************************************************************************** Initialisation du capteur de température intérieure ********************************************************************************************

// if ( !ds.search(addrDS18B20)) 
if (true) 
  {
    tft.println("Capteur de temperature intérieure non disponible");
    ds.reset_search();
    delay(28);
  }
  
 if (OneWire::crc8(addrDS18B20, 7) != addrDS18B20[7]) 
 tft.println("Le CRC de la ROM du capteur de température intérieure n'est pas valide !");
 else 
 tft.println("CRC ROM DS18B20       : OK");
 delay(28);


// ********************************************************************************* Initialisation des variables stockées en EEPROM *************************************************************  

 if (!((EEPROM.get(EFISeepromAdresse + 0, QNH) > 940) & (EEPROM.get(EFISeepromAdresse + 0, QNH) < 1050))) 
 {
  QNH=1013;                                                                                                
  magDev=0;                                                                                                
                                                                                                           
                                                                                                           
  YY = 2021; 
  correctionPression = -20;                                                                                
  luminosite = 8;                                                                                          
  accGmax = 1.0;                                                                                           
  accGmin = 1.0;                                                                                           
  
  EEPROM.put(EFISeepromAdresse + 0, QNH);
  EEPROM.put(EFISeepromAdresse + 2, magDev);
  EEPROM.put(EFISeepromAdresse + 8, YY);                                                                                     
  EEPROM.put(EFISeepromAdresse + 10,correctionPression);
  EEPROM.put(EFISeepromAdresse + 12,luminosite);
  EEPROM.put(EFISeepromAdresse + 14,accGmax);
  EEPROM.put(EFISeepromAdresse + 18,accGmin);
  
 } 
 else  
 {                                                                                                
  EEPROM.get(EFISeepromAdresse + 0,QNH);
  EEPROM.get(EFISeepromAdresse + 2,magDev);
  EEPROM.get(EFISeepromAdresse + 8,YY);                                                                                     
  EEPROM.get(EFISeepromAdresse + 10,correctionPression);
  EEPROM.get(EFISeepromAdresse + 12,luminosite);
  EEPROM.get(EFISeepromAdresse + 14,accGmax);
  EEPROM.get(EFISeepromAdresse + 18,accGmin);
 }
tft.println("EEPROM                : OK");
delay(28);
// ************************************************************************************************ Démarrage de l'horloge ************************************************************************************************************************************** 
  // zt = Teensy3Clock.get(); 
  // Teensy3Clock.set(zt); 
  // setSyncProvider(getTeensy3Time); 
  // setSyncInterval(900); 
  // TimerHorloge.begin (setflagAfficheHeure, 500000); 
  tft.println("SRTC                  :NOT OK");
  delay(28);
// **************************************************************************************** Initialisation et tri des options des menus  **********************************************************************************************************************
// Pour faciliter la gestion des options de menu, ces options sont affichées pendant le setup sur le terminal série, avant et après tri.
  uint8_t nb = NbOptionsMenu(); 
  Serial.print ("Nombre de lignes utiles du tableau : ");Serial.println(nb); Serial.println(); 
  Serial.println("**********Tableau avant tri et initialisation des index***********");
  Serial.println("Index------Label--------------Numero--Action---NbOptS--IndexOfOm--");
  for (byte n=1; n<=nb+1; n++)
  {
    sprintf(buf,"%2d",(n-1));
    Serial.print(buf); Serial.print(char(9)); 
    sprintf(buf,"%-20s",(menu[n-1].label));
    Serial.print (buf); Serial.print(char(9)); 
    sprintf (buf,"%4d",(menu[n-1].numero));
    Serial.print (buf); Serial.print (char(9));
    Serial.print(menu[n-1].action); Serial.print (char(9)); 
    sprintf(buf,"%2d",(menu[n-1].nbOptionsSoeurs));
    Serial.print(buf); Serial.print(char(9));       
    sprintf(buf,"%2d",(menu[n-1].indexOfOm));
    Serial.print(buf); Serial.println();   
  }
  triBulleTableau(); 
  initTable();       
  
  Serial.println();
  Serial.println("**********Tableau après tri et initialisation des index***********");
  Serial.println("Index------Label--------------Numero--Action---NbOptS--IndexOfOm--");  
  for (byte n=1; n<=nb+1; n++)
  {
    indexOptionMenuEnCours = n-1;
    sprintf(buf,"%2d",(n-1));
    Serial.print(buf); Serial.print(char(9)); 
    sprintf(buf,"%-20s",(menu[n-1].label));
    Serial.print (buf); Serial.print(char(9)); 
    sprintf (buf,"%4d",(menu[n-1].numero));
    Serial.print (buf); Serial.print (char(9));
    Serial.print(menu[n-1].action); Serial.print (char(9)); 
    sprintf(buf,"%2d",(menu[n-1].nbOptionsSoeurs));
    Serial.print(buf); Serial.print(char(9));       
    sprintf(buf,"%2d",(menu[n-1].indexOfOm));
    Serial.println(buf); 
  }
  tft.println("Menus                 : OK");
  delay(28);
// ****************************************************************************** Initialisation des filtres des capteurs de pression *************************************************************************************
  // AMS5915_1500A.readSensor('A');  
  // valPressionFiltreePrecedente = AMS5915_1500A.getPressure_Pa(); 
  // altitudeVarioOld = int((1-pow((((valPressionFiltreePrecedente + correctionPression*10)/100)/1013.25),0.190284))*145366.45);  
  // AMS5915_050D.readSensor('D');   // lecture des infos du capteur AMS 5915-0050-D pour le badin
  // valPressionDiffFiltreePrecedente = AMS5915_050D.getPressure_Pa();                                                                 
  tft.println("Filtres               :NOT OK");
  delay(28);

// *********************************************************************************************Démarrage du GPS uBlox *****************************************************************************************************

  // Wire2.begin();
  // Wire2.setClock(400000);
  delay(100);
  // if (myGNSS.begin(Wire2, 0x42) == false) 
  if (0 == false) 
    {
       tft.println ("GNSS U-Blox           : absent !");
      delay(5000);
    }
  else tft.println ("GNSS U-Blox           : OK");
  // myGNSS.setI2COutput(COM_TYPE_UBX); 
  // myGNSS.setUSBOutput(COM_TYPE_NMEA); 
  // myGNSS.setNavigationFrequency(20); 
  // myGNSS.setAutoPVT(true);           

// ********************************************************************************************** Démarrage de l'objet horizon ********************************************************************************************    
  delay(2000); 
  Horizon.begin(); 

// ******************************************************* Dessin de l'horloge sur le premier plan (layer 1) et initialisations de quelques variables liées à cette horloge **************************************************
  // tft.writeTo(L1);
   
  // tft.setTextColor(TFT_WHITE, TFT_PINK);
  // for (uint16_t i=0; i<360; i+=30) tft.drawLineAngle (Chx,Chy,i,Rh+1,TFT_WHITE);
  // tft.fillCircle (Chx, Chy, Rh-4,TFT_PINK);
  // // Longueurs des 3 aiguilles
  //  LaigH = Rh*2/3;
  //  LaigM = Rh*8/10;
  //  LaigS = Rh*9/10;
   
tft.setTextColor(TFT_WHITE, TFT_PINK);
for (uint16_t i = 0; i < 360; i += 30) {
    float angle = i * 3.14159265359 / 180.0; // Преобразование угла в радианы
    int x2 = Chx + int(cos(angle) * (Rh + 1));
    int y2 = Chy + int(sin(angle) * (Rh + 1));
    tft.drawLine(Chx, Chy, x2, y2, TFT_WHITE);
}
tft.fillCircle(Chx, Chy, Rh - 4, TFT_PINK);

// Длины 3 стрелок
LaigH = Rh * 2 / 3;
LaigM = Rh * 8 / 10;
LaigS = Rh * 9 / 10;

 //********************************************************************************* On arrive presqu'à la fin de SETUP *************************************************************************************************** 
  topHoraire = millis(); 
  topchronoVario = millis(); 
  topClignote = millis(); 

}// ******************************************************************************************************************FIN DU SETUP***************************************************************************************************

//----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
//                                                                                                                             LOOP
//---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
void loop()
{ 
// tft.brightness(luminosite*16-1); 
// tft.writeTo(L1); 

//  ***************************************************************************** Traitement de l'éventuelle disponibilité d'une trame Naveol **************************************************************************************

  if (dataReady) 
  {

    dataReady = false; 
    
    
    if (tabTrame[0] == 70 && tabTrame[1] == 18 && (*(float*)(tabTrame + 58)) == -1 && ndex==74) // Si la trame est valide, alors extraction des données et filtrage
    {
      // décodage de la trame (uniquement les éléments dont on a besoin)
       roll = *(float*)(tabTrame+ 2); 
       if (roll <2.8 && roll>-2.8) 
       {
          roll = filtrageRII (valRollFiltreePrecedente, roll, 0.1); 
       }   
       valRollFiltreePrecedente = roll; 
       
       pitch = (*(float*)(tabTrame+ 6))*(180/PI); 
       pitch = filtrageRII (valPitchFiltreePrecedente, pitch, 0.1); 
       valPitchFiltreePrecedente = pitch; 
       
       psi = (*(float*)(tabTrame+10));
       if (psi>PI) {flagPsiFiable = false; psi = psi-(4*PI); } 
       else flagPsiFiable = true; 
       psi = psi*(180/PI);
       if (psi<0)psi=360+psi; 
       
       AccY = *(float*)(tabTrame+46); 
       AccY = filtrageRII (valAccYFiltreePrecedente, AccY , 0.01); 
       valAccYFiltreePrecedente = AccY; 
       
       accG = *(float*)(tabTrame+50);
       accG = -1 * accG/9.80665; 
       if ((accG>accGmax) && (pressionDiff >= 380)) 
          {
            accGmax = accG; 
            EEPROM.put(EFISeepromAdresse + 14,accGmax); 
          }
       if ((accG<accGmin) && (pressionDiff >= 380))
          {
            accGmin = accG; 
            EEPROM.put(EFISeepromAdresse + 18,accGmin); 
          }
       accG = filtrageRII (valAccGFiltreePrecedente, accG, 0.1); 
       valAccGFiltreePrecedente = accG;
       
       offsetBille = (AccY * -1 * Rbille)/0.27; 
       if (offsetBille>Rbille*8) offsetBille = Rbille*8; 
       if (offsetBille<-Rbille*8) offsetBille = -Rbille*8;
       
       Vz = *(float*)(tabTrame+54);
       Vz = filtrageRII (valVzFiltreePrecedente, Vz, 0.01); 
       valVzFiltreePrecedente = Vz; 
       Vz = Vz *196.8504; 
       Vzint = (int(Vz+5)/10)*10; 
       
       trk =      (*(float*)(tabTrame+62))*(180/PI);
       if ((trk<0) && (valTrkFiltreePrecedente<90)) 
         {
            valTrkFiltreePrecedente = 360;
         }
       if ((trk>0) && (valTrkFiltreePrecedente>270)) 
         {
            valTrkFiltreePrecedente = 0;
         }
       if (trk<0)trk=360+trk; 
       trk = filtrageRII (valTrkFiltreePrecedente, trk, 0.1); 
       valTrkFiltreePrecedente = trk;  
       trkEntierArrondi = int(trk+0.5);
       nbSat =     *(float*)(tabTrame+70);
       
       if ((flagDataLogging) && (cptDataLog == divFreqDataLog)) 
         {
         
           //Serial.print(day(now()));Serial.print(";");Serial.print(month(now()));Serial.print(";");
           //Serial.print(hour(now()));Serial.print(";");Serial.print(minute(now()));Serial.print(";");Serial.print(second(now()));
           
           //Serial.print (";");  
           //                     Serial.print("'"); Serial.print(char(*    (tabTrame+  0)))  ; Serial.print("'");  //Caractère “F” majuscule indiquant le début de trame
           //Serial.print (";");  Serial.print( *        (tabTrame+  1))   ;   //Octet de valeur 18 indiquant que 18 flottants suivent
           //Serial.print (";");  
                                Serial.print((*(float*)(tabTrame+  2)),4);   //Angle de roulis en radians
           Serial.print (";");  Serial.print((*(float*)(tabTrame+  6)),4);   //Angle de tangage en radians
           Serial.print (";");  Serial.print((*(float*)(tabTrame+ 10)),4);   //Angle de lacet en radians (+ 4 * Pi lorsque invalide, + 0 lorsque valide)
           //Serial.print (";");  Serial.print((*(float*)(tabTrame+ 14)),3);   //Paramètre 0 du quaternion
           //Serial.print (";");  Serial.print((*(float*)(tabTrame+ 18)),3);   //Paramètre 1 du quaternion
           //Serial.print (";");  Serial.print((*(float*)(tabTrame+ 22)),3);   //Paramètre 2 du quaternion
           //Serial.print (";");  Serial.print((*(float*)(tabTrame+ 26)),3);   //Paramètre 3 du quaternion
           //Serial.print (";");  Serial.print((*(float*)(tabTrame+ 30)),3);   //Vitesse angulaire roulis en rad/s
           //Serial.print (";");  Serial.print((*(float*)(tabTrame+ 34)),3);   //Vitesse angulaire tangage en rad/s
           //Serial.print (";");  Serial.print((*(float*)(tabTrame+ 38)),3);   //Vitesse angulaire lacet en rad/s
           //Serial.print (";");  Serial.print((*(float*)(tabTrame+ 42)),3);   //Accélération X en m/s²
           Serial.print (";");  Serial.print((*(float*)(tabTrame+ 46)),4);   //Accélération Y en m/s²
           Serial.print (";");  Serial.print((*(float*)(tabTrame+ 50)),4);   //Accélération Z en m/s² 
           Serial.print (";");  Serial.print((*(float*)(tabTrame+ 54)),3);   //Vitesse verticale en m/s (valeur transmise positive vers le haut)
           Serial.print (";");  Serial.print((*(float*)(tabTrame+ 58)),0);   //Constante -1
           Serial.print (";");  Serial.print((*(float*)(tabTrame+ 62)),4);   //Cap GPS (TK, ou Track) en radians (c’est-à-dire la route)
           Serial.print (";");  Serial.print((*(float*)(tabTrame+ 66)),0);   //Index du menu interne à l’AHRS
           Serial.print (";");  Serial.print((*(float*)(tabTrame+ 70)),0);   //Nombre de satellites GPS utilisés
           //Serial.print(" Sat");
           //Serial.print (";");  Serial.print( *        (tabTrame+  74))   ; 
           Serial.print (";");  Serial.print(" / ");
           Serial.print (";");  Serial.print(roll,4);                        // roll filtré en radians
           Serial.print (";");  Serial.print(pitch,3);                       // pitch filtré en degrés
           Serial.print (";");  Serial.print(psi,1);                         // psi filtré en degrés
           Serial.print (";");  Serial.print(AccY,3);                        // AccY filtrée en m/s²
           Serial.print (";");  Serial.print(accG,3);                        // accG filtrée en G
           Serial.print (";");  Serial.print(Vz,3);                          // Vz filtrée en ft/minute
           Serial.print (";");  Serial.print(trk,3);                         // trk filtrée en degrés

           Serial.print (";");  Serial.print(pression,5);                    // pression absolue filtrée en Pa
           Serial.print (";");  Serial.print(pressionNonF,5);                // pression absolue non filtrée en Pa
           Serial.print (";");  Serial.print(pressionDiff,5);                // pression différentielle filtrée en Pa
           Serial.print (";");  Serial.print(pressionDiffNonF,5);            // pression différentielle non filtrée en Pa

           Serial.print (";");  Serial.print(oat,1);                         // Température intérieure
           Serial.print (";");  Serial.print(altitudeQNH);                   // Altitude QNH
           Serial.print (";");  Serial.print(vitesseIndiquee);               // Vitesse indiquée
           Serial.print (";");  Serial.print(vitessePropre);                 // Vitesse propre
           Serial.print (";");  Serial.print(Vzint);                         // Vzint (vario Naveol) en ft/min
           Serial.print (";");  Serial.print(vario,0);                       // Vario barométrique en ft/min
                       
           Serial.println();

           
         }

         cptDataLog++;
         if(cptDataLog == (divFreqDataLog+1) ) cptDataLog=1;
         
    }
  // Envoi des nouvelles données du Naveol à l'unité distante via le CAN bus
    // Assignation des ID et des longueurs des messages
    // msgRoll.id =  52;
    // msgPitch.id = 53;
    // msgRoll.len =  4;
    // msgPitch.len = 4;
    // Chargement des buffers d'envoi
    // for (uint8_t i = 0; i < 4; i++ ) 
    //  {
    //     msgRoll.buf[i] = ((byte*) &roll)[i];
    //     msgPitch.buf[i] = ((byte*) &pitch)[i];
    //  }
    // Envoi effectif
    // CAN_Module_EFIS.write(msgRoll);
    // CAN_Module_EFIS.write(msgPitch);   
  }

// ***************************************************************************** Lecture des données du GNSS si disponibles ******************************************************************************************
 
  // if (myGNSS.getPVT() && (myGNSS.getInvalidLlh() == false))
  if (false)
  {
 
      altitudeGNSS = 0.0; // Значение по умолчанию для высоты в метрах
      altitudeGNSS = altitudeGNSS * 3.28084 / 1000.0; // Преобразование в футы

      nbSatGNSS = 0; // Значение по умолчанию для числа видимых спутников
      heureGNSS = 0; // Значение по умолчанию для часов
      minuteGNSS = 0; // Значение по умолчанию для минут
      secondeGNSS = 0; // Значение по умолчанию для секунд
      millisecondeGNSS = 0; // Значение по умолчанию для миллисекунд

      verticalSpeedGNSS = 0.0; // Значение по умолчанию для вертикальной скорости в м/с
      verticalSpeedGNSS = filtrageRII(verticalSpeedGNSSOld, verticalSpeedGNSS, 0.1);
      verticalSpeedGNSSOld = verticalSpeedGNSS;
      verticalSpeedGNSS = verticalSpeedGNSS * 196.8504 / 1000.0; // Преобразование в футы/минуту
      if (verticalSpeedGNSS >= 0)
          verticalSpeedGNSS = (int(verticalSpeedGNSS + 5) / 10) * 10;
      else
          verticalSpeedGNSS = (int(verticalSpeedGNSS - 5) / 10) * 10;

      groundSpeedGNSS = 0.0; // Значение по умолчанию для горизонтальной скорости в м/с
      groundSpeedGNSS = filtrageRII(groundSpeedGNSSOld, groundSpeedGNSS, 0.1);
      groundSpeedGNSSOld = groundSpeedGNSS;
      groundSpeedGNSS = groundSpeedGNSS * 36 / 10000.0; // Преобразование в морские узлы

      trackGNSS = 0.0; // Значение по умолчанию для направления в 100000-ых градуса
      trackGNSS = trackGNSS / 100000.0; // Преобразование в градусы
  }
    
// ****************************************************************************************** Lecture des capteurs de pression ****************************************************************************************************
  // AMS5915_050D.readSensor('D');   
  // AMS5915_1500A.readSensor('A');  
  
  // pression = AMS5915_1500A.getPressure_Pa();   
  pression = 100;   
  pressionNonF = pression; 

  pression = filtrageRII (valPressionFiltreePrecedente, pression , 0.005); 
  //pression = 25000;
  valPressionFiltreePrecedente = pression;                                            


  // pressionDiff = AMS5915_050D.getPressure_Pa(); 
  pressionDiff = 50; 
  pressionDiffNonF = pressionDiff; 

  pressionDiff = filtrageRII (valPressionDiffFiltreePrecedente, pressionDiff, 0.005); 
  valPressionDiffFiltreePrecedente = pressionDiff;
  
// ****************************************************************************************** Lecture du capteur de température intérieure *********************************************************************************************  
if (flagDelaiAcquisitionDS18B20Termine == true)
    {
      ds.reset();
      ds.select(addrDS18B20);
      ds.write(0x44);        
      chronoDS18B20 = millis();  
      flagDelaiAcquisitionDS18B20Termine = false;
    } 
  
  if ((millis() - chronoDS18B20) > 1000)
    {
      ds.reset();
      ds.select(addrDS18B20);    
      ds.write(0xBE);         
      for (byte i = 0; i < 9; i++) dataDS18B20[i] = ds.read();
      temperatureDS18B20 = (float)((dataDS18B20[1] << 8) | dataDS18B20[0])/16.0; 
      flagDelaiAcquisitionDS18B20Termine = true;
    }

// ************************************************************************************ Calculs des affichages textuels *********************************************************************************************************

// --------- calcul des altitudes --------- (Les pressiosn sont exprimées en Pascals, et filtrées dès l'étape de lecture des capteurs)
  altitudeQNH = (1-pow((((pression + correctionPression*10)/100)/QNH),0.190284))*145366.45*correctionAltitude;
  //Serial.print ("altitudeQNH : "); Serial.print(altitudeQNH);
  altitudeQNH = int((altitudeQNH+5)/10.0)*10; 
  //Serial.print ("\t altitudeQNH arrondi : "); Serial.println(altitudeQNH);        
  altitudePression = (1-pow((((pression + correctionPression*10)/100)/1013.25),0.190284))*145366.45*correctionAltitude;
  altitudeVario = altitudePression; 
  altitudePression = int((altitudePression+5)/10.0)*10; 
  iat = temperatureDS18B20;

  float pressionSatH20 = 6.1078*pow(10,((7.5*oat)/(237.3+oat)))*100;
  float pressionPartielleH2O = pressionSatH20 * humiditeRelative/100; 
  float densiteAir = ((pression+ correctionPression*10)/(287.05*(oat+273.15))*(1-(0.378*pressionPartielleH2O/(pression+ correctionPression*10)))); 
  altitudeDensite =(44.3308-(42.2665*(pow(densiteAir,0.234969))))*1000*3.28084; 
  altitudeDensite = int((altitudeDensite+5)/10.0)*10; // arrondi à la dizaine la plus proche
  dewPoint = 243.12 * (log(humiditeRelative/100) + 17.62 * oat / (243.12 + oat)) / (17.62 - (log(humiditeRelative/100) + 17.62 * oat / (243.12 + oat))); 

// ----------- Calcul du taux de montée/descente à partir du capteur de pression statique -------------------
  if (millis()>= topchronoVario + tempsIntegrationVario)
   {
    vario =  (altitudeVario - altitudeVarioOld)*60*(1000/tempsIntegrationVario) ;
    topchronoVario = millis();
    altitudeVarioOld = altitudeVario;
    vario = filtrageRII (valVarioFiltreePrecedente, vario , 0.05);     
    valVarioFiltreePrecedente = vario;                                           
    if    (vario>=0) vario = (int(vario+5)/10)*10; // arrondi à la dizaine la plus proche (nombre positif)
    else             vario = (int(vario-5)/10)*10; // arrondi à la dizaine la plus proche (nombre négatif)
   }
   
// ----------------------- calcul des vitesses de l'avion --------------------------------------------
  if (pressionDiff > 4.5) // Cette pression diff corespond à environ 10 km/h. Cette limite évite de voir s'afficher à l'arrêt une vitesse qui oscille entre -2 et +2 km/h
   {
    vitesseIndiquee = int((sqrtf(21.159144 * pressionDiff)/facteurConversionVitesse)+0.5);                       
    vitessePropre = int((84.9528 * sqrtf(pressionDiff * (273.15+oat)/pression)/facteurConversionVitesse)+0.5);   
   }
  else {vitesseIndiquee = 0; vitessePropre = 0;}

// ----------------------- calcul direction et vitesse du vent --------------------------------------------
float GSE= sin(PI/180*trackGNSS)*groundSpeedGNSS;
float GSN= cos(PI/180*trackGNSS)*groundSpeedGNSS;
float TASE= sin(PI/180*(capMagnetique + magDev))*vitessePropre;
float TASN= cos(PI/180*(capMagnetique + magDev))*vitessePropre;
// Puis on calcule les différences
float deltaE = TASE-GSE;
float deltaN = TASN-GSN;
//Et enfin la vitesse et la direction du vent
windSpeed = sqrt(pow(deltaE, 2) + pow(deltaN, 2));
windDirection = atan2(deltaE,deltaN)*180/PI;

//**************************************************************************************** Mise à l'heure de la pendule et des chronos *********************************************************************************************
if (flagAfficheHeure)
{
  afficheHeure();
  flagAfficheHeure = false;
  time_t i; 
  if (flag_chrono1)
     {
       i = now()-t_chrono1;
       tft.setCursor (80,222); printDigits(hour(i)); tft.print(":"); printDigits(minute(i));tft.print(":"); printDigits(second(i));
     }
  if (flag_chrono2)
     {
       i= now()-t_chrono2;
       tft.setCursor (180,222); tft.setTextColor (TFT_GREEN, TFT_BLACK); printDigits(minute(i));tft.print(":"); printDigits(second(i));tft.setTextColor (TFT_WHITE, TFT_PINK);
     }
  if (flag_minuteur) 
     {
      i = t_minuteur - now();
      if (i>=0) {tft.setCursor (260,222); tft.setTextColor (TFT_WHITE, TFT_RED); printDigits(minute(i));tft.print(":"); printDigits(second(i));tft.setTextColor (TFT_WHITE, TFT_PINK);}
      else {flag_minuteur = false; tft.setCursor (260,222);tft.print ("     ");}
     }
}

// ************************************************************************************ Mise à jour des affichages textuels et du vario ****************************************************************************************
  afficheTextes();
  afficheVario();

//******************************************************************************** Data logging sur carte SD ou enregistreur externe *********************************************************************************************
if (millis() >= topClignote + periodeClignote) 
  {
    topClignote = millis(); 
    inverseVoyant = inverseVoyant * -1; 
    if(flagDataLogging)
      {
        if (inverseVoyant==1){tft.setCursor(330,220); tft.setTextColor(TFT_GREEN); tft.print("Logging..."); tft.setTextColor(TFT_WHITE);}
        else {tft.setCursor(330,220); tft.print("          ");}
      }
    else {tft.setCursor(330,220); tft.print("          ");}
  }

  if (flagLog==true) 
  {
    
    flagLog=false; 
    if(flagDataLogging) 
      { 
        /*
        fichier.print(day(now()));fichier.print(";");fichier.print(month(now()));fichier.print(";");
        fichier.print(hour(now()));fichier.print(";");fichier.print(minute(now()));fichier.print(";");fichier.print(second(now()));fichier.print(";");
        fichier.print(roll,0);fichier.print(";");
        fichier.print(pitch,0);fichier.print(";");
        fichier.print(trkEntierArrondi);fichier.print(";");
        fichier.print(oat,1);fichier.print(";");
        fichier.print(altitudeQNH);fichier.print(";");
        fichier.print(pressionDiff,4);fichier.print(";");
        fichier.print(pression,4);fichier.print(";");
        fichier.print(vitesseIndiquee);fichier.print(";");
        fichier.print(vitessePropre);fichier.print(";");
        fichier.print(Vzint);fichier.print(";");
        fichier.print(int(vario));fichier.println(); 
        */
      }
  }

//  ***************************************************************************** Traitement d'un clic sur le bouton de l'encodeur *******************************************************************************************

  if (Bouton_Clic == true) 
  {
    if (Menu_Ouvert == false) 
    { 
      Menu_Ouvert = true; 
      OuvreMenu(0); 
    }
    else  
          
    {
      if (SetVal == false)
       {
         switch (menu[indexOptionMenuEnCours].action)
          {
            case 'Q':                                                                                                               
               Menu_Ouvert = false;
               FermeMenu (); 
               break;
    
            case 'S':                                                                                                               
               OuvreMenu(menu[indexOptionMenuEnCours].indexOfOm);
               break;
    
            case 'B':                                                                                                                
               OuvreMenu(menu[indexOptionMenuEnCours].indexOfOm);;
               break;
    
            case 'P':                                                                                                                 
              SetVal = true; 
              // encodeur.write(0); 
              ValeurPositionEncodeurOld = ValeurPositionEncodeur;
              ValeurPositionEncodeurOldSetVal = 0; 
              Bouton_Clic = false; 
              switch (menu[indexOptionMenuEnCours].numero)
                {                                                  
                  case 101  : ptrGen = &QNH;                       OffsetEFISeepromAdresse =  0;   break; 
                  case 112  : ptrGen = &magDev;                    OffsetEFISeepromAdresse =  2;   break;
                  case 114  : ptrGen = &correctionPression;        OffsetEFISeepromAdresse = 10;   break;
                  case 115  : ptrGen = &luminosite;                OffsetEFISeepromAdresse = 12;   break;                              
                  case 123  : ptrGen = &dureeMinuteur;             OffsetEFISeepromAdresse = 1000; flag_minuteur = false; tft.setCursor (320,222);tft.print ("     "); break;
                  case 1131 : ptrGen = &YY;                        OffsetEFISeepromAdresse =  8;   break;
                  case 1132 : ptrGen = &MM;                        OffsetEFISeepromAdresse = 1000; break;
                  case 1133 : ptrGen = &DD;                        OffsetEFISeepromAdresse = 1000; break;
                  case 1134 : ptrGen = &HH;                        OffsetEFISeepromAdresse = 1000; break;
                  case 1135 : ptrGen = &Mn;                        OffsetEFISeepromAdresse = 1000; break;
                  case 1136 : ptrGen = &Sec;                       OffsetEFISeepromAdresse = 1000; break;
                  default   : break;
                }
              tft.fillRect((((menu[indexOptionMenuEnCours].numero)%10)*largeurCase), (ordonneeMenu-hauteurCase+3),largeurCase, hauteurCase-3, TFT_RED);
              tft.setCursor(((menu[indexOptionMenuEnCours].numero)%10)*largeurCase +(largeurCase/4), ordonneeMenu+4-hauteurCase);
              tft.setTextColor(TFT_WHITE, TFT_RED);
              // tft.setFont(Arial_10);
              tft.setTextFont(1); 
              tft.print(*ptrGen); 
              // tft.setFont(Arial_11);
              tft.setTextFont(1); 
              break; 
    
            case 'A' :                                                                                                                 
              switch (menu[indexOptionMenuEnCours].numero)
                {
                  case 102  : OuvreMenu(indexRetour(indexOptionMenuEnCours)); break;
                  case 103  : accGmax = 1.0; accGmin = 1.0; EEPROM.put(EFISeepromAdresse + 14,accGmax); EEPROM.put(EFISeepromAdresse + 18,accGmin); OuvreMenu(indexRetour(indexOptionMenuEnCours)); break; 

                  case 112  : OuvreMenu(indexRetour(indexOptionMenuEnCours)); break;
                   
                  case 121  : if (flag_chrono1==false) {t_chrono1=now();flag_chrono1=true;}            
                              else {tft.setCursor(80,222); tft.print("        "); flag_chrono1=false;} 
                              break;

                  case 122  : if (flag_chrono2==false) {t_chrono2=now();flag_chrono2=true;}              
                              else {tft.setCursor(220,222); tft.print("        "); flag_chrono2=false;}  
                              break;

                  case 1111 : facteurConversionVitesse = 1.0; OuvreMenu(indexRetour(indexOptionMenuEnCours));   break;   
                  case 1112 : facteurConversionVitesse = 1.852; OuvreMenu(indexRetour(indexOptionMenuEnCours)); break;   

                  case 1137 : setTime(HH,Mn,Sec,DD,MM,YY); break;    

                  case 1161 : if (SD.exists("log.txt")){SD.remove("log.txt");fichier = SD.open("log.txt");fichier.close();} flagDataLogging=false; break; 
                  case 1162 : fichier = SD.open("log.txt", FILE_WRITE); flagDataLogging=true; break; 
                  case 1163 : fichier.close(); flagDataLogging=false; break; 
                  case 1164 : fichier.close(); flagDataLogging=false; fichier = SD.open("log.txt");if (fichier) {while (fichier.available()) {Serial.write(fichier.read());}fichier.close();}break; 
                } 
              break; // break du case 'A'
              
          }
       } 
       
       else 
       {
         if (OffsetEFISeepromAdresse != 1000) EEPROM.put(EFISeepromAdresse + OffsetEFISeepromAdresse, *ptrGen); 
         if (dureeMinuteur <= 0){flag_minuteur = false; tft.setCursor (320,222);tft.print ("     ");}
         else if (flag_minuteur ==false){t_minuteur = now()+ dureeMinuteur*60; flag_minuteur = true;}
         SetVal = false; 
         ptrGen = NULL;  
         OuvreMenu(indexRetour(indexOptionMenuEnCours)); 
       }
    }
    Bouton_Clic = false; 
  }

// ************************************************************************* Polling de la position de l'encodeur à chaque passage dans Loop ************************************************************************************
  if (Menu_Ouvert == true) 
  {
    if (SetVal == false) 
     {
        // ValeurPositionEncodeur = encodeur.read() / 4; 
        ValeurPositionEncodeur = 0; 
        tft.setTextColor (TFT_WHITE, TFT_PINK);
        if (ValeurPositionEncodeur != ValeurPositionEncodeurOld) 
         { 
           
           uint16_t mini = menu[indexOptionMenuEnCours].numero; 
           mini = mini%10; 
           mini= indexOptionMenuEnCours - mini; 
           uint8_t maxi = mini + menu[mini].nbOptionsSoeurs -1; 
           if ((ValeurPositionEncodeur > maxi-mini) || (ValeurPositionEncodeur < 0)) 
             {
              //  encodeur.write(0);  
              //  ValeurPositionEncodeur = encodeur.read() / 4; 
               ValeurPositionEncodeur = 0; 
             }
           indexOptionMenuEnCours = ValeurPositionEncodeur + mini; 
           tft.drawRect ((ValeurPositionEncodeurOld * largeurCase), ordonneeMenu,largeurCase,hauteurCase, BckgrndMenus);   
           tft.drawRect ((ValeurPositionEncodeur * largeurCase), ordonneeMenu,largeurCase,hauteurCase, TFT_WHITE);     
         }
        ValeurPositionEncodeurOld = ValeurPositionEncodeur; 
     }
     else 
     {
    // ValeurPositionEncodeur = encodeur.read() / 4; 
    ValeurPositionEncodeur = 0; 
    if (ValeurPositionEncodeur != ValeurPositionEncodeurOldSetVal) 
      {
        // int8_t sens = encodeur.getHoldDifference(); 
        int8_t sens = 0; 
        *ptrGen = *ptrGen + sens; 
        verificationValeurs();
        tft.fillRect((((menu[indexOptionMenuEnCours].numero)%10)*largeurCase), (ordonneeMenu-hauteurCase+3),largeurCase, hauteurCase-3, TFT_RED);
        tft.setCursor(((menu[indexOptionMenuEnCours].numero)%10)*largeurCase +(largeurCase/4), ordonneeMenu+4-hauteurCase);
        tft.setTextColor(TFT_WHITE, TFT_RED);
        // tft.setFont(Arial_10);
        tft.setTextFont(1);
        tft.print(*ptrGen);
        // tft.setFont(Arial_11); 
        tft.setTextFont(1);       
      }
    ValeurPositionEncodeurOldSetVal = ValeurPositionEncodeur; 
     }
  }

// ************************************************************************************ Mise à jour de l'horizon artificiel****************************************************************************************
  
  Horizon.dessine(roll, pitch, psi, offsetBille, Menu_Ouvert);
  
// ************************************************************************* Mesure de la durée de la boucle principale Loop ************************************************************************************
if (nombreBoucleLoop >=Ndb)
  {
    dureeNdbBoucles = millis() - topHoraire;
    // tft.setFontDefault(); 
    // tft.setFontScale (0);
    tft.setTextFont(1);  
    tft.setTextSize(1);
    tft.setTextColor (TFT_WHITE, TFT_PINK);
    tft.setCursor(210,195); 
    tft.print("Loop : "); 
    tft.print (float(dureeNdbBoucles/Ndb),2); 
    tft.print(" ms");
    topHoraire = millis();
    nombreBoucleLoop = 0;
  }
  else
  {
    nombreBoucleLoop++;
  }  
}


//***************************************************************************************************** Fin de la boucle infinie Loop ***********************************************************************************************

//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
//                                                                                                   ISR du CAN Bus
//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// void canSniff(const CAN_message_t &msg) 
// {
//   switch (msg.id)
//       {
//         case 42:
//             {
//               oat = *(float*)(msg.buf);
//               break;
//             }
            
//         case 43:
//             { 
//               humiditeRelative = *(float*)(msg.buf);
//               break;
//             }

//         case 44:
//             { 
//               capMagnetique = *(uint32_t*)(msg.buf); 
//               break;
//             }    
         
//         default:
//               break;
//       }
// }

//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
//                                                                                          Fonctions de mise à jour des affichages textuels et du vario
//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

void afficheTextes()
{
// ------------- Affichage direction et vitesse du vent -------------------
  tft.setCursor (50,30); sprintf (buf,"%3d",int(windDirection+0.5)); tft.print (buf); tft.print("");
  tft.setCursor (50,45); sprintf (buf,"%3d",int(windSpeed+0.5));tft.print (buf); if(facteurConversionVitesse==1.0)tft.print (" Km/h"); else tft.print (" Kts ");

//sprintf (buf,"%5d",int(altitudePression)); tft.setCursor (415,157); tft.print (buf);
  
// ------------- Affichage IAT ---------------------------
  tft.setCursor (105,156); printDecimalInfCent(iat,1);

// ------------- Affichage OAT ---------------------------
  tft.setCursor (105,171); printDecimalInfCent(oat,1);  

// --------------Affichage Humidité ---------------------
  tft.setCursor (105,186); printDecimalInfCent(humiditeRelative,1); 

// --------------Affichage DewPoint ---------------------
  tft.setCursor (105,201); printDecimalInfCent(dewPoint,1); 

// ------------- Affichage Vitesse indiquée ---------------------------
  // tft.setFontScale (1); tft.setTextColor (TFT_WHITE, TFT_BLACK);
  tft.setTextFont(1); 
  tft.setTextColor(TFT_WHITE, TFT_BLACK); 
  tft.setTextSize(1);
  sprintf (buf,"%3d",(vitesseIndiquee)); tft.setCursor (7,118);  tft.print (buf); 
  tft.setCursor (65,127); 
  // tft.setFontScale (0); tft.setTextColor (TFT_WHITE, TFT_PINK); 
  tft.setTextFont(1); 
  tft.setTextColor(TFT_WHITE, TFT_PINK); 
  tft.setTextSize(1);
  if(facteurConversionVitesse==1.0)tft.print ("Km/h"); else tft.print ("Kts ");

// ------------- Affichage Route GPS ---------------------------
  if (flagPsiFiable == false) tft.setTextColor (TFT_WHITE, TFT_RED); 
  else tft.setTextColor (TFT_WHITE, TFT_BLACK);     
  // tft.setFontScale (1,0); 
  tft.setTextFont(1); 
  // tft.setTextColor(TFT_WHITE, TFT_PINK); 
  tft.setTextSize(1);  
  sprintf (buf,"%3d",trkEntierArrondi); tft.setCursor (276,3); tft.print (buf); 

// --------------Affichage cap magnétique ---------------------
  tft.setTextColor (TFT_WHITE, TFT_BLACK);  
  tft.setCursor (156,3);  sprintf (buf,"%3d",capMagnetique); tft.print(buf);    
   
// ------------- Affichage Vitesse propre (TAS) ---------------------------  
  // tft.setFontScale (0); 
  tft.setTextFont(1); 
  tft.setTextSize(1);
  tft.setTextColor (TFT_WHITE, TFT_PINK);
  sprintf (buf,"%3d",vitessePropre); tft.setCursor (30,97); tft.print (buf); tft.setCursor (65,97); if(facteurConversionVitesse==1.0)tft.print ("Km/h"); else tft.print ("Kts ");

// --------------- Affichage vitesse UBLOX ----------------------------------
  sprintf (buf,"%3d",int(groundSpeedGNSS/facteurConversionVitesse)); tft.setCursor (30,82); tft.print (buf); tft.setCursor (65,82); if(facteurConversionVitesse==1.0)tft.print ("Km/h"); else tft.print ("Kts "); 

  // ------------- Affichage Vz Naveol, Vario barométrique, et vario GNSS Ublox ---------------------------
  tft.setCursor(363,3); tft.print("Vert. speed");
  tft.setCursor (363,33);   tft.print("AHRS :"); sprintf (buf,"%5d",Vzint); tft.print(buf);
  tft.setCursor (363,48);   tft.print("BARO :"); sprintf (buf,"%5d",int(vario)); tft.print(buf);
  tft.setCursor (363,18);   tft.print("GNSS :"); sprintf (buf,"%5d",int(verticalSpeedGNSS)); tft.print(buf);
  

// ------------- Affichage nombre de satellites ---------------------------
  tft.setCursor (210,180);   tft.print("nSat : "); sprintf (buf,"%2d",int(nbSat)); tft.print(buf);tft.print("/");
  sprintf (buf,"%2d",int(nbSatGNSS)); tft.print(buf);
  
// ------------ Affichage trackGNSS -------------------------
tft.setCursor (2,0);   tft.print("trk gnss : "); tft.print(trackGNSS,0);

// ------------- Affichage QNH ---------------------------  
  
  tft.setCursor (420,206); sprintf (buf,"%4d",QNH); tft.print(buf);

 // ------------- Affichage des 3 altitudes --------------------------- 
  // tft.setFontScale (0,1);
  tft.setTextFont(1); 
  tft.setTextSize(1);
  tft.setTextColor (TFT_WHITE, TFT_BLACK);
  sprintf (buf,"%5d",int(altitudeQNH)); tft.setCursor (415,118);  tft.print (buf);
  // tft.setFontScale (0);
  tft.setTextFont(1); 
  tft.setTextSize(1);
  tft.setTextColor (TFT_WHITE, TFT_PINK);
  sprintf (buf,"%5d",int(altitudePression)); tft.setCursor (415,157); tft.print (buf);
  sprintf (buf,"%5d",int(altitudeDensite)); tft.setCursor (415,172); tft.print (buf);
  sprintf (buf,"%5d",int(altitudeGNSS)); tft.setCursor (415,187); tft.print (buf);
  
// ------------- Affichage G, Gmin et Gmax ---------------------------
  tft.setCursor (363,82); tft.print ("G    :"); printDecimalInfDix(accG,1);
  tft.setCursor (363,67); tft.print ("Gmax :"); printDecimalInfDix(accGmax,1);
  tft.setCursor (363,97); tft.print ("Gmin :"); printDecimalInfDix(accGmin,1);
  
}

void afficheVario()
{
// affichage en premier des barres
  int8_t hauteurBarre;
  if (abs(vario)> 2000) hauteurBarre = 116;
  else hauteurBarre = abs(int8_t((vario*58)/1000));
  if (vario < 0) 
  {
    tft.fillRect(461,20,9,116, TFT_PINK);
    tft.fillRect(461,136,9,hauteurBarre,TFT_PURPLE);
    tft.fillRect(461,136+hauteurBarre,9,252-136-hauteurBarre,TFT_PINK);
    tft.drawRect(460,136,10,hauteurBarre+1,TFT_WHITE);
  }
  else
  {
    tft.fillRect(461,20,9,116-hauteurBarre,TFT_PINK);
    tft.fillRect(461,136-hauteurBarre,9,hauteurBarre,0b1111111001000000);
    tft.fillRect(461,136,9,116,TFT_PINK);
    tft.drawRect(460,136-hauteurBarre,10,hauteurBarre+1,TFT_WHITE);
  }

// puis affichage des graduations
  for (uint16_t i=49; i<=272; i=i+58) tft.drawFastHLine (460,i,6, TFT_WHITE);
  
  for (uint16_t i=20; i<=272; i=i+58) {tft.drawFastHLine (460,i,10, TFT_WHITE);}
}

//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
//                                                                                          Fonctions liées au boîtier Naveol
//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

// void SendF() 
// { 
  // Serial5.clear(); 
  // ndex=0;          
  // Serial5.print('F'); 
  // TimerStopReading.trigger(25ms);                                       
// }

void StopReading()  
{
  dataReady = true;
}

void serialEvent5() 
{
  // tabTrame[ndex] = Serial5.read();
  ndex++;
}

//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
//                                                                                          Fonctions liées à l'encodeur rotatif et aux menus 
//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

void verificationValeurs() 
{
  if (luminosite>16) luminosite = 16;       if (luminosite<1) luminosite = 1;
  if (dureeMinuteur>60) dureeMinuteur = 60; if (dureeMinuteur<0) dureeMinuteur = 0;
}

// ************************************************************************* Routine d'interruption liée au traitement d'une pression sur le bouton *******************************************************************************
void Bouton_ISR() 
{
  
  if ((millis() - lastButtonPress) >= debounceTime)
  {
    lastButtonPress = millis ();
    Bouton_Clic = true;
  }
}
// ***************************************************************************************** Procédure d'ouverture du menu ******************************************************************************************************

void OuvreMenu (uint8_t ndx) 
{
  indexOptionMenuEnCours = ndx; 
  tft.fillRect (abscisseMenu, ordonneeMenu-hauteurCase+2, tft.width()-abscisseMenu, hauteurCase*2-2, BckgrndMenus); 
  tft.drawLine(abscisseMenu+1, ordonneeMenu-hauteurCase+2, tft.width()-abscisseMenu, ordonneeMenu-hauteurCase+2, TFT_WHITE);
  // encodeur.write(0); 
  ValeurPositionEncodeur = 0; 
  ValeurPositionEncodeurOld = 0; 
  // tft.setFont(Arial_11); 
  tft.setTextFont(1); 
  tft.setTextSize(1);
  tft.setTextColor (TFT_WHITE, BckgrndMenus);
  tft.setCursor(abscisseMenu+1, ordonneeMenu+5-hauteurCase); 
  if(indexOptionMenuEnCours==0)
  {
    tft.print("MENU GENERAL");
  }
  else
  {
    int8_t i=0;
    tft.setTextColor (TFT_WHITE, BckgrndMenus);
    while(menu[indexOptionMenuEnCours+i].numero != (menu[indexOptionMenuEnCours].numero)/10) i--; 
    tft.print(menu[indexOptionMenuEnCours+i].label);
  }
      for (byte n = ndx; n < ndx + menu[ndx].nbOptionsSoeurs; n++)  
      {
        tft.setCursor ((largeurCase*(n-ndx))+2, ordonneeMenu+3);
        tft.print(menu[n].label);
      }  
   tft.drawRect ((ValeurPositionEncodeur*largeurCase),ordonneeMenu, largeurCase,hauteurCase, 0xFFFF); 
}
// ********************************************************************************************* Procédure de fermeture du menu **************************************************************************************************
void FermeMenu()
{
  tft.fillRect (abscisseMenu, ordonneeMenu-hauteurCase, tft.width()-abscisseMenu, hauteurCase*2, TFT_PINK); 
  Horizon.redessine(); 
  // tft.setFontDefault(); 
  // tft.setFontScale (0);
  tft.setTextFont(1); 
  tft.setTextSize(1);
}
// ************************************************ Cette fonction retourne, à partir d'un index donné quelconque, l'index de la première option soeur (numéro terminant par un zéro) **********************************************
uint8_t indexRetour(uint8_t indexQuelconque)
{
  int8_t i = 0;
  while(menu[indexQuelconque+i].numero > ((menu[indexQuelconque].numero)/10)*10) i--;
  return (indexQuelconque+i);
}

//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
//                                                                                Fonctions utilisées lors de l'initialisation des options des menus
//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

void initTable() // Cette fonction calcule et initialise les champs "nbOptionsSoeurs" et "indexOfOm" de chaque enregistrement, lors du setup.
{
  for (uint8_t i=0; i<NbOptionsMenu(); i++)
  {
    uint16_t n = menu[i].numero;
    int8_t j = 0;
    if ((n/10)*10 == n) 
    {
       while (menu[i+j].numero < n+10) j++; 
       for (uint8_t k = 0; k<j; k++) menu[i+k].nbOptionsSoeurs = j; 
       j = 0;
    }
    
    if(menu[i].action == 'S') 
    {
       while(menu[i+j].numero != n*10) j++; 
       menu[i].indexOfOm = i+j;
       j=0;
    }
    
    if(menu[i].action == 'B')
    {
       while(menu[i+j].numero != (n/100)*10) j--; 
       menu[i].indexOfOm = i+j;
    }
  }
}

void triBulleTableau() 
{
  char * texte; uint32_t nombre; char lettre; 
  for (byte i = NbOptionsMenu()-1; i>0; i--)
  {
    for (byte j = 1; j <= i; j++)
    {
      if (menu[j-1].numero > menu[j].numero) 
      {
        texte =   menu[j].label;             
        nombre = menu[j].numero;
        lettre =  menu[j].action;

        menu[j].label        = menu[j-1].label;      
        menu[j].numero       = menu[j-1].numero;
        menu[j].action       = menu[j-1].action;

        menu[j-1].label        = texte;
        menu[j-1].numero       = nombre;
        menu[j-1].action       = lettre;
      }
    }
  }
}

uint8_t NbOptionsMenu()
{
  uint8_t i=0;
  while (menu[i].numero != 1000000) { i=i+1;}
  return i; 
}


//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
//                                                                                Fonction utilisée pour le filtrage des données brutes issues des capteurs
//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

// Fonction de filtre à réponse impulsionnelle infinie d'ordre 1 : RIIordre1
float filtrageRII (float valeurFiltreePrecedente, float valeurCourante , float coeffFiltrageRII)
{
  return valeurFiltreePrecedente  * (1-coeffFiltrageRII) + valeurCourante * coeffFiltrageRII ;
}

//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
//                                                                                       Fonctions liées à la gestion et à l'affichage de l'heure sur l'écran
//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// time_t getTeensy3Time() 
// {
  // return Teensy3Clock.get();
// }
  
void afficheHeure() 
{
 // affiche l'heure digitale sur l'horloge
  //  tft.setFontDefault(); 
  //  tft.setFontScale(0);
   tft.setTextFont(1); 
   tft.setTextSize(1);
   tft.setCursor (Chx-31,Chy+30); 
   printDigits(hour());
   tft.print(":");
   printDigits(minute()); 
   tft.print(":");
   printDigits(second());   
 // On calcule les angles des 3 aiguilles
   zt = now();
   hs = zt % nbs12h; 
   ms = zt %3600;   
   ss = zt % 60;    
   angleH = 360*hs/nbs12h;
   angleM = 360*ms/3600;
   angleS = 360*ss/60;
 // On efface les anciennes aiguilles
//    tft.drawLineAngle (Chx,Chy,angleHold,LaigH,TFT_PINK);
//    tft.drawLineAngle (Chx,Chy,angleMold,LaigM,TFT_PINK);
//    tft.drawLineAngle (Chx,Chy,angleSold,LaigS,TFT_PINK);
//  // On affiche les nouvelles aiguilles
//    tft.drawLineAngle (Chx,Chy,angleH,LaigH,0xFFFF); 
//    tft.drawLineAngle (Chx,Chy,angleM,LaigM,0xFFFF); 
//    tft.drawLineAngle (Chx,Chy,angleS,LaigS,TFT_GREEN);

// Рисование старых иголок в цвете TFT_PINK
int xH_old = Chx + cos(angleHold) * LaigH;
int yH_old = Chy + sin(angleHold) * LaigH;
tft.drawLine(Chx, Chy, xH_old, yH_old, TFT_PINK);

int xM_old = Chx + cos(angleMold) * LaigM;
int yM_old = Chy + sin(angleMold) * LaigM;
tft.drawLine(Chx, Chy, xM_old, yM_old, TFT_PINK);

int xS_old = Chx + cos(angleSold) * LaigS;
int yS_old = Chy + sin(angleSold) * LaigS;
tft.drawLine(Chx, Chy, xS_old, yS_old, TFT_PINK);

// Рисование новых иголок в цвете 0xFFFF (белый)
int xH_new = Chx + cos(angleH) * LaigH;
int yH_new = Chy + sin(angleH) * LaigH;
tft.drawLine(Chx, Chy, xH_new, yH_new, 0xFFFF);

int xM_new = Chx + cos(angleM) * LaigM;
int yM_new = Chy + sin(angleM) * LaigM;
tft.drawLine(Chx, Chy, xM_new, yM_new, 0xFFFF);

// Рисование секундной стрелки в цвете TFT_GREEN
int xS_new = Chx + cos(angleS) * LaigS;
int yS_new = Chy + sin(angleS) * LaigS;
tft.drawLine(Chx, Chy, xS_new, yS_new, TFT_GREEN);

  // Sans oublier le petit cercle au centre
   tft.fillCircle (Chx,Chy,3,TFT_RED); 
  //  Et on mémorise les positions des aiguilles
   angleHold=angleH;
   angleMold=angleM;
   angleSold=angleS;    
}

void printDigits(int digits) 
{
  if(digits < 10)
    tft.print('0');
  tft.print(digits);
}

void setflagAfficheHeure() 
{
  flagAfficheHeure = true;
}

void setFlagLog() 
{
  flagLog = true;
}

//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
//                                                                                Fonctions utilitaires diverses
//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

void printDecimalInfCent(float nombre, byte nbDec) 
{
  if (nombre>=10) {tft.print(" "); tft.print(nombre,nbDec); return;} 
  if (nombre>=0)  {tft.print("  ");tft.print(nombre,nbDec); return;}
  if (nombre>-10) {tft.print(" "); tft.print(nombre,nbDec); return;}
                                   tft.print(nombre,nbDec);          
}

void printDecimalInfDix(float nombre, byte nbDec) 
{
  if (nombre>=0) {tft.print(" "); tft.print(nombre,nbDec);} 
  else tft.print(nombre,nbDec);
}

void sablier(uint8_t x, uint8_t y) 
{
 tft.fillRect (x-20,y-26,40,2,TFT_WHITE);
 tft.fillRect (x-20,y+25,40,2,TFT_WHITE); 
 tft.drawLine (x-18,y-21,x-18,y+20,TFT_WHITE);
 tft.drawLine (x+18,y-21,x+18,y+20, TFT_WHITE);
//  tft.drawArc (x,y-14,14,1,195,270,TFT_WHITE, TFT_WHITE);
// int cx = x;          // Центр дуги по оси X
// int cy = y - 14;     // Центр дуги по оси Y
// int radius = 14;     // Радиус дуги
// int startAngle = 195; // Начальный угол дуги в градусах
// int endAngle = 270;   // Конечный угол дуги в градусах
// int color = TFT_WHITE; // Цвет дуги

// // Рисование дуги
// for (int angle = startAngle; angle <= endAngle; angle++) {
//     float radians = angle * 0.0174532925; // Перевод угла в радианы
//     int x1 = cx + int(radius * cos(radians));
//     int y1 = cy + int(radius * sin(radians));
//     tft.drawPixel(x1, y1, color);
// }
//  tft.drawArc (x,y-14,14,1,90,165,TFT_WHITE, TFT_WHITE);
//  tft.drawArc (x,y+14,14,1,270,345,TFT_WHITE, TFT_WHITE);
//  tft.drawArc (x,y+14,14,1,15,90,TFT_WHITE, TFT_WHITE);
//  tft.drawArc (x,y+9,32,1,335,25,TFT_WHITE, TFT_WHITE);
//  tft.drawArc (x,y-9,32,1,155,205,TFT_WHITE, TFT_WHITE);
 tft.drawLine(x-13, y-14, x-13, y-19,TFT_WHITE);
 tft.drawLine(x-13, y+14, x-13, y+19,TFT_WHITE);
 tft.drawLine(x+13, y-14, x+13, y-19,TFT_WHITE);
 tft.drawLine(x+13, y+14, x+13, y+19,TFT_WHITE);
//  tft.drawArc (x,y-14,12,13,90,270,TFT_WHITE, TFT_WHITE);
 tft.drawLine (x-1,y-14,x+1,y-14,TFT_WHITE);
 tft.fillRect (x-1,y-3,3,12,TFT_WHITE);
 tft.fillTriangle(x,y+7,x+8,y+20,x-8,y+20,TFT_WHITE);
}

void photo() // affiche une photo lors de la séquence d'introduction après la mise sous tension
{
  fichier = SD.open("FPBCF.out", FILE_READ);
if(fichier)
  {
    unsigned char LSB, MSB;
    uint16_t couleur;
    for (uint16_t j=1; j<=hauteurPhoto; j++)
      {
        for (uint16_t i = 1; i<=largeurPhoto; i++)
          {
            LSB = fichier.read();
            MSB = fichier.read();
            couleur = LSB + (MSB<<8);
            tft.drawPixel(i,j,couleur);
          }
      }
    
    fichier.close();
  } 
else {tft.println("Erreur a l'ouverture du fichier FPBCF.out"); delay(2000);}
}