Logger GPS – Arduino

Obecnie niemal każdy z nas korzysta z GPS znajdującego się w naszym smartfonie. Czy to w celu nawigacji, zapisaniu przebytej trasy, rozrywce takiej jak geocaching czy w wielu innych sytuacjach. W znacznej części wypadków smartfon jest doskonałym rozwiązaniem niemniej niekiedy może przydać nam się dedykowane rozwiązanie do zapisywania naszej trasy np. przy kilkudniowej wycieczce bez dostępu do prądu czy po prostu gdy bateria w naszym telefonie jest już na wyczerpaniu.


Na rynku istnieją gotowe loggery GPS niemniej wybór nie jest zbyt szeroki i atrakcyjny zapewne z powodu tego że większość z nas korzysta z telefonu komórkowego. Dlatego postanowiłem stworzyć logger GPS który będzie nie drogi i wytrzyma bez problemu dłuższy czas pracy a łatwa wymiana baterii i dużą pamięć powinny pozwoli na bardzo długą tułaczkę czy inną wyprawę.


W celu zbudowania loggera GPS będziemy potrzebowali:


  • Mikrokontroler Atmeg 328 P-PU
  • Czytnik kard micro sd
  • NEO-6M GPS
  • LM7805CV
  • 2x baterie 18650
  • Pushbutton
  • Switch
  • 16 MHz rezonator kwarcowy
  • 2×22 nf kondensator
  • 1-10k rezystor
  • Dioda

Sercem układu jest moduł NEO-6M GPS odpowiedzialny za odbiór sygnału GPS. Dane pobrane przez GPS będą podawane obróbce i zapisywane na kartę micro sd. Całość jest zasilana z dwóch baterii 18650. Ich sumaryczne napięcie przy pełnym naładowaniu wynosi 7,6 V. Wszystkie moduły w układzie nie mogą pracować z napięciem większym niż 5V dlatego wykorzystujemy regulator napięcia LM7805CV. W celu przerwania odczytu danych z GPSi dokonania ostatniego zapisu wykorzystujemy push button. Zainstalowana dioda pozwala nam na łatwe obserwowanie pracy układu. Całość jest zarządzana przez mikrokontroler Atmega 328 P-PU. Schemat elektryczny oraz schemat pokazujący jak w celach testowych podłączyć moduły do Arduino UNO są przedstawione poniżej:



Podłączenie Neo-6M GPS polega na podpięciu 5V, uziemienia oraz dwóch pinów do komunikacji TX (podłączony do pinu 6) oraz RX (podłączony do pinu 11). Czytnik kart pamięci pozwala na zapisanie aktualnej szerokości i długości geograficznej wraz z wysokością jak też podaje informacje o aktualnej dacie i godzinie. On również wymaga zasilania 5V oraz uziemienia. Komunikacja z mikrokontrolerem odbywa się przy użyciu pinów MOSI (podłączony do pinu 17), MISO (podłączony do pinu 18), SCK (podłączony do pinu 19), CS (podłączony do pinu 16) przy wykorzystaniu popularnego protokułu SPI. Protokół SPI pozwala na komunikacje między sobą dwóch komponentów. W połączeniu tym dwa komponenty działają że sobą w relacji typy master-slave. MOSI Odpowiada za przesyłanie informacji z urządzenia master do urządzenia slave. MISO przesyła informacje z urządzenia slave do urządzenia master. SCK to zegar umożliwiający synchronizacje danych. CS pozwala na wybór urządzenia slave. Switch button podłączony jest z wykorzystaniem pull-down rezystorem jego naciśnięcie powoduje wysłanie sygnału do 5 pinu Atmegi wywołując metodę przerwania logowania danych GPS. Dioda podłączona do pinu 14 informuje nas o pracy całego układu.


Napisany kod do tego układu prezentuję się następująco:


#include <BlockDriver.h>
#include <FreeStack.h>
#include <MinimumSerial.h>
#include <SdFat.h>
#include <SdFatConfig.h>
#include <sdios.h>
#include <SysCall.h>

#pragma execution_character_set("utf-8")
#include <TinyGPS++.h>
#include <SoftwareSerial.h>
#include <SPI.h>

int RXPin = 5;
int TXPin = 4;

int GPSBaud = 9600;

TinyGPSPlus gps;

SoftwareSerial gpsSerial(RXPin, TXPin);

int CS_PIN = 10;
SdFile file;
SdFat sd;
char rememberFileName[20];
bool firstStart = true;
volatile bool saveBeforeOff = false;
unsigned long lastToggle;
long times = 1;

void setup()
{
  Serial.begin(9600);
  gpsSerial.begin(GPSBaud);
  pinMode(7, OUTPUT);
  initializeSD();
  lastToggle = millis();
  digitalWrite(7, HIGH);
  attachInterrupt(0, saveBeforeOffFunction, RISING);
}

