Auteur Sujet: Bouton poussoir  (Lu 14800 fois)

bricoleau

  • Jr. Member
  • **
  • Messages: 51
    • Voir le profil
Bouton poussoir
« le: janvier 05, 2016, 11:57:14 pm »
Bonjour

Pour mon premier topic, je vais essayer d'apporter ma petite contribution au microcosme locoduino.

J'ai lu avec grand intérêt la plupart des articles du site, vraiment très bien faits et instructifs.
Au début, je galérais un peu dans la navigation pour en retrouver un sur lequel je voulais revenir, mais j'ai fini par trouver une petite bidouille : depuis la page d'accueil, je fais une recherche avec un mot clé au pif, puis je clique sur "tous les articles"   :P

Le dernier qui a attiré mon attention est celui sur les boutons poussoirs, un classique à destination des débutants.
J'aime beaucoup la structure de cet article ainsi que la qualité des illustrations

Concernant le code proposé avec la librairie bounce2, il me semble voir une petite amélioration possible :
bouton.attach(bp, INPUT_PULLUP);qui permet de rendre le code encore un peu plus simple en masquant l'usage de la primitive pinMode.

Bon, il reste encore le INPUT_PULLUP ::)

De mon côté, j'avais mis en ligne ailleurs une petite librairie qui me semble encore plus à la portée des débutants, surtout les francophones anglophobes   :D.
Elle définit complètement l'objet bouton poussoir dans son cablage le plus simple, et fournit toutes les fonctions de base en encapsulant toutes les primitives arduino.
Elle est aussi un poil plus économe en RAM que la bounce2.

Je vous la soumets donc comme une alternative possible, en pièce jointe (librairie complète avec exemples directement disponibles dans l'IDE arduino)

Thierry

  • Global Moderator
  • Hero Member
  • *****
  • Messages: 745
    • Voir le profil
Re : Bouton poussoir
« Réponse #1 le: janvier 06, 2016, 01:21:00 pm »
Bonjour, et bravo pour ta bibliothèque !

Quelques remarques d'un vieux développeur à propos de ton code source.

D'abord, il est bien structuré/formaté et documenté. C'est un plaisir à lire !

Quelques pistes d'amélioration:

  • Tu utilises 'const' abondamment, ce qui est plutôt bien, même si l'intérêt sur les valeurs de retour est discutable... Par contre une fonction peut elle même être const si elle ne touche pas à 'this'. C'est le cas de toutes à part actualiser() et le constructeur bien sûr... Ça donnerai:

        bool estEnfonce() const;

    et cela permettrai à un utilisateur de déclarer sa propre fonction const s'il le souhaite, ce qui n'est pas possible si l'une des fonctions appelées ne l'est pas !
  • Ces mêmes fonctions sont très simples et parfaitement éligibles à 'inline'. Ce mot clé 'advanced user' du langage C permet de ne compiler une fonction que si elle est utilisée quelque part, contrairement aux fonctions classiques dans le cpp qui sont compilées même si elles ne sont pas utilisées ! En compilant l'exemple 'bouton 1' avec ta bibliothèque j'ai besoin de 2860 octets, avec ma version seulement 2782 ! Note que pour pouvoir compiler, j'en ai profité pour passer tes constantes d'état à l'intérieur de la classe, ce qui simplifie encore le .cpp .
  • Enfin, et c'est plus une façon de faire venant d'une longue expérience des langages objet, je suis partisan de mettre un 'this->' devant l’accès à chaque élément d'une classe (donnée, fonction) afin d'être sûr que là on parle bien de quelque chose appartenant à l'objet en cours, et pas d'une variable locale...

Pour info, les fichiers d'exemple devraient être en .ino. Le .pde est plutôt destiné aux scripts 'Processing', même si l'IDE Arduino l'accepte.
Et pour terminer ma liste de critiques (que j'espère constructive !), tu devrais lire mon article http://www.locoduino.org/spip.php?article146 'Monter une bibliothèque' qui décrit tous les fichiers annexes nécessaires à ajouter dans le fichier zip pour pouvoir la gérer depuis le Gestionnaire de l'IDE 1.6.* ...

bricoleau

  • Jr. Member
  • **
  • Messages: 51
    • Voir le profil
Re : Bouton poussoir
« Réponse #2 le: janvier 06, 2016, 07:34:19 pm »
Super

Merci pour les remarques, elles mettent le doigt sur des subtilités que je n'ai pas encore complètement appréhendées.
Je vais creuser tout ça dans le détail.

bricoleau

  • Jr. Member
  • **
  • Messages: 51
    • Voir le profil
Re : Bouton poussoir
« Réponse #3 le: janvier 06, 2016, 11:41:36 pm »
Quelques heures plus tard...

Encore merci pour le temps consacré à ma petite librairie.

Voici quelques compléments & interrogations :

En préambule, ces derniers temps je développe surtout sur arduino, donc il est fort possible que certains bons usages généraux de coding me soient étrangers, car non nécessaires dans le cas de l'IDE arduino et ses options de compil (dont je ne me suis pas encore préoccupé)

1) pour le const, c'est très bête mais je les avais juste mis au début de la ligne au lieu de la fin, alors que mon intention était bien de marquer les méthodes qui n'altèrent pas l'objet. Je m'étais vaguement posé la question de comment le compilo fait la différence avec un const type, mais sans plus. Mea culpa

2) pour le inline, je suis troublé  ;D
Je n'ai jamais approfondi ce sujet, mais je ne l'associais pas à la suppression des fonctions non utilisées.
Pour moi c'était pour que le code exécutable de la fonction appelée soit inséré directement à l'endroit d'appel, au lieu de passer par un saut avec passage de valeurs via la pile etc.
Un peu comme une macro, mais en plus précis.

