Accueil > La technologie > Arduino > Manœuvre des aiguilles avec des servo-moteurs > 8 poussoirs et 8 servos, enfin !

Manœuvre des aiguilles en analogique avec des servo-moteurs

8 poussoirs et 8 servos, enfin !

dimanche 17 novembre 2013, par Jean-Luc

Dans « Commande du servo-moteur par bouton poussoir », nous avons vu comment commander un servo-moteur avec un poussoir. Dans « Plusieurs boutons poussoir sur une entrée analogique », nous avons vu comment connecter 8 poussoirs sur une entrée analogique et détecter lequel est pressé. Il reste maintenant à mettre en œuvre les 8 servos.

Les variables pour manipuler les 8 servos

Si vous vous rappelez, plusieurs variables étaient employées pour notre servo : l’objet de type Servo permettant de le piloter, son angle, sa vitesse, son état. Comme ceci.

Servo monServo;
int vitesse = 0;
int angle = angleMin;
byte etatServo = SERVO_A_ANGLE_MIN;

Si nous avons 8 servos, il va falloir 8 exemplaires de chacune de ces variables. Plutôt que de répliquer 8 fois ces définitions, ce qui serait fastidieux, nous allons créer un tableau avec autant d’éléments que de servos. Mais avant de créer ce tableau, nous devons mettre ces variables ensemble. Pour les mettre ensemble, il existe en C les struct pour structure. Comme ceci.

struct DescripteurServo {
  Servo objetServo;
  int vitesse;
  int angle;
  byte etatServo;
};

Une struct a un nom, ici DescripteurServo, et des membres, objetServo, vitesse, angle et etatServo. struct DescripteurServo est en quelques sortes un nouveau type de donnée, comme int ou byte, et on peut l’utiliser pour créer un tableau de 8 éléments que nous appelons servoMoteur comme ceci.

struct DescripteurServo servoMoteur[8];

servoMoteur est le nom de notre tableau, le [8] indique qu’il contient 8 éléments et struct DescripteurServo est le type d’un élément.

Pour accéder à un élément du tableau servoMoteur, il faut indiquer son numéro, entre 0 et 7 compris. Ainsi, servoMoteur[4] est le 5e élément [1] du tableau.

Enfin, pour accéder à un membre d’un élément, il faut ajouter un . et le nom de l’élément. Ainsi, servoMoteur[4].angle permet d’accéder à l’angle du 5e servo.

La mise à jour de l’angle des 8 servos

Pour un seul servo, le morceau de programme de mise à jour de l’angle était le suivant.

    /* 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;
    }

Comme nous avons maintenant un tableau de variables, il faut le réécrire. Pour bien séparer les différentes parties du programme qui commence à grossir, nous allons déplacer ce morceau de programme dans une fonction, appelons la gereServo. Cette fonction prendra un argument, le numéro de servo à gérer. Ce numéro de servo va servir à accéder au tableau.

void gereServo(int numServo)
{
    servoMoteur[numServo].objetServo.writeMicroseconds(
          servoMoteur[numServo].angle);

    servoMoteur[numServo].angle += servoMoteur[numServo].vitesse;

    if (servoMoteur[numServo].angle > angleMax) {
      servoMoteur[numServo].angle = angleMax;
      servoMoteur[numServo].vitesse = 0;
      servoMoteur[numServo].etatServo = SERVO_A_ANGLE_MAX;
    }
    else if (servoMoteur[numServo].angle < angleMin) {
      servoMoteur[numServo].angle = angleMin;
      servoMoteur[numServo].vitesse = 0;
      servoMoteur[numServo].etatServo = SERVO_A_ANGLE_MIN;
    }
}

Dans loop(), nous allons appeler cette fonction gereServo pour chacun des servos. Pour cela nous allons faire une boucle for( ... ; ... ; ... ). Pour parcourir tous les servos, nous allons écrire for( numServo = 0; numServo < 8; numServo++). Le numServo = 0; est exécuté une fois au début de la boucle. il s’agit de l’initialisation. Le numServo < 8 est une condition qui est testée au début de chaque itération. Si la condition est vraie, la boucle continue, sinon elle s’arrête. Enfin le numServo++ est exécuté à la fin de chaque itération de la boucle, numServo est augmenté de 1. Par conséquent, cette boucle va produire 8 itérations et numServo vaudra successivement 0, 1, 2, 3, 4, 5, 6 et 7, ce qui correspond bien au parcours des éléments de notre tableau servoMoteur.

Gérer les 8 servos est donc pris en charge par cette ligne dans loop()

for (numServo = 0; numServo < 8; numServo++) gereServo(numServo);

Intégration de la commande par poussoir

Dans « Commande du servo-moteur par bouton poussoir », L’unique poussoir était lu et la vitesse du servo était changée. Comme ceci.

    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;
        } 
    }

Nous allons isoler le switch ... case dans une fonction que nous appelons evenementServo(...) et dont l’argument est le numéro de servo. Ce numéro de servo est le numéro de bouton pressé. Il suffit alors de modifier la vitesse du servo correspondant. Comme ceci.

void evenementServo(int numServo)
{
    switch (servoMoteur[numServo].etatServo) {
      case SERVO_A_ANGLE_MIN:
      case SERVO_EN_MOUVEMENT_VERS_ANGLE_MIN:
        servoMoteur[numServo].vitesse =  1;
        servoMoteur[numServo].etatServo = SERVO_EN_MOUVEMENT_VERS_ANGLE_MAX;
        break;
      case SERVO_A_ANGLE_MAX:
      case SERVO_EN_MOUVEMENT_VERS_ANGLE_MAX:
        servoMoteur[numServo].vitesse = -1;
        servoMoteur[numServo].etatServo = SERVO_EN_MOUVEMENT_VERS_ANGLE_MIN;
        break;
    } 
}

Il ne reste plus qu’à lire l’événement du bouton comme dans « Plusieurs boutons poussoir sur une entrée analogique » et à appeler la fonction evenementServo(...) avec le numéro de bouton recueilli.

    byte evenement = lireEvenement(&numServo);
  
    if (evenement == EVENEMENT_PRESSE) evenementServo(numServo);

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;
 
struct DescripteurServo {
  Servo objetServo;
  int vitesse;
  int angle;
  byte etatServo;
};
 
struct DescripteurServo servoMoteur[8];
 
const byte NON_PRESSE = 0;
const byte ENFONCE = 1;
const byte PRESSE = 2;
 
byte etatAutomate = NON_PRESSE;
int etatPoussoir = -1;
 
const byte AUCUN_EVENEMENT = 0;
const byte EVENEMENT_PRESSE = 1;
const byte EVENEMENT_RELACHE = 2;
 
const int pinPoussoirs = 0;
 
/*
 * Lecture des poussoirs. À l'aide d'un automate, on assure que les valeurs
 * transitoires sont filtrées.
 */
