Nous allons maintenant ajouter un bouton poussoir pour commander le mouvement du servo-moteur. Cet article fait suite à « Mise en œuvre d’un servo-moteur ».
Le système fonctionnera de la manière suivante : Lorsque le servo-moteur est dans une des positions extrêmes, une pression sur le poussoir donnera au servo-moteur une vitesse lui permettant de gagner l’autre position extrême. Lorsque le servo-moteur est en mouvement, une pression sur le bouton inversera la vitesse.
Mise en œuvre du bouton poussoir
Comme déjà expliqué, nous allons réserver les entrées/sorties numériques, les broches 0 à 13 de l’Arduino, au pilotage des servo-moteurs et à l’allumage des LED témoin.
Pour l’instant, l’unique bouton poussoir est connecté sur une entrée analogique, A0 par exemple. Une entrée analogique permet à l’Arduino de lire une grandeur analogique, entre 0V et 5V, et de la convertir en un nombre que l’on pourra manipuler dans le programme. Le nombre obtenu peut prendre des valeurs comprises entre 0 (pour 0V) et 1023 (pour 5V) avec donc 1024 valeurs possibles.
Pour notre bouton, il n’y aura que deux valeurs, celle qui correspond au bouton relâché et celle qui correspond au bouton enfoncé. Le bouton est connecté à l’Arduino comme ceci.
- Connexion du bouton poussoir à l’Arduino Uno
Lorsque le bouton est relâché, l’entrée analogique A0 est tirée à 0V via la résistance de 10kΩ. Lorsque le bouton est pressé, l’entrée est mise à 5V.
Du côté du programme, nous allons séparer l’espace de valeurs (0 à 1023) en deux parties égales : les valeurs inférieures ou égales à 511 et celles supérieures à 511 et nous allons définir une fonction qui lit la valeur analogique, la compare à la valeur milieu et retourne soit RELACHE
, soit PRESSE
. Ces deux constantes sont des booléens, c’est à dire des valeurs logiques. Elle sont définies pour faciliter la lecture du programme. Jusqu’alors nous n’avons vu que des fonctions qui ne retournaient rien, c’est à dire void
. Ici la fonction lireBouton
retourne un booléen, qui sera l’état du bouton. Nous avons donc le morceau de programme suivant avec la définition des constantes nécessaires et la fonction de lecture de l’état du bouton poussoir.
- /* le bouton est connecté à la broche A0 */
- const int pinBouton = 0;
- /* valeur logique pour indiquer que le bouton est pressé */
- const boolean PRESSE = true;
- /* valeur logique pour indiquer que le bouton est relâché */
- const boolean RELACHE = false;
- /*
- * lecture du bouton poussoir. Le bouton poussoir est connecté
- * sur une entrée analogique. Quand il est pressé, l'entrée est
- * connectée au +5V. Quand il est relaché, l'entrée est connectée
- * à la masse. On va discriminer les deux états en comparant la
- * valeur analogique lue à 511 qui est la valeur milieu.
- */
- boolean lirePoussoir()
- {
- boolean resultat = RELACHE;
- if (analogRead(pinBouton) > 511) {
- resultat = PRESSE;
- }
- /* retourne l'état */
- return resultat;
- }
Mais l’état du bouton ne nous intéresse pas directement. En effet, ne mettre le servo-moteur en mouvement que lorsque le bouton est pressé forcerait l’utilisateur à maintenir le bouton pressé pendant le mouvement. Ce sont donc les changements d’état du bouton qui nous intéressent : le fait de passer de l’état RELACHE
à l’état PRESSE
ou le fait de passer de l’état PRESSE
à l’état RELACHE
. Pour détecter ces changements d’état, il faut mémoriser l’état précédent du bouton et, quand on lit son état actuel, le comparé à l’état précédent pour déterminer si l’un des changements a eu lieu. On a donc besoin d’une variable pour y mémoriser l’état précédent. Pour clarifier les choses et rendre le programme lisible, nous allons aussi définir 3 constantes : AUCUN_EVENEMENT
, l’état précédent du bouton est identique à l’état courant, EVENEMENT_PRESSE
, l’état précédent du bouton était RELACHE
, l’état courant est PRESSE
et EVENEMENT_RELACHE
, l’état précédent du bouton était PRESSE
, l’état courant est RELACHE
. Ces constantes sont des byte
, c’est à dire un type de donnée pouvant prendre 256 valeurs différentes, ce qui suffit amplement.
- const byte AUCUN_EVENEMENT = 0;
- const byte EVENEMENT_PRESSE = 1;
- const byte EVENEMENT_RELACHE = 2;
Enfin nous allons définir la fonction lireEvenement()
qui va lire l’état courant du bouton et calculer l’événement. Le code vient assez naturellement.
- /*
- * construction d'un événement.
- * - Si l'état du bouton n'a pas changé entre l'état précédent du bouton
- * et le nouvelle état, l'événement est AUCUN_EVENEMENT.
- * - Si le pouton était précédemment RELACHE et qu'il est maintenant PRESSE,
- * l'événement est EVENEMENT_PRESSE.
- * - Si le pouton était précédemment PRESSE et qu'il est maintenant RELACHE,
- * l'événement est EVENEMENT_RELACHE.
- */
- byte lireEvenement()
- {
- byte evenement;
- /* lit l'état courant du bouton */
- boolean nouvelEtat = lirePoussoir();
- /* calcule l'événement */
- if (nouvelEtat == etatBouton)
- evenement = AUCUN_EVENEMENT;
- if (nouvelEtat == PRESSE && etatBouton == RELACHE)
- evenement = EVENEMENT_PRESSE;
- if (nouvelEtat == RELACHE && etatBouton == PRESSE)
- evenement = EVENEMENT_RELACHE;
- /* L'état courant devient l'état précédent */
- etatBouton = nouvelEtat;
- /* retourne l'événement */
- return evenement;
- }
Afin de tester la lecture du poussoir indépendamment, nous allons mettre en œuvre l’envoi de message sur la ligne série qui, via l’USB, s’affiche dans le Moniteur Série de l’environnement de programmation Arduino. Il faut tout d’abord ouvrir la connexion dans la fonction setup()
en utilisant la méthode begin(...)
de Serial
. Nous allons ensuite utiliser la méthode println
pour afficher un message sur le moniteur série. Voici le programme complet pour cette première application mettant en œuvre un bouton.
- const int pinBouton = 0; /* le bouton est connecté à la broche A0 */
- const boolean PRESSE = true;
- const boolean RELACHE = false;
- boolean etatBouton = RELACHE;
- const byte AUCUN_EVENEMENT = 0;
- const byte EVENEMENT_PRESSE = 1;
- const byte EVENEMENT_RELACHE = 2;
- boolean lirePoussoir()
- {
- boolean resultat = RELACHE;
- if (analogRead(pinBouton) > 512) {
- resultat = PRESSE;
- }
- return resultat;
- }
- byte lireEvenement()
- {
- byte evenement;
- boolean nouvelEtat = lirePoussoir();
- if (nouvelEtat == etatBouton)
- evenement = AUCUN_EVENEMENT;
- if (nouvelEtat == PRESSE && etatBouton == RELACHE)
- evenement = EVENEMENT_PRESSE;
- if (nouvelEtat == RELACHE && etatBouton == PRESSE)
- evenement = EVENEMENT_RELACHE;
- etatBouton = nouvelEtat;
- return evenement;
- }
- void setup()
- {
- Serial.begin(9600);
- }
- void loop()
- {
- byte evenement = lireEvenement();
- if (evenement == EVENEMENT_PRESSE) Serial.println("presse !");
- if (evenement == EVENEMENT_RELACHE) Serial.println("relache !");
- }
À ma grande surprise ce programme fonctionne sans effort supplémentaire ! À ma grande surprise car lorsque l’on presse ou que l’on relâche un bouton, le contact ou la coupure ne s’effectue pas d’un coup et une succession rapide de contact-coupure a lieu. On appelle cela un rebond. Mon poussoir n’a pas de caractéristique particulière et j’ai par ailleurs eu des rebonds en le connectant à une entrée numérique. La réponse à ce mystère est donc ailleurs.
Deux techniques sont possibles pour filtrer les rebonds des poussoirs :
- par matériel : un condensateur de filtrage ;
- par logiciel : laisser passer du temps entre deux lectures.
Or, la conversion analogique numérique n’est pas instantanée. Entre deux conversions successives et donc deux acquisitions successive de la valeur analogique, il s’écoule 100µs. Ce qui équivaut à la solution logicielle de « laisser passer du temps entre 2 lectures ». Cette durée semble être suffisante pour filtrer les rebonds de notre bouton.
Intégration avec le programme de pilotage du servo-moteur
Nous allons maintenant intégrer notre fonction de lecture d’événement au programme de pilotage du servo-moteur tel que nous l’avons présenté dans l’article précédent.
Il faut procéder à quelques modification. Tout d’abord, il faut supprimer l’aller-retour qu’effectue le servo-moteur. Ensuite, il faut ajouter une variable qui va permettre de savoir dans quel état se trouve le servo-moteur afin de prendre la décision adéquate quand le bouton est pressé. On va distinguer 4 états :
SERVO_A_ANGLE_MIN
: le servo-moteur est arrêté dans la position correspondant à l’angle minimum. Sa vitesse est égale à 0.
SERVO_A_ANGLE_MAX
: le servo-moteur est arrêté dans la position correspondant à l’angle maximum. Sa vitesse est égale à 0.
SERVO_EN_MOUVEMENT_VERS_ANGLE_MAX
: le servo est en mouvement vers la position d’angle maximum. Sa vitesse est égale à 1.
SERVO_EN_MOUVEMENT_VERS_ANGLE_MIN
: le servo est en mouvement vers la position d’angle minimum. Sa vitesse est égale à -1.
Comme tout est nombre, nous allons définir chacune de ces constantes comme étant un byte
et définir une variable etatServo
de type byte
comme ceci.
- const byte SERVO_A_ANGLE_MIN = 0;
- const byte SERVO_A_ANGLE_MAX = 1;
- const byte SERVO_EN_MOUVEMENT_VERS_ANGLE_MAX = 2;
- const byte SERVO_EN_MOUVEMENT_VERS_ANGLE_MIN = 3;
- const int angleMin = 1250;
- const int angleMax = 1750;
- Servo monServo;
- int vitesse = 0;
- int angle = angleMin;
- byte etatServo = SERVO_A_ANGLE_MIN;
Dans setup()
, nous retrouvons l’accrochage de monServo
à la broche 2. La gestion du mouvement du servo est modifiée. Quand l’angle dépasse l’angle maximum, il est recalé comme auparavant. Mais dorénavant la vitesse est mise à 0 et l’état du servo est changé en SERVO_A_ANGLE_MAX
. Quand l’angle dépasse l’angle minimum, il est recalé à l’angle minimum, la vitesse est mise à 0 et l’état du servo est changé en SERVO_A_ANGLE_MIN
. De cette manière, le servo arrête son mouvement quand il atteint l’une des positions extrêmes.
On va ensuite lire l’événement. Si l’événement est EVENEMENT_PRESSE
, on va effectuer les actions suivantes :
- Si le servo est dans l’état
SERVO_A_ANGLE_MIN
ou
SERVO_EN_MOUVEMENT_VERS_ANGLE_MIN
, la vitesse est mise à 1 et l’état du servo est changé enSERVO_EN_MOUVEMENT_VERS_ANGLE_MAX
. - Si le servo est dans l’état
SERVO_A_ANGLE_MAX
ou
SERVO_EN_MOUVEMENT_VERS_ANGLE_MAX
, la vitesse est mise à -1 et et l’état du servo est changé enSERVO_EN_MOUVEMENT_VERS_ANGLE_MIN
Le programme complet est le suivant.
- #include <Servo.h>
- const byte SERVO_A_ANGLE_MIN = 0;
- const byte SERVO_A_ANGLE_MAX = 1;
- const byte SERVO_EN_MOUVEMENT_VERS_ANGLE_MAX = 2;
- const byte SERVO_EN_MOUVEMENT_VERS_ANGLE_MIN = 3;
- const int angleMin = 1250;
- const int angleMax = 1750;
- Servo monServo;
- int vitesse = 0;
- int angle = angleMin;
- byte etatServo = SERVO_A_ANGLE_MIN;
- const int pinBouton = 0; /* le bouton est connecté à la broche A0 */
- const boolean PRESSE = true;
- const boolean RELACHE = false;
- boolean etatBouton = RELACHE;
- const byte AUCUN_EVENEMENT = 0;
- const byte EVENEMENT_PRESSE = 1;
- const byte EVENEMENT_RELACHE = 2;
- /*
- * fonctions de gestion du poussoir
- */
- boolean lirePoussoir()
- {
- boolean resultat = RELACHE;
- if (analogRead(pinBouton) > 512) {
- resultat = PRESSE;
- }
- return resultat;
- }
- byte lireEvenement()
- {
- byte evenement;
- boolean nouvelEtat = lirePoussoir();
- if (nouvelEtat == etatBouton)
- evenement = AUCUN_EVENEMENT;
- if (nouvelEtat == PRESSE && etatBouton == RELACHE)
- evenement = EVENEMENT_PRESSE;
- if (nouvelEtat == RELACHE && etatBouton == PRESSE)
- evenement = EVENEMENT_RELACHE;
- etatBouton = nouvelEtat;
- return evenement;
- }
- /*
- * La fonction setup() est exécutée 1 fois
- * au démarrage du programme
- */
- void setup()
- {
- monServo.attach(2);
- }
- /*
- * La fonction loop() est exécutée
- * répétitivement
- */
- void loop()
- {
- /* actualisation de l'angle du servo */
- monServo.writeMicroseconds(angle);
- angle = angle + vitesse;
- if (angle > angleMax) {
- angle = angleMax;
- vitesse = 0;
- etatServo = SERVO_A_ANGLE_MAX;
- }
- else if (angle < angleMin) {
- angle = angleMin;
- vitesse = 0;
- etatServo = SERVO_A_ANGLE_MIN;
- }
- /* lecture de la commande de l'utilisateur */
- byte evenement = lireEvenement();
- if (evenement == EVENEMENT_PRESSE) {
- switch (etatServo) {
- case SERVO_A_ANGLE_MIN:
- case SERVO_EN_MOUVEMENT_VERS_ANGLE_MIN:
- vitesse = 1;
- etatServo = SERVO_EN_MOUVEMENT_VERS_ANGLE_MAX;
- break;
- case SERVO_A_ANGLE_MAX:
- case SERVO_EN_MOUVEMENT_VERS_ANGLE_MAX:
- vitesse = -1;
- etatServo = SERVO_EN_MOUVEMENT_VERS_ANGLE_MIN;
- break;
- }
- }
- delay(3);
- }
Ce programme est l’occasion d’introduire une nouvelle construction du C, le switch ... case
. Il s’agit d’un choix multiple. On a vu avec le if ... else ...
un choix double. Ici la valeur de la variable etatServo
indique à quel case
le programme continue son exécution. L’exécution se poursuit jusqu’à rencontrer un break
qui fait que le programme continue sont exécution après le switch
.
Distinguer les états SERVO_A_ANGLE_MIN
et SERVO_EN_MOUVEMENT_VERS_ANGLE_MIN
n’est pas nécessaire, idem pour SERVO_A_ANGLE_MAX
et SERVO_EN_MOUVEMENT_VERS_ANGLE_MAX
. On le conserve tout de même car le programme est plus lisible ainsi et la distinction qui est faite va nous servir plus tard.
Une petite vidéo du montage volant et de l’exécution de ce dernier programme.