Interrupts, Interrupts, and more Interrupts!

There seems to be a lot of questions as well as misinformation about PIC18F interrupts and the robot code/devices. I thought a separate thread to ask more general questions would be worthwhile.

The purpose of this thread is share technical information at a detail level that most team members and even programmers won’t care about. That’s because building a solid device interrupt service routine only needs to be crafted once and then used multiple times. So if you’re one of those crazies that want to understand how to craft interrupt service routines then welcome and have at it!

The are a couple documents that serve well for information:

DS31008A 33023a.pdf PICmicro™Mid-Range MCU Family Reference Manual
DS39646B 39646b.pdf PIC18F8722 Family Data Sheet
DS00097D MPLAB® C18 C COMPILER USER’S GUIDE

These documents, and more, are available from the Microchip website. For example, there are chip errata sheets that explain specific flaws that appear in specific chip models and how to get around them. There may be newer versions of the above, but these are the ones I currently have downloaded and use for references.

To answer a question, PLEASE DONT GUESS! Look it up and post the answer and reference if there is one.

For example, someone asked about interrupt latency in another thread. The chip latency is defined in DS31008A above as:

Section 8.3
:
Interrupt latency is defined as the time from the interrupt event (the interrupt flag bit gets set) to the time that the instruction at *<the interrupt vector address> * starts execution (when that interrupt is enabled).

DS31008A-page 8-10

The interrupt latency is 3-4 instruction cycles in all cases, or 0.3-0.4 usec. The lower 0x7FF address range is protected by the configuration bits of the chip within the IFI controller as that is where the boot loader and other code resides. So there is a jump from the standard high/low priority vectors up to relocated vectors at 0x808 and 0x818. The standard IFI low priority interrupt handler starts with

#pragma code InterruptVectorReset = LOW_INT_VECTOR
void InterruptVectorLow (void)
{
  _asm
    goto InterruptHandlerLow  /*jump to interrupt routine*/
  _endasm
}

This IFI code just jumps to the real start of the service routine. So taking into account the chip latency of 3-4 instruction cycles and two GOTO instructions which are 2 cycles each, the user supplied interrupt handler will take 0.7-0.8usec before it starts execution.

If curious, you an see some of this information in the attached image in MPLAB’s View->Program Memory window.





Will too many interupts will cause the red-light-of-death?

It is possible, but is just as easily avoided.

One of the other common misconceptions is that having too high of an interrupt load will starve off user code from executing and result in the red-light-of-death. This is true of the standard IFI code framework but not true of either MBLAB/WPILIB or EasyC environments. Why? Because in these frameworks the Getdata/Putdata calls are performed at interrupt level and not at user level… so as long an one interrupt service routine doesn’t take forever to run, the appropriate get/put data will occur and prevent bad things from happening by doing all this in the background. Its also why the main loops look kinda of funky in that they loop forever and don’t care how long they take to run. But I’ll tackle that one in another reply.

	
<within the system clock routine, do...>

             if (statusflag.NEW_SPI_DATA != 0)
	{
                    Getdata(&rxdata);
                    Putdata(&txdata);
             }

The high level priority interrupt in the IFI user processor is using the serial SPI bus between the master and itself for exchanging data. This is done by having a double buffer used by the interrupt service routine. The serial transfer of incoming/outgoing data is done simultaneously (read info on the SPI, Section 19.0 in the DS39646B). The Getdata/Putdata just read the new data into the rxdata which the rest of the user code then references for current OI info and writes the current txdata structure that the user has been filling with information to the “open”/next buffer that will be sent once the current buffer has been exchanged with the master processor. The code is available in ifi_library.c in the default IFI project (its not in the list of source files by default, but is in the source directory).

Anyway, doing these operations in the background at interrupt time frees the user code from having to worry about taking too long between updates causing the red-light-of-death.

How can the autonomous routine loop and loop and loop and never
(apparently) return and still work under MPLAB/WPILIB or EasyC?

I’ve done something similar before. Again, in the system clock routine check to determine when autonomous mode is ending (I was in autonomous last clock tick, but this time I’m not – so autonomous period just ended). When you detect this, reset the variable stack managed by the C compiler and reset the hardware stack location (STKPTR) to point to the return to main().

