Connexion du module qui est monté sur shield RS232: * fil vert = broche 3 (TX gps) du connecteur DB9 male * fil noir = broche 5 (GND) du connecteur DB9 male ====Solution pour le test de la fonction de parsing de flottants==== [[tpethindus2soluce]] ====Solution pour le projet complet, y compris arduino et simulateur python==== https://bvdp.inetdoc.net/files/iut/tp_gps/TPGPS_soluce.zip ====Documentation module récepteur gps ublox neo 6M==== avec utilisation de librairie TinyGPS++: http://gilles.thebault.free.fr/spip.php?article50 ====Documentation module récepteur gps skm53==== http://www.skylab.com.cn/en/productview-82.html https://www.nooelec.com/files/SKM53_Tutorial.pdf ====Format de trames NMEA 183==== https://fr.wikipedia.org/wiki/NMEA_0183 http://www.cedricaoun.net/eie/tp-prog-gps_v1.2.pdf ====Convertisseur LAT/LONG UTM==== https://www.latlong.net/lat-long-utm.html ====Gestion de l'heure==== http://leapsecond.com/java/gpsclock.htm ====Parsing en Python (mais semble foireux pour la checksum)==== https://raspberry-pi.developpez.com/cours-tutoriels/projets-rpi-zero/traceur-gps/ {{https://bvdp.inetdoc.net/files/iut/tp_pic/warning.jpeg}} A FAIRE A LA FIN DE CHAQUE SEANCE POUR COPIER VOS FICHIERS SUR UN DISQUE PARTAGE, ET PERMETTRE LA RECUPERATION DES FICHIERS SI UN MEMBRE DU BINOME EST ABSENT, SINON TANT PIS POUR VOUS! copier coller dans une console: rsync -av --delete ~/TPGPS /mnt/etu/s4 et compléter la ligne en appuyant 3 fois sur la touche Tabulation, puis entrée. =====TP COMMUNICATION: Décodage de trame au format NMEA d'un GPS.===== === Objectifs === * Interprétation et décodage d'une trame ASCII * Extraction et manipulation d'information * Vérification de la validité de la trame * Utilisation de FIFO * Démarche de développement avec test sur PC avant portage sur microcontroleur ====Sujet de TD==== [[tdethindus1distanciel]] ancien: https://bvdp.inetdoc.net/files/iut/tp_gps/TD_GPS_v6_2018.pdf =====Présentation du matériel et du projet===== Dans ce TP, vous devrez programmer une carte ARDUINO pour qu'elle décode les trames émises par un module récepteur GPS. Le module GPS pourra, pour faciliter le développement être remplacé par un simulateur matériel ou même par le port série du PC. Dans un premier temps, le développement sera réalisé intégralement sur PC en utilisant QTCreator puis le code sera compilé pour l'Arduino. ===Travail à réaliser=== En vous basant sur l'analyse du format de la trame réalisée en TD, vous devez construire un programme permettant de décoder avec le microcontrôleur les trames GPS reçues sur l'UART émulé (soft) via les broches 5 et 6 et de les afficher sur l'écran d'un PC à l'aide de l'uart intégré (hard). Le format d'affichage n'est pas imposé mais vous devrez faire apparaître l'heure courante, la longitude et la latitude. Le programme principal devra être construit avec uniquement des fonctions non bloquantes. Vous devez vous référer au sujet du TD de communication sur le GPS pour les détails. Le modèle de machine à états du parser de trame est visible sur: https://bvdp.inetdoc.net/files/iut/tp_gps/MAE_parseGps.svg https://bvdp.inetdoc.net/files/iut/tp_gps/MAE_parseGps.pdf ===Démarche de développement=== Nous utiliserons QTcreator pour développer les fonctions de Stream Parsing (décodage par flux) afin de disposer du Debugguer. Ceci permet de tester le programme en pas à pas tout en suivant l'évolution de la valeur des différentes variables. Dans un premier temps, vous devez créer un projet avec QTcreator. Pour cela lancer l'outils (ALT+F2 et taper qtcreator). Ensuite, cliquer sur créer un projet, choisir "Applications" et sélectionner "QT console Application" et cliquer sur choisir. Donner ensuite un nom au projet (saisir exactement "TPGPS" en majuscule) et laisser l'emplacement par défaut (votre dossier /home/IUT/login/TPGPS) et cliquer sur "Suivant","Suivant" et "Terminer". Dans le fichier **main.cpp** commenter les 2 lignes: //QCoreApplication a(argc, argv); //return a.exec(); Puis décrire les variables et sous programmes dont vous aurez besoin. Pour rappel, l'utilisation de qtcreator est décrite sur la page: [[tpqt]]. struct gps_data { char valid ; //trame valide float time ; //champ time de la trame float lat ; //champ latitude de la trame float lon ; //champ longitude de la trame unsigned char received_checksum; }; Compiler et exécuter ce programme vide. ==Initialisation du système de gestion de version en local== POUR LA PREMIERE SEANCE UNIQUEMENT!, Copier/coller dans une console: echo commence cd ~/TPGPS git init git add *.c* *.pro git commit -m'initial version' gitk & echo fini Vous devez voir une application graphique s'ouvrir et afficher les fichiers suivis, sinon appeler l'enseignant. Durant le TP, jusqu'à l'exercice 4 inclus, vous travaillerez dans le fichier **main.cpp**, à ne pas sauver avec des noms différents car le fichier est suivi par l'outils de suivi. ===Notice d'utilisation de l'outils QTCreator=== Une fois le projet ouvert, l'utilisateur peut choisir entre plusieurs mode d'affichages, notamment "Editer" et "Debogage" à l'aide des boutons sur la gauche de la fenêtre. La fenêtre Projets fait apparaître les fichiers du (des) projet(s) ouvert(s), triés selon leur type. Sur la droite, la fenêtre affiche le contenu d'un fichier sélectionné. **CTRL+Click souris gauche** sur un mot du programme permet de se déplacer à la déclaration d'une variable, d'un objet, d'une fonction, d'un type ou autre. Cela sera très utile pour connaître par exemple les méthodes utilisables sur un objet d'une certaine classe. **CTRL+B** permet de compiler l'application, la fenêtre Sortie de compilation permet alors d'observer les éventuelles erreurs, sur lesquelles vous pourrez cliquer pour aller à la ligne correspondante dans le code. **CTRL+R** permet d’exécuter le programme. Le programme devra être fermé en cliquant sur le bouton croix de la fenêtre avant de le recompiler ou de l’exécuter à nouveau. **F5** permet d'exécuter le programme en mode DEBUG. L'utilisateur passe alors en mode Debug, ce qui ajoute à l'affichage une zone à droite dans laquelle l'utilisateur pourra observer les variables et leurs valeurs. L'utilisateur pourra placer ou retirer des points d'arrêt en cliquant à gauche du numéro de ligne d'un fichier. Il pourra exécuter en pas à pas sans rentrer dans les fonctions à l'aide de **F10** ou bien en entrant dans les fonctions à l'aide de **F11**. Il pourra ajouter une variable particulière à la liste des variables affichées en Debug en selectionant le nom de la variable dans le fichier programme et en cliquant droit dessus puis clic gauche sur **ajouter un évaluateur d'expression**. -------------------------------------------------------------------------------------------------------- =====Exercice 1: Test de la fonction de décodage des nombres flottants===== La fonction de décodage des nombres flottants est fournie: char parseFloatField(char c, float * ptr_val, unsigned char * ptr_count, unsigned char * ptr_dp_count) //La fonction renvoie 0 tant que le décodage n'est pas terminé // 1 lorsque le décodage est terminé correctement // -1 lorsque le décodage a échoué { if (c >= '0' && c <= '9') { (*ptr_val) *= 10; (*ptr_val) += c - '0'; (*ptr_count) = (*ptr_count) + 1; return 0; } else if (c == '.') { (*ptr_count) = 0; (*ptr_dp_count) ++ ; return 0; } else if (c == ',') { while ((*ptr_count) > 0 && (*ptr_dp_count) > 0) // équiptr_valent à (*ptr_val) = (*ptr_val)/(10^*count) { (*ptr_val) = (*ptr_val) / 10; (*ptr_count) = (*ptr_count) - 1; } if((*ptr_dp_count) > 1) return -1 ; (*ptr_count) = 0 ; (*ptr_dp_count) = 0 ; return 1; } else // caractère non supporté dans un nombre { (*ptr_count) = 0; return -1; } } Pour la tester, vous pourrez utiliser des chaînes de constantes définies comme celles ci: const char chaine1[10]="123.456,\0"; const char chaine2[10]="789,\0"; const char chaine3[10]=".45,\0"; const char chaine4[10]=".,\0"; Vous devrez également tester le comportement de la fonction lorsqu'elle est alimentée par des chaînes non valides telles que: const char chaine5[20]="123.456.789,\0"; const char chaine6[10]="78a9,\0"; Vous n'aurez donc pas besoin dans un premier temps de vous occuper de l'UART et de la communication à proprement parler. Le programme principal devra juste appeler la fonction de décodage de flottant en lui envoyant successivement le contenu des différentes cases du tableau. La fin du décodage est détectée grâce à la valeur retournée par la fonction qui doit être différente de 0. Il faut également veiller à ne pas dépasser le contenu du tableau, la fin de la chaîne étant indiquée par un caractère '\0'. {{https://bvdp.inetdoc.net/files/iut/tp_gps/TODO.jpg}} Tester la fonction sur différentes chaînes (en utilisant le mode Debug et en changeant le nom de la chaîne testée à chaque exécution, ne vous embêtez pas à faire un long programme qui traite toutes les chaînes à la suite) {{https://bvdp.inetdoc.net/files/iut/tp_gps/TODO.jpg}} Tester la fonction sur la chaîne suivante et proposer une amélioration pour que la valeur décodée dans ce cas ne soit pas 0 mais la valeur NAN, qui indique "Not A Number", ce qui permettra de différencier les cas où un champ vaut 0 et les cas où les champs ne sont pas renseignés: const char chaine7[10]=",\0"; Pour pouvoir utiliser NAN, vous devrez ajouter au début de votre fichier: #include Solution: il suffit d'ajouter au bon endroit: if ( ((*ptr_count) ==0) && ((*ptr_dp_count) ==0) ) { (*ptr_val) =NAN; return 1; } char parseFloatField(char c, float * ptr_val, unsigned char * ptr_count, unsigned char * ptr_dp_count) //La fonction renvoie 0 tant que le décodage n'est pas terminé // 1 lorsque le décodage est terminé correctement // -1 lorsque le décodage a échoué //val est mis à NAN si la chaine décodée est "," { if (c >= '0' && c <= '9') { (*ptr_val) *= 10; (*ptr_val) += c - '0'; (*ptr_count) = (*ptr_count) + 1; return 0; } else if (c == '.') { (*ptr_count) = 0; (*ptr_dp_count) ++ ; return 0; } else if (c == ',') { if ( ((*ptr_count) ==0) && ((*ptr_dp_count) ==0) ) { (*ptr_val) =NAN; // faire un #include return 1; //OK } while ((*ptr_count) > 0 && (*ptr_dp_count) > 0) // équiptr_valent à (*ptr_val) = (*ptr_val)/(10^*count) { (*ptr_val) = (*ptr_val) / 10; (*ptr_count) = (*ptr_count) - 1; } if((*ptr_dp_count) > 1) return -1 ; (*ptr_count) = 0 ; (*ptr_dp_count) = 0 ; return 1; } else // caractère non supporté dans un nombre { (*ptr_count) = 0; return -1; } } {{https://bvdp.inetdoc.net/files/iut/tp_pic/validation.png}} Une fois les réponses validées, mettre à jour le suivi de version en saisissant dans une console: echo commence cd ~/TPGPS git commit -a -m'parsing flottant' gitk & echo fini -------------------------------------------------------------------------------------------------------- =====Exercice 2: Décodage simple de trame GPRMC===== {{https://bvdp.inetdoc.net/files/iut/tp_gps/TODO.jpg}} Implémenter la fonction de décodage de trame **char parseGPS(char c, struct gps_data * dataP) ** **sans tenir compte de la somme de contrôle (Checksum)**. Vous pourrez utiliser des chaines de constantes définies comme celle ci: const char chaine1[100]="$GPRMC,154936.000,A,4338.6124,N,00126.7337,E,0.26,326.08,200113,,,A*60\r\n\0"; Pour cela vous implémenterez la machine à états décrite en TD. Cette machine à états est cadencée par chaque caractère à traiter. Vous êtes libre d'implémenter la MAE de manière simplifiée. La fonction **parseGPS** doit retourner: 0 lorsque le décodage est en cours 1 lorsque le décodage est terminé correctement -1 lorsque le décodage n'a pas pu aboutir Il est vivement conseillé de coder et de tester au fûr et à mesure (par exemple à chaque nouvel état) grâce à l'outils de Debug, plutôt que de tout coder et de se rendre compte à la fin des erreurs. Pour cela, à chaque nouvelle étape, vous pourrez positionner un point d'arrêt dans le programme juste à le fin de ce qui a déjà été testé, et exécuter en pas à pas uniquement les nouvelles parties de code. Vous veillerez également à vérifier que TOUTES les transitions implémentées sont fonctionnelles, en générant des erreurs dans la chaîne de test (par exemple, changer GPRMC en GPRTC pour vérifier si la MAE revient bien à l'état SYNC). Compléter le fichier main.cpp fourni ci dessous. #include #include #include "stdio.h" //const char chaine1[10]="123.456,\0"; const char chaine2[10]="789,\0"; const char chaine3[10]=",\0"; const char chaine1[100]="$GPRMC,154936.000,A,4338.6124,N,00126.7337,E,0.26,326.08,200113,,,A*60\r\n\0"; const char chaine4[200]="$GPRMC,154936.000,A,4338.6124,N,00126.7337,E,0.26,326.08,200113,,,A*60\r\n$GPRMC,154936.000,A,4338.6124,N,00126.7337,E,0.26,326.08,200113,,,A*60\r\n\0"; struct gps_data { char valid ; //trame valide float time ; //champ time de la trame float lat ; //champ latitude de la trame float lon ; //champ longitude de la trame unsigned char received_checksum; }; unsigned char computed_checksum=0; // variable de calcul du checksum char parserState=0; //etat courant du parser unsigned char counter= 0; unsigned char dp_counter=0; #define SYNC 0 #define HEADER 1 #define TIME 2 #define VALID 3 #define LAT 4 #define LAT_DIR 5 #define LONG 6 #define LONG_DIR 7 #define IGNORE 8 #define CHECKSUM 9 char header[6]="GPRMC"; gps_data gps_d; //structure de stockage de la trame ///////////////////////////////////////////////////////////////////////////////////// char parseFloatField(char c, float * ptr_val, unsigned char * ptr_count, unsigned char * ptr_dp_count) //La fonction renvoie 0 tant que le décodage n'est pas terminé // 1 lorsque le décodage est terminé correctement // -1 lorsque le décodage a échoué { if (c >= '0' && c <= '9') { (*ptr_val) *= 10; (*ptr_val) += c - '0'; (*ptr_count) = (*ptr_count) + 1; return 0; } else if (c == '.') { (*ptr_count) = 0; (*ptr_dp_count) ++ ; return 0; } else if (c == ',') { while ((*ptr_count) > 0 && (*ptr_dp_count) > 0) // équiptr_valent à (*ptr_val) = (*ptr_val)/(10^*count) { (*ptr_val) = (*ptr_val) / 10; (*ptr_count) = (*ptr_count) - 1; } if((*ptr_dp_count) > 1) return -1 ; (*ptr_count) = 0 ; (*ptr_dp_count) = 0 ; return 1; } else // caractère non supporté dans un nombre { (*ptr_count) = 0; return -1; } } ///////////////////////////////////////////////////////////////////////////////////// char parseGPS(char c, struct gps_data * ptr_gps_data) { char ret; switch (parserState) { case SYNC: counter = 0; if (c == '$') { ptr_gps_data->lat = 0; //maz de la structure de stockage de la trame ptr_gps_data->lon = 0; ptr_gps_data->time = 0; ptr_gps_data->valid = -1; ptr_gps_data->received_checksum = 0; computed_checksum = 0; parserState = HEADER; //evolution de l'etat } break; case HEADER: computed_checksum = computed_checksum ^ c; // a faire dans chaque etat jusqu'au caractere '*' // le calcul de la somme de controle est fait par le XOR -> ^ //A COMPLETER!!!!!!!!!!!!!!! default: parserState = SYNC; break; } return 0; } ///////////////////////////////////////////////////////////////////////////////////// int main(int argc, char *argv[]) { //A COMPLETER!!!!!!!!!!!!!!! char ret=0; char c; while (ret==0) { //A COMPLETER!!!!!!!!!!!!!!! ret=parseGPS( c, &gps_d) ; if (ret==1) { printf("Trame décodée \n"); printf("lat: %f \n",gps_d.lat); printf("lon: %f \n",gps_d.lon); printf("time: %f \n",gps_d.time); printf("valid: %d \n",(int)gps_d.valid); printf("ck: %02x \n",gps_d.received_checksum); } else if (ret==-1) { printf("Trame non décodée \n"); } } } {{https://bvdp.inetdoc.net/files/iut/tp_pic/validation.png}} Une fois les réponses validées, mettre à jour le suivi de version en saisissant dans une console: echo commence cd ~/TPGPS git commit -a -m'GPRMC sans Checksum' gitk & echo fini -------------------------------------------------------------------------------------------------------- =====Exercice 3: Décodage de trame GPRMC avec gestion de la somme de contrôle===== Nous souhaitons maintenant ajouter la gestion de la somme de contrôle. Pour cela, vous pourrez utiliser la fonction **char parseHexField(char c, unsigned char * val, unsigned char * count) ** qui réalise le décodage d'une chaine ASCII codant un nombre 8 bits en hexadécimal (sur 2 caractères) et terminée par le caractère \n ou \r. Cette fonction est non bloquante et retourne 0 tant que le décodage n'est pas fini, 1 lorsque le décodage s'est déroulé correctement et -1 en cas de problème. char parseHexField(char c, unsigned char * ptr_val, unsigned char * ptr_count) { if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F')) { (*ptr_val) = (*ptr_val) << 4; if(c < 'A') { (*ptr_val) += c - '0' ; } else { (*ptr_val) += c - ('A') + 10; } (*ptr_count) = (*ptr_count) + 1 ; return 0; } else if ( ((c == '\n') || (c == '\r')) && ( (*ptr_count) ==2) ) { (*ptr_count) = 0; return 1; } else { (*ptr_count) = 0; return -1; } } {{https://bvdp.inetdoc.net/files/iut/tp_gps/TODO.jpg}} Insérer l'appel de la fonction **parseHexField** dans la machine à états. Elle permet le décodage du checksum émis par le récepteur GPS. Celui ci doit être comparé avec le checksum calculé de la trame en local (dans votre programme). Pour cela, il est nécessaire de réaliser la fonction XOR pour chaque caractère reçu (dans une variable préalablement initialisée à 0). Le checksum n'est calculé en local que entre le caractère '$ \textdollar $' (exclu) et le caractère '$ \ast $' (exclu). En cas de détection d'un problème au niveau de la somme de contrôle, la fonction parseGPS(...) devra retourner -2. Pour vérifier si votre programme est capable de détecter une erreur, changer à la main un caractère de la trame précédente définie dans le tableau de constante ou du code hexadécimal suivant le caractère '$ \ast $'. Dans la boucle principale, ajouter l'affichage du message suivant lorsque la machine à états a détecté une erreur au niveau de la somme de contrôle: if (ret==-2) { printf("----------Trame erronée au niveau du checksum-----------\n"); } Solution globale: #include #include #include "stdio.h" //const char chaine1[10]="123.456,\0"; const char chaine2[10]="789,\0"; const char chaine3[10]=",\0"; const char chaine1[300]="$GPRMC,154936.000,A,4338.6124,N,00126.7337,E,0.26,326.08,200113,,,A*60\r\n$GPRMC,154937.000,A,4338.6124,N,00126.7337,E,0.26,326.08,200113,,,A*60\r\n$GPRMC,225446,A,4916.45,N,12311.12,W,000.5,054.7,191194,020.3,E*68\r\n\0"; //const char chaine1[300]="$GPRMC,225446,A,4916.45,N,12311.12,W,000.5,054.7,191194,020.3,E*68\r\n\0"; struct gps_data { char valid ; //trame valide float time ; //champ time de la trame float lat ; //champ latitude de la trame float lon ; //champ longitude de la trame unsigned char received_checksum; }; unsigned char computed_checksum=0; char parserState=0; unsigned char counter=0; unsigned char dp_counter=0; #define SYNC 0 #define HEADER 1 #define TIME 2 #define VALID 3 #define LAT 4 #define LAT_DIR 5 #define LONG 6 #define LONG_DIR 7 #define IGNORE 8 #define CHECKSUM 9 char header[6]="GPRMC"; gps_data gps_d; //structure de stockage de la trame ///////////////////////////////////////////////////////////////////////////////////// char parseFloatField(char c, float * val, unsigned char * count, unsigned char * dp_count) //La fonction renvoie 0 tant que le décodage n'est pas terminé // 1 lorsque le décodage est terminé correctement // -1 lorsque le décodage a échoué { if (c >= '0' && c <= '9') { (*val) *= 10; (*val) += c - '0'; (*count) = (*count) + 1; return 0; } else if (c == '.') { (*count) = 0; (*dp_count) ++ ; return 0; } else if (c == ',') { while (*count > 0 && *dp_count > 0) // équivalent à *val = *val/(10^*count) { (*val) = (*val) / 10; (*count) = (*count) - 1; } if((*dp_count) > 1) return -1 ; (*count) = 0 ; (*dp_count) = 0 ; return 1; } else { (*count) = 0; // caractère non supporté dans un float return -1; } } ///////////////////////////////////////////////////////////////////////////////////// char parseHexField(char c, unsigned char * val, unsigned char * count) { if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F')) { (*val) = (*val) << 4; if(c < 'A') { (*val) += c - '0' ; } else { (*val) += c - ('A') + 10; } (*count) = (*count) + 1 ; return 0; } else if ((c == '\n') || (c == '\r')) { (*count) = 0; return 1; } else { (*count) = 0; return -1; } } ///////////////////////////////////////////////////////////////////////////////////// void convAngle(float * val){ float t1,t2,t3; int signe; if ((*val)<0){ signe=-1; (*val)=-(*val); } else signe=+1; t1=floor(*val/100); //troncature t2=(*val)-(t1*100); t3=t2/60; (*val)=t1+t3; (*val)=signe*(*val); } /* void convAngle(float * val) { float degre; float minu; if ((*val)>0) degre = floor((*val)/100.); // attention arrondi, TODO vérifier sur les négatifs et sur les parties fractionnaires >0.5 else degre = ceil((*val)/100.); // attention arrondi, TODO vérifier sur les négatifs et sur les parties fractionnaires >0.5 minu = (*val) - (float)(degre*100); //Serial.println((*val) - 4300.); minu = minu / 60.; *val = degre + minu; } */ ///////////////////////////////////////////////////////////////////////////////////// char parseGPS(char c, struct gps_data * dataP) { char ret; switch (parserState) { case SYNC: counter = 0; if (c == '$') { dataP->lat = 0; //maz de la structure de stockage de la trame dataP->lon = 0; dataP->time = 0; dataP->valid = -1; dataP->received_checksum = 0; computed_checksum = 0; parserState = HEADER; //evolution de l'etat } break; case HEADER: computed_checksum = computed_checksum ^ c; // a faire dans chaque etat jusqu'​au caractere '​*'​ exclu // le calcul de la somme de controle est fait par le XOR -> ^ if ( (c == ',') && (counter==5) ) { parserState = TIME; counter = 0; dp_counter = 0; } else if (c != header[counter]) { parserState = SYNC; } else { counter++; } break; case TIME: computed_checksum ^= c; ret = parseFloatField(c, &dataP->time, &counter,&dp_counter); if (ret == 1) { parserState = VALID; return 0; } else if (ret == -1) { parserState = SYNC; return 0; } break; case VALID: computed_checksum ^= c; if (c == ',') { parserState = LAT; counter = 0; dp_counter = 0; } else if (c == 'A') { dataP->valid = 1; } else { dataP->valid = -1; } break; case LAT: computed_checksum ^= c; ret = parseFloatField(c, &dataP->lat,&counter,&dp_counter); if (ret == 1) { parserState = LAT_DIR; } else if (ret == -1) { parserState = SYNC; } break; case LAT_DIR: computed_checksum ^= c; if (c == ',') { parserState = LONG; counter = 0; dp_counter = 0; } else if (c == 'W') { dataP->lat = -dataP->lat; } break; case LONG: computed_checksum ^= c; ret = parseFloatField(c, &dataP->lon,&counter,&dp_counter); if (ret == 1) { parserState = LONG_DIR; } else if (ret == -1) { parserState = SYNC; } break; case LONG_DIR: computed_checksum ^= c; if (c == ',') { parserState = IGNORE; } else if (c == 'S') { dataP->lon = -dataP->lon; } break; case IGNORE: if (c == '*') { counter=0; parserState = CHECKSUM; }else { computed_checksum ^= c; if (c == '\n') { parserState = SYNC; } } break; case CHECKSUM: ret = parseHexField(c, &dataP->received_checksum ,&counter); if (ret!=0) { parserState = SYNC; if ((computed_checksum == dataP->received_checksum) && (dataP->valid!=0)) { return 1; }else{ return -1; } } break; default: parserState = SYNC; break; } return 0; } ///////////////////////////////////////////////////////////////////////////////////// int main(int argc, char *argv[]) { // QCoreApplication a(argc, argv); // return a.exec(); float val; unsigned char ret; char c; unsigned int i=0; while (chaine1[i]!=0) { c=chaine1[i]; i++; //ret=parseFloatField(c,&val,&count, &dp_count); ret=parseGPS( c, &gps_d) ; if (ret==1) { printf("----------Trame decodee------------\n"); printf("lat: %f \n",gps_d.lat); printf("lon: %f \n",gps_d.lon); printf("time: %f \n",gps_d.time); printf("valid: %d \n",(int)gps_d.valid); printf("ck: %02x \n",gps_d.received_checksum); //conversion vers angles en degrés convAngle(&gps_d.lat); convAngle(&gps_d.lon); printf("lat: %f \n",gps_d.lat); printf("lon: %f \n",gps_d.lon); } if (ret=-1) { printf("----------Trame erronée------------\n"); } if (ret==-2) { printf("----------Trame erronée au niveau du checksum-----------\n"); } } // printf("%f",val); fflush(stdout); } {{https://bvdp.inetdoc.net/files/iut/tp_pic/validation.png}} Une fois les réponses validées, mettre à jour le suivi de version en saisissant dans une console: echo commence cd ~/TPGPS git commit -a -m'GPRMC avec Checksum' gitk & echo fini -------------------------------------------------------------------------------------------------------- =====Exercice 4: Décodage de plusieurs trames GPRMC===== {{https://bvdp.inetdoc.net/files/iut/tp_gps/TODO.jpg}} Adapter le programme précédent pour qu'il soit capable de traiter une chaîne contenant **PLUSIEURS** trames GPRMC et d'afficher le résultat du parsing pour chacune des trames. La chaine suivante contient: - une trame avec une somme de contrôle valide - une trame avec une somme de contrôle non valide - une trame avec une somme de contrôle valide Son traitement doit donc produire l'affichage de trames GPRMC correctement décodées. char chaine1[]="$GPRMC,154936.000,A,4338.6124,N,00126.7337,E,0.26,326.08,200113,,,A*60\r\n$GPRMC,154937.000,A,4338.6124,N,00126.7337,E,0.26,326.08,200113,,,A*60\r\n$GPRMC,225446,A,4916.45,N,12311.12,W,000.5,054.7,191194,020.3,E*68\r\n\0"; {{https://bvdp.inetdoc.net/files/iut/tp_pic/validation.png}} Une fois les réponses validées, mettre à jour le suivi de version en saisissant dans une console: echo commence cd ~/TPGPS git commit -a -m'GPRMC multiples' gitk & echo fini -------------------------------------------------------------------------------------------------------- =====Exercice 5: Adaptation sous forme d'une librairie===== {{https://bvdp.inetdoc.net/files/iut/tp_gps/TODO.jpg}} Vous devez maintenant rendre votre fonction de décodage de trame GPS accessible sous forme d'une librairie. Pour cela, vous devez créer 2 fichiers dans le projet: -Cliquer sur Fichier->Nouveau fichier ou projet -Choisir dans fichier et classes: "C++" , puis dans la partie droite "C++ Class" -Cliquer sur Suivant -Dans la zone "Class name" saisir libgps (le nom de classe ne commence pas par une majuscule, mais cela n'est pas un problème car nous n'utiliserons pas cette classe, juste les 2 fichiers dans lesquels elle sera créée) -Cliquer sur Suivant -Cliquer sur Terminer Le fichier libgps.h doit contenir le prototype de la fonction de parsing, les inclusions et la définition de structure nécessaires. Vous pouvez supprimer la définition de la classe libgps qui a été faite automatiquement par l'assistant. #ifndef LIBGPS_H #define LIBGPS_H #include #include "stdio.h" struct gps_data { char valid ; //trame valide float time ; //champ time de la trame float lat ; //champ latitude de la trame float lon ; //champ longitude de la trame unsigned char received_checksum; }; char parseGPS(char c, struct gps_data * ptr_gps_data) ; #endif // LIBGPS_H Dans le fichier libgps.cpp, vous déplacerez le code des fonctions (hormis la fonction main), la définition des variables utilisées par les fonctions. Vous y ajouterez également au début l'inclusion du fichier libgps.h pour que les structures et inclusions de librairies soient connues lors de la compilation de libgps.cpp. Vous pouvez supprimer la définition du constructeur de la classe libgps qui a été faite automatiquement par l'assistant. #include "libgps.h" unsigned char computed_checksum=0; // variable de calcul du checksum char parserState=0; //etat courant du parser unsigned char counter= 0; unsigned char dp_counter=0; #define SYNC 0 #define HEADER 1 #define TIME 2 #define VALID 3 #define LAT 4 #define LAT_DIR 5 #define LONG 6 #define LONG_DIR 7 #define IGNORE 8 #define CHECKSUM 9 char header[6]="GPRMC"; //............ Compléter avec toutes les fonctions nécessaires Dans le fichier main.cpp, vous devrez ajouter #include "libgps.h", et vous conserverez le main(), la déclaration des chaines de test et de la variable gps_data gps_d; Le code des fonctions de parsing doit quant à lui avoir été déplacé dans libgps.cpp. Exécuter le programme et vérifier qu'il fonctionne toujours correctement. {{https://bvdp.inetdoc.net/files/iut/tp_pic/validation.png}} Une fois les réponses validées, mettre à jour le suivi de version en saisissant dans une console: echo commence cd ~/TPGPS git add libgps.cpp libgps.h git commit -a -m'librairie' gitk & echo fini -------------------------------------------------------------------------------------------------------- =====Exercice 6: Portage du code sur Arduino et simulation du module GPS avec le PC via Uart HARD===== Pour que la librairie libgps soit utilisable par Arduino, il faut que ses fichiers soit dans un dossier particulier. Pour éviter d'avoir à copier la librairie, nous allons créer un lien symbolique (sorte de raccourci) entre le dossier de librairie Arduino et l'emplacement de votre projet QT. Ceci permet de modifier via QT notre librairie et de répercuter directement ces modifications sur la librairie utilisée par Arduino. Pour cela, saisir dans une console: echo commence mkdir -p ~/Arduino mkdir -p ~/Arduino/libraries mkdir -p ~/Arduino/libraries/libgps mkdir -p ~/Arduino/libraries/libgps/src ln -s ~/TPGPS/libgps.* ~/Arduino/libraries/libgps/src cd ~/Arduino/libraries/libgps/ wget https://bvdp.inetdoc.net/files/iut/tp_gps/library.properties echo fini Ceci permet notamment de récupérer le fichier suivant contenant des informations sur votre librairie dans ~/Arduino/libraries/libgps/library.properties: name=libgps version=1.0 author=Vous maintainer=Vous sentence=Votre librairie GPS paragraph=blabla category=Uncategorized url=https://bvdp.inetdoc.net/wiki/doku.php?id=tpethindus2 architectures=* Nous allons maintenant créer un croquis Arduino qui sera égalemnet un lien vers un fichier ino dans le dossier ~/TPGPS, afin de pouvoir également le suivre avec l'outils de gestion de version. Pour cela, saisir dans une console: echo commence cd ~/TPGPS mkdir -p ~/Arduino/tpgps touch ~/TPGPS/tpgps.ino ln -s ~/TPGPS/tpgps.ino ~/Arduino/tpgps/tpgps.ino echo fini Vous pouvez ensuite lancer l'IDE Arduino normalement et la librairie est utilisable grâce à un simple #include "libgps.h" dans votre sketch. {{https://bvdp.inetdoc.net/files/iut/tp_gps/TODO.jpg}} Créer un sketch arduino qui communique à 9600 Bauds au format 8N1 sur l'Uart matériel. Ce programme doit traiter tous les caractères reçus (via la machine à états que vous venez de coder avec QT) et les renvoyer en écho. Copier et adapter le code QT dans le sketch arduino. Normalement, vous ne devriez avoir qu'à changer la partir lecture des caractères (depuis l'UART au lieu d'un tableau) et l'affichage des résultats (Serial.println au lieu de printf). Pour tester, ouvrir la console série arduino, configurer la en 9600Bauds et choisir Both NL & CR à gauche du réglage du débit. Ensuite, copier coller le contenu des chaînes de caractères à traiter (sans le \n et \r) dans la console et contrôler qu'elles sont correctement décodées. {{https://bvdp.inetdoc.net/files/iut/tp_pic/validation.png}} Une fois les réponses validées, mettre à jour le suivi de version en saisissant dans une console: echo commence cd ~/TPGPS git add tpgps.ino git commit -a -m'arduino hard simu' gitk & echo fini Solution: #include gps_data gps_d; void setup() { Serial.begin(9600); Serial.println("Programme GPS"); // Envoi de la chaîne terminée par un saut de ligne } void loop() { char c,ret ; if (Serial.available()){ c = Serial.read(); Serial.write(c); ret=parseGPS( c, &gps_d) ; if (ret==1) { Serial.println("----------Trame decodee------------"); Serial.print("lat: "); Serial.println(gps_d.lat); Serial.print("lon: "); Serial.println(gps_d.lon); Serial.print("time: "); Serial.println(gps_d.time); Serial.print("valid: "); Serial.println(gps_d.valid); Serial.print("received_checksum: "); Serial.println(gps_d.received_checksum); //conversion vers angles en degrés convAngle(&gps_d.lat); convAngle(&gps_d.lon); Serial.print("lat: "); Serial.println(gps_d.lat); Serial.print("lon: "); Serial.println(gps_d.lon); } if (ret==-1) { Serial.println("----------Trame erronée------------"); } if (ret==-2) { printf("----------Trame erronée au niveau du checksum-----------\n"); } } } -------------------------------------------------------------------------------------------------------- =====Exercice 7: Décodage de trame en provenance de UART soft de l'Arduino et d'un simulateur de GPS===== {{https://bvdp.inetdoc.net/files/iut/tp_gps/TODO.jpg}} Il vous faut maintenant modifier le sketch Arduino pour utiliser les fonctions de parsing sur des données issues de l'uart soft de l'arduino. Utiliser l'uart soft pour "alimenter" le programme en caractères à décoder et tester avec le une application sur PC simulant le récepteur GPS réel. Vous configurerez l'UART Hard à une vitesse de 115200Bauds pour ne pas ralentir l'exécution de programme pendant l'affichage du résultat du décodage. Vous configurerez l'UART Soft à une vitesse de 4800Bauds. Pour alimenter l'UART soft de l'arduino avec des caractères correspondants à des trames GPRMC, vous allez utiliser une application python en saisissant dans une console: cd ~/TPGPS/ wget https://bvdp.inetdoc.net/files/iut/tp_gps/simugps.py chmod a+x simugps.py ./simugps.py /dev/ttyS1 Le code python est donné ci dessous pour référence (et pour illustrer à quel point il est facile de coder en Python!) Simulateur en Python3: #!/usr/bin/python3 #B. Vandeportaele 03/2020 import serial import os import math import time #premier paramètre pour le nom du port série, /dev/ttyUSB0 par défaut import sys if len(sys.argv)==2: serialport=sys.argv[1] else: serialport='/dev/ttyUSB0' print("using port: "+str(serialport)) #TODO: with pour permettre debug sans port série? ser = serial.Serial( port=serialport,\ baudrate=4800,\ parity=serial.PARITY_NONE,\ stopbits=serial.STOPBITS_ONE,\ bytesize=serial.EIGHTBITS,\ timeout=1) ####################################### def partie_fractionnaire(f, nbdigits): f=f-math.floor(f) while (nbdigits>0): f=f*10 nbdigits=nbdigits-1 return math.floor(f) ####################################### def partie_entiere( f): return math.floor(f) ####################################### def SendGPRMC( time, lat, lon): if (lat>0): NS='N'; else: NS='S'; lat=-lat; if (lon>0): EW='E'; else: EW='W'; lon=-lon; chaine="GPRMC,"+str(partie_entiere(time))+"."+str(partie_fractionnaire(time,2))+',A,'+str(partie_entiere(lat))+"."+str(partie_fractionnaire(lat,5))+","+str(NS)+","+str(partie_entiere(lon))+"."+str(partie_fractionnaire(lon,5))+","+str(EW)+",,,," # sprintf(chaine, "$GPRMC,%ld.%02ld,A,%ld.%05ld,%c,%ld.%05ld,%c,,,,,,*",partie_entiere(time),partie_fractionnaire(time,2),partie_entiere(lat),partie_fractionnaire(lat,5),NS,partie_entiere(lon),partie_fractionnaire(lon,5),EW); octets = bytearray() octets.extend(map(ord, chaine)) sum=0 for b in octets: #print(b) sum=sum^b #print("sum:"+str(sum)) chaine='$'+chaine+"*"+"{:02X}".format(ord(bytes([sum])))+"\n\r" #somme de controle sur 2 digits en majuscules octets = bytearray() octets.extend(map(ord, chaine)) return octets ####################################### while True: #a=3.14159 #print(str(partie_entiere(a))+','+str(partie_fractionnaire(a, 4))) currenttime=int(time.strftime("%H%M%S", time.localtime())) data=SendGPRMC(currenttime,4338.6124 ,00126.7337) print(data) ser.write(data) #"(line+"\r\n").encode("utf8")) #"serial.LF.write(data) time.sleep(0.1) Vérifier que les caractères émis par le simulateur sont bien interprétés par l'Arduino exécutant votre programme. {{https://bvdp.inetdoc.net/files/iut/tp_pic/validation.png}} Une fois les réponses validées, mettre à jour le suivi de version en saisissant dans une console: echo commence cd ~/TPGPS git add tpgps.ino git commit -a -m'arduino soft simu' gitk & echo fini =====Emulation du recepteur GPS par une carte Arduino===== Solution pour le générateur de trames GPRMC (checksum non testée): long int partie_fractionnaire(float f, int nbdigits) { f=f-(long int)f; while (nbdigits>0) { f=f*10; nbdigits--; } return (long int)f; } long int partie_entiere(float f) { return (long int)f; } void SendGPRMC(float time, float lat,float lon) { char chaine[100]; char *finchaine; char checksum=0; char NS,EW; if (lat>0){ NS='N'; }else{ NS='S'; lat=-lat; } if (lon>0){ EW='E'; }else{ EW='W'; lon=-lon; } sprintf(chaine, "$GPRMC,%ld.%02ld,A,%ld.%05ld,%c,%ld.%05ld,%c,,,,,,*",partie_entiere(time),partie_fractionnaire(time,2),partie_entiere(lat),partie_fractionnaire(lat,5),NS,partie_entiere(lon),partie_fractionnaire(lon,5),EW); finchaine=chaine; while( (*finchaine)!=0) { checksum^=(*finchaine); finchaine++; } finchaine=chaine+strlen(chaine); sprintf(finchaine, "%02X\r\n",checksum); Serial.print(chaine); } void setup() { Serial.begin(9600); Serial.println("Hello, world?"); // Envoi de la chaîne terminée par un saut de ligne } float time=154936.0; void loop() { SendGPRMC(time,4338.6124 ,00126.7337); time=time+0.1; delay(100); } -------------------------------------------------------------------------------------------------------- =====Exercice 8: Conversion des angles en degrés et exploitation des coordonnées issues du récepteur GPS réel===== Les valeurs numériques émises dans la trame GPRMC sont en degrés et minutes de degrés comme vu en TD. {{https://bvdp.inetdoc.net/files/iut/tp_gps/BONUS.jpg}} Implémenter la fonction **void convAngle(float * val)** qui modifie la valeur pointée par val pour obtenir une valeur en degrés. Inclure l'appel de cette fonction dans votre programme et tester. Substituer l'émulateur de GPS par le module récepteur Réel et vérifier le bon fonctionnement. Solution avec test: #include #include #include //floor ne va pas car il arrondi vers l'inférieur donc dans les négatifs ce n'est pas bon ///////////////////////////////////////////////////////////////////////////////////// void convAngle(float * val){ float t1,t2,t3; int signe; if ((*val)<0){ signe=-1; (*val)=-(*val); } else signe=+1; t1=floor(*val/100); //troncature t2=(*val)-(t1*100); t3=t2/60; (*val)=t1+t3; (*val)=signe*(*val); } ///////////////////////////////////////////////////////////////////////////////////// int main(int argc, char *argv[]) { float a; printf("bonjour\n"); a=4433.23;convAngle(&a);printf("%f\n",a); a=-4433.23;convAngle(&a);printf("%f\n",a); a=4453.23;convAngle(&a);printf("%f\n",a); a=-4453.23;convAngle(&a);printf("%f\n",a); } {{https://bvdp.inetdoc.net/files/iut/tp_pic/validation.png}} Une fois les réponses validées, mettre à jour le suivi de version en saisissant dans une console: echo commence cd ~/TPGPS git commit -a -m'arduino final' gitk & echo fini En sortant sur le parking avec un ordinateur portable et la carte Arduino+GPS, vous devriez obtenir: $GPGGA,151316.000,4333.8221,N,00126.7960,E,0,00,,,M,,M,,*41 $GPGSA,A,1,,,,,,,,,,,,,,,*1E $GPGSV,1,1,01,31,81,300,*40 $GPRMC,151316.000,V,4333.8221,N,00126.7960,E,,,230318,,*1D Trame décodée lat:43.5636978149 lon:1.4465999603 time:151316.0000000000 valid:-1 ck:1D puis après 1 ou 2 minutes d'attente: $GPGGA,163422.996,4333.9147,N,00127.4247,E,1,04,01.9,00163.0,M,48.5,M,,*66 $GPGSA,A,3,,,21,,14,31,12,,,,,,04.6,01.9,04.1*0A $GPGSV,3,1,10,29,78,044,28,25,55,079,,21,22,174,34,16,28,286,*7A $GPGSV,3,2,10,14,24,244,28,31,56,295,38,12,20,090,33,02,18,051,*71 $GPGSV,3,3,10,26,18,289,,08,10,142,*78 $GPRMC,163422.996,A,4333.9147,N,00127.4247,E,000.0,237.8,120319,000.7,E*62 Trame décodée lat:43.5652503967 lon:1.4570784568 time:163422.9843750000 valid:1 ck:62 En injectant ces coordonnées dans une URL de Google Maps au format: https://www.google.com/maps/@lat,lon,zoom on obtient: https://www.google.com/maps/@43.5652503967,1.4570784568,20z TODO mettre une photo du module, du simulateur MSP430 et des exemples de trames à copier coller en console en expliquant qu'on peut utiliser le pc pour simuler le récepteur GPS =====Test sur site avec la carte Arduino===== ====Décodage de trames issues de la console PC==== Tout d'abord, vous utiliserez une console sur le PC pour générer manuellement des trames émises à la carte micro contrôleur, via copier/coller. {{https://bvdp.inetdoc.net/files/iut/tp_gps/TODO.jpg}} Transformer votre programme QT en un sketch Arduino et tester le bon fonctionnement du programme lorsque les caractères arrivent de l'interface UART émulée sur les broches 5 et 6 (voir TP com2). L'affichage du résultat du décodage doir être effectué sur l'UART HARD configuré à 115200 Bauds. ====Carte Simulateur de GPS==== La carte utilisée pour vérifier le bon fonctionnement de la communication et le décodage des trames NMEA est à base de microcontroleur MSP430 de Texas Instrument. La carte est alimentée par un cable USB à relier à un PC. Elle communique via une interface RS232 coté DTE. Pour la connecter à un autre DTE (par exemple le port série du PC, il faut utiliser un cable croisé). Vous pouvez la connecter directement à un DCE. La communication se fait avec le format suivant: 9600 bauds, 8bits de donnée, 1 stop, pas de parité. L'intérêt d'utiliser cette carte est que vous n'aurez pas besoin de recevoir des signaux des satellites GPS pour obtenir des trames à décoder. La carte envoie des trames au format: (voir le cours de com pour le détail) $GPRMC,154936.000,A,4338.6124,N,00126.7337,E,0.26,326.08,200113,,,A*60\r\n L'heure codée dans la trame est incrémentée chaque seconde par la carte et le Checksum est calculé pour chaque trame. La carte est dotée d'un bouton reset pour redémarrer le programme (remise de l'heure à 15h49min36sec). Elle est également dotée d'un bouton relié à la broche P1.3. Lors d'un appui sur ce bouton, une valeur est ajoutée au CKSUM de manière à le rendre non correct par rapport aux données transmises, afin de pouvoir vérifier que le programme que vous écrirez est capable de détecter des erreurs. {{https://bvdp.inetdoc.net/files/iut/tp_gps/TODO.jpg}} Testez votre programme Arduino en connectant le simulateur de GPS sur l'interface UART émulée. ====Carte GPS==== Il s'agit d'un vrai module GPS, que l'on devra positionner à l'extérieur ou à défaut en bord de fenêtre. Le format de trame utilisé sera le même que pour le simulateur. Éventuellement le débit sera différent et sera indiqué pendant la séance. {{https://bvdp.inetdoc.net/files/iut/tp_gps/TODO.jpg}} Testez votre programme Arduino en connectant le recepteur GPS sur l'interface UART émulée. #include "stdio.h" #include SoftwareSerial mySerial(5, 6); //Création d'un port Série émulé nommé mySerial avec RX=pin 5, TX=pin 6 //const char chaine1[10]="123.456,\0"; /*const char chaine2[10]="789,\0"; const char chaine3[10]=",\0"; const char chaine1[300]="$GPRMC,154936.000,A,4338.6124,N,00126.7337,E,0.26,326.08,200113,,,A*60\r\n$GPRMC,154937.000,A,4338.6124,N,00126.7337,E,0.26,326.08,200113,,,A*60\r\n$GPRMC,225446,A,4916.45,N,12311.12,W,000.5,054.7,191194,020.3,E*68\r\n\0"; //const char chaine1[300]="$GPRMC,225446,A,4916.45,N,12311.12,W,000.5,054.7,191194,020.3,E*68\r\n\0"; */ long int partie_fractionnaire(float f, int nbdigits) { f=f-(long int)f; while (nbdigits>0) { f=f*10; nbdigits--; } return (long int)f; } long int partie_entiere(float f) { return (long int)f; } void SendGPRMC(float time, float lat,float lon) { char chaine[100]; char *finchaine; char checksum=0; char NS,EW; if (lat>0){ NS='N'; }else{ NS='S'; lat=-lat; } if (lon>0){ EW='E'; }else{ EW='W'; lon=-lon; } sprintf(chaine, "$GPRMC,%ld.%02ld,A,%ld.%05ld,%c,%ld.%05ld,%c,,,,,,*",partie_entiere(time),partie_fractionnaire(time,2),partie_entiere(lat),partie_fractionnaire(lat,5),NS,partie_entiere(lon),partie_fractionnaire(lon,5),EW); finchaine=chaine; while( (*finchaine)!=0) { checksum^=(*finchaine); finchaine++; } finchaine=chaine+strlen(chaine); sprintf(finchaine, "%02X\r\n",checksum); Serial.print(chaine); } struct gps_data { char valid ; //trame valide float time ; //champ time de la trame float lat ; //champ latitude de la trame float lon ; //champ longitude de la trame unsigned char received_checksum; }; unsigned char computed_checksum=0; char parserState=0; unsigned char counter=0; unsigned char dp_counter=0; #define SYNC 0 #define HEADER 1 #define TIME 2 #define VALID 3 #define LAT 4 #define LAT_DIR 5 #define LONG 6 #define LONG_DIR 7 #define IGNORE 8 #define CHECKSUM 9 char header[6]="GPRMC"; gps_data gps_d; //structure de stockage de la trame ///////////////////////////////////////////////////////////////////////////////////// char parseFloatField(char c, float * val, unsigned char * count, unsigned char * dp_count) //La fonction renvoie 0 tant que le décodage n'est pas terminé // 1 lorsque le décodage est terminé correctement // -1 lorsque le décodage a échoué { if (c >= '0' && c <= '9') { (*val) *= 10; (*val) += c - '0'; (*count) = (*count) + 1; return 0; } else if (c == '.') { (*count) = 0; (*dp_count) ++ ; return 0; } else if (c == ',') { while (*count > 0 && *dp_count > 0) // équivalent à *val = *val/(10^*count) { (*val) = (*val) / 10; (*count) = (*count) - 1; } if((*dp_count) > 1) return -1 ; (*count) = 0 ; (*dp_count) = 0 ; return 1; } else { (*count) = 0; // caractère non supporté dans un float return -1; } } ///////////////////////////////////////////////////////////////////////////////////// char parseHexField(char c, unsigned char * val, unsigned char * count) { if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F')) { (*val) = (*val) << 4; if(c < 'A') { (*val) += c - '0' ; } else { (*val) += c - ('A') + 10; } (*count) = (*count) + 1 ; return 0; } else if ((c == '\n') || (c == '\r')) { (*count) = 0; return 1; } else { (*count) = 0; return -1; } } ///////////////////////////////////////////////////////////////////////////////////// void convAngle(float * val){ float t1,t2,t3; int signe; if ((*val)<0){ signe=-1; (*val)=-(*val); } else signe=+1; t1=floor(*val/100); //troncature t2=(*val)-(t1*100); t3=t2/60; (*val)=t1+t3; (*val)=signe*(*val); } ///////////////////////////////////////////////////////////////////////////////////// char parseGPS(char c, struct gps_data * dataP) { char ret; switch (parserState) { case SYNC: counter = 0; if (c == '$') { dataP->lat = 0; //maz de la structure de stockage de la trame dataP->lon = 0; dataP->time = 0; dataP->valid = 0; dataP->received_checksum = 0; computed_checksum = 0; parserState = HEADER; //evolution de l'etat } break; case HEADER: computed_checksum = computed_checksum ^ c; // a faire dans chaque etat jusqu'​au caractere '​*'​ exclu // le calcul de la somme de controle est fait par le XOR -> ^ if ( (c == ',') && (counter==5) ) { parserState = TIME; counter = 0; dp_counter = 0; } else if (c != header[counter]) { parserState = SYNC; } else { counter++; } break; case TIME: computed_checksum ^= c; ret = parseFloatField(c, &dataP->time, &counter,&dp_counter); if (ret == 1) { parserState = VALID; return 0; } else if (ret == -1) { parserState = SYNC; return 0; } break; case VALID: computed_checksum ^= c; if (c == ',') { parserState = LAT; counter = 0; dp_counter = 0; } else if (c == 'A') { dataP->valid = 1; } break; case LAT: computed_checksum ^= c; ret = parseFloatField(c, &dataP->lat,&counter,&dp_counter); if (ret == 1) { parserState = LAT_DIR; } else if (ret == -1) { parserState = SYNC; } break; case LAT_DIR: computed_checksum ^= c; if (c == ',') { parserState = LONG; counter = 0; dp_counter = 0; } else if (c == 'W') { dataP->lat = -dataP->lat; } break; case LONG: computed_checksum ^= c; ret = parseFloatField(c, &dataP->lon,&counter,&dp_counter); if (ret == 1) { parserState = LONG_DIR; } else if (ret == -1) { parserState = SYNC; } break; case LONG_DIR: computed_checksum ^= c; if (c == ',') { parserState = IGNORE; } else if (c == 'S') { dataP->lon = -dataP->lon; } break; case IGNORE: if (c == '*') { counter=0; parserState = CHECKSUM; }else { computed_checksum ^= c; if (c == '\n') { parserState = SYNC; } } break; case CHECKSUM: ret = parseHexField(c, &dataP->received_checksum ,&counter); if (ret!=0) { parserState = SYNC; if ((computed_checksum == dataP->received_checksum) && (dataP->valid!=0)) { return 1; }else{ return 1; } } break; default: parserState = SYNC; break; } return 0; } ///////////////////////////////////////////////////////////////////////////////////// void setup() { mySerial.begin(4800); // configuration du baudrate à 4800 bauds Serial.begin(115200); Serial.println("Hello, world?"); // Envoi de la chaîne terminée par un saut de ligne } float time=154936.0; void loop() { char c,ret ; /* SendGPRMC(time,4338.6124 ,00126.7337); time=time+0.1; delay(100); */ if (mySerial.available()){ c = mySerial.read(); Serial.write(c); ret=parseGPS( c, &gps_d) ; if (ret==1) { Serial.println(); Serial.println("----------Trame decodee------------"); Serial.print("lat: "); Serial.println(gps_d.lat); Serial.print("lon: "); Serial.println(gps_d.lon); Serial.print("time: "); Serial.println(gps_d.time); Serial.print("valid: "); Serial.println(gps_d.valid); Serial.print("received_checksum: "); Serial.println(gps_d.received_checksum); //conversion vers angles en degrés convAngle(&gps_d.lat); convAngle(&gps_d.lon); Serial.print("lat: "); Serial.println(gps_d.lat); Serial.print("lon: "); Serial.println(gps_d.lon); } if (ret==-1) { Serial.println(); Serial.println("----------Trame erronee------------"); } } } =====Solution 2022 avec Messieurs Poque et Augé===== Avec génération de l'url googlemaps #include "libgps.h" #include SoftwareSerial mySerial(5, 6); void correction(float * val){ float degre=(int)(*val)/100; float minute=(*val)-degre*100.; (*val)=degre+(minute/60.); } void setup() { // put your setup code here, to run once: Serial.begin(115200); Serial.println("Bonjour!!!"); mySerial.begin(4800); mySerial.println("Hello World !!!"); } void loop() { // put your main code here, to run repeatedly: // $GPRMC,154936.000,A,4338.6124,N,00126.7337,W,0.26,326.08,200113,,,A*60 static gps_data gps_d; if (mySerial.available()) { char c = mySerial.read(); Serial.print(c); // Serial.print(c); char ret = parseGPS( c, &gps_d) ; if (ret == 1) { Serial.println("Trame décodée"); Serial.print("lat: "); Serial.println(gps_d.lat); correction(&gps_d.lat); Serial.print("lat corrige: "); Serial.println(gps_d.lat); Serial.print("lon: "); Serial.println(gps_d.lon); correction(&gps_d.lon); Serial.print("lon corrige: "); Serial.println(gps_d.lon); Serial.print("time: "); Serial.println(gps_d.time); Serial.print("valid: "); Serial.println((int)gps_d.valid); Serial.print("ck: "); Serial.println(gps_d.received_checksum); Serial.print("https://www.google.com/maps/@"); Serial.print(gps_d.lat, 6); Serial.print(','); Serial.print(gps_d.lon,6); Serial.print(",21z"); Serial.println(); } else if (ret == -1) { Serial.println("Trame non décodée"); } if (ret == -2) { Serial.println("----------Trame erronée au niveau du checksum-----------\n"); } } }