/*
Description :
Il s'agit de tester différents outils via la réalisation d'un chronomètre, d'un minuteur et d'un réveil.
- l'arduino utilisé ici sera un modèle Nano
- On testera pour l'occasion le fonctionement d'un écran OLED 128*64,
d'un encodeur rotatif, d'un module RTC.
Quelqes parties du codes proviennent de :
Jérôme TOMSKI (https://passionelectronique.fr/)
*/
// =======================
// bibliothèques utilisées
// =======================
#include <SPI.h>
#include <Wire.h>
#include "SSD1306Ascii.h" // Ecran Oled, bibliothèque Greiman
#include "SSD1306AsciiWire.h" // Ecran Oled
#include "RTClib.h"
// ====================================
// Paramétrage de l'écran Oled
// ====================================
#define I2C_ADDRESS 0x3C // Adresse de "mon" écran Oled sur le bus i2c
#define RST_PIN -1 // Reset de l'Oled partagé avec l'Arduino (d'où la valeur à -1, et non un numéro de pin)
// Instanciation de l'écran que l'on nomme oled
SSD1306AsciiWire oled;
//========================================
// Instanciation du RTC que l'on nomme rtc
//========================================
RTC_DS3231 rtc;
//=====================================================
// Définition des broches utilisées en entrée ou sortie
//=====================================================
// pour l'encodeur rotatif KY-040
#define PIN_SW 2 // La pin D2 reçoit la ligne SW du module KY-040
#define PIN_ENC_CLK 3 // La pin D3 reçoit la ligne CLK du module KY-040
#define PIN_ENC_DT 4 // La pin D4 reçoit la ligne DT du module KY-040
// Broche pour les boutons poussoir
// Vert : Start/Lap
// Rouge : Stop
#define BTN_START 5
#define BTN_STOP 6
// Broches pour le slide-switch ==> usage futur, pour l'instant pas branchées
#define SLIDE_SW_A 8
#define SLIDE_SW_B 9
// Broche pour le buzzer
#define BUZZER_PIN 10
// Broches pour les LEDs
#define LED_VERTE 11
#define LED_ROUGE 12
//====================================
// Variables pour l'encodeur rotatif
//====================================
//int etatSW = HIGH; // Etat actuel de la ligne SW
//int etatPrecedentSW; // Cette variable nous permettra de stocker le dernier état de la ligne SW, afin de le comparer à l'actuel
volatile int etatPrecedentLigneCLK; // Cette variable nous permettra de stocker le dernier état de la ligne CLK, afin de le comparer à l'actuel
volatile int etatPrecedentLigneDT; // Cette variable nous permettra de stocker le dernier état de la ligne DT, afin de le comparer à l'actuel
volatile int compteur = 0; // Cette variable compte combien de crans ont été parcourus, sur l'encodeur
// volatile car mise à jour dans une procédure interruption
// variable compteur pour chaque cas (menu, chrono, minuteur, alarme)
//int cntMenu = 0;
//int cntVarApp1 = 0; // compteur variables application 1 (Chrono)
//int cntVarApp2 = 0;
//int cntVarApp3 = 0;
//=================================
// variable pour le slide switch
//=================================
//int etatPrecedentSlideSwitch;
//bool etatSlideSwitch = LOW;
//====================================
// Variables pour les boutons poussoir
//====================================
// Bouton Vert que j'appelle Start
int valBtnStart; // valeur acquise par digitalRead
int valBtnStartOld = HIGH; // valeur précédante
unsigned long timeChangeBtnStart = 0; // pour gestion du rebond
bool etatBtnStart = LOW; // état du bouton true si appuyé, false sinon
// Bouton Rouge que j'appelle Stop
int valBtnStop;
int valBtnStopOld = HIGH;
unsigned long timeChangeBtnStop = 0;
bool etatBtnStop = LOW;
// Bouton de l'encodeur rotatif que j'appelle SW
int valPinSW;
int valPinSWOld = HIGH;
unsigned long timeChangePinSW = 0;
bool etatPinSW = LOW;
int rebondBP = 20; // temps d'appui mini pour non rebond
// ==========================================================
// Déclaration des variables liées au choix du menu principal
// ==========================================================
bool demarrage = true;
int selGestionApp = 0;
int gestionApplication = 0; //0 si menu principal, 1 si gestion chronomètre, 2 si g. minuteur, 3 si g. alarme
//int lastGestionApplication = 0;
bool retourEcranChoix = false;
// ================================================================================
// Déclaration pour le module RTC
// la variable now est de type DateTime pour accueillir la valeur donnée par le RTC
// ================================================================================
DateTime now;
// Définition de la zone d'affichage avec son format
char buf1[] = "DD/MM/YYYY hh:mm:ss";
//=================================
// Variables pour le fonctionnement
//=================================
//variables pour le fonctionnement du chrono
unsigned long startMillis; // stocke le temps de début
unsigned long elapsedMillis = 0; //stocke le temps écoulé
unsigned long timeStartChrono = 0;
//unsigned long timeStopChrono = 0;
unsigned long intermediaireChrono = 0;
unsigned long stockChrono = 0;
unsigned long displayChrono = 0;
unsigned long dureeChrono = 0;
// variables pour l'affichage du chrono (jours (soyons fous !), heures, minutes, secondes et centièmes)
int jjChrono = 0;
int hhChrono = 0;
int mmChrono = 0;
int ssChrono = 0;
int ccChrono = 0;
// variables d'état
//bool modeChrono = true; // pour savoir si on esten mode chrono ou en mode minuteur
bool running = false; // état du chronomètre ou du minuteur
bool pause = false; // indique si l'affichage défile ou est en pause
int bpStartChrono = LOW;
int bpStopChrono = LOW;
int swEncChrono = LOW;
//int selectVarChrono = 0;
int etapeChrono = 10;
//int appliChronoActive = 0;
bool finChrono = true;
int bpStartMinuteur = LOW;
int bpStopMinuteur = LOW;
int swEncMinuteur = LOW;
//int selectVarMinuteur = 0;
int statutMinuteur = 20;
//int appliMinuteurActive = 0;
bool finMinuteur = true;
int bpStartAlarme = LOW;
int bpStopAlarme = LOW;
int swEncAlarme = LOW;
int statutAlarme = 30;
bool appliAlarmeActive = false;
bool finAlarme =true;
// Pour l'instant pas de gestion de choix du buzzer actif ou pas, il est toujours actif
bool buzzerActif = true;
// variables pour la durée demandée pour le minuteur
int heureDemande = 0;
int minDemande = 0;
int secDemande = 0;
unsigned long millisDemande = 0;
long resteMillis = 0; // temps restant pourle minuteur
// variables pour l'affichage du minuteur
int hhMinuteur = 0;
int mmMinuteur = 0;
int ssMinuteur = 0;
// variables pour l'affichage des valeurs de l'alarme
int hhAlarme = 0;
int mmAlarme = 0;
DateTime reveil; // contiendra l'heure de réveil voulue
// variables pour le Buzzer
const unsigned long intervalOn = 500; // Durée de la sonnerie en millisecondes
const unsigned long intervalOff = 500; // Durée du silence en millisecondes
unsigned long startMillisFinish; // pour gérer le temps du buzzer en fin de minuteur
unsigned long previousMillis = 0; // Variable pour stocker le temps précédent pour buzzer
unsigned long startMillisFinAlarme = 0; // pour gérer le temps du buzzer en fin d'alarme
// ========================
// Initialisation programme
// ========================
void setup() {
// init des broches utilisées
// Configuration de certaines pins de notre Arduino Nano en "entrées" (celles qui recevront les signaux du KY-040)
pinMode(PIN_SW, INPUT_PULLUP);
pinMode(PIN_ENC_CLK, INPUT);
pinMode(PIN_ENC_DT, INPUT);
pinMode(BTN_START, INPUT_PULLUP);
pinMode(BTN_STOP, INPUT_PULLUP);
pinMode(SLIDE_SW_A, INPUT_PULLUP);
pinMode(SLIDE_SW_B, INPUT_PULLUP);
pinMode(BUZZER_PIN, OUTPUT);
pinMode(LED_VERTE, OUTPUT);
pinMode(LED_ROUGE, OUTPUT);
// Mémorisation des valeurs initiales des lignes CLK/DT, au démarrage du programme
// etatPrecedentSW = digitalRead(PIN_SW);
etatPrecedentLigneCLK = digitalRead(PIN_ENC_CLK);
etatPrecedentLigneDT = digitalRead(PIN_ENC_DT);
// Activation d'interruptions sur les lignes CLK et DT
attachInterrupt(digitalPinToInterrupt(PIN_ENC_CLK), changementSurLigneCLK, CHANGE); // CHANGE => détecte tout changement d'état
// Activation d'interruptions sur le bouton SW de l'encodeur
//attachInterrupt(digitalPinToInterrupt(PIN_SW), changementSurLigneSW, FALLING); // FALLING => détecte les fronts descendants
// Initialisation de la liaison série (arduino nano <-> PC)
Serial.begin(9600);
Serial.println(F("=================="));
Serial.println(F("SETUP du programme"));
Serial.println(F("=================="));
// Initialisation de l'écran Oled
Wire.begin();
Wire.setClock(400000L);
#if RST_PIN >= 0
oled.begin(&Adafruit128x64, I2C_ADDRESS, RST_PIN);
#else // RST_PIN >= 0
oled.begin(&Adafruit128x64, I2C_ADDRESS);
#endif // RST_PIN >= 0
oled.setFont(System5x7);
// Initialisation du module DS 3231
if (!rtc.begin()) {
Serial.println("[ERREUR] Impossible d'établir la connexion avec votre module DS3231 (problème de câblage ?)");
Serial.flush();
while (1);
}
else {
Serial.println(F("Initialisation de la RTC réussie."));
}
// ******************************************************************************
// mise à jour de la date et heure du DS3231, après upload de ce programme,
// SEULEMENT si le DS3231 signale qu'il a "perdu l'heure" (ce qui a pu arriver
// si sa pile de secours a été défaillante, par exemple)
// ******************************************************************************
if (rtc.lostPower()) {
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
Serial.println(F("Mise à jour du RTC réussie."));
}
// on récupère la date et l'heure pour le 1er affichage du setup()
// et on fabrique la zone d'affichage avec le format souhaité
now = rtc.now();
// Effacer le buffer de l'OLED.
oled.clear();
// Display Texte d'introduction
oled.println(F("Bonjour, je m'appelle"));
oled.println("");
oled.println(F(" ARMAVIEL"));
oled.println("");
oled.println(F("Appuie sur le chapeau"));
oled.println("");
oled.println("");
char buf1[] = "DD/MM/YYYY hh:mm:ss";
oled.println(now.toString(buf1));
// On initialise la LED éteinte
digitalWrite(LED_VERTE, LOW);
digitalWrite(LED_ROUGE, LOW);
// affichage de l'heure sur le moniteur pour test seulement
Serial.print("Horloge du module RTC : ");
// char heure[10];
// sprintf(heure, "%02d:%02d:%02d", now.hour(), now.minute(), now.second());
// Serial.println(heure);
Serial.println(now.toString(buf1));
// Petite pause pour laisser le temps aux signaux de se stabiliser
delay(3000);
// Affichage de la valeur initiale du compteur, sur le moniteur série
Serial.print(F("Valeur initiale du compteur = "));
Serial.println(compteur);
} // fin du SETUP
// ==============================================
// Routine d'interruption : changementSurLigneCLK
// ==============================================
void changementSurLigneCLK() {
// Lecture des lignes CLK et DT, issue du KY-040, arrivant sur l'arduino
int etatActuelDeLaLigneCLK = digitalRead(PIN_ENC_CLK);
int etatActuelDeLaLigneDT = digitalRead(PIN_ENC_DT);
// ************************
// * CAS : Incrémentation *
// ************************
// Si CLK = 1 et DT = 0, et que l'ancienCLK = 0 et ancienDT = 1, alors le bouton a été tourné d'un cran vers la droite (sens horaire, donc incrémentation)
if((etatActuelDeLaLigneCLK == HIGH) && (etatActuelDeLaLigneDT == LOW) && (etatPrecedentLigneCLK == LOW) && (etatPrecedentLigneDT == HIGH)) {
// Alors on incrémente le compteur
compteur++;
// Et on affiche ces infos sur le moniteur série
Serial.print(F("Sens = horaire | Valeur du compteur = "));
Serial.println(compteur);
}
// ************************
// * CAS : Décrémentation *
// ************************
// Si CLK = 1 et DT = 1, et que l'ancienCLK = 0 et ancienDT = 0, alors le bouton a été tourné d'un cran vers la gauche (sens antihoraire, donc décrémentation)
if((etatActuelDeLaLigneCLK == HIGH) && (etatActuelDeLaLigneDT == HIGH) && (etatPrecedentLigneCLK == LOW) && (etatPrecedentLigneDT == LOW)) {
// Alors on décrémente le compteur
compteur--;
// Et on affiche ces infos sur le moniteur série
Serial.print(F("Sens = antihoraire | Valeur du compteur = "));
Serial.println(compteur);
}
// Et on mémorise ces états actuels comme étant "les nouveaux anciens", pour le "tour suivant" !
etatPrecedentLigneCLK = etatActuelDeLaLigneCLK;
etatPrecedentLigneDT = etatActuelDeLaLigneDT;
}
/*
// =================================================================
// Routine d'interruption : front descendant (FALLING) Sur bouton SW
// =================================================================
void changementSurLigneSW() {
static u32 previousInterupt = 0; // pour gestion des rebonds (ici délai sans rebonds = 100ms)
u32 _millis = millis(); // on récupère l'heure du front descendant
if((_millis - previousInterupt) >= 100) {
etatPinSW =true;
Serial.println(F("Bouton SW appuyé"));
}
// Puis on mémorise l'heure du dernier front descendant pour la prochaine fois
previousInterupt = _millis;
}
*/
// =================
// Boucle principale
// =================
void loop() {
/* restructuration du code pour mettre en place un menu de choix (je m'appuie sur le code de Bernard)
sur un 1er écran, et ensuite appel de fonctions gérant chaque appli : chrono, minuteur, alarme
Dans la loop on aura donc l'observation des boutons, et affectation de leur statut à chaque appli
puis la gestion de l'écran
puis l'acquisition de la date et heure sur le module RTC
puis le choix dans le menu
puis l'appel des fonctions correspondant aux applis
*/
//Fabrication d'un front descendant sur le BTN_START avec contrôle des rebonds
etatBtnStart = false;
valBtnStart = digitalRead(BTN_START);
if (valBtnStart != valBtnStartOld) {
if (((millis() - timeChangeBtnStart) > rebondBP) && (valBtnStart == false)) {
etatBtnStart = true;
Serial.println(F("Bouton vert appuyé"));
}
timeChangeBtnStart = millis();
}
valBtnStartOld = valBtnStart;
//Affectation des fronts descendants du BTN_START en fonction de l'application en cours à l'écran Oled
if (etatBtnStart && gestionApplication == 1) {
bpStartChrono = true;
} else {
bpStartChrono = false;
}
if (etatBtnStart && gestionApplication == 2) {
bpStartMinuteur = true;
} else {
bpStartMinuteur = false;
}
if (etatBtnStart && ((gestionApplication == 3) || (appliAlarmeActive))) {
bpStartAlarme = true;
} else {
bpStartAlarme = false;
}
//Fabrication d'un front descendant sur le BTN_STOP avec contrôle des rebonds
etatBtnStop = false;
valBtnStop = digitalRead(BTN_STOP);
if (valBtnStop != valBtnStopOld) {
if (((millis() - timeChangeBtnStop) > rebondBP) && (valBtnStop == false)) {
etatBtnStop = true;
Serial.println(F("Bouton rouge appuyé"));
}
timeChangeBtnStop = millis();
}
valBtnStopOld = valBtnStop;
//Affectation des fronts descendants du BTN_STOP en fonction de l'application en cours à l'écran Oled
if (etatBtnStop && gestionApplication == 1) {
bpStopChrono = true;
} else {
bpStopChrono = false;
}
if (etatBtnStop && gestionApplication == 2) {
bpStopMinuteur = true;
} else {
bpStopMinuteur = false;
}
if (etatBtnStop && ((gestionApplication == 3) || (appliAlarmeActive))) {
bpStopAlarme = true;
} else {
bpStopAlarme = false;
}
//Fabrication d'un front descendant sur le switch de l'encodeur avec contrôle des rebonds
etatPinSW = false;
valPinSW = digitalRead(PIN_SW);
if (valPinSW != valPinSWOld) {
if (((millis() - timeChangePinSW) > rebondBP) && (valPinSW == false)) {
etatPinSW = true;
Serial.println(F("Bouton SW appuyé"));
}
timeChangePinSW = millis();
}
valPinSWOld = valPinSW;
//Affectation des fronts descendants du switch de l'encodeur en fonction de l'application en cours à l'écran Oled
if (etatPinSW && gestionApplication == 1) {
swEncChrono = true;
} else {
swEncChrono = false;
}
if (etatPinSW && gestionApplication == 2) {
swEncMinuteur = true;
} else {
swEncMinuteur = false;
}
if (etatPinSW && gestionApplication == 3) {
swEncAlarme = true;
} else {
swEncAlarme = false;
}
// Gestion de l'écran Oled
gestionEcranOled();
// Acquisition date et heure
now = rtc.now();
//Choix de la gestion d'une application dans le menu principal
switch (gestionApplication) {
case 0:
if (gestionApplication == 0) { selGestionApp = compteur; }
if (gestionApplication == 0 && selGestionApp > 3) { compteur = 1; }
if (selGestionApp == 1 && etatPinSW == true) {
gestionApplication = 1; finChrono = false; oled.clear(); compteur = 0;
}
if (selGestionApp == 2 && etatPinSW == true) {
gestionApplication = 2; finMinuteur = false; oled.clear(); compteur = 0;
}
if (selGestionApp == 3 && etatPinSW == true) {
gestionApplication = 3; finAlarme = false; oled.clear(); compteur = 0;
}
break;
case 1:
if (finChrono) {
gestionApplication = 0; compteur = 0; oled.clear();retourEcranChoix = true;
}
else {
appliChrono();
}
break;
case 2:
if (finMinuteur) {
gestionApplication = 0; compteur = 0; oled.clear();retourEcranChoix = true;
}
else {
appliMinuteur();
}
break;
case 3:
if (finAlarme) {
gestionApplication = 0; compteur = 0; oled.clear();retourEcranChoix = true;
}
else {
appliAlarme();
}
break;
}
// pour débug vérif chgmt d'appli
static char lastGestionApplication = 10;
if (gestionApplication != lastGestionApplication) {
Serial.print(F("gestionApplication = "));
Serial.println(gestionApplication);
}
lastGestionApplication = gestionApplication;
// si une alarme est en cours on regarde si on arrive à l'instant du réveil
if (appliAlarmeActive) appliAlarme();
} // fin de LOOP
// ====================================
// Fonction : gestion de l'écran
// ====================================
/*
On distingue l'affichage soit
- du menu général
- du chronomètre
- du minuteur (partie réglage et décompte)
- de l'alarme (partie réglage)
En bas d'écran on a :
- ligne 7 l'heure:minute de l'alarme s'il une alarme est active
- ligne 8 la date et l'heure courante
*/
void gestionEcranOled() {
//Effacement de l'écran d'accueil
if ((demarrage == true && etatPinSW) || retourEcranChoix == true) {
oled.clear();
oled.println(" CHOIX D'UNE APPLI\n");
oled.println(" 1 CHRONO");
oled.println(" 2 MINUTEUR");
oled.println(" 3 ALARME");
demarrage = false;
retourEcranChoix = false;
}
//Positionnement du curseur dans la page de choix de la gestion d'une appli
if (demarrage == false && gestionApplication == 0) {
if (selGestionApp == 1) { oled.setCursor (18,2); oled.setInvertMode(1); oled.print(selGestionApp);}
else {oled.setCursor (18,2); oled.setInvertMode(0); oled.print(1);}
if (selGestionApp == 2) { oled.setCursor (18,3); oled.setInvertMode(1); oled.print(selGestionApp);}
else {oled.setCursor (18,3); oled.setInvertMode(0); oled.print(2);}
if (selGestionApp == 3) { oled.setCursor (18,4); oled.setInvertMode(1); oled.print(selGestionApp);}
else {oled.setCursor (18,4); oled.setInvertMode(0); oled.print(3);}
}
//Gestion Ecran pour l'application chronomètre
if (gestionApplication == 1) {
oled.set1X();
oled.setCursor (45,0);
oled.println ("CHRONO");
// on affiche ensuite mm:ss:cc en taille 2 centré (colonne 20, et ligne 2, ce qui correspond à la 3ème ligne taille 1
oled.set2X();
oled.setCursor (20,2);
if (mmChrono < 10) oled.print("0");
oled.print(mmChrono);
oled.print(":");
if (ssChrono < 10) oled.print("0");
oled.print(ssChrono);
oled.print(":");
if (ccChrono < 10) oled.print("0");
oled.print(ccChrono);
// on affiche sur la ligne suivante (la 6ème) en taille 1 les jours (!) et heures si nécessaire (je fais l'impasse sur la gestion du pluriel)
oled.set1X();
oled.setCursor (0,5);
if (jjChrono > 0) {
oled.print(jjChrono);
oled.print(" jour ");
oled.print(hhChrono);
oled.print(" heure");
}
else {
if (hhChrono > 0) {
oled.print(hhChrono);
oled.print (" heure ");
}
}
}
//Gestion Ecran pour l'application minuteur
if (gestionApplication == 2) {
// affichage titre centré sur 1ère ligne en taille 1
oled.setInvertMode(0);
oled.set1X();
oled.setCursor (40,0);
oled.println ("MINUTEUR");
// affichage HH:MM:SS centré en taille 2 sur 33ème et 4ème ligne
// pendant réglagle, on inverse la couleur pour la variable en réglage.
oled.set2X();
oled.setCursor (20,2);
//affichage HH
if (statutMinuteur == 20) oled.setInvertMode(1);
else oled.setInvertMode(0);
if (hhMinuteur < 10) oled.print("0");
oled.print(hhMinuteur);
oled.setInvertMode(0);
oled.print(":");
//affichage MM
if (statutMinuteur == 21) oled.setInvertMode(1);
else oled.setInvertMode(0);
if (mmMinuteur < 10) oled.print("0");
oled.print(mmMinuteur);
oled.setInvertMode(0);
oled.print(":");
//affichage SS
if (statutMinuteur == 22) oled.setInvertMode(1);
else oled.setInvertMode(0);
if (ssMinuteur < 10) oled.print("0");
oled.print(ssMinuteur);
oled.setInvertMode(0);
} // fin gestion affichage minuteur
//Gestion Ecran pour l'application alarme
if (gestionApplication == 3) {
// affichage titre centré sur 1ère ligne en taille 1
oled.setInvertMode(0);
oled.set1X();
oled.setCursor (21,0);
oled.println ("Reglage Alarme");
// affichage HH:MM centré en taille 2 sur 3ème et 4ème ligne
// pendant réglagle, on inverse la couleur pour la variable en réglage.
oled.set2X();
oled.setCursor (30,2);
//affichage HH
if (statutAlarme == 30) oled.setInvertMode(1);
else oled.setInvertMode(0);
if (hhAlarme < 10) oled.print("0");
oled.print(hhAlarme);
oled.setInvertMode(0);
oled.print(":");
//affichage MM
if (statutAlarme == 31) oled.setInvertMode(1);
else oled.setInvertMode(0);
if (mmAlarme < 10) oled.print("0");
oled.print(mmAlarme);
oled.setInvertMode(0);
} // fin gestion affichage alarme
// sur la 7ème ligne, on affiche l'heure de réveil si il y a une alarme active
if (appliAlarmeActive) {
oled.setInvertMode(0);
oled.set1X();
oled.setCursor (78,6);
if (hhAlarme < 10) oled.print("0");
oled.print(hhAlarme);
oled.print(":");
if (mmAlarme < 10) oled.print("0");
oled.print(mmAlarme);
oled.print(" ");
// note de musique pour indication la signification de HH:MM qui précède ==> ne marche pas donc
// oled.write(14);
// je mettrai ça si le oled.write ne marche pas (c'est un point d'interro à l'envers)
oled.print((char)191);
}
else{
oled.setInvertMode(0);
oled.set1X();
oled.setCursor (78,6);
oled.print(" ");
}
// on affiche sur la 8ème ligne en taille 1 la date et heure courante
oled.set1X();
oled.setCursor (0,7);
oled.setInvertMode(0);
char buf2[] = "DD/MM/YYYY hh:mm:ss";
oled.print(now.toString(buf2));
} // fin de la gestion de l'écran
// ====================================
// Appli CHRONOMETRE (n°1)
// ====================================
void appliChrono() {
if (etapeChrono < 10 || etapeChrono > 13 ) { etapeChrono = 10; }
switch (etapeChrono) {
case 10: //étape d'attente de départ du chrono
// la LED rouge clignote toutes les deux secondes la LED verte est éteinte
clignote(LED_ROUGE, 0, 2000, 2000, false);
digitalWrite(LED_VERTE, LOW);
displayChrono = 0;
dureeChrono = 0;
stockChrono = 0;
intermediaireChrono = 0;
//displayEtatChrono = etatChrono[0];
if (bpStartChrono == true) {
timeStartChrono = millis();
etapeChrono = 11;
}
//if (swEncChrono == true) { gestionApplication = 0; } // Retour au menu choix des applications
break;
case 11: //étape chrono en cours avec affichage de la valeur courante
displayChrono = dureeChrono;
dureeChrono = (millis() - timeStartChrono) + stockChrono;
// led Verte allumée, led Rouge éteinte
digitalWrite(LED_VERTE, HIGH);
digitalWrite(LED_ROUGE, LOW);
//displayEtatChrono = etatChrono[1];
if (bpStartChrono == true) {
intermediaireChrono = dureeChrono;
etapeChrono = 12;
}
if (bpStopChrono == true) {
stockChrono = dureeChrono;
etapeChrono = 13;
}
break;
case 12: //étape chrono en cours avec affichage du temps intermédiaire stocké après un appui sur le bouton Lap
displayChrono = intermediaireChrono;
dureeChrono = (millis() - timeStartChrono) + stockChrono;
// la LED verte clignote toutes les secondes la LED rouge est éteinte
clignote(LED_VERTE, 0, 1000, 1000, false);
digitalWrite(LED_ROUGE, LOW);
//displayEtatChrono = etatChrono[2];
if (bpStartChrono == true) { etapeChrono = 11; }
if (bpStopChrono == true) {
stockChrono = dureeChrono;
etapeChrono = 13;
}
break;
case 13: //étape chrono arrêté (temps mort dans un match de ping pong par ex) avec affichage de la valeur courante après un appui sur le bouton Stop
displayChrono = stockChrono;
// la LED rouge clignote toutes les secondes la LED verte est éteinte
clignote(LED_ROUGE, 0, 1000, 1000, false);
digitalWrite(LED_VERTE, LOW);
//displayEtatChrono = etatChrono[3];
if (bpStartChrono == true) {
timeStartChrono = millis();
etapeChrono = 11;
}
if (bpStopChrono == true) {
// on veut arrêter, donc on se met en situation de retour au menu
stockChrono = 0;
etapeChrono = 10;
finChrono = true;
}
break;
}
// Calculdes valeurs à afficher
ccChrono = (displayChrono % 1000L) / 10;
unsigned long secondes = displayChrono / 1000L;
ssChrono = secondes % 60L;
unsigned long minutes = secondes / 60L;
mmChrono = minutes % 60L;
unsigned long heures = minutes / 60L;
hhChrono = heures % 24L;
jjChrono = heures / 24L;
/*
if (etapeChrono > 10) {
appliChronoActive = true;
} else {
appliChronoActive = false;
}
*/
} // fin appli Chrono
//===============================================
// Appli MINUTEUR (n°2)
//===============================================
void appliMinuteur() {
/* on est en fonction minuteur
On gère 4 état différents dans ce fonctionnement :
- en Initialisation por le réglages des heures, minutes, secondes voulues
- en marche, on fait le décompte
- en pause, si on arrêtre le décompte
- en fin de décompte quand on est arrivé à 00:00:00
On gère ces statuts avec la variable statutMinuteur qui au départ est à 20
- 20 => régler les heures
- 21 =>régler les minutes
- 22 =>régler les secondes
L'encodeur rotatif va nous servir pour les trois réglages et son bouton SW pour valider les choix :
- pour le réglages des heures, minutes et des secondes, on affiche la valeur du compteur (si positif) et on fixe
la valeur demandée par appui du bouton SW
en fin de réglage des secondes, on passe en statutMinuteur = 23 fin des réglages et on attend une action
C'est l'appui sur le bouton Vert Start/Pause qui nous fait sortir de l'initialisation
et nous positionne en état "en marche". => statutMinuteur = 24
Dans l'état "en marche", on doit décompter le temps écoulé et l'afficher à chaque changement de seconde
- On conserve le millis() de démarrage dans startmillis
- à chaque boucle, on calcul le delta entre start et actuel (ellapsedtime)
- on soustrait ce delta au temps de décompte demandé exprimé en secondes
- on passe ce résultat à l'affichage
On va sortir de cet état de deux façons différentes :
- soit appui sur le bouton Vert start/pause qui va nous faire passer dans l'état "en pause" => statutMinuteur = 25
- soit par le décompte arrivant à 00:00:00 qui va nous faire passer dans l'état "en fin de décompte" => statutMinuteur = 26
Dans l'état "en pause", l'affichage est figée et on attend une action, deux possibilités :
- appui sur le bouton Vert Start/Pause qui nous remet dans l'état "en marche" => 24
- appui sur le bouton Rouge Stop qui nous ramène à l'état "en init". => 20
Dans l'état "en fin de décompte" (26)
on doit faire fonctionner le buzzer (si activé) et la led avec un clignotement plus rapide
Ces deux actions se termineront soit au bout d'un temps de 30s par exemple, soit par l'appui du bouton SW
Dans les deux cas on retourne en mode "en init" (20)
*/
if (statutMinuteur == 20 || statutMinuteur == 21 || statutMinuteur == 22) {
// En init de minuteur, la LED rouge clignote toute les 2 secondes
clignote(LED_ROUGE, 0, 2000, 2000, false);
}
switch(statutMinuteur) {
// si le reglage heure est en cours
case 20:
// le compteur de l'encodeur rotatif va donner des heures que l'on va afficher
// alim des variables pour l'affichage
if (compteur < 0) compteur = 0;
hhMinuteur = compteur;
mmMinuteur = 0;
ssMinuteur = 0;
// si on appuie sur SW c'est pour valider le nb d'heures voulues
if (swEncMinuteur == true){
statutMinuteur = 21;
heureDemande = compteur;
compteur = 0;
}
break;
// si le reglage des minutes est en cours
case 21:
// le compteur de l'encodeur rotatif va donner des minutes que l'on va afficher
// alim des variables pour l'affichage
hhMinuteur = heureDemande;
if (compteur < 0) compteur = 0;
mmMinuteur = compteur;
ssMinuteur = 0;
// si on appuie sur SW c'est pour valider le nb de minutes voulues
if (swEncMinuteur == true){
statutMinuteur = 22;
minDemande = compteur;
compteur = 0;
}
break;
// si le réglage des secondes est en cours
case 22:
// le compteur de l'encodeur rotatif va donner des secondes que l'on va afficher
// alim des variables pour l'affichage
hhMinuteur = heureDemande;
mmMinuteur = minDemande;
if (compteur < 0) compteur = 0;
ssMinuteur = compteur;
// si on appuie sur SW c'est pour valider le nb de secondes voulues
if (swEncMinuteur == true){
statutMinuteur = 23;
secDemande = compteur;
compteur = 0;
// on a fini les réglages donc
millisDemande = ((heureDemande * 3600L) + (minDemande * 60L) + secDemande) * 1000L;
Serial.print(F("millisDemande = "));
Serial.println(millisDemande);
}
break;
// On est prêt à démarrer, on attend le feu vert (appuie sur bouton vert) => passage à 24
// mais on peut aussi abandonner par appui sur le bouton rouge ==> sortie de l'appli minuteur
case 23:
// si OK
if (bpStartMinuteur == true) {
statutMinuteur =24;
// on allume LED verte et on éteint LED rouge
digitalWrite(LED_VERTE, HIGH);
digitalWrite(LED_ROUGE, LOW);
// on démarre le décompte en conservant les millis de départ
startMillis = millis();
Serial.print(F("startMillis = "));
Serial.println(startMillis);
}
// si Abandon
if (bpStopMinuteur == true) {
statutMinuteur = 20;
digitalWrite(LED_VERTE, LOW);
digitalWrite(LED_ROUGE, LOW);
finMinuteur =true;
}
break;
// on est en décompte
case 24:
// pour ce case il faut mettre des accolades car on déclare à l'intérieur des variables pour le calcul
// ce qui permet d'en limiter la portée au seul case = 24
// sinon le compilatuer C++ va ignorer les case suivants, considérant que
// si on ne passe pas dans le case 24, alors on pourrait arriver dans les cases suivants
// sans avoir déclarer les variables, ce qui serait problématique.
// longue recherche pour comprendre pourquoi les case 25 et 26 ne fonctionnait pas !
// Ils étaient tout simplement ignorés par le compilateur !!!
{
// on regarde si on a demandé une pause par appui du bouton vert
if (bpStartMinuteur == true) {
statutMinuteur = 25;
}
// on calcul le temps en millisecondes depuis le départ du minuteur
unsigned long elapsedMillis = millis() - startMillis;
// on calcule le temps restant en millisecondes
resteMillis = millisDemande - elapsedMillis;
//Serial.println(resteMillis);
// on teste si on a fini
if (resteMillis < 0) {
statutMinuteur = 26;
startMillisFinish = millis();
resteMillis = 0;
}
// on calcule les HH MM SS pour l'affichage du décompte
unsigned long secondes = resteMillis / 1000L;
ssMinuteur = secondes % 60L;
unsigned long minutes = secondes / 60L;
mmMinuteur = minutes % 60L;
hhMinuteur = minutes / 60L;
}
break;
// si on est en pause,
// Le bouton vert nous permet de redémarrer où on était arrêté
// Le bouton rouge nous permet d'abandonner
case 25:
// on clignote vert et rouge en alternance toutes les deux secondes
clignote(LED_VERTE, LED_ROUGE, 2000, 2000, false);
// gestion du bouton vert pour sortir de la pause
if (bpStartMinuteur == true) {
// si on sort de la pause on s'assure que la LED verte va bien être allumée
// et que la LED rouge va être éteinte
// et on fait en sorte de reprendre le décompte où il s'était arrêté
digitalWrite(LED_VERTE, HIGH);
digitalWrite(LED_ROUGE, LOW);
millisDemande = resteMillis;
startMillis = millis();
// on repasse au statut de marche
statutMinuteur = 24;
}
// Gestion du bouton rouge pour arret du minuteur
if (bpStopMinuteur == true) {
finMinuteur = true;
statutMinuteur = 20;
digitalWrite(LED_VERTE, LOW);
digitalWrite(LED_ROUGE, LOW);
}
break;
// Traitement en fin du décompte
case 26:
// on fait clignoter les leds en synchro et on fait vibrer le buzzer si voulu
// on fait ça pendant 30secondes
// sauf si appui sur bouton rouge Stop pour sortir plus tôt
// on repasse en mode enInit
// on calcul le temps en millisecondes depuis le départ du minuteur
elapsedMillis = millis() - startMillisFinish;
// si 30 secondes écoulées ou si appui sur Rouge Stop on se met en situation de retour au menu
if (elapsedMillis > 30000 || bpStopMinuteur == true){
statutMinuteur = 20;
finMinuteur= true;
noTone(BUZZER_PIN);
digitalWrite(LED_VERTE, LOW);
digitalWrite(LED_ROUGE, LOW);
}
// Clignotement vert et rouge toutes les 1 seconde en synhro
clignote(LED_VERTE, LED_ROUGE, 1000, 1000, true);
// Gestion du buzzer si on veut du son
if (buzzerActif){
buzz();
}
break;
} //fin du switch case
} // fin de l'appli MINUTEUR
//=======================================================
// Appli ALARME (n°3)
//=======================================================
void appliAlarme() {
/*
On gère un statut Alarme:
- en Init il vaut 30
Si on est en init (30), on a deux choses à faire :
- régler les heures : quand OK statut passe de 30 à 31
- régler les minutes : quand OK statut passe de 31 à 32
L'encodeur rotatif va nous servir pour les deux réglages et son bouton SW pour valider les choix :
- pour le réglages des heures et minutes on affiche la valeur du compteur (si positif) et on fixe
la valeur demandée par appui du bouton SW
Attention, si l'HH:MM demandé plus petit que HH:MM courante, alors il faut ajouter 1 jour pour déterminer la date et heure d'alarme
C'est l'appui sur le bouton Vert Start/Pause qui nous fait sortir de l'état "enInit" (passage de 32 à 33)
et nous positionne en état "en marche" (donc statut = 33)
A ce moment là, l'appli doit fonctionner en tâche de fond et on doit pouvoir utiliser le chrono ou le minuteur
donc c'est retour à la gestion du menu
L'affichage doit nous rappeler que l'alarme est active
Dans l'état "en marche", on doit regarder si l'heure du réveil est arrivée
- On conserve la date et heure demandée dans une variable réveil de type DateTime
- à chaque boucle, on compare la variable réveil à la variable now toutes deux de type DateTime.
On va sortir de cet état de deux façons différentes :
- soit appui sur le bouton Rouge Stop qui va nous faire passer dans l'état "on ne veut plus de cette alarme" donc finAlarme = true et statutAlarme = 30 pour la prochaine fois
- soit now > réveil qui nous fait passer dans l'état "sonnerie" statut 34
Dans l'état "sonnerie" (34)
on doit faire fonctionner le buzzer (si activé) et les LEDs avec un clignotement en synchro d'une seconde
Ces deux actions se termineront soit au bout d'un temps de 30s par exemple, soit par l'appui du bouton Rouge Stop
Dans les deux cas on retourne en indiquant finAlarme = true et statutAlarme = 30
*/
// si statut < 33 on est en réglage, et on fait clignoter
if (statutAlarme < 33) {
// En init de alarme, la LED rouge clignote toute les 2 secondes
clignote(LED_ROUGE, 0, 2000, 2000, false);
}
switch(statutAlarme) {
// si le reglage heure pas terminé
case 30:
// le compteur de l'encodeur rotatif va donner des heures que l'on va afficher
// alim des variables pour l'affichage
if (compteur < 0) compteur = 0;
hhAlarme = compteur;
mmAlarme = 0;
// si on appuie sur SW c'est pour valider l'heure voulue
if (swEncAlarme == true){
statutAlarme = 31;
Serial.println(F("passage à 31"));
hhAlarme = compteur;
compteur = 0;
}
break;
// si le reglage des minutes n'est pas terminé
case 31:
// le compteur de l'encodeur rotatif va donner des minutes que l'on va afficher
// alim des variables pour l'affichage
if (compteur < 0) compteur = 0;
mmAlarme = compteur;
// si on appuie sur SW c'est pour valider le nb de minutes voulues
if (swEncAlarme == true){
statutAlarme = 32;
Serial.println(F("passage à 32"));
mmAlarme = compteur;
compteur = 0;
}
break;
// si les réglages sont fait on attend une validation par appui sur le bouton vert start
// ou une annulation par le bouton rouge stop qui nous remet à 30 et fin Alarme
// ce qui nous fera passer de 32 à 33
case 32:
if (bpStartAlarme == true) {
// ici on doit initialiser correctement la variable "réveil" en fonction de l'heure actuelle
// et faire en sorte qu'elle soit supérieure à l'heure actuelle
// en ajoutant éventuellement un jour
DateTime provisoire(now.year(), now.month(), now.day(), hhAlarme, mmAlarme, 0);
reveil = provisoire;
char buf3[] = "DD/MM/YYYY hh:mm:ss";
Serial.println(reveil.toString(buf3));
if (provisoire < now) {
reveil = provisoire + TimeSpan(1,0,0,0);
char buf4[] = "DD/MM/YYYY hh:mm:ss";
Serial.println(reveil.toString(buf4));
}
appliAlarmeActive= true;
// le réglage est terminé
finAlarme = true;
statutAlarme = 33;
Serial.println(F("passage à 33"));
}
// si on appuie sur bouton rouge, on abandonne
if (bpStopAlarme == true) {statutAlarme = 30; finAlarme = true; appliAlarmeActive= false;}
// dans les deux cas
if (bpStartAlarme == true || bpStopAlarme == true){
// on éteint la led rouge
digitalWrite(LED_ROUGE, LOW);
// on met alarme en tâche de fond par retour au menu
gestionApplication = 0;
compteur = 0;
oled.clear();
retourEcranChoix = true;
}
break;
case 33:
// on doit comparer "réveil" et "now"
// si now > réveil, on passe à l'état 34
if (now > reveil) {
startMillisFinAlarme = millis();
statutAlarme = 34;
}
// si on appuie en même temps sur le bouton rouge et le bouton vert,
// c'est qu'on veut annuler cette alarme active avant son réveil
// ici seulement Bouton stop car pas possible de simuler l'appui simultané sur les deux boutons
if (bpStopAlarme == true) {statutAlarme = 30; finAlarme = true; appliAlarmeActive= false;}
break;
case 34:
// on buzze et on allume LED comme pour le minuteur
// puis on repasse le statut à 30 et on indique que la fonction alarme est inactive
// on fait clignoter les leds en synchro et on fait vibrer le buzzer si voulu
// on fait ça pendant 30secondes
// sauf si appui sur bouton rouge Stop pour sortir plus tôt
// on repasse en mode enInit
// on calcul le temps en millisecondes depuis le départ du réveil
unsigned long elapsedMillisAlarme = millis() - startMillisFinAlarme;
// si 30 secondes écoulées ou si appui sur Rouge Stop on se met en situation de retour au menu (on met enInit à true pour un prochain usage)
if (elapsedMillisAlarme > 30000 || bpStopAlarme == true){
appliAlarmeActive= false;
statutAlarme = 30;
noTone(BUZZER_PIN);
digitalWrite(LED_VERTE, LOW);
digitalWrite(LED_ROUGE, LOW);
}
// Clignotement vert et rouge toutes les 1 seconde en synhro
clignote(LED_VERTE, LED_ROUGE, 1000, 1000, true);
// Gestion du buzzer si on veut du son
if (buzzerActif){
buzz();
}
break;
} // fin du switch case
}
//=======================================================
// Routine clignotement une ou deux LEDs synchro ou pas
//=======================================================
void clignote(int led1, int led2, int timeOn, int timeOff, bool synchro) {
static unsigned long previousMillisLED = 0; // Variable pour stocker le temps précédent pour LED
static bool ledIsOn = false; // État actuel de la LED
unsigned long currentMillisLED = millis(); // Obtenir le temps actuel
// gestion du clignotement de la LED1
if (ledIsOn && (currentMillisLED - previousMillisLED >= timeOn)) {
// Si la LED est allumée et que le temps pour l'éteindre est écoulé
digitalWrite(led1, LOW); // Éteindre la LED1
// LED2 si demandée, en synchro ou en alternance
if (led2 != 0){
if (synchro)
digitalWrite(led2, LOW);
else
digitalWrite(led2, HIGH);
}
previousMillisLED = currentMillisLED; // Réinitialiser le temps de référence
ledIsOn = false; // Mettre l'état à éteint
}
else if (!ledIsOn && (currentMillisLED - previousMillisLED >= timeOff)) {
// Si la LED est éteinte et que le temps pour la rallumer est écoulé
digitalWrite(led1, HIGH); // Allumer la LED
// LED2 si demandée, en synchro ou en alternance
if (led2 != 0){
if (synchro)
digitalWrite(led2, HIGH);
else
digitalWrite(led2, LOW);
}
previousMillisLED = currentMillisLED; // Réinitialiser le temps de référence
ledIsOn = true; // Mettre l'état à allumé
}
}
//=======================================================
// Routine de fonctionnement du buzzer
//=======================================================
void buzz(){
static unsigned long previousMillisAlarme = 0; // Variable pour stocker le temps précédent pour buzzer alarme
static bool buzzerIsOn = false; // Etat du buzzer
unsigned long currentMillis = millis(); // Obtenir le temps actuel
if (buzzerIsOn) {
// Si le buzzer est en train de jouer un son
if (currentMillis - previousMillisAlarme >= intervalOn) {
noTone(BUZZER_PIN); // Arrêter le son
previousMillisAlarme = currentMillis; // Mettre à jour le temps de référence
buzzerIsOn = false; // Indiquer que le buzzer est maintenant en silence
}
}
else {
// Si le buzzer est en silence
if (currentMillis - previousMillisAlarme >= intervalOff) {
tone(BUZZER_PIN, 440); // Jouer le son à la fréquence spécifiée (LA = 440Hz)
previousMillisAlarme = currentMillis; // Mettre à jour le temps de référence
buzzerIsOn = true; // Indiquer que le buzzer est maintenant en train de jouer un son
}
}
}