Maintenant abordons la logique de décision, c'est à dire l'ordonnancement des tâches dans la loop() en fonction d'un certain nombre de variables d'état globales.
1- Traitement des messages CAN
C'est le compteur d'octets non lus dans le buffer circulaire Ncan qui conditionne cette tâche. Si Ncan > 2 il y a forcément un message (la taille minimum est 3) et dans ce cas on le traite.
2- Détection d'appui sur le bouton de l'encodeur.
C'est un bouton que l'on teste à chaque tour de loop
//--- Detection d'appui sur SWenc :
SWenc.update();
if ( SWenc.fell() ) {
gSWState = !gSWState;
gSW = true; // appui effectif
gConfig = true; // mode global configuration
}
Ce bouton poussoir est géré par la bibliothèque Bounce2 comme n'importe quel bouton poussoir.
Chaque appui met à true une variable gConfig (mode configuration) et gSW (appui détecté) et inverse la variable gSWState pour différencier les cas du 1er et du 2ème appui.
Le premier appui sert à afficher le choix de la variable à configurer en fonction de la position de l'encodeur qui permet, en tournant le bouton dans un sens ou un autre, de choisir parmi 10 variables de configuration, avec une variable gMode :
// premier appui : mode choix parametre de configuration
if (gSWState && gConfig ) { // passage en choix de mode
if (gSW) {
afficheTitre();
gSW=false;
}
long newPosition = myEnc.read()/4;
if (newPosition != oldPosition) {
if ( newPosition > oldPosition) {
if (gMode < 9) gMode ++;
} else {
if (gMode > 0) gMode--;
}
oldPosition = newPosition;
afficheTitre();
MajLeds();
}
}
La fonction afficheTitre est ici :
// affichage titre
void afficheTitre() {
lcd.clear();
lcd.print(gMode);
if (gMode < 10) lcd.print(' ');
switch (gMode) {
case 0:
lcd.print("Inverser sens");
break;
case 1:
lcd.print("Choix adresse");
break;
case 2:
lcd.print("Mesure vitesse");
break;
case 3:
lcd.print("Choix cran 30");
break;
case 4:
lcd.print("Choix cran 60");
break;
case 5:
lcd.print("Choix cran 90");
break;
case 6:
lcd.print("Choix Vit Max");
break;
case 7:
lcd.print("Choix Vit Min");
break;
case 8:
lcd.print("Distance M/V cm");
break;
case 9:
lcd.print("Erase EEProm ");
break;
default:
break;
}
}
Le deuxième appui sert à dérouler le code de configuration en fonction de la variable gMode :
// Deuxieme appui : passage en modification de parametre
if (!gSWState && gConfig && gSW) { // sortie du choix de mode
gSW=false;
switch (gMode) {
case 0: // inverser sens
lcd.setCursor(0,1);
lcd.print("Normal : taper 0 ");
lcd.setCursor(0,2);
lcd.print("Inverse : taper 1 ");
gSaisie = true;
break;
case 1: // choix @ DCC
lcd.setCursor(0,1);
lcd.print("adresse DCC : ");
gSaisie = true;
gSaisieValeurIndex = 0;
break;
case 2:
lcd.setCursor(0,1);
lcd.print("Attente... ");
gMesureVitesse = true;
gSaisie = true;
//gSaisieValeurIndex = 0;
break;
case 3:
lcd.setCursor(0,1);
lcd.print("Cran vit 30 : ");
gSaisie = true;
gSaisieValeurIndex = 0;
break;
case 4:
lcd.setCursor(0,1);
lcd.print("Cran vit 60 : ");
gSaisie = true;
gSaisieValeurIndex = 0;
break;
case 5:
lcd.setCursor(0,1);
lcd.print("Cran vit 90 : ");
gSaisie = true;
gSaisieValeurIndex = 0;
break;
case 6:
lcd.setCursor(0,1);
lcd.print("Cran vit max: ");
gSaisie = true;
gSaisieValeurIndex = 0;
break;
case 7:
lcd.setCursor(0,1);
lcd.print("Cran vit min: ");
gSaisie = true;
gSaisieValeurIndex = 0;
break;
case 8:
lcd.setCursor(0,1);
lcd.print("Distance M/V: ");
gSaisie = true;
gSaisieValeurIndex = 0;
break;
case 9:
lcd.setCursor(0,2);
lcd.print("Erase & Reset");
EraseEEProm();
reset();
break;
default:
gConfig=false;
break;
}
gChange = true;
}
Ca a l'air simple comme cela, mais ce switch sert à autoriser la saisie de valeurs sur le clavier tactile.
3- lecture du clavier tactile
Donc, en plus de gMode, d'autres variables viennent autoriser la gestion du clavier tactile dont la bonne exécution se terminera par la modification d'un paramètre de configuration qui sera sauvegardé en EEPROM.
Voici la séquence d'entrée dans ce code : elle réactive le rétro-éclairage du LCD
// gestion du clavier tactile
if (digitalRead(PIN_TOUCH_IRQ) == LOW) {
int Key = Read_Keypad();
lcd.backlight();
retrotime = millis();
Deux cas se présentent :
A- Hors configuration on ne fait que choisir une des 12 locos
// choix registre/loco hors configuration et saisie
if ((!gConfig)&&(!gSaisie)&&(Key < 12)) {
gId = Key;
gCurrentLoco = gConfigLocos[gId];
gChange = true;
MajLeds();
}
gId est le numéro de loco en cours, qui est utilisé partout pour paramètrer cette loco.
B- En configuration, on modifie un paramêtre, fonction de gId et il y a autant de programme que de paramêtre.
Cas de gMode = 0 (inversion de sens de la loco)
// mode saisie en configuration
if ((gSaisie)&&(Key < 12)) {
switch (gMode) {
//--- INVERSION de SENS
case 0:
lcd.setCursor(0,1);
gChange = false;
if (gCurrentLoco.inverse) {
lcd.print("Sens Inverse ");
} else {
lcd.print("Sens Normal ");
}
if (Key==0) {
gCurrentLoco.inverse = 0;
gChange = true;
}
if (Key==1) {
gCurrentLoco.inverse = 1;
gChange = true;
}
// toute autre touche ne change pas le sens
if (gChange) {
lcd.setCursor(0,1);
if (gCurrentLoco.inverse) {
lcd.print("Sens Inverse ");
} else {
lcd.print("Sens Normal ");
}
gConfigLocos[gId] = gCurrentLoco; // sauvegarde dans la table des locos
EnregistreLocoEEProm(gId); // enregistre gCurrentLoco dans l'EEPROM
}
gSaisie=false; gConfig=false; gChange = true;
break;
Ce code affiche des choses sur l'écran LCD, modifie le paramètre CurrentLoco.inverse dans l'objet loco et sauvegarde cet objet dans l'EEPROM, puis re-initialise les variables globales d'état pour terminer le processus.
Je donne le cas gMode = 1 : saisie de l'adresse DCC de la loco sélectionnée car il peut être intéressant à réutiliser dans votre code :
//--- saisie adresse DCC
case 1:
if ((Key >= 0)&&(Key <=9)) {
gSaisieValeur[gSaisieValeurIndex] = Key;
if (gSaisieValeurIndex < 3) {
gSaisieValeurIndex++;
lcd.setCursor(14,1);
for (int c=0;c<gSaisieValeurIndex;c++) {
lcd.print(gSaisieValeur[c]);
}
lcd.print(" ");
}
}
if (Key == 10) { // back
if (gSaisieValeurIndex > 0) {
gSaisieValeurIndex--;
lcd.setCursor(14,1);
for (int c=0;c<gSaisieValeurIndex;c++) {
lcd.print(gSaisieValeur[c]);
}
lcd.print(" ");
}
}
if (Key == 11) { // enter
gChange = true;
switch (gSaisieValeurIndex) {
case 0:
gChange = false; // saisie vide
break;
case 1:
gCurrentLoco.dccAddress = gSaisieValeur[0];
break;
case 2:
gCurrentLoco.dccAddress = (gSaisieValeur[0] * 10) + gSaisieValeur[1];
break;
case 3:
gCurrentLoco.dccAddress = (gSaisieValeur[0] * 100) + (gSaisieValeur[1] * 10) + gSaisieValeur[2];
break;
}
lcd.setCursor(14,1);lcd.print(gCurrentLoco.dccAddress);lcd.print(" ");
if (gChange) {
gConfigLocos[gId] = gCurrentLoco; // sauvegarde dans la table des locos
EnregistreLocoEEProm(gId); // enregistre gCurrentLoco dans l'EEPROM
}
gSaisie=false; gConfig=false; gChange = true;
}
break;
Là le but est d'afficher la valeur initiale, de pouvoir taper sur les touches du clavier, corriger une erreur avec la touche "c" et de valider la saisie puis l'enregistrer.
J'ai mis du temps à la mettre au point donc si cela peut vous éviter d'en perdre, j'en serais ravi !
On retrouvera le même principe pour toutes les autres saisies (cran des vitesses 30, 60, 90, choix des vitesses Mini et Maxi, de la distance de mesure de vitesse).
Et pour terminer, le plus simple, c'est la gestion du temps système avec millis() pour gérer les cligotements, le rétro-éclairage, la surveillance du courant et la mise à jour des lignes d'affichage de l'écran LCD :
//clignotements Led Rouge
if (gblinkR) {
if ((millis() - gClignoteR) > 250) {
gClignoteR = millis();
digitalWrite(LedModeR,!digitalRead(LedModeR));
}
}
//clignotements Led Jaune
if (gblinkJ) {
if ((millis() - gClignoteJ) > 500) {
gClignoteJ = millis();
digitalWrite(LedModeJ,!digitalRead(LedModeJ));
}
}
// retro-eclairage
if ((millis() - retrotime) > eclairage) {
lcd.noBacklight();
}
// mise à jour de la ligne d'etat en L3
if ((gChange)&&(!gSaisie)) {
StatutL3();
}
// surveillance courant MAXIMUM 2 A
// affichage courant = la valeur maxi pendant 500 ms
if ((millis() - gCurrentSampleTime) > 500) {
gCurrentSampleTime = millis();
lcd.setCursor(16,3);lcd.print(gCurrent*5);lcd.print(' ');
gCurrent = 0;
}
int iCurrent = analogRead(A0);
if (iCurrent > gCurrent) {
gCurrent = iCurrent;
if (gCurrent > CurrentMax) { // 400 * 5 = 2000 mA
stop_DCC();
}
}
Je passe l'écran terminal pour le debugging et j'aurais aimé faire quelques mesures des temps passés dans chaque taches pour connaitre l'état de saturation du Mega2560, cela viendra plus tard. En tout cas je suis satisfait de ce code qui mériterait quand même d'être amélioré car on peut mtoujours faire mieux, avec du temps..
A suivre...