Bonjour,
Je vous présente un projet que j'ai réalisé avec l'aide de @J-M-L.
Sans lui, il ne serait pas ce qu'il est. Je le remercie pour son aide.
https://forum.arduino.cc/t/gps-avec-fichier-gpx-version-c-string/978295
Principe de fonctionnement :
Enregistrer des sessions de sport (marche, course à pied, vélo…) au format GPX dans la mémoire Flash du µC puis transférer le dernier fichier enregistré vers le PC. A l’aide d’une application comme Décathlon Coach par exemple, on peut télécharger le fichier GPX ainsi transféré (il se trouve dans le dossier « téléchargements » du PC) pour visualiser et mémoriser l’activité sportive.
Le matériel :
- Module GPS ;
- Esp 32 ;
- Micro chargeur lipo ;
- Batterie Lipo ;
- Entretoises ;
- Un petit morceau de PCB ;
- Un petit bouton-poussoir ;
- Un mini interrupteur ;
- Un interrupteur à glissière ;
- des câbles Dupont ;
- Des fils électriques, en silicone, flexible ;
- deux résistances de 10k et 22K ;
- un condensateur céramique de 100 nF (0.1 uF) ;
- une boîte en plastique étanche.
Comme vous pouvez le voir, le montage est simple. L'interrupteur en bas à droite permet d'alimenter l'ESP32 avec la batterie Lipo. Le bouton glissière à gauche est relié sur le pin du haut à 3.3v, sur celui du bas à GND et sur celui du milieu à D13 avec une résistance de 22K.
Pour éviter les contraintes de CEM et filtrer les rebonds du bouton poussoir, j'utilise une résistance de pullup externe de 10K et un condensateur de 100 nF comme ceci :

