; 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 16:01, local time: 2024/11/22 16:47,
18.116.90.161:LOG IN
|
©2024 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://techref.massmind.org/Techref/new/letter/news0312_gpslcd.htm"> December 2003 MassMind newsletter</A> |
Did you find what you needed? |
Welcome to massmind.org! |
Welcome to techref.massmind.org! |
.