ModelleisenbahN

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 !

publié par Jean-Luc, le dimanche 17 novembre 2013

Tags aiguille - arduino - servo

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.

  1. Servo monServo;
  2. int vitesse = 0;
  3. int angle = angleMin;
  4. byte etatServo = SERVO_A_ANGLE_MIN;

Télécharger

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.

  1. struct DescripteurServo {
  2. Servo objetServo;
  3. int vitesse;
  4. int angle;
  5. byte etatServo;
  6. };

Télécharger

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.

  1. 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.

  1. /* actualisation de l'angle du servo */
  2. monServo.writeMicroseconds(angle);
  3.  
  4. angle = angle + vitesse;
  5.  
  6. if (angle > angleMax) {
  7. angle = angleMax;
  8. vitesse = 0;
  9. etatServo = SERVO_A_ANGLE_MAX;
  10. }
  11. else if (angle < angleMin) {
  12. angle = angleMin;
  13. vitesse = 0;
  14. etatServo = SERVO_A_ANGLE_MIN;
  15. }

Télécharger

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.

  1. void gereServo(int numServo)
  2. {
  3. servoMoteur[numServo].objetServo.writeMicroseconds(
  4. servoMoteur[numServo].angle);
  5.  
  6. servoMoteur[numServo].angle += servoMoteur[numServo].vitesse;
  7.  
  8. if (servoMoteur[numServo].angle > angleMax) {
  9. servoMoteur[numServo].angle = angleMax;
  10. servoMoteur[numServo].vitesse = 0;
  11. servoMoteur[numServo].etatServo = SERVO_A_ANGLE_MAX;
  12. }
  13. else if (servoMoteur[numServo].angle < angleMin) {
  14. servoMoteur[numServo].angle = angleMin;
  15. servoMoteur[numServo].vitesse = 0;
  16. servoMoteur[numServo].etatServo = SERVO_A_ANGLE_MIN;
  17. }
  18. }

Télécharger

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()

  1. 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.

  1. byte evenement = lireEvenement();
  2.  
  3. if (evenement == EVENEMENT_PRESSE) {
  4. switch (etatServo) {
  5. case SERVO_A_ANGLE_MIN:
  6. case SERVO_EN_MOUVEMENT_VERS_ANGLE_MIN:
  7. vitesse = 1;
  8. etatServo = SERVO_EN_MOUVEMENT_VERS_ANGLE_MAX;
  9. break;
  10. case SERVO_A_ANGLE_MAX:
  11. case SERVO_EN_MOUVEMENT_VERS_ANGLE_MAX:
  12. vitesse = -1;
  13. etatServo = SERVO_EN_MOUVEMENT_VERS_ANGLE_MIN;
  14. break;
  15. }
  16. }

Télécharger

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.

  1. void evenementServo(int numServo)
  2. {
  3. switch (servoMoteur[numServo].etatServo) {
  4. case SERVO_A_ANGLE_MIN:
  5. case SERVO_EN_MOUVEMENT_VERS_ANGLE_MIN:
  6. servoMoteur[numServo].vitesse = 1;
  7. servoMoteur[numServo].etatServo = SERVO_EN_MOUVEMENT_VERS_ANGLE_MAX;
  8. break;
  9. case SERVO_A_ANGLE_MAX:
  10. case SERVO_EN_MOUVEMENT_VERS_ANGLE_MAX:
  11. servoMoteur[numServo].vitesse = -1;
  12. servoMoteur[numServo].etatServo = SERVO_EN_MOUVEMENT_VERS_ANGLE_MIN;
  13. break;
  14. }
  15. }

Télécharger

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.

  1. byte evenement = lireEvenement(&numServo);
  2.  
  3. if (evenement == EVENEMENT_PRESSE) evenementServo(numServo);

Télécharger

