Outils pour utilisateurs

Outils du site


Action disabled: edit
tdethindus1distanciel

TD Ethernet Industriel: Protocole NMEA pour GPS

Visionner tout d'abord la partie du cours:

bvdp.inetdoc.net_files_iut_tp_pic_warning.jpeg

Vos réponses aux questions 1.3 et 1.8 devront être remises à l'enseignant à la prochaine séance de TP et participeront à votre note.

Le système GPS (Global Positionning System) est composé d'une mire de satellites en orbite haute autour de la terre. Ces satellites transmettent des messages sur les bandes de fréquences de 1.57542 GHz (L1 signal) and 1.2276 GHz (L2 signal) à une vitesse de 50 bit/s. Chaque message de 1500 bits contient une information temporelle (date/heure GPS), l'éphéméride (orbite du satellite) et “l'almanach” qui contient des informations de statut du satellite.

Un récepteur GPS à besoin de recevoir l'information en provenance d'un minimum de 3 satellites afin de trianguler sa position sur la surface de la terre. Le récepteur peut alors transmettre à l'application la position calculée ainsi que des informations temporelles (date/heure). Le format le plus répandu pour la transmission de ces données entre le récepteur GPS et un autre appareil est le format NMEA (National Marine Electronic Association) souvent transmis au moyen d'une liaison série asynchrone ASCII.

Le format NMEA définit un ensemble de trames qui contiennent chacune des informations de navigation (positionnement, vitesse, date/heure …). Une trame NMEA est préfixée par le caractère « $» suivi du nom de la trame sur 5 caractères (GPRMC, GPGGA, GPRMA …) et d'un ensemble de données séparés par des virgules et terminée par le caractère saut de ligne ('\n' soit 0x0d en hexadécimal).

La trame GPRMC (Global Positionning Recommended Minimum C) se décompose comme suit :

$GPRMC,hhmmss.ss,A,llll.ll,a,yyyyy.yy,a,x.x,x.x,ddmmyy,x.x,a*hh suivi de \n\r

Par exemple :

$GPRMC, 225446,A,4916.45,N,12311.12,W,000.5,054.7,191194,020.3,E*68\n\r

Détails de la trame GPRMC

La table suivante détaille les différents champs d'une trame GPRMC:

Numéro Nom et Fonction Exemple
- Délimiteur début de trame ‘$’ et nom de la trame $GPRMC
1 Heure universelle (UTC) du “fix” 22 :54 :46
2 Statut du récepteur, A si le GPS fournit une information valide (fix), V sinon A
3 Latitude en degrés/minutes (voir note1). 49°16.45’
4 (N) Nord ou (S) Sud N (nord)
5 Longitude en degrés/minutes (voir note1). 123°11.12
6 (E) Est ou (W) Ouest W (Ouest)
7 Vitesse au sol en nœuds 0.5 (nœuds)
8 Cap en degrés 54,7°
9 Date 19 novembre 1994
10 Variation magnétique du cap en degrés 20.3°
11 (E) Est ou (W) Ouest E (Est)
Peut contenir d’autres informations
Délimiteur fin de trame ‘*’ *
Somme de contrôle (XOR de tous les octets transmis entre $ et * EXCLUS, affichée en hexadécimal sur deux caractères ASCII) 0x68
Après la trame, il peut y avoir des caractères supplémentaires, ou d’autres trames \n\r

Note 1 : Le format utilisé pour la latitude est le suivant (ddmm.mmmm) avec un nombre de chiffres variables (de 0 à 7) après le point décimal. (dd) code l’angle en degrés (90°max) et (mm.mmmm) code les minutes en décimal (soixantièmes de degrés). Le format utilisé pour la longitude est le suivant (dddmm.mmmm). (ddd) code l’angle en degrés (180°max)

Note 2 : Le standard NMEA indique qu’il peut y avoir d’autres champs entre le 11° et la somme de contrôle. Nous n’essaierons pas de les décoder ici, mais calculerons la somme de contrôle en les utilisant si ils sont présents.

Les champs entre virgules peuvent ne pas être renseignés par le récepteur. La longueur de cette trame est donc de l'ordre de 75 caractères ASCII en considérant le format fourni. Le récepteur peut transmettre cette trame à la fréquence de 1, 5 ou 10hz avec des vitesses de transmission série allant de 9600Bauds à 115200Bauds.

Objectif  de l’exercice

On souhaite réaliser un programme permettant de décoder à la volée (sans stockage, aussi appelée stream parsing) les trames GPRMC d'un module récepteur GPS relié à une carte microcontrôleur via un port de communication série asynchrone à une fréquence d’une trame par seconde, au format 8N1 4800Bauds en utilisant le moins de mémoire RAM possible pour le stockage des données.

Le programme principal sera structuré comme suit :

  1. Une tache de fond ou boucle principale, qui testera la disponibilité d'un caractère en réception sur l'UART émulé, et le cas échéant le consommera pour alimenter la fonction de décodage.
  2. Une fonction d'affichage appelée si la trame entièrement reçue est valide

La fonction de décodage des données GPS est appelée à chaque exécution de la boucle principale. Cette fonction consomme les données (des caractères ASCII) depuis la FIFO de réception d'un UART émulé et doit remplir au fûr et à mesure la structure suivante :

