NMEA

This module sets up an interrupt to buffer NMEA data. It has a number of useful functions that enable you to retrieve NMEA items from the buffer and also get a particular NMEA field. Obtaining an NMEA item is very easy. You just make a call to GetItem(). For example:

dim NMEAItem as TNMEAItem
if NMEA.GetItem(NMEAItem) then
   ...
endif

Notice the use of the TNMEA type. This is a public structure declared in the NMEA module and has the following fields:

Public Structure TNMEA
   Line As String(80)
   Count As Byte
   Valid As Boolean
End Structure

The line field is the actual NMEA sentence buffered by the module. Count returns the number of fields contained in the sentence. For example, a typical $GPGGA sentence has 15 fields. Finally, the boolean valid flag indicates if the sentence checksum was OK. You can also make a call to GetField(), which will retrieve a field from a valid sentence. For example, you may want to retrieve the date field from the following $GPRMC GPS sentence. The date is located at index 9:

NMEA.GetItem(NMEAItem, 9, FieldData)

Probably the best thing to do is see some sample code of a fully working program.

// device and clock
device = 18F452
clock = 10

// import modules...
Include "NMEA.bas"
Include "Usart.bas"
Include "Convert.bas"

// local variables
Dim NMEAItem As TNMEA
Dim NMEAField As String

// this program with display the sentence identifier and the number
// of data fields the sentence has...
USART.SetBaudrate(br4800)
While True
   // get and NMEA item from the buffer, if one is available - check to
   // see if checksum is valid before proceeding....
   If NMEA.GetItem(NMEAItem) And NMEAItem.Valid Then
      NMEA.GetField(NMEAItem,0,NMEAField)
      USART.Write(NMEAField, " : ", DecToStr(NMEAItem.Count), 13, 10)
   EndIf
Wend

Now just hook your PIC up to a GPS receiver - the output from the above should look something like:

 $GPGGA : 15
 $GPRMC : 13
 $GPVTG : 10
 $GPGGA : 15
 $GPGSA : 18
 $GPGSV : 20
 $GPGSV : 20

The following program filters out all non $GPRMC sentences and displays the date and time:

// import modules...
Include "NMEA.bas"
Include "Usart.bas"
Include "Convert.bas"

' local variables
Dim NMEAItem As TNMEA
Dim Field As String

' program start
USART.SetBaudrate(br4800)
While True
   If NMEA.GetItem(NMEAItem) And NMEAItem.Valid Then
      NMEA.GetField(NMEAItem,0,Field)
      If Field = "$GPRMC" Then
         USART.Write(Field)
         NMEA.GetField(NMEAItem, 9, Field)
         USART.Write(" [Date ", Field, "]")
         NMEA.GetField(NMEAItem, 1, Field)
         USART.Write(", [Time ", Field, "]", 13, 10)
         USART.Write(13, 10)         
      EndIf  
   EndIf
Wend

Giving us an output which looks like:

 $GPRMC [Date 200807], [Time 143427.093]
 $GPRMC [Date 200807], [Time 143428.093]
 $GPRMC [Date 200807], [Time 143429.093]
 $GPRMC [Date 200807], [Time 143430.093]
 $GPRMC [Date 200807], [Time 143431.093]
 $GPRMC [Date 200807], [Time 143432.092]

NMEA Module

This is the module code for the above examples. Just copy and paste into the Swordfish IDE and save in you UserLibrary folder as NMEA.bas...

{
*****************************************************************************
*  Name    : NMEA.BAS                                                       *
*  Author  : David John Barker                                              *
*  Notice  : Copyright (c) 2007 Mecanique                                   *
*          : All Rights Reserved                                            *
*  Date    : 24/08/2007                                                     *
*  Version : 1.1                                                            *
*          : 1.1 - Added overflow flag to prevent overwriting of the ring   *
*          : buffer - this ensures that if the main program takes a large   *
*          : amount of time on a task, the interrupt will re-sync with any  *
*          : NMEA sentence                                                  *
*          : 1.0 - Initial release
*  Notes   : All NMEA data is transmitted in the form of sentences. Only    *
*          : printable ASCII characters are allowed, plus CR (carriage      *
*          : return) And LF (line feed). Each sentence starts With a "$"    *
*          : sign and ends With <CR><LF>.                                   *
*          : The format For a talker sentence is: $ttsss,D1,D2,....<CR><LF> *
*          : The first two letters following the "$" are the talker         *
*          : identifier. The next three characters (sss) are the sentence   *
*          : identifier, followed by a number of  data fields separated by  *
*          : commas, followed by an optional checksum, and terminated by    *
*          : carriage return/line feed. The data fields are uniquely        *
*          : defined for each sentence type.                                *
*****************************************************************************
}
Module NMEA

