; gps_lcd.src
;
; DBeals Apr 19, 2004
;
; Receive GPS serial chars, format, send display chars to LCD.
;
;-------------------------------------------------------
; Scenix SX18 processor
; Seetron 2 row by 20 character LCD
; Garmin 25LVC GPS sensor
; (see notes at the end for more device information)
;-------------------------------------------------------
; Port A bit 0 pin ?? Output RS232 serial (TTL level) to LCD
; bit 1 unused
; bit 2 unused
; bit 3 unused
;
; Port B bit 0 pin ?? input RS232 serial (TTL level) from GPS
; bit 1 unused
; bit 2 unused
; bit 3 unused
; bit 4 unused
; bit 5 unused
; bit 6 unused
; bit 7 unused
;-------------------------------------------------------
; data ram:
; 00-07 8 CPU registers
; 08-0F 8 bytes of global RAM
; 10-1F, 30-3F, 50-5F...F0-FF 8 banks of 16 bytes user RAM.
;
; code rom:
; 4 banks of 512 ($200) bytes.
; org $000 = bank 0, $200 = bank 1, $400 = bank2, $600 = bank3
;
;-------------------------------------------------------
device SX18, OSCHS, TURBO, STACKX, OPTIONX
IRC_CAL IRC_FAST
freq 20_000_000
reset start_point
;-------------------------------------------------------
; global variables
org 7 ; (the SX18 can use RAM 7 as global)
RS232out ds 1 ; 7 LCD RS232 out character
RS232in ds 1 ; 8 GPS RS232 in character
timer1 ds 1 ; 9 RS232 loop timer
msgBytes ds 1 ; A counts bytes within GPS message field
bitCount ds 1 ; B LCD display loop
temp ds 1 ; C DDMM decoding (and other places)
pointer1 ds 1 ; D generic RAM pointer
pointer2 ds 1 ; E me too
i ds 1 ; F generic loop counter
; time and latitude characters
org $10
bank_ten = $
latASCII = $10 ; lat: 10 ASCII "03749.2345" 0DDMM.MMMM format
latEnd = $19
timeStart = $1A ; time: 6 characters HHMISS in 24:60:60 format
timeEnd = $1F ;
; longitude, status and altitude chars
org $30
bank_lonstat = $
lonASCII = $30 ; lon: 10 chars "12232.3580" DDDMM.MMMM format
lonEnd = $39
altStart = $3A ; alt: from 0 to 5 chars "122.6" meters
altEnd = $3F ; FIXME - should this be $3E?
statusChar = $3F ; GPS status: one char
; ASCII-binary conversions and multiplier locations
org $50 ;
bank_math = $
multStart = $ ; this needs to precede the four "mult"s
mult0 ds 1 ; 4 byte input to 1 x 4 multiply
mult1 ds 1 ; (also used by subtract routine)
mult2 ds 1
mult3 ds 1
accumStart = $ ; this needs to precede the 4 accums
accum0 ds 1 ; 4 byte output from several math routines
accum1 ds 1
accum2 ds 1
accum3 ds 1
eight ds 1 ; 1 byte input to 1 x 4 multiply routine
counter ds 1 ; "counter" used in the multiply loop
negFlag ds 1 ; set to 1 if subtraction is negative
math0 ds 1 ; spares
math1 ds 1 ;
math2 ds 1 ;
math3 ds 1 ;
; 1 unused byte
; 4 binary 4-byte locations
lonToGoTo = $70 ; 4 bytes: lon to go to, binary
lonAtStart = $74 ; 4 bytes: lon started from, binary
latToGoTo = $78 ; 4 bytes: lat to go to, binary
latAtStart = $7C ; 4 bytes: lat started from, binary
; speed and direction
dirStart = $90 ; dir: 90 - 95 000.0
dirEnd = $95 ; FIXME is 95 or 96 good enuf?
speedStart = $96 ; speed: 96 - 9B 0000.0
speedEnd = $9B ;
; 4? unused bytes in bank $90
; decimal display
org $B0 ; binary to decimal conversion area
bank_toASCII = $
counter2 ds 1
hiBin ds 1
loBin ds 1
hiCmp ds 1
loCmp ds 1
fiveDigits = $ ; here begins 5 decimal digits
digit5 ds 1
digit4 ds 1
digit3 ds 1
digit2 ds 1
digit1 ds 1
; 6 unused bytes
org $D0 ; unused space for 16 vars
; last bank has assorted stuff - timer vars, GPS msg counters, etc.
org $F0
bank_assorted = $ ; F0
timer2 ds 1 ; F1
timer3 ds 1 ; F2
timer4 ds 1 ; F3
string ds 1 ; F4
vlongLow ds 1 ; F5
nybs ds 1 ; F6
hexByte ds 1 ; F7
msgLen ds 1 ; F8 count GPS chars per specific message
msgType ds 1 ; F9 Position vs. Velocity GPS NMEA msg
commaCount ds 1 ; FA detect fields within the NMEA msg
screenToShow ds 1 ; FB toggle thru several screens as this incrs
savedStart ds 1 ; FC flag for initial pos save
charsPerLine ds 1 ; FD
; FE, FF unused space
;-------------------------------------------------------
org 0
;-------------------------------------------------------
; interrupt service routine (currently unused)
;-------------------------------------------------------
isr
reti
;-------------------------------------------------------
; main program
;-------------------------------------------------------
start_point
mode $0F ; prepare for configuring direction mode
; see pg. 144 SZ-key dev man
mov !ra,#%11111110 ; only a0 outie; the rest innie
mov !rb,#%11111111 ; all bs innies
mode $0E ; prepare for configuring pull-up resistor mode
mov !ra, #%0000 ; enable all port A pull-ups
;
; ; these not currently used; here for reference:
; mode $0D ; CMOS/TTL input (n/a when Schmidt used)
; mov !rb,#%00000000 ; set all to CMOS
; mode $0C ; schmidt trigger
; mov !rb,#%00000000 ; all schmidts enabled (pg. 59 and 140)
; mode $0B ; wake-up enable
; mov !rb,#%11111110 ; wake-up interrupt edge bit 4 (pin 1)
; mode $0A ; rising/falling: default falling edge
; mode $09 ; swap W, edge det status (pg. 61)
; mov !rb, %00000000 ; clear edge status
; (and store state in "w" reg)
;-------------------------------------------------------
; main loop
;-------------------------------------------------------
main
call @init
call @hello
; call subtest
; call multTest
; call @binToASCIITest
jmp @getGPSMsgGrp ; get GPS message; display to LCD
jmp main
; End of main
;-------------------------------------------------------
; some strings
shello dw 'Hello World! HaWaYa?',0
sline2 dw 'GPS LCD ver 04.19.04',0
nosync dw 'No signal',0
latToGo dw '03741.3430',0
lonToGo dw '12153.3754',0
;-------------------------------------------------------
; Can't call a function located in the second half of any code page so
; compensate by using this jump table which can reference functions on any
; part of any page.
; This table must exist entirely in a first page half.
; If this grows too large, continue on the top half of other code pages.
; Check gps_lcd.lst: last table entry must be below $FF. "$B2" as of last check.
init jmp @_init
hello jmp @_hello
getByte jmp @_getByte
sendByte jmp @_sendByte
sendSpace jmp @_sendSpace
sendString jmp @_sendString
clearScreen jmp @_clearScreen
lcdLine1 jmp @_lcdLine1
lcdLine2 jmp @_lcdLine2
sendHexByte jmp @_sendHexByte
hexNybble jmp @_hexNybble
halfDelay jmp @_halfDelay
delay9600 jmp @_delay9600
delay1ms jmp @_delay1ms
delay100ms jmp @_delay100ms
delay1sec jmp @_delay1sec
longLow jmp @_longLow
multiply32 jmp @_multiply32
subtract32 jmp @_subtract32
increment32 jmp @_increment32
copy32 jmp @_copy32
showHex32 jmp @_showHex32
shift32 jmp @_shift32
clearAccum jmp @_clearAccum
multPreset jmp @_multPreset
DDMMToBin jmp @_DDMMToBin
Lat2Feet jmp @_Lat2Feet
Lon2Feet jmp @_Lon2Feet
loadLatLon jmp @_loadLatLon
multTest jmp @_multTest
latToBinTest jmp @_latToBinTest
latSubTest jmp @_latSubTest
binToASCII jmp @_binToASCII
binToASCIITest jmp @_binToASCIITest
showDirSpeed jmp @_showDirSpeed
showDistHome jmp @_showDistHome
showLatLon jmp @_showLatLon
showTimeAlt jmp @_showTimeAlt
padLine jmp @_padLine
loader jmp @_loader
;-------------------------------------------------------
_init
; to init the LCD, we need to set its bit low on power-up and
; keep it there for a second, otherwise the LCD driver goes into debug mode.
mov ra, #0 ; initialize the LCD bit
call @delay1sec
bank bank_assorted ; set flag: not yet saved the
mov savedStart, #0 ; initial position lat and lon
mov FSR, #latASCII ; preset the lat hundreds digit to 0 so
mov ISR, #'0' ; Lat "DD" can be processed like lon "DDD"
retp
;--------------------------------------------------------------------
; GPS-receive,parse,LCD-send section.
; These aren't calleable subroutines - they jump from code block to
; code block as individual RS232 characters arrive and are processed,
; then jump back to one of two re-entry points:
; 1. the top, just below here, to get a new message group, or
; 2. just below that, to get a new character of the current group.
;--------------------------------------------------------------------
getGPSMsgGrp
; Begin wait for a group of GPS NMEA messages.
;
; Once a second, the GPS sensor sends a series of the NMEA messages,
; then pauses for about 3/4 of a second.
; When this program is synchronized to the sensor, the code will
; wait here for the next message group to start to arrive.
; It will recieve all message group characters, process the received
; data, display information onto the LCD, then jump back here to wait
; for the next message group.
; On any errors, the code jumps back here to begin a fresh message group wait.
bank bank_assorted
clr msgLen ; length of single NMEA message.
clr commaCount ; count fields within NMEA message group.
mov FSR, #altStart ; preset the altitude.
:loop1 mov IND, #' ' ; Altitude and speed are variable length.
inc FSR ; when out of sync nothing arrives.
cjb FSR, #altEnd, @:loop1 ; But when in sync, only the necessary number
; of chars arrive to describe the current value.
mov FSR, #speedStart ; So if we go from 100 to 99 meters altitude,
:loop2 mov IND, #'_' ; the "1" stays unless it is explicitly
inc FSR ; written over.
cjb FSR, #speedEnd, @:loop2
mov FSR, #dirStart ; preset direction. May be unnecessary.
:loop3 mov IND, #'_'
inc FSR
cjb FSR, #dirEnd, @:loop3
call @longLow ; wait for long pause in received RS232
;-------------------------------------------------------
getGPSchar
call @getByte ; get the next RS232 GPS character
;
bank bank_assorted ; An NMEA GPS msg begins with "$"
inc msgLen ; Count received chars in this msg.
cjne RS232in, #'$', @parseMsg ; If not "$" then parser gets it.
mov msgLen, #1 ; If is "$" then reset counter and
jmp @getGPSchar ; wait for next char.
;-------------------------------------------------------
parseMsg
; Use characters 2-6 to determine the message type.
; GPGGA: time, lat, lon, alt
; PGRMV: velocity N, E, Up
; PGRMT: status message always sent once per minute. not real interesting.
; GPRMC: absolute velocity and compass bearing
; GPVTG: maybe use this someday?
;
; Once we're past character 6, process the message body
; (except if over 80 then probably an error occurred, so reset.)
cje msgLen, #2, @msgChar2 ; expect 'G' or 'P'
cjbe msgLen, #5, @getGPSchar ; go get another char
cje msgLen, #6, @msgChar6 ; expect 'A' or 'G'
cjae msgLen, #80, @getGPSMsgGrp ; reset if count too big
jmp @processChar ; on any other message position,
; let subroutine decide what to do.
;-------------------------------------------------------
msgChar2
; Verify that character 2 is either P or G.
; If G, set the message type to "P" for Position message.
; (will overwrite this if char 6 is also "G")
cje RS232in, #'P', @getGPSchar ; may be info msg
cjne RS232in, #'G', @getGPSMsgGrp ; by here, if not G, not ok.
mov msgType, #'P' ; P for Position message
jmp @getGPSchar ; and wait for the next character
;-------------------------------------------------------
msgChar6
; Verify that character 6 is either A (GPGGA) or G (GPVTG)
cje RS232in, #'A', @getGPSchar ; OK; we have GPGGA
cjne RS232in, #'G', @getGPSMsgGrp ; by here, if not G, not ok.
mov msgType, #'V' ; V for GPVTG velocity type message
jmp @getGPSchar ; go wait for the next character
;-------------------------------------------------------
processChar
; if this character is data, then go process it.
; if it is a field separator then go process that.
; (ahh, the velocity message terminates the last field with "*",
; so it is a field separator in that case, also.)
cje RS232in, #',', @field ; comma - go directly to "field";
cjne RS232in, #'*', @processData ; if not * then go process char.
cjne msgType, #'V', @getGPSchar ; if * but not vel then get next gps,
; else fall into "field".
;-------------------------------------------------------
field
; we have a field separator.
; Do two things:
; 1. prepare for a new field by incrementing commaCount and clearing msgBytes.
; 2. If we have received all the fields we're expecting (currently hex 16),
; (see half a page down for more description of the commaCounts), then
; skip the rest of the message group and display what we've got.
; Otherwise go get yet another GPS character.
inc commaCount
clr msgBytes
cje commaCount, #$16, @doDisplay
jmp @getGPSchar
;-------------------------------------------------------
processData
; we have a data character.
; decide what to do based on the current commaCount.
; Note: let the commaCount manage the entire message group, based
; on the GPS sensor always sending the messages in the same order.
; Is this assumption warranted? It works so far...
; Otherwise would need to test both message type and commaCount...
;
; 1 2 3 4 5 6 7 8 9 A B C DE
; $GPGGA,205343,3749.9327,N,12232.3580,W,1,08,1.1,12.5,M,-28.0,M,,*42
; $PGRMV,0.0,0.0,0.0*5C
; $PGRMT,GPS 25-LVC VER 2.50 ,P,P,R,R,P,,19,R*15
;
; as of 3/28/04 no more PGRMV message.
; instead, configured GPS to send GPVTG (Track Made Good and Ground Speed)
;
; commas 1 2 3 4 5 6 7 8
; or, if continuing the commaCount from the previous field...
; F 0 1 2 3 4 5 6 <- that is, 0x16 is the last expected comma.
; fields 1 2 3 4 5
; $GPVTG, ,T, ,M, ,N, ,K *..
;
; 1: true course: 000.0 to 359.0 degrees
; 2: magnetic course 000.0 to 359.0
; 3: speed: 000.0 to 999.9 knots
; 4. speed: 0000.0 to 1851.8 kilometers per hour
; 5: mode - do I care? not sure, yet.
; when saving individual characters, detect field position by the count
; of the comma preceding the field.
cje commaCount, #$01, @timeField ; 6 time chars
cje commaCount, #$02, @latField ; 8 latitude chars
cje commaCount, #$04, @lonField ; 9 longitude chars
cje commaCount, #$06, @statusField ; 1 status char;
cje commaCount, #$09, @altField ; 0 to 5 altitude chars
cje commaCount, #$0F, @dirField ; 0 to 5 direction chars
cje commaCount, #$15, @speedField ; 0 to 6 speed chars
; if no more fields are of interest then just go get another GPS character:
jmp @getGPSchar
;-------------------------------------------
; Save characters into individual RAM banks.
timeField ; always 6 characters: $10 to $15
mov FSR, #timeStart
jmp @saveChar
latField ; probably always 8 characters: $16 to $1D
mov FSR, #latASCII ; GPS sends only 2 digits of lat degrees
inc FSR ; so incr FSR once before using it
jmp @saveChar
lonField ; probably always 9 chars $30 to $38
mov FSR, #lonASCII
jmp @saveChar
statusField ; always 1
mov FSR, #statusChar
jmp @saveChar
altField ; varies from 0 to 4 or 5: $39 to $3D?
mov FSR, #altStart
jmp @saveChar
dirField ; varies from 0 to 6
mov FSR, #dirStart
jmp @saveChar
speedField ; varies from 0 to 6
mov FSR, #speedStart
jmp @saveChar
; add additional field storage here, if you want any
saveChar
; saveChar: jump here with FSR containing the start address of
; the field that we're saving to. So if we add the current value
; of the msgBytes to the FSR, then we have the storage of this character
; within each field. Yeah, if more chars arrive than we're expecting
; then we'll overwrite some other value...Lets just see if that ever happens.
; (Oh no, a hacker could feed a virus in and hijack the project via field overflow!)
add FSR, msgBytes
mov IND, RS232in
inc msgBytes
jmp @getGPSchar ; go get the next char
;-------------------------------------------------------
doDisplay
; Process and display information.
; The first time that a good set of satellite data is seen,
; save the latitude and longitude binary numbers.
bank bank_assorted
cje savedStart, #1, @:continue ; if 1 then already saved.
bank bank_lonstat
cjne statusChar, #'1', @:continue ; unless 1, sat data not good
mov pointer1, #latASCII ; point to Latitude 0DDMM.MMMM
call @DDMMToBin ; convert to binary
mov pointer1, #accumStart ; and save
mov pointer2, #latAtStart
call @copy32
mov pointer1, #lonASCII ; point to Longitude DDDMM.MMMM
call @DDMMToBin ; convert to binary
mov pointer1, #accumStart ; and save
mov pointer2, #lonAtStart
call @copy32
bank bank_assorted ; OK, we've saved the
mov savedStart, #1 ; starting location!
:continue
; Toggle thru several screens; approximately 1 per second.
;
; 0: display current position and time
; 1: display elapsed time since first satellite sync
; 2: display distance from position of first satellite sync
; 3: (todo) display distance to programmed waypoint
; 4: display current velocity, compass heading
bank bank_assorted
inc screenToShow
cjb screenToShow, #4, @showScreen ; "4" is total number of screens
clr screenToShow
showScreen:
cje screenToShow, #0, @showLatLonspeed
cje screenToShow, #1, @showLatLonDH
; cje screenToShow, #2, @showLatLonDH
; cje screenToShow, #3, @showLatLonDH
cje screenToShow, #2, @showLatLonspeed
cje screenToShow, #3, @showLatLonAlt
; cje screenToShow, #3, @showBinary
; Any individual screen display routine may optionally delay for some
; length of time, like a second or so, to better show its contents,
; before jumping back to wait for the next message group.
; This may cause the program to skip message groups. Ordinarily
; this should be no big deal - missing a group is always a possibility,
; and every GPS data processing routine must allow for this.
; But ideally we will be able to catch every group.
;
; Each of the screen display routines will conclude by jumping
; to wait for a new GPS message group as in the following jmp,
; shown just below, although this particular jmp should never
; be reached if the above screenToShow is working properly:
jmp @getGPSMsgGrp
;-------------------------------------------------------
showLatLonAlt
; On the top line show ASCII Latitude, Longitude.
; On the second line show the time and altitude (if in sync)
call @lcdLine1
call @showLatLon
call @lcdLine2
call @showTimeAlt
jmp @getGPSMsgGrp
;-------------------------------------------------------
showLatLonSpeed
; On the top line show ASCII Latitude, Longitude.
; On the second line show the speed and direction
call @lcdLine1
call @showLatLon
call @lcdLine2
call @showDirSpeed
jmp @getGPSMsgGrp
;-------------------------------------------------------
showLatLonDH
; On the top line show ASCII Latitude, Longitude.
; On the second line show the distance from the start.
call @lcdLine1
call @showLatLon
call @lcdLine2
call @showDistHome
jmp @getGPSMsgGrp
;-------------------------------------------------------
showBinary
; convert the current longitude and latitude to 4 byte integers
; representing 1/10000 of a minute, and display.
call @clearScreen ; prepare the LCD to show results
mov pointer1, #latASCII ; point (1 below) Latitude DDMM.MMMM
call @DDMMToBin ; convert to binary
call @lcdLine1 ; On the LCD top line...
mov pointer1, #accumStart
call @showHex32 ; show 4 hex bytes
mov pointer1, #lonASCII ; point to Longitude DDDMM.MMMM
call @DDMMToBin ; convert to binary
call @lcdLine2 ; On the LCD bottom line...
mov pointer1, #accumStart
call @showHex32 ; show 4 hex bytes
jmp @getGPSMsgGrp
; END of GPS receive message jump routines
;
;**************************************************************************
; code below are subroutines.
; general groups:
;
; Latitude, Longitude management group
; Math group
; LCD group
; RS232 group
; test routine group
;
;**************************************************************************
; Latitude, Longitude management group
;
;-------------------------------------------------------
_showDistHome
; Display the distance in feet from first sync to the current LCD line.
bank bank_assorted
cjne savedStart, #1, @:continue
mov pointer1, #latASCII ; point to Lat, cvrt to binary
call @DDMMToBin ; and leave result in accumulator
mov pointer1, #latAtStart ; copy original saved lat binary
mov pointer2, #multStart ; to the other 4-byte register
call @copy32
call @subtract32 ; get the diff...
bank bank_math
cje negFlag, #0, @:P0 ; test whether we're north or
mov RS232out, #'N' ; south of the starting sync
jmp @:P1
:P0 mov RS232out, #'S'
:P1 call @sendByte ; display compass direction
call @Lat2Feet ; convert difference to feet
mov pointer1, #accumStart
call @showHex32 ; show feet as 4 hex bytes
call @sendSpace
call @sendSpace
mov pointer1, #lonASCII ; point to Lon, cvrt to binary
call @DDMMToBin ; and leave result in accumulator
mov pointer1, #lonAtStart ; copy original saved lon binary
mov pointer2, #multStart ; to the other 4-byte register
call @copy32
call @subtract32 ; get the diff...
bank bank_math
cje negFlag, #0, @:P2 ; test whether we're east or
mov RS232out, #'W' ; west of the starting sync
jmp @:P3
:P2 mov RS232out, #'E'
:P3 call @sendByte ; display compass direction
call @Lon2Feet ; convert difference to feet
mov pointer1, #accumStart
call @showHex32 ; show feet as 4 hex bytes
call @sendSpace
:continue
retp
;-------------------------------------------------------
_showLatLon
; Display the current latitude and Longitude to the current LCD line.
mov FSR, #latASCII ; send 9 latitude chars
inc FSR ; skip dummy "0" at lat beginning
:loop1 mov RS232out, IND
call @sendByte
inc FSR
cjbe FSR, #latEnd, @:loop1
call @sendSpace
mov FSR, #lonASCII ; send 10 longitude characters
:loop2 mov RS232out, IND
call @sendByte
inc FSR
cjbe FSR, #lonEnd, @:loop2
retp
;-------------------------------------------------------
_showDirSpeed
; Display the current direction and speed to the current LCD line.
; (if the status says we're not in sync then just report that and quit)
bank bank_lonstat
cjne statusChar, #'1', @saynosync
mov RS232out, #'D' ; send dir label
call @sendByte
call @sendSpace
mov i, #5
mov FSR, #dirStart ; send 6 dir chars
:loop1 mov RS232out, IND
call @sendByte
inc FSR
djnz i, @:loop1
call @sendSpace
mov RS232out, #'S' ; send speed label
call @sendByte
call @sendSpace
mov i, #6
mov FSR, #speedStart ; send ? speed characters
:loop2 mov RS232out, IND
call @sendByte
inc FSR
djnz i, @:loop2
call @padLine ; clear rest of line with spaces
retp
;-------------------------------------------------------
_showTimeAlt
; Show time and altitude. Time is always available, but if not
; in sync then alt is not available.
mov FSR, #timeStart ; send 6 time characters
:loop mov RS232out, IND
call @sendByte
inc FSR
cjbe FSR, #timeEnd, @:loop
showalt
call @sendSpace
mov RS232out, #'h' ; send height label
call @sendByte
call @sendSpace
bank bank_lonstat
cjne statusChar, #'1', @saynosync
mov FSR, #altStart ; send 4 or so altitude characters
:loop mov RS232out, IND
call @sendByte
inc FSR
cjb FSR, #altEnd, @:loop
call @padLine
retp
saynosync
mov W, #nosync
call @sendString
call @padLine
retp
;-------------------------------------------------------
_Lat2Feet
; Enter with mult holding the binary of the latitude that
; we want to calculate distance from.
; Return with the accumulator containing the feet from the current
; latitude to the pointed-to latitude.
; negFlag set to 1 if we're South of the reference; 0 if North.
; one minute of Lat ~ 6072 feet; 6072/10000 = 0.6072; ~ 39/64
call @clearAccum
bank bank_math
mov eight, #$27 ; multiply by 39 (0x27), leaving the
call @multiply32 ; product in the accumulator.
bank bank_math
mov eight, #6 ; divide by 64 by shifting 6
call @shift32
retp
;-------------------------------------------------------
_Lon2Feet
; Enter with mult holding to the binary of the longitude that
; we want to calculate distance from.
; Return with the accumulator containing the feet from the current
; longitude to the pointed-to longitude.
; negFlag set to 1 if we're East of the reference; 0 if West.
; one min of Lon at 37 deg ~ 4790 feet; 4790/10000 = 0.4790; ~ 122/256
call @clearAccum
bank bank_math
mov eight, #$7A ; multiply by 122 (0x7A), leaving the
call @multiply32 ; product in the accumulator.
bank bank_math
mov eight, #8 ; divide by 256 by shifting 8
call @shift32
retp
;-------------------------------------------------------
_shift32
; divide the 4 byte number at pointer1 by (2 to the power of (eight))
bank bank_math
:divideby
clc ; On rotate, carry bit would rotate
rr accum3 ; into hi bit of mult3, which we
rr accum2 ; don't want, so clear it.
rr accum1
rr accum0
djnz eight, @:divideby
retp
;-------------------------------------------------------
_clearAccum
bank bank_math ; prepare for a 4-byte by 1 byte
clr accum0 ; multiplication.
clr accum1 ; Don't touch the 4 "mult" bytes,
clr accum2 ; because subtract has populated them.
clr accum3
retp
;-------------------------------------------------------
_DDMMToBin
; Convert a quantity in degrees, minutes and decimal minutes from
; 10 ASCII characters in the form DDDMM.MMMM to a 4 byte binary value
; representing ten-thousanths of a minute. (about 6 inches of distance
; when the DDMM is latitude or longitude).
; Call with "pointer1" pointing to the start of the DDDMM.MMMM to convert.
; results will be in the 4 byte accumulator.
;
; any advantage to moving the constants below to a code area lookup table?
; no, looks like would take as much work to set up each load as to
; simply load literals as below.
call @clearAccum
call @multPreset
mov mult3, #$03
mov mult2, #$93 ; hundreds of degrees to minutes: x 6000
mov mult1, #$87 ; minutes to 1/10000 of minute: x 10,000
mov mult0, #$00 ; 600 000 000 = $03 93 87 00
call @multiply32
call @multPreset
mov mult2, #$5B ; tens of degrees to minutes: x 600
mov mult1, #$8D ; minutes to 1/10000 of minute: x 10,000
mov mult0, #$80 ; 60 000 000 = $5B 8D 80
call @multiply32
call @multPreset
mov mult2, #$09 ; degrees to minutes: x 60
mov mult1, #$27 ; minutes to 1/10000 of minute: x 10,000
mov mult0, #$C0 ; 600 000 = $09 27 C0
call @multiply32
call @multPreset
mov mult2, #$01
mov mult1, #$86 ; tens of mins to 1/10000 of minute: x 100,000
mov mult0, #$A0 ; 100,000 = $01 86 A0
call @multiply32
call @multPreset
mov mult1, #$27 ; minutes to 1/10000 of minute: x 10,000
mov mult0, #$10 ; 10,000 = $27 10
call @multiply32
inc pointer1 ; skip the decimal point
call @multPreset
mov mult1, #$03 ; tenths of minute to 1/10000 of minute: x 1000
mov mult0, #$E8 ; 1000 = $03 E8
call @multiply32
call @multPreset
mov mult0, #$64 ; 100 = $64
call @multiply32
call @multPreset
mov mult0, #$0A ; 10 = $0A
call @multiply32
call @multPreset
mov mult0, #$01 ; 1
call @multiply32
retp
_multPreset
mov FSR, pointer1 ; operate on the digit that "pointer"
mov temp, IND ; currently is pointing to
bank bank_math ;
sub temp, #'0' ; Adjust ASCII to number
mov eight, temp ; write to the single-byte multiplier loc
mov mult3, #$00 ; these usually must be zero. When
mov mult2, #$00 ; not, let the above prg overwrite 0.
mov mult1, #$00
inc pointer1
retp
;**************************************************************************
; math subroutines
;
; multiply32 (eight) x (mult) -> (accum)
; increment32 (pointer1)++
; subtract32 (abs(mult - accum)) -> accum; negFlag=1 if minus
; clear32 (pointer1) -> 0
; showHex32 (pointer1 -> 8 hex nybbles -> LCD
; copy32 (pointer1) -> (pointer2)
;
;
;-------------------------------------------------------
_multiply32
; Multiply the 8 bit number in "eight" times the 32 bits in mult0-mult3,
; storing the result in the 4 accumulator bytes accum0-accum3.
; Uses "counter".
; This does not preclear the accumulator, so it can be used for repeated
; multiplications where the results all need to be accumulated together,
; so should call clearmult before its first use.
bank bank_math
mov counter, #8
multloop:
rr eight ; move least bit of "eight" into carry register
jnc @rotate ; if it's 0, don't do any adding
clc
add accum0, mult0 ; add byte 0...
addb accum1, C ; then first add the carry bit into byte1,
add accum1, mult1 ; then add the byte, making the byte 2 carry...
addb accum2, C
add accum2, mult2
addb accum3, C
add accum3, mult3
rotate:
clc ; When we rotate, the carry bit would rotate
rl mult0 ; into the low bit of mult0, which we don't
rl mult1 ; want, so clear it.
rl mult2
rl mult3
djnz counter, @multloop
retp
;-------------------------------------------------------
_subtract32
; Subtract 4 bytes: Absolute value(Mult - Accum) -> Mult.
; If the result is below zero then fix: invert and add 1, a la 2's complement,
; and set a "negative" flag.
bank bank_math
mov negFlag, #0
sub mult0, accum0
jc @:p1
inc accum1
:p1 sub mult1, accum1
jc @:p2
inc accum2
:p2 sub mult2, accum2
jc @:p3
inc accum3
:p3 sub mult3, accum3
jc @:done ; if result >= 0 then done.
mov negFlag, #1 ; Otherwise, set negative flag
not mult0 ; and twiddle the results to be
not mult1 ; as if we subtracted mult from accum.
not mult2 ; (NOT the 4 bytes, then add 1)
not mult3
clc
add mult0, #1
addb mult1, C
addb mult2, C
addb mult3, C
:done retp
;-------------------------------------------------------
_increment32
; increment the pointed-to set of 4 bytes
; mainly for testing various 4-byte math routines
mov FSR, pointer1
add ISR, #1 ; incr LSB, setting the carry bit appropriately
inc FSR
addb ISR, C ; if there's a carry bit then pass it along...
inc FSR
addb ISR, C ; and here...
inc FSR
addb ISR, C ; me, too.
retp
;-------------------------------------------------------
_copy32
; copy values of 4 bytes starting at pointer1 to 4 bytes at pointer2
; modifies pointer1, pointer2, temp
mov i, #4
:loop
mov FSR, pointer1 ; operate on the digit that "pointer"
mov temp, IND ; currently is pointing to
mov FSR, pointer2 ; operate on the digit that "pointer"
mov IND, temp
inc pointer1
inc pointer2
djnz i, :loop
retp
;-------------------------------------------------------
_showHex32
; show contents of four bytes in hex pointed to by "pointer1".
mov FSR, pointer1
inc FSR
inc FSR
inc FSR
mov RS232out, ISR
call @sendHexByte
mov FSR, pointer1
inc FSR
inc FSR
mov RS232out, ISR
call @sendHexByte
mov FSR, pointer1
inc FSR
mov RS232out, ISR
call @sendHexByte
mov FSR, pointer1
mov RS232out, ISR
call @sendHexByte
retp
;**************************************************************************
; LCD functions
_hello
call @clearScreen
call @delay100ms
call @lcdLine1
mov W, #shello
call @sendString
call @delay100ms
call @lcdLine2
mov W, #sline2
call @sendString
call @delay1sec
retp
;---------------------------------------------------------
_sendHexByte
; write value, 0-255, in "RS232out", to LCD in two hex bytes.
bank bank_assorted
mov hexByte, RS232out ; save the value because "RS232out"
mov nybs, hexByte ; is destroyed in hexNybble
swap nybs
call @hexNybble
bank bank_assorted
mov nybs, hexByte
call @hexNybble
retp
_hexNybble
and nybs, #$0F ; cvrt 4 bits to hex and send to LCD
clc ;
csa nybs, #$9 ; if > 9, skip jmp and do add.
jmp @:label
clc ; required if clearx is set
add nybs, #$7
:label add nybs, #$30
mov RS232out, nybs
call @sendByte ; send byte to the LCDisplay
retp
;--------------------------------------------------
_sendString
; Send 0-terminated string of up to 20 chars to LCD
bank bank_assorted
mov string,w
:loop
mov w,string
mov m,#0
iread ; reads value from 11-bit code address in M, W
test w
jnz @:send
retp
:send mov RS232out, w
call @sendByte
bank bank_assorted
inc string
jmp @:loop
;--------------------------------------------------
_clearScreen
mov RS232out, #$FE
call @sendByte
mov RS232out, #$1
call @sendByte
bank bank_assorted
mov charsPerLine, #0
call @delay1ms
retp
;--------------------------------------------------
_lcdLine1
mov RS232out, #$FE
call @sendByte
mov RS232out, #$80
call @sendByte
bank bank_assorted
mov charsPerLine, #0
retp
;--------------------------------------------------
_lcdLine2
mov RS232out, #$FE
call @sendByte
mov RS232out, #$C0
call @sendByte
bank bank_assorted
mov charsPerLine, #0
retp
;--------------------------------------------------
_sendSpace
mov RS232out, #' '
call @sendByte
retp
;--------------------------------------------------
_padLine
bank bank_assorted
:loop
cjae charsPerLine, #20, @:done
call @sendSpace
jmp @:loop
:done
retp
;--------------------------------------------------
;**************************************************************************
; delay functions
_delay1sec
bank bank_assorted
mov timer4,#9
:loop call @delay100ms
bank bank_assorted
djnz timer4, @:loop
retp
;--------------------------------------------------
_delay100ms
bank bank_assorted
mov timer3, #99
:loop call @delay1ms
djnz timer3, @:loop
retp
;--------------------------------------------------
_delay1ms
bank bank_assorted
mov timer2, #9
:loop call @delay9600
bank bank_assorted
djnz timer2, @:loop
retp
;--------------------------------------------------
;**************************************************************************
; RS232 functions
_longLow
; Loop until a GPS message arrives.
; method: loop over the time of about 2 characters, testing
; for a hi bit twice the 9600 baud bitrate. During this looping,
; if a high bit is ever detected, restart the loop from the beginning.
bank bank_assorted
mov vlongLow, #32 ; prepare for loop
:loop
call @halfDelay
test rb.0
jnz @_longLow ; if ever a non-zero bit, getGPSMsgGrp.
djnz vlongLow, @:loop
retp
;-------------------------------------------------------
_getByte
; wait for, then return an RS232 byte in the "RS232in" variable.
bank bank_assorted
test rb.0 ; spin until bit B0 goes high
jz _getByte
call @halfDelay ; wait til middle of start bit
mov bitCount, #8 ; prepare for loop
mov RS232in, #$0 ; and preset result
:loop
call @delay9600 ; wait for middle of data bit
clc ; clear carry bit
rr RS232in ; prepare result for receiving next bit
test rb.0 ; have we received a one or a zero?
jnz @:zerobt ; skip bitset if zero
or RS232in, #$80 ; we got a one, so set hi bit of result byte
:zerobt djnz bitCount, @:loop ; next bit
; done getting bits.
call @delay9600 ; wait for middle of stop bit
retp ; return "RS232in"
;-----------------------------------------------------------
_sendByte
; Send the byte in "RS232out" to the LCD
; Destroys RS232out.
mov bitCount, #8
setb ra.0 ; start bit
call @delay9600
:loop snb RS232out.0 ; 8 data bits
jmp @:sndhi
setb ra.0
jmp @:done
:sndhi clrb ra.0
:done rr RS232out
call @delay9600
djnz bitCount, @:loop
clrb ra.0 ; stop bit
call @delay9600
mov temp, FSR ; count chars sent. Used for padding
bank bank_assorted ; the LCD line with spaces to the end.
inc charsPerLine
mov FSR, temp
retp
;-----------------------------------------------------------
_delay9600
; these are the fundamental delays for RS232, both send and receive.
; tuned via trial and error with what works to the LCD
; 215 10% ok, 205 ok, 200 ok, 198 10% ok, so use 206
call @halfDelay
call @halfDelay
retp
_halfDelay
mov timer1, #206
:loop djnz timer1, @:loop
retp
; This is a version that works properly using a 50 mHz resonator
; instead of the 20 mHz crystal. (But don't like the additional
; current the SX needs: 80 mA - about double that of running at 20.)
;
;halfDelay
; mov timer1, #110
;:loop1 djnz timer1, @:loop1
;:loop2 djnz timer1, @:loop2
;:loop3 djnz timer1, @:loop3
; retp
;**************************************************************************
; test and experimental area
;
; what about a generic loader that uses pointer1 for where to load to,
; a zero-terminated string for what to load...? Any benefit?
; But a zero-terminated string can't load a zero.
; what about instead specifying number of bytes to load?
;
_newLoadLatLon
mov pointer1, latToGo
mov pointer2, #latASCII
mov i, #10
call @loader
mov pointer1, lonToGo
mov pointer2, #lonASCII
mov i, #10
call @loader
retp
;---------------------------------------------------------
_loader
mov M, #0
mov FSR, pointer2
:loop mov W, pointer1
iread
mov IND, W
inc FSR
inc pointer1
djnz i, @:loop
retp
;----------------------------------------------------------
_loadLatLon
mov FSR, #latASCII
mov IND, #'0'
inc FSR
mov IND, #'3'
inc FSR
mov IND, #'7'
inc FSR
mov IND, #'4'
inc FSR
mov IND, #'1'
inc FSR
mov IND, #'.'
inc FSR
mov IND, #'3'
inc FSR
mov IND, #'5'
inc FSR
mov IND, #'5'
inc FSR
mov IND, #'7'
mov FSR, #lonASCII
mov IND, #'1'
inc FSR
mov IND, #'2'
inc FSR
mov IND, #'1'
inc FSR
mov IND, #'5'
inc FSR
mov IND, #'3'
inc FSR
mov IND, #'.'
inc FSR
mov IND, #'3'
inc FSR
mov IND, #'3'
inc FSR
mov IND, #'6'
inc FSR
mov IND, #'5'
retp
;-------------------------------------------------------
subtest
; test 4x4 subtracter by poking in values, timesing, displaying.
:loop
call @delay100ms
call @delay100ms
call @delay100ms
call @delay100ms
bank bank_math
mov mult0, #4
mov mult1, #0
mov mult2, #0
mov mult3, #0
mov accum0, #2
mov accum1, #6
mov accum2, #0
mov accum3, #0
call @clearScreen
call @lcdLine1
bank bank_math
mov pointer1, #multStart
call @showHex32
call @sendSpace
bank bank_math
mov pointer1, #accumStart
call @showHex32
call @subtract32 ; mult - accum -> mult
call @lcdLine2
bank bank_math
mov pointer1, #multStart
call @showHex32
call @sendSpace
bank bank_math
mov RS232out, negFlag
call @sendHexByte
jmp @:loop
retp
;-------------------------------------------------------
_multTest
; test 1x4 multiplier by poking in values, timesing, displaying.
bank bank_math
mov math0, #$01
:loope
call @delay100ms
call @delay100ms
call @delay100ms
bank bank_math
mov mult0, #2
mov mult1, #0
mov mult2, #1
mov mult3, #0
mov accum0, #0
mov accum1, #0
mov accum2, #0
mov accum3, #0
inc math0
mov eight, math0
call @clearScreen
call @lcdLine1
mov pointer1, #multStart
call @showHex32
call @sendSpace
bank bank_math
mov RS232out, math0
call @sendHexByte
call @multiply32
call @lcdLine2
mov pointer1, #accumStart
call @showHex32
jmp @:loope
retp
;-------------------------------------------------------
_latToBinTest
; test latitude to binary conversion: 37 41.3557 -> 01 59 0E 35
call @loadLatLon ; write test lat + lon into "current"
call @clearScreen
call @lcdLine1
call @showLatLon ; outputs exactly 20 characters
mov pointer1, #latASCII
call @DDMMToBin ; convert to binary in accumulator
call @lcdLine2
mov pointer1, #accumStart ; show what we've got:
call @showHex32
retp
;-------------------------------------------------------
_latSubTest
; test latitude subtraction
call @loadLatLon ; write test lat + lon into "current"
call @clearScreen
call @lcdLine1
call @showLatLon ; outputs exactly 20 characters
mov pointer1, #latASCII
call @DDMMToBin ; convert to binary in accumulator
mov pointer1, #accumStart
mov pointer2, #multStart
call @copy32
bank bank_math
clc
add mult0, #1 ; test: tweak one of the numbers: 1 00
stc
sub mult0, #1 ; test other dir: 1 01
call @subtract32
call @lcdLine2
mov pointer1, #multStart ; show what we've got:
call @showHex32
call @sendSpace
bank bank_math
mov RS232out, negFlag
call @sendHexByte
retp
;**************************************************************************
; Experimental area 51
_binToASCIITest
; Testing binToASCII
;
; 1. plug known numbers into hiBin, loBin
; 2. run the routine
; 3. send the 5 digit results to the LCD
; 4. pause half a second
; 5. increment the known numbers by 1, 2, 5, ...
; 6. goto 1
mov pointer1, #0
mov pointer2, #0
:loop
call @delay100ms
call @delay100ms
bank bank_toASCII
mov loBin, pointer1
mov hiBin, pointer2
call @binToASCII
call @clearScreen
call @lcdLine1
mov RS232out, pointer2
call @sendHexByte
mov RS232out, pointer1
call @sendHexByte
call @sendSpace
bank bank_toASCII
mov RS232out, digit5
call @sendByte
bank bank_toASCII
mov RS232out, digit4
call @sendByte
bank bank_toASCII
mov RS232out, digit3
call @sendByte
bank bank_toASCII
mov RS232out, digit2
call @sendByte
bank bank_toASCII
mov RS232out, digit1
call @sendByte
add pointer1, #1
jnc @:loop
add pointer2, #1
jmp @:loop
retp
;-------------------------------------------------------
_binToASCII
; take 2 bytes (hiBin, loBin) and convert to ASCII digits 65535 - 00000
; Basically, think of the bytes as the decimal number.
; For example, if your number is less than 40000 but over 30000,
; then write "3", subtract 30000 from the number, and move on
; to do the same for the thousands place, the hundreds place, ...
;
; recent test: working perfectly for the first byte 0-255, but
; screws up beyond that.
bank bank_toASCII
;---------------------------------------- Top of ten thousands loop
mov counter2, #6
mov hiCmp, #$EA ; EA60 = 60000
mov loCmp, #$60
:tenThousands
cjb hiBin, hiCmp, @:loopOn5 ; if number less than 60000, 50000, ...
cjb loBin, loCmp, @:loopOn5 ; then subtract 10000 and test again.
jmp @:emitDigit5 ; otherwise grab the digit and run...
:loopOn5
sub loCmp, #$10 ; $2710 = 10000
jc @:p51
dec hiCmp
:p51 sub hiCmp, #$27
djnz counter2, @:tenThousands
:emitDigit5
add counter2, #'0' ; When we hit the number that this is
mov digit5, counter2 ; not less than, then BINGO! the counter
sub loBin, loCmp ; holds the digit we want! Subtract off
jc @:p52
dec hiBin
:p52 sub hiBin, hiCmp ; the 10000's place number and continue...
;--------------------------------------- Top of thousands loop
mov counter2, #9
mov hiCmp, #$23 ; $2328 = 9000
mov loCmp, #$28
:thousands
cjb hiBin, hiCmp, @:loopOn4 ; if this number is less than 9000,
cjb loBin, loCmp, @:loopOn4 ; then subtract 1000 and test again.
jmp @:emitDigit4
:loopOn4
sub loCmp, #$E8 ; $03E8 = 1000
jc @:p41
dec hiCmp
:p41 sub hiCmp, #$03
djnz counter2, @:thousands
:emitDigit4
add counter2, #'0' ; When we hit the number that this is
mov digit4, counter2 ; not less than, then BINGO! the counter
sub loBin, loCmp ; holds the digit we want!
jc @:p42
dec hiBin
:p42 sub hiBin, hiCmp
;---------------------------------------- Top of hundreds loop
mov counter2, #9
mov hiCmp, #$03 ; $0384 = 900
mov loCmp, #$84
:hundreds
cjb hiBin, hiCmp, @:loopOn3 ; if this number is less than 900,
cjb loBin, loCmp, @:loopOn3 ; then subtract 100 and test again.
jmp @:emitDigit3
:loopOn3
sub loCmp, #$64
jc @:p31
dec hiCmp
:p31 ; sub hiCmp, #$00 ; $0064 = 100
djnz counter2, @:hundreds
:emitDigit3
add counter2, #'0' ; When we hit the number that this is
mov digit3, counter2 ; not less than, then BINGO! the counter
sub loBin, loCmp ; holds the digit we want!
jc @:p32
dec hiBin
:p32 sub hiBin, hiCmp
;---------------------------------------- Top of tens loop
mov counter2, #9
mov hiCmp, #$00 ; $005A = 90
mov loCmp, #$5A
:tens
cjb hiBin, hiCmp, @:loopOn2 ; if this number is less than 90,
cjb loBin, loCmp, @:loopOn2 ; then subtract 10 and test again.
jmp @:emitDigit2
:loopOn2
sub loCmp, #$A
; jc @:p21
; dec hiCmp
:p21 ; sub hiCmp, #$00 ; $000A = 10
djnz counter2, @:tens
:emitDigit2
add counter2, #'0' ; When we hit the number that this is
mov digit2, counter2 ; not less than, then BINGO! the counter
sub loBin, loCmp ; holds the digit we want!
jc @:p22
dec hiBin
:p22 sub hiBin, hiCmp
;---------------------------------------- Top of Ones part
add loBin, #'0'
mov digit1, loBin
retp
;-------------------------------------------------------
;
; some lookup data tables
; like,
; mov M, lat_table >> 8 ; put top 3 bits into M
; mov W, lat_table ; put bottom 8 bits into W
; IREAD ; read code memory into W
; mov ISR, W ; and move the result into the variable
;-------------------------------------------------------
;org $600
;lat_table dw '03741.3557',0
;lon_table dw '12153.3665',0
; the end
;
;-------------------------------------------------------
; notes
;
; The GPS sensor has the capability to send maybe a dozen standard
; NMEA (G...) and proprietary Garmin (P...) message strings, like
;
; $GPGGA,205343,3749.9327,N,12232.3580,W,1,08,1.1,12.5,M,-28.0,M,,*42
; $PGRMV,0.0,0.0,0.0*5C
; $PGRMT,GPS 25-LVC VER 2.50 ,P,P,R,R,P,,19,R*15
;
; You program the GPS sensor to configure it to enable the messages you
; want at the baud rate you want, by writing a short ASCII configuration
; string into the sensor's RS232 input wire.
; Then, whenever the GPS sensor is powered up, it starts emitting groups of
; all enabled messages, once per second.
; It is up to the user to make sure that you don't enable more characters
; than may be sent in one second at the programmed baud rate.
;
; So once its configured, you send power into the GPS sensor,
; point it at the sky and receive RS232 out a wire.
;
;-------------------------------------------------------
; The LCD works like this. Send RS232 ASCII charscters and they are
; displayed on the LCD.
; RS232 control characters place the "cursor" at the start of line1,
; or line2, or anywhere in the displayable area, actually.
; Other control sequences will clear the screen, draw graphics, etc.
;
;---the end----------------------------------------------------
| file: /Techref/new/letter/news0312_gpslcd.htm, 60KB, , updated: 2014/10/13 17:01, local time: 2025/10/26 03:12,
216.73.216.22,10-3-83-201:LOG IN
|
| ©2025 These pages are served without commercial sponsorship. (No popup ads, etc...).Bandwidth abuse increases hosting cost forcing sponsorship or shutdown. This server aggressively defends against automated copying for any reason including offline viewing, duplication, etc... Please respect this requirement and DO NOT RIP THIS SITE. Questions? <A HREF="http://www.piclist.com/Techref/new/letter/news0312_gpslcd.htm"> December 2003 MassMind newsletter</A> |
| Did you find what you needed? |
|
o List host: MIT, Site host massmind.org, Top posters @none found - Page Editors: James Newton, David Cary, and YOU! * Roman Black of Black Robotics donates from sales of Linistep stepper controller kits. * Ashley Roll of Digital Nemesis donates from sales of RCL-1 RS232 to TTL converters. * Monthly Subscribers: Gregg Rew. on-going support is MOST appreciated! * Contributors: Richard Seriani, Sr. |
Welcome to www.piclist.com! |
.