piątek, 1 kwietnia 2011

GPS i Arduino, czyli zabawy z nawigacją dla każdego.


Autor: Piotr Rzeszut (Piotrva)
Redakcja: Dondu

Zobacz inne artykułu z cyklu: Arduiono ... czyli prościej się nie da :-)

Arduino i GPS - pozycjonowanie na Ziemi.
O tym do czego służy system GPS chyba nie muszę nikomu mówić, bo prawie każdy z nas dysponuje (choćby w smartfonie) systemem nawigacyjnym i zna wszelkie zalety takich rozwiązań.

Warto za to powiedzieć parę słów o tym jak działa system GPS oraz jak możemy wykorzystać dane z niego pochodzące w budowanych przez nas urządzeniach elektronicznych.

Co lata nam nad głową, czyli system GPS


GPS - Siatka satelitów.
Satelity GPS nad Ziemią (źródło


Aby zrozumieć zasadę działania systemu GPS zastanówmy się, jak możemy określić położenie danego punktu, np. na mapie. Rzecz jasna najprościej zerknąć na siatkę i odczytać odpowiednie współrzędne, jednak nie dokładnie o to nam chodzi, gdyż w rzeczywistości, np. w lesie, raczej siatki nie znajdziemy. Co najwyżej przy odrobinie szczęścia trafimy na jakiś słupek o znanym położeniu.


Artykuł powstał dzięki wsparciu:



Tu jednak możemy wpaść na inny pomysł. Jeżeli mielibyśmy możliwość zmierzenia odległości od kilku takich słupków to stosując proste zależności geometryczne moglibyśmy wyznaczyć nasze położenie:


GPS - Koncepcja wyznaczania pozycji.
Mapa obrazująca ideę działania systemu GPS


Na powyższej mapce widzimy ilustrację tej metody. Jak widzimy też do jednoznacznego wyznaczenia punktu potrzeba nam 3 miejsc odniesienia oraz odległości do nich. Np. znając odległość od Krakowa (zielona strzałka) możemy powiedzieć tylko, że znajdujemy się gdzieś na zielonym okręgu.

Dalej jeśli znamy odległości od Krakowa i Torunia (dodatkowo strzałka czerwona) to możemy powiedzieć, że znajdujemy się gdzieś na przecięciu tych okręgów, czyli w punkcie granatowym lub niebieskim. Dodając do tego znajomość odległości do Warszawy odnajdujemy już tylko 1 konkretny punkt.

Właśnie taka jest idea działania nawigacji GPS, z tym, że działa ona w przestrzeni, zatem analogicznie zamiast okręgów rozpatrywalibyśmy sfery, a do pełnej lokalizacji potrzebować będziemy 4 satelitów i odległości od nich.

Ostatecznie system GPS bazuje na tym, że odbiornik mierzy czas w jakim sygnał pokonuje drogę od satelity i na tej podstawie określa odległość od niego:


GPS - Sposób określania pozycji.
GPS - Sposób określania pozycji
Źródło: National Air and Space Museum

Posiadając 4 takie odległości oraz dokładne położenie satelitów w przestrzeni kosmicznej (które jest podawane w odbieranym sygnale) możemy za pomocą odpowiedniego aparatu matematycznego wyznaczyć położenie odbiornika GPS.

Warto wspomnieć, że w danym miejscu na Ziemi mamy możliwość odbioru sygnału jedynie z niektórych satelitów. Ponadto im większa liczba satelitów, z których odbieramy sygnał, tym dokładność określenia naszego położenia może być większa. Niestety nie możemy osiągać dowolnie dużej dokładności, gdyż sygnał do którego mamy cywilnie dostęp ma ograniczenia, aby nie mógł zostać wykorzystany np. do naprowadzania broni - jedynie określone osoby i organizacje (np. wojsko) mają dostęp do zaszyfrowanych danych GPS na podstawie których mogą osiągać znacznie lepsze rezultaty w nawigacji.

I powrót na Ziemię, czyli przejdźmy do rzeczy.

Teraz przyszła kolej na nieco praktycznych aspektów nawigacji, gdyż rzecz jasna nie będziemy zabierali się od podstaw za odbiór sygnału GPS i analizę matematyczną odebranych danych - skorzystamy zatem z jednego z gotowych modułów GPS - konkretnie zajmiemy się modułem ARE0077 z odbiornikiem GPS FGPMMOPA6C.

Układ ten dostarcza nam dane w formacie NMEA (podobnie jak większość standardowych odbiorników, stąd wskazówki dalsze prawdopodobnie będą prawdziwe dla innych modułów GPS) za pomocą interfejsu UART i pracuje przy napięciu zasilania 3,3V.


GPS - Opis wyprowadzeń modułu ARE0077 z układem FGPMMOPA6C
Opis wyprowadzeń modułu ARE0077
z układem FGPMMOPA6C



Moduł GPS Gdzie podłączyć/opis działania
1. VCC 3.3V
2. NC
3. GND GND
4. VBAT Zostawiamy niepodłączony lub podłączamy tu plus baterii 3V.
Dzięki temu moduł nie będzie za każdym razem pobierał wszystkich danych z satelitów (co może trwać nawet kilka minut) tylko rozpocznie podawanie lokalizacji dużo szybciej.
5. 3D-Fix Na tym pinie pojawia się na zmianę stan wysoki i niski do momentu w którym moduł zacznie podawać prawidłową pozycję (migająca czerwona dioda na module jest podłącozna do tego pinu)
6. 1PPS Tu co sekundę pojawia się impuls oznaczający precyzyjnie moment, dla którego ważne są odebrane dane, później przekazywane przez UART. U nas różnica ustalania czasu +-1s i taka sama dokładność czasu ustalenia położenia będą wystarczające.
7. NC
8. GND GND
9. TX Transmisja UART z modułu - tu nadawane są ramki NMEA z prędkością 9600bps - podpinamy do pinu D8
10. RX Komunikacja UART do modułu - za pomocą tego wejścia moglibyśmy dokonywać pewnych zmian konfiguracji pracy modułu, jednak domyślna konfiguracja jest w pełni wystarczająća do naszych potrzeb, w związku z czym pin pozostawiamy niepodłączony
Piny po prawej stronie modułu mają zastosowanie jedynie do mocowania, np. na płytce stykowej.


UWAGA! Nigdy nie podłączajmy modułu do płytki Arduino przed wgraniem nowego programu - sygnały wystawiane przez stary program mogą uszkodzić sprzęt.

Moduł GPS ARE0077 na płytce stykowej
Moduł GPS ARE0077 na płytce stykowej

Jeśli już wcześniej pracowaliśmy z podstawowymi płytkami Arduino (czyli nie Mega) to zapewne zauważymy to, że sprzętowy interfejs UART jest już zajęty przez komunikację z komputerem i podłączenie tam bezpośrednio naszego modułu GPS mogłoby spowodować uszkodzenie sprzętu.

W tej sytuacji mamy 2 wyjścia - albo podłączyć między wyjście TX modułu a wejście RX naszego Arduino rezystor o wartości około 330Ω (na prezentowanej płytce zamontowane są już wystarczające rezystory 220Ω), który w przypadku zapomnienia o odłączeniu modułu ochroni oba układy i odłączanie modułu na czas programowania, albo... skorzystanie z programowego UART'u.

Do tego celu moglibyśmy zastosować domyślną bibliotekę, jednak po testach okazało się, że nie radzi ona sobie zbyt dobrze ze sprawnym odbiorem większej ilości danych z modułu. Dobrą alternatywą jest biblioteka AltSoftSerial, która odbiór realizuje po części sprzętowo. W załączniku znajduje się ta biblioteka z modyfikacją rozmiaru buforów nadawczych i odbiorczych, tak aby nie narażać się na utratę danych z modułu i jednocześnie nie zajmować miejsca nieużywanym buforem nadawczym, o czym zaraz. No właśnie, ale właściwie co będziemy odbierać?

Co nam moduł wysyła?

Pomimo, iż cała teoria działania GPS może wydawać się skomplikowana (szczególnie gdy sięgniemy do literatury opisującej wszystkie zagadnienia matematyczne i inne), to w praktyce będziemy odbierać dane o położeniu podane na talerzu... czyli w formie ramek NMEA.

Ramki takie zaczynają się ciągiem $GP a kończą znakami <CR><LF>. W zależności od typu przekazują nam różne informacje, np. o czasie, położeniu, używanych do określani położenia satelitach itp. Nas jednak najbardziej interesowała będzie ramka RMC zawierająca podstawowe informacje o położeniu i czasie, z której najbardziej interesowała nas będzie informacja o położeniu i dodatkowo o aktualnym czasie.

Przykładowa ramka RMC wygląda tak:

$GPRMC,064951.000,A,2307.1256,N,12016.4438,E,0.03,165.48,260406,,,A*55<CR><LF>

Poszczególne pola oddzielane są przecinkami i mają następujące znaczenie:

Pole Wartość Znaczenie
1 $GPRMC Nagłówek i typ ramki: RMC
2 064951.000 Aktualny czas UTC: 06:49:51 (setne części sekundy nie obsługiwane)
3 A Pole informujące o tym czy ramka zawiera prawidłowe położenie: A - tak (wartość V świadczy o tym, ze dane w ramce nie są prawidłowe)
4 2307.1256 Szerokość geograficzna: 23° 07,1256'
5 N Oznaczenie półkuli
6 12016.4438 Długość geograficzna: 120° 12,4438'
7 E Oznaczenie półkuli
8 0.03 Prędkość pozioma w węzłach
9 165.48 Kurs w stopniach
10 260406 Data UTC: 26 kwietnia 2006
11 Deklinacja magnetyczna - wartość
12 Deklinacja magnetyczna - oznaczenie literowe
13 A Tryb pracy: A - autonomiczny, D - dyferencyjny, E - estymowany
14 55 Suma kontrolna ramki (zawsze poprzedzona gwiazdką)


Teraz więc wystarczy tylko odebrać dane z modułu, znaleźć ramkę RMC, odczytać jej pola i możemy z tak otrzymanymi danymi robić co tylko zechcemy.

Mój pierwszy przykładowy program rozpoznaje interesujące nas pola ramki, wyświetla ich wartości i generuje link do map Google pokazujący dane miejsce.

gps_soft_serial_test.c
#include <AltSoftSerial.h>
// Board          Transmit  Receive   PWM Unusable
// Arduino Uno        9         8         10
// Arduino Leonardo   5        13       (none)
// Arduino Mega      46        48       44, 45

AltSoftSerial SoftSerial;

void setup() {
  Serial.begin(9600);
  while (!Serial) ; // wait for Arduino Serial Monitor to open
  Serial.println("GPS Test");
  SoftSerial.begin(9600);
}

char buffer[256];
byte field_begin[14];
int num=0;

const char GPRMC[]="$GPRMC";

byte hh,mm,ss,d,m,y;
char lat_c,lon_c,stat,mode;
int lat_deg,lon_deg;
float lat_min, lon_min;


void loop() {
  char c;//zmienna pomocznicza
  if (SoftSerial.available()) {//jeśli czeka na odbiór jakiś znak
    c = SoftSerial.read();//to odczytujemy go
    if(c=='\n'){//jeśli to znak zakończenia linii (koniec ramki) to przystępujemy do jej dekodowania
      byte ok=1;//zakładamy, ze to interesująca nas ramka
      for(int i=0;i<6;i++){//w pętli porównujemy pierwsze znaki ramki ze wzorcem $GPRMC
        if(buffer[i]!=GPRMC[i])ok=0;//jeśli jakiś znak się różni to to nie jest interesująca nas ramka
      }
      int j=0;
      if(ok){//jeśli mamy interesującą nas ramkę to możemy ją dekodować
      
        byte check=0;//bajt do liczenia sumy kontrolnej
      
        for(int i=0;i<num;i++){//przeglądamy ją znak po znaku
          if(i>0&&i<num-4)check=check^buffer[i];//dla odpowiednich bajtów z ramki liczymy sumę kontrolną
          if(buffer[i]==','||buffer[i]=='*'){//jeśli trafiliśmy na przecinek lub gwiazdkę
            buffer[i]=0;//to zastępujemy go znakiem 0 - kończy on ciąg znaków
            field_begin[j++]=i+1;//kolejny znak po przecinku to początek nowego pola - zapisujemy jego pozycję
            if(j>13){//maksymalnie w ramce NMEA GPRMC moze być 13 pól - jeśli mamy ich więcej to oznacza to błąd
              j=0;
              ok=0;
            }
          }else if(buffer[i]=='\r'||buffer[i]=='\n')buffer[i]=0;//znaki kończące ramkę też zastąpimy znakami zerowymi
        }
        
        //konwertuję sumę kontrolną wyliczoną na zapis heksadecymalny
        char check_hex[3];
        sprintf(check_hex,"%02X",check);
        //i sprawdzam ją z sumą kontrolną odebraną
        if(check_hex[0]!=buffer[field_begin[12]+0]||check_hex[1]!=buffer[field_begin[12]+1])ok=0;
        
      }
      if(ok){
        //tu dla przykłądu wyświetlalibyśmy wszystkie pola
        /*for(int i=0;i<j;i++){
          Serial.println(&buffer[field_begin[i]]);//stosujemy sztuczkę - przekazujemy wskaźnik na pierwszy element danego napisu, co spowoduje,
          //że wyświetlony zostanie ten znak oraz wszystkie kolejne aż do znaku 0, którym to zastąpiliśmy przecinki - taka mała sztuczka a znacznie upraszcza
          //całą pracę i dzięki niej nie musimy kopiować danych do kolejnych buforów.
        }*/
        
        /*
        eg4. for NMEA 0183 version 3.00 active the Mode indicator field is added
           $GPRMC,hhmmss.ss,A,llll.ll,a,yyyyy.yy,a,x.x,x.x,ddmmyy,x.x,a,m*hh
           Field # (w moim programie numery są od 0)
           1    = UTC time of fix
           2    = Data status (A=Valid position, V=navigation receiver warning)
           3    = Latitude of fix
           4    = N or S of longitude
           5    = Longitude of fix
           6    = E or W of longitude
           7    = Speed over ground in knots
           8    = Track made good in degrees True
           9    = UTC date of fix
           10   = Magnetic variation degrees (Easterly var. subtracts from true course)
           11   = E or W of magnetic variation
           12   = Mode indicator, (A=Autonomous, D=Differential, E=Estimated, N=Data not valid)
           13   = Checksum
        */
        
        
        //dekodujemy czas
        hh=(buffer[field_begin[0]+0]-'0')*10+(buffer[field_begin[0]+1]-'0');
        mm=(buffer[field_begin[0]+2]-'0')*10+(buffer[field_begin[0]+3]-'0');
        ss=(buffer[field_begin[0]+4]-'0')*10+(buffer[field_begin[0]+5]-'0');
        
        //status
        stat=buffer[field_begin[1]];
        
        //długość i szerokość geograficzną (pierwsze cyfry to dane w stopniach, a 2 przed kropką i pozostałe to zapis dziesiętny minut połozenia)
        lat_deg=(buffer[field_begin[2]+0]-'0')*10+(buffer[field_begin[2]+1]-'0');
        lat_min=atof(&buffer[field_begin[2]+2]);
        lat_c=buffer[field_begin[3]];
        
        lon_deg=(buffer[field_begin[4]+0]-'0')*100+(buffer[field_begin[4]+1]-'0')*10+(buffer[field_begin[4]+2]-'0');
        lon_min=atof(&buffer[field_begin[4]+3]);
        lon_c=buffer[field_begin[5]];
        
        //dekodujemy datę
        d=(buffer[field_begin[8]+0]-'0')*10+(buffer[field_begin[8]+1]-'0');
        m=(buffer[field_begin[8]+2]-'0')*10+(buffer[field_begin[8]+3]-'0');
        y=(buffer[field_begin[8]+4]-'0')*10+(buffer[field_begin[8]+5]-'0');
        
        //i na końcu tryb określania położenia
        mode=buffer[field_begin[11]];
        
        //wyświetlam dane - na początek godzina i data UTC
        Serial.print("UTC Time & Date: ");
        if(hh<10)Serial.print("0");
        Serial.print(hh);
        Serial.print(":");
        if(mm<10)Serial.print("0");
        Serial.print(mm);
        Serial.print(":");
        if(ss<10)Serial.print("0");
        Serial.print(ss);
        Serial.print(" ");
        if(d<10)Serial.print("0");
        Serial.print(d);
        Serial.print("-");
        if(m<10)Serial.print("0");
        Serial.print(m);
        Serial.print("-");
        if(y<10)Serial.print("0");
        Serial.println(y);
        
        //status poprawności ustalenia lokalizacji
        Serial.print("Status: ");
        Serial.println(stat);
        
        //jeśli moduł ustalił poprawnie lokalizację
        if(stat=='A'){
          //to ją wyświetlam
          Serial.print("Position: ");
          Serial.print(lat_deg);
          Serial.print(" ");
          Serial.print(lat_min,4);
          Serial.print(" ");
          Serial.print(lat_c);
          Serial.print(" ; ");
          Serial.print(lon_deg);
          Serial.print(" ");
          Serial.print(lon_min,4);
          Serial.print(" ");
          Serial.println(lon_c);
          
          //i generuję link do map google przeliczając pozycję na współrzędne przez nich wykorzystywane
          Serial.print("Google maps link: https://www.google.com/maps/@");
          
          float pos=((float)lat_deg)+lat_min/60.0;
          if(lat_c=='S')pos*=-1.0;
          Serial.print(pos,6);
          Serial.print(",");
          
          pos=((float)lon_deg)+lon_min/60.0;
          if(lon_c=='W')pos*=-1.0;
          Serial.print(pos,6);
          Serial.println(",19z");
          
          Serial.print("Mode: ");
          Serial.println(mode);
        
        }
        Serial.println();
   
      }
      num=0;
    }else{//jeśli to inny znak niż zakończenie ramki to dopisujemy go do bufora
      buffer[num++]=c;
      buffer[num]=0;//ponadto zawsze kończymy zawartośc bufora znakiem 0 - koniec ciągu znaków
      if(num>255)num=0;//i dodatkowo dbamy o to, żeby nie przepełnić bufora
    }
  }
}

Do pobrania: gps_soft_serial_test.c (kopia)

Po drobnej modyfikacji części głównej programu możemy zrealizować zapis informacji na kartę pamięci SD. Informacje o tym jak podłączyć kartę SD do zestawu znajdziemy w istniejących poradnikach i na oficjalnych stronach Arduino: http://arduino.cc/en/Reference/SDCardNotes

gps_SD_logger.c
#include <AltSoftSerial.h>
// Board          Transmit  Receive   PWM Unusable
// Arduino Uno        9         8         10
// Arduino Leonardo   5        13       (none)
// Arduino Mega      46        48       44, 45

#include <SD.h>
const int chipSelect = 10;
File dataFile;

AltSoftSerial SoftSerial;


void setup() {
  Serial.begin(9600);
  while (!Serial) ; // wait for Arduino Serial Monitor to open
  Serial.println("GPS Test");
  SoftSerial.begin(9600);
  
   Serial.print("Initializing SD card...");
  // make sure that the default chip select pin is set to
  // output, even if you don't use it:
  pinMode(10, OUTPUT);
  
  // see if the card is present and can be initialized:
  if (!SD.begin(chipSelect)) {
    Serial.println("Card failed, or not present");
    // don't do anything more:
    return;
  }
  Serial.println("card initialized.");
  
}

char buffer[100];
byte field_begin[14];
int num=0;

const char GPRMC[]="$GPRMC";

byte hh,mm,ss,d,m,y;
char lat_c,lon_c,stat,mode;
int lat_deg,lon_deg;
float lat_min, lon_min;


void loop() {
  char c;//zmienna pomocznicza
  if (SoftSerial.available()) {//jeśli czeka na odbiór jakiś znak
    c = SoftSerial.read();//to odczytujemy go
    if(c=='\n'){//jeśli to znak zakończenia linii (koniec ramki) to przystępujemy do jej dekodowania
      byte ok=1;//zakładamy, ze to interesująca nas ramka
      for(int i=0;i<6;i++){//w pętli porównujemy pierwsze znaki ramki ze wzorcem $GPRMC
        if(buffer[i]!=GPRMC[i])ok=0;//jeśli jakiś znak się różni to to nie jest interesująca nas ramka
      }
      int j=0;
      if(ok){//jeśli mamy interesującą nas ramkę to możemy ją dekodować
      
        byte check=0;//bajt do liczenia sumy kontrolnej
      
        for(int i=0;i<num;i++){//przeglądamy ją znak po znaku
          if(i>0&&i<num-4)check=check^buffer[i];//dla odpowiednich bajtów z ramki liczymy sumę kontrolną
          if(buffer[i]==','||buffer[i]=='*'){//jeśli trafiliśmy na przecinek lub gwiazdkę
            buffer[i]=0;//to zastępujemy go znakiem 0 - kończy on ciąg znaków
            field_begin[j++]=i+1;//kolejny znak po przecinku to początek nowego pola - zapisujemy jego pozycję
            if(j>13){//maksymalnie w ramce NMEA GPRMC moze być 13 pól - jeśli mamy ich więcej to oznacza to błąd
              j=0;
              ok=0;
            }
          }else if(buffer[i]=='\r'||buffer[i]=='\n')buffer[i]=0;//znaki kończące ramkę też zastąpimy znakami zerowymi
        }
        
        //konwertuję sumę kontrolną wyliczoną na zapis heksadecymalny
        char check_hex[3];
        sprintf(check_hex,"%02X",check);
        //i sprawdzam ją z sumą kontrolną odebraną
        if(check_hex[0]!=buffer[field_begin[12]+0]||check_hex[1]!=buffer[field_begin[12]+1])ok=0;
        
      }
      if(ok){
        //tu dla przykłądu wyświetlalibyśmy wszystkie pola
        /*for(int i=0;i<j;i++){
          Serial.println(&buffer[field_begin[i]]);//stosujemy sztuczkę - przekazujemy wskaźnik na pierwszy element danego napisu, co spowoduje,
          //że wyświetlony zostanie ten znak oraz wszystkie kolejne aż do znaku 0, którym to zastąpiliśmy przecinki - taka mała sztuczka a znacznie upraszcza
          //całą pracę i dzięki niej nie musimy kopiować danych do kolejnych buforów.
        }*/
        
        /*
        eg4. for NMEA 0183 version 3.00 active the Mode indicator field is added
           $GPRMC,hhmmss.ss,A,llll.ll,a,yyyyy.yy,a,x.x,x.x,ddmmyy,x.x,a,m*hh
           Field # (w moim programie numery są od 0)
           1    = UTC time of fix
           2    = Data status (A=Valid position, V=navigation receiver warning)
           3    = Latitude of fix
           4    = N or S of longitude
           5    = Longitude of fix
           6    = E or W of longitude
           7    = Speed over ground in knots
           8    = Track made good in degrees True
           9    = UTC date of fix
           10   = Magnetic variation degrees (Easterly var. subtracts from true course)
           11   = E or W of magnetic variation
           12   = Mode indicator, (A=Autonomous, D=Differential, E=Estimated, N=Data not valid)
           13   = Checksum
        */
        
        
        //dekodujemy czas
        hh=(buffer[field_begin[0]+0]-'0')*10+(buffer[field_begin[0]+1]-'0');
        mm=(buffer[field_begin[0]+2]-'0')*10+(buffer[field_begin[0]+3]-'0');
        ss=(buffer[field_begin[0]+4]-'0')*10+(buffer[field_begin[0]+5]-'0');
        
        //status
        stat=buffer[field_begin[1]];
        
        //długość i szerokość geograficzną (pierwsze cyfry to dane w stopniach, a 2 przed kropką i pozostałe to zapis dziesiętny minut połozenia)
        lat_deg=(buffer[field_begin[2]+0]-'0')*10+(buffer[field_begin[2]+1]-'0');
        lat_min=atof(&buffer[field_begin[2]+2]);
        lat_c=buffer[field_begin[3]];
        
        lon_deg=(buffer[field_begin[4]+0]-'0')*100+(buffer[field_begin[4]+1]-'0')*10+(buffer[field_begin[4]+2]-'0');
        lon_min=atof(&buffer[field_begin[4]+3]);
        lon_c=buffer[field_begin[5]];
        
        //dekodujemy datę
        d=(buffer[field_begin[8]+0]-'0')*10+(buffer[field_begin[8]+1]-'0');
        m=(buffer[field_begin[8]+2]-'0')*10+(buffer[field_begin[8]+3]-'0');
        y=(buffer[field_begin[8]+4]-'0')*10+(buffer[field_begin[8]+5]-'0');
        
        //i na końcu tryb określania położenia
        mode=buffer[field_begin[11]];
        
        //status poprawności ustalenia lokalizacji
        Serial.print("Status: ");
        Serial.println(stat);
        
        //jeśli moduł ustalił poprawnie lokalizację
        if(stat=='A'){
          
           // open the file. note that only one file can be open at a time,
            // so you have to close this one before opening another.
            dataFile = SD.open("datalog.txt", FILE_WRITE);
          
            // if the file is available, write to it:
            if (dataFile) {
              
              pos=((float)lon_deg)+lon_min/60.0;
              if(lon_c=='W')pos*=-1.0;
              dataFile.print(pos,6);
              dataFile.print(",");
              
              float pos=((float)lat_deg)+lat_min/60.0;
              if(lat_c=='S')pos*=-1.0;
              dataFile.print(pos,6);
              dataFile.println();
              dataFile.close();
              // print to the serial port too:
            }  
            // if the file isn't open, pop up an error:
            else {
              Serial.println("error opening datalog.txt");
            } 
        
        }
        Serial.println();
   
      }
      num=0;
    }else{//jeśli to inny znak niż zakończenie ramki to dopisujemy go do bufora
      buffer[num++]=c;
      buffer[num]=0;//ponadto zawsze kończymy zawartośc bufora znakiem 0 - koniec ciągu znaków
      if(num>255)num=0;//i dodatkowo dbamy o to, żeby nie przepełnić bufora
    }
  }
}

Do pobrania: gps_SD_logger.c (kopia)

UWAGA! Program powyższy prawie całkowicie zajmuje pamięć SRAM mikrokontrolera ATMega328p (biblioteka AltSoftSerial zoptymalizowana pod kątem rozmiaru buforów, obsługa karty SD i dekodowana). Dlatego dalsza rozbudowa tego programu i deklarowanie kolejnych zmiennych może doprowadzić do nieoczekiwanych i nieprzewidywalnych błędów.


Później dane z pliku txt na karcie SD musimy tylko skopiować, wkleić w odpowiednie miejsce pliku KML (otwieramy za pomocą notatnika i zmieniamy wszystkie dane w sekcji coordinates) załączonego do artykułu i wczytać do programu Google Earth.


Moduł GPS ARE0077 i Arduino z kartą SD
Moduł GPS ARE0077 i Arduino z kartą SD

Do pobrania:

Podsumowanie

Jak widzimy dzięki bardzo prostemu oprogramowaniu możemy z łatwością dekodować dane z modułu GPS i dowolnie je wykorzystać. Stosując większe Arduino (np. Mega) możemy wykonać bardziej zaawansowany zapis danych czy proste systemy nawigacyjne.

Ciekawym zastosowaniem modułu i Arduino może być gra, w której aby otworzyć pudełko należy znaleźć się w odpowiednim miejscu, a urządzenie przed "zablokowaniem się na wieki" może tylko 4 razy podać odległość od poszukiwanego punktu... Mam nadzieję, że zainspiruje to Was do realizacji ciekawych projektów. Powodzenia!


3 komentarze:

  1. od Wrocławia jest okrąg ( może sięga po Toruń, ale środek jest we Wrocławiu)

    "Dalej jeśli znamy odległości od Krakowa i Torunia (dodatkowo strzałka czerwona) to możemy powiedzieć, że znajdujemy się gdzieś na przecięciu tych okręgów, czyli w punkcie granatowym lub niebieskim. Dodając do tego znajomość odległości do Warszawy odnajdujemy już tylko 1 konkretny punkt."

    Pozdrawiam,
    Andrzej

    OdpowiedzUsuń
  2. Witam serdecznie,

    Uprzejmie zwracam się z prośbą do autora czy mógłby wyjaśnić lepiej stwierdzenie "U nas różnica ustalania czasu +-1s i taka sama dokładność czasu ustalenia położenia będą wystarczające". Nie bardzo rozumiem w czym rzecz, natomiast poprzez dokonane pomiary stwierdzam że mniej więcej jednocześnie po złapaniu sygnału gps następuje zakończenie migania pinu 3DFIX oraz rozpoczęcie migania pinu 1PPS.

    OdpowiedzUsuń
  3. Czy autor lub ktoś z obecnych mógłby odnieść się do mojego pytania?
    Bardzo bym prosił, zależy mi gdyż aktualnie jestem na etapie budowy urządzenia pomiarowego opartego właśnie o ten moduł gps...

    OdpowiedzUsuń