Table des matières

bvdp.inetdoc.net_files_iut_tp_pic_warning.jpeg

TP COMMUNICATION: Décodage de trame au format NMEA d'un GPS.

Objectifs

Sujet de TD

tdethindus1distanciel

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.ino
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:

parse_float.ino
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'.

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)

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 <math.h>

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

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.

main.cpp
#include <QtCore/QCoreApplication>
 
#include <math.h>
#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");
        }
    }
 
}

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.

par_hex.ino
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;
  }
}

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");
      }

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

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:

  1. une trame avec une somme de contrôle valide
  2. une trame avec une somme de contrôle non valide
  3. une trame avec une somme de contrôle valide

Son traitement doit donc produire l'affichage de trames GPRMC correctement décodées.

main.cpp
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";

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

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:

  1. Cliquer sur Fichier→Nouveau fichier ou projet
  2. Choisir dans fichier et classes: “C++” , puis dans la partie droite “C++ Class”
  3. Cliquer sur Suivant
  4. 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)
  5. Cliquer sur Suivant
  6. 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.

libgps.h
#ifndef LIBGPS_H
#define LIBGPS_H
 
 
#include <math.h>
#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.

libgps.cpp
#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.

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:

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.

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.

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

Exercice 7: Décodage de trame en provenance de UART soft de l'Arduino et d'un simulateur de GPS

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!) </ifauth> Simulateur en Python3:

simugps.py
#!/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.

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

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.

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.

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