Interrupt Advice

Coding and general discussion relating to the compiler

Moderators: David Barker, Jerry Messina

Post Reply
DannyScott
Posts: 22
Joined: Thu Sep 05, 2013 4:45 pm
Location: United Kingdom

Interrupt Advice

Post by DannyScott » Thu Jun 25, 2015 12:05 pm

After having read and implemented the following:

http://sfcompiler.co.uk/phpBB3/viewtopi ... +interrupt

using Jerry's templates in order to implement interrupts on the same priority levels, I occasionally observe the following as described by 'Gramo' in the original article. I quote...
The result is a couple of unreliable variables that seem to be shared between different subs/functions and become corrupted if accessed while already in use.
This seems to become more apparent when I cross a code space threshold of approximately 120+K, as before this threshold everything appears to work well and is reliable, but appreciate this may be a red herring.

As this thread was rather old and there have been compiler updates since etc, I was wondering if anyone has any updates additional information, new methods, rules or things to definitely avoid
when using more than one interrupt on the same priority?

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

Re: Interrupt Advice

Post by Jerry Messina » Thu Jun 25, 2015 1:29 pm

Hi Danny,

Boy, that is an old thread, although I still use that same sort of structure in most of my programs.

I don't think it's really related to program size per say... just having more variables and subroutine calls may make it worse (or probably more apparent). Sometimes issues like this can be "cured" by the simplest of changes. If you don't see it, then it's "fixed", right?

One thing that thread mentioned but sort of glossed over is you still need to use 'save/restore' if you're doing anything but the simplest of code, and I mean SIMPLE. If you have subroutine calls in the handlers you'll likely have to add the names to the save list (ie 'save(0, sub1, sub2, etc)'. That's usually the part most folks forget.

Having multiple interrupt handlers in a single isr typically isn't the cause of the issue since any temps/stack frame variables required by one handler aren't needed any more in the other handler. Not saving the proper things in the first place is, and sometimes it can be very hard to notice that you even have a problem. It all depends on who ends up stomping on who.

DannyScott
Posts: 22
Joined: Thu Sep 05, 2013 4:45 pm
Location: United Kingdom

Re: Interrupt Advice

Post by DannyScott » Thu Jun 25, 2015 2:53 pm

Hi Jerry,

Interesting! I think you are very probably correct with regard to the fact that the problem becomes more apparent, the larger the code is, and is also probably never 'fixed'. I agree that just because I can't always 'see it', it doesn't mean the problem has been resolved!

I have deliberately avoided using subroutine calls in the interrupt event handlers, and I initially based the template for my interrupt routines on the examples in the SF library as I thought this would be a good starting point. Any variables used within the handler are publicly declared at the module level.

Code: Select all

Public Event Evt_Intr_Handler_usart1()

   Dim PRODRegister As PRODL.AsWord
   Dim SaveFSR0, SaveFSR1, SaveFSR2, SavePROD As Word
  
    ' Context save...
    SaveFSR0 = FSR0
    SaveFSR1 = FSR1
    SaveFSR2 = FSR2
    SavePROD = PRODRegister

    'Handle the interrupt. Any variables used are publicly declared at the module level

    ' reset the peripheral Intr flag, this is an inline sub no need to conext save????
    Intr_reset_flag_usart1()


    ' Context restore...
    FSR0 = SaveFSR0
    FSR1 = SaveFSR1
    FSR2 = SaveFSR2
    PRODRegister = SavePROD

end event

However....

Would it be better to use Save(0) / Restore instead of saving discrete registers?

Code: Select all

Public Event Evt_Intr_Handler_usart1()

    ' Context save...
    Save(0)

    'Handle the interrupt.

    ' reset the peripheral Intr flag, this is an inline sub no need to conext save????
    Intr_reset_flag_usart1()


    ' Context restore...
    Restore

end event

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

Re: Interrupt Advice

Post by Jerry Messina » Thu Jun 25, 2015 6:02 pm

Would it be better to use Save(0) / Restore instead of saving discrete registers?
Probably not, but that depends on what's actually going on during the ''Handle the interrupt" part

Save(0) will save FSR0, FSR1, and the PROD registers for you. It will also save the 25 (+/-) bytes of System variables that are used by various internal routines, such as some of the math operations. If you use floats or do much math other than add/subtract you probably need a 'save(0)'.

What I normally do in situations like this is to try it and see if that clears things up, and take it from there. It adds a lot of overhead, but sometimes it can't be avoided.

If you look at the resulting asm code portion of your interrupt handler you're looking for any references to variables starting with 'SB_' (system ones) or 'F' (stack frame)... something like

Code: Select all

MOVFF F0_U08,SB_SV0
which involves a temp frame variable (F0_U08) and a system one (SB_SV0) is a double whammy.

Variables that are declared at the module level begin with 'M', like 'M0_S16'. These aren't shared so they're safe (but you may end up creating temp variables as you do operations with them)

SHughes_Fusion
Posts: 219
Joined: Wed Sep 11, 2013 1:27 pm
Location: Chesterfield

Re: Interrupt Advice

Post by SHughes_Fusion » Mon Jun 29, 2015 8:09 am

Hope it's OK to butt in on this, I've got a query from what you've written, Jerry.

I'm not sure why but from somewhere I've got this usage of the Save command:

Code: Select all

Save(0,FSR0,FSR1)
Firstly, am I right in thinking that is a bit of a pointless usage of the command as it saves FSR0 and FSR1 anyway when you call Save(0)?

Secondly, you say it saves some system variables. Where in the asm code does this happen? I ask as in the asm for my code it appears the ISR is called from:

Code: Select all

    ORG 0X000808
    GOTO ISR_ONINT_854
And the ISR itself starts:

Code: Select all

ISR_ONINT_854
    MOVLB 0
    MOVFF _FSR0L#M0_U16H,F231_U16H
    MOVFF _FSR0L#M0_U16,F231_U16
    MOVFF _FSR1L#M0_U16H,F233_U16H
    MOVFF _FSR1L#M0_U16,F233_U16
    LFSR 0,F235_U200
    CLRF FSR1L,0
    CLRF FSR1H,0
    MOVLW 25
    MOVFF POSTINC1,POSTINC0
    DECFSZ WREG,1,0
    BRA $ - 6
    MOVFF PRODL,F260_U08
    MOVFF PRODH,F261_U08
Which appears to save FSR0, FSR1 and the PROD register but nothing else.

Is my usage of Save breaking something or is this a bug in the compiler? Or has it just decided that it isn't necessary to save any system variables? The code appears to be working but I also don't appear to use any SB_ variables in my ISR, although I do use some Fxxx variables.

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

Re: Interrupt Advice

Post by Jerry Messina » Mon Jun 29, 2015 10:46 am

Hope it's OK to butt in on this
The more the merrier!
Firstly, am I right in thinking that is a bit of a pointless usage of the command as it saves FSR0 and FSR1 anyway when you call Save(0)?
If you add on 'FSR0, FSR1' to a 'Save(0)' it's smart enough to skip saving them a second time so it doesn't hurt anything, but they're already covered by specifying '0'. Note that '0' doesn't save FSR2, but the compiler doesn't normally use that one (although some libraries might).
Secondly, you say it saves some system variables. Where in the asm code does this happen?
It's right there in front of you (ok, it's clear as mud)...

