/* Programme contenant les routines de base pour développer des programmes sur la platine
   d'expérimentation "UNIVERSELLE" pour la carte NANO ÉTENDUE avec son afficheur OLED intégré.
   Ce noyau conditionne les Entrées/Sorties pour pouvoir exploiter directement :
   • L'afficheur OLED 32 x 128 intégré.
   • Le BUZZER actif.
   • Un bouton poussoir sur A1 et un bouton poussoir sur A3. (Placé entre Ai et GND)
   • Un inverseur ou un B.P. sur A7.
   • Fait clignoter la LED Arduino pour témoigner du déroulement de la boucle de base.
   • Un dialogue via le Moniteur de l'IDE initialisé à 9600 baud.
   Si en développement un dialogue via la ligne USB du Moniteur de l'IDE n'est pas envisagé, toutes les
   procédures relatives à ce dernier sont regroupées pour pouvoir les retirer facilement.
   Si en développement un chronomètre ou une horloge ne sont pas prévus, toutes les
   procédures relatives au chronomètre sont regroupées pour pouvoir le retirer facilement.

NOTE : Dans la boucle de base, c'est le rafraichissement de l'écran OLED qui pénalise le plus sa
       rapidité de traitement. L'écran n'est donc modifié que si une commande quelconque a changé
       le contexte : Ajustement du potentiomètre, Changement d'état de A6 ou A7 pour les "puces",
       clic sur l'un des deux BP ou commande du Moniteur de l'IDE.

BRANCHEMENTS à effectuer :
--------------------------
   • Ponder D9 à la LED verte de diamètre 3mm.
   • Ponter D10 à la LED verte de diamètre 5mm.
   • Enlever le strap et ponter D5 sur la LED rouge de diamètre 3mm.
   • Ponter la sortie du potentiomètre sur A0.
   • A4 est réservé par la bibliothèque U8G2lib.h pour SDA.
   • A5 est réservé par la bibliothèque U8G2lib.h pour SLC.
   • A6 sert à piloter la "puce du haut" en fonction de son état.
   • A6 sert à piloter la "puce du haut" en fonction de son état.
   • A7 va sur un inverseur dont le repos est sur GND et le travail sur +5Vcc.
        ou 10khom au +5Vcc et GND sur un BP simple.
   
Compilateur v1.7.9.             PGM               RAM dynamique
Programme NOYAU :         19794 Octets (64%)    1321 Octets (64%) */

//------------------------ Constantes du programme --------------------------
#define Version "6/9/2025" // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@0@@@@@@@@@
#define Lgr_TEXTE 22 // Longueur maximale d'un texte. @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@0@@@@@@@@@@
#define Taille_PGM " PGM 19794 (64%) Dynamique 1321 (64%)" // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@0@@@@@@@@
#define Delai_voulu 75 // Demi période du clignottement en mS.

//------------------- Définition des broches binaires -----------------------
#define Orange_5mm 2 // La LED est pilotée par D2.
#define Sortie_D3 3 // // État alterné rapidement.
#define BUZZER 4 // Va sur le bruiteur actif.
#define Rouge_3mm 5 // La LED est pilotée par D5.
#define Rouge 6  // LED tricolore.
#define Vert 7 // LED tricolore.
#define Bleu 8 // LED tricolore.
#define Verte_3mm 9 // La LED est pilotée par D9.
#define Verte_5mm 10 // La LED est pilotée par D10.
#define Pilotage_TIP122 11 // D11 Pilote le Darlington de puissance par PWM.
#define LED_Arduino 13
//-------------------- Définition des broches analogiques -------------------
#define MESURE_U 14 // Tension vatiable sur A0.
#define BPA1 15 // Bouton poussoir sur A1.
#define BPA3 17 // Bouton poussoir sur A3.
#define BPA7 21 // Inverseur sur A7. (L'inverseur évite d'avoir à forcer l'état "1".)
//---------------------- Déclarations pour le matériel ----------------------
#include <U8g2lib.h>  // L'afficheur est sur A4 et A5 pour SDA et SCL.
#include <SPI.h> // Impératif pour utiliser la ligne SPI.
#include <Wire.h>   // Impératif pour l'afficheur.
U8G2_SSD1306_128X32_UNIVISION_F_HW_I2C u8g2(U8G2_R0);