Le programme complet est le suivant.

  1. #include <Servo.h>
  2.  
  3. const byte SERVO_A_ANGLE_MIN = 0;
  4. const byte SERVO_A_ANGLE_MAX = 1;
  5. const byte SERVO_EN_MOUVEMENT_VERS_ANGLE_MAX = 2;
  6. const byte SERVO_EN_MOUVEMENT_VERS_ANGLE_MIN = 3;
  7.  
  8. const int angleMin = 1250;
  9. const int angleMax = 1750;
  10.  
  11. struct DescripteurServo {
  12. Servo objetServo;
  13. int vitesse;
  14. int angle;
  15. byte etatServo;
  16. };
  17.  
  18. struct DescripteurServo servoMoteur[8];
  19.  
  20. const byte NON_PRESSE = 0;
  21. const byte ENFONCE = 1;
  22. const byte PRESSE = 2;
  23.  
  24. byte etatAutomate = NON_PRESSE;
  25. int etatPoussoir = -1;
  26.  
  27. const byte AUCUN_EVENEMENT = 0;
  28. const byte EVENEMENT_PRESSE = 1;
  29. const byte EVENEMENT_RELACHE = 2;
  30.  
  31. const int pinPoussoirs = 0;
  32.  
  33. /*
  34.  * Lecture des poussoirs. À l'aide d'un automate, on assure que les valeurs
  35.  * transitoires sont filtrées.
  36.  */
  37. int lirePoussoirs()
  38. {
  39. int resultat;
  40. int numPoussoir = (analogRead(pinPoussoirs) + 64) / 128;
  41.  
  42. int nouvelEtatPoussoir = etatPoussoir; /* à priori rien ne change */
  43.  
  44. switch (etatAutomate) {
  45. case NON_PRESSE:
  46. if (numPoussoir < 8)
  47. etatAutomate = ENFONCE;
  48. break;
  49. case ENFONCE:
  50. if (numPoussoir < 8) {
  51. etatAutomate = PRESSE;
  52. nouvelEtatPoussoir = numPoussoir;
  53. }
  54. else {
  55. etatAutomate = NON_PRESSE;
  56. }
  57. break;
  58. case PRESSE:
  59. if (numPoussoir == 8) {
  60. etatAutomate = NON_PRESSE;
  61. nouvelEtatPoussoir = -1;
  62. }
  63. break;
  64. }
  65.  
  66. return nouvelEtatPoussoir;
  67. }
  68.  
  69. /*
  70.  * construction d'un événement en comparant
  71.  * le nouvel état des poussoirs avec l'état précédent.
  72.  */
  73. byte lireEvenement(int *numPoussoir)
  74. {
  75. byte evenement;
  76. int nouvelEtatPoussoir = lirePoussoirs();
  77.  
  78. if (nouvelEtatPoussoir == etatPoussoir)
  79. evenement = AUCUN_EVENEMENT;
  80. if (nouvelEtatPoussoir >= 0 && etatPoussoir == -1)
  81. evenement = EVENEMENT_PRESSE;
  82. if (nouvelEtatPoussoir == -1 && etatPoussoir >= 0)
  83. evenement = EVENEMENT_RELACHE;
  84.  
  85. etatPoussoir = nouvelEtatPoussoir;
  86. *numPoussoir = etatPoussoir;
  87.  
  88. return evenement;
  89. }
  90.  
  91. /*
  92.  * Initialisations. Chaque servomoteur est initialisé à l'angle minimum
  93.  * sa vitesse est mise à 0
  94.  */
  95. void setup()
  96. {
  97. /* Initialisation des servos */
  98. int numServo;
  99.  
  100. for (numServo = 0; numServo < 8; numServo++) {
  101. servoMoteur[numServo].angle = angleMin;
  102. servoMoteur[numServo].vitesse = 0;
  103. servoMoteur[numServo].etatServo = SERVO_A_ANGLE_MIN;
  104. servoMoteur[numServo].objetServo.attach(numServo+2);
  105. }
  106. }
  107.  
  108. /*
  109.  * Actualisation de l'angle du servo
  110.  */
  111. void gereServo(int numServo)
  112. {
  113. servoMoteur[numServo].objetServo.writeMicroseconds(
  114. servoMoteur[numServo].angle);
  115.  
  116. servoMoteur[numServo].angle += servoMoteur[numServo].vitesse;
  117.  
  118. if (servoMoteur[numServo].angle > angleMax) {
  119. servoMoteur[numServo].angle = angleMax;
  120. servoMoteur[numServo].vitesse = 0;
  121. servoMoteur[numServo].etatServo = SERVO_A_ANGLE_MAX;
  122. }
  123. else if (servoMoteur[numServo].angle < angleMin) {
  124. servoMoteur[numServo].angle = angleMin;
  125. servoMoteur[numServo].vitesse = 0;
  126. servoMoteur[numServo].etatServo = SERVO_A_ANGLE_MIN;
  127. }
  128. }
  129.  
  130. /*
  131.  * changement de consigne d'un servomoteur.
  132.  */
  133. void evenementServo(int numServo)
  134. {
  135. switch (servoMoteur[numServo].etatServo) {
  136. case SERVO_A_ANGLE_MIN:
  137. case SERVO_EN_MOUVEMENT_VERS_ANGLE_MIN:
  138. servoMoteur[numServo].vitesse = 1;
  139. servoMoteur[numServo].etatServo = SERVO_EN_MOUVEMENT_VERS_ANGLE_MAX;
  140. break;
  141. case SERVO_A_ANGLE_MAX:
  142. case SERVO_EN_MOUVEMENT_VERS_ANGLE_MAX:
  143. servoMoteur[numServo].vitesse = -1;
  144. servoMoteur[numServo].etatServo = SERVO_EN_MOUVEMENT_VERS_ANGLE_MIN;
  145. break;
  146. }
  147. }
  148.  
  149. void loop()
  150. {
  151. int numServo;
  152.  
  153. for (numServo = 0; numServo < 8; numServo++) gereServo(numServo);
  154.  
  155. byte evenement = lireEvenement(&numServo);
  156.  
  157. if (evenement == EVENEMENT_PRESSE) evenementServo(numServo);
  158.  
  159. delay(3);
  160. }

