Auteur Sujet: Projet Dominique  (Lu 4488 fois)

DDEFF

  • Sr. Member
  • ****
  • Messages: 415
    • Voir le profil
Re : Projet Dominique
« Réponse #45 le: décembre 31, 2017, 12:14:06 pm »
Bonne Année à toi aussi. ;D

Magnifique programme, testé, qui contient plein d'astuces bien utiles.
A lire à tête reposée... ;)

Denis

Dominique

  • Global Moderator
  • Hero Member
  • *****
  • Messages: 805
  • 100% Arduino et N
    • Voir le profil
Re : Projet Dominique
« Réponse #46 le: décembre 31, 2017, 03:39:42 pm »
Merci Denis,

C'est justement pour t'aider à construire ton réseau que je publies tous ces détails, car tu as choisis aussi le bus CAN et tu vas aussi partir sur DCCpp.

Une des pièces maitresses d'un réseau est justement la grosse fonction qu'il faut mettre entre la capture des informations de rétrosignalisation et le pilotage des trains = le Gestionnaire de réseau. J'explique d'abord tout ce que j'ai mis autour.

Evidemment tu es en plein là dedans !
http://forum.locoduino.org/index.php?topic=211

La grande majorité des modélistes ne voient pas d'autre solution que d'acheter une centrale du commerce (et hop : 500€ !) et d'y ajouter un logiciel sur PC comme RRTC (et hop, encore : 500€ !).

Tu sais bien que nous, à Locoduino, on souhaite expliquer comment faire autrement, en retroussant les manches et avec peu d'huile de coude ? ... si, un peu quand même ! Avec un poil de maitrise de l'environnement Arduino et quelques séances de lecture de Locoduino où on commence à pouvoir trouver l'essentiel pour faire tout soi-même (DIY). Un bon fer à souder et quelques composants et hop !



Mon exemple n'est pas le seul, loin s'en faut. J'ai choisi cette architecture, maintenant je continue sur la lancée, mais je sais qu'elle reste évolutive (module par module), ce dont je ne vais pas me priver.

Donc il y aura plein d'autres épisodes à suivre ...

Dominique
Utilisez votre esprit, vos mains et votre coeur pour bâtir quelque chose qui vous dépasse (Tim Cook, MIT, 9 juin 2017).

Dominique

  • Global Moderator
  • Hero Member
  • *****
  • Messages: 805
  • 100% Arduino et N
    • Voir le profil
Re : Projet Dominique
« Réponse #47 le: décembre 31, 2017, 05:15:47 pm »
Voici maintenant la gestion du clavier tactile 12 touches.

J'ai utilisé un composant comme celui-ci :
https://www.ebay.fr/itm/1Pcs-Mpr121-Capacitive-Touch-Keypad-Shield-Module-Sensitive-Key-Keyboard-New-I-P/252906299459?hash=item3ae263e043:g:JrAAAOSwx2dYIPLs

Il a l'avantage d'avoir sa puce MPR121 du coté opposé aux touches tactiles et 4 trous de fixation, ce qui permet de l'installer avec discrétion et élégance. J'ai réalisé un cache plastifié qui est posé dessus et tient par les même vis de fixation. Ce cache me permet de reorganiser les touches autrement.

Dans les globales j'ai ajouté :
///////////// Touchpad à l'adresse I2C = 0x5A ///////
Adafruit_MPR121 cap = Adafruit_MPR121();
uint16_t lasttouched = 0;
uint16_t currtouched = 0;
int Key;
// Attach the MPR121's IRQ pin to digital pin 4
const int PIN_TOUCH_IRQ = 4;

Pour lire ce clavier tactile, j'ai écrit une fonction Read_Keypad qui retourne un numéro de 0 à 11, ou 12 si rien (car le 0 est déjà affecté à une touche).

///////////////////////////////////////////////////////////////////////
// LECTURE CLAVIER TACTILE
///////////////////////////////////////////////////////////////////////

