Jump to content

Recommended Posts

Hi,

 

Does anyone have source code for using the hardware USART of a PIC16 family chip to extract data from a GPS receiver module that they would like to share with me please?

 

I've spent the last several months surfing the web, printing and studying pertinent data, but I'm not much closer to understanding the process than when I started.

 

I've got several non-commercial, future project ideas in mind using GPS, and I'm pretty sure that I know what to do with the received data once I get past this hurdle.

 

I'm a retired EE (almost exclusively in RF and analog), and I started working with PICs and learning C programming several years ago. I have designed projects that feature PICs, but I have no experience in serial communications.

 

GPS receiver: EM-406A

MCU: 16F873A

Baud: 4800

BoostC v6.96

Link to post
Share on other sites

try this:

 

I call this state based routine to parse the incoming NMEA characters for the RMC sentence once I know my GPS is up and running to get the time data

I use the PPS as an interrupt to signal this, I then use the PPS to get accurate timing.

This code can be adapted for other NMEA sentences.

I also use an interrupt driven buffered serial routine - serHasData() checks to see if there is anything in the buffer.

 

Note that the GPS will only give valid time when it has a fix. the A in field 2 indicates a valid time.

 

char parse_RMC( char * buf){
//* Input $GPRMC,193418.00,A,5551.63161,N,00339.33683,W,0.023,,250709,,,A*6F
char pos;
char nmea_state=0;
bool parsed_NMEA = FALSE;
char gps_char;

timeout = 10; //  global second time out decreased in a timer ISR


while ((parsed_NMEA==FALSE   ) && (timeout > 0))
{

if (serHasData())
{
gps_char = serGetByte();	   // read the character from the buffer

	// now try to see where this character fits in the NMEA RMC message we're trying to decipher
	switch ( nmea_state )			  
	{
		case ( 0 ):					// wait for '$'
			if ( gps_char == '$')  
			nmea_state++;
			break;
					//			
		case ( 1 ):					// wait for 'R'
			if ( gps_char == 'R') 
				nmea_state++;

			break;  
					 //				 
		case ( 2 ):					// wait for 'M'
			if ( gps_char == 'M') 
				nmea_state++;
			else
				nmea_state=0;
			break; 
					// 
		case ( 3 ):					// wait for 'C'
		if ( gps_char == 'C') 
				nmea_state++;
			else
				nmea_state=0;
			break;
				   //
		case ( 4 ): 
			if ( gps_char == ',')// wait for ',' after C
			   { nmea_state++;
				pos=0;}
				else
				nmea_state=0;
			break;

		// state 5 , time comes next in 9 characters	
		//Field 1 hhmmss.hh UTC time -	
				   //   

		case ( 5 ):					// wait for 6 time digits isdigit( char ch )
			if ( isdigit (gps_char)) {
				buf[pos]=gps_char;
				pos++;
					if (pos==6) {nmea_state++;}
				}
				else
				{nmea_state=0;}
				break;
					 //
			case ( 6 ):					// wait for '.'
			if ( gps_char == '.')
			   nmea_state++;
				else
				{nmea_state=0;}
				break;
			break;
		//
					  case ( 7 ):					// 2 .hh digits
			if ( isdigit (gps_char)) {
				buf[pos]=gps_char;
				pos++;
					if (pos==8) {nmea_state++;}
				}
				else
				{nmea_state=0;}
				break;																
				 //
		case ( 8 ):					// wait for ',' end of time field
			if ( gps_char == ',')
			   nmea_state++;
				else
				{nmea_state=0;}
				break;
			break;
				  //
		case ( 9 ):   // Status A valid V invalid
				if ( gps_char == 'A') {
				parsed_NMEA = TRUE;
				}
				else
				{nmea_state=0;}
				break;


	// if we have a valid time it is in buf and parsed_NMEA is TRUE 			
			 default:
	mea_state = 0;  // we should not end up here, but just in case  we start again at the beginning
	break;
 }   // end of switch


 gps_char=0;   // reset input character 

 }  // end of if
} // end of while

return parsed_NMEA;
}

 // end of while with time out

