Aide programmation servomoteurs + joysticks

Bonjour à tous,
Dans le cadre d'un projet en école d’électronique j'ai besoin de réaliser une liaison entre des servomoteurs et deux joysticks par un code ARDUINO. Je n'utilise pas leur fonction switch mais uniquement leurs axes X et Y. J'arrive à récupérer les valeurs des axes X et Y de mes joysticks avec une fonction toute simple et j'aimerais les donner aux servomoteurs. Sauf que j'utilise une carte SHIELD pour contrôler ses derniers et si j'ai bien compris elle utilise une librairie un peu spécifique que je ne comprend pas. Je voudrais donc savoir si quelqu'un pouvais me dire/ m'expliquer comment m'y prendre.
Ma carte ARDUINO est une MEGA 2560 et ma SHIELD une doit.am
Le programme que j'ai fait pour récupérer les valeurs des joysticks est le suivant :

int VRx = 0;
int VRy = 1;
int VRx2 = 2;
int VRy2 = 3;

int xPosition = 0;
int yPosition = 0;
int mapX = 0;
int mapY = 0;
int xPosition2 = 0;
int yPosition2 = 0;
int mapX2 = 0;
int mapY2 = 0;

void setup() {
  Serial.begin(9600); 
  
  pinMode(VRx, INPUT);
  pinMode(VRy, INPUT);
  pinMode(VRx2, INPUT);
  pinMode(VRy2, INPUT);
}

void loop() {
  xPosition = analogRead(VRx);
  yPosition = analogRead(VRy);
  mapX = map(xPosition, 0, 1023, -512, 512);
  mapY = map(yPosition, 0, 1023, -512, 512);
  xPosition2 = analogRead(VRx2);
  yPosition2 = analogRead(VRy2);
  mapX2 = map(xPosition2, 0, 1023, -512, 512);
  mapY2 = map(yPosition2, 0, 1023, -512, 512);
  
  Serial.print("X: ");
  Serial.print(mapX);
  Serial.println();
  Serial.print(" | Y: ");
  Serial.print(mapY);
  Serial.println();
  Serial.print("X2: ");
  Serial.print(mapX2);
  Serial.println();
  Serial.print(" | Y2: ");
  Serial.print(mapY2);
  Serial.println();
  
  delay(1000);
}

et le programme pour tester mes moteurs, que j'ai trouvé est le suivant :

#include <Wire.h>
#include <ServoDriver.h>

ServoDriver pwm = ServoDriver();

#define SERVOMIN  102 // this is the 'minimum' pulse length count (out of 4096)
#define SERVOMAX  512 // this is the 'maximum' pulse length count (out of 4096)

// IMPORTANT: Servo num #
uint8_t servonum = 0;

void setup() {

  pwm.begin();
  pwm.setPWMFreq(50);  // servos run at 50 Hz
}

void loop() {
  // Drive ONE servo  at a time

  for (uint16_t pulselen = SERVOMIN; pulselen < SERVOMAX; pulselen++) {
    pwm.setPWM(servonum, 0, pulselen);
  }
  delay(300);
  for (uint16_t pulselen = SERVOMAX; pulselen > SERVOMIN; pulselen--) {
    pwm.setPWM(servonum, 0, pulselen);
  }
  delay(300);
}

Merci par avance de votre aide ;D

le programme pour MEGA ne fait que lire les entrées analogique A0..A3 et les associer à une valeur entre -512 et +512

où est-ce que le shield dont vous parlez rentre en ligne de compte et quel est ce shield exactement ?? (lien)

C'est bien ça le problème, j'ai deux programmes qui sont totalement indépendants et je ne sais pas comment les mettre en relations. Ma carte SHIELD est faite pas un constructeur chinois, j'ai pas beaucoup d'infos dessus, voici ce que j'ai trouvé pour ses caractéristiques :
http://www.vvdoit.com/original-doit-2-way-motor-16-way-servo-shield-motor-extension-board-for-mobile-robot-arm-tank-car-toy-p1888049.html

et le programme que j'ai trouvé pour tester les servomoteurs est sur github :

Comme je l'ai dit le problème c'est que je ne connais pas du tout la bibliothèque que j'utilise et je ne sais pas si elle est indispensable. Je ne sais donc pas si je devrais essayer de faire avec ou sans.

