Problème avec EEPROM et servos

Bonjour,

j'ai pour projet de créer un animatronic configurable pour ma fille.
Je suis partis sur 2 servos et 2 potentiomètres je sauve les valeurs dans EEPROM et ensuite les rejoue grâce a des boutons.

Cependant lorsque l'un de mes servo rejoue la séquence. Celui-ci ne tourne que dans un sens et péniblement il tente toujours de retourner au centre mais ne va jamais au dela (donc a l'opposé). Dans le code ci-dessous c'est myServo2 qui pose problème

J'ai relu mon code 1000x et je ne comprend pas d'ou viens le problème :slight_smile:

#include<Servo.h>
#include <EEPROM.h>//used to store recorded values
Servo myServo;
Servo myServo2;
int potPin = 0;//Set a variable named potPin as A0
int potPin2 = 1; 
float resolution = 120 ;//MUST be less than EEPROM.length()
float resolution2 = 120 ;
bool recording = false;
float recordTime = 10; //delay time//22sec

const int BUTTON_PINRED = 10;  // the number of the pushbutton pin
const int LED_PINRED =  8;   // the number of the LED pin
const int BUTTON_PINGREEN = 11;  // the number of the pushbutton pin
const int LED_PINGREEN =  9;
// variables will change:
int buttonState1 = 0;  // variable for reading the pushbutton status7
int LED_PINREDstatus=0;
int buttonState2 = 0;

void setup() {
  Serial.begin(9600);
  Serial.println(EEPROM.length());
  myServo.attach(4);
  myServo2.attach(3);
  
  // initialize the LED pin as an output:
  pinMode(LED_PINRED, OUTPUT);
  pinMode(LED_PINGREEN, OUTPUT);
  // initialize the pushbutton pin as an pull-up input:
  // the pull-up input pin will be HIGH when the switch is open and LOW when the switch is closed.
  pinMode(BUTTON_PINRED, INPUT_PULLUP);
  pinMode(BUTTON_PINGREEN, INPUT_PULLUP);
}
void loop() {  
  
 int val = analogRead(potPin); // read the pot value,save it in val
 val = map(val, 0, 1023, 0, 180); //scale the pot readings to 0-180
 myServo.write(val); //set the servo position according to val
 

 int val2 = analogRead(potPin2);
 val2 = map(val2, 0, 1023, 0, 180);
 myServo2.write(val2);
 

   //record
  // read the state of the pushbutton value:
  buttonState1 = digitalRead(BUTTON_PINRED);
  // control LED according to the state of button
  if(buttonState1 == LOW){// If button is pressing

for (int j = 0; j <= resolution2; j++){
      digitalWrite(LED_PINRED, HIGH);
      recording == true;
      val2 = map(analogRead(potPin2), 0, 1023, 0, 180);
      myServo2.write(val2);//set the servo position according to val2
      delay(50);
      EEPROM.write(j, val2);
      digitalWrite(LED_PINRED, LOW);
      delay(recordTime);
    }

    for (int i = 1; i <= resolution; i++) {
       recording == true;
      digitalWrite(LED_PINRED, HIGH); // turn on LED
      digitalWrite(LED_PINGREEN, LOW);
      val = map(analogRead(potPin), 0, 1023, 60, 180);
      myServo.write(val); //set the servo position according to val
      delay(50);
      EEPROM.write(i, val);
      delay(recordTime);
      }

      
      
  }
  


//play
   buttonState2 = digitalRead(BUTTON_PINGREEN);
   if(buttonState2 == LOW){

  for(int j = 0; j <= resolution2; j++){
      
      digitalWrite(LED_PINGREEN, HIGH);
      digitalWrite(LED_PINRED, LOW);
      recording == false;
      int readval2 = EEPROM.read(j);
      myServo2.write(readval2);
      delay(50);
      delay(recordTime);
      digitalWrite(LED_PINGREEN, LOW);
     }

  for (int i = 1; i <= resolution; i++) {
      digitalWrite(LED_PINGREEN, HIGH);
      digitalWrite(LED_PINRED, LOW);
      recording == false;
      int readval = EEPROM.read(i);
     myServo.write(readval);
      delay(50);
      delay(recordTime);
      digitalWrite(LED_PINGREEN, LOW);
      }
     }
  }

Comme tu utilise Serial.print, je suppose que ton montage est branché sur ton PC.
tu aurais intérêt à utiliser le moniteur série pour débugger ton programme et pas juste afficher la taille de ce qui est enregistré dans ton EEPROM.

Si j'ai bien compris ton code dans tes boucles de relectures, tu demandes à ton servos de se positionner au valeur sauvegardé dans ton EEPROM à l'adresse 0 à 120 pour myServo2 et 1 à 120 pour myServo.
Je suppose que la fonction EEPROM.read, d'une position que tu n'a pas sauvegarder te renvois 0 ou une valeur indéterminé.

