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.
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.
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.
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.).
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.
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);
}
}