Code: Select all

ISR_ONINT_854
    MOVLB 0
    MOVFF _FSR0L#M0_U16H,F231_U16H         // save FSR0
    MOVFF _FSR0L#M0_U16,F231_U16
    MOVFF _FSR1L#M0_U16H,F233_U16H         // save FSR1
    MOVFF _FSR1L#M0_U16,F233_U16
    LFSR 0,F235_U200                        // load FSR0 with the address of the save block
    CLRF FSR1L,0                              // load FSR1 with the starting address of the system vars (0)
    CLRF FSR1H,0
    MOVLW 25                                  // set count=25 (sizeof system var block)
    MOVFF POSTINC1,POSTINC0          // copy *FSR1++ -> *FSR0++
    DECFSZ WREG,1,0                       // decr count
    BRA $ - 6
    MOVFF PRODL,F260_U08              // save PROD (L and H)
    MOVFF PRODH,F261_U08
The code appears to be working but I also don't appear to use any SB_ variables in my ISR, although I do use some Fxxx variables.
If you're sure you don't have any calls in the ISR that may end up using a SB_ variable then you'd be safe removing the '0' and just using 'save(FSR0, FSR1, PRODL, PRODH)'. You should specify saving each of the eight-bit PROD regs individually since right now most of the device files have PROD defined as a single 8-bit register. You could also add a 'dim PROD as PRODL.asword' to change that.

The existence of Fxxxx variables means that the ISR is using some of the stack frame and that can be a red flag that something might need protecting, but not always. If the code that generates the frame variables is directly visible in the ISR then the compiler generally takes note of this and doesn't reuse them, effectively making them static. It's also possible that the Fxxxx variable is just part of the context save itself (like in the above code saving FSR), so that's ok too. It's usually only if you have subroutines involved that things get murky.

