Robots jardiniers : Différence entre versions

De Learning Lab Environnements Connectés
Sauter à la navigation Sauter à la recherche
(Robot 1)
(Mode de communication retenu : WIFI)
Ligne 523 : Ligne 523 :
 
=== Mode de communication retenu : WIFI ===
 
=== Mode de communication retenu : WIFI ===
  
Nous nous aidons de la bibliothèque ESP-NOW. Pour avoir tout les méthodes et fonction de la bibliothèques : https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_now.html#_CPPv416esp_now_add_peerPK19esp_now_peer_info_t. Pour avoir un exemple dont nous sommes inspirés : https://dronebotworkshop.com/esp-now/.
+
Nous nous aidons de la bibliothèque ESP-NOW. Pour avoir toutes les méthodes et fonction de la bibliothèque : https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_now.html#_CPPv416esp_now_add_peerPK19esp_now_peer_info_t. Pour avoir un exemple dont nous sommes inspirés : https://dronebotworkshop.com/esp-now/.
  
On souhaite établir une communication entre les robots, via ESP32. On utilise alors ESP-NOW. Il s'agit d'un protocole de communication unique aux ESP32. Le protocole utilise la bande de fréquence 2.4 GHz. Dans le cadre de ce projet, la topologie utilisée est appelée « One Initiator & Multiple Responders ». Le ESP32 du robot 1 joue le rôle dinitiateur (initiator) et les ESP32 des robots 2 et 3 jouent le rôle de récepteur (responder).
+
On souhaite établir une communication entre les robots, via *ESP32*. On utilise alors ESP-NOW. Il s'agit d'un protocole de communication unique aux ESP32. Le protocole utilise la bande de fréquence 2.4 GHz. Dans le cadre de ce projet, la topologie utilisée est appelée broadcast, c'est-à-dire que toutes les cartes *ESP32* recevront le message envoyé. Le *ESP32* du robot 1 joue le rôle d'initiateur (initiator) et les *ESP32* des robots 2 et 3 jouent le rôle de récepteur (responder).
  
 
<br>
 
<br>
Ligne 533 : Ligne 533 :
  
 
<br>
 
<br>
Cette méthode nécessite de connaître les adresses MAC des ESP32 qui auront le rôle de responder, celle-ci peuvent être obtenue facilement avec ce code :
+
Cette méthode **ne nécessite pas de connaître les adresses MAC des *ESP32* ** qui auront le rôle de responder. Cependant, nous avons d'abord essayé de communiquer en point à point avec les adresses MAC. Celle-ci peuvent être obtenue facilement avec ce code :
Il faut bien noter les adresses obtenues pour éviter de le refaire plusieurs fois.
+
 
 +
*Il faut bien noter les adresses obtenues pour éviter de le refaire plusieurs fois.*
 +
 
 +
<br>
 
<pre>
 
<pre>
 
/*
 
  ESP32 MAC Address printout
 
  esp32-mac-address.ino
 
  Prints MAC Address to Serial Monitor
 
 
  DroneBot Workshop 2022
 
  https://dronebotworkshop.com
 
*/
 
 
   
 
   
 
// Include WiFi Library
 
// Include WiFi Library
Ligne 566 : Ligne 560 :
 
}
 
}
  
#Initiator
 
 
</pre>
 
</pre>
 +
<br>
 +
 +
Comme notre carte principale est la *Arduino Due*, on connecte sur les ports serial 3 deux fils vers serial 2 (en inverser) de la *ESP32*. C'est la Due qui enverra les messages à envoyer.
 +
 +
Voici le code utilisé pour envoyer les données en mode broadcast :
 +
 +
<br>
  
Une fois cela fait voici les codes "initiator" et "responder" permettant respectivement d'envoyer et de recevoir une simple chaine de caractère avec ESP NOW:  
+
Pour la *Due* :
 +
 
 +
<br>
  
 
<pre>
 
<pre>
 +
String mvt="avavavdrdrgaalalav";
 +
void setup() {
 +
 
 +
  Serial.begin(115200);
 +
  Serial3.begin(115200);
 +
 
 +
}
 +
 +
void loop() {
 +
Serial3.print(mvt);
 +
delay(1000);
 +
}
 +
 
</pre>
 
</pre>
  
#Responder
+
<br>
 +
 
 +
Pour la *ESP32* en mode Broadcast :
 +
 
 +
<br>
 +
 
 
<pre>
 
