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!