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