is there a XV18 version of the NMEA module

General discussion relating to the library modules supplied with the compiler

Moderators: David Barker, Jerry Messina

Post Reply
animo3d
Posts: 27
Joined: Sun Sep 18, 2011 6:02 am
Location: Monterrey Mexico

is there a XV18 version of the NMEA module

Post by animo3d » Tue Jun 04, 2024 11:12 pm

Hello I have been using the NMEA module for some time on a product I sell a few units per year, I'm looking to redesign this board using Q41 Microcontrollers, I had a lot of trouble trying to convert the module to be able to use XV...

I somewhat have it working using the xvISRRX2, but is not as eficient and streamlined as the original NMEA Module...

Any one has done the conversion?

Best Regards
Sergio

Jerry Messina
Swordfish Developer
Posts: 1486
Joined: Fri Jan 30, 2009 6:27 pm
Location: US

Re: is there a XV18 version of the NMEA module

Post by Jerry Messina » Thu Jun 06, 2024 12:50 pm

With a few minor changes to NMEA.bas to remove the hardcoded references to the interrupt and RCREG definitions and use the generic ones defined in USART.bas, you should be able to changed the module to work with any device without having to restructure it to use the rx isr.
This also adds a new '#option NMEA_USART' to allow using any of the device uarts

NMEA.bas (v1.2):

Code: Select all

{
*****************************************************************************
*  Name    : NMEA.BAS                                                       *
*  Author  : David John Barker                                              *
*  Notice  : Copyright (c) 2007 Mecanique                                   *
*          : All Rights Reserved                                            *
*  Date    : 06 JUN 2024                                                    *
*  Version : 1.2                                                            *
*          : 1.2  06 JUN 2024                                               *
*          : - Add '#option NMEA_USART' to allow using USART 1-5            *
*          : - Added importing USARTx.bas module for usart RCIF/RCIE and    *
*          :   ReadByte() definitions                                       * 
*          : 1.1 24/08/2007                                                 *
*          : - 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 usart module - added v1.2
// #option to specify which USART to use (1-5)
#option NMEA_USART = 1
#if not (NMEA_USART in (1 to _usart))
  #error "invalid NMEA_USART option for " + _device
#endif
#if (NMEA_USART = 1)
  include "USART.bas"
#elseif (NMEA_USART = 2)
  include "USART2.bas"
#elseif (NMEA_USART = 3)
  include "USART3.bas"
#elseif (NMEA_USART = 4)
  include "USART4.bas"
#elseif (NMEA_USART = 5)
  include "USART5.bas"
#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                      *
* V1.2 - changed to use USART.bas definitions for RCIF and ReadByte()      *
****************************************************************************
}
Interrupt OnNMEAData()
   Dim Data As Char

   // If a byte receive has triggered the interrupt, then context save
   // FSR0 and process the data byte...
   If RCIF = 1 Then           // changed v1.2 to use USARTx.bas                          
      Data = ReadByte()       // changed v1.2 to use USARTx.bas

      // 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                                       *
* V1.2 - changed to use USART.bas definitions for RCIE                     *
****************************************************************************
}
Sub Initialize()
   FOverflow = false
   FReadingSentence = False
   FCalculatingChecksum = False
   FChecksum = 0
   FCount = 0
   FFieldCount = 0
   FIndexIn = 0  
   FIndexOut = 0     
   RCIE = 1        // changed v1.2 - enable interrupt on USARTx 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()
Here's a modified version of the wiki example showing its usage for USART1:

Code: Select all

// NMEA example 1
device=18F16Q41
clock=32

include "intosc.bas"            // set to use intosc
#option DIGITALIO_INIT = true   // call setalldigital at startup
include "setdigitalio.bas"      // may be required for usart pins

// 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
Here's another example showing use of different USARTs using the new '#option NMEA_USART':

Code: Select all

// NMEA example 1 with usart1-5
device=18F16Q41
clock=32

include "intosc.bas"            // set to use intosc
#option DIGITALIO_INIT = true   // call setalldigital at startup
include "setdigitalio.bas"      // may be required for usart pins