void loop()
{

  while (gpsSerial.available() > 0){
    if (gps.encode(gpsSerial.read())){
      if (millis() - lastToggle > 500) {
        writePoint();
      }
    }
  }

  if (millis() > 5000 && gps.charsProcessed() < 10)
  {
    while(true);
  }
}

void saveBeforeOffFunction(){
  saveBeforeOff = true;
}

void writePoint(){

  digitalWrite(7, HIGH);
  if (!gps.location.isValid() || !gps.date.isValid() || !gps.time.isValid()){
    return;
  }

  if(firstStart){
    char output[16];
    itoa(gps.date.year(), output, 10);

    rememberFileName[0] = output[0];
    rememberFileName[1] = output[1];
    rememberFileName[2] = output[2];
    rememberFileName[3] = output[3];

    itoa(gps.date.month(), output, 10);
    if(gps.date.month() < 10){
      rememberFileName[4] = '0';
      rememberFileName[5] = output[0];
    }
    else{
      rememberFileName[4] = output[0];
      rememberFileName[5] = output[1];
    }

    itoa(gps.date.day(), output, 10);
    if(gps.date.day() < 10){
      rememberFileName[6] = '0';
      rememberFileName[7] = output[0];
    }
    else{
      rememberFileName[6] = output[0];
      rememberFileName[7] = output[1];
    }
    rememberFileName[8] = '_';


    itoa(gps.time.hour(), output, 10);
    if(gps.time.hour() < 10){
      rememberFileName[9] = '0';
      rememberFileName[10] = output[0];
    }
    else{
      rememberFileName[9] = output[0];
      rememberFileName[10] = output[1];
    }
    itoa(gps.time.minute(), output, 10);
    if(gps.time.minute() < 10){
      rememberFileName[11] = '0';
      rememberFileName[12] = output[0];
    }
    else{
      rememberFileName[11] = output[0];
      rememberFileName[12] = output[1];
    }
    itoa(gps.time.second(), output, 10);
    if(gps.time.second() < 10){
      rememberFileName[13] = '0';
      rememberFileName[14] = output[0];
    }
    else{
      rememberFileName[13] = output[0];
      rememberFileName[14] = output[1];
    }

    rememberFileName[15] = '.';
    rememberFileName[16] = 't';
    rememberFileName[17] = 'x';
    rememberFileName[18] = 't';
    rememberFileName[19] = '\0';

    createFile(rememberFileName);
    openFile(rememberFileName);
    firstStart=false;
  }
    writeNewLine();
    writeToFile("<trkpt lat=\"");
    doubleValueWrite(gps.location.lat());
    writeToFile("\" lon=\"");
    doubleValueWrite(gps.location.lng());
    writeToFile("\">");
    writeNewLine();
    
     writeToFile("<ele>");
     if(gps.altitude.isValid()){
      doubleValueWrite(gps.altitude.meters());
     }
     else{
      writeToFile("0");
     }

    writeToFile("/<ele>");
    writeNewLine();
    writeToFile("<time>");

    intValueWrite(gps.date.year());
    writeToFile("-");
    if (gps.date.month() < 10)
       writeToFile("0");
    intValueWrite(gps.date.month());
    writeToFile("-");
    if (gps.date.day() < 10)
       writeToFile("0");
    intValueWrite(gps.date.day());
    writeToFile("T");
    
    if (gps.time.hour() < 10)
      writeToFile("0");
    intValueWrite(gps.time.hour());
    writeToFile(":");
    
    if (gps.time.minute() < 10)
      writeToFile("0");
    intValueWrite(gps.time.minute());
    writeToFile(":");
    
    if (gps.time.second() < 10)
      writeToFile("0");
    intValueWrite(gps.time.second());
    writeToFile("Z");
    
    writeToFile("</time>");
    writeNewLine();
    writeToFile("</trkpt>");

times++;
if(times%1000==0){
  closeFile();
  openFile(rememberFileName);
}
digitalWrite(7, LOW);
lastToggle = millis();

if(saveBeforeOff){
    writeNewLine();
    writeToFile("</trkseg>");
    writeNewLine();
    writeToFile("</trk>");
    writeNewLine();
    writeToFile("</gpx>");
  
    int blinkiLight = 0;
    while(blinkiLight<25){
       digitalWrite(7, HIGH);
       delay(100);
       digitalWrite(7, LOW);
       delay(100);
       blinkiLight++;
     }
  digitalWrite(7, LOW);
  while(true);
}
}

void doubleValueWrite(double input){
  char output[10];
  dtostrf(input, 0, 8, output);
  writeToFile(output);
}

void intValueWrite(int input){
  char output[16];
  itoa(input, output, 10);
  writeToFile(output);
}

void initializeSD()
{
  digitalWrite(7, HIGH);
  delay(1000);
  pinMode(CS_PIN, OUTPUT);
  sd.begin();
  digitalWrite(7, LOW);
  delay(1000);  
}

