ARDUINO 2WD CAR WIFI ET LABVIEW

Bonjour a tous,

Voila longtemps que j’espérai prendre le temps pour mon petit projet Arduino 2WD car.

Je suis développeur LABVIEW depuis de nombreuses années mais je suis une bille en C, il est temps de remédier un peu à ça.

Mon projet c'est une 2Wd car , avec asservissement surun arduino UNO.
Un arduino MEGA qui traîne dans le placard ainsi qu'un shield WIFI R3 de deuxième génération le seconderont pour gérer le wifi et les capteurs

Un Algorithme A* sur PC , sous labview me renvois des coordonnées de déplacement prévu du véhicule dans l'appartement.

J'ai déjà implémenté, la connexion WIFI entre le PC et le MEGA .
Le MEGA me retourne des infos /lumière/température/capteur Ultra son....
Le MEGA transférera ces coordonnées au UNO qui ne gérera que ça(asservissement distance/angle)

Bien entendu, il faut bien commencer quelques part et je vais essayé de le faire dans l'ordre.

J'ai assemblé le robot et effectivement, pour le prix , on a se qu'on a...

J'en suis au asservissement PID, j'en ai déja réalisé sous LABVIEW mais a coder ça a été une autre histoire.
J'ai donc commencé simplement par un asservissement PID ligne droite.

J'arrive à quelque chose de "potable" ou a 1.7 tours/seconde, le robot se déplace a peu prêt droit.
Mais je reste confronté à une serieuse interrogation.

Mes roues codeuses dispose de 20 fentes et je me retrouvais à compter constamment 80 ticks pour 1 tour de roue, j'ai résolu un peu de ce problème en rajoutant un tampon sur l'interruption mais il m'en reste toujours 40, que j'utilise les fonction FALLING/RISING/HIGH ou LOW, rien n'y fait , 40 ticks par tour de roue.....

Avez vous une idée?

1er essai d'insertion de code.....dsl

#include <SimpleTimer.h>

/* VARIABLES MOTEUR & ROUES CODEUSES-----------------------------------------------------------------------*/

//Timer t;
SimpleTimer PIDTimer;                                   //variable Timer pour le controle des PID

const int FrqEchantillonnage = 30;                      // Fréquence du pid 50
float ConsigneNbTourSecondeG = 1.7;                     //  Nombre de tours de roue par seconde gauche
float ConsigneNbTourSecondeD = 1.7;                     //  Nombre de tours de roue par seconde droite
float Erreur_precedenteG = ConsigneNbTourSecondeG;
float Erreur_precedenteD = ConsigneNbTourSecondeD;
float somme_erreur_MoteurG = 0;                         // Somme des erreurs pour l'intégrateur gauche
float somme_erreur_MoteurD = 0;                         // Somme des erreurs pour l'intégrateur droite

unsigned long tickG = 0;                                // Compteur de tick de la codeuse gauche
unsigned long tickD = 0;                                // Compteur de tick de la codeuse droite
const int RapportReducteur = 1;                         // Rapport entre le nombre de tours de l'arbre moteur et de la roue 29
const int Tick_par_tour_codeuse = 40;                   // Nombre de tick codeuse par tour de l'arbre moteur
int CommandMoteurG = 0;                                 // valeur de la commande du moteur gauche
int CommandMoteurD = 0;                                 // valeur de la commande du moteur droit

long Temps_Antirebond = 3;                              // temps anti rebond d'interruption
volatile unsigned long last_microG;                     // derniere valeur du timer ou a eu lieu une interuptionG
volatile unsigned long last_microD;                     // derniere valeur du timer ou a eu lieu une interuptionD

/* VARIABLES PID MOTEURS------------------------------------------------------------------------------------*/

float kp = 44;                                         // Coefficient proportionnel 50 44
float ki = 10;                                         // Coefficient integrale 10 2
float kd = 20;                                         // Coefficient dérivé 20 8


/* PIN ---const = ROM---------------------------------------------------------------------------------------*/

