Forums LR PRESSE

Où il est question de trains, petits et grands

  • Advertisement

Arduino - Servomoteurs

Toutes les discussions sur l'Arduino !

Modérateur: MOD

Re: Arduino - Servomoteurs

Publié: Mar 12 Nov 2013, 11:24 
Merci Guillaume, je n'avais pas vu que l'IDE fournissait une copie pour les forums. C'est plus sympa avec la coloration syntaxique :coeur1:
Avatar de l’utilisateur
jlb
Fécond
 
Messages: 679
Inscrit le: Jeu 04 Oct 2012, 15:38
Echelle pratiquée: N
Prénom: Jean-Luc

Re: Arduino - Servomoteurs

Publié: Mar 12 Nov 2013, 13:46 
Merci à tous les deux. En fonction de la longueur du code, je choisirai l'un ou l'autre.
Avatar de l’utilisateur
Arduino
Prolixe
 
Messages: 1698
Inscrit le: Mer 25 Sep 2013, 16:14

Re: Arduino - Servomoteurs

Publié: Sam 16 Nov 2013, 23:34 
L'article est également disponible sur mon blog : http://modelleisenbahn.triskell.org/spip.php?article59 avec en prime une vidéo

Plusieurs boutons poussoir sur une entrée analogique

Dans « Commande du servo-moteur par bouton poussoir», nous avons vu comment connecter un bouton poussoir à une entrée analogique. Dans celui-ci nous allons voir comment en connecter plusieurs et lire lequel est enfoncé.

Cette idée de connecter plusieurs poussoirs sur une entrée analogique n'est pas nouvelle. Une Application Note de Freescale existe et l'idée m'a été soufflée par Marc-Henri que je remercie au passage.

Principe de fonctionnement

Avec un seul poussoir, nous avons vu que l'entrée analogique de l'Arduino était tirée à la masse via une résistance de 10kΩ. L'appui sur le poussoir amenait cette entrée à 5V. Pour la suite, on va inverser la connexion. L'entrée analogique esr tirée à +5V et l'appui sur le poussoir amène cette entrée à la masse. En effet, avec plusieurs poussoirs, cette façon de faire est plus commode car, sur le réseau, la masse est distribuée partout alors que ce n'est pas le cas pour le +5V.

Image

Imaginons maintenant que nous ajoutions un second poussoir en parallèle du premier et qu'il soit connecté entre l'entrée analogique de l'Arduino et la masse via une résistance, également de 10kΩ, comme ceci.

Image

Lorsque B0 est pressé, nous avons le même comportement, l'entrée analogique est à 0V. Lorsque B1 est pressé, les deux résistances sont en série entre 5V et 0V et l'entrée analogique est à la tension qui existe entre les deux résistances. On voit qu'il s'agit d'un diviseur de tension, et donc l'entrée analogique est à 2,5V. Evidemment, on ne peut pas presser 2 poussoirs en même temps, l'Arduino ne verra que le poussoir de rang le plus bas.

On peut continuer à ajouter des poussoirs en parallèle avec la résistance adéquate pour que le diviseur de tension donne une tension précise pour chaque poussoir. Pour nos 8 boutons-poussoir, nous allons procéder de cette manière.

Image

Avec 8 poussoirs, il faut diviser les 1024 valeurs possibles en 8 intervalles de 1024 ÷ 8 = 128 valeurs. Plus on ajoute des poussoirs et plus cet intervalle devient petit. Plus cet intervalle devient petit et plus il est nécessaire d'avoir des résistances précises. Or, les résistances standards ont des valeurs déterminées et sont précises à 5%. Si les intervalles sont trop petits, il devient impossible de distinguer deux valeurs voisines de façon fiable. Cela limite le nombre de poussoirs que l'on peut connecter mais 8 poussoirs ne devraient pas poser de problème.

Image