// buf  now has in ascii hhmmsshh
// hours = (buf[0]-48)*10+(buf[1]-48)
// mins  = (buf[2]-48)*10+(buf[3]-48)
// secs  = (buf[4]-48)*10+(buf[5]-48)
// hunds = (buf[6]-48)*10+(buf[7]-48) - normally zero

 

Trimble have a code library that you can also use as a model but as it is not for an embedded target in needs a lot of stripping out.

 

TRIMBLE NMEA Dev Kit

Link to post
Share on other sites

Gentlemen, thank you for your response.

 

TFcroft4,

I've got a copy of another example source code that uses the PPS for timing, although your approach seems to be more straight-forward. I'll definitely give it a try once I get to that point.

 

Scotty,

The link you provided is Jon's update to one of his existing projects, but he moved up to a PIC18-family chip and I don't think I'm ready to tackle PIC18s yet. However, I did find a GPS compass project on his website that uses a 16F628a and may provide some insight.

 

Me

My biggest problem is I don't understand how to set up the USART (including the interrupt for asynchronous operation), get the GPS receiver data into the USART, and output to an RX buffer where I can then parse the NMEA sentences. This is the missing software module that I need.

Link to post
Share on other sites
...My biggest problem is I don't understand how to set up the USART (including the interrupt for asynchronous operation), get the GPS receiver data into the USART, and output to an RX buffer where I can then parse the NMEA sentences. This is the missing software module that I need.

 

Take a look at this topic. It includes latest UART driver and sample code for it.

 

Regards,

Pavel

Link to post
Share on other sites

the following project was based on Jon's compass project. It receives NMEA sentences from a LORAN and retransmitts them as GPS sentences to allow a PC chartplotter to track my course. Until LORAN was shut downearlier this year. Runs on a PIC 16F690

 

#include <system.h>

#pragma CLOCK_FREQ	4000000		// Used with 4Mhz crystal
#pragma DATA _CONFIG,  _INTRC_OSC_NOCLKOUT & _WDT_OFF & _PWRTE_OFF & _MCLRE_OFF & _CP_OFF & _BOR_OFF & _IESO_OFF & _FCMEN_OFF
#define forever 1
#define YES 1
#define NO 0

#define ZED 'z'	//for test
#define DOLLAR '$'	//for NMEA
#define CR 13		//carriage return
#define LF 10		//line feed
#define LCGLL 72	//lat long
#define LCXTE 70	//lat long
#define LCAAM 66	//lat long
#define LCVTG 74	//lat long
#define LCBOD 71	//lat long
#define LCWPL 68	//lat long
#define RX_BUFFER_SIZE 30
#define LAT_LON_BUFFER_SIZE 20

#define NULL 0

short command, cmd, reading;

//NMEA receive variables
unsigned short cRxByteCnt, cRxIsrState, cRxMsgTypeReceived, cReceiveFlag;
char cRxBuffer[ RX_BUFFER_SIZE ] = "$PRGMM,WGS 84			  ";
char cRxBuff[10] = "DEFAULT  ";
char ownship[20], waypoint[20];
char cRxbyteCnt, cRxMsgReady, OKtoSleep;
char *cRxBufferWritePtr;

// char cLat_Lon_Buffer[ LAT_LON_BUFFER_SIZE ];

char string[4];

#define low 0
#define high 1
#define current 2

void putc(char txc)			// Put char function to transmit one char
{
set_bit( txsta, TXEN );			// Enable transmission bit
txreg = txc;					// Put function parameter txc into the txreg	
while(( txsta&0x02 ) == 0 );	// Wait until character transmission finished
}

char getc(void)				// Getc function loop until a character is pressed
{
while( ( pir1&0x20 ) != 0x20 );	// Loop until Rxflag = 1
return rcreg;			// Return rcreg
}

