Suivi de ligne, moteurs pas à pas trop lents

Bonjour à tous,

Je débute dans la programmation d'Arduino. J'ai pour projet un robot suiveur de ligne.

J'utilise un capteur QTR8A (analogique) pour le suivi de ligne afin de recentrer le robot s'il dévie par rapport à une ligne noire. J'ai déjà testé ce capteur seul, mon code fonctionne, chaque cellule retourne une valeur entre 0 et 1000 (plus proche de 1000 si la couleur est noire) et je visualise bien où est la ligne noire par rapport au milieu du capteur en fonction des valeurs retournées par chacune des 8 cellules du capteur QTR8A.

Ensuite, j'ai également testé les deux moteurs pas à pas (28BYJ-48) avec leurs drivers seuls. J'arrive à faire varier leur vitesse, bien que leur couple reste faible. J'ai retenu une vitesse qui me paraissait pas mal pour l'avance du robot et pour effectuer les tests (baseSpeed = 500;).

Dans le code qui combine le capteur QTR8A et les deux moteurs, j'ai donc retenu les mêmes paramètres de vitesse que j'ai retenu dans le code des moteurs seuls.
Cependant, le problème que je rencontre est que les moteurs sont très très lents, et il me semble qu'ils fonctionnent par à-coups. J'ai déjà remplacé les "delay" qui peuvent être une condition "bloquante" au programme, en utilisant plutôt "millis()", mais cela ne change rien. Je me demande si certaines opérations, comme la lecture des capteurs QTR8A ou les calculs de correction pour le suivi de ligne, peuvent ralentir le cycle de la boucle "loop()", réduisant ainsi la fréquence d'exécution de "motorLeft.runSpeed()" et "motorRight.runSpeed()". Les moteurs ne pourraient alors pas tourner en continu et fonctionneraient donc pas à-coups.

J'ai remarqué aussi que lorsqu'on donne comme consigne une vitesse trop élevé aux moteurs pas à pas, ils vibrent. Je ne pense pas que ce soit le cas ici, car la vitesse que j'ai prise, je l'ai testée avec le code des moteurs seuls.

Avez-vous une idée de pourquoi les moteurs tournent très lentement dans le code complet ?
Voyez-vous des conditions "bloquantes" qui font que les moteurs ne pourraient pas tourner de façon continue ?

Merci d'avance pour votre aide !

Voici le code complet en question, qui allie capteur QTR8A et les deux moteurs pas à pas (avec une correction de la vitesse d'un des deux moteurs en fonction de si le robot se trouve trop à gauche ou trop à droite de la ligne pour le recentrer) :


#include <QTRSensors.h>
#include <AccelStepper.h>

// Configuration du capteur QTR-8A
QTRSensors qtr;

const uint8_t SensorCount = 8;
uint16_t sensorValues[SensorCount];

// Configuration des moteurs pas à pas 28BYJ-48
#define STEPS_PER_REV 2048 // Qu'on le mette ou non, j'ai remarqué que ça ne change rien.
//Est-il nécessaire ?

// Pins pour le moteur gauche
#define MOTOR_LEFT_PIN1 2
#define MOTOR_LEFT_PIN2 4
#define MOTOR_LEFT_PIN3 3
#define MOTOR_LEFT_PIN4 5

// Pins pour le moteur droit
#define MOTOR_RIGHT_PIN1 6
#define MOTOR_RIGHT_PIN2 8
#define MOTOR_RIGHT_PIN3 7
#define MOTOR_RIGHT_PIN4 9

// Configuration des moteurs pas à pas 28BYJ-48
AccelStepper motorLeft(AccelStepper::FULL4WIRE, MOTOR_LEFT_PIN1, MOTOR_LEFT_PIN2, MOTOR_LEFT_PIN3, MOTOR_LEFT_PIN4);  // Moteur gauche
AccelStepper motorRight(AccelStepper::FULL4WIRE, MOTOR_RIGHT_PIN1, MOTOR_RIGHT_PIN2, MOTOR_RIGHT_PIN3, MOTOR_RIGHT_PIN4); // Moteur droit

// Paramètres de suivi de ligne
int baseSpeed = 500;           
int maxCorrection = 8000;      // Correction maximale

unsigned long lastPrintTime = 0; // Dernier moment d'affichage
unsigned long printInterval = 100; // Intervalle d'affichage en millisecondes
unsigned long lastMotorUpdateTime = 0; // Dernier moment de mise à jour des moteurs
unsigned long motorUpdateInterval = 5; // Intervalle de mise à jour des moteurs

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

    // Configurer les capteurs QTR-8A en mode analogique
    qtr.setTypeAnalog();
    qtr.setSensorPins((const uint8_t[]){A0, A1, A2, A3, A4, A5, A6, A7}, SensorCount);
    
    // Phase de calibration
    for (uint16_t i = 0; i < 400; i++) {
        qtr.calibrate();
        delay(20);
    }
    Serial.println("Calibration complète");

    // Initialisation des moteurs
    motorLeft.setMaxSpeed(3000);  // Vitesse maximale augmentée
    motorRight.setMaxSpeed(3000);

    
    Serial.print("Vitesse testée : ");
    Serial.println(baseSpeed);
    motorLeft.setSpeed(baseSpeed);      // Vitesse de base
    motorRight.setSpeed(baseSpeed);
    
}

