Electronic – How to tell Microchip’s XC8 compiler that I am using a custom interrupt handler? It tries to place other code at address 4!

assemblyisrmplabxpic

I'm developing firmware for the Microchip PIC16F882 microcontroller using their MPLAB X IDE and the free version of their XC8 C compiler. I'm running into a small problem when trying to add a customized interrupt handler.

Because the free version of the compiler is unreasonably incompetent, I'm writing most of the interrupt handler in assembly, so that I can squeeze out the last few cycles. This works very well so far, but now I want to remove the last bit of overhead: the built-in interrupt wrapper normally placed at address 4 (this PIC has a fixed interrupt vector).

The wrapper is designed to handle generic C functions, so it spends some effort and memory on saving data and registers I do not use.

The manual is not obvious here, and I've tried two things:

Absolute Psects

MPLAB XC8 is able to determine the address bounds of absolute psects and uses this information to ensure that the code produced from C source by the code generator does not use memory required by the assembly code. The code generator will reserve any memory used by the assembly code prior to compiling C source.

I modified their example to place my interrupt handler on address 4:

PSECT my_isr,class=CODE,delta=2,space=0,abs,ovrld
ORG 4
my_isr:
    ...

-P spec

Using a custom linker flag I can place a section where I want. Instead of messing with the ORG 4 directive I added this to the linker flags:

-Pmy_isr=4

The problem: Backup reset condition flags

Both of these correctly places the code at address 4. Problem solved!

…not so fast…

Normally, the first instruction placed at address 0 by the compiler and linker is a GOTO to the initialization routine. Then, if there's no interrupt handler, it places "random" things there, apparently it likes to continue with tables and such.

If an interrupt handler is defined in C, the first instruction is still a GOTO. Then at address 4 is the interrupt wrapper code, which eventually jumps to your interrupt handler. This is completely as expected.

The problem is with this little button called Backup reset condition flags:

enter image description here

Preserve Power-down and Time-out STATUS bits at start up (PIC10/12/16 only).

When I select that, the compiler inserts code at address 0 that saves the relevant bits from the STATUS register before jumping to the initialization code:

0000 0000 NOP
0001 0183 CLRF  STATUS
0002 0803 MOVF  STATUS,w
0003 00A0 MOVWF ___resetbits
0004 2919 GOTO  __initialization

Note that the code ends at address 4, inclusive.

If I add an interrupt handler in C, the compiler knows that address 4 is forbidden, and instead replaces the above code with a NOP and a GOTO:

0000 0000 NOP
0001 2810 GOTO  0x0010
...
0010 0183 CLRF  STATUS
0011 0803 MOVF  STATUS,w
0012 00A0 MOVWF ___resetbits
0013 2919 GOTO  __initialization

But if I implement my own interrupt handler as described above, while enabling the option to save the status register, the compiler generates code at address 0 to 4, which my code then promptly overwrites, leading to a garbled mess that won't boot:

0000 0000 NOP
0001 0183 CLRF  STATUS
0002 0803 MOVF  STATUS,w
0003 00A0 MOVWF ___resetbits
0004 00F0 MOVWF 0x70         ; Oops! Should've been a GOTO!
0005 0E03 SWAPF  STATUS,w
...

I've tried using the GLOBAL directive to make sure that my handler is seen by my main.c, but that only results in an overlap warning, not an actual solution to the problem.

How do I tell the compiler that I have a custom interrupt handler?

Best Answer

intentry

The solution is called intentry, and is the name of a compiler-generated section. The manual reads:

intentry – contains the entry code for the interrupt service routine which is linked to the interrupt vector. This code saves the necessary registers and jumps to the main interrupt code in the case of mid-range devices; for enhanced mid-range devices this psect will contain the interrupt function body. This psect must be linked at the interrupt vector.

This does not sound very inviting, and it is not obvious that you can place your own code in that section. However, if you do, the linker is already set up to place it at the right address, and most importantly, it knows that it must not place other code there.

To use it, write your code like this:

; This is the main interrupt handler. It is placed at address 4.
PSECT intentry,class=CODE,delta=2,space=0
    movwf   isr_temp_w
    swapf   STATUS,w
    clrf    STATUS           ; Switch to bank 0
    movwf   isr_temp_status
    ...
    swapf   isr_temp_status,w
    movwf   STATUS
    swapf   isr_temp_w,f
    swapf   isr_temp_w,w
    retfie

Simple! The linker takes care of the rest. With or without the Backup reset condition flags option, the startup code now avoids the custom handler:

I'm not sure this is the right solution, nor that it is the only solution, but it is one solution.