High/low priortiy interrupts question

Coding and general discussion relating to the compiler

Moderators: David Barker, Jerry Messina

Post Reply
Gordon_h
Posts: 55
Joined: Fri Apr 06, 2007 8:55 pm
Location: Boulder, Colorado

High/low priortiy interrupts question

Post by Gordon_h » Thu Apr 30, 2009 3:57 am

I have a program that has been using four interrupts, all defined as high priority. All has been working, but I need to tighten up the timing, so I left the timer as high priority, and moved the other three routines to low priority. Now, if any of the low priority interrupts fire, the program hangs. I assume that the low priority ones are getting interrupted by the high priority one, and then something doesn't get save/restored correctly? Any thoughts would be appreciated. The description of save/restore in the help file is a little sketchy- does every variable called during the interrupt need to be listed in the SAVE block? What does listing a variable in this block actually do? Using an EVENT makes it hard to know what variables/registers need to be saved, especially if the event is in another module.

Module MC_HPInts

Const
ipLow = 1,
ipHigh = 2

Include "aSetupUSART.bas"
Include "aISRTXRX.bas"
Include "aTimer.bas"
Include "aPortB.bas"
Include "aPins.bas"

Dim GIE As INTCON.7


'****************************************************************************
'* Name : hiISR *
'* Purpose : High priority interrupt service routine *
'* : Checks enables, flags, and triggers events *
'****************************************************************************
Interrupt hiISR(ipHigh) ' High Priority ISR code
Save(0)

If (Timer1IF=1) And (Timer1IE=1) And (Timer1On=1) Then ' TIMER 1 interrupt code
Timer1 = Timer1 + Word(TimerValue) ' Force integer arithmetic
Dec(TimerCounter)
MC_Timer.FOnTimerEvent()
Timer1IF = 0
EndIf

Restore
End Interrupt

'****************************************************************************
'* Name : loISR *
'* Purpose : low priority interrupt service routine *
'* : Checks enables, flags, and triggers events *
'****************************************************************************
Interrupt loISR(ipLow) ' High Priority ISR code
Save(0,RxBuf)
' So long as none of these interrupts occur, all is well
' If they do, the PIC hangs
' But, if they are moved into hiISR, all works
If (TXIE=1) And (TXIF=1) And (TxBufIsEmpty=false) Then ' USART transmit interrupt
aISRTXRX.FOnTxEvent()
EndIf

If (RCIE=1) And (RCIF=1) Then ' USART receive interrupt code
aISRTXRX.FOnRxEvent()
EndIf

If (INT1IF=1) And (INT1IE=1) Then ' RB1 interrupt code
MC_PortB.FOnInt1Event()
INT1IF=0
EndIf

Restore
End Interrupt

'****************************************************************************
'* Name : Initialize *
'* Purpose : *
'* : *
'****************************************************************************
Public Sub Initialize()
MC_Timer.ActivateTimer()
MC_PortB.ActivateInt1() ' Activate key-sense interrupt (RB1)
aISRTXRX.ActivateISRTXRX() ' Activate interrupt TX/RX module
Enable(hiISR) ' Inable interrupt service routine
Enable(loISR) ' Inable interrupt service routine
End Sub

][/code]

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

Post by Jerry Messina » Thu Apr 30, 2009 9:58 am

Nothing obvious jumps out at me, but then again I haven't even finished my first cup of coffee yet.
Some general things to check are...

- A lot of pic's have an errata for the priority saving mechanism. Try using
#option ISR_SHADOW = false and see if this helps.

- Make sure you're setting the IPRx priority register bits correctly for the
peripherals. By default, they're all hi priority

- Be very careful using enable() and disable() when using multiple intr sources,
as they work on a priority level, not the individual intr. It doesn't
appear that you're using them, but the isr libs included with SF use these,
as well as a lot of example code found here.

- In C terminology, an event is nothing more than a pointer to a function,
and 'invoking' an event is an indirect function call. If your event modifies
something that needs to be saved, then it should be in the save/restore
block as well.

- Congratulations. You're one of the first people I've seen who correctly
check for both IE and IF bits being set to see if a peripheral should be serviced.

Jerry

Gordon_h
Posts: 55
Joined: Fri Apr 06, 2007 8:55 pm
Location: Boulder, Colorado

Post by Gordon_h » Thu Apr 30, 2009 2:32 pm

Jerry,

Many thanks- that was it- setting RCIP=0 and TCIP=0 solved the problem. I am using a 4525, and it seems to work okay with the hardware shadowing. Now on with the rest of the project! :D

Gordon

Gordon_h
Posts: 55
Joined: Fri Apr 06, 2007 8:55 pm
Location: Boulder, Colorado

Post by Gordon_h » Thu Apr 30, 2009 11:45 pm