// Déclarer les vitesses des moteurs comme des variables globales
int leftSpeed = 0;
int rightSpeed = 0;

void loop() {
    // Lis les valeurs des capteurs
    qtr.read(sensorValues);

    // Calculer la position de la ligne
    long position = qtr.readLineBlack(sensorValues);
    int error = position - 3500;

    // Afficher la position de la ligne, l'erreur, et les valeurs des capteurs périodiquement
    unsigned long currentTime = millis();
    if (currentTime - lastPrintTime >= printInterval) {
        lastPrintTime = currentTime;

        Serial.print("Line position: ");
        Serial.println(position);  // Affichage de la valeur retournée par qtr.readLineBlack(sensorValues)
        
        Serial.print("Error: ");
        Serial.println(error);  // Affichage de l'erreur

        Serial.print("Sensor values: ");
        for (uint8_t i = 0; i < SensorCount; i++) {
            Serial.print(sensorValues[i]);
            Serial.print('\t');
        }
        Serial.println();
        
        Serial.print("Left motor speed: ");
        Serial.println(leftSpeed);  // Affichage de la vitesse du moteur gauche
        Serial.print("Right motor speed: ");
        Serial.println(rightSpeed);  // Affichage de la vitesse du moteur droit
    }

    // Correction de la trajectoire
    int correctionFactor = abs(error) * maxCorrection / 3500;  // Facteur de correction plus modéré
    correctionFactor = constrain(correctionFactor, 0, baseSpeed * 5);  // Limiter la correction pour ne pas trop dévier

    if (error < 0) {
        // Si trop à gauche, accélérer le moteur droit pour recentrer
        leftSpeed = baseSpeed;
        rightSpeed = baseSpeed + correctionFactor;  // Correction pour le moteur droit
    } else if (error > 0) {
        // Si trop à droite, accélérer le moteur gauche pour recentrer
        leftSpeed = baseSpeed + correctionFactor;  // Correction pour le moteur gauche
        rightSpeed = baseSpeed;
    } else {
        leftSpeed = baseSpeed;
        rightSpeed = baseSpeed;
    }

    // Appliquer les vitesses aux moteurs
    motorLeft.setSpeed(-leftSpeed); // Inverser la vitesse pour le moteur gauche
    motorRight.setSpeed(rightSpeed);

    // Avancer les moteurs sans bloquer
    if (currentTime - lastMotorUpdateTime >= motorUpdateInterval) {
        motorLeft.runSpeed();
        motorRight.runSpeed();
        lastMotorUpdateTime = currentTime; // Mise à jour du dernier moment d'actualisation des moteurs
    }
}

Voici le code qui fait fonctionner uniquement les moteurs à une vitesse :

#include <AccelStepper.h>

