Interrupt functions for Arm7/9/11, Cortex-A, and Cortex-R devices
The IAR C/C++ Compiler for Arm provides the following primitives related to writing interrupt functions for Arm7/9/11, Cortex-A, and Cortex-R devices:
The extended keywords:
__irq,__fiq,__nested,The intrinsic functions:
__enable_interrupt,__disable_interrupt,__get_interrupt_state,__set_interrupt_state
Note
Cortex-M has a different interrupt mechanism than other Arm devices, and for these devices a different set of primitives is available. For more information, see Interrupt functions for Cortex-M devices.
Interrupt functions
In embedded systems, using interrupts is a method for handling external events immediately; for example, detecting that a button was pressed.
Interrupt service routines
In general, when an interrupt occurs in the code, the core immediately stops executing the code it runs, and starts executing an interrupt routine instead. It is important that the environment of the interrupted function is restored after the interrupt is handled—this includes the values of processor registers and the processor status register. This makes it possible to continue the execution of the original code after the code that handled the interrupt was executed.
The compiler supports interrupts, software interrupts, and fast interrupts. For each interrupt type, an interrupt routine can be written.
All interrupt functions must be compiled in Arm mode—if you are using Thumb mode, use the __arm extended keyword or the #pragma type_attribute=__arm directive to override the default behavior. This is not applicable for Cortex-M devices.
Interrupt vectors and the interrupt vector table
Each interrupt routine is associated with a vector address/instruction in the exception vector table, which is specified in the Arm cores documentation. The interrupt vector is the address in the exception vector table. For the Arm cores, the exception vector table starts at address 0x0.
By default, the vector table is populated with a default interrupt handler which loops indefinitely. For each interrupt source that has no explicit interrupt service routine, the default interrupt handler will be called. If you write your own service routine for a specific vector, that routine will override the default interrupt handler.
Defining an interrupt function—an example
To define an interrupt function, the __irq or the __fiq keyword can be used. For example:
__irq __arm void IRQ_Handler(void)
{
/* Do something */
}For more information about the interrupt vector table, see the Arm cores documentation.
Note
An interrupt function must have the return type void, and it cannot specify any parameters.
Interrupt and C++ member functions
Only static member functions can be interrupt functions. When a non-static member function is called, it must be applied to an object. When an interrupt occurs and the interrupt function is called, there is no object available to apply the member function to.
Installing exception functions
All interrupt functions and software interrupt handlers must be installed in the vector table. This is done in assembler language in the system startup file cstartup.s.
The default implementation of the Arm exception vector table in the standard runtime library jumps to predefined functions that implement an infinite loop. Any exception that occurs for an event not handled by your application will therefore be caught in the infinite loop (B. ).
The predefined functions are defined as weak symbols. A weak symbol is only included by the linker as long as no duplicate symbol is found. If another symbol is defined with the same name, it will take precedence. Your application can therefore simply define its own exception function by just defining it using the correct name.
These exception function names are defined in cstartup.s and referred to by the library exception vector code:
Undefined_Handler SVC_Handler Prefetch_Handler Abort_Handler IRQ_Handler FIQ_Handler
To implement your own exception handler, define a function using the appropriate exception function name from the list above.
For example, to add an interrupt function in C, it is sufficient to define an interrupt function named IRQ_Handler:
__irq __arm void IRQ_Handler()
{
}An interrupt function must have C linkage, read more in Calling convention.
If you use C++, an interrupt function could look, for example, like this:
extern "C"
{
__irq __arm void IRQ_Handler(void);
}
__irq __arm void IRQ_Handler(void)
{
}No other changes are needed.
Nested interrupts
Interrupts are automatically disabled by the Arm core prior to entering an interrupt handler. If an interrupt handler re-enables interrupts, calls functions, and another interrupt occurs, then the return address of the interrupted function—stored in LR—is overwritten when the second IRQ is taken. In addition, the contents of SPSR will be destroyed when the second interrupt occurs. The __irq keyword itself does not save and restore LR and SPSR. To make an interrupt handler perform the necessary steps needed when handling nested interrupts, the keyword __nested must be used in addition to __irq. The function prolog—function entrance sequence—that the compiler generates for nested interrupt handlers will switch from IRQ mode to system mode. Make sure that both the IRQ stack and system stack is set up. If you use the default cstartup.s file, both stacks are correctly set up.
Compiler-generated interrupt handlers that allow nested interrupts are supported for IRQ interrupts only. The FIQ interrupts are designed to be serviced quickly, which in most cases mean that the overhead of nested interrupts would be too high.
This example shows how to use nested interrupts with the Arm vectored interrupt controller (VIC):
__irq __nested __arm void interrupt_handler(void)
{
void (*interrupt_task)();
unsigned int vector;
/* Get interrupt vector. */
vector = VICVectAddr;
interrupt_task = (void(*)()) vector;
/* Allow other IRQ interrupts to be serviced. */
__enable_interrupt();
/* Execute the task associated with this interrupt. */
(*interrupt_task)();
}Note
The __nested keyword requires the processor mode to be in either User or System mode.
Software interrupts
Software interrupt functions are slightly more complex than other interrupt functions, in the way that they need a software interrupt handler (a dispatcher), are invoked (called) from running application software, and that they accept arguments and have return values. The mechanisms for calling a software interrupt function and how the software interrupt handler dispatches the call to the actual software interrupt function is described here.
Calling a software interrupt function
To call a software interrupt function from your application source code, the assembler instruction SVC #immed is used, where immed is an integer value that is referred to as the software interrupt number—or svc_number. The compiler provides an easy way to implicitly generate this instruction from C/C++ source code, by using the __svc keyword and the #pragma svc_number directive when declaring the function.
An __svc function can, for example, be declared like this:
#pragma svc_number=0x23 __svc int svc_function(int a, int b);
In this case, the assembler instruction SVC 0x23 will be generated where the function is called.
Software interrupt functions follow the same calling convention regarding parameters and return values as an ordinary function, except for the stack usage, see Calling convention.
For more information, see __svc, and svc_number, respectively.
The software interrupt handler and functions
The interrupt handler—for example SVC_Handler—works as a dispatcher for software interrupt functions. It is invoked from the interrupt vector and is responsible for retrieving the software interrupt number and then calling the proper software interrupt function. The SVC_Handler must be written in assembler as there is no way to retrieve the software interrupt number from C/C++ source code.
The software interrupt functions
The software interrupt functions can be written in C or C++. Use the __svc keyword in a function definition to make the compiler generate a return sequence suited for a specific software interrupt function. The #pragma svc_number directive is not needed in the interrupt function definition.
For more information, see __svc.
Setting up the software interrupt stack pointer
If software interrupts will be used in your application, then the software interrupt stack pointer (SVC_STACK) must be set up and some space must be allocated for the stack. The SVC_STACK pointer can be set up together with the other stacks in the cstartup.s file. As an example, see the set up of the interrupt stack pointer. Relevant space for the SVC_STACK pointer is set up in the linker configuration file, see Setting up stack memory.
Interrupt operations
An interrupt function is called when an external event occurs. Normally it is called immediately while another function is executing. When the interrupt function has finished executing, it returns to the original function. It is imperative that the environment of the interrupted function is restored—this includes the value of processor registers and the processor status register.
When an interrupt occurs, the following actions are performed:
The operating mode is changed corresponding to the particular exception
The address of the instruction following the exception entry instruction is saved in
R14of the new modeThe old value of the
CPSRis saved in theSPSRof the new modeInterrupt requests are disabled by setting bit 7 of the
CPSRand, if the exception is a fast interrupt, further fast interrupts are disabled by setting bit 6 of theCPSRThe PC is forced to begin executing at the relevant vector address.
For example, if an interrupt for vector 0x18 occurs, the processor will start to execute code at address 0x18. The memory area that is used as start location for interrupts is called the interrupt vector table. The content of the interrupt vector is normally a branch instruction jumping to the interrupt routine.
Note
If the interrupt function enables interrupts, the special processor registers needed to return from the interrupt routine must be assumed to be destroyed. For this reason they must be stored by the interrupt routine to be restored before it returns. This is handled automatically if the __nested keyword is used.