const int EnA = 11;                     // analogique PWM pilotage moteur Droit
const int EnB = 5;                      // analogique PWM pilotage moteur Gauche
const int In1 = 9;                      // moteur Droit LOW=avancer/HIGH=reculer
const int In2 = 8;                      // moteur Droit HIGH=avancer/LOW= reculer
const int In3 = 7;                      // moteur Gauche LOW=avancer/HIGH=reculer
const int In4 = 6;                      // moteur Gauche HIGH=avancer/LOW= reculer

/* SETUP  *****************************************************************************************************/

void setup() {
  pinMode(EnA, OUTPUT);                 //toutes les sorties Moteur en OUTPUT
  pinMode(EnB, OUTPUT);
  pinMode(In1, OUTPUT);
  pinMode(In2, OUTPUT);
  pinMode(In3, OUTPUT);
  pinMode(In4, OUTPUT);
  
  analogWrite(EnB, 125);                
  analogWrite(EnA, 125);                

  digitalWrite(In1, LOW);                 //marche Avant
  digitalWrite(In2, HIGH);

  digitalWrite(In3, LOW);
  digitalWrite(In4, HIGH);
  
  attachInterrupt(0,AntirebondG , CHANGE);       // Interruption sur tick de la codeuse PIN 2 moteur droit (9.6)
  attachInterrupt(1,AntirebondD , CHANGE);       // Interruption sur tick de la codeuse PIN 3 moteur gauche (11.10)
  
  PIDTimer.setInterval(1000/FrqEchantillonnage, asservissement);  // Interruption(20ms) pour calcul du PID et asservissement par dans asservissement à cette fonction
    
  Serial.begin(115200);                           
  
}  
/* LOOP ************************************************************************************************************/
void loop() {
  PIDTimer.run();
}  
/*ANTI REBOND AVANT INTERRUPTION ***********************************************************************************/

void AntirebondG(){
  if ((long)(micros() - last_microG) >= Temps_Antirebond * 1000){
  CompteurtickG();
  last_microG = micros();  
  }
}

void AntirebondD(){
  if ((long)(micros() - last_microD) >= Temps_Antirebond * 1000){
  CompteurtickD();
  last_microD = micros();  
  }
}

/*COMPTEUR ROUES TICKD ET TICKG ***********************************************************************************/

void CompteurtickG() {         // Interruption sur tick de la codeuse gauche
  tickG++;                    // On incrémente le nombre de tick de la codeuse gauche
}
void CompteurtickD() {         // Interruption sur tick de la codeuse droite
  tickD++;                    // On incrémente le nombre de tick de la codeuse droite
}

/* CALCUL DES ASSERVISSEMENTS ROUES G & D ************************************************************************/

void asservissement()
{
   
  
  // Calcul de l'erreur
  int FrqCodeuseG = FrqEchantillonnage*tickG;
  int FrqCodeuseD = FrqEchantillonnage*tickD;
  
  float TourSecondeG = (float)FrqCodeuseG/(float)Tick_par_tour_codeuse/(float)RapportReducteur;
  float TourSecondeD = (float)FrqCodeuseD/(float)Tick_par_tour_codeuse/(float)RapportReducteur;
    
  float ErreurG = ConsigneNbTourSecondeG - TourSecondeG;
  float ErreurD = ConsigneNbTourSecondeD - TourSecondeD;
  
  somme_erreur_MoteurG += ErreurG;
  somme_erreur_MoteurD += ErreurD;

  float delta_erreur_MoteurG = ErreurG-Erreur_precedenteG;
  Erreur_precedenteG = ErreurG;
  float delta_erreur_MoteurD = ErreurD-Erreur_precedenteD;
  Erreur_precedenteD = ErreurD;
    
  // PI : calcul de la commande
  CommandMoteurG = kp*ErreurG + ki*somme_erreur_MoteurG + kd*delta_erreur_MoteurG;
  CommandMoteurD = kp*ErreurD + ki*somme_erreur_MoteurD + kd*delta_erreur_MoteurD;

  
  // Normalisation et contrôle du moteur gauche
  if(CommandMoteurG < 00) CommandMoteurG=00;
  else if(CommandMoteurG > 255) CommandMoteurG = 255;
  analogWrite(EnB, CommandMoteurG);
  
   // Normalisation et contrôle du moteur droit
  if(CommandMoteurD < 00) CommandMoteurD=0;
  else if(CommandMoteurD > 255) CommandMoteurD = 255;
  analogWrite(EnA, CommandMoteurD);
  
    
  tickG=0;           // Réinitialisation du nombre de ticks de roue codeuse G
  tickD=0;           // Réinitialisation du nombre de ticks de roue codeuse D
  
  
 
  
}

