• 15 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 08/11/2019

Implémentez une fonction cartographie

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

Dans ce chapitre, nous allons découvrir comment le robot, en mode de pilotage autonome ou manuel, cartographie son environnement. Nous découvrirons les stratégies de déplacement qui lui permettront, en mode automatique, de :

  • couvrir un plan aux dimensions bien définies ;

  • mesurer et cartographier en mémoire les obstacles qui se présentent jusqu’à la connaissance complète de son environnement, de manière à ce qu’il puisse ensuite se mouvoir dans cette cartographie qu’il aura mémorisée.

Au terme de ce chapitre, nous aurons introduit au pilotage “haut niveau” et nous nous serons initiés au comportement autonome et intelligent du petit véhicule. Nous aurons ainsi ouvert à un domaine en plein essor aussi fascinant que prometteur, les véhicules autonomes.

Un robot autonome rudimentaire

Le principe de la cartographie par odométrie

Dans la vidéo qui suit, un comportement autonome mais rudimentaire est implémenté dans le robot. L’engin avance tout droit. Quand il détecte un obstacle, il regarde à gauche puis à droite. Dès qu’une direction est libre, il pivote de 90° et poursuit son avance. Simultanément, il dessine sa trajectoire pour mémoriser les trajets parcourus.

Le code du robot autonome rudimentaire

Cette fois encore, nous utilisons la technique du sketch comme dans de nombreux exemples auparavant. Le code est constitué de 5 onglets. Nous les donnons tous, quand bien même certains avaient déjà été donnés dans des parties antérieures. De façon générale, vous pouvez les copier-coller dans les 5 onglets d'un même sketch que vous créez par Arduino/Fichier/Nouveau. Nous recommandons des noms pour chacun de ces onglets, mais vous pouvez bien sûr les modifier comme bon vous semble.