Télécharger

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.

  1. struct DescripteurServo {
  2. Servo objetServo;
  3. int vitesse;
  4. int angle;
  5. int pin;
  6. byte etatServo;
  7. };

Télécharger

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

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

Télécharger

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

  1. if (servoMoteur[numServo].angle > angleMax) {
  2. servoMoteur[numServo].angle = angleMax;
  3. servoMoteur[numServo].vitesse = 0;
  4. servoMoteur[numServo].objetServo.detach();
  5. servoMoteur[numServo].etatServo = SERVO_A_ANGLE_MAX;
  6. }
  7. else if (servoMoteur[numServo].angle < angleMin) {
  8. servoMoteur[numServo].angle = angleMin;
  9. servoMoteur[numServo].vitesse = 0;
  10. servoMoteur[numServo].objetServo.detach();
  11. servoMoteur[numServo].etatServo = SERVO_A_ANGLE_MIN;
  12. }

Télécharger

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.

  1. void evenementServo(int numServo)
  2. {
  3. switch (servoMoteur[numServo].etatServo) {
  4. case SERVO_A_ANGLE_MIN:
  5. servoMoteur[numServo].objetServo.attach(servoMoteur[numServo].pin);
  6. case SERVO_EN_MOUVEMENT_VERS_ANGLE_MIN:
  7. servoMoteur[numServo].vitesse = 1;
  8. servoMoteur[numServo].etatServo = SERVO_EN_MOUVEMENT_VERS_ANGLE_MAX;
  9. break;
  10. case SERVO_A_ANGLE_MAX:
  11. servoMoteur[numServo].objetServo.attach(servoMoteur[numServo].pin);
  12. case SERVO_EN_MOUVEMENT_VERS_ANGLE_MAX:
  13. servoMoteur[numServo].vitesse = -1;
  14. servoMoteur[numServo].etatServo = SERVO_EN_MOUVEMENT_VERS_ANGLE_MIN;
  15. break;
  16. }
  17. }

Télécharger

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

Zip - 1.4 ko
Programme Arduino avec coupure de la commande PWM
(clic sur l'image pour agrandir ou télécharger)

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.

Notes

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

4 Messages

  • 8 poussoirs et 8 servos, enfin ! 20 novembre 2016 19:09, par Daniel ETHUIN

    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

    repondre message

    • 8 poussoirs et 8 servos, enfin ! 1er décembre 2016 10:39, par Jean-Luc

      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.

      repondre message

  • 8 poussoirs et 8 servos, enfin ! 20 novembre 2016 20:55, par Daniel ETHUIN

    En relisant mon message, je me suis trompé dans la valeur de la 1ere resistance, il fallait lire 47 ohm.

    repondre message

  • 8 poussoirs et 8 servos, enfin ! 15 décembre 2016 11:44, par Etienne

    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

    repondre message

Répondre à cet article

Les thèmes

Archives

Informations

ModelleisenbahN | publié sous licence Creative Commons by-nc-nd 2.0 fr | généré dynamiquement par SPIP & Blog'n Glop