// Pins pour le moteur gauche
#define MOTOR_LEFT_PIN1 2
#define MOTOR_LEFT_PIN2 4
#define MOTOR_LEFT_PIN3 3
#define MOTOR_LEFT_PIN4 5

// Pins pour le moteur droit
#define MOTOR_RIGHT_PIN1 6
#define MOTOR_RIGHT_PIN2 8
#define MOTOR_RIGHT_PIN3 7
#define MOTOR_RIGHT_PIN4 9

// Configuration des moteurs pas à pas 28BYJ-48
AccelStepper motorLeft(AccelStepper::FULL4WIRE, MOTOR_LEFT_PIN1, MOTOR_LEFT_PIN2, MOTOR_LEFT_PIN3, MOTOR_LEFT_PIN4);  // Moteur gauche
AccelStepper motorRight(AccelStepper::FULL4WIRE, MOTOR_RIGHT_PIN1, MOTOR_RIGHT_PIN2, MOTOR_RIGHT_PIN3, MOTOR_RIGHT_PIN4); // Moteur droit

int speed = 500;
void setup() {
  Serial.begin(9600);

  // Initialisation des moteurs
  motorLeft.setMaxSpeed(2000);  // Vitesse maximale (en pas par seconde)
  motorRight.setMaxSpeed(2000);

    Serial.print("Vitesse testée : ");
    Serial.println(speed);

    motorLeft.setSpeed(speed);  // Définir la vitesse du moteur gauche
    motorRight.setSpeed(speed); // Définir la vitesse du moteur droit

    // Faire tourner les moteurs pendant 5 secondes à cette vitesse
    unsigned long startTime = millis();
    while (millis() - startTime < 5000) {
      motorLeft.runSpeed();
      motorRight.runSpeed();
    }

  // Arrêter les moteurs après les tests
  motorLeft.setSpeed(0);
  motorRight.setSpeed(0);
  motorLeft.runSpeed();
  motorRight.runSpeed();
  
  Serial.println("Test terminé.");
}

void loop() {

}

:warning:

Post mis dans la mauvaise section, on parle anglais dans les forums généraux. ➜ je l'ai déplacé vers le forum francophone pour vous pour cette fois...

Merci de prendre en compte les recommandations listées dans "Les bonnes pratiques du Forum Francophone”

il faut appeler runSpeed en permanence, pas de temps en temps. La bibliothèque se charge de faire un pas quand c'est nécessaire.

Bonjour,
Erreur de ma part, désolé, c'est noté pour la prochaine fois.
Merci pour votre réponse !
En retirant simplement "runSpeed" de la condition, comme ceci, cela ne change rien au comportement des moteurs :


void loop() {
    // Lire les valeurs des capteurs
    qtr.read(sensorValues);

    // Calculer la position de la ligne
    long position = qtr.readLineBlack(sensorValues);
    int error = position - 3500;

    // Afficher la position de la ligne, l'erreur, et les valeurs des capteurs périodiquement
    unsigned long currentTime = millis();
    if (currentTime - lastPrintTime >= printInterval) {
        lastPrintTime = currentTime;

        Serial.print("Line position: ");
        Serial.println(position);  // Affichage de la valeur retournée par qtr.readLineBlack(sensorValues)
        
        Serial.print("Error: ");
        Serial.println(error);  // Affichage de l'erreur

        Serial.print("Sensor values: ");
        for (uint8_t i = 0; i < SensorCount; i++) {
            Serial.print(sensorValues[i]);
            Serial.print('\t');
        }
        Serial.println();
        
        Serial.print("Left motor speed: ");
        Serial.println(leftSpeed);  // Affichage de la vitesse du moteur gauche
        Serial.print("Right motor speed: ");
        Serial.println(rightSpeed);  // Affichage de la vitesse du moteur droit
    }

    // Correction de la trajectoire plus progressive
    int correctionFactor = abs(error) * maxCorrection / 3500;  // Facteur de correction plus modéré
    correctionFactor = constrain(correctionFactor, 0, baseSpeed * 5);  // Limiter la correction pour ne pas trop dévier

    if (error < 0) {
        // Si trop à gauche, accélérer le moteur droit pour recentrer
        leftSpeed = baseSpeed;
        rightSpeed = baseSpeed + correctionFactor;  // Correction pour le moteur droit
    } else if (error > 0) {
        // Si trop à droite, accélérer le moteur gauche pour recentrer
        leftSpeed = baseSpeed + correctionFactor;  // Correction pour le moteur gauche
        rightSpeed = baseSpeed;
    } else {
        leftSpeed = baseSpeed;
        rightSpeed = baseSpeed;
    }

    // Appliquer les vitesses aux moteurs
    motorLeft.setSpeed(-leftSpeed); // Inverser la vitesse pour le moteur gauche
    motorRight.setSpeed(rightSpeed);

    // Appeler "runSpeed()"" en permanence pour les moteurs

        motorLeft.runSpeed();
        motorRight.runSpeed();
}
  • essayez en appelant setSpeed() uniquement quand la vitesse a changé

  • enlevez les Serial.print à 9600 bauds ça devient vite un facteur limitant

  • vérifiez combien de temps les appels

    // Lire les valeurs des capteurs
    qtr.read(sensorValues);

    // Calculer la position de la ligne
    long position = qtr.readLineBlack(sensorValues);

