Combining both EUSART interrupts in one library

Coding and general discussion relating to user created compiler modules

Moderators: David Barker, Jerry Messina

Post Reply
User avatar
RadioT
Registered User
Registered User
Posts: 157
Joined: Tue Nov 27, 2007 12:50 pm
Location: Winnipeg, Canada

Combining both EUSART interrupts in one library

Post by RadioT » Sun Apr 13, 2008 6:44 am

Hello,

I need to gather data from both serial ports in succession. I also need high-priority interrupts for other operations, so I combined the ISRRX and NMEA libraries together and am using it as the low priority interrupt. It works - I can run each EUSART together or in succession. Problem is, sometimes one of the buffers is written too far or in the wrong place and ends up clobbering variables and program code. It's tough to debug, I'm using ICD2 and I can only see watches on single locations and not read back whole blocks of code.

It seems that the error occurs after I stop and restart one of the serial port devices. The interrupts are still enabled, the ports are still enabled, just the device stops sending data. I've tried enabling and disabling the ports, and re-initialized the module, no change.

EUSART1 runs at 19200 bps while EUSART2 runs at 9600 bps (it's a GPS). EUSART1 only has to run occasionally.

Here's a copy of the code. If anyone has any suggestions on the code below or debugging in ICD2, please post!
73's,
de Tom.

Code: Select all

{
*****************************************************************************
*  Name    : NMEA_USARTS.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_USARTS

// #option to set the NMEA buffer size...
#option NMEA_BUFFER_SIZE = 100        //changed from 200 bytes since the longest string is 
#if Not (NMEA_BUFFER_SIZE in (100 to 250))
   #error NMEA_BUFFER_SIZE, "Invalid option. Buffer size must be bteween 100 and 250"
#endif

// size of the RX buffer...
#if IsOption(RX_BUFFER_SIZE) And Not (RX_BUFFER_SIZE in (1 to 255))
   #error RX_BUFFER_SIZE, "Invalid option. Buffer size must be between 1 and 255 (bytes)."
#endif
#option RX_BUFFER_SIZE = 64      
Public Const BufferSize = RX_BUFFER_SIZE

// Import conversion module...
Include "convert.bas"
// import USART library...
Include "USART.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

// local variables and aliases for USART1...
Dim 
   FUSART1_Buffer(BufferSize) As Byte,
   FUSART1_IndexIn As Byte,
   FUSART1_IndexOut As Byte,
   FUSART1_ByteRead As Byte,
   FUSART1_ProcessByte As Boolean,
   FUSART1_MaybeOverrun As Boolean 
 //  FUSART1_OnDataEvent As TEvent
 
// public variables and aliases...   
Public Dim   
   USART1_USARTOverrun As USART.Overrun,
   USART1_BufferOverrun As Boolean,
   USART1_DataByte As FUSART1_ByteRead,
   USART1_DataChar As FUSART1_ByteRead.AsChar,
   USART1_ProcessByte As FUSART1_ProcessByte

{
****************************************************************************
* 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                      *  
*         : also contains B0 interrupt vector since it is only high priority*
****************************************************************************
}
Public Interrupt OnNMEAData(1)    // (1) so it's a low priority interrupt
   Dim Data As Char
   // proceed...                   
   // If a byte receive has triggered the interrupt, then context save
   // FSR0 and process the data byte...
   If PIR3.5 = 1 Then  //changed to PIR3 for USART2 used for GPS - EUSART2 flag is set 
      Save(FSR0, PRODL)                                
      Data = RCREG2    //changed to RCREG2 for USART2 used for GPS

      // buffer overflow has occurred, abort...
      If FOverflow Then
         Exit
      EndIf 
         // stop calculating checksum...
         If Data = "*" Then
            FCalculatingChecksum = False
         EndIf            

         // calculate checksum...
         If FCalculatingChecksum Then
            FChecksum = FChecksum Xor RCREG2
         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 
      
   If PIR1.5 = 1 Then   // EUSART1 flag is set 
   Save(FSR0, PRODL)  
   USART1_BufferOverrun = FUSART1_MaybeOverrun
   If Not USART.Overrun Then
      FUSART1_ByteRead = USART.RCRegister
      If Not USART1_BufferOverrun Then
         FUSART1_ProcessByte = true      
         
         If FUSART1_ProcessByte Then
            FUSART1_Buffer(FUSART1_IndexIn) = FUSART1_ByteRead
            Inc(FUSART1_IndexIn)
	        If FUSART1_IndexIn > Bound(FUSART1_Buffer) Then
               FUSART1_IndexIn = 0
            EndIf
            FUSART1_MaybeOverrun = (FUSART1_IndexIn = FUSART1_IndexOut) 
         EndIf   
      EndIf      
   EndIf
   Restore	     
  EndIf 
     
End Interrupt
{
****************************************************************************
* Name    : Initialise (PRIVATE)                                           *
* Notes   : Initialise local module variables and configure the USART for  *
*         : interrupts based receive                                       *
****************************************************************************
}
Public Sub Initialize()
   FOverflow = false
   FReadingSentence = False
   FCalculatingChecksum = False
   FChecksum = 0
   FCount = 0
   FFieldCount = 0
   FIndexIn = 0  
   FIndexOut = 0     
   PIE3.5 = 1            // enable interrupt on USART2 receive
   
   //EUSART1 variables initialize
   FUSART1_IndexIn = 0
   FUSART1_IndexOut = 0
   FUSART1_MaybeOverrun = false
   USART1_BufferOverrun = false
   USART.ClearOverrun
   USART.RCIEnable = true

   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
// *************************************************************************
// EUSART1 subroutines
// *************************************************************************
{
****************************************************************************
* Name    : DataAvailable                                                  *
* Purpose : Check to see if there is data in the buffer                    *
****************************************************************************
}
Public Function DataAvailable() As Boolean
   Disable(OnNMEAData)
   Result = FUSART1_IndexIn <> FUSART1_IndexOut
   Enable(OnNMEAData)
End Function
{
****************************************************************************
* Name    : Overrun                                                        *
* Purpose : Returns true if RC register or buffer has overrun, false       *
*         : otherwise                                                      *
****************************************************************************
}
Public Function Overrun() As Boolean
   Result = USART.Overrun Or USART1_BufferOverrun
End Function
{
****************************************************************************
* Name    : GetByte (PRIVATE)                                              *
* Purpose : Get a single byte from the buffer                              *
****************************************************************************
}
Function GetByte() As Byte
   FUSART1_MaybeOverrun = false
   Result = FUSART1_Buffer(FUSART1_IndexOut)
   Inc(FUSART1_IndexOut)
   If FUSART1_IndexOut > Bound(FUSART1_Buffer) Then
      FUSART1_IndexOut = 0
   EndIf   
End Function
{
****************************************************************************
* Name    : ReadByte                                                       *
* Purpose : Read a single byte from the buffer                             *
****************************************************************************
}
Public Function ReadByte() As Byte
   Disable(OnNMEAData)
   Result = GetByte
   Enable(OnNMEAData)
End Function
{
****************************************************************************
* Name    : ReadWord                                                       *
* Purpose : Read a word from the buffer                                    *
****************************************************************************
}
Public Function ReadWord() As Word
   Disable(OnNMEAData)
   Result.Bytes(0) = GetByte
   Result.Bytes(1) = GetByte
   Enable(OnNMEAData)
End Function
{
****************************************************************************
* Name    : ReadLongWord                                                   *
* Purpose : Read a long word from the buffer                               *
****************************************************************************
}
Public Function ReadLongWord() As LongWord
   Disable(OnNMEAData)
   Result.Bytes(0) = GetByte
   Result.Bytes(1) = GetByte
   Result.Bytes(2) = GetByte
   Result.Bytes(3) = GetByte
   Enable(OnNMEAData)
End Function
{
****************************************************************************
* Name    : ReadFloat                                                      *
* Purpose : Read a floating point number from the buffer                   *
****************************************************************************
}
Public Function ReadFloat() As Float
   Disable(OnNMEAData)
   Result.Bytes(0) = GetByte
   Result.Bytes(1) = GetByte
   Result.Bytes(2) = GetByte
   Result.Bytes(3) = GetByte
   Enable(OnNMEAData)
End Function
{
****************************************************************************
* Name    : ReadStr                                                        *
* Purpose : Read a string from the buffer. Optional parameter pTerminator  *
*         : to specify the input string terminator character. The function *
*         : returns the number of characters read                          *
****************************************************************************
}
Public Function ReadStr(ByRef pText As String, pTerminator As Char = null) As Byte
   Dim Ch As Char
   Dim Text As POSTINC0
   
   Disable(OnNMEAData)
   FSR0 = AddressOf(pText)
   Result = 0
   Repeat
      Ch = GetByte
      If Ch <> pTerminator Then
         Text = Ch
         Inc(Result)
      EndIf   
   Until Ch = pTerminator
   Text = 0
   Enable(OnNMEAData)
End Function

// ****************************************************************************
// initialise module - from original NMEA module
// ****************************************************************************

Initialize
Last edited by RadioT on Mon Apr 14, 2008 9:11 am, edited 1 time in total.

User avatar
RadioT
Registered User
Registered User
Posts: 157
Joined: Tue Nov 27, 2007 12:50 pm
Location: Winnipeg, Canada

Post by RadioT » Mon Apr 14, 2008 9:03 am

Here is the code I am using to test the USART interrrupt library. I am using the ICD2 debugger to walk through it. The error happens whether or not I start/stop either of the USART inputs. The code below is the "bare minimum" to make it all work, and the error still occurs every time I run it.

I set a breakpoint at the end of the program, just after variable "i" is incremented. I have set up "i" as a watchpoint in ICD2. Each time I run the program, I see "i" increment by one. After between 3 and 10 times through the "While True" loop at the bottom, the variable "i" doesn't just increase by 1 but is replaced by an ASCII character from an incoming NMEA string. So it jumps from a "06" to"29" or "33", etc. This suggests to me the circular buffer is not "circling" back to the beginning after filling to the end. I just can't see where it's doing it or how. I'm trying to isolate the conditions to the bare minimum here to see what's going on.

-Tom

Code: Select all

Device = 18F66J16

Clock = 32 
#option NMEA_BUFFER_SIZE = 250
#option RX_BUFFER_SIZE = 250  
Include "NMEA.bas"
Include "USART2.bas"
Include "USART.bas"

Config Fosc = intoscpll

Dim
   NMEAItem As TNMEA,
   Power_LED As PORTC.0,
   i As Byte,
   EUSART2_Input As String(250),
   MPWR As PORTG.4,        //turn on modem
   GPSI As PORTG.2,        //GPS input
   GPS_pwr As PORTG.0,     //power supply to GPS
   MON As PORTC.2,         //enable modem
   result As String(250)   //USART1 string max size to see if that helps   
   OSCTUNE.6 = 1 //turn on the PLL for 32 MHz with intosc
   OSCCON.6 = 1
   OSCCON.5 = 1
   OSCCON.4 = 1
   
Output (PORTC.0)
Output (PORTC.2)
TRISA = %00001101         ' Configure PORTA pins As inputs And outputs
TRISB = %11000011         ' Configure PORTB 
TRISG = %00001100         ' Configure PORTG as inputs/outputs for modem control

USART2.SetBaudrate(br9600)
RCSTA2.7=1
RCSTA2.6=0
RCSTA2.4=1
BAUDCON2.5=0
RCSTA2.5=0
TXSTA2.5=0
USART.SetBaudrate(br19200)

MPWR = 1              'turn on power supply
For i = 0 To 10     //flash an LED to show there is life
   Power_LED = 1
   DelayMS(100)
   Power_LED = 0
   DelayMS(50)
Next

NMEA.Initialize
GPS_pwr=0               //start the gps - start = 0
MON = 0                 //start modem
i = 0 
DelayMS (3000)
USART.Write("ATI4",$0D)  // Get modem ID string
DelayMS (2000)
//read data from the buffer and output...
While NMEA.DataAvailable
   NMEA.ReadStr(result,$0A)
Wend 

While True
   If NMEA.GetItem(NMEAItem) Then
      NMEA.GetField(NMEAItem,0,EUSART2_Input)
         NMEA.GetField(NMEAItem, 3, EUSART2_Input)
         NMEA.GetField(NMEAItem, 5, EUSART2_Input)
         NMEA.GetField(NMEAItem, 9, EUSART2_Input)
         NMEA.GetField(NMEAItem, 1, EUSART2_Input)
         NMEA.GetField(NMEAItem, 2, EUSART2_Input)
         Clear(EUSART2_Input)    
         USART.Write("ATI0",$0D)  // get modem product code
         ' read data from the buffer and output...
          While NMEA.DataAvailable
             NMEA.ReadStr(result,$0A)
          Wend 
          result = result + NULL
      EndIf  
    USART.Write("ATI3",$0D)  // get modem firmware revision
   ' read data from the buffer and output...
   While NMEA.DataAvailable   
      NMEA.ReadStr(result,$0A)
   Wend 
   result=result +null
      Clear(result)
   DelayMS(1359)     
   i = i + 1
   DelayMS(1000)
Wend

User avatar
RadioT
Registered User
Registered User
Posts: 157
Joined: Tue Nov 27, 2007 12:50 pm
Location: Winnipeg, Canada

....AKA adventures with Microchip ICD2......

Post by RadioT » Mon Apr 14, 2008 10:45 am

OK, here I am, answering my own queries; :wink: ICD2 keeps crashing when I read file registers ("File Registers" under the "View" menu) but I finally figured out how to use it "gently" so now it only crashes when I scroll the File Registers window too fast! So, now I can see the RAM locations change as the program hits the breakpoint on each loop. A bit of a pain having to reload everything after each crash but at least I can see it.

When I put the File Registers window up, I can see the USART1 and USART2 data marching along higher and higher in RAM with each run of the loop to the breakpoint. Doing all this gingerly, of course, since ICD2 crashes nearly every time I scroll the File Registers window. And unfortunately, the NULL I expected to see at the end of each USART1 string is not appearing there. So, the routine that reads this data up to a NULL would keep going happily past the end of the designated registers for the variables to read until it randomly encounters a null somewhere later in RAM, innocently leaving a trail of destruction in it's wake as it attempts to shift non-printable characters to the next step in the program.

Hmmm, an approx 80 byte string variable is going past the end of it's bound. The only thing that size is the TNMEA structure, which is 80 for the NMEA line, 1 for the number of bytes and 1 for the boolean "valid".

In the image below, the "i" variable is overwritten. Not sure why it's doing this but at least I can follow what's going on now.

Image

The memory allocation from the assembly listing shows that M593_U08, the designator for "i", is at location 953 or 8B9 hex. What I presume to be the TNMEA structure starts at location 871 or 367 hex. As an aside, you can see where the 250 byte string arrays are for USART2 and USART1, at locations 361 (169 hex) and 617 (269 hex).

[EDIT] OK I found out what it's doing. The routine that writes to the "Line" string array in the TNMEA structure would sometimes not be properly reset to 0 prior to writing. Increasing the buffer to 200 from 80 and testing for several hours, I found that about 150 bytes of the buffer would have been written to over the course of the test. This is a kluge so I need to delve into why it would not reset sometimes. For now, at least, I know why it was failing and I have both serial ports on the same interrupt working well enough to continue with other tests.
[/EDIT]
[EDIT 2] After a couple weeks of use, it's apparent the maximum speed these two USART interrupts can handle is about 9600 bits per second maximum on each port, with a 32 MHz clock. Going higher than this on either of the 2 ports is pushing the capability of the 18F chip at this speed...if someone can contribute a more efficient way to implement this, please do!
[/EDIT 2]

73's,
de Tom

Post Reply