Jak to się robi dziś – wiadomo, nic wielkiego. Dedykowany hardware, biblioteka z netu i ciach -mamy pozycję z nieba. Albo -jak we wpisach obok – Android, geolokalizacja przeglądarki…

A kiedyś? Mamy -powiedzmy odebrany sygnał GPSa, nadawany w postaci NMEA 0182/183 z prędkością 9600Bd i 8 bitowy procesor bez peryferiów. Jak odebrać, zrozumieć etc?

Trąci myszką? Wiem, ale napiszę.

Procesor to Z80A. Zegar 4MHz, pamięci RAM i EPROM w osobnych kościach, podobnie port szeregowy 8251A (jeden z kilku), o są jeszcze do transmisji z komputerem RS232 oraz do transmisji z NOKIA5110 via mBus – nadawanie i odbiór SMS. Mamy rok ~2002 ?

Nie mam emulatora ani debugera ani asemblera Z80. A – przepraszam, mam asembler -sam napisałem w Pascalu. Zatem mogę programować mnemonikami i z komentarzem nawet, nie muszę w HEX…

Sygnał przychodzi 9600 bodów, a więc tyle bitów w czasie sekundy to jego max prędkość. Pamiętać należy o bicie startu, bicie parzystości i bicie stopu. Zatem jeden nadany bajt zajmuje 11 bitów –> w sekundzie 9600/11 = ok. 873 bajty, odbierane przez 8251A bajt po bajcie i konieczne do odczytania przez procesor, zanim nadejdzie kolejny bajt.

NMEA wygląda podobnie do:

$GPRMC,144651.271,A,5354.2019,N,01415.1032,E,2.01,175.95,011206,,,A*6D
$GPVTG,175.95,T,,M,2.01,N,3.7,K,A*05

$GPGGA,144652.271,5354.2018,N,01415.1036,E,1,10,1.1,57.8,M,,,,0000*39
$GPGSA,A,3,27,06,19,18,16,21,18,29,03,07,,,1.9,1.1,1.5*3E
$GPRMC,144652.271,A,5354.2018,N,01415.1036,E,1.97,178.01,011206,,,A*67
$GPVTG,178.01,T,,M,1.97,N,3.6,K,A*08

$GPGGA,144653.271,5354.2016,N,01415.1042,E,1,10,1.1,58.3,M,,,,0000*31
$GPGSA,A,3,27,06,19,18,16,21,18,29,03,07,,,1.9,1.1,1.5*3E
$GPRMC,144653.271,A,5354.2016,N,01415.1042,E,2.06,176.34,011206,,,A*68
$GPVTG,176.34,T,,M,2.06,N,3.8,K,A*05

$GPGGA,144654.271,5354.2012,N,01415.1051,E,1,10,1.1,58.8,M,,,,0000*3B
$GPGSA,A,3,27,06,19,18,16,21,18,29,03,07,,,1.9,1.1,1.5*3E
$GPRMC,144654.271,A,5354.2012,N,01415.1051,E,2.11,177.37,011206,,,A*6D
$GPVTG,177.37,T,,M,2.11,N,3.9,K,A*00

$GPGGA,144655.271,5354.2006,N,01415.1053,E,1,10,1.1,59.3,M,,,,0000*37
$GPGSA,A,3,27,06,19,18,16,21,18,29,03,07,,,1.9,1.1,1.5*3E
$GPRMC,144655.271,A,5354.2006,N,01415.1053,E,2.13,179.38,011206,,,A*68
$GPVTG,179.38,T,,M,2.13,N,4.0,K,A*0D

$GPGGA,144656.271,5354.2000,N,01415.1054,E,1,10,1.1,59.6,M,,,,0000*30
$GPGSA,A,3,27,06,19,18,16,21,18,29,03,07,,,1.9,1.1,1.5*3E

$GPGSV,3,1,11,21,59,073,46,16,58,218,45,07,50,114,46,03,47,285,46*7B
$GPGSV,3,2,11,18,43,127,45,22,23,164,,19,19,284,41,27,13,327,36*7E
$GPGSV,3,3,11,06,12,109,33,29,11,047,41,26,09,060,39*48
$GPRMC,144656.271,A,5354.2000,N,01415.1054,E,2.18,180.20,011206,,,A*6E
$GPVTG,180.20,T,,M,2.18,N,4.0,K,A*09