int lirePoussoirs()
{
    int resultat;
    int numPoussoir = (analogRead(pinPoussoirs) + 64) / 128;
    
    int nouvelEtatPoussoir = etatPoussoir; /* à priori rien ne change */
 
    switch (etatAutomate) {
        case NON_PRESSE:
            if (numPoussoir < 8)
                etatAutomate = ENFONCE;
            break;
        case ENFONCE:
            if (numPoussoir < 8) {
                etatAutomate = PRESSE;
                nouvelEtatPoussoir = numPoussoir;
            }
            else {
                etatAutomate = NON_PRESSE;
            }
            break;
        case PRESSE:
            if (numPoussoir == 8) {
                etatAutomate = NON_PRESSE;
                nouvelEtatPoussoir = -1;
            }
            break;
    }
    
    return nouvelEtatPoussoir;
}
 
/*
 * construction d'un événement en comparant
 * le nouvel état des poussoirs avec l'état précédent.
 */
byte lireEvenement(int *numPoussoir)
{
    byte evenement;
    int nouvelEtatPoussoir = lirePoussoirs();
    
    if (nouvelEtatPoussoir == etatPoussoir)
        evenement = AUCUN_EVENEMENT;
    if (nouvelEtatPoussoir >= 0 && etatPoussoir == -1) 
        evenement = EVENEMENT_PRESSE;
    if (nouvelEtatPoussoir == -1 && etatPoussoir >= 0) 
        evenement = EVENEMENT_RELACHE;
 
    etatPoussoir = nouvelEtatPoussoir;
    *numPoussoir = etatPoussoir;
    
    return evenement;
}
 
/*
 * Initialisations. Chaque servomoteur est initialisé à l'angle minimum
 * sa vitesse est mise à 0
 */
void setup()
{
    /* Initialisation des servos */
    int numServo;
  
    for (numServo = 0; numServo < 8; numServo++) {
        servoMoteur[numServo].angle = angleMin;
        servoMoteur[numServo].vitesse = 0;
        servoMoteur[numServo].etatServo = SERVO_A_ANGLE_MIN;
        servoMoteur[numServo].objetServo.attach(numServo+2);
    }
}
 
/*
 * Actualisation de l'angle du servo
 */
