Swordfish Module - NMEA2

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:


And an example GGA sentence:



Extracting Fields 

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:

RMC Extraction (Swordfish PICBasic)
// 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
    Until NMEA.NewItem        
    For i = 0 To NMEA.FieldCount
        If GetField(i,tmpField) Then
            USART.Write("GPRMC Field ",DecToStr(i), " = ",tmpField,13,10)
    NMEA.NewItem = False

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:



The Module

Swordfish Module: NMEA2
*  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"
// #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"
// #option to set default NMEA sentence...
// 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
        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                               //
    // 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                               //
            pTargetString(c) = LBuffer(i)      // grab the next char and place into pTargetString
            Inc(i)                             // increment index variables
            Inc(c)                             //
    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)
Interrupt USART_RX(1)
    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
                If LCalculateChecksum = 1 Then                // check if checksum calcuation is enabled
                    LChecksum = LChecksum Xor Byte(LChar)     // calculate checksum by XORing each received byte
                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)
                    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)
                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
    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
        RCIP = 0                                              // set USART interrupt priority LOW    
    RCIE = 1                                                  // enable interrupt on USART receive
    Enable(USART_RX)                                          // enable handler    
End Sub


Optional Extras

There are a couple of options which allow further flexibility. There are self explanatory and listed below:

NMEA2 Options
// #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...



If you have improvements for this module, please post on the forum and I will update it accordingly!

Forum Activity

Member Access