Outils pour utilisateurs

Outils du site


tpethindus2

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

Présentation du matériel

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: http://homepages.laas.fr/bvandepo/files/iut/tp_gps/MAE_parseGps.svg http://homepages.laas.fr/bvandepo/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 “Application qt4 en console” et cliquer sur choisir. Donner ensuite un nom au projet 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 {
    unsigned 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;
};

Décodage des nombres flottants

La fonction de décodage des nombres flottants est fournie:

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

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.

homepages.laas.fr_bvandepo_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)

homepages.laas.fr_bvandepo_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";

Décodage simple de trame GPRMC

homepages.laas.fr_bvandepo_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 {
    unsigned 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 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 * 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 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 '*'
        // 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!!!!!!!!!!!!!!!   
 
    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",gps_d.valid);
            printf("ck: %02x \n",gps_d.received_checksum);
 
        }
        else if (ret==-1)
        {
            printf("Trame non décodée \n");
        }
    }
 
}

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 * 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;
  }
}

homepages.laas.fr_bvandepo_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).

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:

printf("----------Trame erronée------------\n");

Décodage de plusieurs trames GPRMC

homepages.laas.fr_bvandepo_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";

Portage du code sur Arduino

homepages.laas.fr_bvandepo_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.

Décodage de trame en provenance de l'uart soft et du récepteur GPS

homepages.laas.fr_bvandepo_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 récepteur GPS réel.

Ce module récepteur GPS communique en simplex aux niveau RS232 à 4800 Bauds et au format 8N1. Sa documentation est disponible ici: http://homepages.laas.fr/bvandepo/files/iut/tp_gps/French_Manuel_Hi203.pdf

Conversion des angles en degrés

Les valeurs numériques émises dans la trame GPRMC sont en degrés et minutes de degrés comme vu en TD.

homepages.laas.fr_bvandepo_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.

tpethindus2.txt · Dernière modification: 2017/05/18 15:07 par bvandepo