For example, in the standard IFI code if:
_entry (reset) jumps to _startup
_startup then calls main()
main() then calls Autonomous()


main()
{
    Autonomous();
    Operator();
}
Autonomous()
{
     while(1)
     {
         // user code here...
     }
}

The call stack will always look like:


 0000
 0001  _startup+n  <- main's return will pop and use this
 0002  _main+n     <- autonomous' return will pop and use this

Slamming the stack pointer and frame pointer managed by the C compiler back to their defaults and then changing the STKPTR index to 2 and then returning normally from the interrupt service routine will make it appear as if the Autonomous() routine just returned to main() on its own. Instead, the interrupt service routine hijacked the stack and did it for us…

Yeah, we could do the same thing between iterations through Autonomous(), but this way again we don’t really care how long the code path through autonomous is, it will get yanked immediately into the Operator() code when autonomous ends.

ghuy’cha’, that’s scary! There should be a prominent label on easyC to warn about this behavior. I can imagine a couple of ways it could break a program if I didn’t know to watch out for it. It certainly violates the principle of least surprise.

If I hadn’t already decided that easyC wasn’t for me, this alone would make me worry about what other “gotchas” were lurking behind the pretty face.

It might be nice to warn people trying this that all their operations on the data received from GetData() and sent to PutData() need to be atomic. I realize the data in the structs are represented as as unsigned chars, and thus accessing them is atomic, but it’s pretty easy to get lazy and use them to store temporary results of calculations. The Default_Routine in the default code does this several times, and it’s asking for trouble if you continue this practice with this style of communication servicing.

I agree. If you are hiding when txdata gets sent, then you hide the whole structure behind SendPWM() and similar calls that interface to it. That way your not tempted to see it as a handy temporary data storage location.

Just a paradigm shift of how you think about the data structure or not since it gets hidden away.

Bud,

Since you can do a better job of explaining these things than I can, how about you discuss why C18 3.0+ does a much better job of handling interrrupts than 2.4 does?

-Kevin

In what way? The ability to use pragma to avoid some context save/restore? Or something else?

Yeah, that’s a cool feature, but what I really like is the ability to specify a different tmpdata area for ISRs.

-Kevin

That was the specific feature I was thinking about. I don’t like the fact that all of the compiler managed resources are saved unless you do a nosave to exclude them… kinda of the opposite of 2.4 where you had to specify them in order to save them. I also really wish there was a “use a return instead of a RETIE” option - that would really help. But I do like that a different .tmpdata section is created for the interrupt service routines.

I’ll give this some thought, because it gets confusing since it deals with SFRs of the PIC and how the compiler uses them (and when).

Honestly, other than reading the docs I haven’t used the 3.0+ compiler yet. I’m hoping to get it installed on my laptop this weekend.

I have played a lot of “games” with context in V2.4. One is to setup the main interrupt service routine with minimal context save: just WREG, BSR, STATUS, FSR0 and PRODL/H. The FSR0 is used by the compiler to hold computed ram addresses - like for data_array[var]. The PRODH/L registers can be used for bit shifts, some data array index calculations and some other general stuff. This covers 80%-90% of the interrupt dispatcher and drivers.

For any driver that needs more than these, I just declare that driver as an interrupt service routine and tell it what additional data or sections or h/w registers to save. But the downside was it would always re-save WREG, BSR, and STATUS on me. With V3.0 I can now avoid that resave. And yeah, I had to play some more games to get this to work (the first RETIE turns interrupts back on). But it can be a big win in that the majority of light weight drivers don’t pay the price of saving a large context that only a few certain drivers needed. The new .tmpdata actually makes this strategy no longer as easy to do (there are always ways - just not as easy anymore).

Anyway, context save/restores is a longish post but will try. I’ll start off with what the C18 manual says about latency. Latency more than just speed is what matters in real-time systems. The latency of an interrupt service routine is broken down into the following chunks:

  1. Processor redirecting execution to the interrupt vector (.3-.4usec)
  2. Interrupt vector execution (.4 usec)
  3. Interrupt service routine preamble (context save, variable)
  4. Interrupt dispatch (locate the interrupt to be serviced) (~.2-.3 usec per interrupt to be checked)
  5. “Driver” for that interrupt (varies, but usually <10usec)
  6. Interrupt service routine epilogue (context save)
  7. RETIE, return and turn interrupts back on