L'ensemble est léger, imperméable et tient dans la main.
c-string c’est quoi :
C’est une chaîne de caractères, un tableau comportant plusieurs données de type char, dont le dernier élément est un caractère nul '\0' non visible. Celui-ci permet d'acter la fin de la chaîne de caractères, la fin du tableau. Les µC ne disposent pas d’une grande quantité de mémoire, la classe String étant trop gourmande, il faut donc privilégier l’utilisation des c-string qui permet au contraire de s’adapter à ce handicap et de l’optimiser.
Tout le programme est architecturé autour de ce concept. Le principe étant de stocker dans un tableau de char les chaînes de caractères que le module GPS génère sous forme de trames. Puis certains éléments constitutifs de ces chaînes sont extraits également sous forme de chaînes afin de pouvoir élaborer un fichier GPX.
Fonctionnement :
Après avoir activé l’alimentation de l’ESP32 par batterie à l’aide de l’interrupteur marche arrêt :
-
Si l’interrupteur à glissière Enregistrement/Transfert est sur la position enregistrement (LOW), la LED_BUILTIN bleue clignote jusqu’à ce que le FIX des satellites soit effectué par le module GPS. A ce moment précis, cette led bleue devient fixe jusqu’à ce qu’un appui soit effectué sur le bouton poussoir. Dés lors, elle s’éteint et l’enregistrement de la session de sport commence. Un nouvel appui sur le BP arrête l’enregistrement et rallume la led. A ce stade, il convient d’éteindre le µC.
-
Si l’interrupteur Enregistrement/Transfert est sur la position Transfert (HIGH), le réseau wifi est activé, le serveur est initialisé. Pendant environ 10 secondes la led bleue devient fixe et le transfert du dernier fichier GPX est effectué vers l’ordinateur. A ce stade, il convient d’éteindre le µC et de se repositionner en mode enregistrement pour une nouvelle session.
Le principe d’une machine à état est utilisé pour mettre en œuvre ces deux processus bien distincts. L’interrupteur à glissière identifie le mode et la machine à état oriente le déroulement de chacun d’entre eux. Chaque changement de position de cet interrupteur se fait hors alimentation de l’ESP32.
Si trois fichiers ont été enregistrés dans la mémoire Flash (const byte nbFichiersMax = 3;), ils sont supprimés définitivement et trois autres fichiers seront générés successivement les uns après les autres au cours de chaque nouvelle session. Les fichiers doivent systématiquement être téléchargés sur l’ordinateur après chaque session afin qu’ils soient sauvegardés dans le répertoire « téléchargements » de l’ordinateur.
Les Trackpoints sont enregistrés toutes les 3 secondes ( const int PROGMEM sample = 3000;).
Le code :
Le module GPS génère des chaînes de caractères ASCII sur le deuxième port série de mon ESP32 (RX2 – TX2), ce sont des trames ou sentences ou encore phrases NMEA.
Exemples de phrases NMEA :
$GPRMC,161957.00,A,4654.95725,N,00035.52878,E,0.271,,020322,,,A71
$GPVTG,,T,,M,0.271,N,0.503,K,A21
$GPGGA,161957.00,4654.95725,N,00035.52878,E,1,06,1.52,54.6,M,46.9,M,,64
$GPGSA,A,3,26,29,31,16,25,23,,,,,,,2.98,1.52,2.560A
$GPGSV,3,1,10,04,04,292,13,05,15,047,14,09,02,324,,16,43,303,267C
$GPGSV,3,2,10,23,10,139,25,25,09,125,16,26,80,282,34,27,20,262,78
$GPGSV,3,3,10,29,32,066,20,31,33,200,2674
$GPGLL,4654.95725,N,00035.52878,E,161957.00,A,A6D
Le programme n’utilise que trois types de sentences (GPRMC, GPGGA et GPGSA).
Toutes les trames NMEA commencent par le caractère $ et se termine par un retour chariot ‘\r’ puis par un retour à la ligne ‘\n’. Ce sont ces délimiteurs qui permettent de les isoler sous la forme d’un tableau de char, une par une, après leur génération successive, de les identifier avec le code et de les traiter.
Voici donc grossièrement et sans entrer dans les détails le principe de fonctionnement du code :
Globalement le programme commence par trier les trames générées par le module GPS qui nous intéressent, puis en extrait les éléments utiles à la constitution du fichier GPX.
Les phrases NMEA sont stockées dans le tableau de char message : char message[tailleMessageMax + 1];
Dés la mise sous tension, si l’interrupteur à glissière est placé sur le mode enregistrement, la procédure traiterMessage() commence le trie entre les phrases GPRMC, GPGGA et GPGSA. Ce trie s’effectue uniquement :
1/ Si une trame a été reçue à l’aide de la fonction booléenne messageRecu() qui renvoie true si elle rencontre le caractère ‘\n’ (détermine la fin d’une sentence). Dans le cas contraire chaque caractère est stocké dans le tableau de char message et la fonction renvoie false jusqu’à ce qu’elle rencontre un retour à la ligne ;
2/ Si le calcul de la somme de contrôle mentionné dans la trame est valide (Voir la fonction booléenne phraseOK(const char *unePhrase)). Le checksum c’est la représentation de deux caractères hexadécimaux d’un XOR de tous les caractères d’une trame entre (mais sans les inclure) le caractère $ et le caractère *. La fonction vérifie donc entre autre l’adéquation entre le checksum inclus dans la phrase et le calcul qu’elle refait while (*ptr != '*') cksum ^= *ptr++;. Cette comparaison s’effectue ainsi : (cksum == ckControl ).
La procédure traiterMessage() sélectionne les trames qui nous intéressent à l’aide de la fonction strncmp puis les transmet à la procédure extraire(char *message, trameType typeRequete) où les variables utiles à l’intérieur des sentences sont elles mêmes stockées dans un autre tableau de char : char *variablesGpx[83]; ce sont des bouts de chaînes extirpés entre les virgules à l’aide de la fonction strsep. Elles sont ensuite affectées à d’autres variables de type char comme heures, dates, sat, alt … Certaines sont utilisées telles quelles, d’autres sont transmises à des procédures ou fonctions pour traitement :
void timeGpx(char *lesDates, char *lesHeures ) formate les chaînes *dates et *heures au format GPX.
char* conversionDecimal (char *latlong, boolean lati) retourne la latitude ou la longitude en degrés décimaux.
Les variables globales extraites des sentences sous la forme de tableaux de char et constitutives du fichier GPX sont donc alimentées de manière permanente en données actualisées par les fonctions et procédures à chaque passage dans la loop :
char latitu [18], longitu[18];
char alti[18], sate[18], hdopss[18];
char dateHeure[25];
A l’aide de la librairie LITTLEFS.h, les éléments constitutifs d’un fichier GPX sont enregistrés toutes les trois secondes dans la mémoire flash de mon ESP32.
Au retour d’une séance de sport les fichiers sont transmis à mon PC avec l’aide de la librairie ESPAsyncWebServer.h.
Les fichiers GPX sont au format XML, il est aisé de les créer avec les procédures :
void initGPX(void); //Initialisation du fichier GPX
void addTrackpt(void); // ajout d'un point de cheminement
void completeGPX(void); // fin de la structure GPX
Voici le code complet :
/* Selon le travail de JML https://forum.arduino.cc/t/gps-avec-fichier-gpx-version-c-string/978295/31
et de Philipp Biedenkopf https://github.com/PBiedenkopf/ES-Arduino-GPX-datalogger?msclkid=c74329f6a6ad11ecb926df952f2de52d
*/
#include <LITTLEFS.h>
#include <WiFi.h>
#include "ESPAsyncWebServer.h"
#define FORMAT_LITTLEFS_IF_FAILED true
const byte RXD2 = 16;
const byte TXD2 = 17;
const byte tailleMessageMax = 82;
char message[tailleMessageMax + 1]; // +1 car on doit avoir un caractère de fin de chaîne en C, le '\0'
const char marqueurDeFin = '\n';
char *variablesGpx[83];
char latitu [18], longitu[18];
char alti[18], sate[18], hdopss[18];
char dateHeure[25];
boolean reception = false; // fix des satellites
// Défini une adresse IP statique
IPAddress local_IP(196, 161, 2, 56);
// Défini l’adresse IP de la passerelle
IPAddress gateway(122, 178, 0, 2558);
// Défini le masque de sous-réseau
IPAddress subnet(157, 25, 556, 23);
//WIFI
const char* ssid = "xxxxx";
const char* password = "123456";
//Nom de Fichier;
int fileID = 0;
char GPXFILE[13];
const byte nbFichiersMax = 3; // défini le nombre de fichiers maximum
// Timer - rythme l'ajout des Trackpoints et le clignotement au départ
unsigned long t_start; // timer pour trkpt - led
unsigned long t_stop; // timer pour trkpt - led
const int PROGMEM sample = 3000; // rythme enregistrement Trkpt
// objets pour le bouton,l'interrupteur, la led, serveur
const uint8_t buttonPin = 14;
const uint8_t pinInterrupteur = 13;
const uint8_t ledPin = LED_BUILTIN;
boolean ledStatut = LOW;
bool b_gpx_tracking = false; // indicateur pour démarrer l'enregistrement gpx
bool b_gpx_init = false; // indique si le fichier gpx est initialisé
AsyncWebServer server(80); // serveur
enum trameType : byte {gprmc = 13, gpgga = 15, gpgsa = 18};
enum {TRANSFERT, FIN_TRANSFERT, ARRET, NOFIX, FIXSAT, EN_ATTENTE} etatCourant; // machine à état
// Déclaration des fonctions et procédures
void initGPX(void); //Initialisation du fichier GPX
void addTrackpt(void); // ajout d'un point de cheminement
void completeGPX(void); // fin de la structure GPX
bool messageRecu();
bool phraseOK(const char *unePhrase);
void traiterMessage();
void extraire(char *message, int types);
void timeGpx(char *lesDates, char *lesHeures );
char* conversionDecimal (char *latlong, boolean lati);
int NombreChiffreAVir (double ChiffreDec);
void initReseauWifi();
void supprime();
void setup() {
Serial.begin(115200); Serial.println();
Serial2.begin(9600, SERIAL_8N1, RXD2, TXD2); // initialise la liaison RX-TX sur espressif WROOM 32
// initialise LITTLEFS
if (!LITTLEFS.begin(FORMAT_LITTLEFS_IF_FAILED)) {
Serial.println("ERREUR LITTLEFS ");
return;
}
// initialise boutton start et ledPin
pinMode(buttonPin, INPUT); //PULLUP externe 10k + condo 100 nF
pinMode(pinInterrupteur, INPUT); //pin haut 3.3v - pin milieu D13 + R22K - pin bas gnd
pinMode(ledPin, OUTPUT);
t_stop = 0;
// machine à état
etatCourant = TRANSFERT;
}
void loop() {
if (digitalRead(pinInterrupteur) == LOW) traiterMessage(); // call back machine à état - on ne traite qu'en mode GPS
switch (etatCourant) {
case TRANSFERT:
if (digitalRead(pinInterrupteur) == HIGH) {
initReseauWifi();
etatCourant = FIN_TRANSFERT;
}
else if (digitalRead(pinInterrupteur) == LOW) {
etatCourant = NOFIX;
}
break;
case FIN_TRANSFERT:
server.end();
WiFi.disconnect(true);
WiFi.mode(WIFI_OFF);
Serial.print("FIN DU TRANSFERT");
etatCourant = ARRET;
break;
case ARRET:
Serial.println("FIN DU PROGRAMME");
break;
case NOFIX: // pas de fix
t_start = millis();
if ((t_start - t_stop) > 500) {
t_stop = t_start;
ledStatut = !ledStatut;
digitalWrite(ledPin, ledStatut);
}
if (reception) etatCourant = FIXSAT;
break;
case FIXSAT:
if (reception && !b_gpx_init) {
digitalWrite(ledPin, HIGH); // On allume la led
etatCourant = EN_ATTENTE;
}
break;
case EN_ATTENTE :
if (digitalRead(buttonPin) == LOW && Serial2.available() > 0) {
if (!b_gpx_tracking) {
// Défini un nom de fichier non existant
while (1) {
sprintf(GPXFILE, "/track%02d.gpx", fileID);
if (!LITTLEFS.exists(GPXFILE)) {
break;
}
else {
fileID++;
}
}
if (fileID == nbFichiersMax) {
supprime();
fileID = 0;
}
sprintf(GPXFILE, "/track%02d.gpx", fileID);
Serial.print(F("Current gpx file: ")); Serial.print(GPXFILE); Serial.print("\n");
initGPX(); // initialise l'entête du fichier GPX
b_gpx_tracking = true;
b_gpx_init = true; // le fichier GPX est initialisé
digitalWrite(ledPin, LOW);
//delay(300);
}
else {
b_gpx_tracking = false;
digitalWrite(ledPin, HIGH);
//delay(300);
}
while (digitalRead(buttonPin) == LOW) {
delay(50);
}
}
if (Serial2.available() > 0) {
// Si les données data sont disponibles via la connection serie
// Enregistre uniquement si le délais est atteint et si le fix est effectif
if (millis() - t_start > sample) {
// enregistre un nouveau Trackpt
if (b_gpx_tracking) {
if (reception) {
addTrackpt();
}
}
//Complete le fichier GPX si l'enregistrement est arrêté aprés avoir été executé une fois
if (b_gpx_init && !b_gpx_tracking) {
completeGPX();
b_gpx_init = false; // le fichier actuel est terminé et n’est plus initialisé
digitalWrite(ledPin, HIGH); // éteint la led
}
// le timer est réinitialisé pour une prochaine itération
t_start = millis();
}
}
//break; pas de break car on reste dans l'attente permanaente de l'enregistrement de données ou on éteint tout
} // fin switch
}
//=========================================== Initialise la structure du fichier gpx
void initGPX() {
Serial.println(F("Initializing GPX file ..."));
File file = LITTLEFS.open(GPXFILE, FILE_WRITE);
if (file) {
// header
file.print(F("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\" ?>\n"));
// start of gpx file body
file.print(F("<gpx version=\"1.1\" creator=\"P. Biedenkopf\" xmlns=\"http://www.topografix.com/GPX/1/1\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n"));
// metadata
file.print(F(" <metadata>\n"));
file.print(F(" <name>")); file.print(GPXFILE); file.print(F("</name>\n"));
file.print(F(" <desc>WROOM 32 avec RX2 TX2</desc>\n"));
//gpx.print(F(" <author>\n"));
//gpx.print(F(" <name>P. Biedenkopf</name>\n"));
//gpx.print(F(" <email>ph.bied13@gmail.com</email>\n"));
//gpx.print(F(" </author>\n"));
file.print(F(" </metadata>\n"));
// start track and track segment (only single segmented tracks possible)
file.print(F(" <trk>\n"));
file.print (F(" <name>")); file.print(GPXFILE); file.print(F("</name>\n"));
file.print(F(" <desc>WROOM 32 avec RX2 TX2</desc>\n"));
file.print(F(" <trkseg>\n"));
file.close();
}
else {
Serial.println(F("Erreur ouverture gpx-file"));
}
}
//================================================ Création d'un trackpoint dans la syntaxe gpx
void addTrackpt() {
Serial.println(F("Ajoute un Trackpoint ..."));
File file = LITTLEFS.open(GPXFILE, FILE_APPEND);
if (file) {
file.print(F(" <trkpt lat=\""));
file.print(latitu);
file.print(F("\" lon=\""));
file.print(longitu);
file.println(F("\">"));
file.print(F(" <ele>")); file.print(alti); file.print(F("</ele>\n"));
Serial.println(dateHeure); // test
file.print(F(" <time>")); file.print(dateHeure); file.print(F("</time>\n"));
file.print(F(" <sat>")); file.print(sate); file.print(F("</sat>\n"));
file.print(F(" <hdop>")); file.print(hdopss); file.print(F( "</hdop>\n"));
file.print(F(" </trkpt>\n"));
file.close();
}
else {
Serial.println(F("Erreur ouverture fichier gpx"));
}
}
//======================================================= Fin de la structure GPX
void completeGPX() {
Serial.println(F("Completing GPX file ..."));
File file = LITTLEFS.open(GPXFILE, FILE_APPEND);
if (file) {
// Ferme le parcours enregistré, le segment et le corps du fichier gpx
file.print(F(" </trkseg>\n"));
file.print(F(" </trk>\n"));
file.print(F("</gpx>\n"));
file.close();
}
else {
Serial.println(F("Erreur ouverture fichier gpx"));
}
}
//====================================================================================
bool messageRecu() {
static byte indexMessage = 0;
int r = Serial2.read();
if (r != -1) { // on a reçu un caractère
if (r == marqueurDeFin) {
message[indexMessage] = '\0'; // on termine la c-string
indexMessage = 0; // on se remet au début pour la prochaine fois
return true;
} else {
if ((r != '\r') && (indexMessage < tailleMessageMax))
message[indexMessage++] = (char) r; // on stocke le caractère et on passe à la case suivante
}
}
return false;
}
//================================================
bool phraseOK(const char *unePhrase) {
byte cksum = 0;
if (*unePhrase != '$') return false; // une phrase commence toujours par $
const char* starPtr = strchr(unePhrase, '*');
if (starPtr != nullptr) { // on a une étoile donc un CKSUM
const char *ptr = unePhrase + 1;
while (*ptr != '*') cksum ^= *ptr++;
char *endPtr;
unsigned long ckControl = strtoul (starPtr + 1, &endPtr, 16);
return (*endPtr == '\0') && (cksum == ckControl);
}
return false; // pas d'étoile, donc pas de checksum à vérifier (ce qui est OK dans certains cas mais ici toutes nos phrases sont attendues avec un check sum)
}
//========================================================================
void traiterMessage() {
if (messageRecu() && phraseOK(message) ) {
if (strncmp(message, "$GPRMC", 6) == 0) {
extraire(message, gprmc);
}
else if (strncmp(message, "$GPGGA", 6) == 0) {
extraire(message, gpgga);
}
else if (strncmp(message, "$GPGSA", 6) == 0) {
extraire(message, gpgsa);
}
}
}
//============================================================================
void extraire(char *message, trameType typeRequete) { // extrait les champs utiles des phrases GPRMC - GPGGA - GPGSA
int nbVirgule = 0;
char *valide;
char *heures, *dates;
char *latit, *longit;
char *alt, *sat, *hdops;
char *fixSat;
char* champNmea;
for (int i = 0; i < typeRequete; i++) {
champNmea = strsep(&message, ",");
variablesGpx[nbVirgule++] = champNmea;
}
if (typeRequete == gprmc) { //GPRMC
heures = variablesGpx[1];
valide = variablesGpx[2];
latit = variablesGpx[3];
longit = variablesGpx[5];
dates = variablesGpx[9];
if (*valide == 'A') { // Statut: A = données valides, V = données non valides - 3eme champ des phrases NMEA de type GPRMC
if (*heures != '\0' && *dates != '\0') { // on ne transmet pas de champs vides à la fonction timeGpx
timeGpx(dates, heures);
}
if (*latit != '\0' && *longit != '\0') { // on ne transmet pas de champs vides à la fonction conversionDecimal
conversionDecimal(latit, 1);
conversionDecimal(longit, 0);
}
} // si statut vaut A
}
else if (typeRequete == gpgga) { //GPGGA
fixSat = variablesGpx[6];
sat = variablesGpx[7];
alt = variablesGpx[9];
//Serial.print(" fixSat : "); Serial.println(fixSat);
//atoi(fixSat) > 0 ? reception = true : reception = false; // fix des satellites
(*fixSat == '1') ? reception = true : reception = false; // fix des satellites
if (*sat != '\0' && atof(sat) != 0) sprintf(sate, "%s", sat); // on ne traîte pas de champs vides et de valeur égale à 0
if (*alt != '\0' && atof(alt) != 0) sprintf(alti, "%s", alt); // on ne traîte pas de champs vides et de valeur égale à 00
}
else if (typeRequete == gpgsa) { //GPGSA
hdops = variablesGpx[16];
if (*hdops != '\0' && atof(hdops) != 99.99 && atof(hdops) != 0) sprintf(hdopss, "%s", hdops); // on ne traîte pas un champ vide et de valeur égale à 0 et de valeur = 99.99
}
}
//=======================================Formate la date et l'heure au format GPX
void timeGpx(char *lesDates, char *lesHeures ) {
sprintf(dateHeure, "20%.2s-%.2s-%.2sT%.2s:%.2s:%.2sZ", &lesDates[4], &lesDates[2], &lesDates[0], &lesHeures[0], &lesHeures[2], &lesHeures[4]);
}
//=======================================Retoune la latitude ou la longitude en degres decimal
char* conversionDecimal (char *latlong, boolean lati) {
char degres[4];
char minut [9];
if (lati) {
strlcpy ( degres, &latlong[0], 3);
strlcpy ( minut, &latlong[2], sizeof minut );
}
else
{
strlcpy ( degres, &latlong[0], sizeof degres );
strlcpy ( minut, &latlong[3], sizeof minut );
}
double resultat = ((atoi(degres)) + ((atof(minut) / 60)));
return lati > 0 ? dtostrf(resultat, 0, NombreChiffreAVir(resultat), latitu) : dtostrf(resultat, 0, NombreChiffreAVir(resultat), longitu);
}
//=======================================supprime les '0' après la virigule lors de la conversion de la latitude ou de la longitude en degres decimal, retourne le nombre de chiffres après la virgule
int NombreChiffreAVir (double ChiffreDec) { // retourne le nombre de chiffres après la vrigule d'un nombre décimal - précision sur 13 chiffres
int nb = 0;
char tmpBuffer[21];
char *recherchePoint = strchr(dtostrf(ChiffreDec, 0, 13, tmpBuffer), '.');
if (recherchePoint == nullptr) return 0;
for (int f = strlen(tmpBuffer) - 1; f >= 0; --f)
if (tmpBuffer[f] == '0') nb++;
else break;
return 13 - nb;
}
//===============================Initialise le reseau Wifi et le serveur
void initReseauWifi () {
// Quel est le dernier fichier ?
while (1) {
sprintf(GPXFILE, "/track%02d.gpx", fileID);
if (!LITTLEFS.exists(GPXFILE)) {
break;
}
else {
fileID++;
}
}
sprintf(GPXFILE, "/track%02d.gpx", fileID - 1); // il est là
// wifi IP statique
if (!WiFi.config(local_IP, gateway, subnet)) {
Serial.println("erreur de configuration STA");
}
// WiFi
Serial.print("Connexion à ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("WiFi connecté");
Serial.println("addresse IP : ");
Serial.println(WiFi.localIP());
//serveur
server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
request->send(LITTLEFS, GPXFILE, "text/xml", true);
});
server.on("/interpret", HTTP_GET, [](AsyncWebServerRequest * request) {
request->send(LITTLEFS, GPXFILE, "text/xml", false);
});
server.begin();
digitalWrite(ledPin, HIGH);
delay(10000);
digitalWrite(ledPin, LOW);
}
void supprime() { //===============Supprime les 'nbFichiersMax' fichiers
for (byte i = 0; i < nbFichiersMax; i++) {
sprintf(GPXFILE, "/track%02d.gpx", i);
if (LITTLEFS.remove(GPXFILE)) {
Serial.println("- Fichier supprimé");
} else {
Serial.println("- Fichier non supprimé");
}
}
}
L'autonomie du GPS de sport est de 5 heures. Je cours 4 fois 1 heure par semaine. Je le recharge donc une fois par semaine avec le micro chargeur lipo sur le montage (j'ai soudé le cavalier de ce chargeur pour un courant de charge à 500 mA).