byte Read_Keypad(void)
{
  uint8_t i;
  bool keydown = false;
  // Get the currently touched pads
  currtouched = cap.touched();
  for (i=0; i<12; i++) {
    // if it *is* touched and *wasnt* touched before => key down
    if ((currtouched & _BV(i)) && !(lasttouched & _BV(i)) ) {
      keydown = true;
      //Serial.print(i); Serial.println(" touched");
      break;
    }
    // if it *was* touched and now *isnt* => key up (ignored)
    if (!(currtouched & _BV(i)) && (lasttouched & _BV(i)) ) {
      keydown = false;
      //Serial.print(i); Serial.println(" released");
      break;
    }
  } 
  lasttouched = currtouched;
  switch (i) {
    case 0:  i=9;  break;
    case 1:  i=6;  break;
    case 2:  i=3;  break;
    case 3:  i=11; break;
    case 4:  i=8;  break;
    case 5:  i=5;  break;
    case 6:  i=2;  break;
    case 7:  i=0;  break;
    case 8:  i=7;  break;
    case 9:  i=4;  break;
    case 10: i=1;  break;
    case 11: i=10; break;   
  }
  if (keydown) {
    return (i);
  } else {
    return (i+12);
  }
}

On remarque le switch qui permet de changer les numéros de touches, car l'ordre des touches du produit de base n'est pas bien adapté à ce que je recherche.

Dans la loop, la lecture des appuis sur le clavier tactile est très simple :

   // gestion du clavier tactile
   if (digitalRead(PIN_TOUCH_IRQ) == LOW) {
    int Key = Read_Keypad();
   }

Dans mon programme, les entrées sur ce clavier tactile sont très nombreuses et dépendent de plusieurs indicateurs, par exemple :

- Hors configuration, l'appui sur une touche sélectionne une loco dans la table et affiche ses variables courantes (nom, vitesse, direction, ..)

    // choix registre/loco hors configuration et saisie
    if ((!gConfig)&&(!gSaisie)&&(Key < 12)) {
      gId = Key;
      gCurrentLoco = gConfigLocos[gId];
      gChange = true;
      MajLeds();
    }

- En configuration, le clavier sert à saisir des valeurs sur 1, 2 ou 3 chiffres significatifs, avec affichage sur l'écran LCD, possibilité de correction avec la touche 'c' (numéro 10)  et validation avec la touche 11.

Par exemple, pour la configuration de l'adresse DCC :

        //--- saisie adresse DCC
        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;
        }

Utilisez votre esprit, vos mains et votre coeur pour bâtir quelque chose qui vous dépasse (Tim Cook, MIT, 9 juin 2017).

Dominique

  • Global Moderator
  • Hero Member
  • *****
  • Messages: 805
  • 100% Arduino et N
    • Voir le profil
Re : Projet Dominique
« Réponse #48 le: décembre 31, 2017, 05:24:57 pm »
Et maintenant le mesure de vitesse des trains !

Très simplement j'ai tiré 2 fils (signal et masse) entre 1 capteur de consommation d'une zone droite et horizontale d'une longueur de 172 cm sur mon réseau et une entrée capt1 de l'Arduino.

Quand une loco entre dans cette zone, capt1 passe à l'état bas. Quand elle sort de la zone, capt1 passe à l'état haut.

Le code pour gérer cela est :

void mesureV1() {
  int v1 = digitalRead(capt1);
  if (v1 != gOldcapt1) {    // changement d'état capt1
    gOldcapt1 = v1;
    if (v1 == 0) {          // zone occupée
      if (gModeMV1 == 0) {  // entree dans zone de mesure 1
        gModeMV1 = 1;
        gTempsMV1 = millis();
        lcd.setCursor(0,1);
        lcd.print("Vitesse 1          ");
      }
    } else {                // v1 = 1 : zone libérée
      if (gModeMV1 == 1) {  // sortie et calcul vitesse
        gModeMV1 = 0;
        gTempsMV1 = millis() - gTempsMV1;
        gVitesse1 = calcul(gTempsMV1);
        lcd.setCursor(0,1);
        lcd.print("Vitesse 1 = ");
        lcd.print(gVitesse1);lcd.print(" km/h ");
        lcd.backlight();
        retrotime = millis();
      }
    }
  }
}

Le principe est simple : On enregistre le temps système (millis()) à l'entrée dans la zone, puis à la sortie de la zone. Ensuite on utilise la différence de temps et la longueur de la zone pour calculer la vitesse à l'échelle N et on l'affiche sur le LCD. On pourrait configurer cette longueur dans le configurateur.

Le calcul de la vitesse est :