void puts(char *source)		//	put string function, return the refered string
{
while (*source != 0 ) // wait until tx register is empty
{ 
	putc(*source++);	// Send the string refered by pointer
}
}	

void interrupt( void )		// Called function on interrupts
{	
char cChar, display;
int count;
if test_bit( pir1,RCIF) // If Rxflag = 1 then ----->
{
//		puts("You pressed "); // Send the string "You pressed "
	cChar = getc(); // Call getc function then put the character pressed
	switch ( cRxIsrState )
	{
	case 0:
		{
		if (cChar == DOLLAR)	// start of NMEA sentence
			{
			cRxByteCnt = 0;	//reset byte count
			cRxMsgTypeReceived = NULL;
			cRxIsrState++;
//				putc(DOLLAR);
			}
		break;
		}
	case 1:
	case 2:
	case 3:
	case 4:
	case 5:
		{
		cRxMsgTypeReceived ^= cChar;	//XOR in character received
		if ( cRxIsrState++ == 5 )		//check message type
			{
			if ( cChar == 'D' )
				cRxMsgTypeReceived++;
//					cRxIsrState = 0;
			if ( (cRxMsgTypeReceived == LCGLL) | (cRxMsgTypeReceived == LCWPL) )
				{
				cReceiveFlag = YES;
				cRxBufferWritePtr = cRxBuffer;
				}
			else
				{
				cRxIsrState = 0;	//wait for next message
				}
			}
			display = cChar + 64;
//				putc(display);
		break;
		}
	case 6:
		{
		//case 6 skips the comma
		cRxIsrState++;
		break;
		}		
	default:
	{
	if ( cReceiveFlag == YES )
		{
		*cRxBufferWritePtr = cChar; 		//put character in fifo
		cRxBufferWritePtr++;
		if ( cRxBufferWritePtr == ( cRxBuffer + RX_BUFFER_SIZE))
			{
			cRxBufferWritePtr = cRxBuffer;  //set pointer to start of buffer
			}
		cRxbyteCnt++;
		if (cChar == CR )
			{
//				cRxMsgReady = YES;	//message ready
			cRxIsrState = 0;	//allow next message
			cReceiveFlag = NO;	//end receive

		if (cRxMsgTypeReceived == LCGLL)
		{
		for (count=0; count < 20; count++)
		{
		ownship[count] = cRxBuffer[count];
		}
		}

		if (cRxMsgTypeReceived == LCWPL)
		{
		for (count=0; count < 20; count++)
		{
		waypoint[count] = cRxBuffer[count];
		OKtoSleep = YES;	//allow sleep mode
		cRxMsgReady = YES;	//message ready
		cRxIsrState = 0;	//allow next message
		}
		}


			}
		}
	}
}
}
}	