// import modules...
#option NMEA_USART = 2          // NMEA usart (default = 1)
Include "NMEA.bas"
#if (NMEA_USART = 1)
  include "USART.bas"
#elseif (NMEA_USART = 2)
  include "USART2.bas"
#elseif (NMEA_USART = 3)
  include "USART3.bas"
#elseif (NMEA_USART = 4)
  include "USART4.bas"
#elseif (NMEA_USART = 5)
  include "USART5.bas"
#endif
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...
SetBaudrate(br4800)         // USARTx
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)
      Write(NMEAField, " : ", DecToStr(NMEAItem.Count), 13, 10)     // USARTx
   EndIf
Wend
I didn't test it on a real device, so if you have problems...

animo3d
Posts: 27
Joined: Sun Sep 18, 2011 6:02 am
Location: Monterrey Mexico

Re: is there a XV18 version of the NMEA module

Post by animo3d » Sun Jun 09, 2024 11:33 pm

Thanks a lot Jerry, I will try it this week...

Greetings
Sergio

Jerry Messina
Swordfish Developer
Posts: 1486
Joined: Fri Jan 30, 2009 6:27 pm
Location: US

Re: is there a XV18 version of the NMEA module

Post by Jerry Messina » Thu Jun 20, 2024 1:16 pm

The attached zip file contains an updated NMEA module (v1.3) that has a few new features:
- can use either high or low priority interrupts (set via #option)
- context save optimized when used with xv18 devices
- NMEA.bas contains aliases for various USARTx routines like SetBaudrate so you don't have to change the main program when switching usarts
- interrupts are no longer automatically enabled at startup as part of NMEA.Initialize (set via #option)

The last one is important. It allows you to set up all the hardware prior to enabling the NMEA usart interrupt.
The downside is you now have to call NMEA.EnableInterrupt() when you're ready to start (typically after NMEA.SetBaudrate)

Here's an updated example for the Q41 that shows usage, including PPS setup

Code: Select all

// NMEA example 1 for xv18 and PPS
device = 18F16Q41
clock = 64

// enable hdw shadow register use for _xv18 intr
#if defined(_xv18)
  #option ISR_SHADOW_HIGH = true
  #option ISR_SHADOW_LOW = true
#endif

// import modules...
include "intosc.bas"            // set to use intosc
#option DIGITALIO_INIT = true   // call setalldigital at startup
include "setdigitalio.bas"      // may be required for usart pins
include "pps.bas"               // pps mappable pins

// NMEA module setup for USART2 with RC4=TX and RC5=RX
// you must define the USART2 pin options (used by USART2.bas) before including the NMEA module
// and then assign the pins using PPS. NMEA will include the USARTx.bas file based on the  NMEA_USART option
#option USART2_TX = PORTC.4
#option USART2_RX = PORTC.5
#option NMEA_USART = 2          // NMEA uses usart2 (default=1)
Include "NMEA.bas"

Include "USART2.bas"            // for usart baudrate constants
Include "Convert.bas"

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


// setup pps (must match USART2 pin options above)
pps.unlock()
pps.assign_input(U2RXPPS, PPS_PORTC, 5)     // assigns usart RX2 input to RC5
pps.assign_output(RC4PPS, PPS_UART2_TX)     // assigns usart TX2 output to RC4

// this program with display the sentence identifier and the number
// of data fields the sentence has...
NMEA.SetBaudrate(br4800)        // USARTx
NMEA.EnableInterrupt()          // new - user must enable NMEA interrupt

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)
      NMEA.Write(NMEAField, " : ", DecToStr(NMEAItem.Count), 13, 10)     // USARTx
   EndIf
Wend
Attachments
nmea_v13.zip
(5.46 KiB) Downloaded 32 times

animo3d
Posts: 27
Joined: Sun Sep 18, 2011 6:02 am
Location: Monterrey Mexico

Re: is there a XV18 version of the NMEA module

Post by animo3d » Mon Jul 01, 2024 7:04 pm

Sorry for the late update, but everything worked like a charm!

Worked First try, just updated the module and compiled with my old software on the Q41 chip...

Jerry you are the man!
Thanks for keeping this comunity alive!

Best regards
Sergio

Post Reply