void gereServo(int numServo)
{
    servoMoteur[numServo].objetServo.writeMicroseconds(
        servoMoteur[numServo].angle);
 
    servoMoteur[numServo].angle += servoMoteur[numServo].vitesse;
 
    if (servoMoteur[numServo].angle > angleMax) {
      servoMoteur[numServo].angle = angleMax;
      servoMoteur[numServo].vitesse = 0;
      servoMoteur[numServo].etatServo = SERVO_A_ANGLE_MAX;
    }
    else if (servoMoteur[numServo].angle < angleMin) {
      servoMoteur[numServo].angle = angleMin;
      servoMoteur[numServo].vitesse = 0;
      servoMoteur[numServo].etatServo = SERVO_A_ANGLE_MIN;
    }
}
 
/*
 * changement de consigne d'un servomoteur.
 */
void evenementServo(int numServo)
{
    switch (servoMoteur[numServo].etatServo) {
      case SERVO_A_ANGLE_MIN:
      case SERVO_EN_MOUVEMENT_VERS_ANGLE_MIN:
        servoMoteur[numServo].vitesse =  1;
        servoMoteur[numServo].etatServo = SERVO_EN_MOUVEMENT_VERS_ANGLE_MAX;
        break;
      case SERVO_A_ANGLE_MAX:
      case SERVO_EN_MOUVEMENT_VERS_ANGLE_MAX:
        servoMoteur[numServo].vitesse = -1;
        servoMoteur[numServo].etatServo = SERVO_EN_MOUVEMENT_VERS_ANGLE_MIN;
        break;
    } 
}
 
void loop()
{
    int numServo;
  
    for (numServo = 0; numServo < 8; numServo++) gereServo(numServo);
 
    byte evenement = lireEvenement(&numServo);
  
    if (evenement == EVENEMENT_PRESSE) evenementServo(numServo);
  
    delay(3);
}

Coupure de la commande dans les positions extrêmes

Jusqu’à maintenant, une fois qu’un servomoteur a gagné une de ses deux positions extrêmes, la commande PWM de position reste active. Par conséquent l’électronique du servomoteur continue d’asservir la position. Or, en présence d’une résistance mécanique comme par exemple l’effet ressort de la tige de commande de l’aiguille, l’asservissement de position fait que le servomoteur grogne. C’est un inconvénient, à la fois pour les oreilles et pour la consommation électrique.

Pour supprimer cet asservissement une fois que le servomoteur a accompli son mouvement, il suffit de couper la commande PWM. Ce n’est pas explicitement prévu dans la bibliothèque Servo de l’Arduino mais il suffit de détacher le servo de la broche de sortie par la méthode detach() pour couper la commande PWM.

Pour simplifier le programme, on va ajouter dans DescripteurServo un membre pour stocker le numéro de broche. Appelons le pin.

struct DescripteurServo {
  Servo objetServo;
  int vitesse;
  int angle;
  int pin;
  byte etatServo;
};

Il faut évidemment ne pas oublier d’initialiser pin dans setup()

    for (numServo = 0; numServo < 8; numServo++) {
        servoMoteur[numServo].angle = angleMin;
        servoMoteur[numServo].vitesse = 0;
        servoMoteur[numServo].etatServo = SERVO_A_ANGLE_MIN;
        servoMoteur[numServo].pin = numServo + 2;
        servoMoteur[numServo].objetServo.attach(servoMoteur[numServo].pin);
    }

Dans la fonction gereServo(...) on va détacher le servo quand une des positions extrêmes est atteinte.

    if (servoMoteur[numServo].angle > angleMax) {
      servoMoteur[numServo].angle = angleMax;
      servoMoteur[numServo].vitesse = 0;
      servoMoteur[numServo].objetServo.detach();
      servoMoteur[numServo].etatServo = SERVO_A_ANGLE_MAX;
    }
    else if (servoMoteur[numServo].angle < angleMin) {
      servoMoteur[numServo].angle = angleMin;
      servoMoteur[numServo].vitesse = 0;
      servoMoteur[numServo].objetServo.detach();
      servoMoteur[numServo].etatServo = SERVO_A_ANGLE_MIN;
    }

Enfin, dans la fonction evenementServo, on va attacher de nouveau le servo à la broche quand si le servo est dans une des positions extrêmes. On voit ici l’intérêt d’avoir séparer les cas SERVO_A_ANGLE_x et SERVO_EN_MOUVEMENT_VERS_ANGLE_x car pour le second il ne faut pas attacher le servo à la broche.

void evenementServo(int numServo)
{
    switch (servoMoteur[numServo].etatServo) {
      case SERVO_A_ANGLE_MIN:
        servoMoteur[numServo].objetServo.attach(servoMoteur[numServo].pin);
      case SERVO_EN_MOUVEMENT_VERS_ANGLE_MIN:
        servoMoteur[numServo].vitesse =  1;
        servoMoteur[numServo].etatServo = SERVO_EN_MOUVEMENT_VERS_ANGLE_MAX;
        break;
      case SERVO_A_ANGLE_MAX:
        servoMoteur[numServo].objetServo.attach(servoMoteur[numServo].pin);
      case SERVO_EN_MOUVEMENT_VERS_ANGLE_MAX:
        servoMoteur[numServo].vitesse = -1;
        servoMoteur[numServo].etatServo = SERVO_EN_MOUVEMENT_VERS_ANGLE_MIN;
        break;
    } 
}