<pre>
/*
+
// Include Libraries
   ESP-NOW Demo - Receive
+
#include <WiFi.h>
   esp-now-demo-rcv.ino
+
#include <esp_now.h>
   Reads data from Initiator
+
 +
//Message to send
 +
String recv;
 +
 +
 +
void formatMacAddress(const uint8_t *macAddr, char *buffer, int maxLength)
 +
// Formats MAC Address
 +
{
 +
  snprintf(buffer, maxLength, "%02x:%02x:%02x:%02x:%02x:%02x", macAddr[0], macAddr[1], macAddr[2], macAddr[3], macAddr[4], macAddr[5]);
 +
}
 +
 +
 +
void receiveCallback(const uint8_t *macAddr, const uint8_t *data, int dataLen)
 +
// Called when data is received
 +
{
 +
  // Only allow a maximum of 250 characters in the message + a null terminating byte
 +
  char buffer[ESP_NOW_MAX_DATA_LEN + 1];
 +
  int msgLen = min(ESP_NOW_MAX_DATA_LEN, dataLen);
 +
  strncpy(buffer, (const char *)data, msgLen);
 +
 +
  // Make sure we are null terminated
 +
  buffer[msgLen] = 0;
 +
 +
  // Format the MAC address
 +
  char macStr[18];
 +
  formatMacAddress(macAddr, macStr, 18);
 +
 +
  // Send Debug log message to the serial port
 +
  Serial.printf("Received message from: %s - %s\n", macStr, buffer);
 +
 +
}
 +
 +
void sentCallback(const uint8_t *macAddr, esp_now_send_status_t status)
 +
// Called when data is sent
 +
{
 +
  char macStr[18];
 +
  formatMacAddress(macAddr, macStr, 18);
 +
  Serial.print("Last Packet Sent to: ");
 +
  Serial.println(macStr);
 +
  Serial.print("Last Packet Send Status: ");
 +
  Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");
 +
}
 +
 +
void broadcast(const String &message){
 +
  // Emulates a broadcast
 +
  // Broadcast a message to every device in range
 +
  uint8_t broadcastAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
 +
  esp_now_peer_info_t peerInfo = {};
 +
  memcpy(&peerInfo.peer_addr, broadcastAddress, 6);
 +
  if (!esp_now_is_peer_exist(broadcastAddress))
 +
  {
 +
    esp_now_add_peer(&peerInfo);
 +
  }
 +
  // Send message
 +
  esp_err_t result = esp_now_send(broadcastAddress, (const uint8_t *)message.c_str(), message.length());
 +
 +
   // Print results to serial monitor
 +
  if (result == ESP_OK)
 +
  {
 +
    Serial.println("Broadcast message success");
 +
  }
 +
  else if (result == ESP_ERR_ESPNOW_NOT_INIT)
 +
  {
 +
    Serial.println("ESP-NOW not Init.");
 +
  }
 +
  else if (result == ESP_ERR_ESPNOW_ARG)
 +
  {
 +
    Serial.println("Invalid Argument");
 +
  }
 +
  else if (result == ESP_ERR_ESPNOW_INTERNAL)
 +
  {
 +
    Serial.println("Internal Error");
 +
  }
 +
  else if (result == ESP_ERR_ESPNOW_NO_MEM)
 +
  {
 +
    Serial.println("ESP_ERR_ESPNOW_NO_MEM");
 +
  }
 +
  else if (result == ESP_ERR_ESPNOW_NOT_FOUND)
 +
  {
 +
    Serial.println("Peer not found.");
 +
  }
 +
  else
 +
  {
 +
    Serial.println("Unknown error");
 +
  }
 +
}
 +
 +
void setup()
 +
{
 +
 +
  // Set up Serial Monitor
 +
   Serial.begin(115200);
 +
   Serial2.begin(115200);
 
    
 
    
   DroneBot Workshop 2022
+
   https://dronebotworkshop.com
+
  // Set ESP32 in STA mode to begin with
*/
+
  WiFi.mode(WIFI_STA);
 +
  Serial.println("ESP-NOW Broadcast Demo");
 +
 +
   // Print MAC address
 +
   Serial.print("MAC Address: ");
 +
  Serial.println(WiFi.macAddress());
 +
 +
 +
  // Initialize ESP-NOW
 +
  if (esp_now_init() == ESP_OK)
 +
  {
 +
    Serial.println("ESP-NOW Init Success");
 +
    esp_now_register_recv_cb(receiveCallback);
 +
    esp_now_register_send_cb(sentCallback);
 +
  }
 +
  else
 +
  {
 +
    Serial.println("ESP-NOW Init Failed");
 +
    delay(3000);
 +
    ESP.restart();
 +
  }
 +
 +
 
 +
}
 +
 +
void loop() {
 +
 
 +
//if(Serial2.available()){
 +
    //recv=Serial2.readStringUntil('\n');
 +
    recv="avavardrgaalal";
 +
    broadcast(recv);
 +
//}
 +
 
 +
delay(5000);
 +
 
 +
}
 +
</pre>
 +
 
 +
Pour les ESP32 en mode responder :
 +
 
 +
<pre>
  
// Include Libraries
 
 
#include <esp_now.h>
 
#include <esp_now.h>
 
#include <WiFi.h>
 
#include <WiFi.h>
  
 +
char msg[32];
 +
char sous_chaine[3]; // Tableau de char pour stocker les sous-chaînes
 +
int i=0;
 +
// Create a structured object
  
//Create type of Data
+
char Data[2];
 
 
 
 
 
 
// Callback function executed when data is received
 
// Callback function executed when data is received
 
void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) {
 
void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) {
   memcpy(&Data, incomingData, sizeof(Data));
+
   memcpy(&msg, incomingData, sizeof(msg));
   Serial.print("Data received: ");
+
   Serial.print("Message received: ");
   Serial.println(Data);
+
   Serial.println(msg);
   if (Data == "al"); {
+
   for(i=0;i<strlen(msg);i+=2){
     Serial.println("OK");
+
     sous_chaine[0] = msg[i];
     Serial2.print("al");
+
     sous_chaine[1] = msg[i+1];
     digitalWrite(5, true);
+
     sous_chaine[2] = '\0'; // Ajoute un caractère nul à la fin de la sous-chaîne
  }
+
     Serial.println(sous_chaine); // Affiche la sous-chaîne dans le moniteur série
  if (Data == "et"); {
+
      
     Serial2.print("et");
 
     digitalWrite(5, false);
 
 
   }
 
   }
 
 
 
}
 
}
 