One thing you can do to help you decipher what the Fxxxx variables are for is to add

Code: Select all

#option _showvar = true
to your main program. That will add some decoration to the Fxxx name if it's a declared variable...

Code: Select all

#option _showvar = true

sub s()
  dim a, b, c as word
  <snip>
  
A_F41_U16 EQU 66
A_F41_U16H EQU 67
B_F43_U16 EQU 68
B_F43_U16H EQU 69
C_F45_U16 EQU 70
C_F45_U16H EQU 71
F47_U08 EQU 72
F48_U08 EQU 73
F49_U08 EQU 74
You can see that the Fxxxx names for A, B, and C are prefixed by the identifier, making them easier to find. The remaining Fxxx ones are created by the compiler. In the above, the EQU is specifying the memory address in RAM. If two variables are sharing the same locations, this is where you'll find it... they'll have the same EQU location.

Another way to find out what's used by a block of code is to turn the block into a subroutine on purpose, and add the name of the subroutine to the save list. That'll tell the compiler to go look at the code and determine what it might need to save.

SHughes_Fusion
Posts: 219
Joined: Wed Sep 11, 2013 1:27 pm
Location: Chesterfield

Re: Interrupt Advice

Post by SHughes_Fusion » Mon Jun 29, 2015 11:06 am

OK, maybe I should avoid posting before lunchtime on a Monday! You are right, Jerry, it is there when you look. I was expecting MOVFF-type instructions to save them, I forgot about the FSR trick.

When you mention 'calls in the ISR' do you mean sub / function calls? I was always taught never to call subs or functions from an ISR, I always set a flag and the call is done outside the ISR.

All I do in this particular ISR aside from setting these flags is reloading timer registers, decrementing some global variables and handling serial buffers. All data handled are bytes or bits/booleans, although there is some array access - in this implementation the array size is under 256 bytes, I use the same code elsewhere with larger arrays though. (Not in this software)

From what I can tell, the only F registers used in the ISR correspond to local variables I use to hold the latest byte received by the UART and the framing error flag corresponding to that byte.

Is there anything else I need to look for, and what do I need to look out for going wrong if I remove the saving of those system variables?

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

Re: Interrupt Advice

Post by Jerry Messina » Mon Jun 29, 2015 11:35 am

do you mean sub / function calls? I was always taught never to call subs or functions from an ISR
Yes. Never using any calls certainly makes it a lot less complicated and more efficient... just watch out for ones created by the compiler to do math operations and the like. I'm a subroutine junkie so I always seem to make things harder for myself. If you do use them you definitely need to understand what's going to happen...
and what do I need to look out for going wrong if I remove the saving of those system variables?
That's the thing... you never know what might go wrong since it's next to impossible to predict when the clash might occur. If it's because of the system vars then it'll likely be an error in some math or string operation somewhere.

I usually stumble across these by accident when things that "were working" just seem to go out to lunch all of a sudden after making a simple change to some completely unrelated code. That's usually a good indicator that something's getting stomped on by an interrupt. If you disable the isr and things clear up you know where to start hunting.

SHughes_Fusion
Posts: 219
Joined: Wed Sep 11, 2013 1:27 pm
Location: Chesterfield

Re: Interrupt Advice

Post by SHughes_Fusion » Mon Jun 29, 2015 1:11 pm

From what I can see these F... references are unique to the ISR. I guess the best idea would be to try and complete development then see if it is safe to disable saving the system variables as that way you are less likely to change something at a later date which breaks it?

At the system I'm working on it battery powered anything I can do to allow me to reduce the clock speed helps. Saving and restoring 25 bytes is quite an overhead if it isn't needed. The ISR is being called around 300 times a second without any data traffic. Transmissions are usually under 100 characters but if we say 200 characters for a log retrieval command then that's 500 ISR calls per second with over 200 instruction cycles just to save and restore these variables...

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

Re: Interrupt Advice

Post by Jerry Messina » Mon Jun 29, 2015 5:15 pm

From what I can see these F... references are unique to the ISR
If you don't see anything else EQU'd to the same addresses then you're likely safe.
I guess the best idea would be to try and complete development then see if it is safe to disable saving the system variables as that way you are less likely to change something at a later date which breaks it?
I might be inclined to go the other route... if you're pretty much done with the ISR portion then remove any extraneous saves now. That way as you add more code things will continuously change and the likelihood of uncovering any odd behavior might increase (??). If at some point things just suddenly seem to start acting wiggy check out the ISR's and see if they still make sense before you go off on a wild goose chase.