Sur la figure précédente, la plage de valeurs analogiques a été découpée en 8 intervalles. En (a) le poussoir pressé, en (b) la tension correspondante, en (c) la valeur numérique, en (d) cette même valeur numérique en binaire. Il faut donc déterminer les résistances R1 à R7 de manière à obtenir plus ou moins la tension voulue. On va voir que ça se fait assez bien.

Une fois une valeur analogique numérisée, il faut déterminer par programme de quel poussoir il s'agit. On peut le faire par une succession de if ... else ... en testant la valeur lue par rapport au milieu des intervalles : entre 0 et 63, aucun poussoir n'est pressé, entre 63 et 191, B0 est pressé, entre 192 et 319, B1 est pressé, etc. Mais c'est un peu long. On peut écrire cela de manière plus concise, un programmeur est un fainéant, moins il écrit de code, mieux il se porte.

Pour cela, nous allons diviser la valeur lue par la taille de l'intervalle (128).
Il s'agit évidemment d'une division entière, le résultat donnerait le numéro du poussoir pressé, il s'agit des 3 bits de poids fort dans la colonne (d), ceux qui sont séparés des autres. Malheureusement, on peut voir que ce résultat change au voisinage de la valeur lue. Par exemple au voisinage de 127, le résultat peut être 0 ou 1 et au voisinage de 255, le résultat peut être 1 ou 2. Par conséquent on peut confondre les deux poussoirs !

Pour malgré tout procéder de cette manière, il suffit de décaler l'intervalle. En ajoutant la moitié de la taille de l'intervalle à la valeur lue, 64 donc, le résultat donné pas la division entière est directement le numéro du poussoir (de 0 à 7), ou bien 8 si aucun poussoir n'est pressé, et ce numéro est stable et centré au voisinage des valeurs lues, comme montré à la colonne (d). La détermination du poussoir pressé se fait alors en une ligne de code.

    int numPoussoir = (analogRead(pinPoussoirs) + 64) / 128;


Cette concision ne peut que réjouir le programmeur ^_^

Immunité au bruit et détermination des résistances

Les essais avec un seul poussoir ont employé une résistance de 10kΩ traversée par une courant de 0,5mA quand le poussoir est pressé. Avec de longs fils entre les poussoirs et l'Arduino, le bruit électrique dû aux ondes électromagnétiques qui nous environnent peut être important. Il est préférable d'utiliser des résistances de plus faible valeur afin que le courant circulant dans le circuit quand un poussoir est pressé soit plus élevé et que le bruit puisse être absorbé par l'alimentation. Nous allons donc choisir un courant maximum d'environ 15mA, soit une résistance de 330Ω à la place de la résistance de 10kΩ utilisée jusqu'à présent.

Il faut maintenant déterminer les valeurs des résistances R1 à R7 pour obtenir les tensions que nous avons déterminées. Les diviseurs de tension pour chacun des poussoirs sont les suivants.

Image

Par calcul on détermine les valeurs des résistances pour chaque poussoir. La colonne Résistance donne la résistance qui serait nécessaire, la colonne Résistance approchée donne la valeur standard employée et la colonne Tension approchée donne la tension effectivement obtenue en supposant évidemment que la résistance est juste.

Image

Le retour du rebond

Il ne s'agit pas tout à fait d'un rebond mais il va falloir prendre en compte ce que l'on appelle une transitoire.

Quand l'un des poussoirs est pressé, l'entrée analogique passe de 5V à la tension correspondante telle qu'elle a été réglée par les résistances. Mais elle ne le fait pas instantanément. Cela prend un certain temps.

Imaginons maintenant que le convertisseur analogique-numérique de l'Arduino capture la tension alors que celle-ci est en train de descendre. Le résultat va être que le logiciel croit voir une pression sur un poussoir de rang supérieur à celui véritablement pressé. De même quand le poussoir va être relâcher, la capture de la tension peut avoir lieu alors qu'elle est en train de monter et retourner également un numéro de poussoir de rang supérieur. Il faut donc filtrer ces transitoires et n'accepter la valeur brute calculée dans numPoussoir que sous certaines conditions.

Avec un seul poussoir ce phénomène n'existait pas car il n'y avait pas d'autre poussoir.