Il est inutile de lire les capteurs à toute vitesse, loop() doit s'exécuter plusieurs milliers de fois par seconde.
Par contre il faut appeler runSpeed() le plus souvent possible pour ne pas rater de pas.
La loop() devrait avoir une structure dans ce genre:

void loop(void){
    unsigned long currentTime = millis();
    if (currentTime - lastSampleTime >= sampleInterval) {
        retourCapteur = lectureCapteur();
        adapteVitesseMoteur(retourCapteur);
        lastSampleTime = currentTime;
    }
    motorLeft.runSpeed();
    motorRight.runSpeed();
}

sampleInterval est à déterminer empiriquement pour que le mobile soit réactif mais sans surcharger inutilement le processeur.
Si tu veux conserver des impressions pour voir si ton algorithme se comporte comme attendu, il faut passer le baudrate à 115200 pour réduire au maximum le temps passé dans la transmission.

Merci pour vos réponses à tous les deux !
J'ai essayé de modifier mon code en tenant compte de vos remarques.
J'ai aussi essayé de mettre certaines logiques (par exemple la correction de vitesse) dans des fonctions à part, que j'appelle dans la boucle. J'imagine que ça ne peut qu'aider...
J'ai essayé ce code dans le robot, les moteurs tournent maintenant de façon continue plus rapidement. Le problème a l'air d'être résolu.
Voyez-vous encore des anomalies ?
J'ai encore un soucis au niveau de la correction de trajectoire, la vitesse d'un des moteurs ne semble pas être plus importante lorsqu'il doit corriger sa trajectoire pour se recentrer. La logique de correction me paraît bonne, si vous voyez une approche différente, je serai preneur.
Merci !
Voici ce que ça donne au niveau du code maintenant :


#include <QTRSensors.h>
#include <AccelStepper.h>
 
// Configuration du capteur QTR-8A
QTRSensors qtr;
const uint8_t SensorCount = 8;
uint16_t sensorValues[SensorCount];
 
// Moteurs
#define MOTOR_LEFT_PIN1 2
#define MOTOR_LEFT_PIN2 4
#define MOTOR_LEFT_PIN3 3
#define MOTOR_LEFT_PIN4 5
 
#define MOTOR_RIGHT_PIN1 6
#define MOTOR_RIGHT_PIN2 8
#define MOTOR_RIGHT_PIN3 7
#define MOTOR_RIGHT_PIN4 9
 
AccelStepper motorLeft(AccelStepper::FULL4WIRE, MOTOR_LEFT_PIN1, MOTOR_LEFT_PIN2, MOTOR_LEFT_PIN3, MOTOR_LEFT_PIN4);
AccelStepper motorRight(AccelStepper::FULL4WIRE, MOTOR_RIGHT_PIN1, MOTOR_RIGHT_PIN2, MOTOR_RIGHT_PIN3, MOTOR_RIGHT_PIN4);
 
