Gps +rs232+pic18f4580

Status
Not open for further replies.

kathy90000

New Member
Hello,

(reposted but modified "sorry")

I'm working on a project consisting in receiving a frame from a GPS via a RS232 port using a PIC 18F4580 microcontroller

The project consists in extracting (latitude, longitude, time) off a GGA frame and speed off the RMC one

I saw a previous example, posted in here "below" http://www.gpspassion.com/forumsen/topic.asp?TOPIC_ID=24076, but in my project, I use the gps directly to receive frames without using the file.txt and the extracted parameters must be stored in tables, such as latitude: 48 ° 07,038 'N == > 48 (degrees) and 07 in a table in another table, the same for the other parameters (longitude, time etc).

In fact, referring to the previous program ,I'm not interested in converting to digital, I have to keep the frames in ASCII, my GPS speed is 4800 bauds.

I'd like to know if there is a code example in order to get inspired from or any piece of advice getting me in the correct way to get it right.


I would like to adapt the same program (proposed & corrected by a french user, in the previous link ) to an application of reading frames directly from the serial port of the GPS (especially frames GGA and RMC) via a PIC 18F4580. without going through the filename.txt as proposed in the link ! What should I replace by the lines (file.txt)?!


Thank you in advance for any possible help .


Yours sincerly.
 
It's quite easy to do, you read the incoming ascii data in real time and wait for the GGA tag, and directly after that tag is detected the data is separated by ascii comma characters. The same data fields are always separated by the same number of commas from the tag, so you just count commas and then store the ascii characters.

If the GPS has not locked the data will be blanks (usually means no characters at all between the commas on blank fields).

It's unknown whether the GGA or RMC stream will be first, so your code should just read whichever one is detected. However like you are suggesting you don't need to store the entire GPS data output and then decode it (the way most software works), you can decode the ascii data easily in real time.

Also keep in mind the GPS unit might not be RS232 serial, it might be TTL level serial data which can be connected directly to the PIC serial RX pin.
 
Hello,

I'm working on a project consisting in receiving a frame from a GPS (4800 baud). via a RS232 port using a PIC 18F4580 microcontroller on MPLAB C18

Step2 : The project consists in extracting (latitude, longitude, time) off a GGA frame and speed off the RMC one.

This is a code "below" that should be only receiving frames of GPS (not for extravting NMEA frames) in which i used 'getsUSART, openUSART functions' as described in the book of c18 compiler. ...

unfortunately I do not know if my code below works or not!? First I would like to receive a frame, save it in an array and display it (before extracting the GGA and RMC)

Could you check my code please and see if it's correct or not & maybe be suggesting me some modifications if it's possible ...

My Code :

Code:
#include <p18f4580.h> 

#include <usart.h> 
#include <sw_uart.h> 
#include <stdlib.h> 
#include <stdio.h> 
#include <delays.h> 

void closeUSART(void); 

//void getsUSART ( char * gps , unsigned char 80 ); 
void main(void) 
{ 

   char gps[80]; // array for receiving frames 

   OpenUSART (USART_TX_INT_OFF & USART_RX_INT_ON & USART_ASYNCH_MODE & USART_EIGHT_BIT & USART_CONT_RX & USART_BRGH_HIGH, 130); //4800bauds 

   TRISCbits.TRISC6 = 0 ; //  
   TRISCbits.TRISC7 = 1; 
   while(1){ 
      getsUSART( gps, 80); // receiving frames 
      } 
}

Thanks for any reply for code corrections !

(I'd like to know if there is a code example in order to get inspired from or any piece of advices to show me how to extract GGA & RMC frames as well ... )

Thank you in advance for any possible help.


Yours sincerely.
 
Last edited by a moderator:
Your frame buffer of 80 bytes is much too small, you need a few hundred bytes to get all the incoming ascii data strings.

That's why I suggested reading the data on the fly, as it arrives;
1; monitor incoming data to detect "GGA" tag etc
2; then count ascii "comma" values for ? number of commas
3; then store the LAT and LONG data etc as ascii into small buffers

If you want to do it the other way, you need a frame buffer of 512 bytes or more, than store a whole data set, then you STILL have to do the extraction process I showed above anyway.
 
This is what I use on an 18F pic to receive GPS data.

The subroutine chk_gps_data has to be called more often than the byte rate. The subroutine will always return quickly.

There is probably a lot that you can cut out, as this does quite a lot of maths to convert units etc.


Code:
;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"
 
Status
Not open for further replies.
Cookies are required to use this site. You must accept them to continue using the site. Learn more…