It is working, but I still do not understand how to tell what to save! In my timer, it is working great until I include the two statements that are commented out below:

Code: Select all

'****************************************************************************
'* Name    : OnTimer()                                                      *
'* Purpose : Event triggered by timer interrupt                             *
'*         : Sets flags which are used in main loop and then reset          *
'*         : Also takes and converts two ADC readings                       *
'****************************************************************************
Public Event OnTimer()
  High(oPGC)                                               ' Scope timing marker
  ADCON0=%0000001                                          ' Select channel 0 (bits 5..3) Read AC line
  Inc(ticks)                                               ' Do code here as timing to allow S/H to charge
  If ticks=20 Then
    Inc(msecUnits)                                         ' Add to count of msec
    msFlag=true                                            ' Set msec flag
    ticks=0
  EndIf
  If msecUnits=10 Then
    Inc(msecTens)                                          ' Add to tens count
    tenmsFlag=true                                         ' Set 10ms flag
    msecUnits=0                                            ' Start counting again
  EndIf
  ADCON0.1=1                                               ' Start conversion
  While ADCON0.1=1
  Wend
  vacRaw.byte1=adresh
  vacRaw.byte0=adresl
'  VPri=VacM*(vacRaw-VacB)>>5                               ' Convert    <<< Causes problems
  ADCON0=%0010001                                          ' Select channel 4 (bits 5..3) Read Vp
  If msecTens=10 Then                                      ' Do code here as timing to allow S/H to charge
    Inc(msecHundreds)                                      ' add to hundreds count
    hundredmsFlag=true                                     ' Set hundred msec flag
    msecTens=0                                             ' Start counting again
  EndIf
  If msecHundreds=10 Then
    Inc(seconds)                                           ' Add to count of seconds
    secFlag=true                                           ' Set 1 second flag
    msecHundreds=0                                         ' start counting again
  EndIf
  ADCON0.1=1                                               ' Start conversion
  While ADCON0.1=1
  Wend                                                     ' 11uS to here
  vpRaw.byte1=adresh
  vpRaw.byte0=adresl                                       ' 11uS to here
'  Vp=(VpM*vpRaw)>>6                                       ' <<< Causes problems
 Low(oPGC)
End Event

What should I be "saving" to make those work correctly?


Gordon_h
Posts: 55
Joined: Fri Apr 06, 2007 8:55 pm
Location: Boulder, Colorado

Post by Gordon_h » Thu Apr 30, 2009 11:49 pm

I just re-read my previous post, and realize I did't describe the problem very well :oops: - the problem that is caused is that the LOW priority interrupts and main loop "hang" when I un-comment the two statements in the above code block. The HIGH priority interrupt continues to run- so it must be that those statements clobber something that is being used in the main loop and/or the LOW priority interrupts- question is, how to tell?

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

Post by Jerry Messina » Fri May 01, 2009 10:42 am

Take a look at the asm listing. Depending on how you've declared the
variables, you'll probably find that the multiply is using PRODH:PRODL
registers. But that is probably the least of your issues.

If the comments are correct, it looks like you've got the timer intr set
to generate 20 ticks/msec, which is a 50us period. In that 50us, you're
trying to handle the timer count code, take 2 ADC readings, and then
scale the adc results. I doubt that there's much (if any) time left in that
timer tick. This is all attached to a high-priority interrupt, which means
that if you're leaving the timer intr enabled, there can't be any real time
left for the low-priority and main loop code to run.

In addition, you'll need to disable the timer intr in the routine that uses
the Vpri and Vp variables, otherwise you'll run into resource access
conflicts between the foreground and background code.

I think you need to take another look at how you've structured things,
and try to get the ADC code out of the timer intr. Also, see if you can't
reduce the timer intr frequency. Trying to handle a 50us tick intr rate
isn't really workable... I usually set mine for 1ms tops, and typically
attach it to the low priority intr. That way, I can have any communication
interfaces run at high priority so there's less chance of communication
failures.

Gordon_h
Posts: 55
Joined: Fri Apr 06, 2007 8:55 pm
Location: Boulder, Colorado

Post by Gordon_h » Fri May 01, 2009 11:37 pm

Yes, PRODH and PRODL turned out to be important! Now it is a time thing- I think all of the interrupt code now works, but I am overall running out processor cycles, and I don't think I should be. Even with my ADC routines in the timer interrupt, the whole thing only takes around 21 usec. Since the timer fires every 50 usec, there should still be 60% of the processor cycles for everything else. It may be one of the low priority interrupts, or maybe something in my main loop. I need to track it down and solve it, if possible. It is weird-looking when the serial port spits out data, it looks like an old 300 baud modem! Since I am running the chip at 40 MHz, even with the 40% used in the interrupt, I should have the equivalent of 24MHz available for other tasks. But, even though my main loop gets in knots and goes off in the weeds, the high priority interrupt keeps running!