More later. I’ll need to find some specific examples of C lines using the different compiler managed resources to show why they need to be saved. Sometimes its not obvious what gets used.

How do functions return values and do I need to be aware of them in interrupt routines?

From the C18 compiler manual:

3.2.3 Return Values
The location of the return value is dependent on the size of the return value. Table 3-2
details the location of the return value based on its size.

TABLE 3-2: RETURN VALUES
Note 1: Locations reserved for use by the compiler.
Return  Return
Value   Value
Size    Location
------- --------------------------------------------
 8 bits WREG
16 bits PRODH:PRODL
24 bits (AARGB2+2):(AARGB2+1):AARGB2(1)  	   (MATH_DATA section)
32 bits (AARGB3+3):(AARGB3+2):(AARGB3+1):AARGB3(1) (MATH_DATA section)
> 32 bits On the stack, and FSR0 points to the return value

And yes you do have to worry about them in interrupt routines. If you
call any routine, for example, that returns a long then you now need
to save/restore the MATH_DATA section. If the routine is one of your
own, then creating one just for use by the interrupt routine that passes
back the long via a global would eliminate the need to save the MATH_DATA
section.

What are compiler managed resources and do I save them in interrupt routines?

There is only one register within the PIC’s CPU - the working register: WREG.
Almost all instructions need to move data into the WREG before operating on it.
Since there are intermediate results that may need to be calculated within a
C source line before arriving at the final results for the user, the compiler
uses a number of different resources within the chip. The compiler “owns” these
resources and uses them as needed - both within user code and interrupt routines.

The result is the user could be in the middle of computing the array index for:

data = sample[ndx];

when an interrupt occurs. This line could be using the FSR0 register, PROD register,
and .tmpdata section. The interrupt service routine if it too uses any of the
compiler managed resources MUST save them. Otherwise very bad things happen. Bad things
would not necessarily happen immediately or all the time mind you… but they would
eventually happen.

If your interrupt routine “forgot” to save PCLAT for example
and used a function callback based in ram (*callback_function)(). Then most of the
time the robot would run fine as PCLAT isn’t used too often by the compiler and you
might have only one other place in your user code that has C code that results in the
compiler using this register. Your robot will run most of the time
just fine but occasionally flakes out and wierd stuff happens. Thats because eventually
the code associated with that one line of C in user code was in the middle of processing, had loaded part
of the PCLAT register set and then got interrupted. When the processor returned back
to continue running the user code, the PCLAT no longer had YOUR data, it had some
left over junk from the interrupt service routine. Weird stuff follows.

So, what is the full list of compiler manager resources?

From the C18 manual;

3.4 COMPILER-MANAGED RESOURCES
Certain special function registers and data sections of the PIC18 PICmicro
microcontrollers are used by MPLAB C18 and are not available for general purpose
user code. Table 3-4 indicates each of these resources and their primary use by the
compiler. All compiler-managed resources are automatically preserved across an ISR.
TABLE 3-4: COMPILER-MANAGED RESOURCES
Compiler-
Managed 
Resource 	Primary Use(s)                                             my notes
PC 		Execution control				Saved on h/w stack
WREG		Intermediate calculations			must be saved
STATUS 		Calculation results				must be saved
BSR 		Bank selection					must be saved
PROD 		Multiplication results, 			likely needs to be saved
		return values, intermediate calculations
.tmpdata(1) 	Intermediate calculations			maybe needs to be saved
FSR0 		Pointers to RAM					likely needs to be saved
FSR1 		Stack Pointer					its used and doesn't need to be saved
FSR2 		Frame Pointer					automatically saved by compiler call framework
TBLPTR(2) 	Accessing values in program memory		maybe
TABLAT 		Accessing values in program memory		maybe
PCLATH 		Function pointer invocation			not likely, but could if (*function)() calls made
PCLATU(3) 	Function pointer invocation
MATH_DATA 	Arguments, return values and 			possible if math used or certain return size used
		temporary locations for math library functions

How do I know which of these registers I’ve used in my interrupt routine or device driver?

Good question. Some are pretty obvious, others not so obvious. I think that is why the V3.0+
compiler now saves them ALL unless you exclude them with the nosave= option.