Le programme devenant plus gros, je vais cesser de donner son code in-extenso sur les pages du blog. Le voici donc à télécharger

Programme Arduino avec coupure de la commande PWM

Les essais

Attention, avec 8 servomoteurs tirant sur l’alimentation de l’Arduino, il faut prendre certaines précautions. Il faut tout d’abord s’assurer que la consommation totale n’excède pas ce que l’Arduino peut fournir. Son régulateur de tension est donné pour 1A mais la carte elle-même consomme.

Pour mes expérimentations j’ai mesuré la consommation des servos HK15178. Au repos, il consomment 7mA. En mouvement à la vitesse imposée, ils montent à un peu moins de 60mA. Si il n’y qu’un seul servo en mouvement à un instant, l’ensemble va donc consommer 7 × 7mA + 60mA = 110mA ce qui reste raisonnable. Bloqué, il consomme 150mA. Dans les faits la connexion du 6e servo a provoqué la coupure de l’alimentation car le régulateur a détecté une consommation excessive. Comme déjà indiqué dans « La carte de commande 6 servo-moteurs, le matériel », l’appel de courant lors de la mise sous tension d’un servomoteur peut être important.

Pour une installation sur le réseau, il est donc préférable de prévoir une alimentation séparée pour les servomoteurs.

Voici la vidéo des essais.

La prochain article sera consacré à l’ajout de la fonction de réglage des butées.


[1Oui le 5e puisque le premier à le numéro 0

Messages

  • Bonjour

    Votre description de la commande de 8 servos par boutons poussoirs m’intéresse particulièrement.
    J’ai un réseau équipé de 2 Quad-Pic Tam Valley, et il s’étend ainsi que le Nb d’aiguillages.
    D’où ma recherche de décodeurs de servos à moindre coût.

    J’ai réalisé le circuit avec les 8 poussoirs et 4 servos. Mais j’ai un souci, le bouton 1 commande parfois le servo 0 ou 1 ?.
    J’ai mesuré les tensions que je vous communique :
    4.93V, 0.18, 0.61, 1.40, 1.92, 2.56, 3.12, 3.72, 4.34V

    Le 3 premieres résistances que j’ai pu trouver sont approximatives :
    4 ohm, 74 ohm, et 88 ohm les autres ont la bonnes valeurs.

    Le cafouillage entre servos 0 et 1 serait-il du aux resistances ?.

    Un petite aide me serait utile. J’espère arriver au bout de cette commande 8 servos.
    Et un grand merci pour vos articles et réalisations bien documentées.

    Cordialement.
    Daniel Ethuin

    • Bonjour,

      Si vous vous référez à la table qui est dans cet article : http://modelleisenbahn.triskell.org/spip.php?article59 Vous verrez que les valeurs de tensions que vous donnent vos résistances sont trop éloignées de ce qui est attendu. Vous avez deux solutions :

      1. mettre les bonnes valeur de résistance
      2. changer le programme pour que les seuils soient calculés différemment. Il va falloir faire une succession de if-then-else.
  • En relisant mon message, je me suis trompé dans la valeur de la 1ere resistance, il fallait lire 47 ohm.

  • Bonjour,
    Je débute avec un arduino mega 2560.
    J’ai pour projet 16 aguillages par servos commandés par 16 boutons poussoir.
    Tout en sachant que chaque bouton poussoir serait connecté aux entrées A0,A1,...
    Je recherche un programme car franchement je suis perdu avec tout ce langage.
    Merci d’avance

  • Bonjour
    J’utilise votre montage depuis ps mal de temps sans problème, mais j’aimerai remplacé les boutons poussoirs par des inverseurs permanents.
    Malgré plusieurs essais je ne trouve pas le moyen de la réaliser.
    Que dois je changer dans le code afin d’y arrivé ?
    Merci de votre réponse
    Jean

  • Bonjour,

    Comme il ne peut y avoir qu’un seul poussoir enfoncé à un instant donné, il ne pourra y avoir qu’un seul interrupteur permanent en position fermé. Je ne suis pas sûr que cette technique puisse être utile dans ce cas.

Un message, un commentaire ?

Qui êtes-vous ?
Votre message

Pour créer des paragraphes, laissez simplement des lignes vides.

Lien hypertexte

(Si votre message se réfère à un article publié sur le Web, ou à une page fournissant plus d’informations, vous pouvez indiquer ci-après le titre de la page et son adresse.)