Commande de moteur par pid

Bonjour, et bonne fêtes.

Je suis en train d'essayer de commander un moteur comme expliqué sur cette page par pid.

http://www.ferdinandpiette.com/blog/2012/04/asservissement-en-vitesse-dun-moteur-avec-arduino/

J'affiche la vitesse du moteur a la fin du programme avec le plotter intégré.

J'ai renseigné les differentes informations, mais lorsque je me contente d'utiliser la parametre P, je n'obtiens pas le résultat voulu, j'ai testé différente valeur pour KP.

Voici les variables utilisées:

const int frequence_echantillonnage = 50; // Fréquence du pid
const int rapport_reducteur = 200; // Rapport entre le nombre de tours de l'arbre moteur et de la roue
const int tick_par_tour_codeuse = 1; // Nombre de tick codeuse par tour de l'arbre moteur

float consigne_moteur_nombre_tours_par_seconde = 1; // Nombre de tours de roue par seconde
float erreur_precedente = consigne_moteur_nombre_tours_par_seconde;
float somme_erreur = 0; // Somme des erreurs pour l'intégrateur
float kp = 20; // Coefficient proportionnel
float ki = 0; // Coefficient intégrateur
float kd = 0; // Coefficient dérivateur

Mon code complet est ici: (le code de la page indiqué plus haut)

#include <SimpleTimer.h> // http://arduino.cc/playground/Code/SimpleTimer
#define _DEBUG true
SimpleTimer timer; // Timer pour échantillonnage
const int _MOTEUR = 9; // Digital pin pour commande moteur
unsigned int tick_codeuse = 0; // Compteur de tick de la codeuse
int cmd = 0; // Commande du moteur
const int frequence_echantillonnage = 50; // Fréquence du pid
const int rapport_reducteur = 200; // Rapport entre le nombre de tours de l'arbre moteur et de la roue
const int tick_par_tour_codeuse = 1; // Nombre de tick codeuse par tour de l'arbre moteur

float consigne_moteur_nombre_tours_par_seconde = 1; // Nombre de tours de roue par seconde
float erreur_precedente = consigne_moteur_nombre_tours_par_seconde;
float somme_erreur = 0; // Somme des erreurs pour l'intégrateur
float kp = 20; // Coefficient proportionnel
float ki = 0; // Coefficient intégrateur
float kd = 0; // Coefficient dérivateur


/* Routine d'initialisation */
void setup() {
    Serial.begin(115200); // Initialisation port COM
    pinMode(_MOTEUR, OUTPUT); // Sortie moteur
    analogWrite(_MOTEUR, 255); // Sortie moteur à 0
    delay(5000); // Pause de 5 sec pour laisser le temps au moteur de
    //s'arréter si celui-ci est en marche
    attachInterrupt(0, compteur, CHANGE); // Interruption sur tick de la codeuse (interruption 0 = pin2 arduino mega)
    timer.setInterval(1000/frequence_echantillonnage, asservissement); // Interruption pour calcul du PID et asservissement
}


/* Fonction principale */
void loop(){
    timer.run();
    delay(10);
}

/* Interruption sur tick de la codeuse */
void compteur(){
    tick_codeuse++; // On incrémente le nombre de tick de la codeuse
    
}



/* Interruption pour calcul du PID */
void asservissement(){
    // Réinitialisation du nombre de tick de la codeuse
    int tick = tick_codeuse;
    tick_codeuse=0;
    // Calcul des erreurs
    int frequence_codeuse = frequence_echantillonnage*tick;
    float nb_tour_par_sec = (float)frequence_codeuse/(float)tick_par_tour_codeuse/(float)rapport_reducteur;
    float erreur = consigne_moteur_nombre_tours_par_seconde - nb_tour_par_sec;
    somme_erreur += erreur;
    float delta_erreur = erreur-erreur_precedente;
    erreur_precedente = erreur;
    // PID : calcul de la commande
    cmd = kp*erreur + ki*somme_erreur + kd*delta_erreur;
    // Normalisation et contrôle du moteur
    if(cmd < 0) cmd=0;
    else if(cmd > 255) cmd = 255;
    analogWrite(_MOTEUR, 255-cmd);
    // DEBUG
    if(_DEBUG) Serial.println(nb_tour_par_sec,8);
}


Je devrais normalement obtenir une courbe qui oscille légèrement autour de la valeur consigne.

Hors se n'est pas le cas, je souhaiterais valider déjà cette étape pour pouvoir passer au autres paramètre du pid.

Quelqu'un aurais t'il une idée?

tick_codeuse Devrait être déclarée comme volatile et Ceci

Devrait être dans une section critique

que voulez vous dire exactement?

tick_codeuse est modifiée lors d'une interruption.
Il faut masquer les interruptions avant d'exploiter tick_codeuse dans ton code et les rétablir aussitôt après car elle pourrait être modifiée, sans que tu le saches, pendant que tu y accèdes.

J'ai un doute sur le nombre de fronts montants je dois avoir avec ce type de moteur, je n'ai pas trouvé d'infos sur le site du vendeur.