Ton soucis principal, c'est que toutes boucles 0 à 120 ne servent à rien (les 4).
Car tu ne fait que lire la position de ton potar et appliquer la valeur directement à ton servos.
Pire, tu gaspille 120 écritures à chaque loop, même si je suppose que la classe EEPROM ne fait pas réellement l'écriture si la valeur n'a pas changée.

Pas du tout.
EEPROM.write() fait systématiquement les écritures.
Il faut utiliser EEPROM.update() ou EEPROM.put() si tu veux que l'écriture ne se produise que s'il y a changement de la valeur.


1er problème

Le problème avec cette ligne c'est que les écriture se produisent tant que le bouton est appuyé donc tu peux faire plusieurs fois la boucle d'écriture à la suite.

Il faudrait tester le changement d'état du bouton. C'est-à-dire

  • lire l'état du bouton
  • comparer avec son état précédent
  • si le bouton était HIGH et que maintenant il est LOW
    • lancer le cycle d'écriture
    • attendre le relâché du bouton pour continuer.
  • mémoriser l'état actuel du bouton pour la prochaine comparaison

2ème problème
Tu positionnes tes potentiomètres et tu appuies sur le bouton. Tu lances alors tes 2 boucles d'écriture qui vont écrire 120 fois la même valeurs à toutes les adresses en EEPROM.

Pour cela il faudrait que la boucle de programmation soit organisée différemment.

  • adresse EEPROM = 0
  • tant que adresse EEPROM < 120
    • lecture des potentiomètres
    • positionnement des servos
    • si appui sur le bouton
      • écrire la position des servos à l'adresse EEPROM
      • adresse EEPROM = adresse EEPROM + 1
      • attendre relâché du bouton

Ici, la sortie de boucle se fait sur l'atteinte du nombre de pas maximum. Il serait intéressant de prévoir condition supplémentaire de sortie de la boucle par un appui sur le second bouton dans le cas où tu ne voudrais pas introduire 120 pas dans la séquence de programmation.

C’est bon merci pour votre support.
J’avais mal compris le fonctionnement de EEPROM…
Et l’arduino ne peut faire bouger qu’un servo à la fois.
J’ai même pu rajouter un servo pour le mouvement des yeux ^^
Bon il es en carton en attendant l’achat d’une imprimante 3D mais l’idée es la.


Je l’ai baptisé Bender :v:t3:

Bonjour kaizoku27

Si tu utilises VarSpeedServo, tu as un mode attente fin de mouvement (wait) ou pas, qui permet de faire fonctionner plusieurs servo en même temps. Avec cette bibliothèque, tu peux même régler la vitesse du servo.

Cordialement
jpbbricole

1 Like

Bender est en métal

Tu as des exemples ?

Y en a dans le lien autant pour moi et merci du tip

Tu verra, ça facilite l'usage des servo :wink:

Comment ça, pourquoi dis tu ça ?
que veux tu faire exactement, j'ai l'impression que ton code ne correspond pas à ce que tu veux faire.

Bonjour terwal

Je me permets de te répondre, en effet avec l'usage de la bibliothèque la plus utilisée, Servo.h on ne peut fait fonctionner qu'un servo à la fois, myservo.write(val); est bloquante.
La bibliothèque VarSpeedServo a un mode attente ou pas (non bloquante), qui permet de faire fonctionner plusieurs servo à la fois.

Bonne soirée
jpbbricole

Bonjour @jpbbricole ok, je comprends mieux.
à noter même si la la librairie semble plus intéressante VarSpeedServo, il est quand même possible de faire fonctionner du "pas à pas" en simultané au prix de certaines restrictions notamment les vitesses bien sûre.
Et que du coup tu ne sais pas quand le déplacement est finis réellement?

Bonsoir terwal

Ca peut se tester avec la fonction .isMoving()

ok, la fonction m'avais échapée :+1:

oui, enfin, dans la mesure où il n'y a pas de retour servo on ne sait pas vraiment quand il est arrivé, on peut seulement l'estimer.
Si par exemple il y a un blocage mécanique, le code ne peut pas le savoir et il t'annoncera quand même que le servo ne bouge plus (ce qui en soi est vrai mais il ne sera pas à la position attendue).

C'est clair, mais tu auras exactement le même comportement avec une librairie synchrone.
L'idée ici est de faire bougé les deux servos en paralleles et pas l'un après l'autre.

Si le besoin, c'est de faire bouger les servos ensemble et qu'ils arrivent au bout en même temps la librairie varSpeedServo n'est pas plus adaptée que la librairie servo.