//------------------------- Variables du programme --------------------------
boolean Ancien_Puce1, Ancien_Puce2, Puce1, Puce2;
byte Evenement, Frequence, Num_couleur, Num_isolee;
int Compteur;
float SAV_Action, SAV_U, U; // Conversion en volts de la tension mesurée sur A0.
unsigned long TENSION;
unsigned long Temps_Ecoule_pour_clignoter = 0;
unsigned long Temps_Ecoule_pour_le_BP = 0;
unsigned long Temps_Ecoule_pour_le_chronometre = 0;

//--------- Variables si commandes par Moniteur de l'IDE conservées ---------
#define CR 13 // Code du Retour Chariot qui vaut 13 codé en ASCII sous forme décimale.
#define LF 10 // Code du passage à la ligne qui vaut 10 codé en ASCII sous forme décimale.
boolean Encadrement, Noir, Silence;
char CAR_Moniteur;
byte Index;
char TEXTE[Lgr_TEXTE];


//========================= Procédures de servitude ============================
void BIP() {
  if (!Silence) {digitalWrite(BUZZER, HIGH); delay(100); digitalWrite(BUZZER, LOW);}}

void Inverser_LED_Arduino() {digitalWrite(LED_Arduino,!digitalRead(LED_Arduino));}

int SRAM_LIBRE() { // Fonction qui retourne la taille de SRAM disponible.
    extern int __heap_start, *__brkval; // Déclaration des deux pointeurs dédiés.
    byte BIDON; // Dernière variable allouée, donc occupe le "haut" du TAS.
    if (__brkval == 0) {return (int) &BIDON -(int) &__heap_start;}
    else {return (int) &BIDON -(int) __brkval;}}

void Mesure_la_tension() { TENSION = 0;  // Mesure analogique CAN : [0 à 1023].
  for (int I = 0; I < 50; I++) {TENSION = TENSION + analogRead(MESURE_U);}
  TENSION = int(TENSION / 50); TENSION = map(TENSION, 0, 1023, 0, 50); U = float(TENSION/10.0);}

void Debut_de_ligne() {Serial.print(' '); Serial.print(char(149)); Serial.print(F(" : "));}

void Souligner(byte Nb) {
  Serial.print(' '); for (byte I = 0; I < Nb; I++) Serial.print('-'); Serial.println();}

void Liste_les_commandes() {Compteur++;
   Serial.println(); Serial.println(F(" Rappel des commandes :")); Souligner(22); Debut_de_ligne();
   Serial.println(F("Chaque clic du BP sur A1 fait changer de couleur la LED triple."));
   Debut_de_ligne(); Serial.print(F("Chaque clic du BP sur A3 fait allumer une LED isol"));
   Serial.print(char(233)); Serial.print(F("e diff")); Serial.print(char(233));
   Serial.println(F("rente.")); Debut_de_ligne(); Serial.print(F("Une seule LED sera active "));
   Serial.print(char(224)); Serial.println(F(" la fois.")); Debut_de_ligne();
   Serial.print(F("Chaque commande incr")); Serial.print(char(233)); 
   Serial.println(F("mente le Compteur.")); Debut_de_ligne(); Serial.print('D'); Serial.print(char(232));
   Serial.print(F("s le premier clic sur un BP un chronom")); Serial.print(char(232));
   Serial.println(F("tre remplace la version.")); Debut_de_ligne();
   Serial.println(F("Chaque changement d'ETAT sur A6 ou A7 modifie les cercles.")); Debut_de_ligne();
   Serial.println(F("Tout clic sur l'inverseur de A7 provoque l'extinction des LEDs"));
   Serial.print(F("     et la Remise ")); Serial.print(char(224)); Serial.print(F(" z"));
   Serial.print(char(233)); Serial.println(F("ro du CRONOMETRE.")); Souligner(69);
   Serial.println(F(" a ou A : Alarme. (BIP)"));
   Serial.print(F(" c ou C : Remise ")); Serial.print(char(224)); Serial.print(F(" z"));
   Serial.print(char(233)); Serial.println(F("ro du compteur de commandes."));
   Serial.println(F(" d ou D : Discret. (BIPs NON.)"));Serial.println(F(" e ou E : Encadrement OUI/NON."));
   Serial.println(F(" h ou H : Liste les commandes. (Help)"));
   Serial.print(F(" l ou L : Illumine une LED isol")); Serial.print(char(233)); Serial.println(F("e."));
   Serial.println(F(" m ou M : Affiche la MEMOIRE.")); Serial.println(F(" n ou N : Ecran noir OUI/NON."));
   Serial.println(F(" o ou O : Etat des options."));
   Serial.println(F(" r ou R : RESET des OPTIONS. >>>> BIP redevient actif !"));
   Serial.print(F(" p ou P : Pr")); Serial.print(char(233)); Serial.println(F("sentation du logiciel."));
   Serial.println(F(" s ou S : Silence OUI/NON.")); Serial.println(F(" t ou T : Illumine la LED TRIPLE."));
   Serial.print(F(" z ou Z : Remise ")); Serial.print(char(224)); Serial.print(F(" z"));
   Serial.print(char(233)); Serial.println(F("ro du CRONOMETRE."));}