Pour filtrer les valeurs non voulues, nous allons mettre en œuvre ce que l'on appelle un système séquentiel. On représente ce genre de système par un graphe, appelé automate. Un ovale est un état, une flèche est une transition qui permet de passer d'un état à l'autre. Les textes en vert sont des conditions qui permettent ou empêchent une transition et les textes en rouge des actions qui sont effectuées lors d'une transition. L'ovale à double bordure est l'état initial. La flèche qui vient de nulle part et qui pointe sur l'état initial correspond aux actions d'initialisation.

Voici l'automate de notre système.

Image

Le passage de l'automate à un programme C est direct. On va avoir besoin d'une variable pour stocker l'état dans lequel se trouve l'automate ainsi que des variables employées par l'automate. Comme il est impossible de détecter plusieurs poussoirs enfoncés, il est inutile d'avoir une variable d'état par poussoir. Une seule variable suffit mais elle a autant d'états que de poussoirs plus une pour représenter le fait qu'aucun poussoir n'est enfoncé.

const byte NON_PRESSE = 0;
const byte ENFONCE = 1;
const byte PRESSE = 2;

byte etatAutomate = NON_PRESSE;
int etatPoussoir = -1;

const int pinPoussoirs = 0;

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


Il reste à modifier lireEvenement(...) pour l'adapter à lirePoussoirs(). En effet, en plus de l'information de présence d'événement, il est nécessaire d'obtenir le numéro du poussoir qui a déclenché l'événement. Or une fonction C ne retourne qu'une seule valeur. Il faut donc trouver un moyen pour obtenir la seconde valeur, le numéro du poussoir.

Nous allons employer un argument. En C les arguments sont passés aux fonctions par valeur. C'est à dire que la valeur contenue dans la variable que l'on passe à la fonction est recopiée dans une variable locale de la fonction. Si la fonction change le contenu de cette variable, seule sa copie est changée. Il faut donc employer un autre moyen pour que la fonction puisse modifier la variable qui lui est passée. Ce moyen, ce sont les pointeurs.

Une variable est placée dans la mémoire RAM du micro-contrôleur. On peut voir la RAM comme un tableau de cases. Chaque case a un numéro, on dit une adresse. Il est possible d'obtenir l'adresse d'une variable via l'opérateur &, de définir une variable qui soit un pointeur vers un type, via la notation * et il est possible d'écrire ou de lire dans une variable par l'intermédiaire de son adresse via l'opérateur *. Ainsi:

Code: Tout sélectionner
int *p;


définit un pointeur vers une variable de type int. Ce pointeur est pour l'instant non initialisé, il ne pointe sur rien. Essayer d'accéder à ce qu'il pointe est une erreur.

Code: Tout sélectionner
int a;
int *p = &a;


définit un pointeur p vers une variable de type int qui pointe sur a. &a retourne l'adresse de a.

Code: Tout sélectionner
*p = 3;


met la valeur 3 dans la variable a.

Pour définir la fonction lireEvenement(...) qui retourne un byte et prend pour argument un pointeur vers un int, et qui modifie la variable pointée, on écrira.

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



L'argument int *numPoussoir définit numPoussoir qui est un pointeur sur un int. À l'avant dernière ligne de la fonction, l'état du poussoir est écrit dans la variable pointée par numPoussoir, il s'agit de la ligne *numPoussoir = etatPoussoir;

Il faut donc que numPoussoir pointe sur la variable dans laquelle on désire récupérer le numéro de poussoir. Donc, pour appeler cette fonction, on écrira.

    int numPoussoir;
    byte evenement = lireEvenement(&numPoussoir);



ce qui a pour effet de passer à la fonction l'adresse, opérateur &, de numeroPoussoir afin d'obtenir la valeur désirée dans cette variable.

Essai du système

La première étape consiste à câbler sur la breadboard les poussoirs est les résistances comme ceci.

Image