For example, if you call a routine that returns a long anywhere in the interrupt service routine
or from any of the drivers it calls, you just used the MATH_DATA section and need to save it!

Called an routine that returns an unsigned int? Add PRODH/L to the context save list.

Did any array calculations using a variable array index like data[var] = 0? You just used
FSR0 and possibly PROD which is often used to calculate the array offset. Did any
array manipulation like in the adc driver? Add the .tmpdata section as it is used for
intermediate results. Add these to the list.

And so on. The easiest way to figure out whats been used is to open up the View->Program Memory
window in MPLAB. Search for your interrupt routine that you are interested in and look at the
assembler – it lists most of the symbols in the code listing. See any __tmp_0? You just
used .tmpdata section. See the attached figures for an example. This is the only sure fire
method I know of to figure out definitively what got used.

This may seem extreme, but the driver code of MOST drivers is fairly simple and straight forward.
You only end up looking at a small chunk of code. Don’t want to do that? Enlist some help from
a mentor - or just break down and save everything as a last resort. This last resort works
particularly well if the 'bot is flaking out and you suspect the interrupt service routine.
If saving all the compiler managed resources still doesn’t fix the problem, then there is
likely a logic bump like non-atomic access to a multibyte variable within user code.

How much time does it take to save context anyway?

Saving a register takes .4usec. There is a 2-cycle MOVFF <SFR>, PREINC1 instruction that pushes
the specified special funcion register onto the stack (FSR1 is ths stack). A similar instruction
is required in the context restore part of the routine. Not much to worry about here.

Saving a data section takes a lot longer. You can find out how big a data section is and which
variables are in it by looking at the .map file from your project. The .tmpdata is 12 bytes long
and MATH_DATA is 20 bytes long. The compiler generates a loop to save/restore data sections.
Saving .tmpdata takes 15.6usec and 25.2usec for MATH_DATA.
Since I’ve an interrupt service routine that takes a maximum of ~12-15usec to run with a majority of
device drivers (thats start to finish), adding an additional 40.8usec to save/restore sections
really hurts! With the V3.0+ compiler, a separate .tmpdata section is created for each
routine declared as an interrupt. The section name is <routine_name>_tmp. That just saved us
15.6usec - our interrupt service routine just had a dramatic increase in speed!

If I’m running a couple encoders, adc, serial port and have say 5000 interrupts per second, I just
returned 78ms/second of processor time to be used by the user. Almost 10% more available
time!

Any routine that the interrupt service routine calls needs to use the same temp data section
as the interrupt service routine. This requires a bit of coordination between the interrupt
dispatcher and driver writers. If you didn’t do this, then the driver would resort to using
the .tmpdata section … which is being used by user code … and we’d have to be saving
.tmpdata again.

I’m using a complicated driver that makes lots of calls and I need to save all
compiler managed resources. Is there a way around this?

If you have this question or even thought it, then you want the answer to be “yes”.

The answer is yes & no. You definitely need to save the full context when running
this driver, but the main interrupt dispatch routine and other drivers don’t
necessarily need to save all context. There are several ways of doing this…
and it sounds like a great exercise for programmers to do some brainstorming!

I’ve done this at least three different ways - none of them I really like, but
the last one I came up with is the least offensive. It also has the advantage
of being faster than the code the compiler generates. I just wish I could convince
the compiler to generate a RETURN instead of a RETIE in some cases, it would
so simplify things!







All rx/tx data fields are byte sized and therefore access to each field is atomic by default.

Right, but the entire data structure is not. Look at how the IFI default code works – it treats some of the PWM outputs as temporary locations before finishing the single-joystick mixing. This works fine when the get/put is done explicitly. If you have a need to coordinate values, you have to consider the possibility that the EasyC magic communication might happen when you’ve started setting them but haven’t completed setting them all.

New question related to interrupts. I use EasyCv2 for Vex and would like to know how to read the values of the 6 interrupt ports (similar to the way that you use GetDigitalInput(I) to read one of the digital input ports). Basically, I want to create something similar to the FVC/FTC competition template but not be limited by the 20 second/2 minute demarcations of the template the way it currently exists.

I keep forgetting about that. Doing stuff like that is just so foriegn to me, that is the rxdata as recieved data is “read only” in my mind.