strucgps.cpp
	struct gps_data {
  	  unsigned char valid ; //trame valide
   	  float time ; //champ time de la trame: heure UTC
 	  float lat ; //champ latitude de la trame
  	  float lon ; //champ longitude de la trame
  	  unsigned char received_checksum;	// valeur du checksum reçu
	 };

Question 1.1

Proposer une machine à états pour décoder à la volée une suite de caractères codant une valeur numérique décimale avec éventuellement une partie fractionnaire (séparée de la partie entière par le caractère '.') et ranger la valeur décodée dans une variable flottante. La machine à états doit être capable de décoder des suites de caractères terminées par le caractère ',' telles que:

1234,
1234.56,
.78,
0,
0.,
.0,

Dans le cas de ces chaines, la machine à états doit indiquer, après réception du caractère ',' que le décodage s'est terminé correctement.

La machine à états peut également être alimentée par des suites de caractères ne codant pas une valeur correcte, telles que:

1234.56.78,
..,
a4,
bonjour,

Dans le cas de ces chaines, la machine à états doit indiquer, que le décodage ne s'est pas terminé correctement.

Indice: Plusieurs solutions sont possibles, mais la plupart utilisent le fait qu'à chaque caractère reçu pour la partie entière, la valeur décodée peut être mise à jour par une multiplication de la valeur précédente par 10 et par l'ajout du nouveau chiffre. Pour la partie fractionnaire, cherchez.

Vous montrerez à l'enseignant lors de la prochaine séance de TP votre proposition de machine à état pour correction et évaluation.

Tester cette machine à états avec chacune des chaines d'exemples fournies dans la question et vérifier que le décodage est correct ou bien que l'erreur de décodage est détectée.

Question 1.2

Implémenter en C une fonction avec le prototype suivant: char parseFloatField(char c, float * ptr_val, unsigned char * ptr_count, unsigned char * ptr_dp_count);

Lorsque cette fonction a terminé de décoder le champ (lorsqu'un caractère ',' est reçu), elle renvoie la valeur 1 si la trame est valide et -1 si la trame n'est pas valide. Tant que la champ n'est pas entièrement décodé, la fonction renvoie 0.

Les différentes variables passées à la fonction via pointeurs (ptr_val, ptr_count et ptr_dp_count) servent à lui fournir en entrée/sortie les variables utilisées pour le décodage (voir la solution proposée pour l'exercice 1.1)

Question 1.3

Proposer une fonction (ou au moins son algorithme) permettant de tester automatiquement la fonction char parseFloatField(…) en l'alimentant avec différentes séquences de caractères. Les résultats produits par char parseFloatField(…) doivent être comparés avec les valeurs attendues et si il y a une erreur, votre fonction doit l'afficher. Prévoir une version sur papier de votre réponse à cet exercice que vous rendrez à l'enseignant à la prochaine séance de TP.

Question 1.4

Définir la structure générale de la fonction de décodage (MAE) dont le prototype est le suivant : char parseGPS (char c, struct gps_data * ptr_gpsP);

Lorsque la fonction aura terminé de décoder la trame, elle devra renvoyer la valeur 1 si la trame est valide et -1 si la trame n'est pas valide. Tant que la trame n'est pas entièrement décodée, la fonction renvoie 0;

Pour décoder les champs de données codant l'heure, la latitude, la longitude et la date, vous devrez faire en sorte que la machine à état de décodage de la trame GPRMC donne les caractères qu'elle traite à la machine à état décodant les valeurs de type flottant ( via un appel à char parseFloatField(…) ) et exploite sa valeur de retour pour déterminer si le décodage du nombre s'est terminé correctement ou non.

Question 1.5

En appliquant la même démarche qu'aux exercices 1.1 et 1.2, proposer une fonction dont le prototype est char parseHexField(char c, unsigned char * ptr_val, unsigned char * ptr_count)

Cette fonction doit permettre le décodage de 2 caractères ASCII consécutifs codant une valeur en hexadécimal. *ptr_count peut être utilisé pour comptabiliser le nombre de caractères reçus à un instant donné.

Question 1.6

Les latitudes et longitudes peuvent être représentées dans un repère orienté Nord Est, (Latitude positive quand orientée Nord, Longitude positive quand orientée Est). Compléter la fonction char parseGPS (char c, struct gps_data * ptr_gpsP); pour traiter l'orientation des latitudes longitudes.

Question 1.7

Compléter la machine à états pour le calcul et la vérification du checksum (somme de contrôle) de la trame. Pour cela, utilisez la fonction char parseHexField(…) que vous avez fait à la question 1.5.

Question 1.8

Donner l’algorithme du programme principal (dont le rôle est d’afficher sur le terminal la position courante ainsi que l’heure ; ces données étant transmises par le récepteur GPS). Traduire en langage C cet algorithme.

Question 1.9

Supposons que l'on souhaite améliorer la machine à états de l'exercice 1.1 pour permettre de décoder des séquences de caractères contenant éventuellement un caractère de préfixe '+' ou '-' pour indiquer le signe. Proposer les changements nécessaires pour prendre en charge ce nouveau caractère. Prévoir une version sur papier de votre réponse à cet exercice que vous rendrez à l'enseignant à la prochaine séance de TP.


Solutions partie 1:

Question 1.1

Voici une proposition de machine à états:

Question 1.2

Comme la machine à états dans ce cas n'a qu'un seul état, le code de la fonction peut s'écrire simplement:

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;
  }
}
tdethindus1distanciel.txt · Dernière modification : 2022/03/09 17:37 de bvandepo