
I had a couple of issues when using David Barkers module NMEA.bas on my Big GPS Clock project. In particular, variables were being corrupted making the program very unstable.
I'm not familiar with Davids programming approach, so I went on to create my own NMEA parser.
The National Marine Electronics Association (NMEA) has developed a specification that defines the interface between various pieces of marine electronic equipment. The standard permits marine electronics to send information to computers and to other equipment such as GPS receivers.
A GPS receiver will output NMEA sentences continuously. Each sentence has a number of fields which comply with the standard to ensure correct formatting and integrity. Two of the most common NMEA sentences from GPS modules are RMC and GGA. More information on either can be found in this GPS Article.
Here's an example RMC sentence:
$GPGGA,053740.000,2503.6319,N,12136.0099,E,1,08,1.1,63.8,M,15.2,M,,0000*64
And an example GGA sentence:
$GPRMC,053740.000,A,2503.6319,N,12136.0099,E,2.69,79.65,100106,,,A*53
The NMEA2 module makes it easy to receive and extract NMEA fields. Here's an example of retrieving the RMC sentence and displaying every field found:
// define device, clock and disable MCLRE
Device = 18F2520
Clock = 32
Config MCLRE = Off
Include "InternalOscillator.bas" // this module configures the internal oscillator to operate at 32Mhz from the get-go
Include "NMEA2.bas"
Include "USART.bas" // the USART module is required to configure the baud rate
Include "Convert.bas"
Dim tmpField As String(12)
Dim i As Byte
// main program start...
USART.SetBaudrate(br38400) // configure USART
NMEA2.Initialise() // intialise NMEA2 module
// main program loop
While True
NMEA2.NextSentence("GPRMC")
Repeat
Until NMEA.NewItem
For i = 0 To NMEA.FieldCount
If GetField(i,tmpField) Then
USART.Write("GPRMC Field ",DecToStr(i), " = ",tmpField,13,10)
EndIf
Next
NMEA.NewItem = False
Wend
The desired sentence is requested for retrieval by calling NMEA2.NextSentence("GPRMC"). You could modify the passed string to any NMEA sentence header.
From there, NMEA.NewItem will be set true when a sentence is successfully received.
Extracting a field is made easy by calling GetField(i,tmpField) where i is the desired field and tmpField is the target string to store the field.
Once you are finished extracting fields, NMEA.NewItem = False will force the program to retrieve the next NMEA sentence that matches your defined header (GPRMC in the example above).
Here's what the above example will behave:

