jorick23 wrote on Thursday, June 26, 2008:
Processor: STR750 (ARM7)
Compiler: IAR
I just saw a post asking about interrupt wrappers for the ARM7 so I thought I’d respond with this post. Since it doesn’t really answer his question, I’m making it a new thread.
I’ve set up my interrupts to be normal functions running in Thumb mode. They’re called from an interrupt handler that takes care of everything: saving and restoring task contexts, resetting the interrupt pending bits, etc. The IRQ handler even contains the SWI handler. In order to speed things up (30% less execution time) and reduce stack usage, I have the RTOS store the task context in the Task Control Block instead of on the stack. There was a previous post to this board about how to do that so I took the code and optimized it.
Here is the interrupt function:
/******************************************************************************
*~ _Vector_SWI Software Interrupt exception vector *
*~ _Vector_IRQ Interrupt exception vector *
*~ vPortStartFirstTask Start the first task *
*******************************************************************************
* *
* This function handles exceptions. The task state is saved prior to calling *
* the exception handler and restored upon completion. During the exception, *
* it’s possible for the task context to change if a task was awakened by the *
* exception handler. *
* *
* Normally, ARM interrupts on the STR750 are handled by branching to the *
* Interrupt Vector Register which holds a previously stored “ldr pc, #offset” *
* instruction, and the offset was read and set from a table. This requires *
* interrupt functions to be in ARM mode. However, some interrupt functions *
* in the pump software were written in Thumb mode so this method of handling *
* interrupts won’t work. So instead of an instruction, interrupt vectors are *
* read from a table in ROM. *
* *
* Args: void *
* *
* Return: void *
* *
******************************************************************************/
// ***** SWI exception handler ************************************************
_Vector_SWI:
// The return address is bumped by 4 to make it look like the context was saved
// during an IRQ handler.
add lr, lr, #4 // Adjust the link register
// ***** IRQ exception handler ************************************************
_Vector_IRQ:
// ***** Save the task context
// The task context is saved in the task control block. If the task is changed
// during the interrupt, the task context restored at the end of this function
// will be different. First, the r0 register is saved and used to read the
// current TCB pointer. The registers r1 through r14 are saved in the TCB.
stmdb sp!, {r0} // Save r0 for use as scratch register
ldr r0, =pxCurrentTCB // Point to the current TCB pointer
ldr r0, [r0] // Get it
add r0, r0, #16 // Find the position of the r1 register
stmia r0, {r1-r14}^ // Save the task context
// The critical nesting depth, status register, link register, and saved r0
// register are loaded into registers and saved in the task control block.
ldr r1, =ulCriticalNesting // Point to the critical nesting depth
ldr r1, [r1] // Get it
mrs r2, spsr // Get the status register
mov r3, lr // Get the link register
ldmia sp!, {r4} // Get the saved r0 register
sub r0, r0, #16 // Find the start of the context frame
stmia r0, {r1-r4} // Save the rest of the task context
// ***** SWI: Switch contexts
// If this function was called from _Vector_SWI (the SWI handler), it switches
// contexts. Otherwise, if this function was called because of a pending
// interrupt, the interrupt is handled.
mrs r0, cpsr // Get the condition codes
tst r0, #1 // Check if SWI or IRQ
beq _IrqHandler // If IRQ, go handle the interrupt
ldr r0, =vTaskSwitchContext // Point to the task switcher
mov lr, pc // Set up the return address
bx r0 // Switch task contexts
b vPortStartFirstTask // Go restore the task context
// ***** IRQ: Handle the interrupt
// If this is an interrupt, the interrupting channel is read and used to index
// into the vector table to find the address of the handler, which is then
// called.
_IrqHandler:
ldr r0, =EIC // Point to the EIC registers
ldr r1, [r0, #EIC_IVR] // Update them (dummy read)
ldr r0, [r0, #EIC_CICR] // Get the interrupting channel number
mov r0, r0, lsl #2 // Convert it to a table index
ldr r1, =_IrqVectorTable // Point to the vector table
ldr r0, [r1, +r0] // Get the handler address
mov lr, pc // Set up the return address
bx r0 // Call the appropriate function
// After the interrupt has been handled, the interrupting channel is read and
// used to clear the pending flag.
ldr r0, =EIC // Point to the EIC registers
ldr r1, [r0, #EIC_CICR] // Get the interrupting channel number
mov r2, #1 // Set up a flag bit
mov r2, r2, lsl r1 // Position it in the word
str r2, [r0, #EIC_IPR] // Clear the interrupt pending flag
// ***** Restore the task context
// This part of the exception handler also starts the first task by restoring
// the task context created by the task scheduler.
vPortStartFirstTask:
// The critical nesting depth, status register, and link register are restored.
load r0, pxCurrentTCB // Get the current TCB pointer
ldmia r0!, {r1-r2, lr} // Read the values
ldr r3, =ulCriticalNesting // Point to the critical nesting depth
str r1, [r3] // Restore it
msr spsr_cxsf, r2 // Restore the status register
// The task’s system mode registers are restored.
ldmia r0, {r0-r14}^ // Restore the user mode registers
nop // [Banked register access insurance]
// The function returns to the task. This might not be the same task that was
// interrupted, if the task context was changed.
subs pc, lr, #4 // Return from the interrupt
// ***** Interrupt (EIC) vector table
_IrqVectorTable:
dc32 _Vector_SpuriousIrq // 0: Wakeup
dc32 _Vector_SpuriousIrq // 1: TIM2 Output Compare 2 (not used)
dc32 _Vector_SpuriousIrq // 2: TIM2 Output Compare 1 (not used)
dc32 _Vector_SpuriousIrq // 3: TIM2 Input Capture (not used)
dc32 _Vector_SpuriousIrq // 4: TIM2 Update (not used)
dc32 _Vector_SpuriousIrq // 5: TIM1 Output Compare 2 (not used)
dc32 _Vector_SpuriousIrq // 6: TIM1 Output Compare 1 (not used)
dc32 _Vector_SpuriousIrq // 7: TIM1 Input Capture (not used)
dc32 _Vector_SpuriousIrq // 8: TIM1 Update (not used)
dc32 _Vector_SpuriousIrq // 9: TIM0 Output Compare 2 (not used)
dc32 _Vector_SpuriousIrq // 10: TIM0 Output Compare 1 (not used)
dc32 _Vector_SpuriousIrq // 11: TIM0 Input Capture (not used)
dc32 _Vector_SpuriousIrq // 12: TIM0 Update (not used)
dc32 _Vector_SpuriousIrq // 13: PWM Output Compare (not used)
dc32 _Vector_SpuriousIrq // 14: PWM Timer Emergency (not used)
dc32 _Vector_SpuriousIrq // 15: PWM Timer Update (not used)
dc32 I2C_Irq // 16: I2C
dc32 _Vector_SpuriousIrq // 17: SSP1 (not used)
dc32 _Vector_SpuriousIrq // 18: SSP0 (not used)
dc32 Keypad_Irq_Touch // 19: UART 2 (Touchscreen interface)
dc32 Serial_Rs485_Irq // 20: UART 1 (RS485)
dc32 Serial_RS232_Irq // 21: UART 0 (RS232)
dc32 _Vector_SpuriousIrq // 22: CAN (not used)
dc32 USB_Istr // 23: Low priority USB
dc32 CTR_HP // 24: High priority USB
dc32 ADC_Irq // 25: Analog to Digital Converter
dc32 _Vector_SpuriousIrq // 26: DMA (not used)
dc32 _Vector_EXTIT // 27: External interrupts
dc32 _Vector_SpuriousIrq // 28: MRCC (not used)
dc32 _Vector_SpuriousIrq // 29: FLASHSMI (not used)
dc32 _Vector_SpuriousIrq // 30: Real Time Clock (not used)
dc32 vPortPreemptiveTick // 31: Time base timer
// ***** Spurious interrupt
// The registers are set as follows:
// r0 = EIC register pointer
// r1 = IRQ vector table pointer
// r2 = spsr
// r3 = Return address to the point just after the interrupt occurred
// r4-r12 are undefined
_Vector_SpuriousIrq:
sub lr, lr, #4 // Point to the interrupted instruction
b . // SPURIOUS INTERRUPT TRAP
In the TCB, I added one line right at the beginning, so it now reads:
typedef struct tskTaskControlBlock
{
portSTACK_TYPE pxCpuContextFrame[ portCPU_CONTEXT_SIZE ];
volatile portSTACK_TYPE *pxTopOfStack; /*< Points to the location of the last item placed on the tasks stack.>*/
…
The pxPortInitialiseStack function has been changed to:
portSTACK_TYPE *pxPortInitialiseStack( portSTACK_TYPE *ctx, portSTACK_TYPE *pxTopOfStack, pdTASK_CODE pxCode, void *pvParameters )
{
/* Interrupt flags cannot always be stored on the stack and will
instead be stored in a variable, which is then saved as part of the
tasks context. */
*ctx = portNO_CRITICAL_NESTING;
ctx++;
/* The status register is set for system mode, with interrupts enabled. */
*ctx = ( portSTACK_TYPE ) portINITIAL_SPSR;
ctx++;
/* The return address - which in this case is the start of the task. The
offset is added to make the return address appear as it would within an
IRQ ISR.*/
*ctx = ( portSTACK_TYPE ) pxCode + portINSTRUCTION_SIZE;
ctx++;
/* When the task starts is will expect to find the function parameter in
R0. */
*ctx = ( portSTACK_TYPE ) pvParameters; /* R0 */
ctx++;
*ctx = ( portSTACK_TYPE ) 0x01010101; /* R1 */
ctx++;
*ctx = ( portSTACK_TYPE ) 0x02020202; /* R2 */
ctx++;
*ctx = ( portSTACK_TYPE ) 0x03030303; /* R3 */
ctx++;
*ctx = ( portSTACK_TYPE ) 0x04040404; /* R4 */
ctx++;
*ctx = ( portSTACK_TYPE ) 0x05050505; /* R5 */
ctx++;
*ctx = ( portSTACK_TYPE ) 0x06060606; /* R6 */
ctx++;
*ctx = ( portSTACK_TYPE ) 0x07070707; /* R7 */
ctx++;
*ctx = ( portSTACK_TYPE ) 0x08080808; /* R8 */
ctx++;
*ctx = ( portSTACK_TYPE ) 0x09090909; /* R9 */
ctx++;
*ctx = ( portSTACK_TYPE ) 0x10101010; /* R10 */
ctx++;
*ctx = ( portSTACK_TYPE ) 0x11111111; /* R11 */
ctx++;
*ctx = ( portSTACK_TYPE ) 0x12121212; /* R12 */
ctx++;
*ctx = ( portSTACK_TYPE ) pxTopOfStack; /* Stack used when task starts goes in R13. */
ctx++;
*ctx = ( portSTACK_TYPE ) 0xaaaaaaaa; /* R14 */
return pxTopOfStack;
}
Since there’s an extra parameter in pxPortInitialiseStack, the call in Task.c needs to change to:
pxNewTCB->pxTopOfStack = pxPortInitialiseStack( pxNewTCB->pxCpuContextFrame, pxTopOfStack, pvTaskCode, pvParameters );
An extra bonus is that my interrupt functions can now be located anywhere in memory, not just the first 64K as is the case when using the interrupt vector register.