J'ai pas trop l'habitude des interruptions, mais je pense que CHANGE va déjà compter en double.
De plus, une variable modifiée dans une interruption doit être déclarée volatile. Mais comme tu le fais, les variables tickG et tickD sont modifiées dans des fonctions appelées par les fonctions des interruptions. C'est inhabituel, donc je ne sais pas si la règle s'applique ici.

Mais, si tu simplifies ton code et que tu mets les incrémentations directement dans les fonctions appelées par les interruptions, alors il faut déclarer les variables en volatile.

Voir ici. Il est dit notamment

millis() relies on interrupts to count, so it will never increment inside an ISR.

Note qu'avec des nombres de ticks en unsigned long, ton robot va pouvoir aller vraiment très très loin... :slight_smile: 10000 km...

Tu devrais te limiter à

volatile int tickG = 0;

Et simplifier ta fonction

void AntirebondG(){
  tickG++;
}

Il est aussi conseillé d'utiliser digitalPinToInterrupt:

  attachInterrupt(digitalPinToInterrupt(2), AntirebondG, RISING);
  attachInterrupt(digitalPinToInterrupt(3), AntirebondD, RISING);

Merci beaucoup pour vos réponses....mais rien n'y fait. :confused:

lesept, ma fonction anti-rebond par laquelle passe l’interruption me permet d'enlever une bonne partie des fronts "Fantôme" sans ça, ce n'est pas 40 mais 80 fronts qui sont détectés au lieu de 20...

j'ai isolé la fonction

volatile int tick_codeuse = 0;     // Compteur de tick de la codeuse
long Temps_Antirebond = 16;                              // temps anti rebond d'interruption
volatile unsigned long last_microG;                     // derniere valeur du timer ou a eu lieu une interuptionG

/* Routine d'initialisation */
void setup() {
    Serial.begin(115200);
  
   attachInterrupt(digitalPinToInterrupt(2), AntirebondG,RISING);    // Interruption sur tick de la codeuse (interruption 0 = pin2 arduino mega)
}
 
/* Fonction principale */
void loop(){
    delay(200);
    Serial.println(tick_codeuse);
}
/*ANTI REBOND AVANT INTERRUPTION ***********************************************************************************/

void AntirebondG(){
  if ((long)(micros() - last_microG) >= Temps_Antirebond * 1000){
  compteur();
  last_microG = micros();  
  }
} 
/* Interruption sur tick de la codeuse */
void compteur(){
    tick_codeuse++;  // On incrémente le nombre de tick de la codeuse
    
}

j'utilise ce type de moteur avec roue codeuse.

En cherchant un peu sur internet, on voit que le problème est connu mais on trouve peu de solutions. C'est en effet un problème de "rebond", schématisé ici

Coté solution, les gens suggèrent d'ajouter un filtre passe bas RC, ou un trigger de Schmitt en aval du capteur.

On trouve des solutions logicielles, que tu pourrais tester. Google "arduino interrupt wheel encoder too many"

... Bonne chance !...

Merci pour ta réponse, j'ai essayé quelques une des solutions soft proposées mais rien n'y fait. Je penche pour un problème de paramétrage , je vais essayé d’être clair dans mon explication.

Lorsque l'une des fentes de ma roues codeuse passe devant le capteur, celui-ci allume une de ses diodes.
Hors, si je fais tourner la roue entre une fente et un espace masqué j'ai une interruption généré(FALLING) alors que je suis configuré sur RISING.