void main()
{
trisa = 0;	// set all ports as outputs
trisb = 0;
trisc = 0;
porta = 0;	//set all ports low
portb = 0;
portc = 0;
OKtoSleep = YES;	//allow sleep mode
anselh = 0;			//digital inputs
cRxMsgReady = NO;	//initialize receive
ansel = 0;
cRxIsrState = 0;	//initialize NMEA state
// cLat_Lon_Buffer = *cRxBuffer;
// set up the EUSART
set_bit(trisb,TRISB5);	//eusart receive pin
set_bit(trisb,TRISB6);	
set_bit(trisb,TRISB7);	//eusart transmit pin
set_bit(intcon,PEIE);	//enable peripheral interrupt
set_bit(pie1,RCIE);		//enable EUSART interrupt
set_bit(intcon,GIE);	//enable global interrupt
set_bit(baudctl, BRG16);
spbrgh = 0;
spbrg = 207;				//Set SPBRG to 4800 BPS
clear_bit(txsta, SYNC);
set_bit(rcsta,SPEN);
set_bit(rcsta, CREN);
set_bit(txsta, BRGH);
puts("test the serial port "); // Send the string "test the serial port "		
//	puts("$GPRMC,120002,A,4731.3888,N,12223.8193,W,0.0,41.1,250208,0.0,W*47");
putc(CR);
putc(LF);
//	puts("$GPRMA,A,llll.ll,N,lllll.ll,W,,,,,");
//	putc(CR);
//	putc(LF);
while (forever)
{
if ( cRxMsgReady == YES)	//message ready
	{
	cRxMsgReady = NO;	//end receive
	cRxIsrState = 0;	//allow next message

	puts("$PRGMM,WGS 84"); // Send the datum sentence
	putc(CR);
	putc(LF);
	puts("$PFEC,GPwpl,");
//						$PFEC,GPwpl,3351.446,N,11910.866,W,NWP001,,@rNAMED WAYPOINT T,A,,,,
	unsigned short count;
	for (count=0; count < 20; count++)
		{
		putc(ownship[count]);
		}
	puts(",OWNSHP,,@rPRESENT POSITION,A,,,");
//		puts(cLat_Lon_Buffer);
//						$PFEC,GPxfr,CTL,E  
	putc(CR);
	putc(LF);
	puts("$PFEC,GPwpl,");
//						$PFEC,GPwpl,3351.446,N,11910.866,W,NWP001,,@rNAMED WAYPOINT T,A,,,,
	for (count=0; count < 20; count++)
		{
		putc(waypoint[count]);
		}
	puts(",NWP001,,@rPRESENT POSITION,A,,,");
//		puts(cLat_Lon_Buffer);
//						$PFEC,GPxfr,CTL,E  
	putc(CR);
	putc(LF);
	puts("$PFEC,GPxfr,CTL,E"); //end of waypoint sentence
	putc(CR);
	putc(LF);
	cRxBufferWritePtr = cRxBuffer;	//reset buffer pointer
	}
	if ( OKtoSleep == YES)	//no message ready
	{
		set_bit(baudctl, WUE);	//enable wake up
		OKtoSleep = NO;	//don't allow sleep after wake up 
//			puts("sleep");
		sleep();
//			puts("awake");
		clear_bit(baudctl, WUE);	//disable wake up
	}
}
}

Link to post
Share on other sites

Pavel,

 

Thanks for the link. I had already downloaded and printed this code and "The SourceBoost UART Driver Reference Manual," unsuccessfully studied them, then I started this thread.

 

As a person just starting to learn about serial communications, I don't know what parts in the sample code (not including the pragma) may be specific to the PIC18 target (I'm using PIC16s), I don't understand the helper defines, and having the TX and RX UARTS in the same code is confusing to me.

 

I fell like I'm at an awkward in-the-middle stage where I'm already familiar with the material presented in beginners books, and with the next step up books I get lost on page two!

 

Scotty,

 

Thanks for the code; I've downloaded and printed it. I'll be studying it to see if I can get it to work for me.

Link to post
Share on other sites