// Paramètres du suivi de ligne
int baseSpeed = 500;
int maxCorrection = 6000;
int leftSpeed = 0, rightSpeed = 0;
 
// Gestion du timing
unsigned long lastSampleTime = 0;
unsigned long sampleInterval = 20; // À ajuster pour un bon compromis réactivité/charge
 
void setup() {
    Serial.begin(115200);
 
    qtr.setTypeAnalog();
    qtr.setSensorPins((const uint8_t[]){A0, A1, A2, A3, A4, A5, A6, A7}, SensorCount);
 
    for (uint16_t i = 0; i < 400; i++) {
        qtr.calibrate();
        delay(5); 
    }
 
    motorLeft.setMaxSpeed(4000);
    motorRight.setMaxSpeed(4000);
}
 
void adapteVitesseMoteur(long position) {
    int error = position - 3500;
    int correctionFactor = abs(error) * maxCorrection / 14000;
    correctionFactor = constrain(correctionFactor, 0, baseSpeed * 5);
 
    if (error < 0) {
        leftSpeed = baseSpeed;
        rightSpeed = baseSpeed + correctionFactor;
    } else if (error > 0) {
        leftSpeed = baseSpeed + correctionFactor;
        rightSpeed = baseSpeed;
    } else {
        leftSpeed = baseSpeed;
        rightSpeed = baseSpeed;
    }
 //Appel de setSpeed() uniquement quand la vitesse a changé
    if (motorLeft.speed() != -leftSpeed) {
        motorLeft.setSpeed(-leftSpeed);
    }
    if (motorRight.speed() != rightSpeed) {
        motorRight.setSpeed(rightSpeed);
    }
}
 
long lectureCapteur() {
    qtr.read(sensorValues);
    return qtr.readLineBlack(sensorValues);
}
 
void loop() {
    unsigned long currentTime = millis();
    if (currentTime - lastSampleTime >= sampleInterval) {
        lastSampleTime = currentTime;
 
        long position = lectureCapteur();
        adapteVitesseMoteur(position);
 
        Serial.print("Position: ");
        Serial.println(position);
        
        Serial.print("Capteurs : ");
        for (uint8_t i = 0; i < SensorCount; i++) {
        Serial.print(sensorValues[i]);
        Serial.print('\t'); // Tabulation pour séparer les valeurs
        }

        Serial.println(); // Nouvelle ligne

        Serial.print("Left Speed: ");
        Serial.println(leftSpeed);
        Serial.print("Right Speed: ");
        Serial.println(rightSpeed);
    }
 
    // Appel des moteurs en permanence pour éviter de rater des pas
    motorLeft.runSpeed();
    motorRight.runSpeed();
}

Est-ce que c'est grave si je mets

motorLeft.runSpeed();
motorRight.runSpeed();

dans la boucle, alors qu'ils appellent

motorLeft.setSpeed(-leftSpeed);
motorRight.setSpeed(righttSpeed);

qui sont dans la fonction
void adapteVitesseMoteur(long position)
fonction qui est définie en dehors de la boucle, même si j'appelle cette fonction dans la boucle ?

Non cela n'a aucune conséquence.

Quand vous appelez runSpeed() la bibliothèque regarde s'il faut faire un pas. La décision est prise en fonction de la vitesse (le nombre de pas par secondes) et du moment où le dernier pas a été effectué.

Si vous avez appelé setSpeed() entre temps, ça change simplement le résultat du calcul pour voir si le temps avant de faire un nouveau pas est écoulé.

il y a encore beaucoup trop de print (tout un tas de données toutes les 20ms...)

Merci pour votre réponse !

J'ai encore du mal à comprendre comment appeler les paramètres motorLeft.setSpeed() et motorLeft.runSpeed. Je ne comprends pas bien la subtilité de l'appel de ces paramètres.

Lorsque j'appelle ma fonction "suiviLigne" dans mon loop, les moteurs tournent normalement.
Fonction "suiviLigne" :