I'm probably being way too paranoid here. It's just that I've seem some people ask why their interrupt code is flaky, only to find out that they're accessing an I2C RTC, doing SD card operations, and updating their graphics LCD inside the ISR and wondering why they have problems.

DannyScott
Posts: 22
Joined: Thu Sep 05, 2013 4:45 pm
Location: United Kingdom

Re: Interrupt Advice

Post by DannyScott » Wed Jul 01, 2015 8:16 pm

Gentlemen,

Many thanks for your input and advice so far!

I have now coded a 'belt and braces' version using Jerry's event templates, plus

Code: Select all

Save(0, FSR2) 
...As I frequently use FSR2. I admit is more wasteful of resources, however it does appear to be working very reliably now (touch wood). The infrequency of the error, I feel may have been attributed to our 'radio' message interrupts conflicting with 'something else', as the messages are very infrequent (seconds between), but we are now attempting to 'hammer' this partictular interrupt with dummy messages, purely to prove the reliability.

I am not using any calls to graphics, SPI, I2C etc. within the interrupt routines, and have tried to be mindful of what is going on 'under the hood', and the advice given on this thread in studying the .asm has proved invaluable even though I must confess I am not an expert with .asm, hence why I use a compiler!

Whilst I love the open libraries, If anyone would like, or be willing to share some alternative USART interrupt routines for example other than those which exist currently in the library (I found the 'gotcha' when the read / write pointer indeces are equal BTW) I would be extremely grateful?

Thanks again! When time permits, I can perhaps find out 'why'... We are using a relatively new PIC, and Microchip seem to be very fond of moving registers and bits around!

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

Re: Interrupt Advice

Post by Jerry Messina » Thu Jul 09, 2015 2:38 pm

If anyone would like, or be willing to share some alternative USART interrupt routines
Here's the shell of something I've used in the past. It's similar to the ISRRX module, but it handles buffer overruns differently, and doesn't require interrupts to be disabled to read the buffer.

To make life simple, you must set the buffer size to a power of 2. The code implements a circular buffer with a RXHead and a RxTail index. When RxHead = RxTail, the buffer is empty, so the buffer can only store RX_BUFFER_SIZE-1 entries.

If the buffer or USART ever overflow it just sets a flag and throws away the bytes. It's up to you to monitor the Overrun() condition and do what's appropriate. Avoiding a buffer overrun is really up to the design, and you may have to implement some form of handshaking to prevent it (which is what I typically do).

UartBuffer.bas:

Code: Select all

module UartBuffer

// import USART library...
include "USART.bas"
include "system.bas"        // for ClrWDT()

// set interrupt priority level...
#if IsOption(RX_PRIORITY) and not (RX_PRIORITY in (ipLow, ipHigh))
   #error RX_PRIORITY, "Invalid option. Priority must be ipHigh or ipLow."
#endif
#option RX_PRIORITY = ipHigh
const PriorityLevel = RX_PRIORITY

// size of the RX buffer...
#if IsOption(RX_BUFFER_SIZE) and not (RX_BUFFER_SIZE in (4, 8, 16, 32, 61, 128, 256))
   #error RX_BUFFER_SIZE, "Invalid option. RX_BUFFER_SIZE must be <=256 and power of 2"
#endif
#option RX_BUFFER_SIZE = 64      
public const BUFFER_SIZE = RX_BUFFER_SIZE
// modulo 2 mask
const BUFFER_MASK as byte = (BUFFER_SIZE-1)

type TEvent = event()

// local variables and aliases...
dim 
   RxBuffer(BUFFER_SIZE) as byte,
   RxHead as byte,                  // data goes into the buffer at head
   RxTail as byte,                  // and comes out from the tail
   RxByte as byte,
   OnDataEvent as TEvent
 
// public variables and aliases...   
public dim   
   BufferOverrun as boolean,        // buffer/usart error flag. must be cleared by user
   ProcessByte as boolean           // can be used by event to signal skip buffering this char