The reason for the ADC stuff being in the interrupt code is that I have to turn on some big power devices (IGBTs) as precisely as possible, and just setting a flag and then using that to read the ADC's leaves too much variability. I have tried to optimize in the interrupt routine by doing some of the timer stuff while the ADC sample and hold is charging, rather than using a DelayUS statement. Also I switched to >> for scaling rather than using /, as the divide routine takes way too long. But the while/wend loops waiting for the ADC to convert is a big chunk of the 21 usec still. I don't think it would matter if this was in or out of the interrupt code, though.

I am trying to do it this way because I have hardware that needs minimal modification. If I was doing it from scratch, I could have used off-chip ADCs, and then timing would have been easier.

Thanks for all the help- I have learned a lot. It is the first time I have got two-level interrupts working, so I am well pleased!

Gordon_h
Posts: 55
Joined: Fri Apr 06, 2007 8:55 pm
Location: Boulder, Colorado

Post by Gordon_h » Sat May 02, 2009 1:32 am

One more note that touches on something I haven't seen mentioned before- the overhead associated with an interrupt. I have scope points set up in my code so I can see when the interrupt "steals" time from my main loop, and also how long it then takes to execute the code inside the interrupt. The time "stolen" is 52 usec, but the time for the interrupt code to execute is only 21 usec. So the OVERHEAD to service the interrupt is 31 usec :shock: . So that explains why my system performance has gone south- I have a 50 usec high priority timer interrupt going, and with 21 uSec of interrupt plus 31 uSec of overhead, I have used up apparently more than 100% of my CPU cycles :!: This is why my 115.2kBd serial port looks like a Hayes 300 baud modem.... This is at 40 MHz. With 4 clock cycles per typical instruction, this means that the interrupt overhead is something like 310 instructions. This seems terribly high, but the measurements seem accurate after checking several ways. It would seem to be better to simply poll the interrupt flag bit, a bit like the pseudo-interrupt approach in Picbasic but that does not allow you to have prioritized interrupts. I may be well and truly up a gum pole on this one :(

Gordon_h
Posts: 55
Joined: Fri Apr 06, 2007 8:55 pm
Location: Boulder, Colorado

Post by Gordon_h » Sat May 02, 2009 3:13 am

This may be the last post on this thread- examining the ASM file, there are about 16 instructions to get into the interrupt, and a similar number to leave. So the discrepancy is huge- it *should* take only 3.2 uSec of overhead to service the interrupt- but it is actually taking around 31 uSec, an order of magnitude more :?

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

Post by Jerry Messina » Sat May 02, 2009 9:14 am

If I remember correctly, the save(0)/restore block takes about 160 cycles to execute.

Gordon_h
Posts: 55
Joined: Fri Apr 06, 2007 8:55 pm
Location: Boulder, Colorado

Post by Gordon_h » Sat May 02, 2009 5:42 pm

I imagine the exact amount would depend on how many items are being saved? If so, that would account for most of the missing time.

Gordon_h
Posts: 55
Joined: Fri Apr 06, 2007 8:55 pm
Location: Boulder, Colorado

Post by Gordon_h » Sat May 02, 2009 6:39 pm

Jerry,

Okay- that was it. Moving my scope timing points to before the save and after the restore statements (they were "inside" before) showed that you were correct- the SAVE(0) statement takes about 30 usec. Commenting out the save/restore showed that my interrupt can execute in around 22 uSec, which is okay. Of course, that nuked my main loop. I eventually settled on SAVE(PRODL,PRODH), which seems to allow things to work, apparently saving 0 is not needed in my case. Serial port now runs at a decent speed on low priority interrupt. If it all tests out thoroughly, I may have a workable solution. I guess the SAVE(0) function is like another issue at the moment, it should be safe, available and rare....

I have one more question, is there overhead associated with an EVENT, like there is with a subroutine call? I guess examination of the ASM file might reveal that. If there is, I will move all my code inside the ISR and forgo the structural nicety of an event.

Again, thanks for the pointers- you have saved me days, possibly weeks of coming up the learning curve. I really like this forum. :D

Gordon_h
Posts: 55
Joined: Fri Apr 06, 2007 8:55 pm
Location: Boulder, Colorado

Post by Gordon_h » Sat May 02, 2009 7:57 pm

Last note- the overhead for an event is 1.4 usec at 40 MHz, so in extreme timing crunches, avoid events.

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

Post by Jerry Messina » Sat May 02, 2009 10:02 pm

Glad to hear things are working out for you.

I try and avoid save(0) if I can get away with it (and if you're really
careful in what you do in the intr handler, you usually can).

As far as events go, as I said before, events are an indirect function call.
With the pic architecture, that amount of overhead isn't all that bad, really.

Jerry

Post Reply