void Aff_presentation_sur_USB() {
  Serial.println(); Serial.println(); Serial.println(F(" NOYAU PLATINE UNIVERSELLE.")); Souligner(26);
  Serial.println(F(" > Pour carte NANO ETENDUE <")); Serial.print  (F(" Version du "));
  Serial.println(Version); Serial.print(F(" Pile - Tas = ")); Serial.print(SRAM_LIBRE());
  Serial.println('.');}

//********************** Procédures pour le chronomètre **********************
// Tous les éléments du chronomètre sont regroupés ici pour être facilement enlevés du croquis.
boolean Aff_le_chrono = false;
byte Heures, Minutes, Secondes;

void RAZ_Chronometre() {Heures = 0; Minutes = 0; Secondes = 0;}

void Affiche_le_chronometre() {
  u8g2.drawStr(11,27,"Crono. "); u8g2.setCursor(56,27);
  if (Heures < 10) u8g2.print("0"); u8g2.print(Heures); u8g2.print(' ');
  if (Minutes  < 10) u8g2.print('0'); u8g2.print(Minutes); u8g2.print(' ');
  if (Secondes  < 10) u8g2.print('0'); u8g2.print(Secondes);}

void Incremente_le_chronometre() {
  Secondes++;
  if (Secondes == 60)
     {Secondes = 0; Minutes++;
     if (Minutes == 60) {Minutes = 0;  Heures++;
        if (Heures == 24) Heures = 0;}}}
  
//****************************************************************************
void Affiche_page_sur_OLED() {
  u8g2.clearBuffer(); // Efface la mémoire interne.
    u8g2.setFont(u8g2_font_shylock_nbp_tf); // Choisir la police de caractère affichée.
       if (!Noir) {
       if (Puce1) u8g2.drawDisc(120,9,3); else u8g2.drawCircle(120,9,3);
       if (Puce2) u8g2.drawDisc(120,24,3); else u8g2.drawCircle(120,24,3); 
       if (Encadrement) u8g2.drawFrame(0,0,128,32);
       u8g2.drawStr(11,14,"U sur A0 = "); u8g2.setCursor(76,14); u8g2.print(U,1); u8g2.drawStr(94,14,"V");
       if (Aff_le_chrono) Affiche_le_chronometre();
       else {u8g2.drawStr(11,27,"Rev : "); u8g2.setCursor(47,27); u8g2.print(Version);}}
 u8g2.sendBuffer();} // Transférer le texte sur l'afficheur.

void Eteindre_la_LED_triple() {
  digitalWrite(Rouge, LOW); digitalWrite(Vert, LOW); digitalWrite(Bleu, LOW);}

void Eteindre_les_LEDs_isolees() {
  digitalWrite(Rouge_3mm, HIGH); digitalWrite(Orange_5mm, HIGH); digitalWrite(Verte_3mm, LOW);
  digitalWrite(Verte_5mm, LOW);}

void Eteindre_toutes_les_LEDs() {Eteindre_la_LED_triple(); Eteindre_les_LEDs_isolees();}

void Modifie_les_LEDs() {Eteindre_toutes_les_LEDs(); // Seule une LED sera allumée à la fois.
  if (Evenement == 1) {
     switch (Num_couleur) {
        case 1 : {digitalWrite(Rouge, HIGH); break;}
        case 2 : {digitalWrite(Vert, HIGH); break;}
        case 3 : {digitalWrite(Bleu, HIGH); break;}
        case 4 : {digitalWrite(Rouge, HIGH); digitalWrite(Vert, HIGH);break;}
        case 5 : {digitalWrite(Rouge, HIGH); digitalWrite(Bleu, HIGH);break;}
        case 6 : {digitalWrite(Vert, HIGH); digitalWrite(Bleu, HIGH);break;}
        case 7 : {digitalWrite(Rouge, HIGH); digitalWrite(Vert, HIGH); break; digitalWrite(Bleu, HIGH);}}
     Num_couleur++; if (Num_couleur == 8) Num_couleur = 1;}
  else {switch (Num_isolee) {
           case 1 : {digitalWrite(Verte_3mm, HIGH); break;}
           case 2 : {digitalWrite(Rouge_3mm, LOW); break;}
           case 3 : {digitalWrite(Orange_5mm, LOW); break;}
           case 4 : digitalWrite(Verte_5mm, HIGH);}
       Num_isolee++; if (Num_isolee == 5) Num_isolee = 1;}}