{
****************************************************************************
* Name    : OnRX (PRIVATE)                                                 *
* Purpose : Interrupt Service Routine (ISR) to buffer incoming data        *
****************************************************************************
}
interrupt OnRX(PriorityLevel)
   dim FSRSave as word
   dim tmpHead as byte
   
   // save FSR0 (used for buffer array index)
   FSRSave = FSR0
   
   // check for usart overrun...
   if (USART.Overrun) then
      BufferOverrun = true          // signal error
      // read and discard usart data
      WREG = USART.RCRegister
      WREG = USART.RCRegister
      // clear overrun so we can receive more data
      USART.ClearOverrun()      
   else
      // check for framing error (optional)
      if (USART.FrameError) then
         // framing errors are usually due to a mismatch in the hardware setup
         // (baudrate, number of databits, etc) but can also be due to a serial
         // BREAK (rxd low > 13 bit times)
         // handle as appropriate... the status flag clears when the RCREG is read
      endif
      
      // read the received data
      RxByte = USART.RCRegister
      
      // calculate buffer index 
      // tmpHead = (RxHead + 1) and BUFFER_MASK
      tmpHead = RxHead
      tmpHead = tmpHead + 1
      tmpHead = tmpHead and BUFFER_MASK
      
      // check to see if there's room in the buffer
      if (tmpHead = RxTail) then        // buffer is full... don't add it
          BufferOverrun = true          // signal error
      else
         ProcessByte = true
         // execute handler...
         OnDataEvent()
         if (ProcessByte) then
            RxHead = tmpHead              // update index
            RxBuffer(tmpHead) = RxByte    // and add the byte to the buffer
         endif
      endif
   endif

   // restore FSR0
   FSR0 = FSRSave
end interrupt

{
****************************************************************************
* Name    : Initialize                                                     *
* Purpose : Initialize buffering - with optional OnData event handler      *
****************************************************************************
}
public sub Initialize(pOnDataEvent as TEvent = 0)
   OnDataEvent = pOnDataEvent
   RxHead = 0
   RxTail = 0
   BufferOverrun = false
   USART.ClearOverrun()
  #if RX_PRIORITY = ipLow
   USART.RCIPHigh = false
  #endif
   USART.RCIEnable = true
   enable(OnRX)
end sub

{
****************************************************************************
* Name    : Reset                                                          *
* Purpose : Reset module                                                   *
****************************************************************************
}
public sub Reset()
   disable(OnRX)
   Initialize(OnDataEvent)
end sub   
{
****************************************************************************
* Name    : Start                                                          *
* Purpose : Start interrupt handling                                       *
****************************************************************************
}
public sub Start()
   enable(OnRX)
end sub
{
****************************************************************************
* Name    : Stop                                                           *
* Purpose : Stop interrupt handling                                        *
****************************************************************************
}
public sub Stop()
   disable(OnRX)
end sub
{
****************************************************************************
* Name    : DataAvailable                                                  *
* Purpose : Check to see if there is data in the buffer                    *
****************************************************************************
}
public function DataAvailable() as boolean
   // assume no data in the buffer
   result = false
   if (RxHead <> RxTail) then
      result = true         // buffer has data
   endif
end function
{
****************************************************************************
* Name    : GetCount                                                       *
* Purpose : returns number of bytes in the buffer                          *
****************************************************************************
}
public function GetCount() as byte
   result = (RxHead - RxTail) and BUFFER_MASK
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 BufferOverrun
end function
{
****************************************************************************
* Name    : ReadByte                                                       *
* Purpose : Read a single byte from the buffer                             *
*         : DataAvailable() MUST return true before calling this routine   *
****************************************************************************
}
public function ReadByte() as byte
   dim tmpTail as byte
   
   // tmpTail = (RxTail + 1) and BUFFER_MASK
   tmpTail = RxTail
   tmpTail = tmpTail + 1
   tmpTail = tmpTail and BUFFER_MASK

   RxTail = tmpTail
   result = RxBuffer(tmpTail)
end function
{
****************************************************************************
* Name    : WaitByte                                                       *
* Purpose : Waits for and returns a byte from the buffer                   *
****************************************************************************
}
public function WaitByte() as byte
   while (DataAvailable() = false)
      clrWDT()
   end while
   result = ReadByte()
end function

end module
example usage:

Code: Select all

#option RX_BUFFER_SIZE = 128
include "UartBuffer.bas"
include "USART.bas"

dim b as byte

USART.SetBaudrate(br115200)

UartBuffer.Initialize()
UartBuffer.Start()

if (UartBuffer.DataAvailable()) then
    // read one byte
    b = UartBuffer.ReadByte()
endif

// get number of bytes in the buffer
b = UartBuffer.GetCount()

// check for overrun. this is a fatal error
if (UartBuffer.Overrun()) then
    UartBuffer.Reset()
endif

Post Reply