Voici le programme complet de test du système. Les messages d'appui ou de relâchement des poussoirs sont affichés sur le moniteur série avec le numéro de poussoir enfoncé.

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;

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

void setup()
{
    Serial.begin(9600);
}

void loop()
{
    int numPoussoir;
    byte evenement = lireEvenement(&numPoussoir);

    switch (evenement) {
        case EVENEMENT_PRESSE:
            Serial.print("Presse : ");
            Serial.println(numPoussoir);
            break;
        case EVENEMENT_RELACHE:
            Serial.println("Relache");
            break;
    }
    
    delay(3);
}



Le système de commande à 8 boutons sur une entrée analogique est opérationnel.

Nous avons vu quelques éléments supplémentaires de langage C comme les pointeurs et leur emploi pour retourner plusieurs valeurs à partir d'une fonction. Nous avons vu rapidement les automates et comment les mettre en œuvre avec un switch ... case. La prochaine fois nous ajouterons nos 8 servo-moteurs.
Avatar de l’utilisateur
jlb
Fécond
 
Messages: 679
Inscrit le: Jeu 04 Oct 2012, 15:38
Echelle pratiquée: N
Prénom: Jean-Luc

Re: Arduino - Servomoteurs

Publié: Dim 17 Nov 2013, 08:10 
Bonjour

Pourquoi tu ne fait pas un diviseur de tension avec 7 résistances de même valeurs, entre le 5V et la masse. Cela donne 8 tensions différentes pouvant êtres connectées sur l'entrée analogique par les 8 poussoirs.

Pierre
Avatar de l’utilisateur
Pierre59
Papotier
 
Messages: 147
Inscrit le: Dim 07 Mars 2010, 09:17
Localisation: Villeneuve d'Ascq (59650)
Âge: 75
Echelle pratiquée: HO
Club: Lille Modélisme

Re: Arduino - Servomoteurs

Publié: Dim 17 Nov 2013, 08:58 
Bonjour Pierre

Parce que les différences entre 2 tensions successives ne seront pas les mêmes avec des résistances de même valeur.

Pour préciser, voici comment évolue la tension de sortie d'un diviseur de tension pour une résistance de tirage à 5V de 330Ω. Comme ceci

Image

En x, la résistance R1, en y la tension de sortie

Image
Avatar de l’utilisateur
jlb
Fécond
 
Messages: 679
Inscrit le: Jeu 04 Oct 2012, 15:38
Echelle pratiquée: N
Prénom: Jean-Luc

Re: Arduino - Servomoteurs

Publié: Dim 17 Nov 2013, 10:16 
Je viens de faire un essai avec un diviseur de tension composé de 7 résistances de 10K. Je l'alimente sous 7V (6.99) pour faciliter les calculs. J'obtiens les 8 tensions suivantes :
6.99 5.98 4.98 3.98 2.99 1.99 0.99 0.00
l'échelonnement est bien d'un volt ??????

Pierre
Avatar de l’utilisateur
Pierre59
Papotier
 
Messages: 147
Inscrit le: Dim 07 Mars 2010, 09:17
Localisation: Villeneuve d'Ascq (59650)
Âge: 75
Echelle pratiquée: HO
Club: Lille Modélisme

Re: Arduino - Servomoteurs

Publié: Dim 17 Nov 2013, 10:35 
Pourrais-tu nous donner le détail des calculs ?
Avatar de l’utilisateur
jlb
Fécond
 
Messages: 679
Inscrit le: Jeu 04 Oct 2012, 15:38
Echelle pratiquée: N
Prénom: Jean-Luc

Re: Arduino - Servomoteurs

Publié: Dim 17 Nov 2013, 10:48 
C'est des mesures.

En fait je viens de voir qu'il faut 9 états : rien et un des 8 boutons enfoncé.

Il me faut donc une résistance de plus, pour avoir 9 tensions. Il faut aussi donner un potentiel défini à l'entrée analogique quand aucun bouton n'est enfoncé, une résistance de tirage de 1M ohms sur le 0 volts (masse) devrait convenir sans trop perturber le pont diviseur.