$GPGGA,144657.270,5354.1993,N,01415.1051,E,1,10,1.1,60.0,M,,,,0000*39
$GPGSA,A,3,27,06,19,18,16,21,18,29,03,07,,,1.9,1.1,1.5*3E
$GPRMC,144657.270,A,5354.1993,N,01415.1051,E,2.24,180.72,011206,,,A*63
$GPVTG,180.72,T,,M,2.24,N,4.2,K,A*03
$GPGGA,144658.270,5354.1988,N,01415.1047,E,1,10,1.1,60.5,M,,,,0000*3E
$GPGSA,A,3,27,06,19,18,16,21,18,29,03,07,,,1.9,1.1,1.5*3E
$GPRMC,144658.270,A,5354.1988,N,01415.1047,E,2.29,181.90,011206,,,A*61
$GPVTG,181.90,T,,M,2.29,N,4.2,K,A*03
$GPGGA,144659.270,5354.1983,N,01415.1047,E,1,10,1.1,60.2,M,,,,0000*33
$GPGSA,A,3,27,06,19,18,16,21,18,29,03,07,,,1.9,1.1,1.5*3E
$GPRMC,144659.270,A,5354.1983,N,01415.1047,E,2.33,182.08,011206,,,A*62
$GPVTG,182.08,T,,M,2.33,N,4.3,K,A*0B
$GPGGA,144700.270,5354.1978,N,01415.1041,E,1,10,1.1,60.7,M,,,,0000*39
$GPGSA,A,3,27,06,19,18,16,21,18,29,03,07,,,1.9,1.1,1.5*3E
$GPRMC,144700.270,A,5354.1978,N,01415.1041,E,2.38,182.82,011206,,,A*64
$GPVTG,182.82,T,,M,2.38,N,4.4,K,A*05
$GPGGA,144701.270,5354.1972,N,01415.1038,E,1,10,1.1,59.6,M,,,,0000*37
$GPGSA,A,3,27,06,19,18,16,21,18,29,03,07,,,1.9,1.1,1.5*3E
$GPGSV,3,1,11,21,59,073,45,16,58,218,45,07,50,114,44,03,47,285,45*79
$GPGSV,3,2,11,18,43,127,45,22,23,164,,19,19,284,43,27,13,327,32*78
$GPGSV,3,3,11,06,12,109,32,29,11,047,40,26,09,060,38*49
$GPRMC,144701.270,A,5354.1972,N,01415.1038,E,2.43,183.10,011206,,,A*67
$GPVTG,183.10,T,,M,2.43,N,4.5,K,A*02
$GPGGA,144702.270,5354.1969,N,01415.1031,E,1,10,1.1,59.4,M,,,,0000*35
$GPGSA,A,3,27,06,19,18,16,21,18,29,03,07,,,1.9,1.1,1.5*3E
$GPRMC,144702.270,A,5354.1969,N,01415.1031,E,2.49,182.29,011206,,,A*66
$GPVTG,182.29,T,,M,2.49,N,4.6,K,A*00
$GPGGA,144703.270,5354.1965,N,01415.1024,E,1,10,1.1,58.3,M,,,,0000*3A
$GPGSA,A,3,27,06,19,18,16,21,18,29,03,07,,,1.9,1.1,1.5*3E
$GPRMC,144703.270,A,5354.1965,N,01415.1024,E,2.42,185.01,011206,,,A*69
$GPVTG,185.01,T,,M,2.42,N,4.5,K,A*05
$GPGGA,144704.270,5354.1960,N,01415.1022,E,1,10,1.1,57.4,M,,,,0000*36
$GPGSA,A,3,27,06,19,18,16,21,18,29,03,07,,,1.9,1.1,1.5*3E
$GPRMC,144704.270,A,5354.1960,N,01415.1022,E,2.36,183.57,011206,,,A*6B
$GPVTG,183.57,T,,M,2.36,N,4.4,K,A*02
$GPGGA,144705.270,5354.1953,N,01415.1024,E,1,10,1.1,56.5,M,,,,0000*31
$GPGSA,A,3,27,06,19,18,16,21,18,29,03,07,,,1.9,1.1,1.5*3E
$GPRMC,144705.270,A,5354.1953,N,01415.1024,E,2.47,182.80,011206,,,A*61
$GPVTG,182.80,T,,M,2.47,N,4.6,K,A*0D
$GPGGA,144706.270,5354.1948,N,01415.1012,E,1,10,1.1,56.9,M,,,,0000*31
$GPGSA,A,3,27,06,19,18,16,21,18,29,03,07,,,1.9,1.1,1.5*3E
$GPGSV,3,1,11,21,59,073,50,16,58,218,47,07,50,114,43,03,47,285,45*78
$GPGSV,3,2,11,18,43,127,44,22,23,164,,19,19,284,42,27,13,327,39*73
$GPGSV,3,3,11,06,12,109,27,29,11,047,42,26,09,060,37*40

$GPRMC,144706.270,A,5354.1948,N,01415.1012,E,2.38,179.45,011206,,,A*68
$GPVTG,179.45,T,,M,2.38,N,4.4,K,A*0A

Gdzie np. dla pierwsze frazy:

$GPRMC, - rodzaj frazy
144651.271,A, - godzina oraz rodzaj pozycji (A=active)
5354.2019,N,01415.1032,E, - pozycja
2.01,175.95,011206,,,A
*6D - autosuma kontrolna całości

Czyli odbiór rozpoczynam znakiem $ następnie odczytuje rodzaj frazy, jeśli mnie interesuje to jej treść i na końcu sprawdzam czy zgadza się autosuma kontrolna.

Przerwanie odbierające bajt NMEA, nie może przekraczać max czasu wykonania, zatem kroki muszą być wyliczone precyzyjnie w taktach procesora (to nie RISC – tutaj czasy wykonania rozkazów są różne). Odebrany bajt z portu 8251A ląduje w kolejce o określonej długości, która jest cyklicznie nadpisywana i ma dwa indexy – zapisanego ostatniego bajtu oraz odczytanego ostatniego bajtu. Zwiększa to nieco elastyczność czasową.