Nous commençons évidemment par le programme principal (qu'on peut nommer "autonome") qui contient la boucle infinie, void loop :

/* Ce programme intègre en plus des codes présentées dans les chapitres précédents, un mode autonome rudimentaire
/*********************************************************************/
// bibliothèque de fonctions pour la gestion des servomoteurs
#include <Servo.h>
// bibliotheque de la liaison série software
#include <SoftwareSerial.h>
#define RxD 4 // Pin 4 pour la reception dans l'arduino
#define TxD 5 //Pin 5 pour l'émission de l'arduino
SoftwareSerial BTSerie(RxD,TxD); // affectation des broches de communication de la liaison bluetooth
// mesures sur les broches 2 et 3 pour les capteurs d'odométrie
const int capteurG = 10; // capteur odométrique gauche
const int capteurD = 3; // capteur odométrique droit
int distanceD = 0; // distance parcourue par la roue droite
int distanceG = 0; // distance parcourue par la roue gauche
byte etatD; // état du capteur de roue droit
byte etatG; // état du capteur de roue gauche
// pour le calcul des distances nous devons connaitre le mouvement du robot
char action=0;
char commande = 0; // variable de mouvement
// position du robot par la méthode temporelle
float x = 20;
float y = 15;
float a = 0; // position X, Y, angle
float delta; // increment de déplacement
float deltaAngle; // increment de rotation
float e = 5.5; // entraxe des roues 2e=11 cm
// position du robot par la méthode odométrique
float x0 = 20;
float y0 = 15;
float a0 = 0; // position X, Y, angle
float delta0; // increment de déplacement
float deltaAngle0; // increment de rotation
// mesure du temps pour faire une mesure de distance a interval de temps régulier
unsigned long debut=0;
unsigned long fin=0;
int duree=0;
// pour les roues
Servo roueG, roueD; // définit le nom des deux servos
Servo servoT; // nom de la tourelle
int posG, posD; // position des servos
const int pinG = 6; // broche de la roue gauche
const int pinD = 7; // broche de la roue droite
const int vmax = 10; // vitesse maximale des servos
const int repos = 90; // position de repos des servos
// pour la tourelle
const int pinT = 8; // broche de la tourelle
const int droit = 00; // position de la tourelle
const int gauche = 170;
const int face = 90;
const int telemetre = 0; // broche analogique du capteur de distance
int distanceFace;
int distanceGauche;
int distanceDroit;
// pour le debug
const byte led = 13; // declaration de la led interne
volatile byte state = LOW; // etat de la led
/*********************************************************************/
// sous programme d'initialisation et il n'est exécuté qu'une fois
void setup() {
roueG.attach(pinG); // déclaration des broches des roues
roueD.attach(pinD);
servoT.attach(pinT); // déclaration de la broche de la tourelle
Serial.begin(9600); // initialise la liaison série à 9600 bauds;
while (!Serial); // attends l'ouverture du la fenetre terminal
// initialisation de la la liaison Bluetooth
pinMode(RxD, INPUT); // place la broche RX en entrée
pinMode(TxD, OUTPUT); // place la broche TX en sortie
BTSerie.begin(9600); // déclare la vitesse de communication
pinMode(led, OUTPUT);
pinMode(capteurD, INPUT_PULLUP);
pinMode(capteurG, INPUT_PULLUP);
etatD = digitalRead(capteurD);
etatG = digitalRead(capteurG);
//initialisation des deplacements
delta = 0.52; // vitesse du robot
deltaAngle = delta / (2*e);
delta0 = 0.72; // increment odométrie
deltaAngle0 = delta0 / (2*e);
arreter(); // place le robot à l'arret
commande ='M';
}
/*********************************************************************/
// programme principâl
/*********************************************************************/
void loop() {
commande = decodeCommande(); // regarde si une commande arrive de la tablette
if (commande = 'M')
manuel();
if (commande ='A')
automatique();
}
/*********************************************************************/
// mode de fontionnement automatique
/*********************************************************************/
void automatique(){
int obstacle[7]; // tableau des mesures
int limite = 15;
int g,f,d ; // variable pour la présence des obstacles
int mouv;
do {
commande = 'X'; // place le robot à l'arret pour faire les meures
deplacement(commande);
servoT.write(0); // permet d'intialiser la position de depart
delay(500);
for (int i=0; i<7; i++){ // mesure a 180° tous les 30 °
obstacle[i]=mesureDistance(i*30);
Serial.print("*"); // pour le debug on transmet la distance
Serial.print("O");
Serial.print(i);
Serial.print("=>");
Serial.print(obstacle[i]);
Serial.print(" ");
Serial.print("*");
}
servoT.write(90); // replace le servo en position face avant
// recherche des obstacles
d=0; // initialisation des obstacles
f=0;
g=0;
if ((obstacle[0]<limite) or (obstacle[1]<limite)) d=1; // obstacle à droite
if ((obstacle[2]<limite) or (obstacle[3]<limite) or (obstacle[4]<limite)) f=1; // obstacle en face
if ((obstacle[5]<limite) or (obstacle[6]<limite)) g=1; // obstacle à gauche
Serial.print("*"); // pour le debug on transmet la distance
Serial.print("W");
Serial.print(" d");
Serial.print("=>");
Serial.print(d);
Serial.print(" f");
Serial.print("=>");
Serial.print(f);
Serial.print(" g");
Serial.print("=>");
Serial.print(g);
Serial.print("*");
// trajectoire d'évitement
if (f<1)
commande = 'U'; // on avance
else if (d<1)
if (g<1)
if (obstacle[0]<obstacle[6])
commande = 'L';
else
commande = 'R';
else
commande = 'R';
else
if (g<1)
commande = 'L';
else
commande = 'D';
for (int i=0;i<30;i++){
deplacement(commande);
odometrie();
}
commande = decodeCommande(); // regarde si une commande arrive de la tablette
} while (commande != 'M'); // passe en mode manuel
arreter();
}
/*********************************************************************/
// mode de fontionnement manuel
/*********************************************************************/
void manuel(){
do {
commande = decodeCommande(); // ragarde si une commande arrive de la tablette
deplacement (commande);
odometrie();
} while (commande != 'A'); // passe en mode automatique
}
void deplacement(int commande){
if (commande=='U') avancer();
if (commande=='X') arreter();
if (commande=='D') reculer();
if (commande=='R') tournerD();
if (commande=='L') tournerG();
if (commande=='M') resetDistance();
}
void odometrie(){
odometrieRoue(); // odométrie par mesure de distance sur les roues
odometrieTemps();
afficheDistance();
}
/*********************************************************************/
void resetDistance (){
x=20;
y=15;
a=0;
distanceD=0;
distanceG=0;
x0=20;
y0=15;
a0=0;
Serial.print('*');
Serial.print('H');
Serial.print('C');
Serial.print('*');
}
// routine de debug
// la liaison série virtuelle ne permet pas la transmission de beaucoup de données
// sans perturber le reste du montage.
// nous avons changé le branchement entre le module bluetooth et l'arduino.
// nous utilisons la broche 1 (tx de l'arduino) au lieu de 5 (tx soft).
void afficheDistance(){
if (action !='X') {
Serial.print('*');
Serial.print('H');
Serial.print('X');
Serial.print(x);
Serial.print('Y');
Serial.print(y);
Serial.print(',');
Serial.print('X');
Serial.print(x0);
Serial.print('Y');
Serial.print(y0);
Serial.print('*');
}
}
void mesureG(){
if (action=='D')
distanceG = distanceG - 1;
else
distanceG = distanceG + 1;
}

Le deuxième onglet correspond à la gestion de la liaison Bluetooth (qu'on peut nommer "Bluetooth"), déjà rencontrée précédemment :

/* sous programme de gestion de la liaion Bluetooth
*
*/
// programme écho renvoie sur le liaion Bluetooth les signaux de la console et inversement
void echo(){
char recvChar;
//On lit caractere par caractere sur le BTSerie et on affiche sur le Terminal Serie
if (BTSerie.available()) {
recvChar = BTSerie.read();
Serial.write(recvChar);
}
//On lit caractere par caractere sur le terminal Serie et on affiche sur le BTSerie
if (Serial.available()) {
recvChar = Serial.read();
BTSerie.write(recvChar);
}
}
//Décode les touches envoyées par la tablette
char decodeCommande(){
char recvChar=0;
//On lit caractere par caractere sur le BTSerie et on affiche sur le Terminal Serie
if (BTSerie.available()) {
recvChar = BTSerie.read();
}
return recvChar;
}
// dessine la trajectoire
void dessine(){
// if (action != "X") {
BTSerie.print('*');
BTSerie.print('H');
BTSerie.print('X');
BTSerie.print(x);
BTSerie.print('Y');
BTSerie.print(y);
BTSerie.print('*');
// }
}

Le code suivant permet de faire la conversion de distance (on le nomme "ConversionDistance") :

//////////////////////////////////
//déclaration des sous routine de la tourelle
/////////////////////////////////
int mesureDistance(int pos){
int mesure = 0;
servoT.write(pos); // positione le servo
delay(150); // delay de réaction
// réalise une moyenne de 4 mesures
for (int i=0;i<4;i++){
mesure=mesure + analogRead(telemetre);
delay(20); // temposiration pour filtrer les parasites
}
mesure = mesure>>2; // divise la distance par 4 pour faire la moyenne
mesure = conversion(mesure); // change la valeur du CAN en CM
return mesure;
}
/* ce programme récupère la valeur du CAN et la traduit en distance
* grace à la caractéristique de la courbe de la documentation technique
* Attention la distance n'est pas précise car la surface de reflexion
* à une grande influence.
*/
int conversion(int can){
int table[] ={620,530,456,393,350,307,276,250,230,214,200,185,173,162,152,140,130,120,115,110,106,100,97,94,91,89};
int i = 0;
while ((can<table[i]) and (i<28)){
i++;
}
if (i>28) {
return 30;
} else {
return (i+2);
}
}

Nous sommes bien familiers maintenant du code suivant qui définit les mouvements élémentaires du robot et qu'on nommera "mouvements" :

//////////////////////////////////
//déclaration des sous routine de mouvement
/////////////////////////////////
void mouvement(int posD, int posG){
roueG.write(posG); // indique au servo de rejoindre sa position
roueD.write(posD);
}
void arreter(){
mouvement(repos,repos);
action = 'X';
}
void accelerer(){
for (int i=0; i<vmax; i++){ // augmentation prograssive de la vitesse
posG = repos + i;
posD = repos - i;
mouvement(posD,posG);
delay(20); // vitesse de la rampe d'accélération
}
action = 'U';
}
void freiner(){
for (int i=vmax; i>0; i--){ // reduction prograssive de la vitesse
posG = repos + i;
posD = repos - i;
mouvement(posD,posG);
delay(20); // vitesse de la rampe de freinage
}
action = 'U';
}
void avancer(){
posG = repos + vmax;
posD = repos - vmax;
mouvement(posD,posG);
action = 'U';
}
void reculer(){
posG = repos - vmax;
posD = repos + vmax;
mouvement(posD,posG);
action = 'D';
}
void tournerD(){
posG = repos + vmax;
posD = repos;
mouvement(posD,posG);
action = 'R';
}
void tournerG(){
posG = repos;
posD = repos - vmax;
mouvement(posD,posG);
action ='L';
}

et pour finir, le dernier onglet, qu'on nommera "odometrie", réalise les calculs d'odométrie à proprement parler.

Dans le code ci-dessous, nous utilisons la technique des arcs de cercle pour calculer la trajectoire du robot. Il y a 4 mouvements : avancer, reculer, tourner à droite et tourner à gauche.

Le robot sera modélisé par le centre de l’axe des roues : C. Le point D représente la roue Droite et G la roue Gauche. La distance entre les roues est 2e. Ainsi nous repérons le robot dans la pièce par 3 variables : x, y les coordonnées de C  et a l’angle en l’axe des X et la perpendiculaire à DG.
Les points ont donc pour coordonnées :

  • C (x,y) 

  • G (x - e * sin (a), y + e * cos (a))

  • D (x + e * sin (a), y - e * cos (a))

Pour une translation avant d’une distance à partir du point xd, yd, nous pouvons calculer les coordonnées d’arrivée par :

  • xf = l * cos ad + xd

  • yf = l * sin ad + yd

Et l’angle ne change pas :

  • af = ad

Pour un mouvement arrière, les équations sont les même mais l est négatif.

Lorsque le robot tourne à droite. Le point D ne bouge pas et les points G et C décrivent des arcs de cercle de centre D.  Avec l’odomètre, nous mesurons la distance parcourue par la roue mobile :

  • l = 2 * e * ar

  • ar est l’angle de rotation du robot. Le robot tourne dans le sens horaire.

Le point C décrit un arc de cercle de rayon e. Le point D est le centre de la rotation donc il ne bouge pas. Calculons les nouvelles coordonnées de C :

  • ar = l/(2 * e)

  • af = ad - ar

  • xf = x + e * sin (ad) - e * sin (ad - ar)

  • yf = y - e * cos (ad) + e * cos (ad - ar)

Pour une rotation à gauche. Cette fois c’est le point G qui ne bouge pas, et le robot tourne dans le sens trigonométrique :

  • ar = l / (2 * e)

  • af = ad + ar

  • xf = x - e * sin (ad) + e * sin (ad + ar)

  • yf = y + e * cos (ad) - e * cos (ad + ar)

void odometrieTemps(){
fin = millis(); // odométrie pas mesure du temps
duree = fin - debut;
if (duree >50){
debut = fin;
if (action =='U'){
x=x+delta*cos(a);
y=y+delta*sin(a);
}
if (action =='D'){
x=x-delta*cos(a);
y=y-delta*sin(a);
}
if (action =='R'){
x = x + e * sin (a) - e * sin (a - deltaAngle);
y = y - e * cos (a) + e * cos (a - deltaAngle);
a = a - deltaAngle;
}
if (action =='L'){
x = x - e * sin (a) + e * sin (a + deltaAngle);
y = y + e * cos (a) - e * cos (a + deltaAngle);
a = a + deltaAngle;
}
}
}
/* odometrie avec les changements de position des roues du robot
* le capteur D gère les mouvements avancer reculer et tourner à gauche.
* le capteur G gere que la rotation à droite
*/
void odometrieRoue(){
byte tempo;
tempo = digitalRead(capteurD);
if (etatD != tempo) { // s'il y a un changement d'état du capeur
etatD = tempo; // nouvelle position
if (action =='U'){
x0=x0+delta0*cos(a0);
y0=y0+delta0*sin(a0);
}
if (action =='D'){
x0=x0-delta0*cos(a0);
y0=y0-delta0*sin(a0);
}
if (action =='L'){
x0 = x0 - e * sin (a0) + e * sin (a0 + deltaAngle0);
y0 = y0 + e * cos (a0) - e * cos (a0 + deltaAngle0);
a0 = a0 + deltaAngle0;
}
}
tempo = digitalRead(capteurG);
if (etatG != tempo) { // s'il y a un changement d'état du capeur
etatG = tempo; // nouvelle position
if (action =='R'){
x0 = x0 + e * sin (a0) - e * sin (a0 - deltaAngle0);
y0 = y0 - e * cos (a0) + e * cos (a0 - deltaAngle0);
a0 = a0 - deltaAngle0;
}
}
}

Dans la vidéo qui suit, un comportement autonome mais rudimentaire est implémenté dans le robot. L’engin avance tout droit. Quand il détecte un obstacle, il regarde à gauche puis à droite. Dès qu’une direction est libre, il pivote de 90° et poursuit son avance. Simultanément, il dessine sa trajectoire pour mémoriser les trajets parcourus.

En résumé

Passons au dernier chapitre où nous vous proposons d'aller plus loin en réalisant deux activités !

Exemple de certificat de réussite
Exemple de certificat de réussite