2 servo moteurs en simultané

Bonjour,
J’ai pour projet de construire un bras scripteur (constitué de 2 servo d’axe z et un d’axe y globalement comme notre bras humain épaule (servo) coude (servo2) et poignet (servo3) pas très utile pour la suite mais je contextualise). J’en suis pour l’instant au tout début. J’ai un souci pour coder 2 servo qui font des choses différentes en simultané.

Je suis partie du code exemple Sweep fourni par arduino.

#include <Servo.h>

Servo myservo;  
int pos = 0; 
Servo myservo2;  
int pos2 = 0; 
Servo myservo3;  
int pos3 = 0; 
int j=0;

void setup() {
  myservo3.attach(5);
  myservo2.attach(6);
  myservo.attach(9);  
}

void loop() {
    myservo2.write(175);
    while (j!=3) {
    for (pos = 0,pos3=90; (pos <= 180) && (pos3<= 110); pos += 1,pos3+=1) {
          myservo.write(pos);
          delay(20);
          myservo3.write(pos3);
          delay(100);
    }
//    for (pos = 180; pos >= 0; pos -= 1) {
//      myservo.write(pos);
//      delay(20);
//  }
  j=j+1;
 }
}

Le but est de déplacer le servo1 de 0 à 180 pendant que le servo3 fait de petits allers-retours de 90° à 110°
Quand j’effectue ce code, le servo3 se déplace de 90 à 110 pendant que le servo1 reste fixe puis le servo1 se déplace de 0 a 180 tout en laissant le servo3 à 110°.

Si quelqu’un est vraiment intéressé et disponible pour m’aider je vous transmettrait le montage complet de mon bras.

Merci!
Léa

voici un exemple de code qui fait bouger “en même temps” un certain nombre de servos

#include <Servo.h>
enum : byte {epaule, coude, poignet};
const char* nomArticulation[] = {"Epaule", "Coude", "Poignet"};
const byte pinArticulation[] = {9, 6, 5};
const byte nombreArticulations = sizeof pinArticulation / sizeof pinArticulation[0] ;

Servo articluation[nombreArticulations];
int position[nombreArticulations] = {0, 90, 60}; // positions initiales
int destination[nombreArticulations];


void bouge(byte articulationID) {
  if (articulationID >= nombreArticulations) return;       // erreur d'indice
  if (position[articulationID] == destination[articulationID]) return;  // on est arrivé
  position[articulationID] += (position[articulationID] < destination[articulationID]) ? 1 : -1; // nouvel angle
  articluation[articulationID].write(position[articulationID]);
 // *** DEBUG ***
  Serial.print(nomArticulation[articulationID]);
  Serial.write('\t');
  Serial.print(position[articulationID]);
  Serial.print(F("\t -> "));
  Serial.println(destination[articulationID]);
}

void gestionArticulations() {
  const unsigned long deltaT = 100;
  static unsigned long chrono;

  if (millis() - chrono >= deltaT) { // on fait un pas vers la destination toutes les deltaT ms
    for (byte i = 0; i < nombreArticulations; i++) bouge(i);
    chrono += deltaT;
  }
}

void setup() {
  Serial.begin(115200);
  for (byte i = 0; i < nombreArticulations; i++) {
    articluation[i].attach(pinArticulation[i]);
    articluation[i].write(position[i]);
  }

  // par exemple on règle les destinations à
  destination[epaule] = 8;
  destination[coude] = 85;
  destination[poignet] = 70;
}

void loop() {
  gestionArticulations(); // fait faire un pas à chaque articulation toutes les deltaT ms
  // ici vous pouvez faire autre chose, par exemple modifier les destinations pour le prochain pas si vous êtes arrivé 
}

il n’y a pas de calcul de synchronisation entre les 3, ils font un pas dans la bonne direction toutes les deltaT ms (100ms dans ce code) s’ils doivent bouger, c’est à dire si la position en cours est différente de la destination. ça veut dire qu’ils n’arrivent pas ensembles à la destination.

Dans cet exemple le moniteur série (à 115200 bauds) affichera

[color=purple]
Epaule	1	 -> 8
Coude	89	 -> 85
Poignet	61	 -> 70
Epaule	2	 -> 8
Coude	88	 -> 85
Poignet	62	 -> 70
Epaule	3	 -> 8
Coude	87	 -> 85
Poignet	63	 -> 70
Epaule	4	 -> 8
Coude	86	 -> 85
Poignet	64	 -> 70
Epaule	5	 -> 8
Coude	85	 -> 85
Poignet	65	 -> 70
Epaule	6	 -> 8
Poignet	66	 -> 70
Epaule	7	 -> 8
Poignet	67	 -> 70
Epaule	8	 -> 8
Poignet	68	 -> 70
Poignet	69	 -> 70
Poignet	70	 -> 70
[/color]