Quoi qu'il en soit, j'ai essayé de mettre des inline partout, par endroits ou nulle part, sans obtenir la moindre variation de la taille du fichier .hex sur cette seule modification.
Comme si le compilateur n'en faisait qu'à sa tête.

Par ailleurs, et sauf erreur de ma part, tout ce qui n'est pas utilisé est automatiquement dégagé à la compil (ou au link?), y compris les méthodes de classe non utilisées.
J'ai fait des tests, toujours avec le programme demo 1, de supprimer les méthodes non utilisées. Dans tous les cas le fichier .hex fait la même taille

Dans le même ordre d'idée, j'ai tendance à abuser des variables constantes isolées (comme les masquexxxx de ma lib) car elles disparaissent à la compil, et sont remplacés par leur valeur directement dans le code exécutable.
Les calculs entre elles sont effectués par le (pré?)compilateur.
Il suffit de faire un test en les remplaçant par des #define pour constater que le .hex et la ram utilisée sont les mêmes.
Nb : dans ta version (constantes dans la classe), j'ai observé que l'ajout de static est obligatoire pour arriver au même résultat. Le const seul ne suffit pas, alors que je le trouvais assez "explicite". Encore un truc que je ne maîtrise pas totalement.

Du coup j'ai cherché à comprendre pourquoi ton exécutable est moins lourd, et là je n'arrive pas à poser de verdict.
Cela semble être uniquement lié au fait de définir entièrement les méthodes "simples" dans le header, au lieu de les déporter dans le cpp.
J'ai testé pas mal de combinaisons (déport d'une seule méthode, de plusieurs, etc.) et j'obtiens des résultats que je ne m'explique pas.
Par exemple pour les méthodes estEnfonce() et estRelache(), cela ne fait aucune différence.
Alors que la méthode vientDEtreEnfonceOuRelache() donne un exécutable plus léger lorsqu'elle est entièrement dans le header. Un comble quand on voit que son code est quasi le même que EstRelache(), à une constante près.
Bref y a un truc qui m'échappe au niveau de la compilation.
Après, je ne suis pas assez compétent pour aller zieuter le fichier .hex et comprendre où sont les différences exactement.

3) pour le this-> j'ai encore du mal à me faire une religion
Sur le principe je suis assez d'accord
Par exemple, sur les propriétés privées j'utilise le préfixe _ justement pour bien les repérer.
Mais autant je suis partisan d'utiliser des noms à rallonge pour avoir un code plus lisible et facile à comprendre, autant je trouve que ces 6 caractères de préfixe polluent parfois à forte dose et finissent par nuire à la lisibilité.
Du coup j'ai tendance à les mettre ou non selon les endroits.

Enfin, j'essaye aussi d'avoir un header le plus simple possible à lire.
Je me dis qu'idéalement, tout ce qui relève de l'arrière cuisine ne devrait pas y être visible.
Mais bon, le C++ semble avoir ses limites sur ce point.
Par exemple, je n'ai toujours pas compris pourquoi une classe doit exposer ses parties privées en publique (si j'ose dire  ;D)

Je vais également tâcher de mettre en stricte application ton article sur la création de lib dans les règles de l'art. Je dois creuser ça aussi.

Encore merci
« Modifié: janvier 06, 2016, 11:45:05 pm par bricoleau »

bricoleau

  • Jr. Member
  • **
  • Messages: 51
    • Voir le profil
Re : Bouton poussoir
« Réponse #4 le: janvier 12, 2016, 10:31:44 pm »
Bonsoir

J'ai approfondi le point concernant le inline, et pense être arrivé à une explication qui me semble tenir la route.
Lorsque l'on met le corps d'une méthode de classe dans le .h il n'est pas nécessaire de le mettre en inline car c'est implicite.
Derrière le compilateur choisit ou non de suivre la directive, selon son analyse du code, car le inline a ses avantages et ses inconvénients. Tout n'est pas complètement clair pour moi.