J'ai pris cet exemple mais c'est le cas pour tout type de mode que je choisisse.
J'ai juste le double ,40 au lieu de 20, propre, toujours la même erreur.

Ca ne ressemble plus à du rebond, avec lequel j'avais plutot des nombres immenses.

Quel est ton code à présent ? Comment tout cela est il connecté ?

le code n'a pas trop changé a par les PullUp activées en + sur les entrées 2 et 3 des interruptions.

les capteurs IR des roues codeuses ont 3 broches +5/GND/OUT tout le monde est à sa place, OUT sur INT0 (pin2)

Je viens de faire un essai sur un autre UNO seul , sans rien de connecté,idem

Oui mais mettre micros () ou millis () dans une interruption, ça fout le b*** dans les timers. Donc si ton code n'a pas changé, il y a peu de chance que ça tombe en marche tout seul...

j'ai peur qu'on se soit mal compris, sur ma version de départ je n'avais pas l'anti rebond et j'avais entre 80 et 100 ticks par tour en mode FALLING au lieu de 20.

voici cette version améliorée des pull up logiciel qui n'y change rien

volatile int tick_codeuse = 0;                            // Compteur de tick de la codeuse
int Temps_Antirebond = 12;                               // temps anti rebond d'interruption
volatile unsigned long last_microG;                      // derniere valeur du timer ou a eu lieu une interuptionG

/* Routine d'initialisation */
void setup() {
  pinMode (2, INPUT_PULLUP);
  Serial.begin(115200);
  
   attachInterrupt(digitalPinToInterrupt(2), compteur,FALLING);    // Interruption sur tick de la codeuse (interruption 0 = pin2 arduino mega)
}
 
/* Fonction principale */
void loop(){
    delay(200);
    Serial.println(tick_codeuse);
}

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

Cela dis, j'avance quand même en prenant parti des 40 ticks par tour de roue.
le PID se comporte plutôt bien sur 4m.

J'ai un petit soucis au départ du a la roue folle qui , si elle est placé sur sa gauche ou sa droite impose une force de traction plus importante d'un coté....dommage, il vaut mieux une bille dans ce cas précis.

J'aimerai ensuite me lancer dans un asservissement en angle et distance. En attendant de comprendre se qu'il se passe avec mes roues codeuses, je vais ajouter un arduino MEGA pour la gestion de l'ultra son et de la communication WIFI avec le Pc.

Pensez vous que je doive utiliser le module shield wifi en client ou en server?

Essai du soir, j'ai ressorti l'oscilloscope du garage...il fonctionne encore...!!

j'ai visionné la broche d'entre du tick..

-les niveaux Haut sont 3 fois plus cours que les niveaux Bas, ça m’étonne un peu mais bon

j'ai lancé la roue a un PWM de 50 qui fait environ 1,1 tours par seconde.

la période du signal est de environ 60ms(mon oscillo est pas terrible) soit, normalement 18 ticks par tour de roues, en arrondissant mon erreur on doit retrouver 22

Hors avec mon compteur, j'ai 805 ticks pour 20 tours en 22s soit 40.25 ticks par tour.

Les signaux sont très propre , je ne vois par les microsecondes mais l'anti-rebond doit etre suffisant pour faire le taf.

Remarque, ne par mettre un anti-rebond>3ms parceque , comme je l'ai dis, les niveaux son plus courts que les niveaux bas.....et a un PWM de 255 ils font 3.165ms on peu donc cacher des fronts...

Problème résolu.

Tu avais raison Lesept, malgré les bon créneaux l'activation des PullUp internes et l'ajout de la routine anti rebond j'ai finalement ajouter un filtre RC et cela à resolu mon probleme.