`----------------------------------
`
`analizuj NMEA 0182/0183
`trwa: +106tkt + to co w procedurce
`wcześniej od wywołania @RxGPS minęło max ok. +318tkt
`tak więc na procedurkę mamy około 1505-106= ok. 1399tkt
LD HL,(#871A)             @GPana `wykonaj analizę NMEA 0182/183
LD A,(HL)                        `
AND A                            `
RET Z `JP Z,@sprGP                `porównaj z poszczególnymi pozycjami GPSa /mamy ok. +1468tkt/
DEC A                            `
LD D,#00                         `
LD E,A                           `
LD HL,@GPint                     `
ADD HL,DE                        `
ADD HL,DE                        `po tym w HL adres adresu
LD A,(HL)                        `
INC HL                           `
LD H,(HL)                        `
LD L,A                           `
JP (HL)                          `wykonaj procedurkę
`----------------------------------

…adresy kroków pobieram z:

`----------------------------------
`
DEFETYK @GPint            `tabela interpretacji kroków rozpoznania 
`        na każdą z tych procedur mamy max. ok. 1399tkt         
DEFTABE GRMC1 `  +1046tkt rozpoznano nagłówek RMC, rozpoznaj czas i czy pozycja aktywna,
DEFTABE GPaer `DEFTABE GGGA2 `rozpoznano nagłówek GGA, +1227tkt omija 3 przecinki,
DEFTABE GPaer `DEFTABE GVTG3 `rozpoznano nagłówek VTG, +1227tkt omija 3 przecinki,
`DEFTABE GPaer `GPkxY `rozpoznano nagłówek GSA 
`DEFTABE GPaer `GPkxX `rozpoznano nagłówek GSV 
`---                                      
DEFTABE GRMC4 `kolejny krok dla $GPRMC,  +806tkt analizuj fi stopnie i minuty
DEFTABE GRMC5 `kolejny krok dla $GPRMC, +1236tkt analizuj fi ułamki minut, półkula
DEFTABE GRMC6 `kolejny krok dla $GPRMC,  +899tkt analizuj lambda stopnie i minuty
DEFTABE GRMC7 `kolejny krok dla $GPRMC, +1236tkt analizuj lambda ułamki minut, półkula
DEFTABE GRMC8 `kolejny krok dla $GPRMC,  +464tkt olej prędkość w węzłach
DEFTABE GRMC9 `kolejny krok dla $GPRMC, +1201tkt analizuj kurs
DEFTABE GRMCa `kolejny krok dla $GPRMC,  +773tkt odczytaj i sprawdź datę
DEFTABE GRMCb `kolejny krok dla $GPRMC,  +862tkt określ strefę czasową, popraw godzinę i dzień-mca
DEFTABE GRMCc `kolejny krok dla $GPRMC, +1148tkt sprawdź datę, oblicz dzień tygodnia, 
`                                                        sprawdź StatusTime ustaw zegar
DEFTABE GRMCd `ostatni krok dla $GPRMC, +1125tkt tworzy text pozycji z NMEA i przesyła
`                     bajty pozycji i czasu jej określenia, startuje stoper określenia pozycji
DEFTABE G3pre `kolejny krok dla $GPGGA, +1220tkt omija 3 przecinki,
DEFTABE GGGAf `ostatni krok dla $GPGGA,  +280tkt odczytuje liczbę satelitów
DEFTABE G3pre `kolejny krok dla $GPVTG, +1220tkt omija 3 przecinki,
DEFTABE GVTGh `kolejny krok dla $GPVTG,  +785tkt odczytuje cz. całkowitą prędkości km/h
DEFTABE GVTGi `ostatni krok dla $GPVTG, +1170tkt odczytuje ułamek prędkości km/h
`...
`----------------------------------

Np pierwszy krok dla GPGGA wygląda tak:

` rozpoznaj/analizuj wstępnie czas i czy aktywna/niaktywna pozycja
`np. GPRMC,144651.271,A,5354.2019,N,01415.1032,E,2.01,175.95,011206,,,A*6D
` gdzie: GPRMC =konieczne określenie rodzaju komunikatu,
`        144651.271  => godzina, minuta, sekunda UTC, trzeba uwzględnić strefę czasową ;o)
`        A           => A oznacza że mamy pozycję, inny znak lub brak -> olewamy całość
`        5354.2019   => fi
`        N           => znak fi
`        01415.1032  => lambda
`        E           => znak lambda
`        2.01        => prędkość w węzłach
`        175.95      => kurs
`        011206      => dzień, miesiąc, rok
`   resztę olewamy         
`        pusty       => olewamy
`        pusty       => olewamy
`        A           => ? nie opisane!
` trwa +1046tkt max        
LD DE,#8708                @GRMC1 `rozpoznaj czas i czy aktywna pozycja, masz 3400tkt
LD HL,(#871C)                     `
CALL @rdLi2                       `odczytaj liczbę 2-cyfrową
JR C,@GPaer                       `error
CP #18                            `= &24
JR NC,@GPaer                      `error
LD (DE),A                         `zapisz godzinę
DEC DE
CALL @rdLi2                       `odczytaj liczbę 2-cyfrową
JR C,@GPaer                       `error
CP #3C                            `= &60
JR NC,@GPaer                      `error
LD (DE),A                         `zapisz minutę
DEC DE
CALL @rdLi2                       `odczytaj liczbę 2-cyfrową
JR C,@GPaer                       `error
CP #3C                            `= &60
JR NC,@GPaer                      `error
LD (DE),A                         `zapisz sekundę
LD B,#05                          `dla szukania przecinka 5 znaków
CALL @GPpr_                       `szukaj przecinka
JR NZ,@GPaer                      `hop -> nie znaleziono przecinka
`  tutaj adres HL jest za przecinkiem
LD A,(HL)                         `
CP #41                            `'A' =poprawna, odebrana pozycja GPSa
JR NZ,@GPaer                      `hop -> inne olewamy
INC HL                            `next znak
LD A,(HL)                         `powinien tu być przecinek
CP #2C                            `przecinek =poprawnie
JR NZ,@GPaer                      `hop -> inaczej error
INC HL                            `next znak
LD A,#04                          `naxt krok dla $GPRMC to #04
LD (#871C),HL              @GoKro `+50tkt zapisz dynamiczny adres znaku
LD HL,(#871A)                     `adres bajtu kroku analizy tego wiersza
LD (HL),A                         `
RET                               `
`----------------------------------

Np rozkodowanie szerokości geograficznej to:

`----------------------------------
`
`
`        5354.2019   => fi
`trwa: +806tkt max.
LD HL,(#871C)              @GRMC4 `dla $GPRMC rozpoznaj fi stopnie i minuty
LD (#870D),HL                     `zapisz adres tmp textowej postaci pozycji GPSa
`   #870D - 2b to tmp adres tekstowej postaci określonej pozycji GPSa
LD DE,#0000                       `poprawka na 0 przed dziesiątkami stopni
CALL @rdSMU                       `odczytaj 'SSMM'
JP C,@GPaer                       `hop -> error
` wynik w DE, adres w HL           
LD A,E                            `
LD E,D                            `
LD D,A                            `
LD (#8700),DE                     `zapisz tmp fi stopnie i minuty
JP @InKro                         `zapisz adres w linijce i zmień krok
`-------------------------------------
`
`
`  odczytaj 'SSMM' od (DE)
`  w HL jest adres pierwszego odczytywanego znaku a po wyjściu, 
`  adres pierwszego znaku za ostatnią odczytaną cyfrą
` w DE musi być #0000 lyb 100stopni *60 gdy to lambda i była jedynka
` NC =ok
` C  =error
`trwa +645tkt
CALL @rdLi2                @rdSMU `odczytaj liczbę 2-cyfrową /stopnie fi/lambda/
RET C                             `error
CP #5A                            `= &90
RET NC                            `error
PUSH HL                           `
LD L,A                            `stopnie fi/lambda
LD H,#00                          `
LD C,L                            `
LD B,H                            `
ADD HL,HL                         `*2
ADD HL,HL                         `*4
ADD HL,BC                         `*5
ADD HL,HL                         `*&10
ADD HL,HL                         `*&20
LD C,L                            `
LD B,H                            `
ADD HL,BC                         `*&40
ADD HL,BC                         `*&60
ADD HL,DE                         `dodaj ew. cyfrę 1 przed lambda
LD C,L                            `
LD B,H                            `
POP HL                            `
PUSH BC                           `
CALL @rdLi2                       `odczytaj liczbę 2-cyfrową /minuty fi/
POP BC                            `
RET C                             `error
CP #3C                            `= &60
RET NC                            `error
PUSH HL                           `
LD L,A                            `minuty fi
LD H,#00                          `
ADD HL,BC                         `*&60
EX DE,HL                          `
` teraz stopnie i minuty są w DE   
POP HL                            `adres znaku '.' w komunikacie NMEA
RET                               `tutaj adres HL jest za ostatnią cyfrą, na kropce
`---------------------------------------
`
`
`pomocnicza, odczytuje z (HL) liczbę 2-cyfrową do Acc
`NC -ok i HL za odczytaną drugą cyfrą
`CY-error
` nie zmienia BDE
` trwa +177tkt
CALL @rdCyf                @rdLi2 `
RET C                             `
LD C,A                            `
ADD A,A                           `
ADD A,A                           `
ADD A,C                           `
ADD A,A                           `starsza cyfra * &10 
LD C,A                            `
CALL @rdCyf                       `
RET C                             `
ADD A,C                           `
RET                               `
`----------------------------------
`
`
`pomocnicza odczytuje cyfrę ASCII z (HL) w HL adres ZA odczytanym znakiem (zawsze)
`NC -ok
`CY-error
` trwa +47tkt, 
LD A,(HL)                  @rdCyf `
INC HL                            `
SUB #30                           `
RET C                             `error
CP #0A                            `
CCF                               `
RET                               `C to error
`---------------------------------------

Odczyt zaś części ułamkowej to:

`----------------------------------
`
`
`        5354.2019   => fi
`        N           => znak fi
`trwa: +1236tkt max.
LD HL,(#871C)              @GRMC5 `dla $GPRMC rozpoznaj fi stopnie i minuty
CALL @rd.3c                       `odczytaj kropkę i 3 z 5-ciu cyfr ułamka do C
JP C,@GPaer                       `hop -> error
LD A,(HL)                         `
INC HL                            `
INC HL                            `
CP #4E                            `'N' =NORD
JR Z,@_go07                       `
CP #53                            `'S' =SOUTH
JP NZ,@GPaer                      `hop -> inny znak to error
` zmień znak                       
LD A,(#8700)                      `
OR #80                            `odwróć bo znak ujemny dla 'South'
LD (#8700),A                      `
PUSH HL                    @_go07 `zapamiętaj adres w komunikacie
LD HL,#8702                       `adres dla fi ułamka
LD (HL),C                         `
POP HL                            `
JP @InKro                         `zapisz adres w linijce i zmień krok
`---------------------------------------
`
`
`wynik w Crej, format odczytywanych danych: '.XXX??,'
`pomocnicza odczytaj ułamek liczby począwszy od kropki
`wyjście NC =oki, HL za następnym przecinkiem!
`        CY=error /pokaże też error gdy odczytamy ułamek ostatniej danej w linijce NMEA 
`                  bo nie kończy się przecinkiem tylko * (a u nas chyba #00)/
`maksymalny błąd w odczycie dla 3 cyfr to wartość 1,072 bita lub 4,1875 najmniejznaczącej cyfry
` czyli wzdłuż południka to 7,76[m]
`trwa: max +1011tkt
LD A,(HL)                  @rd.3c `
INC HL                            `
CP #2E                            `'.' =kropka dziesiętna
SCF                               `
`RET NZ                           `hop -> inny znak to error
CALL Z,@rdCyf                     `tylko gdy była kropka -pierwsza cyfra
RET C                             `error -brak kropki lub cyfry
`   mnożenie cyfry przez 25,6 (pierwsza cyfra po kropce dziesiętnej):
LD B,A                            `ta cyfra
ADD A,A                           `*2
ADD A,A                           `*4
ADD A,A                           `*8
LD D,A                            `=cyfra *8
ADD A,A                           `*16
ADD A,D                           `=*24
ADD A,B                           `=*25
LD D,A                            `zapamiętaj wynik pośredni
LD A,B                            `ta cyfra
ADD A,A                           `*2
ADD A,B                           `*3
ADD A,A                           `*6
` zakres Acc &54                   
CALL @Adv10                       `wykonaj dzielenie Acc przez &10, wynik w Acc, reszta w Brej
ADD A,D                           `wynik *25,6
LD C,A                            `zapamiętaj w Crej
CALL @rdCyf                       `druga cyfra
JR NC,@_dv04                      `
`ADD A,#30                        `
`CP #2C                           `
CP #FC                            `czy to przecinek?
RET Z                             `wynik jest już w Crej, HL za przecinkiem
SCF                               `
RET C                             `error
`   mnożenie drugiej cyfry przez 2,56:
LD E,A                     @_dv04 `ta liczba
CP #01                            `wyjątek
JR Z,@__dv5                       `
CP #03                            `wyjątek
JR Z,@__dv5                       `
INC A                             `
AND A                      @__dv5 `
RR A                              `
LD D,#00                          `
JR C,@_divi                       `
LD D,#05                          `
` liczba oczek.wynik.reszty  incA     div2
` 0       0.0                 1        0 
` 1       0.6               wyjątek 1  0 
` 2       1.2                 2        1 
` 3       1.8               wyjątek 3  1 
` 4       2.4                 5        2 
` 5       3.0                 6        3 
` 6       3.6                 7        3 
` 7       4.2                 8        4 
` 8       4.8                 9        4 
` 9       5.4                 A        5 
ADD A,B                    @_divi `dodaj resztę z poprzedniego
ADD A,D                           `dodaj resztę z mnożenia cyfra * 2,5
LD B,A                            `
LD A,E                            `ta cyfra (druga)
ADD A,A                           `*2
ADD A,A                           `*4
ADD A,E                           `*5
RRA                               `/2 bo CY był zero!
ADD A,C                           `dodaj wynik mnożenia 1szej liczby
LD C,A                            `zapamiętaj w Crej
CALL @rdCyf                       `trzecia cyfra
JR NC,@_dv05                      ` /jest NC!/
`ADD A,#30                        `
`CP #2C                           `
DEC HL                            `bo i tak będzie INC HL gdy skoczy bo nie cyfra
CP #FC                            `czy to przecinek?
JR Z,@__dv8                       `wynik jest już w Crej, HL na przecinku
INC HL                            `
SCF                               `
RET C                             `error
`   mnożenie trzeciej cyfry przez 0,256:
LD E,A                     @_dv05 `przechowaj 3cią cyfrę
` zaczynamy od pomnożenia przez 0,25:
RRA                               `/2 bo CY był zero! jest cykliczne ale wychodzi NC!
AND A                             `NC
RRA                               `/4 bo CY był zero! jest cykliczne ale wychodzi NC!
ADD A,C                           `dodaj wynik z poprzednich cyfr
LD C,A                            `zapamiętaj w Crej
` teraz jeszcze pomnożymy przez 0,006 (i razy 10):???
LD A,E                            `jeszcze sprawdzamy resztę
` liczba oczek.reszta obliczona reszta
`  0      0            0
`  1      2            2
`  2      5            5
`  3      7            7
`  4      0            0
`  5      2            2
`  6      5            5
`  7      7            7
`  8      0            0
`  9      3            2
AND #03                           `
RLA                               `*2 i wchodzi NC
CP #03                            `
JR C,@__dv7                       `
INC A                             `
ADD A,B                    @__dv7 `dodaj poprzednie reszty
LD B,A                            `zapisz w Brej bo z Brej zaraz czytamy
LD A,B                     @__dv8 `+255tkt poprzednie reszty uwzględniamy teraz
`   wynik dotychczasowy jest w Crej 
`   zakres reszty uzależniony od reszt z 3 cyfr: dla 1 cyfry max 8
`                                                dla 2 cyfry max 5
`                                                dla 3 cyfry max 7, razem zakres 0-&20
CALL @Adv10                       `podziel Acc przez &10, wynik w Acc, reszta w B
ADD A,C                           `dodaj resztę do wyników
LD C,A                            `zapamiętaj w Crej
CALL @GPprz                       `szukaj przecinka
RET Z                             `
SCF                               `
RET                               `
`---------------------------------------

Podobnie z długością / lambda. Prędkość i kierunek/kurs to już łatwiej. Podobnie data i czas.

Mam odczytany tekst i mam dane, kończy się fraza NMEA, sprawdzam autosumę, gdy poprawna – mogę uznać dane za prawidłowe i przepisać je w miejsce docelowe:

`------------------------------------------
`
`
`kolejny krok, przeładowuje dane uzyskane z RMC do bufora GPSa
`trwa: +1125tkt                    
LD HL,#8706                @GRMCd `dla $GPRMC rozpoznaj 
DEC HL                            `
LD (HL),#00                       `zapisz liczbę znaków (bez kończącego zera!)
PUSH HL                           `
INC HL                            `
`#8706 - 7b to tmp czas odczytany z GPSa z $GPRMC, kolejno:
`           sek, min, godz, dn-mca, miesiąc, rok, dzień tygodnia
LD DE,#86F0                       `bufor strefowego czasu i daty
`#86F0 - 7b to czas określenia ostatniej pozycji GPS z $GPRMC, (czas naszej strefy) kolejno:
`           sek, min, godz, dn-mca, miesiąc, rok, dzień tygodnia
LD BC,#0007                       `długość
LDIR                              `prześlij do bufora GPSu
`6*21+16=142
LD HL,#8700                       `
`#8700 - 3b to tmp fi w postaci stałoprzecinkowej 2b to stopnie*60+minuty, 1b to ułamki minut
`#8703 - 3b to tmp lambda w postaci stałoprzecinkowej 2b to stopnie*60+minuty, 1b to ułamki minut
LD DE,#86F7                       `
`#86F7 - 3b to fi w postaci stałoprzecinkowej 2b to stopnie*60+minuty, 1b to ułamki minut
`#86FA - 3b to lambda w postaci stałoprzecinkowej 2b to stopnie*60+minuty, 1b to ułamki minut
LD BC,#0006                       `długość
LDIR                              `prześlij do bufora GPSu
`5*21+16=121
LD HL,#0001                       `czas, jaki upłynął od określenia 
`                                            ostatniej pozycji GPSa i czasu/daty
LD (#86E8),HL                     `
`#86E8 - 2b to zegar cykający sekundy od określenia ostatniej pozycji z GPSa
`
`już 349tkt
`
` jeśli jest tutaj, to znaczy że fi i lambda mają oczekiwaną postać tekstową
` z więc nie trzeba sprawdzać ile znaków do przecinka, tylko przesłać fi, potem N/S,
` potem lambda i E/W
`
LD HL,(#870D)                     `adres tmp textowej postaci pozycji GPSa
`   #870D - 2b to tmp adres tekstowej postaci określonej pozycji GPSa
` '5354.2019,N,01415.1032,E,'      
LD DE,#86D3                       `
`#86D2 - &22b to fi, lambda w formie textu, gdzie:
`   #86D2 - 1b liczba znaków ASCII (bez zliczenia znaku końca #00)m
`              gdy =0 to brak zapisu
`   #86D3 - do &21b ASCIIZ tekstu zakończonego #00 opisującego ostatnią odczytaną pozycję
`              w formacie: ` '5354.201N 01415.103E'
PUSH DE                           `
LD BC,#0008                       `8 znaków
LDIR                              `mamy: '5354.201'
`7*21+16=163                       
`już +512tkt                       
LD B,#03                          `
CALL @GPpr_                       `szuka przecinka (kod #2C)
`szuka przecinka od adresu (HL) włącznie do Brej znaków
` nie zmienia DEC
`wyjście NZ  B=#00 to nie znaleziono,
`        Z  B<>#00 to znaleziono, HL za przecinkiem,
`         +(38+n*31)tkt gdy znaleziono w (HL+n)
` max trwa ? 131tkt ?
JR NZ,@GPc__                      `hop -> error
LD A,(HL)                         `
LD (DE),A                         `Nord/South
INC HL                            `
INC DE                            `
LD A,#20                          `kod spacji
LD (DE),A                         `
INC DE                            `
INC HL                            `HL na pierwszej cyfrze lambda
LD BC,#0009                       `9 znaków
LDIR                              `mamy: '5354.201N 01415.103'
`8*21+16=184                       
`już +896tkt                       
LD B,#03                          `
CALL @GPpr_                       `szuka przecinka (kod #2C)
`szuka przecinka od adresu (HL) włącznie do Brej znaków
` nie zmienia DEC
`wyjście NZ  B=#00 to nie znaleziono,
`        Z  B<>#00 to znaleziono, HL za przecinkiem,
`         +(38+n*31)tkt gdy znaleziono w (HL+n)
` max trwa ? 131tkt ?
JR NZ,@GPc__                      `hop -> error
LD A,(HL)                         `
LD (DE),A                         `East/West
INC DE                            `
SUB A                             `kod końca textu ASCIIZ
LD (DE),A                         `
POP HL                     @GPc__ `odtwórz startowy adres docelowy
DEC HL                            `
LD (HL),#00                       `zapisz liczbę znaków (bez kończącego zera!)
JP @GPaer                         `hop -> zakończ analizę $GPRMC...
`---------------------------------------

Co dalej?

Zerknijmy na potrzeby. Jeśli chcemy odrobinkę analizować te dane, a nie jedynie je odczytywać … np chciałbym sprawdzić, jak dana pozycja jest daleko od innej – założonej (co mogłoby dać odpowiedź, że obiekt monitorowany przemieścił się od pozycji 'zakotwiczenia’ – np ktoś nam ukradł samochód, wywożąc na lawecie).

Do takiej analizy potrzeba odrobinkę wiedzy z nawigacji.

Czym zatem jest pozycjia GPS – fi/lambda (szerokość/długość geograficzna)? Fi/lambda określa nam miejsce -przez analigię jak X i Y na wykresie 2D. Problem w tym, że jesteśmy na powierzchni kuli a nie na płaszczyźnie.

O ile fi (szerokość geograficzna) jest łatwa do przeliczenia na odległości, bo 1 stopień szerokości to 60 minut a 1 minuta to 1 Mila Morska -czyli 1852,5m – i jest to stałe(*) w każdym miejscu globu. To z lambda (długość geograficzna), sprawa nie jest już tak łatwa. Oczywiście 1 stopień to 60 minut a 1 minuta to 1852,5m – ale TYLKO na równiku. Im dalej od równika tym 1 minuta jest krótsza od 1852,5m.

Aby dowiedzieć się, jak uzyskać odległość w metrach, odpowiadającą 1 minucie długości geograficznaj na danej szerokości geograficznaj, należy przemnożyć ją przez cosinus tej szerokości. Proste prawda?

…ale czekaj, mamy tylko 8 bitowy Z80 z operacjami dodawania i odejmowania wyrazów 16 bitowych… Nie mam mnożenia ani -tym bardzie funkcji trygonometrycznych!

Z mnożeniem poradzę sobie tak:

`------------------------------------
`
`mnożenie dwóch dodatnich liczb 3 bajtowych z wynikiem 3 bajtowym
`wejście: adres w HL tablicy 3 2bajtowych adresów kolejno: mnożnikA, mnożnejB i wyniku
` procedura zmienia mnożnąB
` wynik jest zerowany przez tą procedurę na początku
`trwa:
PUSH HL                     @multi `
INC HL                             `zaczynamy od obliczania adresu wyniku w tabeli
INC HL                             `
INC HL                             `
INC HL                             `
LD A,(HL)                          `odczytujemy adres wyniku
INC HL                             `
LD H,(HL)                          `
LD L,A                             `
SUB A                              `
LD (HL),A                          `zerujemy wynik
INC HL                             `
LD (HL),A                          `
INC HL                             `
LD (HL),A                          `
POP HL                             `
LD E,(HL)                          `
INC HL                             `
LD D,(HL)                          `DE =adres mnożnejA
INC HL                             `
LD B,#03                           `3 bajty mnożnikA
PUSH BC                     @_dod1 `
` DE=adres danej A                  
LD A,(DE)                          `odczytaj bajt mnożnikA
LD B,#08                           `8 bitów bajtu mnożnikA
RLCA                        @_dod2 `do CY bit mnożnika, kolejno od najstarszego...
PUSH HL                            `
PUSH DE                            `
PUSH BC                            `
PUSH AF                            `
` HL=adres adresu mnożnejB          
LD E,(HL)                          `odczytujemy adres mnożnejB
INC HL                             `
LD D,(HL)                          `
INC HL                             `
LD A,(HL)                          `odczytujemy adres wyniku
INC HL                             `
LD H,(HL)                          `
LD L,A                             `
` HL=adres wyniku                   
` DE=adres mnożnejB                  
JR NC,@_dod5                       `hop -> nie dodawaj bo bit mnożnikA jest =0
INC HL                             `
INC HL                             `lsb wyniku
PUSH DE                            `
INC DE                             `
INC DE                             `lsb mnożnejB
` DE=adres danej B +2               
` HL=adres wyniku +2                
AND A                              `CY=0
LD B,#03                           `3 bajty do zsumowania
LD A,(DE)                   @_dod3 `bajt mnożnejB
ADC A,(HL)                         `bajt sumowanego wyniku
LD (HL),A                          `zapisz do wyniku
`        w CY bit przeniesienia      
DEC HL                             `
DEC DE                             `
DJNZ @_dod3                        `pętla -> kolejny bajt
POP DE                             `
AND A                       @_dod5 `odtwórz adres mnożnejB, CY = 0
LD B,#03                           `3 bajty do przesunięcia
LD A,(DE)                   @_dod4 `bajt mnożnejB
RR A                               `lsbit do CY
LD (DE),A                          `
INC DE                             `
DJNZ @_dod4                        `pętla -> kolejny bajt
POP AF                             `
POP BC                             `
POP DE                             `
POP HL                             `
DJNZ @_dod2                        `
POP BC                             `
INC DE                             `
DJNZ @_dod1                        `
RET                                `
`-----------------------------------

Co zaś z trygomometrią? Co z cosinusem?

Z matematyki pamiętamy, że… cosinus możemy uzyskać poprzez obliczenie ciągu o następującym wzorze, gdy jako x podstawimy kąt w radianach:

A kąt w radianach uzyskamy dzieląc kąt w stopniach przez 2 * pi

`-----------------------------------
`
`LD HL,#0000                 @aryt+ `
`LD (#8700),HL                      `
`LD A,#00                           ` 00 stopni 00 minut
`LD (#8702),A                       `
`                                    
`1   znormalizowana wartość bezwzględna fi w stopniach
LD HL,#8702                 @aryt+ `wartość fi tmp
LD DE,#87C5                        `
PUSH DE                            `
AND A                              `
LD B,#03                           `
LD A,(HL)                   @_ary1 `
RL A                               `
LD (DE),A                          `
DEC HL                             `
DEC DE                             `
DJNZ @_ary1                        `
POP HL                             `
AND A                              `
LD B,#03                           `
LD A,(HL)                   @_ary2 `
RL A                               `
LD (HL),A                          `
DEC HL                             `
DJNZ @_ary2                        `
`2   wpisz wartość mnożnika radianowego
LD HL,#8298                        `\
LD A,#5A                           `\mnożnik radianowy
LD (#87BA),HL                      `/
LD (#87BC),A                       `/
`3   mnożenie fi przez mnożnik radianowy
LD HL,#87C3                        `
LD (#87C6),HL                      `
LD HL,#87BA                        `
LD (#87C8),HL                      `
LD HL,#87BD                        `
LD (#87CA),HL                      `
LD HL,#87C6                        `
CALL @multi                        `
`4   kopiowanie fi_rad              
LD HL,(#87BD)                      `
LD A,(#87BF)                       `
LD (#87BA),HL                      `
LD (#87BC),A                       `
`5   mnożenie fi_rad ^ 2            
LD HL,#87BA                        `
LD (#87C6),HL                      `
LD HL,#87BD                        `
LD (#87C8),HL                      `
LD HL,#87C0                        `
LD (#87CA),HL                      `
LD HL,#87C6                        `
CALL @multi                        `
`6   kopiujemy fi_rad ^ 2           
LD HL,(#87C0)                      `
LD A,(#87C2)                       `
LD (#87BD),HL                      `
LD (#87BF),A                       `
`7   mnożenie (fi_rad^2) ^ 2
LD HL,#87BD                        `
LD (#87C6),HL                      `
LD HL,#87C0                        `
LD (#87C8),HL                      `
LD HL,#87BA                        `
LD (#87CA),HL                      `
LD HL,#87C6                        `
CALL @multi                        `
`8   wpis  4 / 24                   
LD HL,#AA2A                        `\
LD A,#AA                           `\mnożnik 8 / 24
LD (#87C0),HL                      `/
LD (#87C2),A                       `/
`9   mnożenie (fi_rad^4) * 8/24     
LD HL,#87BA                        `
LD (#87C6),HL                      `
LD HL,#87C0                        `
LD (#87C8),HL                      `
LD HL,#87C3                        `
LD (#87CA),HL                      `
LD HL,#87C6                        `
CALL @multi                        `
`10  dodajemy 1                     
LD A,(#87C3)                       `
OR #40                             `
LD (#87C3),A                       `
`11  odejmowanie 1+(fi^4/4!)-(fi^2/2!)
LD DE,#87C3                        `
LD HL,#87BD                        `
CALL @subst                        `
`12 normalizujemy                   
LD HL,#87BF                        `
AND A                              `
LD B,#03                           `
LD A,(HL)                   @_mul2 `
RL A                               `
LD (HL),A                          `
DEC HL                             `
DJNZ @_mul2                        `
`----                               
`[...]

W praktyce, dla naszych wartości fi/lambda, wystarczą trzy pierwsze elementy tego ciągu:

1-(fi^2/2!)+(fi^4/4!)

Jak z dokładnością? 3 bajty dla lambda to dwa bajty dla minut(i stopni przeliczonych na minuty) oraz trzeci bajt dla ułamka minut. najmniejsza określona przez nas część ułamka minut to 1/256 minuty, czyli 1852,5m / 256 = 7m23cm . Takiej dokładności nie uzyskamy z komercyjnego GPSa, więc jest ona tutaj wystarczająca.

Teraz, mając gwie pozycje, aby obliczyć odległość pomiędzy nimi, z tw. Pitagorasa:

odległość = pierwiastek_kwadratowy( delta_fi ^2 + delta_L ^2 ) [gdzie delta_L = delta_lambda * cosinus(fi) ]

No i na koniec – porównujemy uzyskaną warość odległości z progiem wywłującym alarm, jeśli zostanie przekroczona. Alarmem może być SMS wysłany z Nokii5110 (wraz z pozycją GPS) lub uruchomienie syreny samochodu. Ale to temat na odrębny wpis.

Wzmiankowany tutaj alarm został zaprojektowany i wykonany od pomysłu do działającego prototypu przez autora.