+
 
void setup() {
 
void setup() {
 
   // Set up Serial Monitor
 
   // Set up Serial Monitor
 
   Serial.begin(115200);
 
   Serial.begin(115200);
  Serial2.begin(115200);
 
 
  pinMode(5, OUTPUT);
 
 
    
 
    
 
   // Set ESP32 as a Wi-Fi Station
 
   // Set ESP32 as a Wi-Fi Station
 
   WiFi.mode(WIFI_STA);
 
   WiFi.mode(WIFI_STA);
 
+
 
   // Initilize ESP-NOW
 
   // Initilize ESP-NOW
 
   if (esp_now_init() != ESP_OK) {
 
   if (esp_now_init() != ESP_OK) {
Ligne 632 : Ligne 778 :
 
   
 
   
 
void loop() {
 
void loop() {
 +
 
}
 
}
 +
 
</pre>
 
</pre>
 +
 +
<br>
 +
 +
A noté que ici, on découpe le message reçu en chaines de caractères de longueur 2 qui corresponde directement aux ordres à donner au robot pour qu'il avance/recule/tourne.

Version du 5 mai 2023 à 14:58

Missions des robots jardiniers

Les robots jardiniers devront réaliser les actions suivantes :

  1. départ de la base du robot 1 ;
  2. exploration de l'environnement en évitant les obstacles ;
  3. évaluation des paramètres environnementaux : lumière, UV, température, humidité, humidité du sol\ldots
  4. si détection de paramètres propices, communication vers les robots 2 et 3 ;
  5. cheminement des robots 2 et 3 jusqu'au point de sinistre, à partir des informations fournies par le robot 1 ;
  6. action des robots 2 et 3 au point cible : arrosage, forage du sol\ldots

Décomposition initiale du travail

Pour le début de ce projet, nous avons décidé de décomposer le groupe en plusieurs sous-groupes de travail, chacun constitué d'un chef. Egalement, nous avons décidé d'inclure un chef global de groupe qui permettra de faciliter la communication entre les groupes en gardant un œil sur les différents groupes. Ces groupes sont les suivants :

  • Groupe 1: Robot 1 (robot détecteur équipé de plusieurs capteurs permettant de créer une carte à suivre pour les autres robots): 6 étudiants
  • Groupe 2: Robot 2 (robot arroseur qui suivra le chemin créé par le robot 1): 3 étudiants
  • Groupe 3: Robot 3 (robot planteur qui suivra le chemin créé par le robot 1): 3 étudiants

Cette composition sera très vite amenée à changer, étant donné que les robots 2 et 3 sont très similaires, les étudiants de ces 2 groupes travailleront souvent ensembles.

Algorithmes des robots

Pour déplacer le Robot 1, nous avons retenu le choix de discrétiser l'espace en 10 cm. Chaque mouvement effectué fera donc 10 cm, nous avons des capteurs à ultrasons qui transmettront au robot s'il est capable d'avancer de 10 cm sans rentrer dans un obstacle. Comme le Robot 1 ne tourne pas (il n'y pas vraiment de gauche ou de droite avec les roues holonomes) cela rend la tâche plus facile. Les autres robots auront seulement besoin de reproduire les déplacements du robot 1.

Choix de l'algorithme de déplacement

Nous avons choisi un algorithme simple permettant de parcourir une grande distance et d'éviter la plupart des obstacles.

Etant donné la durée du projet assez courte, faire un algorithme trop complexe aurait été assez contraignant. Le projet consiste à avancer jusqu'au fond du terrain (on considère ici que le terrain est un rectangle). Le robot avance jusqu'à détecter un objet puis on se dirige vers la droite. Ensuite, le robot continue a droite jusqu'à détecter un objet, ce qui nous fera aller vers l'arrière. Le robot roulera vers l'arrière pendant quelques centimètres puis ira à gauche. Lors de la prochaine détection d'un objet, le robot réutilisera la même méthode mais en inversant la droite et la gauche.

Voici un schéma explicatif de l'algorithme utilisé:

chemin normal du robot
chemin du robot avec obstacle détecté
















Pour le retour, il faudra refaire le même chemin en sens inverse, on retiendra chacun des mouvements.

Pseudo code

int deplacement ; //1=avant 2=gauche 3= droite 4= arrière
int direction; //2= gauche 3= droite


switch(deplacement)  //suivant le déplacement qu'on a choisi
  case 1:
    moteur_avant();  //on avance le moteur correspondant
    break;
  case2: 
    moteur_gauche();
    break;
  case 3:
    moteur_droite();
    break;
  default :
    moteur_arriere(10); //le robot descend seulement de 10cm (voir les schémas ci-dessus)
    if (direction==2)  //si la direction était gauche alors elle devient droite et inversement
      deplacement=3;
    else 
      deplacement=2;

    break;

Interruptions :

- capteur_av()  // dès que le capteur avant détecte une détection, on veut se déplacer à droite
    
    if (capteur_droite != 1) //s'il n'y a pas d'obstacle à droite
      deplacement=3;         //on se déplace à droite
    else                     //sinon
      fin interruption       // on quitte l'interruption capteur avant pour aller dans l'interruption capteur droite

- capteur_droite() ou capteur_gauche()

    deplacement=4;  // lorsque qu'un mur ou un obstacle est détecté, on veut descendre

-capteur_ar()
   deplacement=1; //dès qu'on arrive en bas du jardin on fait demi-tour

Simulation

La simulation de robot permet de vérifier que le comportement du robot est adéquat avec les fonctionnalités recherchés.

Fenetre de simulation Webots

Nous avons essayé de simuler avec le logiciel Webots. Nous avons commencer par modéliser le terrain, c'est à dire un carré d'herbe avec des objets qu'on pourrait trouver dans un jardin. Cependant, nous n'avons pas réussi à trouver un robot avec des roues holonomes ainsi nous n'aurions pas pu effectuer une simulation concrète de notre algorithme. La simulation et la création d'un nouveau robot auraient pris beaucoup de temps par rapport à celui dont on dispose, nous avons choisi de ne pas finir la simulation même si cette dernière aurait été très intéressante pour tester notre algorithme.

Choix des capteurs

Afin de pouvoir mesurer au mieux les caractéristiques du terrain pour déterminer où il devra planter et où il devra arroser, notre robot aura besoin de plusieurs capteurs:

  • Un capteur à UV pour la luminosité
  • Un capteur d'humidité de l'air
  • Un capteur de température
  • Un capteur pour mesurer d'humidité du sol
  • 4 capteurs de proximité à ultrason pour détecter les obstacles

Nous avons alors décidé d'utiliser les différents capteurs disponibles à Télécom car ils remplissaient les fonctions demandées, nous utiliserons alors:

Capteur UV

Pour capter la luminosité, nous utilisons le capteur d'UV de Grove "Grove UV sensor"
Grove uv sensor.jpg
Ce capteur retourne une tension de l'ordre du mV qu'on transformera en indice UV grâce à une fonction qu'on retrouvera dans la Documentation. On utilisera cet indice UV pour détecter si la zone est bien éclairée, donc s'il faut planter au point ou les mesures sont prises

Capteur humidité et température

Pour détecter les conditions idéales de plantation et d'arrosage des plantes, nous avons utilisé un capteur combinant la détection de température et d'humidité de l'air, il s'agit du "Grove humidity and temperature sensor"
Grove humidity temperature.jpg
Ce capteur nous retournera un signal en mV qui nous permettra via un rapide programme présent dans la Documentation de récupérer le taux d'humidité de l'air et la température en degrés

Capteur humidité du sol

Après étude, nous avons choisi d'utiliser le capteur "Grove moisture sensor". Documentation Sa forme de fourche lui permet de se planter dans le sol plus facilement. Son fonctionnement est simple, plus le sol est humide, plus le courant pourra passer entre les 2 dents de la fourche

Sol sec.jpg
Prise du capteur d'humidité du sol dans un sol sec


Tableau récapitulatif des mesures du sol (prises à Télécom un lendemain de jour de pluie et un jour sec)
Etat du sol Valeur mesurée par le capteur
Terre sèche 300
Terre sèche avec herbe 350
Terre avec herbe (pluie la veille) 600
Terre sèche après avoir été humidifiée 650
Terre trempée 720

Ces mesures nous permettent alors de déterminer les valeurs pour lesquelles le sol a besoin d'être arrosé ou non. On détermine alors que lorsque le sol a besoin d'être arrosées, nos valeurs de retour seront inférieures à 400, et lorsque le seuil a été arrosé récemment, nos valeurs seront supérieures à 400.
On décide alors de fixer le seuil du capteur à 400 .

Capteur de gouttes de pluie

Raindrop.jpg
Documentation Un capteur de gouttes de pluie, ou capteur de détection de gouttes de pluie, est utilisé pour détecter s'il pleut ou non, ainsi que les précipitations. Il est largement utilisé dans des systèmes d'essuie-glace automatique, des systèmes d'éclairages intelligents et de toits ouvrants d'automobiles.
Ce capteur est également doté d'un module additionnel d'analyse permettant de recevoir en entrée de la carte une information simple: la présence de gouttes de pluies, on recevra alors un signal booléen permettant de savoir si il pleut. Si tel est le cas, on ordonnera au robot 1 de rentrer à la base.

Choix des cartes

Robot 1

Ce robot utilise les cartes de développement suivantes : Arduino Due et Teensy.

Arduino Due :
Nous avons choisi une carte Arduino car nous trouvons que la programmation est plus facile. Nous avons pris le modèle Due car la Arduino Uno ne possédait pas assez de ports d'interruptions (seulement 2). L'inconvénient est que la carte est grande par rapport à notre robot.

Teensy :

Tout d'abbord, nous avons choisi la Teensy car c'est la carte utilisée par les années précédentes. Ainsi, nous disposons de tous les codes pour faire tourner les moteurs. Elles possède aussi des avantages comme sa polyvalence et sa petite taille.

Robot 2

Ce robot utilise les cartes de développement suivantes : Esp32 Thing et Teensy.

- Esp32 Thing :
Nous avons choisi cette carte car elle est dotée d'une communication BLE et une faible consommation.

- Teensy :

Nous avons choisi la Teensy pour les mêmes raisons que précédemment.

Robot 3

Ce robot utilise les cartes de développement Esp32 Thing et Teensy pour les mêmes raisons que précédemment.

Prise en main des plateformes

Alimentation

  • Le robot maître :
  1. Il possède un convertisseur de tension 9-18 à 5VDC. Il s'alimente à l'aide d'une batterie de 9.6V composée de 8 cellules Ni-Mh, la tension maximale est d'environ 10.7V.
  2. Les moteurs fonctionnent jusqu'à un maximum de 7.2V, ils consomment chacun à 0.5A en fonctionnement à vide et jusqu'à 1.3A en fonctionnement nominal (à un couple de 2 kgf-cm) : Documentation du moteur
  3. Les cartes sont alimentées par des batteries externes de 5V
  • Les robots esclaves ont deux sources d'alimentation :
  1. La partie logique est alimentée par une pile 9V au travers d'une carte TRACO POWER (4.5-9V -> 3.3V). Celle-ci alimente la carte Teensy et la carte Sparkfun ESP32 Thing.
  2. La partie puissance est alimentée par un ensemble de 8 piles Ni-Mh, qui délivre 9.6V. Elles sont connectées au port Pvex1 (et à la masse juste en dessous) d'après cette représentation du PCB.
Les deux parties sont séparées sur le PCB à l'aide du cavalier placé du côté du connecteur d'alimentation.

Robot 1

Ce robot utilise les cartes de développement suivantes : Arduino Due et Teensy.

Arduino Due :
Nous avons choisi une carte Arduino car nous trouvons que la programmation est plus facile. Nous avons pris le modèle Due car la Arduino Uno ne possédait pas assez de ports d'interruptions (seulement 2). L'inconvénient est que la carte est grande par rapport à notre robot.

Teensy :

Nous avons choisi la Teensy comme carte motrice car c'est la carte utilisée par les années précédentes. Ainsi, nous disposons de tous les codes pour faire tourner les moteurs. Ses avantages sont sa polyvalence et sa petite taille.

Les deux cartes sont connectées par une liaison série pour communiquer.

Déplacement des robots

Robot 1

Afin de Controller les déplacements du robot, on va utiliser le code pour une plateforme holonome moteurs. La procédure à suivre afin de vérifier et tester le fonctionnement des moteurs et de l'asservissement:

  1. Téléverser ce code dans la carte Teensy. La vitesse maximale est définie par le paramètre paramVitesse dans le début du code.
  2. Téléverser
  3. Tester les moteurs en envoyer les commandes suivantes:
    -av1 pour démarrer le moteur 1 à pleine vitesse
    -am1 pour démarrer le moteur 1 à demi vitesse
    -st pour stopper tous les moteurs
  4. Tester les déplacements en entiers
    -avd pour que le robot avance : on active simultanément les moteurs 1 et 3.
    -avg pour que le robot se déplace à gauche : on active simultanément les moteurs 2 et 4.
    -rg pour que le robot fasse une rotation dans le sens trigonométrique : on active simultanément les moteurs 1,2,3 et 4.

On peut tester le code grâce à une console série avec une vitesse de 115200. On utilise alors le port série par défaut de la carte, il faut donc penser à l'ouvrir (Serial.begin( 115200);).

Lors des premiers essais de déplacement, nous avons eu quelques problèmes concernant l'asservissement. En effet, on a remarqué au début du projet que la roue 1 avait une vitesse de rotation très importante comparée aux autres et que le moteur 2 ne tournait que dans un sens. Ces difficultés posaient problème pour le déplacement du robot. On a donc test toute la chaine en partant de la Teensy jusqu'aux moteurs :

Premièrement, concernant le moteur qui ne tourne que dans un sens, le problème vient du pont en H qui ne fait pas circuler le courant dans un sens. Ce pont est à changer l'année prochaine.

Deuxièmement, en étudiant le moteur 1, nous avons remarqué qu'il ne répondait pas aux consignes demandés et en particulier l'asservissement. Il semblerait effectivement que les branchements de l'asservissement sur la Teensy sur le schéma électrique fourni ne sont plus d'actualités. Les ports de la Teensy n'étaient donc pas les bons ports dans le code de l'Arduino. Après vérification, voici les bons ports :

M1Out1 = 16;

M1Out0 = 17;

M2Out1 = 14;

M2Out0 = 15;

M3Out1 = 11;

M3Out0 = 12;

M4Out1 = 9;

M4Out0 = 10;


Une fois les ports correctement associés, l'asservissement de la roue 1 fonctionne correctement et les roues tournent toutes à la même vitesse.

Pour effectuer les opérations de déplacement à l'aide de l'Arduino Due, il faut connecter les deux cartes à l'aide des ports séries en croisant le Tx et le Rx. Il faut également ouvrir le port série de la due utilisé (SerialX.begin(115200);) pour le port série X. Finalement, ne pas oublier de relier les masses de la Teensy et de la Due ensemble.

Robot 2 et 3

Ces robots utilisent les cartes de développement suivantes : Esp32 Thing et Teensy.

Esp32 Thing :
Nous avons choisi cette carte, car elle possède une communication BLE et un module WiFi.

Teensy :

Nous avons choisi la Teensy pour les mêmes raisons que précedemment.

La première étape est de contrôler les moteurs avec la teensy, pour cela il faut utiliser le code suivant: Les déplacements des robots 2 et 3 sont gérés avec les commandes suivantes:

  1. La commande avXXX permet d'indiquer la direction, sous forme d'angle en degrés (ex XXX = 120), dans laquelle le robot va se déplacer (cette commande ne fait pas bouger le robot).
  2. La commande ho3 entraîne le déplacement du robot dans la direction indiquée avec la commande avXXX.
  3. Vous pouvez aussi tester les commandes at et et permettant respectivement d'allumer et d'éteindre la led de la Teensy.

Ensuite, il faut établir une liaison série entre l'ESP32 et la Teensy, l'objectif est de contrôler le robot depuis l'ESP32 car c'est cette carte qui recevra les informations du robot 1, pour cela vous pouvez commencer par utiliser ce code simple permettant de faire clignoter la LED de la Teensy. Dans notre cas nous avons utilisé les ports 16 et 17 de l'ESP32 qui correspondent à UART2, pour envoyer les commandes nous avons donc utiliser Serial2.

void setup() {

  Serial2.begin(115200); // Initialize serial communication at 115200 baud

}

 

void loop() {

  

    Serial2.print("al"); // Send "Blink" command to Teensy

    delay(1000); // Wait for 1 second   

    Serial2.print("et"); // Send "Blink" command to Teensy

    delay(1000); // Wait for 1 second   

  
}

Il faut ensuite modifier le code pour envoyer les commandes permettant au robot de se déplacer.

Communication entre les robots

Pour communiquer entre les robots, nous avons choisi le protocole suivant : Le robot se déplace et prend plusieurs mesures, il les enregistre et au choisi le meilleur point pour planter des choux. Le chemin qu'il a parcouru est stocké en mémoire, il envoie donc les instructions de se déplacer jusqu'à ce point au robot 2 puis au 3. Les robots 2 et 3 reproduisent le même parcours que le robot 1, certes ce n'est pas le chemin le plus court, mais c'est la solution la plus facile.

Les instructions seront envoyées comme suit :


  • av : Avance tout droit de 10 cm
  • dr : Tourne à droite et avance de 10 cm
  • ga : Tourne à gauche et avance de 10 cm
  • ar : Recule de 10 cm.

1ère tentative : Bluetooth (ECHEC)


Erreur lors de la création de la miniature : Fichier avec des dimensions supérieures à 12,5 MP


Les instructions seront envoyées comme suit :


  • 1 bit de start, 1 bit de stop
  • 00 : Avance tout droit de 10 cm
  • 01 : Tourne à droite et avance de 10 cm
  • 10 : Tourne à gauche et avance de 10 cm
  • 11 : Recule de 10 cm.





Pour utiliser le protocole Bluetooth, on utilise le module série Bluetooth v3.01.

IMG 5040.jpg


Pour utiliser le protocole Bluetooth, on utilise des commandes AT pour initialiser la communication entre le maitre (Robot 1) et l'esclave (Robot 2 et 3). Pour plus d'information sur les commandes AT, voici le site officiel de Seedstudio : https://wiki.seeedstudio.com/Bluetooth_Bee_v2.0/. Si le module est connecté, la petite LED verte de celui-ci s'arrête de clignoter. Pour voir un exemple d'utilisation dont nous sommes fortement inspirés, consulter le site suivant : https://wiki.seeedstudio.com/Grove-Serial_Bluetooth_v3.0/.



Voici le code pour initier la connexion :


Code Maitre : (ici sous arduino UNO)

  #include <SoftwareSerial.h>                         // Software Serial Port

#define RxD         7
#define TxD         6


#define DEBUG_ENABLED  1



SoftwareSerial blueToothSerial(RxD,TxD);

void setup()
{
  Serial.begin(9600);
  pinMode(RxD, INPUT);
  pinMode(TxD, OUTPUT);
 
    
  blueToothSerial.begin(9600);  

  Serial.println("AT : ");
  blueToothSerial.print("AT");  
  delay(400);
  if(blueToothSerial.available()){
    Serial.println(blueToothSerial.readStringUntil('\n'));
  }
  
  delay(400); 
  
  Serial.println("AT+Version : ");
  blueToothSerial.print("AT+VERSION");             // Restore all setup value to factory setup
  delay(400);
  if(blueToothSerial.available()){
    Serial.println(blueToothSerial.readStringUntil('\n'));
  } 

  delay(400);
  
  Serial.println("AT+Default : ");
  blueToothSerial.print("AT+DEFAULT");             // Restore all setup value to factory setup
  delay(400);
  if(blueToothSerial.available()){
    Serial.println(blueToothSerial.readStringUntil('\n'));
  }
  
  delay(400);  

  Serial.println("AT+NAMESeeedMaster0 : ");
  blueToothSerial.print("AT+NAMESeeedMaster0");    // set the bluetooth name as "SeeedMaster" ,the length of bluetooth name must less than 12 characters.
  delay(400);
  if(blueToothSerial.available()){
    Serial.println(blueToothSerial.readStringUntil('\n'));
  }
  
  delay(400);

  Serial.println("AT+ROLEM : ");
  blueToothSerial.print("AT+ROLEM");             // set the bluetooth work in master mode
  delay(400);
  if(blueToothSerial.available()){
    Serial.println(blueToothSerial.readStringUntil('\n'));
  }
  
  delay(400); 
  
  Serial.println("AT+AUTH1 : ");
  blueToothSerial.print("AT+AUTH1");
   delay(400);      
  if(blueToothSerial.available()){
    Serial.println(blueToothSerial.readStringUntil('\n'));
  }      
    delay(400);    

  Serial.println("AT+CLEAR : ");
  blueToothSerial.print("AT+CLEAR");             // Clear connected device mac address
  if(blueToothSerial.available()){
    Serial.println(blueToothSerial.readStringUntil('\n'));
  }
    delay(400);   

  Serial.println("AT+PIN1111 : "); 
  blueToothSerial.print("AT+PIN1111"); 
  
  delay(400);
  if(blueToothSerial.available()){
    Serial.println(blueToothSerial.readStringUntil('\n'));
  }  
  
    //wait 1s and flush the serial buffer
    delay(1000);
    Serial.flush();
    blueToothSerial.flush();

    Serial.println("Debut de la communication");
}



Voici les réponses aux commandes AT :

Réponse commandes AT.JPG


Code Esclave : (ici avec une DUE, donc pas besoin de SoftwareSerial)

  #define DEBUG_ENABLED  1


void setup()
{
  Serial.begin(9600);
  Serial3.begin(9600);
 
  Serial.println("AT");
  Serial3.print("AT");
  
  delay(400);
  if(Serial3.available()){
    Serial.println(Serial3.readStringUntil('\n'));
  }  
  delay(400);

  Serial.println("AT+Default");
  Serial3.print("AT+DEFAULT");  
  delay(400);
  if(Serial3.available()){
    Serial.println(Serial3.readStringUntil('\n'));
  }  
  delay(400);

  Serial.println("AT+NAMESeeedSlave0");
  Serial3.print("AT+NAMESeeedSlave0");    // set the bluetooth name as "SeeedMaster" ,the length of bluetooth name must less than 12 characters.
  delay(400);
  if(Serial3.available()){
    Serial.println(Serial3.readStringUntil('\n'));
  }  
  delay(400);

  Serial.println("AT+PIN1111"); 
  Serial3.print("AT+PIN1111"); 
  delay(400);
  if(Serial3.available()){
    Serial.println(Serial3.readStringUntil('\n'));
  }  

  delay(400);
   Serial.println("AT+AUTH1");
   Serial3.print("AT+AUTH1");    
    if(Serial3.available()){
    Serial.print(lnSerial3.readStringUntil('\n'));
  }  

    //wait 1s and flush the serial buffer
    delay(1000);
    Serial.flush();
    Serial3.flush();
   
  Serial.println("Debut de la communication");
}

Mode de communication retenu : WIFI

Nous nous aidons de la bibliothèque ESP-NOW. Pour avoir toutes les méthodes et fonction de la bibliothèque : https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_now.html#_CPPv416esp_now_add_peerPK19esp_now_peer_info_t. Pour avoir un exemple dont nous sommes inspirés : https://dronebotworkshop.com/esp-now/.

On souhaite établir une communication entre les robots, via *ESP32*. On utilise alors ESP-NOW. Il s'agit d'un protocole de communication unique aux ESP32. Le protocole utilise la bande de fréquence 2.4 GHz. Dans le cadre de ce projet, la topologie utilisée est appelée broadcast, c'est-à-dire que toutes les cartes *ESP32* recevront le message envoyé. Le *ESP32* du robot 1 joue le rôle d'initiateur (initiator) et les *ESP32* des robots 2 et 3 jouent le rôle de récepteur (responder).



Esp-now bb.jpg


Cette méthode **ne nécessite pas de connaître les adresses MAC des *ESP32* ** qui auront le rôle de responder. Cependant, nous avons d'abord essayé de communiquer en point à point avec les adresses MAC. Celle-ci peuvent être obtenue facilement avec ce code :

  • Il faut bien noter les adresses obtenues pour éviter de le refaire plusieurs fois.*


 
// Include WiFi Library
#include "WiFi.h"
 
void setup() {
 
  // Setup Serial Monitor
  Serial.begin(115200);
 
  // Put ESP32 into Station mode
  WiFi.mode(WIFI_MODE_STA);
 
  // Print MAC Address to Serial monitor
  Serial.print("MAC Address: ");
  Serial.println(WiFi.macAddress());
}
 
void loop() {
 
}


Comme notre carte principale est la *Arduino Due*, on connecte sur les ports serial 3 deux fils vers serial 2 (en inverser) de la *ESP32*. C'est la Due qui enverra les messages à envoyer.

Voici le code utilisé pour envoyer les données en mode broadcast :


Pour la *Due* :


String mvt="avavavdrdrgaalalav";
void setup() {
  
  Serial.begin(115200);
  Serial3.begin(115200);
  
}

void loop() {
 Serial3.print(mvt);
 delay(1000);
}


Pour la *ESP32* en mode Broadcast :


// Include Libraries
#include <WiFi.h>
#include <esp_now.h>
 
//Message to send
String recv;
 
 
void formatMacAddress(const uint8_t *macAddr, char *buffer, int maxLength)
// Formats MAC Address
{
  snprintf(buffer, maxLength, "%02x:%02x:%02x:%02x:%02x:%02x", macAddr[0], macAddr[1], macAddr[2], macAddr[3], macAddr[4], macAddr[5]);
}
 
 
void receiveCallback(const uint8_t *macAddr, const uint8_t *data, int dataLen)
// Called when data is received
{
  // Only allow a maximum of 250 characters in the message + a null terminating byte
  char buffer[ESP_NOW_MAX_DATA_LEN + 1];
  int msgLen = min(ESP_NOW_MAX_DATA_LEN, dataLen);
  strncpy(buffer, (const char *)data, msgLen);
 
  // Make sure we are null terminated
  buffer[msgLen] = 0;
 
  // Format the MAC address
  char macStr[18];
  formatMacAddress(macAddr, macStr, 18);
 
  // Send Debug log message to the serial port
  Serial.printf("Received message from: %s - %s\n", macStr, buffer);
 
} 
 
void sentCallback(const uint8_t *macAddr, esp_now_send_status_t status)
// Called when data is sent
{
  char macStr[18];
  formatMacAddress(macAddr, macStr, 18);
  Serial.print("Last Packet Sent to: ");
  Serial.println(macStr);
  Serial.print("Last Packet Send Status: ");
  Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail");
}
 
void broadcast(const String &message){
  // Emulates a broadcast
  // Broadcast a message to every device in range
  uint8_t broadcastAddress[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
  esp_now_peer_info_t peerInfo = {};
  memcpy(&peerInfo.peer_addr, broadcastAddress, 6);
  if (!esp_now_is_peer_exist(broadcastAddress))
  {
    esp_now_add_peer(&peerInfo);
  }
  // Send message
  esp_err_t result = esp_now_send(broadcastAddress, (const uint8_t *)message.c_str(), message.length());
 
  // Print results to serial monitor
  if (result == ESP_OK)
  {
    Serial.println("Broadcast message success");
  }
  else if (result == ESP_ERR_ESPNOW_NOT_INIT)
  {
    Serial.println("ESP-NOW not Init.");
  }
  else if (result == ESP_ERR_ESPNOW_ARG)
  {
    Serial.println("Invalid Argument");
  }
  else if (result == ESP_ERR_ESPNOW_INTERNAL)
  {
    Serial.println("Internal Error");
  }
  else if (result == ESP_ERR_ESPNOW_NO_MEM)
  {
    Serial.println("ESP_ERR_ESPNOW_NO_MEM");
  }
  else if (result == ESP_ERR_ESPNOW_NOT_FOUND)
  {
    Serial.println("Peer not found.");
  }
  else
  {
    Serial.println("Unknown error");
  }
}
 
void setup()
{
 
  // Set up Serial Monitor
  Serial.begin(115200);
  Serial2.begin(115200);
  
 
  // Set ESP32 in STA mode to begin with
  WiFi.mode(WIFI_STA);
  Serial.println("ESP-NOW Broadcast Demo");
 
  // Print MAC address
  Serial.print("MAC Address: ");
  Serial.println(WiFi.macAddress());
 
 
  // Initialize ESP-NOW
  if (esp_now_init() == ESP_OK)
  {
    Serial.println("ESP-NOW Init Success");
    esp_now_register_recv_cb(receiveCallback);
    esp_now_register_send_cb(sentCallback);
  }
  else
  {
    Serial.println("ESP-NOW Init Failed");
    delay(3000);
    ESP.restart();
  }
 

}
 
void loop() {

//if(Serial2.available()){
    //recv=Serial2.readStringUntil('\n');
    recv="avavardrgaalal";
    broadcast(recv);
//}

delay(5000);

}

Pour les ESP32 en mode responder :


#include <esp_now.h>
#include <WiFi.h>

char msg[32];
char sous_chaine[3]; // Tableau de char pour stocker les sous-chaînes
int i=0;
// Create a structured object

 
// Callback function executed when data is received
void OnDataRecv(const uint8_t * mac, const uint8_t *incomingData, int len) {
  memcpy(&msg, incomingData, sizeof(msg));
  Serial.print("Message received: ");
  Serial.println(msg);
  for(i=0;i<strlen(msg);i+=2){
    sous_chaine[0] = msg[i];
    sous_chaine[1] = msg[i+1];
    sous_chaine[2] = '\0'; // Ajoute un caractère nul à la fin de la sous-chaîne
    Serial.println(sous_chaine); // Affiche la sous-chaîne dans le moniteur série
    
  }
}
 
void setup() {
  // Set up Serial Monitor
  Serial.begin(115200);
  
  // Set ESP32 as a Wi-Fi Station
  WiFi.mode(WIFI_STA);
 
  // Initilize ESP-NOW
  if (esp_now_init() != ESP_OK) {
    Serial.println("Error initializing ESP-NOW");
    return;
  }
  
  // Register callback function
  esp_now_register_recv_cb(OnDataRecv);
}
 
void loop() {
 
}


A noté que ici, on découpe le message reçu en chaines de caractères de longueur 2 qui corresponde directement aux ordres à donner au robot pour qu'il avance/recule/tourne.