votre code pourrait définir une destination initiale mais une fois appelé gestionArticulations() vous pourriez regarder si le servo qui fait les petits mouvements est arrivé à sa position finale et lui redonner une autre destination (alterner entre 90° et 110°)

Alors déjà merci beaucoup mais vu le code que je vous ai fourni je débute en arduino et ce que vous me montrer me parait un peu trop avancé pour ma compréhension. J'ai lancé le programme en remplaçant juste les destinations et les positions initiales à ce que je voulais et j'obtiens exactement le même résultat qu'avec le code du premier message (avec quelques bugs en plus mais je vais supposer que ce sont simplement les servomoteurs qui sont fatigués).

la fonction gestionArticulations() va regarder si 100 ms se sont écoulées depuis le dernier mouvement et si oui, pour chaque articulation va appeler la fonction bouge()

la fonction bouge a un paramètre (articulationID ) qui est le N° de l’articulation à déplacer.

Si le N° d’articulation n’est pas correct, on ne fait rien (return)
Si la destination de cette articulation correspond à la position de l’articulation, c’est qu’on est déjà au bon angle donc on ne fait rien (return)
sinon c’est qu’il faut faire un pas d’un degré dans une direction. Pour savoir s’il faut ajouter 1 ou soustraire 1 à la position en cours, on regarde si la position courante est plus petite que la destination. Si c’est le cas on fait +1 sinon on fait -1 et on bouge le moteur

votre projet est ambitieux, il vaudrait mieux faire un tuto de base en C et quelques tutos Arduinos avant de vous lancer…

ok merci j'avais parlé trop vite j'ai maintenant compris le fond du code. on est d'accord tel qu'il est écrit là le code me permet simplement d'aller d'une position de départ du bras à une position d'arrivée. donc si je veux pouvoir faire plusieurs mouvements je devrais régler les destinations dans le void.loop ?

donc si je veux pouvoir faire plusieurs mouvements je devrais régler les destinations dans le void.loop ?

oui par exemple si vous voulez que le coude oscille

void loop() {
  gestionArticulations(); // fait faire un pas à chaque articulation toutes les deltaT ms

  if (position[coude] == destination[coude]) { // si le coude est arrivé
    if (destination[coude] == 85) destination[coude] = 90;
    else destination[coude] = 85;
  }
}