Dans mes tests, j'ai l'impression que les méthodes utilisées seulement dans le programme principal conduisent à un .hex moins gros lorsqu'elles codées dans le .h, alors que celles qui sont aussi appelées depuis le .cpp n'ont pas d'impact sur la taille du .hex

Cela pourrait être cohérent avec un choix du compilo de les implémenter en inline, sauf si elles sont appelées depuis plusieurs endroits.

Quoi qu'il en soit, le gain est marginal (80 octets sur la flash, un débutant n'est pas à ça près).
Aussi je vais conserver mon optique de produire un .h le plus lisible possible, pour cette lib à destination des débutants.

Finalisation à suivre...

bricoleau

  • Jr. Member
  • **
  • Messages: 51
    • Voir le profil
Re : Bouton poussoir
« Réponse #5 le: janvier 29, 2016, 11:09:35 pm »
Bonjour

A toutes fins utiles, voici la version 2 de ma bibliothèque boutons poussoirs à destination des débutants (avec plein d'exemples fournis avec).
Ainsi que ses deux petites soeurs :
- une pour gérer les leds
- une pour gérer les délais sans utiliser les primitives "delay" ni "millis" (du moins pas directement)

a+

Dominique

  • Global Moderator
  • Hero Member
  • *****
  • Messages: 2889
  • 100% Arduino et N
    • Voir le profil
Re : Bouton poussoir
« Réponse #6 le: décembre 23, 2017, 03:51:15 pm »
Bonjour,

A toutes fins utiles, je propose ici un système qui permet d'associer une Led et un Bouton, sur la même pin.

J'utilise ce système dans le module de traction de mon projet : http://forum.locoduino.org/index.php?topic=290.msg3999#msg3999

Le schéma est le suivant :



Pour chaque loco, j'ai un potentiomètre pour régler la vitesse, un bouton pour changer de direction (conditionné par la vitesse = 0) et 2 leds une verte pour indiquer la marche avant et une rouge pour indiquer la marche arrière.

De plus, chaque bouton doit être "débouncé" c'est à dire qu'il faut lire son état 2 fois avec un intervalle de quelques millisecondes (25 dans mon cas). Le principe est simple pour gérer le bouton et la led : alternativement la pin est en INPUT et en OUTPUT.

Le code est le suivant :

bool CmdMaj( bool d) {
    bool dir = d;
    bool change = false;
    bool buttonValue;
    if (millis()-previousTime >= intervalTime) {   
      previousTime = millis();   
      pinMode(pinLedVerte, INPUT);
      buttonValue = digitalRead(pinLedVerte);
      if (buttonValue != buttonVState) {    // changement d'etat de la pin
        switch (this->etat) {
          case 0:
          if (!buttonValue) {               // 1er appui
          this->etat = 1;                   // attente confirmation
          }
          break;
          case 1:
          if (!buttonValue) {               // confirmation d'appui
            buttonVState = buttonValue;     // nouvel ancien etat de la pin
            dir = !dir;                     // inversion sens de marche
            this->etat = 2;                 // appui en cours, do nothing
            change = true;                  // job done !
          }
          break;
          case 2:
          if (buttonValue) {                // relaché
            this->etat = 3;                 // attente confirmation
          }
          break;
          case 3:
          if (buttonValue) {                // relaché
            buttonVState = buttonValue;     // nouvel etat
            this->etat = 0;                 // fin process
          }
        }
      }
      pinMode(pinLedVerte, OUTPUT);
      digitalWrite(pinLedRouge, dir);
      digitalWrite(pinLedVerte, !dir);      // allume si false
    }
    return (change);
  }

Enfin, pour commander une loco avec le potentiomètre, le bouton et les leds, j'ai fait une classe :

class CmdLoco {
private :
  int pinLedVerte; // et bouton
  int pinLedRouge;
  int pinPotar;
  unsigned long previousTime ;
  unsigned int intervalTime;
  int etat; // 0 : attente, 1 : 1er changement, 2 : confirmation
  bool buttonVState;
  bool marcheAvant;
  int potValue;
 
public :
  CmdLoco(int pLV, int pLR, int pPot, int pIt) {           // constructeur
    pinLedVerte = pLV;
    pinLedRouge = pLR;
    pinPotar = pPot;
    intervalTime = pIt;
  }

  void CmdInit() {
    marcheAvant=true;
    pinMode(pinLedVerte, OUTPUT);
    pinMode(pinLedRouge, OUTPUT);
    digitalWrite(pinLedVerte, !marcheAvant);        // allume si false
    digitalWrite(pinLedRouge, marcheAvant);         // eteinte
    previousTime = millis();
    this->etat = 0;
    buttonVState = true;   
  }

  void MajLed(bool d) {
    digitalWrite(pinLedRouge, d);
    digitalWrite(pinLedVerte, !d);      // allume si false
  }

  bool CmdMaj( bool d) {
    bool dir = d;
    bool change = false;
    bool buttonValue;
    if (millis()-previousTime >= intervalTime) {   
      previousTime = millis();   
      pinMode(pinLedVerte, INPUT);
      buttonValue = digitalRead(pinLedVerte);
      if (buttonValue != buttonVState) {    // changement d'etat de la pin
        switch (this->etat) {
          case 0:
          if (!buttonValue) {               // 1er appui
          this->etat = 1;                   // attente confirmation
          }
          break;
          case 1:
          if (!buttonValue) {               // confirmation d'appui
            buttonVState = buttonValue;     // nouvel ancien etat de la pin
            dir = !dir;                     // inversion sens de marche
            this->etat = 2;                 // appui en cours, do nothing
            change = true;                  // job done !
          }
          break;
          case 2:
          if (buttonValue) {                // relaché
            this->etat = 3;                 // attente confirmation
          }
          break;
          case 3:
          if (buttonValue) {                // relaché
            buttonVState = buttonValue;     // nouvel etat
            this->etat = 0;                 // fin process
          }
        }
      }
      pinMode(pinLedVerte, OUTPUT);
      digitalWrite(pinLedRouge, dir);
      digitalWrite(pinLedVerte, !dir);      // allume si false
    }
    return (change);
  }
 
}; // fin classe CmdLoco

L'utilisation de cette classe est simple :

1) déclarer mes 12 locos dans un table :
//--- TABLE DES LOCOS
CmdLoco * gTableCloco[MaxLocos];          // Table des locos