-J'en deduis donc que la routine antirebond ne sert a rien (je vais faire le test en la suppriment).
-Que les interruptions peuvent etre luent en quelques micro secondes(il m'a semblé lire 6 quelque part)

J'ai ajouter une resistance de 22K et une capacité de 22nF, ce qui me fait un filtre de 3ms environ, c'est un peu trop mais je n'avait pas d'autres composants sous la main ce soir, 3ms étant le temps minimum de mon créneau haut il va falloir que je j'évite la vitesse max.

j'ai réalisé le cablage comme celui-ci

http://playground.arduino.cc/Main/RotaryEncoders

J'en ai profité pour regarder les tensions efficaces d'alimentation des moteurs, et il semble que pour 6Volts en sortie du L298 je ne doivent pas dépasser des commandes PWM de 150 max et descendre en dessous de 55 pour que les moteurs tournes.

Je vais ajuster mon PID de pour rouler tout droit et voir si l'amélioration.

Je passerai ensuite à l'asservissement en position et distance.

Asservissement_PID_tout_droit.ino (8.37 KB)

OK, bonne continuation !

J'avnce a très petits par sur cette histoire d'orientation et un j'aurai besoin d'un éclaircissement de lanterne sur cette histoire.

J'ai donc réalisé 1 code qui fonctionne pas trop mal, je lui donne une consigne d'orientation et de distance et mon bot réagit bien a ma consigne.
Je n'ai rien inventé et beaucoup pioché, et lu ,et pioché, mais je butte toujours sur 2 points:

j'arrive a régler convenablement (disons a 10cm près) mes PID (orientation et distance) .

Le problème est que lorsque je change de consigne, de distance par exemple, bien sur je me retrouve avec un robot qui par exemple par a fond, ou est tres mou.(ex: de 100cm à 200cm).
Je vous passe en plus le fait que mon sol est tres glissant et qu'au delà de 70PWM ça dérape.

Je doit donc rajouter un asservissement en vitesse de mes roues? mais j'ai déja 2 PID ..

asservissement_angle_distance.ino (6.09 KB)

Bon, je continu mon bonhomme de chemin, un peu tout seul :frowning: .

J'ai donc ré-implémenté un compromis de ce que j'avais sourcé en refaisant les calculs.

J'ai trouvé quelques incohérences(enfin je pense!) notamment sur les directions. j'ai donc rajouté un calcul intermédiaire des positions supposées xT et yT qui représentes le mouvement effectué par le robot au cours du dernier mouvement (a chaque tour le robot est comme replacé a (0,0)).
J'ajoute ensuite ses valeurs relatives aux coordonnées Réelles du robot.

J'ai également misé sur un double PID (distance et angle) , j'ai un peu galéré avec degrés et radian, n'étant pas tres a l'aise avec les radians, je me suis fait avoir plus d'une fois.

Je m'interroge maintenant sur le calibrage des coefficients angulaires.
Sur la page suivante d'ou j'ai pioché pas mal de savoir ici ils font tourner le robot de dix tours, a la main pour obtenir un coefficient par roue de degrés de rotation du robot, et ce, pour chacune des roues.
Hors, il n'est pas précisé si c'est pour une rotation sur lui meme (je suppose que oui car ils font a la fin une commandeG=-commandeD).
Hors je n'aime pas trop cette méthode barbare d'appliquer des coofficents inverses sur les roues pour touner, j'ai l'ipression que le comportement du robot sera moins naturel, et surtout mes moteurs ne me permettent pas grand choses.

D'apres mes essais, en faisant le meme essai qu'eux, j'ai un nombre de degré par tick de 8,2° , alors qu'en laissant une roue fixe j'ai 4.5 (calcul)..qu'en pensez vous?

remarque , mes moteurs decolles à partir d'une consigne PWM de 50-55 et à 70 le robot est déja trop rapide et patine beacoup....j'ai prévu d'investir dans des moteurs dignes de ce non maintenant que j'avance. Peut etre également un IMU, meme si j'aimerai déja finir ma première approche par odométrie.

asservissement_angle_distance_V4.ino (10.4 KB)

Je pense avoir trouvé ma faille, enfin, ..l'une d'elle lol.

En fait, comme je me déplace dans un repère orthonormé mais que tous mes calculs d'angles sont en trigo (-180,+180) je me retrouve embêté lorsque mon robot est par exemple en orientation 74° (disons pointe un peu avant le haut), si je me déplace a gauche encore un peu et bien les xTransitoires de viennent des Ytransitoires (apres les 90°).autrement dis, quand je tourne de plus ou moins 90° , je suis perdu.

C'est dommage , d'autant plus que pour trouver l'orientation de ma cible (C) par rapport a mon point actuel (R) j'utilise la fonction ATAN2() qui est très interressante et surtout va de (-pi, pi)...

comment resoudre ce problème?

Problème résolu, j'avais trop poussé mes vérifs d'angle, Sin () et Cos() fonctionnais encore au dela des 180°.
Quelques jours de calculs et une bonne prise d'expérience en trigo..

J'ai encore du mal a le faire arrivé à la cible, mais il y est parvenu quelques fois (je dirais 2 fois sur 10).
J'en déduis que la précision des captures de ticks est médiocre et que mon calibrage angulaire est encore un peu flou,mes roues tourne en patate, le sol glisse et les moteurs ne m'autorise qu'un PWM de 55 à 65, avec ça difficile de compbiner les deux PID.

j'attend mon IMU et mes deux moteurs avec roues codeuses avec impatience.

j'avance l'interface Labview, du coup et je passe sur la com Serie entre le MEGA et le UNO.
Je voudrais faire quelque chose de solide avec Checsum mais j'avoue je suis pas assez calé.

Ou trouver de la documentation?

Bonjour

voir ici : somme simple modulo 256, CRC....

CRC : on trouve quelques librairies dans l'IDE Arduino, voir leurs exemples,

Merci,

Apres plusieurs heures dessus je ne sais toujours pas bien comment faire fonctionner ça.
Pourtant, je l'ai déja mis en oeuvre sous Labview pour vérifier des données au départ et en provenance d'un micro (au boulot) mais la en C ,je sèche un peu.

En parallèle j'ai reçu mes nouveaux moteurs , j'ai donc refait un chassis pour le bot (en alluminium) , j'ai aussi recu l'IMU 9250, j'arrive a obtenir les données Acc, Gyros, mais il faut encore que je travail le sujet pour convertir ça en roulis et angle de marche (c'est pas gagné).
si quelqu'un connait un site en francais qui explique la fusion des données..