Hélas les problèmes sont toujours plus compliqués que ce que l'on pense au début.

Pierre
Avatar de l’utilisateur
Pierre59
Papotier
 
Messages: 147
Inscrit le: Dim 07 Mars 2010, 09:17
Localisation: Villeneuve d'Ascq (59650)
Âge: 75
Echelle pratiquée: HO
Club: Lille Modélisme

Re: Arduino - Servomoteurs

Publié: Dim 17 Nov 2013, 10:57 
Effectivement si ta résistance de tirage est très grande devant les autres, tu linéarises mais dans ce cas le courant qui circule est tellement faible que le montage est très sensible au bruit et les fils des poussoirs doivent être très très courts
Avatar de l’utilisateur
jlb
Fécond
 
Messages: 679
Inscrit le: Jeu 04 Oct 2012, 15:38
Echelle pratiquée: N
Prénom: Jean-Luc

Re: Arduino - Servomoteurs

Publié: Dim 17 Nov 2013, 11:20 
Effectivement, très bonne analyse de la part de jlb.

Il ne faut pas céder à la tentation de mettre des résistances identiques ; le pont diviseur de tension peut être considéré comme un capteur. En électronique, on cherche toujours à avoir des capteurs qui soient linéaires, c'est-à-dire dont la courbe signal en fonction de l'entrée soit une droite.

Et on peut obtenir cela en utilisant des résistances différentes. Il est facile de calculer leurs valeurs avec la loi d'Ohm (cette loi est d'une importance capitale en électronique ; on s'en sert partout et elle n'est pas encore trop compliquée).

La loi d'Ohm est expliquée dans la fiche pratique II.7 du LR791 de juin 2013.

Un des articles à paraitre reprend le principe de calcul du diviseur de tension de jlb, mais pour autre chose.

Christian
Avatar de l’utilisateur
Arduino
Prolixe
 
Messages: 1698
Inscrit le: Mer 25 Sep 2013, 16:14

Re: Arduino - Servomoteurs

Publié: Dim 17 Nov 2013, 14:37 
Voici la suite.

L'article est également disponible sur mon blog : http://modelleisenbahn.triskell.org/spip.php?article60 avec en prime une vidéo

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ément que de servo. 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 du tableau (Oui le 5e puisque le premier à le numéro 0).

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.

  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 [/b]numServo < 8[/b] 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.

Code: Tout sélectionner
#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;

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

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

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

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
Code: Tout sélectionner
DescripteurServo
un membre pour stocker le numéro de broche. Appelons le
Code: Tout sélectionner
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 > servoMoteur[numServo].angleMax) {
      servoMoteur[numServo].angle = servoMoteur[numServo].angleMax;
      servoMoteur[numServo].vitesse = 0;
      servoMoteur[numServo].objetServo.detach();
      servoMoteur[numServo].etatServo = SERVO_A_ANGLE_MAX;
    }
    else if (servoMoteur[numServo].angle < servoMoteur[numServo].angleMin) {
      servoMoteur[numServo].angle = servoMoteur[numServo].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.

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



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.

La prochain article sera consacré à l'ajout de la fonction de réglage des butées.
Dernière édition par jlb le Lun 18 Nov 2013, 23:04, édité 2 fois au total.
Avatar de l’utilisateur
jlb
Fécond
 
Messages: 679
Inscrit le: Jeu 04 Oct 2012, 15:38
Echelle pratiquée: N
Prénom: Jean-Luc

Re: Arduino - Servomoteurs

Publié: Dim 17 Nov 2013, 19:55 
Très bien expliqué, à garder bien au chaud
Guillaume22
Papotier
 
Messages: 145
Inscrit le: Dim 29 Sep 2013, 08:20

Re: Arduino - Servomoteurs

Publié: Sam 23 Nov 2013, 11:54 
Merci Guillaume :)

La suite ci-dessous.

Un article de transition entre le C et le C++. En effet, vous avez pu constater que le passage de 1 à 8 servos avait alourdi le code. On a partout des servoMoteur[numServo].blabla.

