
Le module GPS pour M5Stack Atom est un mini boitier qui combine un module GPS u-blox UBX-M8030-KT et un mini module M5Atom Lite ESP32-PICO-D4 de M5Stack. Pour ce projet, nous allons développer un tracker GPS à l’aide de la librairie Arduino TinyGPS++ qui nous servira à récupérer les données de localisation, le nombre de satellite, l’altitude et la précision de positionnement (HDOP). Les données GPS du tracker seront enregistrées sur une carte microSD au format GPX afin de pouvoir afficher le tracé sur un logiciel de cartographie tel que OpenStreetMap, Google MAP ou Leaflet.
Un fichier de statistique est également généré automatiquement par le programme Arduino. Il exporte la distance parcourue en mètres et en kilomètres, la vitesse maxi et moyenne.
Présentation du module GPS M5Atom Lite de M5Stack
Le module GPS de M5Stack est un boitier qui embarque un récepteur GPS UBX-M8030-KT u-blox sur lequel vient s’insérer un Core M5Atom Lite (la version avec un seul point lumineux).
Le package contient les éléments suivants dans un boitier de protection en plastique transparent
M5Atom Lite construit autour d’un ESP32-PICO-D4 d’EspressifLe boitier GPS u-bloxUn câble USB-C pour la programmationVisserie + Clé 6-pans
Le boitier GPS est un équipé des éléments suivants
D’un récepteur GPS UBX-M8030-KT du fabricant u-bloxD’un lecteur de carte Micro-SDD’un port Grove A PH2.0 4 broches exposant le bus I2C (broches 21 et 22 de l’ESP32-PICO-D4)
Petit regret, le boitier n’embarque pas de batterie LiPo. Pour alimenter le pack, il faudra utiliser une Power Bank, le bloc d’alimentation TailBat de M5Stack vendu séparément ou bricoler sa propre solution par impression 3D.
Présentation du boitier M5Atom Lite ESP32-PICO-D4
Le M5Stack Atom Lite est une mini carte de développement très compacte mesurant 24x24x10mm construit autour d’un micro-contrôleur ESP32-PICO-D4 d’Espressif équipé de 4Mo de mémoire flash SPI intégré. Le PICO est une version adaptée à l’électronique vestimentaire ou mobile. Comme son grand frère, il dispose de la connectivité WiFi et Bluetooth.
Voici les principales équipement et caractéristiques techniques de l’Atom Lite
RESETGPIO compatible breadboardG19, G21, G22, G23, G25, G33
3V3, 5V, GNDPortsUSB-C pour alimentation et programmation
GROVE (I2C + I/0 + UART) connecteur PH2.0 4 broches. GND, 5V, G26, G32Antenne WiFiAntenne 3D 2.4GTempérature de fonctionnement0°C à 40°CPoids3gDimensions24 x 24 x 10 mm
Plus d’informations sur cette page.
Récepteur GPS UBX-M8030 u-blox (série M8)
Le récepteur UBX-M8030 est une puce GPS polyvalente de la série u-blox M8 dont voici les principales caractéristiques :
Réception simultanée jusqu’à 3 types de systèmes de positionnement GNSS (GPS, Galileo, GLONASS, BeiDou)Sensibilité de navigation allant jusqu’à -167 dBmFaible consommation énergétiquePrécision de positionnement supérieur en milieu urbainPrise en charge de tous les systèmes satellitesPlage de températures de fonctionnement de -40°C à +105°C (qualité automobile)
D’un point de vue informatique, le récepteur GPS UBX-M8030 envoie à la fréquence de 10Hz (10 fois par secondes) des messages au standard NMEA via plusieurs interfaces (UART, I2C, USB et SPI). M5Stack a opté pour l’interface série UART à 9600 bauds.
Ressources utiles
Voici quelques liens vers des ressources utiles
Librairie Arduino M5AtomFiche technique Atom LiteFiche technique ESP-32 PicoFiche technique UBX-M8030-KT
Le standard NMEA 0183
Il existe plusieurs constellations de satellites permettant de faire de positionnement
Galileo le système EuropéenGPS le système Américain le plus ancien (et le plus connu)GLONASS le système RuseBeiDou le système Chinois
Chaque satellites envoie des trames qui sont une simple chaîne de caractère que le récepteur satellite va récupérer et décoder. Le NMEA 0183 est un standard employé par tous les système de positionnement ce qui a permis le développement de puces polyvalentes multi-systèmes. La norme NMEA 0183 est une spécification pour la communication entre équipements marins, dont les équipements GPS. Elle est définie et contrôlée par la National Marine Electronics Association (NMEA), association américaine de fabricants d’appareils électroniques maritimes.
Chaque système dispose de son préfixe pour l’identifier
GA pour GalileoGP pour le GPSBD ou GB pour BeidouGL pour GLONASS
Voici un exemple de trame NMEA
$GPGGA,064036.289,4836.5375,N,00740.9373,E,1,04,3.2,200.2,M,,,,0000*0E
$GPGGA : Type de trame
074036.289 : Trame envoyée à 07h40m 36,289s (heure au standard UTC)
4936.5375,N : Latitude 49,608958° Nord = 48°36’32.25″ Nord
00740.9373,E : Longitude 7,682288° Est = 7°40’56.238″ Est
1 : Type de positionnement. 1 pour le GPS
04 : Nombre de satellites utilisés pour calculer les coordonnées
3.2 : Précision horizontale ou HDOP (Horizontal dilution of precision)
202.2,M : Altitude 202.2 mètres
,,,,,0000 : D’autres informations peuvent être inscrites dans ces champs
*0E : Somme de contrôle de parité, un simple XOR sur les caractères entre $ et *3
Plus d’informations sur cette page Wikipedia.
En récupérant le signal de plusieurs satellites, le récepteur GPS est capable de déterminer par triangulation sa position au sol. Plus il y aura de satellite, meilleur sera la précision de positionnement. C’est le HDOP.
TinyGPSPlus, la librairie idéale pour un Tracker GPS
Il existe plusieurs librairies Arduino compatibles ESP32 / ESP8266 qui permettent de décoder les messages GPS au standard NMEA. Vous trouverez également dans l’exemple AtomicGPS avec la librairie M5Atom une librairie pour décoder les messages NMEA.
Après avoir testé la librairie M5Stack et MicroNMEA de Steve Marple, je vous conseille d’utiliser TinyGPSPlus de Mikal Hart. Le gros avantage de TinyGPS++ est quelle dispose de pleins d’outils :
Extraction de la date et de l’heure qui est au format UTCVérifie la validité de toutes les informations (localisation, date, heure, vitesse…)Et surtout la méthode distanceBetween() qui permet de calculer la distance parcourue entre deux points
Créer un projet et installer les librairies ESP32 et M5Stack sur l’IDE Arduino
Avant de commencer, vous devrez déjà installer le SDK Arduino-ESP32 prenant en charge les cartes de développement ESP32 et ESP32-Pico. Tout est expliqué en détail dans ce tutoriel.
Une fois l’IDE Arduino prêt, ouvrez le gestionnaire de librairie et installez les librairies suivantes :
M5Atom la librairie de base qui permet d’accéder à la LED et ou bouton utilisateurFastLED elle est utilisée par la librairie M5Atom pour piloter la LED située sous le bouton utilisateurTinyGPSPlus la librairie pour décoder les trames GPS NMEACSV Parser utilisée dans le projet pour stocker et recharger quelques statistiques (distance parcourue, vitesse moyenne et vitesse maxi.)
Sélectionner la carte de développement M5Stick-C
Le M5Atom Lite est construit une version allégée de l’ESP32, l’ESP32-PICO. Si vous choisissez une carte de développement ESP32 standard, vous obtiendrez une erreur en téléversant le programme depuis l’IDE Arduino.
Il n’y a pas (encore) de configuration pour les Core M5Atom Lite et M5Atom. Il faut sélectionner le M5Stick-C dans la liste qui embarque également un ESP32-PICO.
Diminuez la vitesse de transfert à 115200 bauds sinon vous obtiendrez une erreur.
Sélectionner le niveau des messages de mise au point, Info par exemple.
Créer un projet et installer les librairies sur PlatformIO
Sur PlatformIO, il y a beaucoup moins de préparatifs. Ouvrez l’écran d’accueil de PlatformIO
Créer un nouveau projet
Dans l’assistant de configuration :
Donner un nom à votre projetSélectionner la carte M5Stick-C (comme sur l’IDE Arduino)Conserver le framework ArduinoModifier éventuellement le dossier d’enregistrementLancer la création du projet en cliquant sur Finish
Lorsque le projet est prêt, ouvrez le fichier de configuration platformio.ini et collez la configuration suivante pour installer les librairies. L’installation des librairie débute à chaque sauvegarde du fichier platformio.ini.
[env:m5atom]platform = espressif32board = m5stick-cframework = arduinomonitor_speed = 115200lib_deps = m5stack/M5Atom @ ^0.0.1 fastled/FastLED @ ^3.3.3 mikalhart/TinyGPSPlus @ ^1.0.2 michalmonday/CSV Parser @ ^0.2.0build_flags = -DCORE_DEBUG_LEVEL=5
Que contient cette configuration
permet de modifier la vitesse du moniteur série de PlatformIO qui utilise par défaut un débit de 9600 bauds la liste des librairies à installer (M5Atom, FastLED, TinyGPSPlus, CSV Parser) permet de définir le niveau des messages de la librairie ESP_LOG, plus pratique que le Serial.print().
Un projet de tracker GPS avec export au format GPX
L’objectif de ce projet est d’enregistrer à intervalle régulier les coordonnées ainsi que au format GPX. Le format GPX est un format d’enregistrement de coordonnées GPS standard supporté par tous les logiciels de cartographies. C’est un fichier au format XML dont voici un exemple tiré du projet. Pour en savoir plus sur le format GPX, vous trouverez plein d’infos sur cette page Wikipedia.
trkseg : segment
trkpt : coordonnées GPS (latitude, longitude)
time : horodatage au format ISO 8601
sat : nombre de satellites
ele : altitude
hdop : précision
Plus la fréquence d’acquisition augmente, plus le risque de détruire la carte micro SD augmente lorsqu’on souhaite récupérer les données. Plutôt que de couper brutalement l’alimentation en retirant la batterie par exemple, il suffira d’appuyer une fois sur le bouton utilisateur (broche GPIO39 de l’ESP32) et d’attendre que la LED clignote deux fois en orange.
Pour reprendre l’enregistrement, insérer la carte micro SD dans le lecteur et faites un Reset à l’aide du bouton latéral.
Paramètres du programme Arduino
Voici les paramètres que vous pouvez ajuster en fonction de vos besoins. La valeur entre (parenthèses) est la valeur par défaut.
TIME_ZONE (1) compensation du décalage horaireRECORD_FREQUENCY (5000) Fréquence d’enregistrementACTIVATE_DEEP_SLEEP_ONCLICK (true) active la mise en veille lorsqu’on appuie sur le bouton utilisateurGPS_TIMEOUT (5000) Signale une erreur si le GPS n’a pas répondu dans le temps allouéFORCE_UPDATE_STATS (false) Permet de force l’enregistrement des statistiquesSPEED_BUFFER_SIZE (10) Nombre de points permettant de calculer la vitesse moyenne
Comment utiliser la librairie TinyGPS++
Le récepteur GPS envoie en permanence sur le port série (UART) les messages. Pour récupérer ces chaînes, on va donc ouvrir un second port série et “passer” les chaînes à la librairie TinyGPS++ qui va s’occuper de décoder tout ça.
On créé un objet de type TinyGPSPlus qui contient toutes les méthodes et les données GPS ainsi qu’un deuxième port série (le premier étant utilisé pour téléverser et mettre au point le programme). Le nom n’a aucune importance.
TinyGPSPlus gps;HardwareSerial gps_uart(1);
Dans le setup(), il suffira ensuite d’ouvrir le port série sur la broche 22. La broche RX du module GPS n’étant pas connectée, on passe la valeur -1 comme paramètre.
gps_uart.begin(9600,SERIAL_8N1,22,-1);
Ensuite, il suffit de récupérer à intervalle régulier (par exemple chaque seconde) les messages envoyés par le module GPS dans la loop().
Comment récupérer les données GPS avec TinyGPS++ (API)
Les données GPS sont regroupés dans la librairie TinyGPS++ dans les objets suivants. Chaque objet dispose d’une méthode isValid() qui permet de contrôler que les valeurs décodées sont correctes et isUpdated() qui permet de savoir si la valeur a été actualisée depuis la dernière acquisition. Cela permet de réduire le nombre de points GPS dans le fichier GPX.
TinyGPSLocation location, coordonnées GPS. Fonctions age(), lat(), lng(), rawlat(), rawlng()TinyGPSDate date , date du point GPS. Fonctions age(), year(), month(), day()TinyGPSTime time heure du point GPS. Fonctions age(), hour(), minute(), second(), centisecond()TinyGPSSpeed speed. Vitesse. Fonctions knots(), mph(), mps(), kmph()TinyGPSCourse course. Fonction course()TinyGPSAltitude altitude. Fonctions meter(), miles(), kilometers(), feet()TinyGPSInteger satellites. Fonctions value() pour récupérer le nombre de satellitesTinyGPSHDOP hdop. fonction hdop()
Par exemple pour récupérer la vitesse du véhicule en km/h, on exécutera
gps.speed.kmph();
En plus de ces méthodes, on peut connaître la distance entre deux points (coordonnées GPS) à l’aide des méthodes
static double distanceBetween(double lat1, double long1, double lat2, double long2)
ou
static double courseTo(double lat1, double long1, double lat2, double long2)
Par exemple, connaissant les coordonnées GPS du point précédent, on pourra estimée la distance parcourue entre deux mesures comme ceci
double _distance = gps.distanceBetween(gps.location.lat(), gps.location.lng(), prev_lat, prev_long);Serial.print(« distance = « ); Serial.println(_distance);
Comment créer puis ajouter des données à un fichier GPX ?
Dans l’article “Stocker des données sur une carte micro SD. Code Arduino compatible ESP32, ESP8266”, nous avons vu les méthodes de base de stocker des données sur une carte SD. C’est finalement aussi simple que d’imprimer du texte sur le moniteur série.
Ici, on souhaite ajouter des données à un fichier existant à chaque fois qu’on récupère de nouvelles coordonnées GPS. Pour ajouter un nouveau point au fichier GPX (qui est un format XML), l’astuce consiste à déplacer le pointeur à l’endroit ou l’on souhaite insérer de nouvelles données à l’aide de la fonction seek().
0 ) { float *val_col = (float*)cp[« value »]; if (val_col) today_stats.dist = (float)val_col[0]; today_stats.speed_max = (float)val_col[1]; today_stats.speed_mean = (float)val_col[2]; cp.print(); //Serial.printf(« dist %f | max speed %f | mean speed %f n », today_stats.dist, today_stats.speed_max, today_stats.speed_mean); } else { // Le fichier est probablement corrompu, on le supprime ESP_LOGW(TAG, »Stat file removed because probably corrupted. I’ll be re-saved next time »); SD.remove(filepath_stats); } } } // Créé le fichier GPX du jour vide s’il n’existe pas if ( !SD.exists(filepath_gpx) ) { ESP_LOGI(TAG, « Create new GPX file %s », filepath_gpx); // Create GPX file on startup if not exists // Créé le fichier GPX s’il n’existe pas File gpxFile = SD.open(filepath_gpx, FILE_WRITE); // GPX file header // Entête du fichier GPX if ( gpxFile ) { gpxFile.print(F( « rn » « rn » « trnrn »)); gpxFile.print(F(« rnrnrn »)); gpxFile.close(); } else { ESP_LOGW(TAG, « Impossible to open %s GPX file », filepath_gpx); blink_led_red(); } } else { ESP_LOGI(TAG, « %s file already exists », filepath_gpx); } }/********************************************//* LIT LE MESSAGE RENVOYE PAR LE MODULE GPS *//********************************************/static void readGPS(unsigned long ms){ ESP_LOGI(TAG, « Read GPS stream »); bool led_state = true; unsigned long start = millis(); do { while (gps_uart.available()) gps.encode(gps_uart.read()); //Serial.println( (millis() – start) % 250); if ( (millis() – start) % 250 != 80 ) { led_state = !led_state; } if ( led_state ) M5.dis.drawpix(0,0,0x0099ff); else M5.dis.drawpix(0,0,0); } while (millis() – start 5 km/h, update max/mean speed, distance traveled // Uniquement si la vitesse > 5 km/h, actualise vites moyenne/max, distance parcourue if ( gps.speed.kmph() > 5 ) { // Add new speed point in the buffer // Ajoute la vitesse au buffer tournant permettant de calculer la vitesse moyenne double current_speed = gps.speed.kmph(); today_stats.speedbuffer[today_stats.speedbufferpos] = current_speed; today_stats.speedbufferpos += 1; if ( current_speed > today_stats.speed_max ) today_stats.speed_max = current_speed; if ( today_stats.speedbufferpos >= 10 ) { today_stats.speedbufferpos = 0; today_stats.speedbufferfull = true; } if ( today_stats.speedbufferfull ) { float speed_mean = 0; for (int l = 0; l 0 ) { // Stocke la position actuelle pour la prochaine estimation de distance parcourue today_stats.dist += _distance; updateStatFile(); } prev_lat = gps.location.lat(); prev_long = gps.location.lng(); //ESP_LOGI(TAG, »Distance traveled %d », _distance ); } else { prev_lat = gps.location.lat(); prev_long = gps.location.lng(); } } if ( FORCE_UPDATE_STATS ) updateStatFile(); sprintf(point_date, « %4d-%02d-%02dT%02d:%02d:%02dZ »,gps.date.year(), gps.date.month(), gps.date.day(), gps.time.hour() + TIME_ZONE, gps.time.minute(),gps.time.second()); File gpxFile = SD.open(filepath_gpx, FILE_WRITE); if( !gpxFile ) { ESP_LOGW(TAG, « Impossible to open %s GPX file », filepath_gpx); blink_led_red(); } else { ESP_LOGI(TAG, « Add new point %s », point_date); double _lat = gps.location.lat(); double _lng = gps.location.lng(); double _alt = gps.altitude.meters(); double _hdop = gps.hdop.hdop(); int _sat = gps.satellites.value(); unsigned long filesize = gpxFile.size(); // back up the file pointer to just before the closing tags filesize -= 27; gpxFile.seek(filesize); gpxFile.print(F(« »)); gpxFile.print(F(« »)); gpxFile.print(point_date); gpxFile.println(F(« »)); // Satellites gpxFile.print(F(« »)); gpxFile.print(_sat); gpxFile.println(F(« »)); // Elevation | Altitude gpxFile.print(F(« »)); gpxFile.print(_alt,1); gpxFile.print(F(« rn »)); gpxFile.print(_hdop,3); gpxFile.println(F(« rn »)); gpxFile.print(F(« rnrnrn »)); gpxFile.close(); blink_led_blue(); } }/*********************************//* CREATE OR UPDATE STAT FILE *//* CREE OU ACTUALISE LES STATS *//*********************************/static void updateStatFile(){ File statsFile = SD.open(filepath_stats, FILE_WRITE); if(!statsFile) { ESP_LOGW(TAG, « Impossible to open %s stats file », filepath_stats); } else { ESP_LOGI(TAG, « Add stats data to file »); statsFile.print(F(« key,value,unitrn »)); // Distance parcourue aujourd’hui en mètres statsFile.print(F(« distance, »)); statsFile.print(today_stats.dist); statsFile.print(F(« ,m »)); statsFile.print(F(« rn »)); // Distance parcourue aujourd’hui en km statsFile.print(F(« distance, »)); statsFile.print(today_stats.dist / 1000., 2); statsFile.print(F(« ,km »)); statsFile.print(F(« rn »)); // Vitesse maxi aujourd’hui statsFile.print(F(« speed_max, »)); statsFile.print(today_stats.speed_max); statsFile.print(F(« ,hm/h »)); statsFile.print(F(« rn »)); // Vitesse moyenne aujourd’hui statsFile.print(F(« speed_mean, »)); statsFile.print(today_stats.speed_mean); statsFile.print(F(« ,km/h »)); statsFile.print(F(« rn »)); statsFile.close(); } }static void printGPSData(){ Serial.print(F(« Location: « )); if (gps.location.isValid()) { Serial.print(gps.location.lat(), 6); Serial.print(F(« , »)); Serial.print(gps.location.lng(), 6); Serial.print(F(« , age: »)); Serial.print(gps.location.age(), 6); Serial.print(F(« , hdop: »)); Serial.print(gps.hdop.hdop(), 3); } else { Serial.print(F(« INVALID LOCATION »)); } Serial.print(F( » Date/Time: « )); if (gps.date.isValid()) { Serial.print(gps.date.month()); Serial.print(F(« / »)); Serial.print(gps.date.day()); Serial.print(F(« / »)); Serial.print(gps.date.year()); } else { Serial.print(F(« INVALID DATE »)); } Serial.print(F( » « )); if (gps.time.isValid()) { if (gps.time.hour()
Fichiers générés par le programme Arduino du Tracker
Chaque trame GPS étant horodatée, le programme créé automatiquement un dossier à la date du jour dès qu’on démarre le Tracker GPS. Ce dossier contient le fichier tracker.gpx qui est votre parcours et le fichier stats.csv (pour Comma Separated Values).
20201220 |_ tracker.gpx |_ stats.csv
Les données statistiques sont séparées par une virgule : distance parcourue en mètres, distance parcourue en km, vitesse maxi en km/h, vitesse moyenne en km/h. Les données sont organisées sur trois colonnes (clé, valeur, unité).
key,value,unitdistance,3566.13,mdistance,3.57,kmspeed_max,38.97,hm/hspeed_mean,24.02,km/h
Visualiser un fichier GPX ?
Il existe une multitude de site internets qui permettent de tracer des fichiers GPX et de suivre le dénivelé de la randonnée
Google MapsGPS VisualizerGPX ViewMyGPSFilesMaplorer
Si vous utilisez VSCode (Visual Studio Code) pour développer vos projets ESP32 avec PlatformIO, il existe également deux plugins très pratiques (Geo data Viewer et VSCode Map Preview) qui permettent de visualiser des tracés GPX, KML, GeoJSON, CSV… tout cela sans quitter l’éditeur de code. Pratique !
Importer un tracé GPX sur Google Maps
Pour importer un tracé GPX sur Google Maps, vous aurez besoin de disposer d’un compte et de vous y connecter. Allez ensuite sur Google Map et ouvrez le menu latéral pour accéder à vos adresses.
Allez sur l’onglet Cartes puis Créer une carte en bas du menu
Cliquer sur Importer puis glisser le fichier GPX récupéré sur la carte SD.
Google Map supporte également l’importation des formats CSV, XLSX (Microsoft Excel) et KML.
Le tracé s’affiche immédiatement après la fin du transfert du fichier GPX sur les serveurs de Google.
Afficher un fichier GPX directement dans VSCode avec le plugin Geo data Viewer
Si vous n’aimez pas l’idée d’envoyer vos données personnelles sur les serveurs de Google, vous pouvez très bien visualiser vos fichiers GPX directement dans l’éditeur de code VSCode de Microsoft à l’aide du plugin Geo Data Viewer. Ouvrez le menu plugin pour l’installer.
Pour afficher un fichier GPX (ou un autre format supporté), ouvrez le fichier dans l’éditeur puis convoquez le menu avec la combinaison de touches Ctrl + Alt + M.
Geo Data Viewer ouvre une nouvelle page avec de nombreux outils de visualisation et d’export.
Mises à jour
22/12/2020 Publication de l’article
ESP32, broches GPIO et fonctions associées. I/O, PWM, RTC, I2C, SPI, ADC, DACESP32-CAM. Broches et équipements ESP-EYE, AI Thinker, TTGO T-Camera, M5Stack Timer Camera…ESP32-CAM. Quel modèle choisir ? ESP-EYE, AI Thinker, TTGO T-Camera, M5Stack Timer Camera…M5Stack Atomic GPS. Tracker ESP32 TinyGPS++, export GPX sur carte SD, visualisation sur Google Maps ou VSCodeHome Assistant. Installer le snap sur NAS Synology sur une machine virtuelle Ubuntu