Pour ceux qui pensent que je m'éparpille j'ai calé mon emploi du temps , le soir je bosse la mécanique et les essais moteur (bruit et établi) et la journée je bosse le matin les capteurs et l’après midi la COM. ;D

Si joint un graph recupéré sous labview de la trajectoire avec les anciens moteurs.Je n'arrive pas a faire mieux, départ (0;0), arrivée (100;100)

Et d'ailleurs connaissez vous un bon hébergeur pour les image a poster sur le forum?

Des nouvelles du Rover!?

Aller, le chassis alu va tres bien , j'ai fixé les batteries dessous...mais pas encore relié les deux pour avoir plus de puissance.

Je me retrouve avec un decallage d'avancement des moteur différent de 50% avec le meme PWM , il faut que je résolve ça , ça m'impose un réglage du proportionnel du PID angulaire tres fort, ce qui dénature un peu les déplacements...

J'ai reussi a faire un tableau de coordonnées pour que le robot aille de points en point et cela fonctionne plutot bien.mais étant donné mes problemes de moteur j'ai encore du mal a etre tres précis. environ 20cm au bout de 4m de trajets (avec 4 virages)

je me demande si mon shield moteur n'est pas défaillant ou la puissance fourni insuffisante à gauche.

Je continu a mettre en place la com mais je rame un peu (Pc--->wifi--->MEGA--->Serie--->UNO)

J'essai aussi de trouver a étalonner l'IMU9255 et m'en servi com référence pour mes déplacements, j 'ai du mal a comprendre comment faire.....je vais fouiller

V2 ALuminium 2WD UNO PID Polar