Le traitement des trames GGA .

Dans cet article le logiciel a été développé sur une platine Arduino Uno associée à une platine shield SparkFun gérant un récepteur EM406 .


Les informations issues de la platine sont exploitées en utilisant la librairie SoftwareSerial qui gère la lecture de la voie série à 4800 bauds qui la relie à l'Arduino.
Par ailleurs la platine shield utilise les broches digitales d2 et d3 de l'Arduino et de ce fait il n'y a pas de conflits avec la liaison série reliant (via l'usb) l'Arduino et le PC lors du développement . Autre avantage : les broches d4,d5, etc ... de la platine étant disponibles nous les utiliserons pour implémanter un affichage sur un afficheur standart 16x2 .

Déclarations spécifiques à la librairie SoftwareSerial .
    #include <SoftwareSerial.h >
    
    #define rx 2
    #define tx 3

    SoftwareSerial gps(rx,tx) ;

Déclarations relatives aux lectures des voies séries .

    void setup(){
       Serial.begin(115200); // liaison série à 115200 bauds avec le PC
       gps.begin(4800)     ; // liaison série à   4800 bauds avec le gps 
    }

Structure générale du logiciel

Le logiciel comporte deux parties.
Dans la première , en exploitant le flux émis par le récepteur GPS, on extrait de ce dernier une trame GGA valide.
Dans la seconde on extrait de la trame GGA les chaines associées aux champs qui nous intéressent.
Principalement ce sont ceux relatifs à l'heure, la latitude, la longitude , l'altitude.
Les routines de conversion permettent ensuite d'exprimer la latitude et la longitude dans les deux systèmes : degrés décimaux ou degés , minutes, secondes .
  void loop(){
  
     //  Première partie
     
     acquisition des signaux GPS    ;
     extraction d'une trame GGA     ;
     validation de la trame trouvée ;
     
     //  Seconde partie
     
     traitement de la trame trouvée ;
  }
Première partie
Les satellites envoient des signaux toutes les secondes.
Le minimum requis pour une réception gps valide est de 4 satellites.
En effet, bien que la position du récepteur puisse être déterminée en théorie par 3 satellites
en se basant sur la durée de propagation des signaux issus de ces derniers, la précision de la
mesure des durées par les horloges des récepteurs gps , très insuffisante, nécessite un
4 ème satellite.
(Les satellites sont munis d'horloges atomiques synchronisées très précises , ce qui n'est pas le cas
des récepteurs gps) .

Acquisition des signaux.

La recherche d'une trame GGA consistera à lire pendant 1 seconde, toutes les informations transmises
par le récepteur gps . Elle sont stockées dans un tableau buffer[] de type char.
Le buffer choisi a une taille de 600 caractères ce qui nous semble suffisant.
NB: A 4800 bauds/sec on reçoit (approximativement) 600 caractères ce qui correspond à un peu plus
que la longueur de 7 trames de longueur 80 .

    
    //    millis() donne le nombre de millisecondes écoulées depuis l'exécution du logiciel sur l'Arduino.
    //    Les fonctions gps.available() et gps.read() sont fournies par la bibliothèque SoftwareSerial).
   
    i = 0  ;
    start = millis()  ;
    do{
        while ((gps.available())&&(i < BUFFERSIZE))  buffer[i++] = gps.read()  ;
    }  while(millis() - start < 1000)  ;
    
A ce stade il est intéressant de voir les trames qui ont été lues en affichant le buffer
au sortir de la boucle do{}while .
Comme les trames sont terminées par 0x0D,0x0A le saut et retour à la ligne se font automatiquement
et les trames NMEA se trouvent alignées à l'écran.

Recherche d'une trame GGA.

Dans un premier temps on recherche le caractère $ qui marque le début d'une trame.
Dans un deuxième temps on extrait de buffer[] dans le tableau nmea[] tous les caractères
compris entre $ et 0x0D ( retour chariot).
Finalement on teste si cette trame est une trame GGA .

La variable 'courant' de type unsigned dénote l'offset du caractère courant dans buffer[] lors
du processus de recherche d'une trame GGA .
La variable booléenne 'trouve' pilote la recherche d'une trame GGA valide.

    courant = 0	    ;
    trouve = false  ;
    while(!trouve)){
      //  recherche une trame GGA valide.  
    }

Recherche d'une trame GGA .
    // 	recherche du caractere $   
    
    while((buffer[courant] != '$')&&(courant < BUFFERSIZE))courant++  ;
    if(courant == BUFFERSIZE) break  ;
    
    //  on extrait l'en tête de la trame (de longueur 6)dans le tableau trame[]
    //  grace à la fonction substring() décrite en annexe .
    
    substring(buffer,trame , 6)	;
    
    // si type_trame vaut zero on a affaire à une trame GGA qui devra être validée
    // sinon on poursuit la recherche
    
    type_trame= cmpstr(trame,"$GPGGA");
    if(!type_trame){
       //  extraction , validation , puis traitement d'une trame GGA    
    }
    else courant += 6	;		 // poursuivre la recherche
Dans l'hypothèse on l'on a détecté l'en tête d'une trame GGA on va , dans un
premier temps, extraire cette dernière dans le tableau nmea[].
Cette extraction se fait grace à la routine substring().
On rappelle qu'une chaine nmea commence toujours par le caractère $ et se termine par le
caractère de code 0x0D (retour chariot).
    //  La variable len vaut la longueur d'une chaine commençant à $ et finissant à 0x0D 
    
    debut = courant	;
    while((buffer[courant] != 0x0D)&&(courant < BUFFERSIZE))courant++  ;
    if(courant == BUFFERSIZE) break  ;
    len = courant - debut  ;
    substring(buffer,nmea,debut, len)  ;
    

Validation de la trame GGA extraite>

