7.3 Manual Context Switch

If your device does not automatically save the state of core registers to shadow registers when an interrupt occurs, or if you need to preserve registers or objects in addition to those saved by hardware, the following steps need to be taken.
  • Define sufficient storage in RAM for the registers that must be saved
  • Have the ISR copy the contents of the relevant registers to the storage area before the registers are modified
  • Have the ISR restore the contents of the registers from the storage area after those registers no longer need to be used

The following code snippet is written for a Mid-range device that does not automatically save context when an interrupt occurs, such as a PIC12F609. The code assumes the device implements common memory. This code illustrates the above points and is described in the following paragraphs. Adapt this code to suite your device and the registers you wish to preserve.

#include <xc.inc>

PSECT udata_shr
saved_WREG:
    DS      1               ;space for WREG in common memory

PSECT udata_bank0
mainSaveArea:               ;space for other registers in banked memory
saved_STATUS:
    DS      1
saved_PCLATH:
    DS      1

PSECT isrVec,class=CODE,delta=2
isr:
    ;save context
    movwf   saved_WREG      ;WREG saved to common memory
    movf    STATUS,w        ;STATUS (bank selection bits) copied to WREG...
    BANKSEL mainSaveArea    ;...allowing a different bank to be selected...
    movwf   saved_STATUS    ;STATUS saved
    movf    PCLATH,w
    movwf   saved_PCLATH    ;PCLATH saved

;interrupt code that can use STATUS, WREG, PCLATH goes here

exitISR:
    ;restore context
    BANKSEL mainSaveArea
    movf    saved_PCLATH,w
    movwf   PCLATH          ;PCLATH restored
    movf    saved_STATUS,w
    movwf   STATUS          ;STATUS restored
    swapf   saved_WREG,f
    swapf   saved_WREG,w    ;WREG restored without altering STATUS

    retfie

Space must be allocated for all registers that are to be preserved. This space does not need to be contiguous or even in the same bank. Storing context in common memory or in the PIC18 Access bank makes the context switching code much easier, but there might not be enough common memory for all the registers, nor might you wish to use this precious memory resource solely for interrupts.

In this example, one byte has been allocated (using the DS directive) in common memory (udata_shr psect) to hold the state of the W register, and memory was allocated in a block of bank 0 memory (udata_bank0 psect) to store the state of the STATUS and PCLATH registers. This arrangement simplifies the context switch code, as you will see in the following discussion, but does not use much common memory. You may, as was done in this example, define a label with each byte of storage so that each byte can be uniquely referenced, or you may simply create one label and use an offset from that label to access each byte in that memory block. So, for example, mainSaveArea + 1 and saved_PCLATH both represent the same memory location.

The code at the beginning of the ISR copies the registers to the allocated storage. You must ensure that this code does not overwrite any register that must be preserved until the content of that register has been saved. This might mean that the registers need to be saved in a particular order.

The first instruction in the above example moves the current content of the W register into the saved_WREG object. Because the storage for this object was allocated in common memory, the bank of this memory does not need to be selected. This is important because on Mid-range devices, the bank selection bits are in the STATUS register, which is one of the registers that must be preserved. A new bank cannot be selected without clobbering the STATUS register; similarly, the STATUS register cannot be saved first because that code would clobber the W register.

With the W register saved, the subsequent instruction can then load the W register with the content of the STATUS register. And with the STATUS register now copied (although not yet stored) the bank of the main storage block can be selected using the BANKSEL directive. The subsequent code can then use regular movf/movwf instructions to save the remaining registers.

The restoration code at the end of the ISR typically restores the registers in the reverse order to that in which they were saved. You must ensure that once a register has been restored, no subsequent code writes to that register, so again, these operations need to be performed in a strict order and may not be able to use some instructions.

The restoration code in this example first selects the bank of the main allocated space and copies the saved values back to the PCLATH and STATUS registers using movf/movwf instructions. Remember that with the STATUS register restored, the bank currently selected also returns to that in effect when the interrupt occurred. As the content of the remaining register to be restored in this example was stored in common memory, this is of no concern.

Restoring the W register is more difficult. The instruction, movf saved_WREG,w, can't be used because it affects the Zero bit in the STATUS register, which has already been restored. Instead, the example code restores the W register using two swapf instructions, which do not affect the STATUS register.