C'est la 1ère fois que j'utilise réellement une carte Arduino, je débute totalement donc excusez-moi par avance si je dis des bêtises ou si je peux paraître un peu stupide ^^'

Je ne connais pas ce shield mais en regardant comme ça rapidement, c'est un shield qui se branche sur un UNO et capable de gérer 2 moteurs et/ou 16 Servos

Pour les Servo, le shield utilise l'I2C pour communiquer.

Sur un UNO les pins de contrôle sont sur A4 (SDA), A5 (SCL) et aussi repiquées après la pin 13

uno.png

mais sur votre MEGA elles seront sur 20 (SDA) et 21 (SCL).

Si vous avez un MEGA officiel récent, il se peut qu'elles soient aussi repiquées après la pin 13 comme sur le UNO auquel cas ça peu marcher, mais si vous avez un clone et qu'il n'y a pas ces pins, alors la connexion I2C n'est pas établie et donc les commandes servo ne vont pas fonctionner.

Dans ce cas donc, si vous avez enfiché directement le shield sur votre MEGA, la librairie qui utilise sans doute SDA et SCL ne va pas piloter les bonnes broches du shield

il faut donc que vous "tordiez" un peu les pins du shield qui sont connectées à SCL et SDA (ils sont à 2 endroits) de manière à ce qu'elle ne s'enfoncent plus dans votre MEGA et que vous mettiez un fil qui va du vrai SCL du MEGA vers une de ces 2 pins SCL (ou les 2, je ne sais pas s'ils les ont vraiment reliés) et un fil qui va du vrai SDA du MEGA vers une de ces 2 pins SDA (ou les 2, je ne sais pas s'ils les ont vraiment reliés)

Je n'ai pas votre shield, donc je n'ai pas testé...