// #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 bteween 100 and 250"
#endif

// Import conversion module...
Include "convert.bas"

// An NMEA structure - this holds the NMEA data line, the number
// of data fields in the sentence and finally the cumputed NMEA checksum
Public Structure TNMEA
   Line As String(80)
   Count As Byte
   Valid As Boolean
End Structure

// These local constants and variables are used by the NMEA interrupt handler
Const NMEABufferSize = NMEA_BUFFER_SIZE // Size of the buffer
Dim FBuffer(NMEABufferSize) As Byte     // Array for holding received characters
Dim FIndexIn As Byte                    // Pointer to the next empty location in the buffer
Dim FIndexOut As Byte                   // Pointer to the location of the oldest character in the buffer 
Dim FCount As Byte                      // Number of NMEA items in the buffer
Dim FFieldCount As Byte                 // Number of fields in the NMEA sentence
Dim FReadingSentence As Boolean         // Are we reading a NMEA sentence
Dim FCalculatingChecksum As Boolean     // Are we calculating checksum   
Dim FChecksum As Byte                   // NMEA checksum 
Dim FOverflow As Boolean                // Buffer overflow flag           
{
****************************************************************************
* Name    : SetBufferData (PRIVATE)                                        *
* Purpose : Local helper function to set buffer data                       *
****************************************************************************
}
Sub SetBufferData(pData As PRODL)
   Inc(FIndexIn)                       
   If FIndexIn >= NMEABufferSize Then  
      FIndexIn = 0              
   EndIf
   FSR0 = @FBuffer
   Inc(FSR0, FIndexIn)
   If FIndexIn = FIndexOut Then
      FOverflow = true
   Else
      INDF0 = pData 
   EndIf   
End Sub
{
****************************************************************************
* Name    : GetBufferData (PRIVATE)                                        *
* Purpose : Local helper function to get a buffer byte                     *
****************************************************************************
}
Function GetBufferData() As INDF1
   Inc(FIndexOut)
   If FIndexOut >= NMEABufferSize Then
      FIndexOut = 0  
   EndIf
   FSR1 = @FBuffer
   Inc(FSR1,FIndexOut)
End Function
{
****************************************************************************
* Name    : OnNMEAData (PRIVATE)                                           *
* Notes   : Interrupt based ring buffer that reads characters from the     *
*         : USART. The routine also calculates a NMEA checksum and counts  *
*         : the number of data fields as data arrives                      *
****************************************************************************
}
Interrupt OnNMEAData()
   Dim Data As Char

   // If a byte receive has triggered the interrupt, then context save
   // FSR0 and process the data byte...
   If PIR1.5 = 1 Then                                     
      Data = RCREG 

      // buffer overflow has occurred, abort...
      If FOverflow Then
         Exit
      EndIf 

      // proceed...             
      Save(FSR0, PRODL)

         // stop calculating checksum...
         If Data = "*" Then
            FCalculatingChecksum = False
         EndIf            

         // calculate checksum...
         If FCalculatingChecksum Then
            FChecksum = FChecksum Xor Byte(Data)
         EndIf  

         // start reading a sentence, start calculating checksum and
         // initialise the number of NMEA fields to zero...
         If Data = "$" Then
            FReadingSentence = True
            FCalculatingChecksum = True
            FChecksum = 0
            FFieldCount = 0            
         EndIf

         // are we reading a sentence? If so, set the buffer data...   
         If FReadingSentence Then 
            SetBufferData(Data)

            // if the character is a non-whitespace, then look to see
            // if it is a field comma. If it is, increment field count...
            If Data >= " " Then
               If Data = "," Then
                  Inc(FFieldCount)
               EndIf

            // its a whitespace character - terminate sentence reading
            // and save checksum and field count to the ring buffer...
            Else  
               Inc(FFieldCount)
               FReadingSentence = False

               INDF0 = 0 ' null terminator
               SetBufferData(FChecksum)
               SetBufferData(FFieldCount)
               Inc(FCount)  
            EndIf
         EndIf   
      Restore
   EndIf