Il faudrait faire un automate qui implémente un algorithme type Bresenham pour piloter les servos en incrémentant/décrémentant leur durée d'impulsion respective à chaque itération pour donner un mouvement fluide. L'automate peut parfaitement être non bloquant encore que je ne pense pas que ce soit le besoin ici puisqu'il semblerait qu'il n'y ait qu'une paire de servos à faire bouger de manière synchrone mais si le besoin devait évoluer à plus de 2 servos alors il faudrait effectivement une machine à états non bloquante.

on peut faire simple avec une interpolation linéaire sur le temps du mouvement.

un truc sans la sauvegarde EEPROM qui démontre l'approche

on commence en mode enregistrement
les potentiomètres règlent les positions des servos
quand on appuie sur le bouton rouge, on enregistre la position en cours comme une nouvelle étape
➜ enregistrer un certain nombre de positions (max 100 dans ce code)

Quand on appuie sur play, ça joue toutes les étapes enregistrées puis se remet en mode enregistrement

le code ne gère pas l'EEPROM vraiment, ça resterait à faire.

le code
/* ============================================
  code is placed under the MIT license
  Copyright (c) 2024 J-M-L
  For the Arduino Forum : https://forum.arduino.cc/u/j-m-l

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in
  all copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  THE SOFTWARE.
  ===============================================
*/

#include <Toggle.h>
#include<Servo.h>
#include <EEPROM.h> // to do

const byte servoPins[] = {9, 10};
const byte nbServo = sizeof servoPins / sizeof * servoPins;
Servo servos[nbServo];

const byte potPins[nbServo] = {A0, A1};
static_assert(nbServo == sizeof(potPins) / sizeof(*potPins), "servoPins et potPins doivent avoir le même nombre de pins.");

const byte recordPin = 3;
const byte playPin = 2;

Toggle boutonRecord, boutonPlay;

struct ServoPos {
  int16_t angle[nbServo];
};

const byte maxPosition = 100;
int nbPositions = 0;
byte positionEnCours = 0;
ServoPos lesPositions[maxPosition];

const unsigned long tpsEntreDeuxPas = 100;
const unsigned long tpsEntrePositions = 5000;
unsigned long debutMouvement;
unsigned long chronoMouvement;

enum {RECORDING, PLAYING} mode = RECORDING;


void setup() {
  for (byte i = 0; i < nbServo; i++) {
    servos[i].write(map(analogRead(potPins[i]), 0, 1023, 0, 180));
    servos[i].attach(servoPins[i]);
  }
  boutonRecord.begin(recordPin);
  boutonPlay.begin(playPin);
  Serial.begin(115200);
}

void loop() {


  switch (mode) {
    case RECORDING:
      boutonPlay.poll();
      if (boutonPlay.onPress()) {
        if (nbPositions != 0) {
          positionEnCours = 0;
          for (byte i = 0; i < nbServo; i++) servos[i].write(lesPositions[0].angle[i]);
          chronoMouvement = debutMouvement = millis();
          mode = PLAYING;
        } else {
          Serial.println("aucune position enregistrée à jouer");
        }
        break;
      }

      boutonRecord.poll();
      if (boutonRecord.onPress()) {
        for (byte i = 0; i < nbServo; i++) {
          lesPositions[nbPositions].angle[i] = map(analogRead(potPins[i]), 0, 1023, 0, 180);
        }
        if (++nbPositions >= maxPosition) nbPositions = maxPosition - 1; // ne pas déborder, on écrasera la dernière
        Serial.println("ici besoin d'écrire le tableau en EEPROM - to do");
        break;
      }

      for (byte i = 0; i < nbServo; i++) servos[i].write(map(analogRead(potPins[i]), 0, 1023, 0, 180));
      break;

    case PLAYING:
      if (positionEnCours < nbPositions - 1) {
        if (millis() - debutMouvement <= tpsEntrePositions) {
          if (millis() - chronoMouvement >= tpsEntreDeuxPas) {
            chronoMouvement += tpsEntreDeuxPas;
            for (byte i = 0; i < nbServo; i++) servos[i].write(map(millis() - debutMouvement, 0, tpsEntrePositions, lesPositions[positionEnCours].angle[i], lesPositions[positionEnCours + 1].angle[i]));
          }
        } else {
          debutMouvement = millis();
          positionEnCours++;
          for (byte i = 0; i < nbServo; i++) servos[i].write(lesPositions[positionEnCours].angle[i]);
        }
      }
      else if (positionEnCours == nbPositions - 1) mode = RECORDING;
      break;
  }
}

Oui, il faudrait que @kaizoku27 précise si il a reelement un telle besoin de précision.
Ou si un mouvement non bloquant suffit, sans besoin de synchronisation, car dans ce cas la librairie varSpeedServo, parrrait simple d'utilisation.