void Gere_les_BP() {// L'antirebonds est rudimentaire dans ce démonstrateur et réduit à un simple delai.
  byte SAV_Evenement = Evenement; Evenement = 0;
  if (analogRead(BPA1) < 511) {Temps_Ecoule_pour_le_BP = millis(); // Arme pour les 4 secondes.
     Serial.println(); Serial.print(F(" BP sur A1 activ")); Serial.print(char(233)); Evenement = 1;
     //---------------- Attendre le relâcher ----------------
     while(analogRead(BPA1) < 511); delay(50);}
  //--------------------- Vérifier un clic sur le BPA3 ----------------------
  if (analogRead(BPA3) < 511) {Temps_Ecoule_pour_le_BP = millis(); // Arme pour les 4 secondes.
     Serial.println(); Serial.print(F(" BP sur A3 activ")); Serial.print(char(233)); Evenement = 2;
     //---------------- Attendre le relâcher ----------------
     while(analogRead(BPA3) < 511); delay(50);}
  if (Evenement > 0) {Modifie_les_LEDs(); Aff_le_chrono = true; Compteur++;}
                     else Evenement = SAV_Evenement;}

void Lire_A6_et_A7() { // Dans l'ordre : Entrée A6 puis Entrée A7.
  Puce1 = analogRead(20) > 512; Puce2 = analogRead(21) > 512;}

//================= Procédures pour les commandes Moniteur ===================
void Saisir_une_commande_USB() { // Effectuer une SAISIE sur le Moniteur de l'IDE.
  if (Serial.available()) {Index = 0;
     while (Serial.available()) {CAR_Moniteur = Serial.read();
            if ((CAR_Moniteur == ',') || (CAR_Moniteur == '?')) CAR_Moniteur = 'H';
            if ((Index < Lgr_TEXTE) && (CAR_Moniteur != CR) && (CAR_Moniteur != LF)) {
                //---------- Passer les lettre en MAJUSCULES ----------
                if ((CAR_Moniteur >= 'a') && (CAR_Moniteur < char(123))) CAR_Moniteur = CAR_Moniteur & 223;
                // Bien moins couteux en octets que Chaine_Memorisee.toUpperCase().
                //-----------------------------------------------------
                TEXTE[Index] = CAR_Moniteur; Index++;} delay(5);}}} // Délai impératif.

void Prompt_commande_USB() { Serial.println(); Serial.print(F(" Commande > "));}

void AFF_la_commande() {
  Serial.print('['); for (byte I = 0; I < Index; I++) Serial.print(TEXTE[I]);
  Serial.print(F("]  (Compteur = "));
  if (TEXTE[0] != 'Z') Serial.print(Compteur); else Serial.print(0); Serial.println(')');}

void RESET_OPTIONS() {Encadrement = true; Noir = false; Silence = false;}

void Aff_OUI_NON(boolean OUI) {if (OUI) Serial.print(F("OUI.")); else Serial.print(F("NON."));}

void Liste_les_options() {
  Serial.println(); Serial.print(F(" Encadrement = ")); Aff_OUI_NON(Encadrement);
  Serial.print(F(" Silence = ")); Aff_OUI_NON(Silence); Serial.print(F(" Ecran Noir = "));
  Aff_OUI_NON(Noir); Serial.println();}