{ ***************************************************************************** * Name : NMEA2.bas * * Author : Graham Mitchell * * Notice : Copyright (c) 2010 Graham Mitchell 2010 * * : All Rights Reserved * * Date : 28/07/2010 * * : * * Version : 1.0 * * Notes : NMEA2 is a module which parses incoming NMEA sentences and * * : extracts fields as requested. * * : * * : a note regarding function NMEA2.GetField - ENSURE the target * * : string is long enough to accommodate the target field contents * * : + null terminator. For example: * * : * * : Example field data: "123.1234567" (11 characters) * * : The target string declared as: Dim tString As String(12) * * : * * : The string could be longer, but at a minimum it should be 11+1 * * : characters = 12. * * : * ***************************************************************************** } Module NMEA2 // #option to set the NMEA buffer size... #option NMEA_BUFFER_SIZE = 200 #if Not (NMEA_BUFFER_SIZE in (100 to 250)) #error NMEA_BUFFER_SIZE, "Invalid option. Buffer size must be between 100 and 250" #endif // #option to set interrupt priority... #option NMEA_PRIORITY = ipHigh #if Not (NMEA_PRIORITY in (ipLow, ipHigh)) #error NMEA_PRIORITY, "Invalid option. NMEA interrupt priority must be ipLow or ipHigh" #endif // #option to set default NMEA sentence... #option NMEA_HEADER = "GPRMC" // local includes Include "USART.bas" // used for register alias definitions Include "Convert.bas" // used for a HexToStr call // public module variables Structure TNMEA NewItem As Boolean FieldCount As Byte End Structure Public Dim NMEA As TNMEA // local module variables Dim LChecksum As Byte, LIndex As Byte, LChar As Char, LNMEA_Header As String(6), LBuffer As String(NMEA_BUFFER_SIZE), LCRC As String(3), LFlags As Byte, LCalculateChecksum As LFlags.0, LParserEnabled As LFlags.1, LHeader As LFlags.2, LNMEAEnabled As LFlags.3 Const BufferSize = NMEA_BUFFER_SIZE, NMEA_DefaultHeader = NMEA_HEADER // returns true if the NMEA sentence validates Public Function ValidateNMEA() As Boolean Dim Checksum As String(3) Result = false // preload the function result End Function // the defined field is extracted and placed into pTargetString Public Function GetField(ByVal pFieldNumber As Byte, ByRef pTargetString As String) As Boolean Dim i,CurrentField,c As Byte Result = True // preload the function result // parse the sentence to the defined field number i = 0 // preload variables CurrentField = 0 // While CurrentField < pFieldNumber // loop until defined field number is reached If LBuffer(i) = "," Then // check for field character "," Inc(CurrentField) // increment the current field variable EndIf Inc(i) // increment the index If LBuffer(i) = null Then // check if end of string has been reached Result = false // end of string reached, error occured.. set function result and exit Exit // EndIf Wend // fill pTargetString with field content c = 0 // preload variables pTargetString = "" // While 1=1 If (LBuffer(i) = ",") Or (LBuffer(i) = "*") Then Break // end-of-field symbol found - leave loop ElseIf LBuffer(i) = null Then // check if end of string reached Result = false // if so, an error occurred - time to leave Exit // Else pTargetString(c) = LBuffer(i) // grab the next char and place into pTargetString Inc(i) // increment index variables Inc(c) // EndIf Wend pTargetString(c) = null // place a null on the end of pTargetString End Function // usart interrupt handle // check if high/low priority is in use #if NMEA_PRIORITY = ipHigh Interrupt USART_RX(2) #else Interrupt USART_RX(1) #endif Save(0,Convert.HexToStr) // context save all system variables If RCIF = 1 Then // ensure USART RX has occured LChar = Char(RCRegister) // read the USART buffer If (NMEA.NewItem = false) And (LNMEAEnabled = 1) Then // ensure there is no new data, and check semaphore flag If LChar = "$" Then // check for start char "$" LIndex = 0 // reset local variables LChecksum = 0 // LCalculateChecksum = 1 // LParserEnabled = 1 // LHeader = 1 // NMEA.FieldCount = 0 // ElseIf LParserEnabled = 1 Then // not a start char.. ensure parser enabled If LChar = "*" Then // check for CRC char "*" LCalculateChecksum = 0 // disable checksum calculation during CRC reception EndIf If LCalculateChecksum = 1 Then // check if checksum calcuation is enabled LChecksum = LChecksum Xor Byte(LChar) // calculate checksum by XORing each received byte EndIf If LChar >= " " Then // ensure received char is not white-space LBuffer(LIndex) = LChar // load the char into sentence buffer If LChar = "," Then // check if char is field symbol "," LHeader = 0 // if so, clear header flag (stops header validation checks) Inc(NMEA.FieldCount) // increment the field count variable ElseIf LHeader = 1 Then // check if still parsing header field If LNMEA_Header(LIndex) <> LChar Then // if so, validate header against defined NMEA header variable LParserEnabled = 0 // validation failed.. disable parser (will force wait until "$" is received - resetting the process) EndIf EndIf Inc(LIndex) // increment the sentence index If LIndex = BufferSize-1 Then // ensure we haven't reached the end of the sentence buffer (minus 1 for null terminator) LParserEnabled = 0 // disable parser (will force wait until "$" is received - resetting the process) EndIf Else // white-space character received (most likely CR or LF) - time to close the buffer LParserEnabled = 0 // disable the parser If LCalculateChecksum = 0 Then // ensure checksum data was received LCRC(0) = LBuffer(LIndex-2) // extract the Checksum string LCRC(1) = LBuffer(LIndex-1) // If HexToStr(LChecksum,2) = LCRC Then // compare extracted and calculated checksums NMEA.NewItem = True // set the new data flag (which will hault all NMEA parsing until CLEARED) LBuffer(LIndex) = null // tack a null on the end of the string EndIf EndIf EndIf EndIf EndIf EndIf Restore // restore all system variables End Interrupt // disable parsing (protect shared variables) Public Sub Stop() LNMEAEnabled = 0 LParserEnabled = 0 End Sub // resume parsing (unprotect shared variables) Public Sub Start() LNMEAEnabled = 1 End Sub // set the next sentence to parse Public Sub NextSentence(ByVal pHeader As String) NMEA2.Stop // protect shared variables LNMEA_Header = pHeader // load new header definition NMEA2.Start // unprotect shared variables End Sub //intialise USART module Public Sub Initialise() LFlags = $00 // initialise flags NextSentence(NMEA_DefaultHeader) // set default header string LCRC(2) = null // place a null at the end of LCRC // check if high/low interrupts are in use #if NMEA_PRIORITY = ipHigh RCIP = 1 // set USART interrupt priority HIGH #else RCIP = 0 // set USART interrupt priority LOW #endif RCIE = 1 // enable interrupt on USART receive Enable(USART_RX) // enable handler End Sub
There are a couple of options which allow further flexibility. There are self explanatory and listed below:
// #option to set the NMEA buffer size...
#option NMEA_BUFFER_SIZE = 200
// #option to set interrupt priority...
#option NMEA_PRIORITY = ipHigh
// #option to set default NMEA sentence...
#option NMEA_HEADER = "GPRMC"
If you have improvements for this module, please post on the forum and I will update it accordingly!
The days of standing there 'rocking' your PCB etchant tank are over. PCB Rocker is a setup and forget...
Have a browse of the C18 category. Feel free to share your own guides with the community
Jon Chandler shares a method to achieve reliable results with single header connectors
One PIC controlling 24 timers, easy! Add visual and audiable indications for each to spice things up
Recent Comments
Thanks Graham, I was afraid I was describing the obvious... I hat...
By Jon Chandler
I'm not sure how many times I have included a separate header for...
By Graham
ohararp brought up a good point on the Swordfish forum: To see th...
By Jon Chandler
I hope this hasn't been obvious to everyone but me :)
By Jon Chandler
I added a couple pictures showing the 10-turn pot I use in an enc...
By Jon Chandler