' ========================================================================= ' ' File...... GIS_GPS.SXB ' Purpose... Parallax GPS Module ' Author.... Joe Grand, Grand Idea Studio, Inc. [www.grandideastudio.com] ' E-mail.... support@parallax.com ' Updated... 26 FEB 2010 ' Revision.. 2.0 (also defined in VER_FW constant) ' ' ========================================================================= ' ------------------------------------------------------------------------- ' Program Description ' ------------------------------------------------------------------------- ' ' Distributed under a Creative Commons Attribution-Share Alike 3.0 Unported ' license: http://creativecommons.org/licenses/by-sa/3.0/ ' ' The main control code for the Parallax GPS Module. ' ' This code communicates with the Polstar PMB-648 GPS receiver unit ' (http://www.polstargps.com/Polstar_GPSproducts_PMB648.html) via a ' 4800bps, 8N1 serial interface. The standard NMEA0183-formatted data is ' parsed and returned to the user via a single, bi-directional serial pin ' (SIO) based on the data that was requested by the user. ' ' Optionally, the raw NMEA0183 data stream from the GPS receiver can be ' returned to the user by bringing the /RAW pin LOW. ' ' In theory, this code should work as-is with any GPS receiver that outputs ' SiRF NMEA-compatible data. More information on the SiRF NMEA protocol ' can be found at http://www.usglobalsat.com/downloads/NMEA_commands.pdf ' ' Commands: ' ' "!GPS", 0x00 - GetInfo: GPS Receiver Module version (2 bytes) ' "!GPS", 0x01 - GetValid: Check validity of received data (1 byte) ' "!GPS", 0x02 - GetSats: Number of acquired satellites (1 byte) ' "!GPS", 0x03 - GetTime: Time (UTC/Greenwich Mean Time) (3 bytes) ' "!GPS", 0x04 - GetDate: Date (UTC/Greenwich Mean Time) (3 bytes) ' "!GPS", 0x05 - GetLat: Latitude (5 bytes) ' "!GPS", 0x06 - GetLong: Longitude (5 bytes) ' "!GPS", 0x07 - GetAlt: Altitude above mean-sea-level (decimal: 0-6553.5 meters) (2 bytes) ' "!GPS", 0x08 - GetSpeed: Speed over ground (2 bytes) ' "!GPS", 0x09 - GetHead: Heading/direction of travel over ground (2 bytes) ' "!GPS", 0x0A - GetAltExt: Altitude above mean-sea-level (integer: 0-65535 meters) (2 bytes) ' ' This code currently parses data from the NMEA0183 $GPRMC and $GPGGA ' strings on an as-requested basis: ' ' $GPRMC,POS_UTC,POS_STAT,LAT,LAT_D,LON,LON_D,SPD,HDG,DATE,MAG_VAR,MAG_REF,*CC ' ' POS_UTC - UTC of position. Hours, minutes and seconds. (hhmmss.sss) ' POS_STAT - Position status. (A = Data valid, V = Data invalid) ' LAT - Latitude (ddmm.ffff) ' LAT_D - Latitude direction. (N = North, S = South) ' LON - Longitude (dddmm.ffff) ' LON_D - Longitude direction (E = East, W = West) ' SPD - Speed over ground. (knots) (0 - 999.9) (field length not fixed) ' HDG - Heading/track made good (degrees relative to True North) (xxx.x) (field length not fixed) ' DATE - Date (ddmmyy) ' MAG_VAR - Magnetic variation (Unsupported/unused by SiRF chipset) ' MAG_REF - Magnetic variation (E = East, W = West) (Unsupported/unused by SiRF chipset) ' *CC - Checksum ' ' $GPGGA,POS_UTC,LAT,LAT_D,LON,LON_D,POS_FIX,SATS,HDOP,ALT,ALT_U,GEO,GEO_U,DGPS_AGE,DGPS_ID,*CC ' ' POS_UTC - UTC of position. Hours, minutes and seconds. (hhmmss.sss) ' LAT - Latitude (ddmm.ffff) ' LAT_D - Latitude direction. (N = North, S = South) ' LON - Longitude (dddmm.ffff) ' LON_D - Longitude direction (E = East, W = West) ' POS_FIX - Position Fix Indicator (0 = Fix N/A or invalid, 1 = GPS SPS mode, ' 2 = Differential GPG/SPS Mode, 3-5 = Not Supported, ' 6 = Dead Reckoning) ' SATS - Number of acquired satellites used in position calculations (0 - 12) ' HDOP - Horizontal dilution of position (00.0 - 99.9) ' ALT - MSL Altitude (0 - 99999.9) (field length not fixed) ' ALT_U - Altitude unit (M = meters) ' GEO - Geoidal separation (-000.0 - -999.9) ' GEO_U - Geoidal separation unit (M = meters) ' DGPS_AGE - Age of DGPS data (time elapsed since DGPS reception) (seconds) (00 - 99) ' DGPS_ID - DGPS Reference Station ID (xxxx) ' *CC - Checksum ' ------------------------------------------------------------------------- ' Notes ' ------------------------------------------------------------------------- ' It is possible for this code to enter an infinite loop if we are waiting ' for specific GPS data that never comes (e.g., if the GPS unit fails) ' ' GetAltExt command provided by Mark J Culross (KD5RXT), kd5rxt@arrl.net: ' ' The GetAltExt command returns whole number altitude (no decimal portion), ' providing an altitude range of 00000 to 65535 ($0000-$FFFF) meters ' (approximately 215,118 feet maximum). ' ' The Polstar PMB-648 GPS receiver is spec'd to only provide altitude ' data to 18000 meters (approximately 59,085 feet maximum), so word- ' boundary checking need not be done. In the event that this command is ' used with a different GPS receiver, word-boundary checking is only ' required if the receiver supports altitudes greater than 65535 meters. ' ------------------------------------------------------------------------- ' Device Settings ' ------------------------------------------------------------------------- DEVICE SX20, OSCXT2, TURBO, STACKX, OPTIONX FREQ 20_000_000 ' ------------------------------------------------------------------------- ' IO Pins ' ------------------------------------------------------------------------- Raw VAR RA.2 ' pull-up via 4.7K (active low) Sio VAR RA.3 ' pull-up via 4.7K Gps_rx VAR RB.2 ' RX from PGM-648 Gps_tx VAR RB.3 ' TX to PGM-648 (For future use, ' Must be set to INPUT when unused) ' ------------------------------------------------------------------------- ' Constants ' ------------------------------------------------------------------------- VER_HW CON $10 ' hardware version (x.y) VER_FW CON $20 ' firmware version (x.y) Baud CON "T4800" CR CON 13 ' carriage return LF CON 10 ' line feed ' ------------------------------------------------------------------------- ' Variables ' ------------------------------------------------------------------------- serByte VAR Byte ' serial IO byte rcvBuf VAR Byte(12) ' buffer for received GPS data idx VAR Byte ' loop control ' temporary work variables for main routines tmp1 VAR Byte tmp2 VAR Byte tmp3 VAR Byte tmpWord VAR Byte(2) ' temporary work variables for subroutines temp1 VAR Byte temp2 VAR Byte temp3 VAR Byte temp4 VAR Byte temp5 VAR Byte temp6 VAR Byte ' ========================================================================= PROGRAM Start ' ========================================================================= Pgm_ID: DATA "Parallax GPS Receiver Module", 0 ' ------------------------------------------------------------------------- ' Subroutine Declarations ' ------------------------------------------------------------------------- CONVERT_VALUE SUB ' convert ASCII value into decimal WAIT_FOR_COMMA SUB ' wait for a comma to be received from GPS WAIT_FOR_RMC SUB ' wait for the string "RMC" from GPS WAIT_FOR_GGA SUB ' wait for the string "GGA" from GPS TX_BYTE SUB 1 ' transmit byte to serial interface RX_BYTE SUB ' receive byte from serial interface RX_BYTE_GPS SUB ' receive byte from GPS interface WORD_ADD_BYTE SUB 2 '@Result16, Value8 WORD_MULT_BYTE SUB 2 '@Result16, Value8 ' ------------------------------------------------------------------------- ' Program Code ' ------------------------------------------------------------------------- Start: ' hardware initialization CMP_B = 1 ' disable comparator TRIS_B = %11111100 ' RB.7-2 is input, RB.3 & RB.1-0 is output WKEN_B = %11111111 ' disable interrupts ST_B = %11111111 ' disable schmitt trigger inputs Main: IF Raw = 0 THEN ' /RAW pin is low, so enter "raw" mode INPUT Gps_rx ' ensure Gps_rx is set to input to avoid any contention GOTO Main ' let the external analog switch handle the rest by routing the desired signal to SIO ELSE ' /RAW pin is high, so enter "smart" mode ' wait for header from user serByte = RX_BYTE IF serByte <> "!" THEN Main serByte = RX_BYTE IF serByte <> "G" THEN Main serByte = RX_BYTE IF serByte <> "P" THEN Main serByte = RX_BYTE IF serByte <> "S" THEN Main Get_Cmd: ' get command from user (specifies desired data to receive from the GPS) serByte = RX_BYTE IF serByte = $00 THEN Get_Info IF serByte = $01 THEN Get_Valid IF serByte = $02 THEN Get_Sats IF serByte = $03 THEN Get_Time IF serByte = $04 THEN Get_Date IF serByte = $05 THEN Get_Lat IF serByte = $06 THEN Get_Long IF serByte = $07 THEN Get_Alt IF serByte = $08 THEN Get_Speed IF serByte = $09 THEN Get_Head IF serByte = $0A THEN Get_AltExt GOTO Main ENDIF ' ---------------------------------------------------- Get_Info: ' command = $00 PAUSE 3 ' let host get ready TX_BYTE VER_HW ' hardware version TX_BYTE VER_FW ' firmware version GOTO Main ' ---------------------------------------------------- Get_Valid: ' command = $01 WAIT_FOR_RMC ' wait for $GPRMC header from GPS module WAIT_FOR_COMMA WAIT_FOR_COMMA ' receive byte from GPS stream rcvBuf(0) = RX_BYTE_GPS tmp1 = 0 IF rcvBuf(0) = "A" THEN ' any other character must be invalid data tmp1 = 1 ENDIF tmp1_return: TX_BYTE tmp1 GOTO Main ' ---------------------------------------------------- Get_Sats: ' command = $02 WAIT_FOR_GGA ' wait for $GPGGA header from GPS module FOR idx = 0 TO 6 ' jump to (n+1)th field WAIT_FOR_COMMA NEXT ' receive string from GPS stream rcvBuf(0) = RX_BYTE_GPS rcvBuf(1) = RX_BYTE_GPS ' extract values from buffer rcvBuf(0) = rcvBuf(0) - "0" rcvBuf(1) = rcvBuf(1) - "0" tmp1 = rcvBuf(0) * 10 tmp1 = tmp1 + rcvBuf(1) GOTO tmp1_return ' ---------------------------------------------------- Get_Time: ' command = $03 WAIT_FOR_RMC ' wait for $GPRMC header from GPS module WAIT_FOR_COMMA FOR idx = 0 TO 5 ' receive string from GPS stream (hhmmss) rcvBuf(idx) = RX_BYTE_GPS NEXT ' extract values from buffer FOR idx = 0 TO 5 rcvBuf(idx) = rcvBuf(idx) - "0" NEXT tmp1 = rcvBuf(0) * 10 tmp1 = tmp1 + rcvBuf(1) tmp2 = rcvBuf(2) * 10 tmp2 = tmp2 + rcvBuf(3) tmp3 = rcvBuf(4) * 10 tmp3 = tmp3 + rcvBuf(5) tmp123_return: TX_BYTE tmp1 ' hours (or day) TX_BYTE tmp2 ' minutes (or month) TX_BYTE tmp3 ' seconds (or year) GOTO Main ' ---------------------------------------------------- Get_Date: ' command = $04 WAIT_FOR_RMC ' wait for $GPRMC header from GPS module FOR idx = 0 TO 8 ' jump to (n+1)th field WAIT_FOR_COMMA NEXT FOR idx = 0 TO 5 ' receive string from GPS stream rcvBuf(idx) = RX_BYTE_GPS NEXT ' extract values from buffer FOR idx = 0 TO 5 rcvBuf(idx) = rcvBuf(idx) - "0" NEXT tmp1 = rcvBuf(0) * 10 tmp1 = tmp1 + rcvBuf(1) tmp2 = rcvBuf(2) * 10 tmp2 = tmp2 + rcvBuf(3) tmp3 = rcvBuf(4) * 10 tmp3 = tmp3 + rcvBuf(5) GOTO tmp123_return ' ---------------------------------------------------- Get_Lat: ' command = $05 WAIT_FOR_RMC ' wait for $GPRMC header from GPS module FOR idx = 0 TO 2 ' jump to (n+1)th field WAIT_FOR_COMMA NEXT FOR idx = 0 TO 10 ' receive string from GPS stream rcvBuf(idx) = RX_BYTE_GPS NEXT ' extract values from buffer ' degrees rcvBuf(0) = rcvBuf(0) - "0" rcvBuf(1) = rcvBuf(1) - "0" tmp1 = rcvBuf(0) * 10 tmp1 = tmp1 + rcvBuf(1) ' minutes rcvBuf(2) = rcvBuf(2) - "0" rcvBuf(3) = rcvBuf(3) - "0" tmp2 = rcvBuf(2) * 10 tmp2 = tmp2 + rcvBuf(3) ' fractional minutes tmpWord = 0 FOR idx = 5 TO 8 CONVERT_VALUE NEXT tmp3 = 0 ' default to "N" IF rcvBuf(10) = "S" THEN tmp3 = 1 ENDIF latlon_return: TX_BYTE tmp1 TX_BYTE tmp2 TX_BYTE tmpWord(1) TX_BYTE tmpWord(0) TX_BYTE tmp3 GOTO Main ' ---------------------------------------------------- Get_Long: ' command = $06 WAIT_FOR_RMC ' wait for $GPRMC header from GPS module FOR idx = 0 TO 4 ' jump to (n+1)th field WAIT_FOR_COMMA NEXT FOR idx = 0 TO 11 ' receive string from GPS stream rcvBuf(idx) = RX_BYTE_GPS NEXT ' extract values from buffer ' degrees tmp1 = 0 FOR idx = 0 TO 2 rcvBuf(idx) = rcvBuf(idx) - "0" tmp1 = tmp1 * 10 tmp1 = tmp1 + rcvBuf(idx) NEXT ' minutes rcvBuf(3) = rcvBuf(3) - "0" rcvBuf(4) = rcvBuf(4) - "0" tmp2 = rcvBuf(3) * 10 tmp2 = tmp2 + rcvBuf(4) ' fractional minutes tmpWord = 0 FOR idx = 6 TO 9 CONVERT_VALUE NEXT tmp3 = 0 ' default to "E" IF rcvBuf(11) = "W" THEN tmp3 = 1 ENDIF GOTO latlon_return ' ---------------------------------------------------- Get_Alt: ' command = $07 WAIT_FOR_GGA ' wait for $GPGGA header from GPS module FOR idx = 0 TO 8 ' jump to (n+1)th field WAIT_FOR_COMMA NEXT FOR idx = 0 TO 6 ' receive string from GPS stream (maximum 7 bytes in length) rcvBuf(idx) = RX_BYTE_GPS NEXT ' set maximum height/ceiling to 6553.5m to prevent overflow of word variable IF rcvBuf(5) = "." THEN ' if the 6th byte is a decimal point, then we must be at an altitude > 10000m alt_max: tmpWord(0) = $FF ' set maximum tmpWord(1) = $FF GOTO tmpWord_return ENDIF IF rcvBuf(4) = "." THEN ' if the 5th byte is a decimal point, we may be up to 9999.9 meters IF rcvBuf(0) > "6" THEN GOTO alt_max ENDIF IF rcvBuf(0) = "6" THEN ' we're at least 6000m, so we might need to set the ceiling IF rcvBuf(1) > "5" THEN GOTO alt_max ENDIF IF rcvBuf(1) = "5" THEN IF rcvBuf(2) > "5" THEN GOTO alt_max ENDIF IF rcvBuf(2) = "5" THEN IF rcvBuf(3) > "3" THEN GOTO alt_max ENDIF IF rcvBuf(3) = "3" THEN IF rcvBuf(5) >= "5" THEN GOTO alt_max ENDIF ENDIF ENDIF ENDIF ENDIF ENDIF ' extract values from buffer tmpWord = 0 idx = 0 Next_Digit: ' gather digits as long as we haven't exceeded the length of this field IF idx <= 6 THEN IF rcvBuf(idx) = "." THEN idx = idx + 1 ' skip the decimal point ENDIF ' keep going as long as we haven't exceeded the length of this field IF idx <= 6 THEN ' & as long as we don't get into the next field IF rcvBuf(idx) <> "," THEN CONVERT_VALUE idx = idx + 1 ' go get the next digit ELSE idx = 99 ENDIF ENDIF ENDIF IF idx <= 6 THEN GOTO Next_Digit tmpWord_return: TX_BYTE tmpWord(1) TX_BYTE tmpWord(0) GOTO Main ' ---------------------------------------------------- Get_AltExt: ' command = $0A WAIT_FOR_GGA ' wait for $GPGGA header from GPS module FOR idx = 0 TO 8 ' jump to (n+1)th field WAIT_FOR_COMMA NEXT FOR idx = 0 TO 5 ' receive string from GPS stream (maximum 7 bytes in length including number after decimal point) rcvBuf(idx) = RX_BYTE_GPS NEXT ' extract values from buffer tmpWord = 0 idx = 0 Next_Digit2: ' gather digits as long as we haven't exceeded the length of this field IF idx <= 5 THEN IF rcvBuf(idx) = "." THEN idx = 99 ' skip the decimal point and the digit afterwards ENDIF ' keep going as long as we haven't exceeded the length of this field IF idx <= 5 THEN ' & as long as we don't get into the next field IF rcvBuf(idx) <> "," THEN CONVERT_VALUE idx = idx + 1 ' go get the next digit ELSE idx = 99 ENDIF ENDIF ENDIF IF idx <= 5 THEN GOTO Next_Digit2 GOTO tmpWord_return ' ---------------------------------------------------- Get_Speed: ' command = $08 WAIT_FOR_RMC ' wait for $GPRMC header from GPS module FOR idx = 0 TO 6 ' jump to (n+1)th field WAIT_FOR_COMMA NEXT FOR idx = 0 TO 4 ' receive string from GPS stream rcvBuf(idx) = RX_BYTE_GPS NEXT ' extract values from buffer tmpWord = 0 idx = 0 Next_Digit3: ' gather digits as long as we haven't exceeded the length of this field IF idx <= 4 THEN IF rcvBuf(idx) = "." THEN ' grab the tenths value and then we're done (ignore the hundredths) idx = idx + 1 CONVERT_VALUE idx = 99 ENDIF ' keep going as long as we haven't exceeded the length of this field IF idx <= 4 THEN ' & as long as we don't get into the next field IF rcvBuf(idx) <> "," THEN CONVERT_VALUE idx = idx + 1 ' go get the next digit ELSE idx = 99 ENDIF ENDIF ENDIF IF idx <= 4 THEN GOTO Next_Digit3 GOTO tmpWord_return ' ---------------------------------------------------- Get_Head: ' command = $09 WAIT_FOR_RMC ' wait for $GPRMC header from GPS module FOR idx = 0 TO 7 ' jump to (n+1)th field WAIT_FOR_COMMA NEXT FOR idx = 0 TO 4 ' receive string from GPS stream rcvBuf(idx) = RX_BYTE_GPS NEXT ' extract values from buffer tmpWord = 0 idx = 0 Next_Digit4: ' gather digits as long as we haven't exceeded the length of this field IF idx <= 4 THEN IF rcvBuf(idx) = "." THEN ' grab the tenths value and then we're done (ignore the hundredths) idx = idx + 1 CONVERT_VALUE idx = 99 ENDIF ' keep going as long as we haven't exceeded the length of this field IF idx <= 4 THEN ' & as long as we don't get into the next field IF rcvBuf(idx) <> "," THEN CONVERT_VALUE idx = idx + 1 ' go get the next digit ELSE idx = 99 ENDIF ENDIF ENDIF IF idx <= 4 THEN GOTO Next_Digit4 GOTO tmpWord_return ' ------------------------------------------------------------------------- ' Subroutine Code ' ------------------------------------------------------------------------- ' ---------------------------------------------------- ' Convert ASCII value into decimal CONVERT_VALUE: rcvBuf(idx) = rcvBuf(idx) - "0" WORD_MULT_BYTE tmpWord, 10 ' tmpWord = tmpWord * 10 WORD_ADD_BYTE tmpWord, rcvBuf(idx) ' tmpWord = tmpWord + rcvBuf(idx) RETURN ' ---------------------------------------------------- ' Wait for the characters "GGA" to be received from the GPS interface WAIT_FOR_GGA: RX_BYTE_GPS TRY_AGAIN_GGA: IF temp2 <> "G" THEN WAIT_FOR_GGA RX_BYTE_GPS IF temp2 <> "G" THEN WAIT_FOR_GGA RX_BYTE_GPS IF temp2 <> "A" THEN TRY_AGAIN_GGA RETURN ' ---------------------------------------------------- ' Wait for the characters "RMC" to be received from the GPS interface WAIT_FOR_RMC: RX_BYTE_GPS TRY_AGAIN_RMC: IF temp2 <> "R" THEN WAIT_FOR_RMC RX_BYTE_GPS IF temp2 <> "M" THEN TRY_AGAIN_RMC RX_BYTE_GPS IF temp2 <> "C" THEN TRY_AGAIN_RMC RETURN ' ---------------------------------------------------- ' Wait for a comma character to be received ' from the GPS interface WAIT_FOR_COMMA: RX_BYTE_GPS IF temp2 <> "," THEN WAIT_FOR_COMMA RETURN ' ---------------------------------------------------- ' Use: char = RX_BYTE ' -- reads byte from serial input and places in 'char' RX_BYTE: SERIN Sio, Baud, temp1 ' receive a byte RETURN temp1 ' return to caller ' ---------------------------------------------------- ' Use: char = GPS_RX_BYTE ' -- reads byte from serial input and places in 'char' RX_BYTE_GPS: SERIN Gps_rx, Baud, temp2 ' receive a byte RETURN temp2 ' return to caller ' ---------------------------------------------------- ' Use: TX_BYTE theByte ' -- transmit "theByte" at "Baud" on "Sio" TX_BYTE: temp1 = __PARAM1 ' save byte SEROUT Sio, Baud, temp1 ' send the byte RETURN ' ---------------------------------------------------- ' MATH16 routines courtesy of Hitt Consulting WORD_ADD_BYTE: ASM MOV FSR,__PARAM1 ADD IND,__PARAM2 INC FSR ADDB IND,C ENDASM BANK ' Restore FSR RETURN ' ---------------------------------------------------- WORD_MULT_BYTE: ASM ' PARAM1=8 BIT VALUE ' PARAM2=RESULT LSB ' PARAM3=RESULT MSB ' PARAM4=COUNTER MOV FSR,__PARAM1 MOV __PARAM1,__PARAM2 CLR __PARAM2 CLR __PARAM3 MOV __PARAM4,#8 :MULT_LOOP CLC RL __PARAM2 RL __PARAM3 RL __PARAM1 JNC @:MULT_DONE ADD __PARAM2,IND ADDB __PARAM3,C INC FSR ADD __PARAM3,IND DEC FSR :MULT_DONE DJNZ __PARAM4,@:MULT_LOOP MOV IND,__PARAM2 INC FSR MOV IND,__PARAM3 ENDASM BANK ' Restore FSR RETURN ' ----------------------------------------------------