;This uses the eusart, because it is the faster data stream.
;There may well not be enough memory to grab the whole of the NMEA string, so the
;data is processed on the fly.
;Checksum is ascii representation of all the characters between, and not including, $ and *
;$ starts the procedure
chk_gps_data
;$GPRMC,220516,A,5133.82,N,00042.24,W,173.8,231.8,130694,004.2,W*70
;$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A
lfsr fsr0, gps_recieve_buffer
incf gps_recieve_pointer, w
andlw b'00001111'
addlw low(gps_recieve_buffer)
movwf fsr0l, a
movwf gps_recieve_pointer
movf rcreg, w ;real
movwf indf0, a
sublw "*"
btfsc status, zero, a
bcf chksum_running ;stop checksum when * is recieved
movf i ndf0, w, a
btfsc chksum_running
xorwf chksum, f ;calculate checksum if required
btfsc indf0, 6
bcf indf0, 5 ;devious convert to upper case
sublw '$' ;w is still the character that has just arrived
btfsc status, zero
bra clear_chksum
movlw high (gps_lookup_table)
movwf pclath
movlw (gps_lookup_table_end - gps_lookup_table)/2
subwf gps_recieve_mode, w
bc data_fail_local
rlncf gps_recieve_mode, w
addlw low (gps_lookup_table)
btfsc status, carry
incf pclath, f
movwf pcl
gps_lookup_table
bra find_gprmc ;0
bra find_comma ;1
bra collect_time ;2
bra find_comma ;3
bra A_or_V ;4
bra recieve_latitude ;5
bra recieve_latitude_fractions ;6
bra N_or_S ;7
bra find_comma ;8
bra recieve_longditude ;9
bra recieve_longditude_fractions;10
bra E_or_W ;11
bra find_comma ;12
bra knots ;13 ;includes find_comma as it is variable length
bra bearing ;14
bra find_comma ;15
bra date ;16
bra year_decode ;17
bra speed_ph ;18
bra speed_ph_bcd ;19
bra await_chksum_star ;20
bra await_chksum ;21
gps_lookup_table_end
collect_time
;this collects the time into two bcd numbers
;gps_data_count is 0, 1, 2, or 3
lfsr 1, temp_hours_gps ;point to hours
btfsc gps_data_count, 1
incf fsr1l, f ;or minutes if needed
movlw "0"
subwf indf0, w ;convert from ascii
incf gps_data_count, f ;point to next character
btfsc gps_data_count, 2
incf gps_recieve_mode, f ;point to next function if needed
btfss gps_data_count, 0 ;this takes account of the fact that gps_data_count has just been incremented
bra time_units
swapf wreg, f ;get into correct half
movwf indf1 ;and store
return
time_units
iorwf indf1, f
return
;If the string name hasn't been found,
;the first thing to do is to compare the front part of the string with GPRMC
;This relies on the indirect addressing not rolling, but the pointer is reset on each $ recieved
find_gprmc
movlw 0x05
movwf compare_count
movlw 0x100 - 0x04 ;point to 5 characters back from the present recieve position
addwf fsr0l, f
movlw (gprmc_eeprom - lookuph)
call string_compare
bnz GPRMC_fail ;see if the last 6 characters say "$GPRMC"
incf gps_recieve_mode, f ;that's GPRMC found so we can look for data
clrf gps_recieve_pointer ;reset this so that buffer doesn't roll
clrf gps_data_count
GPRMC_fail
return
A_or_V
;I expect an "A" or a "V" here but "V" is for void so if it is an
;"A" it goes on, otherwise anything else fails it
movlw "A"
subwf indf0, w, a
btfss status, zero, a
bra data_fail_V
incf gps_recieve_mode, f
clrf gps_recieve_pointer
clrf gps_data_count
return
recieve_latitude
;the latitude is in the form ddmm.mmm but the number of digits can vary
;The degrees are stored as degrees but the minutes are rotated in from the bottom
;However each rotation is a multiply by 10 not a simple rotate
movlw "."
subwf postdec0, w
btfss status, zero
return ;just wait for the decimal point
clrf temp_n_deg_hex_frac ;This is cleared as the data is later multiplied and added into here
movf postdec0, w
andlw b'00001111'
movwf temp_n_deg_hex_frac2
movf postdec0, w
andlw b'00001111'
mullw d'10'
movf prodl, w
addwf temp_n_deg_hex_frac2 ;collect minutes as binary
movf postdec0, w
andlw b'00001111'
movwf temp_n_deg ;collect the units of degrees
movlw "0"
subwf indf0, w
bnc return_code
addlw 0x100 - d'10'
bc return_code ;skip out if not ascii 0 .. 9
swapf indf0, w
andlw b'11110000'
iorwf temp_n_deg, f
return_code
incf gps_recieve_mode, f
return
recieve_latitude_fractions
;this needs to recieve 3 fractional digits of minutes
;It has to multiply by 10 exactly 3 times
;So the pointer moves on once 3 rotates have occoured.
;If there are only two decimal places of minutes, the third multiply still happens but nothing is added after that
;The largest number is 59.999 minutes so we can end up with 0 - 59999
;Which is shown as 0x00 to 0xEA5F in temp_n_hex_frac_deg and temp_n_deg_hex_frac2
incf gps_data_count, f
btfsc gps_data_count, 2 ;skip out if beyond 3
bra find_comma_reset ;and point to next function
lfsr 1, temp_n_deg_hex_frac
rcall mult_indf1_by_10
movlw "0"
subwf indf0, w
bnc find_comma_reset
addlw 0x100 - d'10'
bc find_comma_reset ;skip out if not ascii
movf indf0, w
andlw b'00001111'
addwf temp_n_deg_hex_frac2, f
btfsc status, carry
incf temp_n_deg_hex_frac, f ;add the most recent digit
bra find_comma
N_or_S
clrf gps_data_count
incf gps_recieve_mode, f
movlw "N"
subwf indf0, w, a
btfsc status, zero, a
bsf temp_gps_mode, rmc_north
movlw "S"
btfss status, zero, a
subwf indf0, w, a
btfss status, zero, a
data_fail_local
bra data_fail ;neither found
lfsr 0, temp_n_deg_hex_frac
seconds_correction
;This corrects the number of thousanths of seconds into a hex fraction of degrees
;so we need to multiply by 65536 / 60000 = 0x1179E
;This multiples by 0x179E and adds to the original
movff postinc0, mult_a_h
movff indf0, mult_a_l
movlw 0x17
movwf mult_b_h
movlw 0x9e
movwf mult_b_l
call multiply
movf prod_1, w
addwf postdec0, f
movf prod_0, w
addwfc indf0, f
return
find_comma_reset
clrf gps_recieve_pointer
find_comma
;this simply increments gps_recieve_mode when comma is found
;If it never arrives, a * or $ will reset the whole thing
movlw ","
subwf indf0, w
btfsc status, zero
incf gps_recieve_mode, f
return
return_reset_speed
clrf temp_spd_ph_units_tenths
clrf temp_spd_ph_hundreds_tens
clrf temp_speed_kn_hundreds_tens
clrf temp_speed_kn_units_tenths
return
recieve_longditude
;the longditude is in the form dddmm.mmm but the number of digits can vary
movlw "."
subwf postdec0, w
bnz return_reset_speed ;just wait for the decimal point
bcf temp_gps_mode, ew_hundred
clrf temp_e_deg_hex_frac
movf postdec0, w
andlw b'00001111'
movwf temp_e_deg_hex_frac2
movf postdec0, w
andlw b'00001111'
mullw d'10'
movf prodl, w
addwf temp_e_deg_hex_frac2 ;collect minutes as binary
movf postdec0, w
andlw b'00001111'
movwf temp_e_deg ;collect the units of degrees
movlw "0"
subwf indf0, w
bnc return_code_long
addlw 0x100 - d'10'
bc return_code_long ;skip out if not ascii 0 .. 9
swapf postdec0, w
andlw b'11110000'
iorwf temp_e_deg, f
movlw "0"
subwf indf0, w
bnc return_code_long
addlw 0x100 - d'2'
bc return_code_long ;skip out if not ascii 0 .. 1
btfsc indf0, 0
bsf temp_gps_mode, ew_hundred
return_code_long
incf gps_recieve_mode, f
return
recieve_longditude_fractions
;this needs to recieve 3 fractional digits of minutes
;It has to multiply by 10 exactly 3 times
incf gps_data_count, f
btfsc gps_data_count, 2 ;skip out if beyond 3
bra find_comma_reset ;and point to next function
lfsr 1, temp_e_deg_hex_frac
rcall mult_indf1_by_10
movlw "0"
subwf indf0, w
bnc find_comma_reset
addlw 0x100 - d'10'
bc find_comma_reset ;skip out if not ascii
movf indf0, w
andlw b'00001111'
addwf temp_e_deg_hex_frac2, f
btfsc status, carry
incf temp_e_deg_hex_frac, f
find_comma2
bra find_comma
E_or_W
clrf gps_data_count
incf gps_recieve_mode, f
movlw "E"
subwf indf0, w, a
btfsc status, zero, a
bsf temp_gps_mode, rmc_east
movlw "W"
btfss status, zero, a
subwf indf0, w, a
btfss status, zero, a
bra data_fail ;neither found
lfsr 0, temp_e_deg_hex_frac
bra seconds_correction
knots_dp_found
movlw b'01111111'
movwf gps_data_count
return
knots
;This finishes pointing to the digit after the comma
;the speed in 1/10ths of knots is in temp_spd_ph_hundreds_tens, temp_spd_ph_units_tenths in hex
;and the knots are stored in bcd in temp_speed_kn_hundreds_tens, temp_speed_kn_units_tenths
movlw "."
subwf indf0, w
bz knots_dp_found
btfsc gps_data_count, 7
bra find_comma ;done if one digit after the decimal point
;temp_speed_kn_units_tenths
;temp_speed_kn_hundreds_tens
swapf temp_speed_kn_hundreds_tens, w
andlw b'11110000'
movwf temp_speed_kn_hundreds_tens
swapf temp_speed_kn_units_tenths, w
andlw b'00001111'
iorwf temp_speed_kn_hundreds_tens
swapf temp_speed_kn_units_tenths, w
andlw b'11110000'
movwf temp_speed_kn_units_tenths ;move all the digits up by one in bcd
movf temp_spd_ph_units_tenths, w
mullw d'10'
movf temp_spd_ph_hundreds_tens, w
movff prodh, temp_spd_ph_hundreds_tens
movff prodl, temp_spd_ph_units_tenths
mullw d'10'
movf prodl, w
addwf temp_spd_ph_hundreds_tens, f ;multiply by 10
incf gps_data_count, f
movlw "0"
subwf indf0, w
bnc find_comma2
addlw 0x100 - d'10'
bc find_comma2 ;skip out if not ascii
movlw "0"
subwf indf0, w
iorwf temp_speed_kn_units_tenths, f ;add into the bcd knots
addwf temp_spd_ph_units_tenths, f ;add into the hex knots
btfsc status, carry
incf temp_spd_ph_hundreds_tens, f
return
bearing
;this gets temp_course_hundreds and temp_course_tens_units completed
;It stops when it finds a character that isn't a digit
movlw "0"
subwf indf0, w
bnc bearing_done
addlw 0x100 - d'10'
bc bearing_done ;skip out if not ascii
movlw b'11110000'
andwf temp_course_hundreds
swapf temp_course_tens_units, w
andlw b'00001111'
iorwf temp_course_hundreds ;top half is temp_gps_mode
swapf temp_course_tens_units, w
andlw b'11110000'
movwf temp_course_tens_units ;move each character up by 1 place
movf indf0, w
andlw b'00001111'
iorwf temp_course_tens_units, f
return
bearing_done
clrf gps_recieve_pointer
clrf gps_data_count
incf gps_recieve_mode, f
bra find_comma
date
;this simply stores the date
lfsr 1, temp_day_gps
btfsc gps_data_count, 1
decf fsr1l, f ;point to day or month
btfsc gps_data_count, 0
bra second_half
swapf indf0, w
andlw b'11110000'
movwf indf1
bra date_done
second_half
movf indf0, w
andlw b'00001111'
iorwf indf1, f
date_done
clrf temp_year_gps
incf gps_data_count, f
btfsc gps_data_count, 2
incf gps_recieve_mode, f
return
year_decode
;This starts wtih gps_data_count as 4 or 0b0100
movf indf0, w ;collect data, expected to be 0x30 ... 0x39
andlw b'00001111' ;get the data that is 0 - 9
btfss gps_data_count, 0
swapf wreg, w ;move the first digit to the first half
iorwf temp_year_gps ;store
incf gps_data_count, f
btfsc gps_data_count, 1 ;skip out at 0b110
incf gps_recieve_mode, f
return
speed_ph ;this multiples temp_spd_ph_hundreds_tens (2 bytes) by 0x1da (for km/h) or 0x127 (for mph) and divides by 0x100 to get knots into km/h
;and moves it back into temp_spd_ph_hundreds_tens.
movlw 0x27 ;second byte of 0x127
btfsc km_units
movlw 0xda ;second byte of 0x1da
movwf mult_b_l ;store second byte of factor
incf gps_recieve_mode, f ;do this once only
movff temp_spd_ph_units_tenths, mult_a_l
movff temp_spd_ph_hundreds_tens, mult_a_h ;collect knots
movlw 0x01
movwf mult_b_h ;store 1st byte of 0x1da or 0x127
call multiply
movff prod_1, temp_spd_ph_hundreds_tens
movff prod_2, temp_spd_ph_units_tenths ;store middle two bytes of product, which divides by 0x100
return
speed_ph_bcd ;converts mph speed to BCD by calling bcdconvert
clrf gps_recieve_pointer
incf gps_recieve_mode, f ;do this once only
movff temp_spd_ph_hundreds_tens, hex1
movff temp_spd_ph_units_tenths, hex0 ;load data
call bcdconvert ;call convert routine
movff bcd1, temp_spd_ph_hundreds_tens ;retrieve data
movff bcd0, temp_spd_ph_units_tenths
return
await_chksum_star
;Now we look for a * and if found, test the checksum
movf indf0, w
sublw "*"
btfsc status, zero
incf gps_recieve_mode ;
clrf gps_data_count
return
await_chksum
incf gps_data_count, f
btfss gps_data_count, 1
return ;wait for second character
movf postdec0, w, a ;collect second character of checksum
call ascii_2_hex
movwf count1 ;put as least significant nibble
call indf0_2_hex_in_w ;collect first character of checksum
swapf wreg, w
iorwf count1, w ;put with value of second character
subwf chksum, w ;compare with checksum
bnz data_fail
rmc_good
;Now we just have to transfer all the data from the temporary registers
movff temp_course_hundreds ,course_hundreds
movff temp_course_tens_units ,course_tens_units
movff temp_e_deg ,e_deg
movff temp_e_deg_hex_frac ,e_hex_frac
movff temp_e_deg_hex_frac2 ,e_hex_frac2
movff temp_month_gps ,month_gps
movff temp_day_gps ,day_gps
movff temp_hours_gps ,hours_gps
movff temp_minutes_gps ,minutes_gps
movff temp_speed_kn_hundreds_tens,speed_kn_hundreds_tens
movff temp_speed_kn_units_tenths ,speed_kn_units_tenths
movff temp_spd_ph_hundreds_tens,spd_ph_hundreds_tens
movff temp_spd_ph_units_tenths ,spd_ph_units_tenths
movff temp_year_gps ,year_gps
movff temp_course_hundreds ,gps_mode
return
data_fail_V
data_fail
clear_chksum
movlw low(gps_recieve_buffer)
movwf gps_recieve_pointer
clrf gps_recieve_mode
clrf temp_course_tens_units
clrf chksum
bsf chksum_running
compare_fail
clrf temp_gps_mode
return
mult_indf1_by_10
;this multiples indf1 and the following byte by 10
movf indf1, w
mullw d'10'
movff prodl, postinc1 ;multiply the MSB by 10
movf indf1, w
mullw d'10'
movff prodl, postdec1 ;multiply the LSB by 10
movf prodh, w
addwf indf1, f ;and this is the decimal carry
return
indf0_2_hex_in_w ;colects the value in indf0 and converts it from ascii to hex
movf indf0, w, a
ascii_2_hex ;this version trims the output so that only 0x00 - 0x0f can be returned
;It can also accept lower case a - f and work as expected
btfsc wreg, 6 ;this version only works with 18F processors where bits of wreg can be tested
addlw 0x100 - d'7' ;subtract 7 for the letters to give 0x30 .... 0x3f
andlw b'00001111' ;trim
return
multiply
;the usual multiply routine
;It multiplies mult_a_h, mult_a_l by mult_b_h, mult_b_l
;result in prod_0 .... prod_3
clrf prod_0
clrf prod_1
movf mult_a_l, w
mulwf mult_b_l
movff prodh, prod_2
movff prodl, prod_3
mulwf mult_b_h
movf prodl, w
addwf prod_2, f
movf prodh, w
addwfc prod_1, f
movf mult_a_h, w
mulwf mult_b_l
movf prodl, w
addwf prod_2, f
movf prodh, w
addwfc prod_1, f
btfsc status, carry
incf prod_0, f
movf mult_a_h, w
mulwf mult_b_h
movf prodl, w
addwf prod_1, f
movf prodh, w
addwfc prod_0, f
return
string_compare
;this is case insensitive
movwf tblptrl
movlw high(lookuph)
movwf tblptrh, a
decf fsr0l, f
compare_loop
incf fsr0l, f, a
bcf fsr0l, 4
movf indf0, w, a ;collect byte
btfsc wreg, 6
bcf wreg, 5 ;swift convert to upper case
tblrd*+ ;read
subwf tablat, w, a ;compare
btfss status, zero
return ; bnz return_from_string_compare is not used as it takes longer for comparisons
;that fail on the first character, which will be most of them
decfsz compare_count, f
bra compare_loop
clrf indf0 ;destroy what is compared so that messages are only seen once
return_from_string_compare
return
lookuph equ 0x3d00
gprmc_eeprom de "GPRMC"