int calcul(unsigned long temps) {         // exemple 100 cm en 10 s => 57,6 km/h
  unsigned long VKM;
  if (temps>0) { //VKM=(gDistanceMV*5760)/temps; // 172*5760 / 10000 = 57,60 km/h
    VKM=990720/temps; // ici 172*5760 / 10000 = 57,60 km/h
    Serial.println(VKM);
  }
  return ((int)VKM);
}
« Modifié: décembre 31, 2017, 05:27:40 pm par Dominique »
Utilisez votre esprit, vos mains et votre coeur pour bâtir quelque chose qui vous dépasse (Tim Cook, MIT, 9 juin 2017).

Dominique

  • Global Moderator
  • Hero Member
  • *****
  • Messages: 805
  • 100% Arduino et N
    • Voir le profil
Re : Projet Dominique
« Réponse #49 le: janvier 03, 2018, 03:22:44 pm »
Maintenant il faut assembler tous les morceaux pour que l'Arduino fasse toutes ces tâches séquentiellement (à l'échelle humaine on ne s'en rend pas compte et cela semble simultané) et sans blocage. Ce qui compte c'est que toutes les commandes de l'interface utilisateur soit les plus instantanées possibles.

On va donc placer dans le loop() des séquences de code, les unes après les autres, chacune se chargeant d'une tâche simple. Aucune de ces séquence ne doit bloquer l'Arduino :

1- récupérer un message CAN et le traiter (on l'a vu ici http://forum.locoduino.org/index.php?topic=290.msg4319#msg4319)
2- détecter un appui sur le bouton poussoir de l'encodeur quadratique
---> le 1er appui démarre le choix d'une séquence de configuration
---> le 2ème appui démarre la modification d'un paramètre
3- lire le clavier tactile (vu ici http://forum.locoduino.org/index.php?topic=290.msg4328#msg4328)
---> en mode configuration c'est une saisie de paramètre
---> hors mode configuration, c'est une sélection de loco
4- lire la position de l'encodeur quadratique
---> en configuration, c'est une sélection de paramètre
---> hors configuration, c'est une variation de vitesse de la loco sélectionnée
5- lire les potentiomètres de vitesse (vu ici http://forum.locoduino.org/index.php?topic=290.msg4320#msg4320)
6- gérer les boutons et leds de direction (idem)
7- gérer les boutons "panic", F0 (FL), F1 et F2
8- mesurer la vitesse des trains dans 2 zones distinctes (vu ici http://forum.locoduino.org/index.php?topic=290.msg4328#msg4328)
9- gérer les clignotements des leds
10- gérer le rétro-éclairage de l'écran LCD
11- gérer la mise à jour des affichages de l'écran LCD (état de la loco sélectionnée)
12- surveiller le courant pour couper le DCC en cas de court-circuits
13- gérer l'interface avec un petit moniteur de debugging sur l'écran terminal

OUF !!!!

Il en fait des choses cet Arduino  (MEGA). Mais certaines des fonctions listées ci-dessus sont conditionnées par des variables qui permettent d'exécuter certains parties du code et pas d'autres.

C'est ce que je vais appeler pompeusement ici "la logique de décision".

Dans un tel projet, c'est très important. Définir les variables globales d'état de ces séquences conditionne la réussite du projet.
Cela demande un long moment de réflexion avant d'écrire le code. Tout au plus, on peut écrire des petits programmes pour tester séparément les différentes parties du code, puis on assemble le tout à la fin. C'est ce que j'ai fait et je pense que je modifierai encore le code pour l'optimiser si cela est nécessaire.

« Modifié: janvier 03, 2018, 04:07:41 pm par Dominique »
Utilisez votre esprit, vos mains et votre coeur pour bâtir quelque chose qui vous dépasse (Tim Cook, MIT, 9 juin 2017).

Dominique

  • Global Moderator
  • Hero Member
  • *****
  • Messages: 805
  • 100% Arduino et N
    • Voir le profil
Re : Projet Dominique
« Réponse #50 le: janvier 03, 2018, 04:50:57 pm »
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...
« Modifié: janvier 03, 2018, 05:04:53 pm par Dominique »
Utilisez votre esprit, vos mains et votre coeur pour bâtir quelque chose qui vous dépasse (Tim Cook, MIT, 9 juin 2017).