on teste si le coude a atteint sa destination, et si oui on recalcule vers une autre destination (ici j'alterne entre 85° et 90°)

ok merci je pense avoir réussi à faire ce que je voulais au niveau du code mais par contre physiquement le servomoteur de l’épaule galère un peu à tourner avec ce programme alors que lorsque je teste un truc tout simple il fonctionne bien. Pensez vous que ça peut-être du au fait que je n’utilise pas d’alimentation externe ? (c’est prévu j’ai juste plus de piles qui marchent là…)

oui il faut des alimentations externes pour les moteurs. Ne passez pas par des petites piles (ni par la sortie 5V de l'arduino), prenez des batteries Lithium Ion

Ah mince, mon école m’a déjà fournit le boitier pour les piles je pensais pouvoir me débrouiller avec ça… Pourquoi faudrait-il utiliser des batteries lithium ? quelles differences? merci

bonjour

lisez cet article sur les piles

pour ce qui est du fonctionnement, quand vous dites

le servomoteur de l’épaule galère un peu à tourner avec ce programme

quel comportement voyez vous exactement ?

et quand vous dites

lorsque je teste un truc tout simple il fonctionne bien

est-ce avec les 2 moteurs qui bougent ensemble ?

Un bras humain a sept degrés de liberté : trois dans l’épaule, un dans le coude, trois dans le poignet. Pour faire pareil il vous faudrait sept servos. Votre projet s’apparente peut-être plus à ce qu’on appelle un “robot scara”, lequel a trois degrés de liberté. Une photo de votre mécanique et l’explication de ce que vous voulez faire avec serait utile pour vous guider sur la manière de commander ça.

Bonjour, j’ai réussi à faire fonctionner le programme. Je reviens simplement vers vous pour une question sur cette fonction :

void bouge(byte articulationID) {
  if (articulationID >= 3) return;       // erreur d'indice
  if (position[articulationID] == destination[articulationID]) return;  // on est arrivé
  position[articulationID] += (position[articulationID] < destination[articulationID]) ? 1 : -1; // nouvel angle
  articulation[articulationID].write(position[articulationID]);
}

J’ai du mal à comprendre la 3ème ligne dans le détail. Que signifie “?1:-1”
Merci

l’opérateur condition ? expression1 : expression2 (appelé opérateur ternaire) s’évalue comme cela:

si la condition est vraie alors on exécute expression1 sinon on exécute expression2

donc ici j’ajoute à la position courante de l’articulation soit +1 ou -1 en fonction de la direction dans laquelle on doit bouger (si je suis en dessous de l’angle cible il faut rajouter 1, sinon il faut enlever 1)

Merci beaucoup c’est plus clair.
Je suis maintenant en train d’essayer de programmer plusieurs positions du bras. En fait, pour tracer mon trait horizontal j’ai calculé plusieurs positions d’angles qui nous permettent d’obtenir ce qu’on veut. c’est-à-dire qu’il faut que les servo de l’épaule et du coude arrivent à leur position simultanément avant de repartir pour la suivante.

#include <Servo.h>
const byte pinArticulation[] = {9, 6, 5};
const char* nomArticulation[] = {"Epaule", "Coude", "Poignet"};

Servo articulation[3];
int position[3] = {159, 140, 110}; // positions initiales
int destination[3][4]={{167, 173, 177, 179},
                        {130, 119, 109, 100},
                        {110, 110, 110, 110}};

void bouge(byte articulationID) {
  if (articulationID >= 3) return;       // erreur d'indice
  for (byte j = 0; j<4; j++) {
  if (position[articulationID] == destination[articulationID][j]) return;  // on est arrivé
  position[articulationID] += (position[articulationID] < destination[articulationID][j]) ? 1 : -1; // nouvel angle
  articulation[articulationID].write(position[articulationID]);
  }
}

void gestionArticulations() {
  const unsigned long deltaT = 50;
  static unsigned long chrono;

  if (millis() - chrono >= deltaT) { // on fait un pas vers la destination toutes les deltaT ms
    for (byte i = 0; i < 3; i++) bouge(i);
    chrono += deltaT;
  }
}

void setup() {
  Serial.begin(115200);
  for (byte i = 0; i < 3; i++) {
    articulation[i].attach(pinArticulation[i]);
    articulation[i].write(position[i]);
  }
}

void loop() {  
  
    gestionArticulations(); // fait faire un pas à chaque articulation toutes les deltaT ms
}

Finalement j’ai seulement changé destination en un tableau de tableau et ajouté une boucle for dans la fonction bouge.

Qu’en pensez vous?

je ne suis pas sûr de comprendre ce que vous voulez faire dans la boucle for dans la fonction bouge

je voudrais que l’epaule arrive a 167 en même temps que le coude à 130, puis l’epaule à 173 en meme temps que le coude a 119 etc… En fait on a plusieurs positions à atteindre qui sont échelonnées

comme dit dans la première réponse

il n’y a pas de calcul de synchronisation entre les 3, ils font un pas dans la bonne direction toutes les deltaT ms (100ms dans ce code) s’ils doivent bouger, c’est à dire si la position en cours est différente de la destination. ça veut dire qu’ils n’arrivent pas ensembles à la destination.

Ce que vous voulez faire nécessite de modifier les pas à effectuer.

Si l’épaule va de A à B et le poignet de C à D et que vous voulez qu’ils arrivent ensemble, Il faut définir par exemple le nombre de pas pour y arriver et faire une fonction linéaire en fonction de la distance à parcourir

par exemple si l’épaule doit aller des 10 à 40 (donc 30°) et le poignet de 60 à 20 (donc 40° en valeur absolue) vous pourriez dire que vous allez effectuer 40 pas de 1° (prendre le max des déplacements) mais pour l’épaule certaines fois vous ne ferez pas un pas. c’est une simple fonction affine à calculer :slight_smile:

Vous avez à faire une interpollation. Si vous ne gérez pas les accélérations, ll y a plusieurs algos pour faire ça mais le plus simple est de calculer le nombre de pas à effectuer sur chaque axe, déterminer le temps de déplacement voulu, par exemple 5000 ms, et faire une boucle sur une fréquence d’horloge, par exemple 100 fois par seconde. Donc vous aurez 50 passages dans la boucle pendant le mouvement. En théorie, si vous divisez les nombre de “pas” à effectuer par 50, vous aurez l’incrément de pas à chaque passage dans la boucle. Comme il est fractionnaire, au vu des valeurs de position que vous donnez, soit vous faites une incrémentation en flottant et vous vous ramenez en valeur entière par une transformation flottant-entier, soit vous travaillez entièrement en entiers, par exemple en prenant une unité de résolution égale à un 512eme de vos pas, et vous détectez par soft les passages en dessus / en dessous des multiples de 512 pour modifier votre sortie de commande servo. Vous pouvez utiliser une technique voisine, à peine plus compliquée, en gérant les accélérations et décélérations.

Voici un exemple. On fixe une limite de vitesse, identique sur tous les axes à 0.2 pas par intervalle de temps de 50 ms. Le temps du déplacement sera fixé par l’axe le plus sollicité.

//#include <Servo.h>
//const byte pinArticulation[] = {9, 6, 5};
const char* nomArticulation[] = {"Epaule", "Coude", "Poignet"};
const byte nombreSegments = 4 ;
const float vitesseMax = 0.2 ; // pas par 1/20 de s

//Servo articulation[3];
int position[3] = {159, 140, 110}; // positions initiales
int destination[3][nombreSegments]={
     {167, 173, 177, 179},
     {130, 119, 109, 100},
     {110, 110, 110, 110}};
float vitesse [3][nombreSegments] ; // vitesse en pas par 1/20eme de seconde
float positionFractionnaire [3] ;
int segmentEnCours ;

void calculeSegment (int segment) {
    // détermination de la course de chaque axe, de la course maxi, des vitesses
    int course [3] ;
    int courseMax = 0 ;
    for (int axe = 0 ; axe < 3 ; axe++) {
        if (segment == 0) course [axe] =  destination[axe][segment] - position [axe] ;
        else course [axe] = destination[axe][segment] - destination[axe][segment-1] ;
        courseMax = max (courseMax, course [axe]) ;
    }
    // Calcule le temps de déplacement sur ce segment
    float temps = courseMax / vitesseMax ;
    // calcule la vitesse sur chaque axe
    for (int axe = 0 ; axe < 3 ; axe++) vitesse [axe][segment] = course [axe] / temps ;
}

void bouge(byte segment, byte axe) {
  // Calcul de la position fractionnaire et si nécessaire mouvement de l'axe
  positionFractionnaire[axe] += vitesse [axe][segment] ;
  int pos = round (positionFractionnaire [axe]) ;
  Serial.print (pos) ; 
  if (pos != position [axe]) {
      //articulation[axe].write(pos);
      Serial.print (" * ") ;
      position [axe] = pos ;
  } else Serial.print ("   ") ;
}

boolean gestionArticulations() {
    const unsigned long deltaT = 50;
    static unsigned long chrono;
  
    // on examine la nécessité de faire un pas vers la destination toutes les deltaT ms
    if (millis() - chrono < deltaT) return false ;
    Serial.print (chrono) ; Serial.print ("  ") ;
    chrono += deltaT;
    // Test : changement de segment / arrivée au but
    int distanceCible = 0 ;
    for (int axe = 0 ; axe < 3 ; axe++) 
        distanceCible += abs (position[axe] - destination[axe][segmentEnCours]) ;
    if (distanceCible == 0) {
        segmentEnCours += 1 ;
        if (segmentEnCours >= nombreSegments) return true ; // arrivé
    } 
    // On fait évoluer la position
    for (byte axe = 0; axe < 3; axe++) bouge(segmentEnCours, axe);
    Serial.println () ;
    return false ;
}

void setup() {
  delay (200) ;
  Serial.begin(115200);
  while (!Serial) ;
  delay (200) ;
  for (byte axe = 0; axe < 3; axe++) {
    //articulation[axe].attach(pinArticulation[axe]);
    //articulation[axe].write(position[axe]);
    positionFractionnaire [axe] = position [axe] ;
  }
  for (byte segment = 0 ; segment < nombreSegments ; segment++) 
      calculeSegment (segment) ;
  segmentEnCours = 0 ;
}

void loop() {  
    if (gestionArticulations()) while (true) ; // harakiri 
}

Comme je n’ai pas les servos, j’ai remplacé la sortie vers chacun d’eux par une écriture sur la console. Les astérisques, sur cette écriture, vous montrent où arrivent les changements sur chaque servo.

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.