/*
Projet : Réalisation d'un chronomètre/ minuteur en utilisant les outils de conception
Arduino et Fusion 360.
Programme : Chrono
Fonction : Gestion du fonctionnement du chronomètre/ minuteur:
- comptage/ décomptage du temps écoulé depuis un instant,
- affichage du temps compté/ décompté,
- saisie des règles de fonctionnement du minutage (durée du minutage,
durée du signal sonore en fin de minutage).
Description : Le système est basé sur une carte Arduino Nano à laquelle sont connectés les
éléments suivants :
- 1 interrupteur 3 positions pour alimenter le système et sélectionner la fonction
(1 position 'Off', 1 position 'Chronomètre', 1 position 'Minuteur'),
- 1 afficheur Oled 128x64 pixels,
- 1 bouton-poussoir pour :
- lancer l'exécution de la fonction sélectionnée (chronométrage/ minutage),
- gérer l'affichage du temps compté (ou décompté selon la fonction en cours),
La pression sur ce bouton a un effet dépendant du contexte :
- si aucune fonction n'est en cours d'exécution, la fonction sélectionnée
est lancée,
- si une fonction est en cours d'exécution, 2 cas selon la fonction en cours :
- si la fonction en cours est le chronométrage, l'effet porte sur le mode
d'affichage du temps :
- si le temps compté est affiché en temps réel (i.e. en continu), cet
affichage est mis en pause (i.e. le temps compté lors de la pression
sur le bouton reste affiché en permanence),
- si l'affichage est en pause, celui-ci est relancé; le temps (qui
continuait à être compté pendant la pause de l'affichage) est,
à nouveau, affiché en continu,
- si la fonction en cours est le minutage, 2 cas selon que cette fonction
est en cours ou en pause :
- si le minutage est en cours, celui-ci est mis en pause (ainsi que
l'affichage),
- si le minutage est en pause, celui-ci repart là où il en était (ainsi
que l'affichage),
- 1 encodeur rotatif dont :
- le bouton-poussoir a un effet variant selon le contexte :
- si le minutage est en attente de lancement, la saisie des règles associées
est lancée,
- si la saisie des règles de minutage est en cours, la valeur saisie est
validée,
- si une fonction est en cours d'exécution et si l'affichage est en pause,
l'exécution de la fonction est arrêtée et l'affichage est réinitialisé,
- si le minutage est terminé (phase d'alerte), le signal de fin est arrêté,
et la fonction de minutage revient en phase d'attente de lancement.
- la molette peut être utilisée dans 2 cas :
- pour changer de fonction (chrono <-> minutage) : lorsque la fonction est en
pause, au lieu d'appuyer sur le bouton (de l'encodeur), il faut tourner la
molette (dans n'importe quel sens),
- lors de la modification des règles de minutage, la molette permet de modifier
la valeur en cours de saisie (en plus, dans le sens horaire; en moins, dans
l'autre sens),
- 2 leds pour indiquer l'état de fonctionnement du système,
- 1 buzzer pour signaler, en mode 'Minuteur', l'écoulement complet du temps
(si demandé, d'après les règles).
Création : 29/09/24, M. Chauvat.
Evolutions :
- 21/10/24, M. Chauvat:
- Remplacement de l'afficheur 4 digits (réf. TH16K33') par l'afficheur Oled 128x64 pixels
(réf. 'SSD1306'),
- Remplacement d'un des boutons-poussoirs par un encodeur rotatif (réf. 'KY-040').
----------------------------------------------------------------------------------------------------
*/
#define _test_ // à mettre en commentaire dans la version définitive
// Afficheur (réf. 'SSD1306')
#include <Adafruit_SSD1306.h>
Adafruit_SSD1306 _affic( 128, 64, &Wire, -1); // 128 : largeur de l'écran (en nb de pixels)
// 64 : hauteur de l'écran
#define _afficCarLarg_ 6 // largeur d'un caractère (dans la police par défaut, en taille 1)
#define _afficCarHaut_ 8 // hauteur d'un caractère
#define _afficLignInter_ 2 // intervalle entre 2 lignes successives
struct _AfficCoord { // coordonnées d'affichage
int16_t x = 0;
int16_t y = 0;
};
/*// Horloge RTC (réf. 'DS1307')
#include <RTClib.h>
RTC_DS1307 _horl;*/
// Encodeur (réf. 'KY-040')
enum _EncodActs { // actions possibles sur l'encodeur
Aucune,
BoutPress, // pression sur le bouton
MolRotHor, // rotation de la molette dans le sens horaire
MolRotInv}; // " " " inverse
// Carte Arduino (connexions aux broches d'entrée/sortie)
#define _brocInter_ 2 // interrupteur (position 'Chrono')
#define _brocBout_ 4 // bouton
#define _brocEncodMolA_ 5 // encodeur - molette (broche 'A' (ou 'CLK'))
#define _brocEncodMolB_ 6 // encodeur - molette (broche 'B' (ou 'DT'))
#define _brocEncodBout_ 7 // encodeur - bouton (broche 'C' (ou 'SW'))
#define _brocLedVerte_ 11 // led verte
#define _brocLedRouge_ 12 // led rouge
#define _brocBuzzer_ 13 // buzzer
// Fonctionnement du système
enum _SystFoncts { // fonctions du système
Chrono, // chronomètrage
Minuteur}; // minutage
enum _SystFoncts _fonctCour; // fonction en cours
bool _fonctActive; // indicateur de fonction active
bool _afficActif; // indicateur d'affichage actif
struct _MinutRegl { // règles de minutage
int temps = 10; // temps de minutage (en secondes)
int finSignDuree = 10; // durée du signal (sonore) de fin de minutage
}; // (en secondes; 0 si aucun signal)
_MinutRegl _minutRegl;
unsigned long _minutFin; // instant de fin de minutage
// Mémorisation des règles de minutage
#include <EEPROM.h>
#define _minutReglTempsMemoAdr_ 1 // adresse de mémorisation du (dernier) temps de minutage
#define _minutReglFinSignDureeMemoAdr_ 2 // adresse de mémorisation de la (dernière) durée du
// signal de fin de minutage
// Buzzer
enum _BuzzMusiqExecOrdres {
Commencer,
Poursuivre,
Arreter};
//==================================================================================================
void setup()
/*
Fonction : /
Paramètres : /
Description : /
Création : ../../24, M. Chauvat.
Evolutions :
- 04/12/24, M. Chauvat : ajout de la lecture des dernières règles de minutage.
----------------------------------------------------------------------------------------------------
*/
{
// Initialisation du moniteur
#ifdef _test_
Serial.begin( 9600);
Serial.println( ">> Début de 'setup' ...");
#endif
// Définition des modes de connexion des broches de la carte
pinMode( _brocInter_, INPUT_PULLUP);
pinMode( _brocBout_, INPUT_PULLUP);
pinMode( _brocEncodBout_, INPUT_PULLUP);
pinMode( _brocEncodMolA_, INPUT);
pinMode( _brocEncodMolB_, INPUT);
pinMode( _brocLedVerte_, OUTPUT);
pinMode( _brocLedRouge_, OUTPUT);
pinMode( _brocBuzzer_, OUTPUT);
/*// Initialisation de l'horloge
if (! _horl.begin()) while( true);
_horl.adjust( DateTime( F(__DATE__), F(__TIME__)));*/
// Initialisation de l'afficheur
if( ! _affic.begin( SSD1306_SWITCHCAPVCC, 0x3C)) { // 0x3C : adresse I2C
#ifdef _test_
Serial.println( "** Init. de '_affic' impossible ...");
#endif
while( true);
}
// Il faudrait ajouter ici l'affichage du nom du système et de sa version ...
_affic.setTextColor( WHITE);
_affic.setTextSize( 1);
// Lecture de la fonction sélectionnée via l'interrupteur
if( digitalRead( _brocInter_) == LOW) {
_fonctCour = _SystFoncts::Chrono;
} else {
_fonctCour = _SystFoncts::Minuteur;
}
// Lecture des dernières règles de fonctionnement du minuteur
_minutRegl.temps = EEPROM[ _minutReglTempsMemoAdr_];
_minutRegl.finSignDuree = EEPROM[ _minutReglFinSignDureeMemoAdr_];
}
//==================================================================================================
void loop() {
#ifdef _test_
Serial.println( ">> Début de 'loop' ...");
#endif
bool fonctChang;
switch( _fonctCour) {
case _SystFoncts::Chrono :
chrono( fonctChang);
if( fonctChang) _fonctCour = _SystFoncts::Minuteur;
break;
case _SystFoncts::Minuteur :
minut( fonctChang);
if( fonctChang) _fonctCour = _SystFoncts::Chrono;
}
}
//==================================================================================================
void affic( int ent, _AfficCoord pos, int longu, bool avec0, bool enBlanc)
/*
Fonction : Afficher une valeur entière.
Paramètres :
- En entrée :
- ent : entier à afficher
- pos : position d'affichage
- longu : longueur d'affichage
- avec0 : indicateur d'affichage des zéros de début
- enBlanc : indicateur d'affichage en blanc (sur fond noir)
Description : Avant d'envoyer la valeur dans le buffer, on remet tous les pixels de la zone
d'affichage dans la couleur du fond (en envoyant un rectangle plein).
Remarque : le rectangle envoyé déborde d'1 pixel de la zone d'affichage de la valeur
(sur tous les côtés) afin de faciliter la lecture de la valeur lorsque la couleur de
fond est inversée.
Remarque : le fonctionnement n'envisage pas le cas où le nombre de chiffres dépasse
la longueur d'affichage.
Création : 26/11/24, M. Chauvat.
Evolutions :
- 04/12/24, M. Chauvat : ajout du paramètre d'entrée 'avec0'.
----------------------------------------------------------------------------------------------------
*/
{
#ifdef _test_
Serial.println( ">> Début de 'affic' ...");
#endif
// Effacement de la zone
_AfficCoord rectPos;
int16_t rectLarg, rectHaut;
rectPos = pos;
rectLarg = _afficCarLarg_ * longu;
rectHaut = _afficCarHaut_;
if( rectPos.x > 0) {
rectPos.x--;
rectLarg++;
}
if( rectPos.y > 0) {
rectPos.y--;
rectHaut++;
}
if(( rectPos.x + rectLarg) < _affic.width()) rectLarg++;
if(( rectPos.y + rectHaut) < _affic.height()) rectHaut++;
uint16_t coulFond = BLACK;
if( ! enBlanc) coulFond = WHITE;
_affic.fillRect( rectPos.x, rectPos.y, rectLarg, rectHaut, coulFond);
_affic.setCursor( pos.x, pos.y);
_affic.setTextColor( INVERSE);
if( ent < 10) {
if( avec0) {
_affic.print( "0");
} else {
_affic.print( " ");
}
}
_affic.print( ent);
_affic.display();
} // affic(...)
//==================================================================================================
void afficEntete( String texte, int16_t& haut)
/*
Fonction : Afficher une entête.
Paramètres :
- En entrée :
- ent : texte à afficher
- En sortie :
- haut : hauteur de l'entête
Description : /
Création : 27/11/24, M. Chauvat.
Evolutions : /
----------------------------------------------------------------------------------------------------
*/
{
#ifdef _test_
Serial.println( ">> Début de 'afficEntete' ...");
#endif
_affic.clearDisplay();
_AfficCoord pos;
// Affichage du texte
{
// Calcul de la position (en X) pour un affichage centré
unsigned int longMax = _affic.width() / _afficCarLarg_;
unsigned int longu = min( texte.length(), longMax);
if( longu < longMax) pos.x = ((longMax - longu) / 2) * _afficCarLarg_;
_affic.setCursor( pos.x, pos.y);
_affic.print( texte.substring( 0, longu));
}
// Affichage de la ligne de séparation
pos.x = 0;
pos.y = _afficCarHaut_ + _afficLignInter_;
_affic.drawLine( pos.x, pos.y, _affic.width() - 1, pos.y, WHITE);
_affic.display();
haut = pos.y + 1;
} // afficEntete(...)
//==================================================================================================
void afficInit()
/*
Fonction : Initialisation de l'affichage.
Paramètres : /
Description : /
Création : 10/10/24, M. Chauvat.
Evolutions :
- 04/12/24, M. Chauvat : Ajout de l'affichage des règles de minutage (sur l'écran du minuteur).
----------------------------------------------------------------------------------------------------
*/
{
#ifdef _test_
Serial.println( ">> Début de 'afficInit' ...");
#endif
_affic.clearDisplay(); // vidage du buffer
_affic.setTextColor( WHITE);
int16_t entHaut;
switch (_fonctCour) {
case _SystFoncts::Chrono :
afficEntete( "Chronometre", entHaut);
afficTemps( 0, true);
break;
case _SystFoncts::Minuteur :
afficEntete( "Minuteur", entHaut);
// Affichage des règles
_AfficCoord pos;
pos.y = entHaut + _afficLignInter_;
// Temps de minutage
_affic.setCursor( pos.x, pos.y);
_affic.print( "Temps:");
pos.x += _afficCarLarg_ * 6;
int val = _minutRegl.temps;
affic( val / 60, pos, 2, false, true);
pos.x += _afficCarLarg_ * 2;
_affic.print( ":");
pos.x += _afficCarLarg_ * 1;
affic( val % 60, pos, 2, true, true);
pos.x += _afficCarLarg_ * 2;
// Durée du signal sonore de fin de minutage
// _affic.print( "|");
pos.x += _afficCarLarg_ * 1 / 2;
_affic.drawLine( pos.x, pos.y - _afficLignInter_,
pos.x, pos.y + _afficCarHaut_ + _afficLignInter_, WHITE);
pos.x += _afficCarLarg_ * 1 / 2;
val = _minutRegl.finSignDuree;
_affic.setCursor( pos.x, pos.y);
if( val == 0) { // pas de signal sonore de fin de minutage
_affic.write( 14);
break;
}
_affic.print( "Al.:");
pos.x += _afficCarLarg_ * 4;
affic( val / 60, pos, 2, false, true);
pos.x += _afficCarLarg_ * 2;
_affic.print( ":");
pos.x += _afficCarLarg_ * 1;
affic( val % 60, pos, 2, true, true);
// Traçage d'une ligne de séparation
pos.x = 0;
pos.y += _afficCarHaut_ + _afficLignInter_;
_affic.drawLine( pos.x, pos.y, _affic.width() - 1, pos.y, WHITE);
afficTemps( _minutRegl.temps, false); // affichage du temps initial
}
_afficActif = false;
} // afficInit
//==================================================================================================
void afficTemps( long temps, bool enMs)
/*
Fonction : Affichage d'un temps (sur l'afficheur).
Paramètres :
- En entrée :
- temps : temps à afficher (en secondes ou millisecondes, selon la valeur de 'milliSec')
- enMs : indique si le temps est exprimé en millisecondes (sinon, la seconde)
- En sortie : /
Description : L'afficheur doit être un afficheur OLed 128x64 (réf. SSD1306).
Création : 10/10/24, M. Chauvat.
Evolutions :
- 04/11/24, M. Chauvat : ajout du paramètre 'enMs' pour l'affichage en mode 'Minuteur'.
----------------------------------------------------------------------------------------------------
*/
{
#ifdef _test_
Serial.println( ">> Début de 'afficTemps' ...");
#endif
if( enMs && (temps / 60000 > 9)) {
_affic.setTextSize( 2);
} else {
_affic.setTextSize( 3);
}
_AfficCoord pos;
if( ! enMs) pos.x = 20;
pos.y = 35;
_affic.fillRect( pos.x, pos.y, 128, 24, BLACK); // Effacement du temps affiché
_affic.setCursor( pos.x, pos.y);
if( temps < 0) {
temps = abs( temps);
_affic.print( "-");
} else {
if( ! enMs) _affic.print( " ");
}
long secNb = temps;
if( enMs) secNb /= 1000;
// Minutes
int nb = secNb / 60;
if(( nb < 10) && ! enMs) _affic.print( " ");
_affic.print( nb);
// Secondes
_affic.print( ":");
nb = secNb % 60;
if( nb < 10) _affic.print( 0);
_affic.print( nb);
// Centièmes
if( enMs) {
_affic.print( ":");
nb = (temps % 1000) / 10;
if( nb < 10) _affic.print( 0);
_affic.print( nb);
}
_affic.display();
_affic.setTextSize( 1);
} // afficTemps(...)
//==================================================================================================
bool boutPress()
/*
Fonction : Indiquer si le bouton a été appuyé.
Paramètres : /
Retour : Indicateur de bouton appuyé.
Description : /
Création : 30/11/24, M. Chauvat.
Evolutions : /
----------------------------------------------------------------------------------------------------
*/
{
#ifdef _test_
Serial.println( ">> Début de 'boutPress' ...");
#endif
static bool BoutEtatPrec = HIGH;
bool boutEtat;
boutEtat = digitalRead( _brocBout_);
if( boutEtat != BoutEtatPrec) {
BoutEtatPrec = boutEtat;
return ( boutEtat == LOW);
}
return false;
} // boutPress
//==================================================================================================
bool buzzMusiqExec( int brocNum, enum _BuzzMusiqExecOrdres ordre, int execNbr, int pauseDuree)
/*
Fonction : Exécuter un air de musique.
Paramètres :
- En entrée :
- brocNum : numéro de la broche connectée au buzzer
- ordre : ordre d'exécution
-[execNbr] : nombre d'exécutions (si ordre = '...::Commencer')
-[pauseDuree]: durée de la pause entre chaque exécution, en secondes
(si ordre = '...::Commencer' et nbExec # 1)
Retour : indicateur d'exécution en cours
Description : /
Création : 07/12/24, M. Chauvat.
Evolutions : /
----------------------------------------------------------------------------------------------------
*/
{
#ifdef _test_
Serial.println( ">> Début de 'buzzMusiqExec' ...");
#endif
// Notes
/*
static const byte NoteNbr = 39;
static const int NoteFrequs[ NoteNbr] = {
392, 392, 392, 311, 466, 392, 311, 466, 392, 587,
587, 587, 622, 466, 369, 311, 466, 392, 784, 392,
392, 784, 739, 698, 659, 622, 659, 415, 554, 523,
493, 466, 440, 466, 311, 369, 311, 466, 392};
static const int NoteDurees[ NoteNbr] = {
350, 350, 350, 250, 100, 350, 250, 100, 700, 350,
350, 350, 250, 100, 350, 250, 100, 700, 350, 250,
100, 350, 250, 100, 100, 100, 450, 150, 350, 250,
100, 100, 100, 450, 150, 350, 250, 100, 750};
*/
/* // Sirène des pompiers
// https://www.bruitparif.fr/documentation-du-bruit-des-sirenes-des-vehicules-prioritaires/
static const byte NoteNbr = 2;
static const int NoteFrequs[ NoteNbr] = { 735, 488}; // Si, La
static const int NoteDurees[ NoteNbr] = { 1000, 1000};
*/
// https://ledisrupteurdimensionnel.com/arduino/creer-des-sons-avec-arduino-buzzer/
static const byte NoteNbr = 7;
static const int NoteFrequs[ NoteNbr] = {
262, 196, 196, 220, 196, 247, 262};
static const int NoteDurees[ NoteNbr] = {
250, 125, 125, 250, 250, 250, 250};
static byte NoteNum; // numéro de la note en cours
static bool NoteEnExec; // indicateur de note en cours d'exécution
static unsigned int NoteDeb; // instant de début de la note en cours
static int ExecNbr; // nombre d'exécutions
static int ExecNum; // numéro d'ordre de l'exécution en cours
static int PauseDuree; // durée de la pause entre chaque exécution
static bool ExecEnPause; // indicateur d'exécution en pause
static unsigned int PauseDeb; // instant de début de la pause en cours
switch( ordre) {
case _BuzzMusiqExecOrdres::Commencer :
ExecNbr = execNbr;
PauseDuree = pauseDuree;
ExecNum = 1;
NoteNum = 0;
break;
case _BuzzMusiqExecOrdres::Poursuivre :
if( NoteEnExec) {
if(( millis() - NoteDeb) < NoteDurees[ NoteNum]) return true; // durée de la note non écoulée
// Arrêt de la note
noTone( brocNum);
NoteEnExec = false;
ExecEnPause = false;
return true;
} else {
if(( millis() - NoteDeb) < ( NoteDurees[ NoteNum] * 1.3)) return true; // durée du silence non
// écoulée
}
// Passage à la note suivante
if( NoteNum < NoteNbr - 1) {
NoteNum++;
break;
}
// Pause (si demandée)
if( PauseDuree > 0) {
if( ! ExecEnPause) {
ExecEnPause = true;
PauseDeb = millis();
return true;
} else {
if(( millis() - PauseDeb) < (PauseDuree * 1000)) return true;
}
}
// Exécution suivante
if(( ExecNbr > 0) && (ExecNum == ExecNbr)) return false;
ExecNum++;
NoteNum = 0;
break;
case _BuzzMusiqExecOrdres::Arreter :
noTone( brocNum);
return false;
}
tone( brocNum, NoteFrequs[ NoteNum]);
NoteDeb = millis();
NoteEnExec = true;
return true;
} // buzzMusiqExec(...)
//==================================================================================================
void chrono( bool& fonctChang)
/*
Fonction : Gérer le fonctionnement en mode 'chronomètre'.
Paramètres :
- En entrée : /
- En sortie : indicateur de demande de changement de fonction
Description : /
Création : 30/11/24, M. Chauvat.
Evolutions : /
----------------------------------------------------------------------------------------------------
*/
{
#ifdef _test_
Serial.println( ">> Début de 'chrono' ...");
#endif
_fonctActive = false;
afficInit();
// Mémorisation de l'instant de début de comptage
unsigned long comptDeb; // instant de début de comptage
long temps;
while( true) {
// Prise en compte du bouton
if( boutPress()) {
if( ! _fonctActive) {
_fonctActive = true;
comptDeb = millis();
_afficActif = true;
} else {
_afficActif = ! _afficActif;
}
}
// Prise en compte de l'encodeur
if(( ! _fonctActive) || ( ! _afficActif)) {
enum _EncodActs act = encodActLire( false);
if( act != _EncodActs::Aucune) {
if( ! _fonctActive) {
if( act != _EncodActs::BoutPress) {
fonctChang = true;
return;
}
} else {
if( act == _EncodActs::BoutPress) {
fonctChang = false;
return;
}
}
}
}
// Mise à jour de l'affichage
if( _afficActif) {
temps = millis() - comptDeb;
afficTemps( temps, true);
}
// Gestion de l'allumage des leds
ledAllum();
} // while( true)
} // chrono()
//==================================================================================================
enum _EncodActs encodActLire( bool attAct)
/*
Fonction : Lire une action sur l'encodeur.
Paramètres :
- En entrée :
- attAct : indicateur d'attente d'une action
- En sortie : /
Retour : action lue.
Description : Programme inspiré du programme 'prgArduino-1-TestKY040simple.ino'présenté à la page
'https://passionelectronique.fr/encodeur-rotatif-incremental-mecanique/'
Création : 19/11/24, M. Chauvat.
Evolutions :
- 25/11/24, M. Chauvat : Ajout du paramètre 'attAct' pour gérer le cas où une action n'est pas
attendue (obligatoire).
----------------------------------------------------------------------------------------------------
*/
{
#ifdef _test_
Serial.println( ">> Début de 'encodActLire' ...");
#endif
int boutEtat, molAEtat, molBEtat;
static int BoutEtatPrec = digitalRead( _brocEncodBout_);
static int MolAEtatPrec = digitalRead( _brocEncodMolA_);
do {
// Lecture des nouvelles positions
boutEtat = digitalRead( _brocEncodBout_);
molAEtat = digitalRead( _brocEncodMolA_);
molBEtat = digitalRead( _brocEncodMolB_);
// Bouton
if( boutEtat != BoutEtatPrec) {
BoutEtatPrec = boutEtat;
if( boutEtat == LOW) { // bouton appuyé
delay( 10);
return _EncodActs::BoutPress;
}
}
// Molette
if( molAEtat != MolAEtatPrec) {
MolAEtatPrec = molAEtat;
if( molAEtat == LOW) {
if( molAEtat != molBEtat) {
return _EncodActs::MolRotHor;
} else {
return _EncodActs::MolRotInv;
}
}
delay( 10);
}
} while( attAct);
return _EncodActs::Aucune;
} // encodActLire
//==================================================================================================
void ledAllum()
/*
Fonction : Gestion de l'allumage des leds (en fonction du contexte).
Paramètres : /
Description : L'allumage des leds dépend de la fonction et de l'étape en cours de cette fonction,
selon les règles suivantes :
- Fonction en attente de démarrage : la led verte est allumée,
- Fonction en cours d'exécution : la led verte clignote,
- Affichage en pause (en mode 'chrono'): les 2 leds clignotent en alternance,
- Fonction en pause (en mode 'Minuteur') : seule, la led rouge clignote,
- Minutage terminé (temps écoulé) : les 2 leds clignotent en même temps.
Remarque : la fréquence de clignotement est la seconde.
Le traitement comporte 6 étapes :
1) Identification du contexte de fonctionnement,
2) Détermination des modalités d'allumage des leds en fonction du contexte,
3) Détermination du besoin (ou non) de modifier l'état des leds,
Si l'allumage doit être modifié :
4) Identification des états à affecter aux leds,
5) Affectation des états aux leds,
6) Mémorisation du contexte et des états (pour pouvoir déterminer les états à
affecter lors de l'itération suivante).
Création : 11/11/24, M. Chauvat.
Evolutions : /
----------------------------------------------------------------------------------------------------
*/
{
#ifdef _test_
Serial.println( ">> Début de 'ledAllum' ...");
#endif
// Contexte de fonctionnement (fonction + exécution + affichage/pause)
enum ContexFonct{ ChronoEnAttente = 1, ChronoEnCours, ChronoAfficEnPause,
MinutEnAttente, MinutEnCours, MinutEnPause, MinutTerm};
enum ContexFonct contexFonct;
static enum ContexFonct ContexFonctPrec;
static bool Led1Prec, Led2Prec; // état des leds à l'itération précédente
static unsigned long ClignDernDeb; // début du dernier clignotement
// Identification du contexte de fonctionnement
switch( _fonctCour) {
case _SystFoncts::Chrono :
if( ! _fonctActive) {
contexFonct = ContexFonct::ChronoEnAttente;
} else {
if( _afficActif) {
contexFonct = ContexFonct::ChronoEnCours;
} else {
contexFonct = ContexFonct::ChronoAfficEnPause;
}
}
break;
case _SystFoncts::Minuteur :
if( ! _fonctActive) {
contexFonct = ContexFonct::MinutEnAttente;
} else {
if( _afficActif) {
if( _minutFin == 0) {
contexFonct = ContexFonct::MinutEnCours;
} else {
contexFonct = ContexFonct::MinutTerm;
}
} else {
contexFonct = ContexFonct::MinutEnPause;
}
}
} // switch( _fonctCour)
// Détermination du mode d'allumage des leds
enum AllumMode{ Led1, // on n'utilise que la led n° 1 (avec ou sans clignotement)
Led2, // on n'utilise que la led n° 2 (avec ou sans clignotement)
Led1Ou2, // On allume les 2 leds alternativement (avec clignotement)
Led1Et2}; // on allume les 2 leds simultanément (avec ou sans clignotement)
enum AllumMode allumMode = AllumMode::Led1;
int clignFrequ = 0; // fréquence de clignotement en secondes
switch( contexFonct) {
case ContexFonct::ChronoEnCours :
case ContexFonct::MinutEnCours :
clignFrequ = 1;
break;
case ContexFonct::ChronoAfficEnPause :
allumMode = AllumMode::Led1Ou2;
clignFrequ = 1;
break;
case ContexFonct::MinutEnPause :
allumMode = AllumMode::Led2;
clignFrequ = 1;
break;
case ContexFonct::MinutTerm :
allumMode = AllumMode::Led1Et2;
clignFrequ = 1;
}
// Détermination du besoin (ou non) de modifier l'état des leds
if( contexFonct == ContexFonctPrec) {
if( clignFrequ == 0) return;
if((( millis() - ClignDernDeb) / 1000) < abs( clignFrequ)) return;
}
// Identification des états à affecter aux leds
bool led1 = false, led2 = false; // indicateur d'allumage des leds
if( clignFrequ == 0) {
if( allumMode == AllumMode::Led1) {
led1 = true;
} else if( allumMode == AllumMode::Led2) {
led2 = true;
} else {
led1 = true;
led2 = true;
}
} else {
ClignDernDeb = millis();
if( contexFonct != ContexFonctPrec) {
switch( allumMode) {
case AllumMode::Led1 :
led1 = true;
break;
case AllumMode::Led1Ou2 :
case AllumMode::Led2 :
led2 = true;
break;
case AllumMode::Led1Et2 :
led1 = true;
led2 = true;
}
} else { // -> clignotement des leds allumées
if( allumMode != AllumMode::Led2) led1 = ! Led1Prec;
if( allumMode != AllumMode::Led1) led2 = ! Led2Prec;
}
}
// Affectation des états aux leds
digitalWrite( _brocLedVerte_, led1);
digitalWrite( _brocLedRouge_, led2);
// Mémorisation du contexte et des états
ContexFonctPrec = contexFonct;
Led1Prec = led1;
Led2Prec = led2;
} // ledAllum()
//==================================================================================================
void minut( bool& fonctChang)
/*
Fonction : Gérer le fonctionnement en mode 'minuteur'.
Paramètres :
- En entrée : /
- En sortie : indicateur de demande de changement de fonction
Description : /
Création : 30/11/24, M. Chauvat.
Evolutions : /
----------------------------------------------------------------------------------------------------
*/
{
#ifdef _test_
Serial.println( ">> Début de 'minut' ...");
#endif
_fonctActive = false;
afficInit();
// Mémorisation des instants de début de comptage et de pause
unsigned long comptDeb, // instant de début de comptage
pauseDeb, // instant de début de dernière pause
pauseTempsCum;// temps cumulé de pause
long temps;
while( true) {
// Prise en compte du bouton
if( boutPress()) {
if( ! _fonctActive) {
_fonctActive = true;
comptDeb = millis();
_afficActif = true;
pauseTempsCum = 0;
_minutFin = 0;
} else {
if( _minutFin > 0) break; // la pression sur le bouton est ignorée si le temps est écoulé
if( _afficActif) {
pauseDeb = millis();
} else {
pauseTempsCum += (millis() - pauseDeb);
}
_afficActif = ! _afficActif;
}
}
// Prise en compte de l'encodeur
if(( ! _fonctActive) || ( ! _afficActif) || ( _minutFin > 0)) {
enum _EncodActs act = encodActLire( false);
if( act != _EncodActs::Aucune) {
if( ! _fonctActive) {
if( act == _EncodActs::BoutPress) {
minutReglModif( _minutRegl);
fonctChang = false;
} else {
fonctChang = true;
}
return;
} else {
if( act == _EncodActs::BoutPress) {
fonctChang = false;
return;
}
}
}
}
// Mise à jour de l'affichage (si 'en cours')
if( _afficActif) {
temps = _minutRegl.temps - (((millis() - comptDeb) - pauseTempsCum) / 1000L);
afficTemps( temps, false);
}
// Gestion du signal de fin de minutage
int intIgn;
if( _afficActif) {
if( temps <= 0) {
if( _minutFin == 0) {
_minutFin = millis();
buzzMusiqExec( _brocBuzzer_, _BuzzMusiqExecOrdres::Commencer, 0, 10);
} else {
if(( millis() - _minutFin) < _minutRegl.finSignDuree) {
buzzMusiqExec( _brocBuzzer_, _BuzzMusiqExecOrdres::Poursuivre, intIgn, intIgn);
} else {
buzzMusiqExec( _brocBuzzer_, _BuzzMusiqExecOrdres::Arreter, intIgn, intIgn);
}
}
}
}
// Gestion de l'allumage des leds
ledAllum();
} // while( true)
} // minut()
//==================================================================================================
void minutReglModif( _MinutRegl& regl)
/*
Fonction : Modifier les règles de fonctionnement du minuteur.
Paramètres :
- En entrée :
- regl : règles actuelles
- En sortie :
- regl : nouvelles règles
Description : /
Création : 16/11/24, M. Chauvat.
Evolutions :
- 06/12/24, M. Chauvat : Ajout de la mémorisation des règles.
----------------------------------------------------------------------------------------------------
*/
{
#ifdef _test_
Serial.println( ">> Début de 'minutReglModif' ...");
#endif
// Affichage de l'entête d'écran
int16_t entHaut; // hauteur de l'entête
afficEntete( "REGLAGE> Minuteur", entHaut);
// Affichage des règles actuelles
int16_t lignPos[2], // position (en X) des lignes de règle
valPos[2]; // position (en Y) des valeurs (nombre de minutes et de secondes)
_AfficCoord pos;
int val,
vals[2][2]; // valeurs pour chaque règle
{
// Temps de minutage
pos.y = entHaut + _afficLignInter_;
lignPos[0] = pos.y;
_affic.setCursor( 0, pos.y);
_affic.print( "Temps : ");
val = _minutRegl.temps / 60; // nombre de minutes
vals[0][0] = val;
pos.x = _afficCarLarg_ * 11;
valPos[0] = pos.x;
affic( val, pos, 2, false, true);
_affic.print( ":");
val = _minutRegl.temps % 60; // nombre de secondes
vals[0][1] = val;
pos.x += _afficCarLarg_ * 3;
valPos[1] = pos.x;
affic( val, pos, 2, true, true);
// Durée de la sonnerie
pos.y += _afficCarHaut_ + _afficLignInter_;
lignPos[1] = pos.y;
_affic.setCursor( 0, pos.y);
_affic.print( "Sonnerie : ");
val = _minutRegl.finSignDuree / 60; // nombre de minutes
vals[1][0] = val;
pos.x = valPos[0];
affic( val, pos, 2, false, true);
_affic.print( ":");
val = _minutRegl.finSignDuree % 60;
vals[1][1] = val;
pos.x = valPos[1];
affic( val, pos, 2, true, true);
}
// Saisie des nouvelles valeurs
for( int reglNum = 0; reglNum < 2; reglNum++) {
pos.y = lignPos[reglNum];
for( int valNum = 0; valNum < 2; valNum++) {
pos.x = valPos[valNum];
valSaisir( vals[reglNum][valNum], 0, 59, pos);
}
}
// Enregistrement des nouvelles valeurs
_minutRegl.temps = vals[0][0] * 60 + vals[0][1];
_minutRegl.finSignDuree = vals[1][0] * 60 + vals[1][1];
// Mémorisation des valeurs
EEPROM.put( _minutReglTempsMemoAdr_, _minutRegl.temps);
EEPROM.put( _minutReglFinSignDureeMemoAdr_, _minutRegl.finSignDuree);
} // minutReglModif()
//==================================================================================================
void valSaisir( int& val, int valMin, int valMax, _AfficCoord pos)
/*
Fonction : Modifier une valeur (affichée).
Paramètres :
- En entrée :
- val : valeur initiale
- valMin : valeur minimale
- valMax : valeur maximale
- pos : position de la saisie (en X,Y; à gauche, en haut)
- En sortie :
- val : nouvelle valeur
Retour : /
Description : /
Création : 20/11/24, M. Chauvat.
Evolutions : /
----------------------------------------------------------------------------------------------------
*/
{
#ifdef _test_
Serial.println( ">> Début de 'valSaisir' ...");
#endif
// Affichage de la valeur sur fond blanc
affic( val, pos, 2, false, false); // remarque : il faudrait calculer le '2' ...
enum _EncodActs act;
while( true) {
// Lecture d'une action sur l'encodeur
act = encodActLire( true);
// Prise en compte de l'action
switch( act) {
case _EncodActs::BoutPress : // validation de la nouvelle valeur
// Affichage de la valeur en "positif"
affic( val, pos, 2, false, true);
return;
case _EncodActs::MolRotHor :
if( val < valMax) {
val++;
} else {
val = valMin;
}
break;
case _EncodActs::MolRotInv :
if( val > valMin) {
val--;
} else {
val = valMax;
}
}
// Affichage de la nouvelle valeur
affic( val, pos, 2, false, false);
}
} // valSaisir
//==================================================================================================