End Interrupt
{
****************************************************************************
* Name    : Initialise (PRIVATE)                                           *
* Notes   : Initialise local module variables and configure the USART for  *
*         : interrupts based receive                                       *
****************************************************************************
}
Sub Initialize()
   FOverflow = false
   FReadingSentence = False
   FCalculatingChecksum = False
   FChecksum = 0
   FCount = 0
   FFieldCount = 0
   FIndexIn = 0  
   FIndexOut = 0     
   PIE1.5 = 1            // enable interrupt on USART1 receive
   Enable(OnNMEAData)    // enable handler
End Sub
{
****************************************************************************
* Name    : GetItem                                                        *
* Purpose : Get a NMEA item structure held in the buffer. Function returns *
*         : true if item found, false otherwise                            *
****************************************************************************
}
Public Function GetItem(ByRef pNMEA As TNMEA) As Boolean
   Dim ChecksumStart, Checksum, Index As Byte
   Dim Sum As String

   // no NMEA data in the buffer...
   If FCount = 0 Then
      result = False

   // NMEA data is held in the buffer, start processing...   
   Else
      Result = True
      Dec(FCount)

      // Copy the NMEA line stored in the buffer into the NMEA.Line - also
      // note the where the checksum index of the NMEA string is located
      ChecksumStart = 0
      Index = 0
      FSR0 = @pNMEA.Line
      Repeat
         POSTINC0 = GetBufferData
         If INDF1 = byte("*") Then
            ChecksumStart = Index + 1
         EndIf
         Inc(Index)   
      Until INDF1 = 0 

      // We have read the string from the buffer, the next two bytes
      // in the buffer hold the checksum that was computed when the string
      // was loaded into the buffer and also the number of data fields the
      // NMEA sentence has...
      Checksum = GetBufferData
      pNMEA.Count = GetBufferData

      // The NMEA sentence has a checksum of the form *xx, where xx is a
      // two byte HEX number. We need to extract the two digit string data...
      If ChecksumStart > 0 Then
         FSR0 = @Sum
         FSR1 = @pNMEA.Line
         Inc(FSR1, ChecksumStart)
         Repeat
            POSTINC0 = INDF1
         Until POSTINC1 = 0
      EndIf

      // Now we validate the checksum and set the NMEA.Valid flag as needed...
      If HexToStr(Checksum,2) = Sum Then
         pNMEA.Valid = True
      Else
         pNMEA.Valid = False
      EndIf 
   EndIf

   // if a buffer overflow has occurred, wait until all items have been
   // read out of the buffer before clearing the overflow error...
   If FOverflow And FCount = 0 Then
      FOverflow = false
   EndIf
End Function
{
****************************************************************************
* Name    : GetField                                                       *
* Purpose : Find a NMEA field from within the NMEA line. Pass a NMEA       *
*         : structure, togther with the field index you want to retreive.  *
*         : Returns true if a field found, false otherwise                 *
****************************************************************************
}
Public Function GetField(ByRef pNMEA As TNMEA, pItemIndex As Byte, ByRef pItem As String) As Boolean   
   // default is not found...
   Result = False

   // set line address and loop though line until we find 'ItemIndex' occurance
   // of the seperator char...
   FSR1 = @pNMEA.Line
   While pItemIndex > 0 And INDF1 <> 0
      If POSTINC1 = byte(",") Then
         Dec(pItemIndex)
      EndIf
   Wend

   // load item address and load characters from line until next seperator
   // or line terminator found...
   FSR0 = @pItem
   INDF0 = 0
   If pItemIndex = 0 Then
      Result = True
      While INDF1 <> byte(",") And INDF1 <> 0 
         POSTINC0 = POSTINC1   
      Wend
      INDF0 = 0 // set item line terminator  
   EndIf   
End Function

// initialise module
Initialize