Pour bien comprendre les moteurs (et pour bien débuter avec l'arduino), c'est bien de lire Le mouvement grâce aux moteurs - les tutos d'eskimon.

uno.png

Bonjour,
Tout d'abord merci beaucoup pour votre aide.
Après relecture de ce topic, je me suis rendu compte que je me suis mal exprimé.
Veuillez m'en excuser.
Ce que j'ai oublié de dire, c'est que j'ai déjà tester les sevomoteurs ET les joysticks avec les codes que j'ai mentionner précédemment. Je dis donc que je sais que les servomoteurs ET les joysticks fonctionnent. Autrement dit, ma carte ARDUINO et ma carte SHIELD sont déjà branché et je sais comment brancher tous les composants dessus (j'avais reçu l'aide de certains camarades qui m'ont aider). Je pense donc ne pas avoir besoin d'aide sur ce plan. J'ai fait marché indépendamment mes moteurs et mes joysticks. Tout mon problème se situe donc dans le code, j'aimerais associer écrire un code qui me permet de gérer les servomoteurs avec les joysticks.
Et pour répondre correctement à votre question du ou ma SHIELD entre en compte, elle me permet de :

  1. Simplifier mes branchements de servomoteurs
  2. Normalement simplifier le code
    Si j'ai bien compris c'est la bibliothèque ServoDriver.h qui entre en compte pour gérer les moteurs de la carte SHIELD.

est-ce que vous savez écrire (et poster) un code qui met chaque moteur à une position donnée.

Si j'ai bien compris, ça doit ressembler à cela :

#include <Wire.h>
#include <ServoDriver.h>  // J'introduis les bibliothèques

ServoDriver pwm = ServoDriver();

#define SERVOMIN  102 
#define SERVOMAX  512 

uint8_t servonum0 = 0; // Je définis chaque servomoteurs à leurs pin respectifs qui les gèrent
uint8_t servonum1= 1;
uint8_t servonum2 = 2;
uint8_t servonum3 = 3;

void setup() {

  pwm.begin();
  pwm.setPWMFreq(50);  // Met tous les servomoteurs à une fréquence de 50Hz
}

void loop() {

}

La je définis tous les servomoteurs et je les mets à une fréquence de 50 Hz (qui est, en fait il me semble, leur vitesse)

Lien vers votre bibliothèque ServoDriver.h ?

Alors ça c'est le code de la bibliothèque qui comprend 2 fichiers, un fichier .cpp et un fichier .h :
Code du fichier cpp :

/***************************************************
  Library for 2 DC motor & 16 Servo driver

  by DOIT. http://www.doit.am
 ****************************************************/

#include <ServoDriver.h>
#include <Wire.h>
#if defined(__AVR__)
 #define WIRE Wire
#elif defined(CORE_TEENSY)
 #define WIRE Wire
#else // Arduino Due
 #define WIRE Wire
#endif


ServoDriver::ServoDriver(uint8_t addr) {
  _i2caddr = addr;
}

void ServoDriver::begin(void) {
 WIRE.begin();
 reset();
}

void ServoDriver::reset(void) {
 write8(PCA9685_MODE1, 0x0);
}


void ServoDriver::setPWMFreq(float freq) {

  freq *= 0.9;
  float prescaleval = 25000000;
  prescaleval /= 4096;
  prescaleval /= freq;
  prescaleval -= 1;

  uint8_t prescale = floor(prescaleval + 0.5);

  uint8_t oldmode = read8(PCA9685_MODE1);
  uint8_t newmode = (oldmode&0x7F) | 0x10;
  write8(PCA9685_MODE1, newmode);
  write8(PCA9685_PRESCALE, prescale);
  write8(PCA9685_MODE1, oldmode);
  delay(5);
  write8(PCA9685_MODE1, oldmode | 0xa1);

}

void ServoDriver::setPWM(uint8_t num, uint16_t on, uint16_t off) {

  WIRE.beginTransmission(_i2caddr);
  WIRE.write(LED0_ON_L+4*num);
  WIRE.write(on);
  WIRE.write(on>>8);
  WIRE.write(off);
  WIRE.write(off>>8);
  WIRE.endTransmission();
}

uint8_t ServoDriver::read8(uint8_t addr) {
  WIRE.beginTransmission(_i2caddr);
  WIRE.write(addr);
  WIRE.endTransmission();

  WIRE.requestFrom((uint8_t)_i2caddr, (uint8_t)1);
  return WIRE.read();
}

void ServoDriver::write8(uint8_t addr, uint8_t d) {
  WIRE.beginTransmission(_i2caddr);
  WIRE.write(addr);
  WIRE.write(d);
  WIRE.endTransmission();

}

Code du fichier .h :

/***************************************************
  Library for 2 DC motor & 16 Servo driver

  by DOIT. http://www.doit.am
 ****************************************************/

#ifndef _ServoDriver_H
#define _ServoDriver_H
#if ARDUINO >= 100
 #include "Arduino.h"
#else
 #include "WProgram.h"
#endif

#define LED0_ON_L 0x6
#define PCA9685_MODE1 0x0
#define PCA9685_PRESCALE 0xFE


class ServoDriver {
 public:
  ServoDriver(uint8_t addr = 0x40);
  void begin(void);
  void reset(void);
  void setPWMFreq(float freq);
  void setPWM(uint8_t num, uint16_t on, uint16_t off);

 private:
  uint8_t _i2caddr;
  void write8(uint8_t addr, uint8_t d);
  uint8_t read8(uint8_t addr);
};

#endif

et le lien pour télécharger la bibliothèque :

À regarder comme cela je serais plus tenté de dire que c’est la méthode void ServoDriver::setPWM(uint8_t num, uint16_t on, uint16_t off) { qui sert à piloter un servo particulier et setPWMFreq() servirait plutôt à piloter le PWM global du module

Sans trop savoir, je dirais que num serait le N° du moteur, et on et off permettraient de préciser quand le PWM doit débuter et s’arrêter (des chiffres entre 0 et 4095) - comme sur ce Adafruit —> ils ont un tuto sur leur carte

En branchant un servo sur le port 0 et un autre sur le port 3, et un autre sur le port 5, Je testerais un truc du genre

#include <Wire.h>
#include <ServoDriver.h>

ServoDriver controleur = ServoDriver();

#define SERVOMIN  102 // this is the 'minimum' pulse length count (out of 4096)
#define SERVOMAX  512 // this is the 'maximum' pulse length count (out of 4096)

// IMPORTANT: Servo num #
const uint8_t servos[] = {0,3,5}; // les pins du module où sont branchés vos servos
const uint8_t nbServos = sizeof(servos)/sizeof(servos[0]);

void setup() {
  controleur.begin();
  controleur.setPWMFreq(50);  // servos  @ 50 Hz
}

void loop()
{
  for (uint8_t s = 0; s < nbServos; s++) {
    for (int16_t pulselen = SERVOMIN; pulselen < SERVOMAX; pulselen++) controleur.setPWM(servos[s], 0, pulselen);
    delay(500);
    for (int16_t pulselen = SERVOMAX; pulselen > SERVOMIN; pulselen--) controleur.setPWM(servos[s], 0, pulselen);
  }
}

et voir si vos 3 servos font un aller l’un après l’autre

Je suis sur mon mobile donc tapé ça ici sur la base du code de votre premier post, dites nous ce qu’il se passe avec ce code

Quand j'entre le code et que je le vérifie j'ai un message d'erreur :
'pwm' was not declared in this scope

A la ligne :

 for (int16_t pulselen = SERVOMAX; pulselen > SERVOMIN; pulselen--) pwm.setPWM(servonum, 0, pulselen);

Ah oui je l’ai appelé controleur pas pwm - corrigé dans le code ci dessus

Bon, j'ai téléversé le code dans la carte Arduino, il marche parfaitement, j'ai les 3 moteurs qui font successivement un aller-retour de rotations droite puis gauche.

C’est un bon début... :slight_smile:

Donc controleur.setPWM(N° du servo, 0, valeur); va vous donner une position.

Dans le code ci dessus valeur variait 102 et 512. Est-ce que ça vous donnait la plage de tous les angles entre 0 et 180 ? Si non en tâtonnant trouvez la valeur basse qui donne proche de 0° et la valeur haute qu’il donne proche de 180°
Voici le code qui met un angle donné sur le servo connecté en broche 0

#include <Wire.h>
#include <ServoDriver.h>

ServoDriver controleur = ServoDriver();

void setup() {
  controleur.begin();
  controleur.setPWMFreq(50);  // servos  @ 50 Hz
  controleur.setPWM(0, 0, 102); // <<=== changez 102  avec différentes valeurs pour trouver 0° et 180°
}

void loop() {  }

Si oui il faut que vous associez la valeur analogique que vous lisez de votre joystick (qui sera entre une borne min et une borne Max) vers ce 102 ... 512 par une « fonction de transfert » de type valeur = A x joystick + B et il faut trouver À et B et sachant que pour joystick = borneMin, valeur doit être 102 et pour joystick = borneMax, valeur doit être 512

NB/ (La fonction map() fait ça pour vous)

Donc la loop doit ressembler à cela:
lire les 4 entrées analogiques sur A0..A3
Les convertir en valeur correspondantes
Affecter aux servos par controleur.setPWM

ça devrait vous mettre sur la voie...

Ah oui effectivement, merci beaucoup je viens de comprendre quelque chose d'assez important.
Je ne comprenais pas pourquoi mes moteurs, même à l'arrêt faisait du bruit. C'est parcequ'il essayait d'aller à des valeurs inférieurs ou supérieurs à celles qu'ils pouvaient aller.
La plage à l'air de se situer entre environ 130 et 450.
J'ai donc modifier le code de mes joysticks qui ressemblent à ça maintenant :

int VRx = 0;
int VRy = 1;

int xPosition = 0;
int yPosition = 0;
int mapX = 0;
int mapY = 0;

void setup() {
  Serial.begin(9600); 
  
  pinMode(VRx, INPUT);
  pinMode(VRy, INPUT);
  
}

void loop() {
  xPosition = analogRead(VRx);
  yPosition = analogRead(VRy);
  mapX = map(xPosition, 0, 1024, 130, 450);
  mapY = map(yPosition, 0, 1024, 130, 450);
  
  Serial.print("X: ");
  Serial.println();
  Serial.print(mapX);
  Serial.println();
  Serial.print(" | Y: ");
  Serial.print(mapY);
  Serial.println();

  delay(500);
}

Après vérification, il me renvois bien des valeurs entre 130 et 450

J'ai donc suivis vos instructions et écrit cela :

#include <Wire.h>
#include <ServoDriver.h>

int VRx = 0;
int VRy = 1;

ServoDriver pwm = ServoDriver();

int xPosition = 0;
int yPosition = 0;
int mapX = 0;
int mapY = 0;

const uint8_t servos[] = {0,3,5}; 
const uint8_t nbServos = sizeof(servos)/sizeof(servos[0]);

void setup() {
  Serial.begin(9600); 
  pwm.begin();
  pwm.setPWMFreq(50);  // servos  @ 50 Hz
  pinMode(VRx, INPUT);
  pinMode(VRy, INPUT);
  
}

void loop() {
  xPosition = analogRead(VRx);
  yPosition = analogRead(VRy);
  mapX = map(xPosition, 0, 1024, 130, 450);
  mapY = map(yPosition, 0, 1024, 130, 450);
  pwm.setPWM(servos[s], 0, mapX);
  delay(500);
}

Apres test, cela marche parfaitement !
Merci beaucoup !
Une dernière question, comment puis-je m'y prendre si je veux que les servo se déplace progressivement lorsque j'avance les joysticks et qu'il ne prennent donc pas la position par rapport au joystick.
Autrement dit que les moteurs tourne petit à petit que j'avance le joystick. Donc plus on avance le joystick plus cela fait tourner le moteur rapidement.

Vous êtes sûr que ça marche? Parce que dans ce bout de code pwm.setPWM(servos[s], 0, mapX);la variable s n’a pas été définie..

Si je comprend bien vous voulez maintenant contrôler l’évolution de l’angle en fonction du joystick

Par exemple si le joystick est au milieu l’angle est 90°, si je monte un peu le joystick l’angle doit se mettre à augmenter et si je le baisse à diminuer et plus le joystick est loin du centre plus l’evolution De l’angle doit être rapide.

C’est ça ?

Comme vous ne contrôlez pas la vitesse de déplacement du servo, juste son angle, vous allez devoir gérer le temps entre deux ordres.

Par exemple si je suis à 90°, et que vous poussez doucement, vous devez augmenter d’un degrés toutes les 300ms mais si vous poussez un peu plus fort alors vous augmentez d’un degrés toutes les 200ms et si encore plus fort alors 100ms (à affiner pour le temps d’attente).

—> votre joystick contrôle maintenant cette durée d’attente

ça se gère avec la fonction millis() et en se souvenant de l’angle en cours et en demandant de changer d’angle au bon moment

Oui, je viens de remarquer que j'ai laissé une erreur que j'ai corrigé par la suite, au final le code pour mes 4 moteurs donne :

#include <Wire.h>
#include <ServoDriver.h>

int VRx1 = 0;
int VRy1 = 1;
int VRx2 = 2;
int VRy2 = 3;

ServoDriver pwm = ServoDriver();

int xPosition1 = 0;
int yPosition1 = 0;
int xPosition2 = 0;
int yPosition2 = 0;
int mapX1 = 0;
int mapY1 = 0;
int mapX2 = 0;
int mapY2 = 0;

const uint8_t servos[] = {0,2,4,6}; 
const uint8_t nbServos = sizeof(servos)/sizeof(servos[0]);

void setup() {
  Serial.begin(9600); 
  pwm.begin();
  pwm.setPWMFreq(50);  // servos  @ 50 Hz
  pinMode(VRx1, INPUT);
  pinMode(VRy1, INPUT);
  pinMode(VRx2, INPUT);
  pinMode(VRy2, INPUT);
  
}

void loop() {
  xPosition1 = analogRead(VRx1);
  yPosition1 = analogRead(VRy1);
  xPosition2 = analogRead(VRx2);
  yPosition2 = analogRead(VRy2);
  mapX1 = map(xPosition1, 0, 1024, 130, 450);
  mapY1 = map(yPosition1, 0, 1024, 130, 450);
  mapX2 = map(xPosition2, 0, 1024, 130, 450);
  mapY2 = map(yPosition2, 0, 1024, 130, 450);
  pwm.setPWM(servos[0], 0, mapX1);
  pwm.setPWM(servos[1], 0, mapY1);
  pwm.setPWM(servos[2], 0, mapX2);
  pwm.setPWM(servos[3], 0, mapY2);
  delay(500);
}

Je l'ai testé, il marche parfaitement.
Et oui effectivement, c'est exactement ce que je veux faire.
J'ai fait quelques recherches sur internet sur cette fonction millis() mais je n'arrive pas à vraiment comprendre son fonctionnement.
En revanche j'ai parfaitement compris l'idée général pour le code.

c'est un peu dommage de ne pas mettre des tableaux pour vos éléments... ça simplifierait le code:

#include <Wire.h>
#include <ServoDriver.h>
ServoDriver servoControl = ServoDriver();

const uint8_t joystickPins[]  = {A0, A1, A2, A3};
const uint8_t servos[] = {0, 2, 4, 6};
const uint8_t nbServos = sizeof(servos) / sizeof(servos[0]);

void setup() {
  Serial.begin(115200);
  servoControl.begin();
  servoControl.setservoControlFreq(50);  // servos  @ 50 Hz
  for (uint8_t p = 0; p < nbServos; p++) pinMode(joystickPins[p], INPUT);
}

void loop() {
  for (uint8_t s = 0; s < nbServos; s++)
    servoControl.setservoControl(servos[s], 0, map(analogRead(joystickPins[s]), 0, 1024, 130, 450));
  delay(100);
}

concernant la gestion du temps, il faut associer à la position du Joystick un temps d'attente. Plus vous êtes loin du centre du joystick, plus ce temps doit être court. il faut aussi associer un sens de modification de l'angle, si vous êtes "en dessous" de la position d'équilibre alors la progression doit être négative sinon positive.

donc ça pourrait ressembler à cela (tapé ici, non testé, non compilé)

const uint8_t pinPotentionmetre = A0;

// entre ces 2 bornes on considère que le joystick ne donne pas d'ordre de mouvement
const int zoneNeutretBorneMin = 500;
const int zoneNeutretBorneMax = 524;

// temps entre deux tics pour les servos
const unsigned long attenteMin = 10;
const unsigned long attenteMax = 300;

int angle = 90; // angle entre 0° et 180°
int8_t pasModifcationAngle = 1; // +1 ou -1 ou 0

void faireUnPas()
{
  static uint32_t tempsAttente = attenteMax;
  static uint32_t chrono = 0;

  int valPot =  analogRead(pinPotentionmetre);
  if ((valPot >= zoneNeutretBorneMin) && (valPot < - zoneNeutretBorneMax)) {
    pasModifcationAngle = 0;
  } else if (valPot > zoneNeutretBorneMax) {
    // on est entre zoneNeutretBorneMax et 1023. si on est proche de 1023 on va vite, sinon on va lentement
    pasModifcationAngle = +1;
    tempsAttente = map(valPot, zoneNeutretBorneMax, 1023, attenteMax, attenteMin);
  }
  else {
    // on est entre 0 et zoneNeutretBorneMin - si on est proche de 0 on va vite, sinon on va lentement
    pasModifcationAngle = -1;
    tempsAttente = map(valPot, 0, zoneNeutretBorneMin, attenteMin, attenteMax);
  }

  if (pasModifcationAngle != 0) {
    if (millis() - chrono >= tempsAttente) {
      angle += pasModifcationAngle;
      angle = constrain(angle, 0, 180); // https://www.arduino.cc/reference/en/language/functions/math/constrain/

      // ici déplacement sur le bon angle
      Serial.println(angle);

      chrono = millis();
    }
  }
}

void setup()
{
  Serial.begin(115200);
  pinMode(pinPotentionmetre, INPUT);
}

void loop()
{
  faireUnPas();
}

J'ai lu le programme et je l'ai bien compris en partie, il me semble juste qu'il y a une faute à la ligne :

  if ((valPot >= zoneNeutretBorneMin) && (valPot < - zoneNeutretBorneMax)) {

que j'ai remplacé par :

  if ((valPot >= zoneNeutretBorneMin) && (valPot <  zoneNeutretBorneMax)) {

(juste un - en trop)
Sinon quand je téléverse le programme et que j'ouvre la console, on est bien d'accord que la console est censé m'afficher un angle avec la ligne :

      Serial.println(angle);

Sauf que elle me renvoie des ??????
Malgré cela je peux m'apercevoir que l'idée du programme marche, quand je bouge le joystick, plus il est incliné et plus il y a de ? qui apparaissent vite et si je laisse le joystick "au point mort" aucun ? supplémentaire n'apparaissent.

oui c'est un = que je voulais taper pas un moins

si vous voyez des trucs louches sur la console, c'est parce qu'il faut passer en 115200 bauds   Serial.begin(115200);

il n'y a aucune raison qui justifie de discuter à 9600 bauds avec votre ordinateur...c'est lent pour rien