Implementing an Interrupt Service Routine in C on the PSoC1

Question: How do I implement an interrupt service routine in C on the PSoC1?




C interrupt service routines can be implemented in the PSoC1 either by manually saving and restoring the CPU context and adding an lcall within the assembly ISR, or by making an interrupt declaration in C and adding an ljmp to the assembly language file. This article explains both approaches. 


Manually Saving and Restoring CPU Context


There are two assembly macros defined in that save and restore the CPU context - PRESERVE_CPU_CONTEXT andRESTORE_CPU_CONTEXT.


These assembly macros manually preserve and restore the A register and all of the virtual registers that could possibly be used by the compiler. They do not preserve and restore the X register, however X is preserved automatically by the compiler within any function call.


To call a C interrupt service routine named foo from an assembly interrupt handler, the usage is as follows:



lcall _foo




The benefit of this is that no additional declaration is necessary for the C function. Also, the program control will return to the assembly interrupt handler so that assembly instructions can be run after the C ISR returns. The drawback of this method is that the macro preserves and restores all the virtual registers, whether or not they are used by the C function. This results in extra instruction cycles and flash overhead for the ISR.


C ISR Declaration


The Imagecraft compiler uses the #pragma interrupt_handler directive. This is documented in section 6.6 of the C Language Users Guide. The #pragma interrupt_handler will tell the C-compiler to preserve/restore any registers used by the C routine and use an interrupt return at the end of execution.


The Imagecraft usage is as follows for an interrupt named foo:


#pragma interrupt_handler foo

void foo(void)

//Place interrupt service code here



Make sure you are using the correct name in your #pragma statement, since the Imagecraft compiler will not give you a warning if you make a mistake with the name used in the #pragma (and your ISR will not work properly).


 Assembly LJMP


You will also need to place an ljmp _foo either in the interrupt vector inside boot.tpl or inside the user module generated assembly source file for interrupts (usually int.asm, or psocgpioint.asm for GPIO interrupts). If there is a user module generated interrupt assembly file, you will see an ljmp already in boot.asm which jumps to the ISR function in the assembly file. Find this ISR function and insert your ljmp to the C file within the function. Note that this ljmp should be added between the custom code banners in the assembly ISR function. Below is an example using the PSoC GPIO interrupt (in psocgpioint.asm):




;@PSoC_UserCode_BODY@ (Do not change this line.)
; Insert your custom code below this banner

 ljmp _foo

; Insert your custom code above this banner
;@PSoC_UserCode_END@ (Do not change this line.)



Additional Pitfalls - No Return to Assembly


Note that when you ljmp to a properly declared C ISR, the compiler inserts a RETI at the end of the C routine. Therefore, program execution does not return to the assembly file. Some PSoC assembly ISRs have code underneath the banner that needs to run, therefore you have to be careful and inspect the assembly in the ISR after your inserted ljmp to avoid problems. Even a single POP instruction in the generated assembly code after your inserted ljmp can cause issues, since if the POP doesn't run there could eventually be a stack overflow. The code below lays out an advanced method by which you can call a C ISR and have the code execution return to the assembly ISR. You can also use this method to enable a lower priority interrupt during the execution of a C ISR - however, you must use caution when doing this. Note that an alternative to this would be to use the PRESERVE_CPU_CONTEXT andRESTORE_CPU_CONTEXT macros as outlined above, however this advanced method could result in fewer instruction cycles and a smaller flash size.


             ;This routine is intended to call a C ISR and return execution to this code

             ; It works by manipulating the stack so that the compiler generated

; RETI returns here.  We also supply the value for the flags register

; that is restored by the RETI. A RETI expects the stack to contain the

; flags value to restore and the program counter location to return.

;NOTE: This code introduces a stack overhead of four bytes (in addition to the
; stack space used by the C ISR)


push A          ;preserve A since we are using it

call .callstub  ;puts the two byte PC on the stack

jmp .continue   ;flow returns here after C ISR RETI


and F, 0        ;C interrupt handler expects flags to be clear

mov A, 0   

push A          ;Push Flags register value that will be popped by generated reti (in this
                 ; case, 0).

                ; If your asm ISR routine uses a page mode other than 0,                

                ; you would push the appropriate value onto the stack instead

                ; of 0. Note that this value also has an effect on global                

                ; interrupt enable/disable upon return

ljmp _foo      ;ljmp to the C ISR. RETI will return

                ; control to a jmp to the next line

pop A          ;Restore A