Je pensais au départ qu'il y avait une impulsion par tour mais j'ai un gros doute.

Sa semble être un aimant en forme ronde.(peu être y a t'il plusieurs pôles dans cet aimant)


Désactiver les interruptions avant la copie et remise à zéro puis réactiver les interruptions

ok, je vais essayer, mais je ne pense pas que se soit critique car le code a semble t'il déja été testé.(je suppose)

J'ai essayé de poster sur le site qui propose le code mais apparemment tous les commentaire sont systématiquement supprimées.

La variable est un int donc sur 2 octets.
Si tu lis le premier octet et qu'une interruption tombe avant que tu ais lu le second octet tu auras un résultat erroné.
Par exemple:

  • tick_codeuse vaut 0x00FF
  • Tu lis le premier octet 0xFF
  • L'interruption arrive.
    • Dans la routine d'interruption tick_codeuse est incrémentée et passe donc à 0x0100.
    • Retour dans ta tâche de fond
  • Tu lis le second octet soit 0x01
  • Et tu as tick = 0x01FF

Il y a beaucoup de mauvais code sur internet

Cela dit ce n’est peut être pas le problème, mais c’en est un

Je viens de faire des tests avec le paramètre kp à 1000, voici ce que j’obtiens:

Les autres paramètres a 0 et consigne à 1tour seconde et echantillonage à 100hz:

Sa semble bon? Quant pensez vous?

J'ai contrôlé la sortie du réducteur, j'obtiens 56 tours minute au lieu de 60 attendu.

Un très grand Kp seul agit comme un filtre passe-bas en amplifiant les basses fréquences tout en atténuant les hautes, mais peut rendre le système instable (lorsque le gain amplifie excessivement les variations de l’erreur, provoquant des oscillations continues ou une divergence, particulièrement si le délai de réponse ou l’inertie du système empêche une correction rapide et précise.).

Donc c'est pas bon!
C'est le moment de passer au chapitre suivant de ton lien et d'introduire le terme I pour faire un PI.

Oui c'est bon .........pour un régulateur P
Passer maintenant de P à PI comme indiqué ci-dessus

Le terme I (Intégral) , devrait permettre de réduite fortement l'erreur statique que tu constates

Mon graphe est pas exactement pareil, sur mon graphe je suis juste en dessous de la consigne.(1 tour seconde)

je viens de tester différentes valeurs pour ki, voici ce que sa donne avec ki = 0.1:

je suis très proche de la consigne (1 tour minute d’après le graphe) mais j'ai toujours 56 tours minutes en sortie du réducteur.(étrange)

Peut etre ai je fais une erreur de precison des valeurs en mesurant la reduction du réducteur et peut etre que le nombre de tick_par_tour_codeuse n'est pas exacte.

pour avoir une bonne précision du rapport tours en sortie/ impulsions sur l'encodeur, il faut faire la mesure sur un nombre important de tours (disons 50 ou 100) comme ça, tu réduis l'erreur sur l'estimation d'un tour et surtout tu feras ressortir si ce rapport est fractionnaire.

Je viens de faire des mesures sur des durées plus longues , 15minutes.

J'obtiens de tick codeuse de 14. (passage de l’état haut vers bas et bas vers haut)

Et la réduction de 300.

Avec ces nouvelles valeurs je reste toujours en dessous de la consigne, hors d'après le graphe je devrais être a peine au dessus.

Est ce normal ou est ce un problème qui persiste? Je constate également que le nombre de tours en sortie du réducteur, mesuré sur 1 minute est toujours en dessous de la consigne.

J'ai toujours un doute sur la précision mesuré de mon réducteur qui je pense est de 300x. (pas évident a mesurer)

Je reviens vers vous car je n'ai pas trouvé la solution au problème qui est que en sortie réducteur je n'obtiens pas 60 tours / minutes lorsque je met une consigne de 1 tour seconde.

Quelqu'un aurait t'il une idée?

Vous avez corrigé le point soulevé en #2 ?
quel est votre code ?

Voici le code : (je désactive les interruptions avant d'utiliser la variable )

#include <SimpleTimer.h> // http://arduino.cc/playground/Code/SimpleTimer
#define _DEBUG true
SimpleTimer timer; // Timer pour échantillonnage
const int _MOTEUR = 9; // Digital pin pour commande moteur
volatile unsigned int tick_codeuse = 0; // Compteur de tick de la codeuse
int cmd = 0; // Commande du moteur
const int frequence_echantillonnage = 100; // Fréquence du pid
const int rapport_reducteur = 300; // Rapport entre le nombre de tours de l'arbre moteur et de la roue 300
const int tick_par_tour_codeuse = 14 ; // Nombre de tick codeuse par tour de l'arbre moteur 14

float consigne_moteur_nombre_tours_par_seconde = 1; // Nombre de tours de roue par seconde
float erreur_precedente = consigne_moteur_nombre_tours_par_seconde;
float somme_erreur = 0; // Somme des erreurs pour l'intégrateur
float kp = 600; // Coefficient proportionnel 600
float ki = 0.15; // Coefficient intégrateur 0.25
float kd = 0; // Coefficient dérivateur


/* Routine d'initialisation */
void setup() {
    Serial.begin(115200); // Initialisation port COM
    pinMode(_MOTEUR, OUTPUT); // Sortie moteur
    analogWrite(_MOTEUR, 255); // Sortie moteur à 0
    delay(1000); // Pause de 5 sec pour laisser le temps au moteur de
    //s'arréter si celui-ci est en marche
    attachInterrupt(0, compteur, CHANGE); // Interruption sur tick de la codeuse (interruption 0 = pin2 arduino mega)
    timer.setInterval(1000/frequence_echantillonnage, asservissement); // Interruption pour calcul du PID et asservissement
}


/* Fonction principale */
void loop(){
    timer.run();
    delay(10);
}

/* Interruption sur tick de la codeuse */
void compteur(){
    tick_codeuse++; // On incrémente le nombre de tick de la codeuse
    
}



/* Interruption pour calcul du PID */
void asservissement(){
    // Réinitialisation du nombre de tick de la codeuse
    noInterrupts();
    int tick = tick_codeuse;
    tick_codeuse = 0;
    interrupts();

    // Calcul des erreurs
    int frequence_codeuse = frequence_echantillonnage*tick;
    float nb_tour_par_sec = (float)frequence_codeuse/(float)tick_par_tour_codeuse/(float)rapport_reducteur;
    float erreur = consigne_moteur_nombre_tours_par_seconde - nb_tour_par_sec;
    somme_erreur += erreur;
    float delta_erreur = erreur-erreur_precedente;
    erreur_precedente = erreur;
    // PID : calcul de la commande
    cmd = kp*erreur + ki*somme_erreur + kd*delta_erreur;

    analogWrite(_MOTEUR, 255-cmd);
    // DEBUG
    if(_DEBUG) Serial.println(nb_tour_par_sec,8);
}







le timer que vous utilisez fait du polling. vous n'êtes pas 100 garanti que asservissement est appelé exactement toutes les 10ms, peut être que sur la durée ça joue

essayez sans la bibliothèque SimpleTimer, avec millis() et tenant compte du temps "réel" écoulé

un truc comme cela (tapé ici donc sans garanties)

#define _DEBUG true

const int rapportReducteur = 300; // Rapport entre le nombre de tours de l'arbre moteur et de la roue
const int ticksParTourCodeuse = 14; // Nombre de ticks codeuse par tour de l'arbre moteur
const unsigned long intervalleMesure = 10; // ms entre 2 mesures

const byte brocheMoteur = 9; // Broche numérique pour commande moteur
const byte brocheCodeuse = 2; // Broche numérique pour la codeuse
volatile unsigned int ticksCodeuse = 0; // Compteur de ticks de la codeuse

float consigneToursParSeconde = 1.0f; // Nombre de tours de roue par seconde
float erreurPrecedente = consigneToursParSeconde;
float sommeErreur = 0.0f; // Somme des erreurs pour l'intégrateur
float coefficientProportionnel = 600.0f; // Coefficient proportionnel
float coefficientIntegrateur = 0.15f; // Coefficient intégrateur
float coefficientDerivateur = 0.0f; // Coefficient dérivateur

unsigned long tempsPrecedent = 0;

/* Interruption sur tick de la codeuse */
void compteur() {
  ticksCodeuse++; // On incrémente le nombre de ticks de la codeuse
}

/* Routine d'initialisation */
void setup() {
  pinMode(brocheMoteur, OUTPUT); // Sortie moteur
  digitalWrite(brocheMoteur, HIGH); // Sortie moteur à 0
  Serial.begin(115200); // Initialisation port COM
  delay(1000); // Pause pour laisser le moteur s'arrêter
  attachInterrupt(digitalPinToInterrupt(brocheCodeuse), compteur, CHANGE); // Interruption sur tick de la codeuse
}

/* Fonction principale */
void loop() {
  unsigned long tempsActuel = millis();
  if (tempsActuel - tempsPrecedent >= intervalleMesure) {
    // section critique
    noInterrupts();
    unsigned int ticks = ticksCodeuse;
    ticksCodeuse = 0;
    interrupts();

    unsigned long tempsEcoule = tempsActuel - tempsPrecedent;
    tempsPrecedent = tempsActuel;

    // Calcul des erreurs en utilisant le temps écoulé
    float frequenceCodeuse = (ticks * 1000.0f) / tempsEcoule;
    float toursParSeconde = frequenceCodeuse / ticksParTourCodeuse / rapportReducteur;
    float erreur = consigneToursParSeconde - toursParSeconde;
    sommeErreur += erreur;
    float deltaErreur = erreur - erreurPrecedente;
    erreurPrecedente = erreur;

    // PID : calcul de la commande
    int commande = static_cast<int>(coefficientProportionnel * erreur + coefficientIntegrateur * sommeErreur + coefficientDerivateur * deltaErreur);
    analogWrite(brocheMoteur, 255 - commande);

    if (_DEBUG) Serial.println(toursParSeconde, 8);
  }
}