void Traite_la_commande() {
  switch(TEXTE[0]) {
     case 'A' : {BIP(); break;}
     case 'C' : {Compteur = 0; break;}
     case 'D' : {Silence = true; Serial.println(); Serial.print(F(" Mode discret")); break;}
     case 'E' : {Encadrement = !Encadrement; Serial.println(); Serial.print(F(" Encadrement = "));
                 Aff_OUI_NON(Encadrement);Serial.println(); break;}
     case 'H' : {Liste_les_commandes(); break;} // 'H' pour Help.
     case 'L' : {Evenement = 2; Modifie_les_LEDs(); break;}
     case 'M' : {Serial.println(); Serial.print(F(" Occupation :")); Serial.print(Taille_PGM); break;}
     case 'N' : {Noir = !Noir; Serial.println(); Serial.print(F(" Ecran Noir = "));
                 Aff_OUI_NON(Noir); Serial.println(); break;}
     case 'O' : {Liste_les_options(); break;}
     case 'P' : {Affiche_page_sur_OLED(); Aff_presentation_sur_USB(); break;}
     case 'R' : {RESET_OPTIONS(); Liste_les_options(); break;}
     case 'S' : {Silence = !Silence; Serial.println(); Serial.print(F(" Silence = "));
                 Aff_OUI_NON(Silence); Serial.println(); break;}
     case 'T' : {Evenement = 1; Modifie_les_LEDs(); break;}
     case 'Z' : {RAZ_Chronometre(); delay(1000); break;}
     default : {BIP(); Serial.print(F(" Caractere non valide !")); Serial.println();}}}

//========================= Corps du programme ===============================

void setup() {
  Serial.begin(9600); Aff_presentation_sur_USB(); pinMode(LED_Arduino,OUTPUT);
  pinMode(LED_Arduino,OUTPUT); pinMode(Rouge_3mm,OUTPUT); pinMode(Verte_3mm,OUTPUT);
  pinMode(Orange_5mm,OUTPUT); pinMode(Verte_5mm,OUTPUT); pinMode(BUZZER,OUTPUT); digitalWrite(BUZZER,LOW);
  Eteindre_toutes_les_LEDs();
  u8g2.begin(); Affiche_page_sur_OLED(); Liste_les_commandes();
  //------------------ Initialisation des variables ------------------
  Lire_A6_et_A7(); Ancien_Puce1 = Puce1; Ancien_Puce2 = Puce2; RESET_OPTIONS(); Compteur = 0;
  Num_couleur = 1; Num_isolee = 1; Prompt_commande_USB();}

void loop() {
  //--------------------- Vérifier un clic sur le BPA1 ----------------------
  Gere_les_BP(); // Mis en procédure pour "raccourcir" le listage de la boucle de base.
  //-------------------------------------------------------------------------
  Mesure_la_tension();
  if ((U != SAV_U) || (Evenement != SAV_Action)) {Affiche_page_sur_OLED();
     SAV_U = U; SAV_Action = Evenement;} // Prise en compte des actions.
  //====================== Chronométrage pour clignoter =====================
  if (millis() - Temps_Ecoule_pour_clignoter > Delai_voulu)  {
     Temps_Ecoule_pour_clignoter = millis(); Inverser_LED_Arduino();}
  //============== Chronométrage pour un délai d'une secondes ===============
  if (millis() - Temps_Ecoule_pour_le_chronometre > 1000)  {
     Temps_Ecoule_pour_le_chronometre = millis(); Incremente_le_chronometre(); Affiche_page_sur_OLED();}
  //============ Chronométrage pour un délai de quatre secondes =============
  if (millis() - Temps_Ecoule_pour_le_BP > 4000)  {Temps_Ecoule_pour_le_BP = millis();
  Affiche_page_sur_OLED();}
  //---------------- Traitement de A6 et A7 pour les "puces" ----------------
  Lire_A6_et_A7();
  if (Ancien_Puce1 != Puce1) { // Evenement passé à 3 pour faire afficher le compteur.
      Evenement = 3;  Compteur ++; Affiche_page_sur_OLED();
      Ancien_Puce1 = Puce1; delay(2000); Aff_le_chrono = true;}
  if (Ancien_Puce2 != Puce2) { Evenement = 3; // Evenement passé à 3 pour faire afficher le compteur.
      Compteur ++; RAZ_Chronometre(); Aff_le_chrono = true; Affiche_page_sur_OLED(); Ancien_Puce2 = Puce2;
      Eteindre_toutes_les_LEDs(); Incremente_le_chronometre(); Incremente_le_chronometre(); delay(2000);}
  //----------------- Traitement de la commande du Moniteur -----------------
  Saisir_une_commande_USB();
  if (Index > 0) {
     Compteur++; Evenement = 3; Affiche_page_sur_OLED(); AFF_la_commande(); Aff_le_chrono = true;
     if (Index == 1) Traite_la_commande(); Index = 0; Prompt_commande_USB();}
  //-------------------------------------------------------------------------
  Mesure_la_tension();}