On va donc utiliser une classe C++ pour les servos. Le code est beaucoup plus clair et facile à comprendre. Le revers de la médaille : quelques notions de plus à comprendre.
Avatar de l’utilisateur
jlb
Fécond
 
Messages: 679
Inscrit le: Jeu 04 Oct 2012, 15:38
Echelle pratiquée: N
Prénom: Jean-Luc

Re: Arduino - Servomoteurs

Publié: Sam 23 Nov 2013, 12:01 
jlb a écrit:………... Le revers de la médaille : quelques notions de plus à comprendre.


Si cela peut nous simplifier la vie, allons Y.

:D
Cordialement,

Christian.
Avatar de l’utilisateur
likiki
Causant
 
Messages: 270
Inscrit le: Dim 29 Avr 2012, 14:21
Localisation: Corbeil Essonne
Âge: 51
Echelle pratiquée: H0 3R
Prénom: Christian

Re: Arduino - Servomoteurs

Publié: Sam 23 Nov 2013, 12:14 
Un zeste de C++

L'article est également disponible sur mon blog : http://modelleisenbahn.triskell.org/spip.php?article63

Comme nous l'avons détaillé dans l'article précédent « 8 poussoirs et 8 servos, enfin ! », le passage d'un servomoteur à 8 servomoteurs, nous à contraint à rassembler les variables permettant de décrire l'état d'un servomoteur dans une struct afin de créer un tableau de 8 éléments de ce type.

struct DescripteurServo {
  Servo objetServo;
  int vitesse;
  int angle;
  int pin;
  byte etatServo;
};
 
struct DescripteurServo servoMoteur[8];



Malheureusement, le programme manipulant un servomoteur s'en est trouvé alourdi car dorénavant, il faut préciser de quel élément du tableau il s'agit quand on veut modifier l'un des membre du descripteur de servomoteur. On se retrouve partout avec des servoMoteur[numServo]., Ce qui n'est ni joli, ni agréable.

De plus, si on examine le programme tel qu'il est actuellement, on s'aperçoit que seules deux fonctions s'occupent du descripteur de servomoteur : gereServo pour accomplir le mouvement lent et evenementServo pour donner l'ordre de mouvement. Il y a aussi setup() qui initialise les descripteurs mais on pourrait très bien le faire dans une fonction appelée par setup(). Il est donc tentant de mettre tout cela ensemble, le descripteur de servomoteur et les fonctions qui le manipule, et c'est exactement ce que permet le C++.

Le C++ va donc venir à notre secours pour simplifier et aérer considérablement notre programme.

Le C++ pris dans sa globalité est un langage complexe et nous n'avons pas besoin de tout. Nous n'allons prendre que ce dont nous avons besoin et qui va nous faciliter la tâche.


Le C++ est très utilisé dans l'Arduino. Par exemple, la bibliothèque Servo que nous utilisons est écrite en C++. C++ est une extension du C qui ajoute principalement une chose : les objets. Un objet contient une ou plusieurs données et une ou plusieurs fonctions (on dit méthode dans le jargon) pour lire/écrire cette valeur mais pas seulement. Pour créer un objet, on a besoin d'un type, comme pour créer une variable entière, on a besoin du type int. Pour créer un type d'objet, on déclare une classe. Faisons ça directement avec notre DescripteurServo :

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



Rien de renversant jusqu'à là. C'est exactement comme une struct. La particularité est de pouvoir y mettre aussi des fonctions qui deviennent donc membres de la classe. Mettons y gereServo() et evenementServo().

class DescripteurServo {
    Servo objetServo;
    int vitesse;
    int angle;
    int pin;
    byte etatServo;

  public:

    void gereServo()
    {
        objetServo.writeMicroseconds(angle);
     
        angle += vitesse;
     
        if (angle > angleMax) {
            angle = angleMax;
            vitesse = 0;
            objetServo.detach();
            etatServo = SERVO_A_ANGLE_MAX;
        }
        else if (angle < angleMin) {
          angle = angleMin;
          vitesse = 0;
          objetServo.detach();
          etatServo = SERVO_A_ANGLE_MIN;
        }
    }
    