2) Créer les objets et les initialiser :
  //--- creation des objets Cloco et de la table
  for (int L=0; L<MaxLocos; L++) {
    gTableCloco[L] = new CmdLoco(ledVerte[L],ledRouge[L],gPotPin[L], 25); // ledVerte, ledRouge, pot, intervalTime)
  }
  //--- initialisation des objets Cloco
  for (int L=0;L<MaxLocos;L++) {
    gTableCloco[L]->CmdInit();   
  }

Pour terminer, voici le bout de code qui est dans loop() pour utiliser cette classe :


// potentiometres

  for (int i = 0; i < 12; i++) {
    gSensorValue = analogRead(gPotPin[i]);
    gOutputValue = map(gSensorValue, 0, 1023, 0, 127);
    if (gOutputValue != gPot[i]) {
      gPot[i] = gOutputValue;
      gLocoSpeed[i] = gPot[i];
      gPotChange = true;
      DccDirection = gLocoDirection[i];
      if (gCurrentLoco.inverse) {DccDirection = !DccDirection;}
      DCCpp::setSpeedMain(gConfigLocos[i].registre, gConfigLocos[i].dccAddress, gLocoStepsNumber, gLocoSpeed[i], DccDirection);     
    }
  }
 
  // boutons et leds de direction

  for (int u=0;u<MaxLocos;u++) {
    if (gLocoSpeed[u] == 0) {
      if (gTableCloco[u]->CmdMaj(gLocoDirection[u])) {     
        gLocoDirection[u] = !gLocoDirection[u];
      }       
    }
  }


Chacun en déduira quelles sont les variables globales utilisées.

Bon Noël à tous
« Modifié: décembre 23, 2017, 04:58:09 pm par Dominique »
Cordialement,
Dominique

Thierry

  • Global Moderator
  • Hero Member
  • *****
  • Messages: 745
    • Voir le profil
Re : Bouton poussoir
« Réponse #7 le: décembre 23, 2017, 05:16:09 pm »
Ingénieux la gestion des poussoirs, mais pourquoi passer par des allocations dynamique pour les locos ? Tu sais dès la compilation combien de locos tu veux, autant laisser faire le compilateur:

CmdLoco Cloco[MaxLocos];
et utiliser ensuite une fonction d'initialisation :
for (int L=0; L<MaxLocos; L++) {
    Cloco[L].begin(ledVerte[L],ledRouge[L],gPotPin[L], 25);
  }

Il y a plusieurs avantages à cette façon de faire :

  • Le bilan mémoire fourni par le compilateur avant de téléverser intégrera ainsi aussi cette partie, et il sera plus facile de détecter un problème.
  • La mémoire vive sera moins fractionnée par le recours à 'new'.
  • Le begin pourra être utilisé à tout moment!
  • Il y aura un petit peu de mémoire économisée puisqu'il n'y a plus besoin de pointeurs intermédiaires.