void suiviLigne(long position) {
    int error = position - 3500;
    //int correctionFactor = abs(error) * maxCorrection / 14000;
    //correctionFactor = constrain(correctionFactor, 0, baseSpeed * 5);

    if (error < 0) {
        leftSpeed = 300;
        rightSpeed = baseSpeed;
    } 
    else if (error > 0) {
        leftSpeed = baseSpeed;
        rightSpeed = 300;
    } 
    else {
        leftSpeed = baseSpeed;
        rightSpeed = baseSpeed;
    }

    motorLeft.setSpeed(-leftSpeed);
    motorRight.setSpeed(rightSpeed);
}

J'appelle la fonction suiviLigne donc dans le loop, dans le else à la fin de la boucle :

void loop() {

  while (enTrainDeTournerDroite == true || enTrainDeTournerGauche == true || enTrainDeTournerDemiTour == true || enTrainDeToutDroit == true) {
    long position = lectureCapteur();
    if (enTrainDeTournerDroite == true && position < 3500 && millis() - derniereTransitionDroite >= delaiDroite) {
      Serial.println("est rentré dans if (enTrainDeTournerDroite == true && position < 3500 && millis() - derniereTransitionDroite >= delaiDroite)");
      croisementOk = false;
      enTrainDeTournerDroite = false;
    }

    if (enTrainDeTournerGauche == true && position > 3500 && millis() - derniereTransitionGauche >= delaiGauche) {
      croisementOk = false;
      enTrainDeTournerGauche = false;
    }

    if (enTrainDeTournerDemiTour == true && position > 4000 && millis() - derniereTransitionDemiTour >= delaiDemiTour) {
      croisementOk = false;
      enTrainDeTournerDemiTour = false;
    }

    if (enTrainDeToutDroit == true && millis() - derniereTransitionToutDroit >= delaiToutDroit) {
      croisementOk = false;
      enTrainDeToutDroit = false;
    }
  }


    if ((millis() - derniereTransition) > delai) {

        if(distanceGauche>50 && distanceFace>200 && distanceDroite>50){
            fin = true; 
        }
            
        int sensorValueR = digitalRead(sensorR);
        int sensorValueL = digitalRead(sensorL);

        if (sensorValueR == HIGH && sensorValueL == HIGH) {
            croisementOk = true;
            Serial.println("est rentré dans if (sensorValueR == HIGH && sensorValueL == HIGH)");
        } 

        if (croisementOk == true || fin == true) {  //quand le capteur de suivi de ligne detecte un croisement
          scan_alentours();          //appel de la sous routine pour scan des obstacles alentours
          ChooseDirrection();
                      Serial.println("est rentré dans if (croisementOk == true || fin == true)");

        } 
            
        else {
            // Sinon, suivi de ligne normal
            long position = lectureCapteur();
            suiviLigne(position);
        }
    }


    motorLeft.runSpeed();
    motorRight.runSpeed();
}

Puis à la fin de la boucle j'ajoute les paramètres motor.runSpeed().

Par contre, si j'écris ma fonction comme ceci :

void tout_droit() {
    
    motorLeft.setSpeed(-baseSpeed);
    motorRight.setSpeed(baseSpeed);

    motorLeft.runSpeed();
    motorRight.runSpeed();

}

et que j'appelle uniquement cette fonction dans le loop, ça ne fonctionne pas. Je ne comprends pas bien la subtilité d'appel des paramètres, à quelle fréquence il faut les appeler.
Que je mette les paramètres motor.runSpeed() dans la fonction directement puis que j'appelle cette fonction dans le loop, ou que je mette ces paramètres à la fin du loop et pas dans la fonction, c'est pareil ?

Merci d'avance pour votre aide !

comme dit plus avant

➜ il ne faut appeler setSpeed() que quand vous changez la vitesse et il faut appeler runSpeed() le plus souvent possible (la bibliothèque regarde s'il faut faire un pas et ne fait rien si ce n'est pas le moment, donc ça ne gêne pas de l'appeler plus souvent que nécessaire - c'est même mieux pour ne pas rater le moment de faire un pas).

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