Here are two files from my library (http://www.tedrossin.atbhost.net/Electronics/Pic/Pic.html) CBLib.zip that work on 16F parts that have the hardware serial ports hooked up to port C 6 and 7. Just change RS232Init to use different pins if your part is different. It seems that you understand the basics but you just need some simple code to set your baud rate based on you clock frequency. This code just does simple polling with no receive and transmit FIFOs.

 

I hope this helps and does not confuse the issue more.

 

1. Call RS232Init with your clock frequency and desired baud rate

2. make calls to RS232getch and RS232putch to read and write bytes.

 

Serial.h

void RS232Init(unsigned char BaudRate);
void RS232putch(unsigned char ByteVal);
char RS232getch(void);
unsigned char RS232peekch(void);
#define RS232_20MHZ_BAUD_115000  10
#define RS232_20MHZ_BAUD_57600  20
#define RS232_20MHZ_BAUD_28800  42
#define RS232_20MHZ_BAUD_19200  64
#define RS232_20MHZ_BAUD_9600  129
#define RS232_16MHZ_BAUD_115000  8
#define RS232_16MHZ_BAUD_57600  16
#define RS232_16MHZ_BAUD_28800  33
#define RS232_16MHZ_BAUD_19200  51
#define RS232_16MHZ_BAUD_9600  103
#define RS232_10MHZ_BAUD_115000  4
#define RS232_10MHZ_BAUD_57600  10
#define RS232_10MHZ_BAUD_28800  21
#define RS232_10MHZ_BAUD_19200  31
#define RS232_10MHZ_BAUD_9600  64
#define RS232_4MHZ_BAUD_115000  1
#define RS232_4MHZ_BAUD_57600  3
#define RS232_4MHZ_BAUD_28800  8
#define RS232_4MHZ_BAUD_19200  12
#define RS232_4MHZ_BAUD_9600  25

 

serial.c

#include <system.h>

void RS232putch(unsigned char ByteVal)
{
while(!(test_bit(pir1,4)));
txreg = ByteVal;
}

unsigned char RS232getch(void)
{
while(!(test_bit(pir1,5)));
clear_bit(pir1,5);
return(rcreg);
}

unsigned char RS232peekch(void)
{
if(test_bit(pir1,5)) return(1);
return(0);
}

void RS232Init(unsigned char BaudRate)
{
set_bit(trisc,7); // PORTC[RX] is Input
clear_bit(trisc,6); // PORTC[TX] is Output
spbrg = BaudRate; // Set baud. see header file
txsta = 0x24;  // Set TXEN and BRGH to 1
rcsta = 0x90;  // Set SPEN and CREN to 1 

 /*
movlw (1<<TXIE)|(1<<RXIE)
iorwf PIE1 ; Enable Transmit and receive interrupts
*/
}

Edited by trossin
Link to post
Share on other sites

Although you want to receive data from a GPS, you may do better getting over the serial comms learning curve communicating with a terminal program on a PC. Simple stuff like type a single digit hex number on the PC and get the PIC to display the binary value on four LEDs or send 'Switch 1' or 'Switch 2' etc. to the PC as you press some buttons. Once you have that working OK, reading and parsing NMEA0183 data will be a lot easier.

Link to post
Share on other sites

I thank everyone for taking their time to help me solve my problem. Although I don't have the answer yet, I have a lot of data to read and study, and for that I am thankful.

 

Gizmo

Link to post
Share on other sites
As a person just starting to learn about serial communications, I don't know what parts in the sample code (not including the pragma) may be specific to the PIC18 target (I'm using PIC16s), I don't understand the helper defines, and having the TX and RX UARTS in the same code is confusing to me.

 

For PIC16 helper macros for UART driver will look like:

 

#define uart1Init					rs232Init<PIE1,TXIE,PIE1,RCIE,RCSTA,CREN,RCSTA,SPEN>
#define uart1TxInterruptHandler	  rs232TxInterruptHandler<PIR1,TXIF,TXREG,sizeof(txBuffer),TXSTA,TXEN,TXSTA,TRMT>
#define uart1RxInterruptHandler	  rs232RxInterruptHandler<PIR1,RCIF,RCREG,sizeof(rxBuffer),RCSTA,CREN,RCSTA,OERR,RCSTA,FERR>
#define uart1Rx					  rs232Rx<sizeof(rxBuffer)>
#define uart1Tx					  rs232Tx<sizeof(txBuffer),TXSTA,TXEN>

 

Regards,

Pavel

Link to post
Share on other sites

Pavel,

 

Thanks for the info.  That helped me understand some other parts of the example 

code too. 

 

What is the purpose of the High Priority interrupt and its port D Data Latch?

Link to post
Share on other sites
What is the purpose of the High Priority interrupt and its port D Data Latch?

 

High Priority interrupt is PIC18 specific. PIC18 has 2 interrupts, PIC16 just one. UART driver doesn't care from which it is used.

 

Code that uses port D Data Latch is not related to UART driver functionality. It's used in the code to illustrate how to implement a heart beat led.

 

Regards,

Pavel

Link to post
Share on other sites

Join the conversation

You are posting as a guest. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
×
×
  • Create New...