Hur jag dekoder GPS-data

Användarvisningsbild
Icecap
Inlägg: 26105
Blev medlem: 10 januari 2005, 14:52:15
Ort: Aabenraa, Danmark

Hur jag dekoder GPS-data

Inlägg av Icecap »

Jag tänkte att en mjukvarulösning kan vara lika mycket projekt som ett fysiskt bygge.

Från en GPS-mottagare får man oftast ut NMEA-data i form av strängar. Dessa data beskriver datum & tid för en positionsfix, antal satellit som det mäts på och en massa annat. En del av dessa meddelanden kommer på "fasta" tidpunkter och resten kommer däremellan.

Jag har vald att fokusera på "GPRMC"-meddelandet som är en "bare minimum GPS-data". Orsaken är att det meddelanden innehåller datum & tid för positionsbestämmandet (fixet) samt fixets position (latitude & longitude) - vilket är vad jag vill ha.

På den mottagare jag använder finns det en separat utgång som ger en puls varje sekund (mycket precist) men då jag kopplar in på en seriell port får det vara med den. Jag får alltså som bäst 1 sekunds noggrannhet - och det duger för mig anser jag.

Min GPS-mottagare är DUM! Den har ingen EEPROM eller batteri backup så jag KAN ställa saker om vilka data jag vill ha och hur snabbt - men de spara inte och återställs varje gång jag startar om.

Nåväl, de strängar jag får ser ut som följer:
$GPGSV,3,2,12,03,34,237,12,17,26,314,23,28,18,275,25,08,18,181,14*77
$GPGSV,3,3,12,14,49,103,20,32,43,070,04,27,65,208,,09,58,184,*72
$GPRMC,095515.000,A,5923.2266,N,01328.7139,E,0.00,223.12,291018,,,A*69
$GPGGA,095516.000,5923.2266,N,01328.7139,E,1,07,2.1,55.6,M,35.5,M,,0000*6A
$GPGSA,A,3,01,18,11,22,17,28,08,,,,,,2.7,2.1,1.7*3E
$GPRMC,095516.000,A,5923.2266,N,01328.7139,E,0.00,223.12,291018,,,A*6A
$GPGGA,095517.000,5923.2266,N,01328.7139,E,1,07,2.1,55.6,M,35.5,M,,0000*6B
$GPGSA,A,3,01,18,11,22,17,28,08,,,,,,2.7,2.1,1.7*3E
$GPRMC,095517.000,A,5923.2266,N,01328.7139,E,0.00,223.12,291018,,,A*6B
$GPGGA,095518.000,5923.2266,N,01328.7139,E,1,07,2.1,55.6,M,35.5,M,,0000*64
$GPGSA,A,3,01,18,11,22,17,28,08,,,,,,2.7,2.1,1.7*3E
$GPRMC,095518.000,A,5923.2266,N,01328.7139,E,0.00,223.12,291018,,,A*64
$GPGGA,095519.000,5923.2266,N,01328.7139,E,1,07,2.1,55.6,M,35.5,M,,0000*65

Varje sträng avslutas med CRLF.

När man startar en GPS-mottagare "kallt" (Power On) saknar den information om satelliternas banor, dessa ska laddas ner först så en fix tar sin lilla tid. Det kan finnas olika sätt att hålla kvar denna information men jag har accepterat att så är det.

Jag kallar rutinen med en pekare till datasträngen samt en pekare på det datablock som resultatet ska klämmas in i.

Jag bryr mig bara om meddelanden av typen GPRMC och det kommer varje sekund. Ett exempel:
$GPRMC,095518.000,A,5923.2266,N,01328.7139,E,0.00,223.12,291018,,,A*64

Först kollar jag om det verkligen är en GPRMC:
if(strncmp(Datastring, "$GPRMC", 6)) return false; // If not correct type bail out

Sedan tar jag en pekare (_UBYTE* Ptr) som jag använder för att stega in i texten.
Först ska jag kolla checksummen. Den består av en XOR av alla bytes mellan den inledande '$' och '*' omedelbart innan checksummen i HEX. Startvärde är 00h.

Under tiden jag stegar igenom datan för att hitta '*' kör jag en XOR på alla bytes, startande direkt EFTER '$' och slutande direkt INNAN '*'. Sedan tar jag min "hex2bin"-rutin och omvandlar (i detta fall) "64" till 100 decimalt (= 64h). Detta värde XOR jag sedan med den checksum som jag har XOR'et med alla bytens och slutresultatet ska bli noll.

Om checksummen efter detta är icke-noll returnerar jag från rutinen med en false för att indikera att det pruttade sig.

Nu är det rätt meddelandetyp och checksummen är OK, då kör vi!

Det blev snart klart för mig att de olika fält kan ha olika storlekar. Visst, vissa är fasta men andra kan variera - så jag behöver en rutin som kan leta upp dessa fält. Den ser ut som följer:
while(*Ptr && (',' != *Ptr)) Ptr++; Ptr++; // Find a comma + 1

Ptr initieras till att peka på första byte i textsträngen och sedan kör jag.

Första fält som pekas på är tiden: 095518.000
Detta motsvarar 09:55:18 UTC (eller Zulu om man vill) och ".000" betyder att den är låst "ordentligt". Ja, jag kollar på ".000" om de verkligen är ".000", är de INTE brukar resten av data vara kassa och jag hoppar ur rutinen med dåligt resultat.

Stegar till nästa fält och ett 'A' där betyder att den är i synk och har fått tag på data. Är det INTE 'A' där lämnar jag rutinen med dåligt resultat.

Nästa fält är "5923.2266" (latitude). Denna ska läsas som 59°23'22.66" men jag vill ha den som decimaltal så jag räknar ut den:
Först omvandla "2266" (sekunderna) till flyttal: 22.66.
Sedan dela med 60.0.
Sedan lägga till 23 (minuterna).
Sedan dela den summa med 60.0.
Och slutligen lägga till graderna (59) och jag får 59.38962777 i latitude.
Nästa fält är (N)orr eller (S)yd. Om det är 'S' gör jag en:
Latitude *= -1.0;

Jag gör det samma med longitude fast där är det East/West och jag vänder förtecken vid 'E'.

Sedan stegar jag mig från komma till komma till jag når "291018" som är datumfältet. I detta fall betyder det ??18-10-29. Då årtalet ju bara är tvåsifrigt kollar jag om det är lägre än 2000, är det lägger jag 2000 till och får då 2018-10-29.

Nu är allt klart och då jag har definierat en datablock som tar dessa värden skrivs de in i det block eftersom de räknas ut.
Sedan avslutar jag rutinen med en true för att ange att det gick bra.
Användarvisningsbild
rvl
Inlägg: 5719
Blev medlem: 5 april 2016, 14:58:53
Ort: Helsingfors

Re: Hur jag dekoder GPS-data

Inlägg av rvl »

Nästa fält är "5923.2266" (latitude). Denna ska läsas som 59°23'22.66" men jag vill ha den som decimaltal så jag räknar ut den:
Det brukar betyda decimalminuter utan inblandning av sekunder (DDmm.mmmm). Säker på att just din mottagare inte menar 59°23.2266'? Dessutom låter longitudens 71.39" (>60") väldigt osannolikt.
Jag gör det samma med longitude fast där är det East/West och jag vänder förtecken vid 'E'.
Typo med E istället för W, eller nån anledning att göra "tvärtom"?
Skriv svar