    void evenementServo()
    {
        switch (etatServo) {
          case SERVO_A_ANGLE_MIN:
            objetServo.attach(pin);
          case SERVO_EN_MOUVEMENT_VERS_ANGLE_MIN:
            vitesse =  1;
            etatServo = SERVO_EN_MOUVEMENT_VERS_ANGLE_MAX;
            break;
          case SERVO_A_ANGLE_MAX:
            objetServo.attach(pin);
          case SERVO_EN_MOUVEMENT_VERS_ANGLE_MAX:
            vitesse = -1;
            etatServo = SERVO_EN_MOUVEMENT_VERS_ANGLE_MIN;
            break;
        } 
    }
};



Comme vous pouvez le constater, tous les servoMoteur[numServo]. ont disparu. En effet, les fonctions membre accèdent directement aux données membres de la classe. Par ailleurs, l'argument numServo des fonctions a disparu pour la même raison.
Enfin, le public: permet de rendre les deux fonctions membres appelables depuis l'extérieur de l'objet. Notez aussi que les données membres ne sont pas publiques, on ne peut donc y accéder depuis l'extérieur de l'objet.

Il reste une dernier point à régler. Dans setup(), une boucle initialise chaque descripteur de servo. Certaines initialisations sont dépendantes du numéro de servo (en fait du numéro de broche à laquelle le servo est connecté), d'autre ne le sont pas. En C++, un objet peut avoir un constructeur. Il s'agit d'une fonction membre qui porte le même nom que la classe et qui est exécutée lors de la création de l'objet. Nous allons ajouter ce constructeur pour y mettre les initialisations indépendantes du numéro de broche. Enfin nous allons ajouter une fonction membre pour les initialisations qui sont dépendantes du numéro de broche.

    DescripteurServo()
    {
        angle = angleMin;
        vitesse = 0;
        etatServo = SERVO_A_ANGLE_MIN;
    }
    
    void connecte(int pinDeConnection)
    {
        pin = pinDeConnection;
        objetServo.attach(pinDeConnection);
    }



Il reste à modifier le reste du programme pour l'adapter. Tout d'abord, dans setup() les initialisations consistent maintenant à appeler la fonction membre connecte(...). Les intialisation indépendantes du numéro de broche sont déjà faites.

void setup()
{
    /* Initialisation des servos */
    int numServo;
  
    for (numServo = 0; numServo < 8; numServo++)
        servoMoteur[numServo].connecte(numServo + 2);
}



Notez la syntaxe pour appeler une fonction membre. servoMoteur[numServo] est un objet du tableau, le . permet d'accéder à un de ses membres et connecte(numServo +2) permet d'appeler la fonction membre connecte en lui passant numServo + 2 comme argument.

De la même manière, la boucle qui appelait gereServo() pour chacun des servomoteurs est modifiée.

    for (numServo = 0; numServo < 8; numServo++)
        servoMoteur[numServo].gereServo();



Enfin, la notification d'un événement sur le poussoir est aussi modifié.

    byte evenement = lireEvenement(&numServo);
  
    if (evenement == EVENEMENT_PRESSE) {
        servoMoteur[numServo].evenementServo();
    }



Rien de bien sorcier finalement. Le programme est beaucoup plus clair écrit de cette manière et cerise sur le gâteau, il prend 274 octets de moins une fois compilé !

Le sketch Arduino correspondant est à téléchargé en bas de la page de l'article
Avatar de l’utilisateur
jlb
Fécond
 
Messages: 679
Inscrit le: Jeu 04 Oct 2012, 15:38
Echelle pratiquée: N
Prénom: Jean-Luc

PrécédentSuivant

Retour vers Arduino

Qui est en ligne ?

Utilisateur(s) parcourant actuellement ce forum : Aucun utilisateur inscrit et 3 invité(s)