int writeToFile(char text[])
{
    file.print(text);
    file.flush();
}

int writeNewLine()
{
    file.println("");
    file.flush(); 
}

void closeFile()
{  
    file.close();  
}

void openFile(char filename[])
{
  file.open(filename, FILE_WRITE);
}
void createFile(char filename[])
{
  file.open(filename, FILE_WRITE);

  char headerName[] = "header.txt";
  SdFile headerFile;
  headerFile.open(headerName, FILE_READ);
  
  int data;

  while ((data = headerFile.read()) >= 0) {
    file.write(data);
  }

 file.close();
 headerFile.close(); 
}

Do poprawnego działania tego kodu będziemy potrzebowali kilka bibliotek. Wszystkie informacje jakie otrzymujemy od modułu gps są w standardzie NMEA (National Marine Electronics Association) wyglądający przykładowo tak $GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47. Żeby czytanie naszej pozycji było prostsze korzystamy z biblioteki TinyGps którą można pobrać z https://github.com/mikalhart/TinyGPSPlus/archive/master.zip


Kolejna potrzebna biblioteka to SDFat dostępna pod https://github.com/greiman/SdFat Wykorzystujemy ją w projekcie bowiem domyślna biblioteka współpracująca z kartami sd pozwala jedynie zapisywać pliki których nazwa nie przekracza 8 znaków. W projekcie pliki zapisywane są jako data kiedy rozpoczęto zapisywanie danych co wymaga więcej niż 8 znaków.


Na samym początku w części setup() inicjalizujemy wszystkie obiekty. Później w stałej pętli loop() sprawdzamy dane z gpsu. Jeśli uda nam się pobrać dane z GPS w odstępie 0,5 s będziemy zapisywać pobrany punkt.


Metoda writePoint() na samym początku zapala diodę sygnalizując początek zapisu. Sprawdzamy czy możemy odczytać wszystkie informacje z gpsu. Jeśli jest to pierwsze uruchomienie urządzenia tworzymy plik tekstowy z nazwą odpowiadającej aktualnej dacie i czasie. Informacje zapisywane są w standardzie gpx. Dlatego dla każdego nowo utworzonego pliku kopiujemy do niego niniejsze informacje (funkcja createFile):


<?xml version="1.0" encoding="UTF-8"?>
<gpx version="1.1" creator="Sariel" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd" xmlns="http://www.topografix.com/GPX/1/1" xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1" xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <metadata>
    <author>
      <name>Sariel</name>
    </author>
    <link href="http://binaryalchemist.pl">
      <text>Binary Alchemist</text>
    </link>
  </metadata>
  <trk>
    <trkseg>

Informacje te znajdują się w pliku header.txt i plik ten powinien znajdować się na naszej karcie sd aby program działał prawidłowo.


Po utworzeniu pliku zapisujemy do niego dane z GPS czyli szerokość, długość wysokość oraz aktualną datę i zapisujemy je do utworzonego pliku na karcie sd w formacie gpx. Pojedynczy wpis może wyglądać następująco:


<trkpt lat="58.34874300" lon="21.62502100">
<ele>0/<ele>
<time>2020-01-12T16:29:00Z</time>
</trkpt>

Kiedy chcemy zakończyć logowanie danych naciskami pushbutton. Powoduje to natychmiastowe wywołanie metody przerwania saveBeforeOffFunction. Zmienia ona wartość saveBeforeOff na true. W wyniku czego w funkcji writePoint() dochodzi do zapisania ostatniego punktu GPS oraz zamknięcia otwartych wcześniej znaczników.


</trkseg>
</trk>
</gpx>

Całość wizualizowana jest przez kilkukrotnie migającą diode led. Po tym możemy bezpiecznie wyłączyć logger GPS.


Kiedy mamy wszystko gotowe całość musimy przenieść ma płytkę prototypową i zalutować. W moim wypadku wyglądało to jak poniżej. Niestety ostateczne gabaryty tego urządzenia okazały się stosunkowo duże. Niemniej na pewno dałoby się to rozmieścić bardziej optymalnie a jeśli mielibyśmy więcej czasu i chęci najlepszym wyjściem byłoby przygotowanie płytki drukowanej.



Ostatnia rzecz to umieszczenie naszego urządzenia w odpornym pojemniku. Zapewne znajdziemy wiele pasujących kontenerów po produktach spożywczych. Niemniej jeśli mamy drukarkę 3d możemy stworzyć dopasowany co do milimetra pojemnik na nasz logger GPS.



Wszystkie pliki potrzebne do stworzenia Loggera Gps:

arduino code

header.txt

3d print box blender file

3d print box stl file

3d print box front stl file

3d print box top file

Dodaj komentarz

WordPress Video Lightbox Plugin