Le checksum recalculé sur la chaine nmea[] doit coincider avec celui donné par
la chaine elle même dans le champ cc.

Calcul de la checksum .

Ce calcul s'effectue sur tous les caractères de nmea[] de $ non compris
à * non compris. La variable checkcalc contient la valeur de la checksum.
La routine checksum() est donnée en annexe.

    checkcalc = checkSum(nmea) ;
    
On lit le checksum du champ cc de la trame dans le tableau checklu[].
    substring(nmea,checklu,offset_checksum,2)  ;
checklu[] est ensuite converti en l'entier checklucalc.
La routine de conversion ascToByte() est donnée en annexe.
    checklucalc = ascToByte(checklu)            ;
Finalement la chaine nmea[] extraite est retenue si le checksum lu sur cette dernière
coincide avec celui calculé directement.

    if(checkcalc == checklucalc) trouve = true ;
    else		courant++ ;          // poursuivre la recherche.
    
Seconde partie
Dès qu'une trame GGA valide est trouvée les champs qui nous intéressent sont extraits .
On rappelle ci dessous les offsets des champs nécessaires à leur exploitation par
la routine substring2(). Cette dernière est une variante de substring() mieux adaptée à l'extraction
des champs, notamment s'ils sont variables.

    #define offset_utc        07
    #define offset_latitude   18   
    #define offset_longitude  30
    #define offset_fix        43
    #define offset_satellite  45
    #define offset_altitude   52
    #define offset_checksum   72

Extraction des champs latitude, longitude et heure UTC dans les tableaux
latitude1[],longitude1[] et utc1[] .

    substring2(nmea,latitude1,offset_latitude)      ;
    substring2(nmea,longitude1,offset_longitude)    ;
    substring2(nmea,utc1,offset_utc)                ;
    substring2(nmea,altitude,offset_altitude)       ;

Mise en forme de l'heure ( heure, minutes, secondes ) dans le tableau utc2[] .

    utc2[0] = utc1[0]  ;
    utc2[1] = utc1[1]  ;
    utt2[2] = 'h'      ;
    utc2[3] = ' '      ;
    utc2[4] = utc1[2]  ;
    utc2[5] = utc1[3]  ;
    utc2[6] = 'm'      ;
    utc2[7] = ' '      ;
    utc2[8] = utc1[4]  ;
    utc2[9] = utc1[5]  ;
    utc2[10] = 's'     ;
    utc2[11] = 0       ;

Décodage de la latitude et de la longitude dans le système degrés, décimal .

La routine decodeDegre10() admet pour arguments :
Premier argument : 0 pour latitude, 1 pour longitude .
Deuxième argument : tableau brut extrait de la trame nmea ( ici longitude1[] (resp latitude1[])).
Retourne dans le troisième argument une chaîne représentative au format degrés décimaux.
La routine decodeDegre10() est détaillée en annexe .

    decodeDegre10(0, latitude1, latitude2)   ;
    decodeDegre10(1, longitude1,longitude2)  ;

Décodage de la latitude et de la longitude dans le système degrés, minutes, secondes .

La routine decodeDegreMinSec() admet les mêmes arguments que decodeDegre10().

    decodeDegreMinSec(0, latitude1, latitude2)   ;
    decodeDegreMinSec(0, longitude1, longitude2) ;
    

Affichage des résultats

Il ne reste plus qu'à afficher les résultats , soit à l'écran , soit sur un afficheur.

Affichage à l'écran du PC
Exemple d'affichage à l'écran de la latitude et de la longitude en degrés décimaux:
La routine decodeDegre10() a été utilisée .
  
    Serial.print("  heure utc : ")     ;
    Serial.print(utc2)                 ;
    Serial.print("   nbr sat : ")      ;
    Serial.print(satellite)            ;
    Serial.print("   altitude : ")     ;
    Serial.print(altitude)             ;
    //
    Serial.print("   latitude : ")     ;
    Serial.print(latitude2)            ;
    Serial.print("   longitude : ")    ;
    Serial.print(longitude2)           ;
    Serial.println()                   ; 
    
Ce qui donne :

    heure utc :  10h 13m 38s   nbr sat : 07  altitude : 64.8 latitude : 49.3574°   longitude : 003.2499°
    
Affichage sur un écran LCD
La platine shield utilisant les entées numériques d2 et d3 de l'Arduino nous pouvons
utiliser les entrées numériques restantes pour l'interfacer avec un afficheur standart 16 x 2 .

    platine      gnd  vcc   (1)   d11   gnd   d10  gnd  gnd  gnd gnd   d7   d6   d5    d4
    afficheur    1    2      3    4     5     6    7    8    9   10    11   12   13    14  
    
      
(1) La broche 3 de l'afficheur sert à régler le contraste.
Elle est reliée à la sortie d'un potentiomètre de 10 kOhms intercallé entre le vcc et le gnd de la platine.

Exemple de code:
    
    //	Inclure la bibliothèque de gestion de l'afficheur
    
    #include <LiquidCrystal.h>	
    
    //	Déclaration de 'lcd' compatible avec le branchement proposé .
    
    LiquidCrystal lcd(11,10,7,6,5,4);
    
    //	A inclure dans void  setup()
    
    lcd.begin(16,2); // ecran 16x2
    
    //	Dans void loop() si on veut un affichage dans le système degrés, minutes, secondes
          
    decodeDegreMinSec(0,latitude1,latitude2)    ;
    decodeDegreMinSec(1,longitude1,longitude2)  ;
    
    lcd.setCursor(0,0)            ;  // premiere ligne de l'afficheur
    lcd.write(latitude2)          ; 
    
    lcd.setCursor(0,1)            ;  // seconde ligne de l'afficheur
    lcd.write(longitude2)         ;