Quality RTOS & Embedded Software

 Real time embedded FreeRTOS RSS feed 
Quick Start Supported MCUs PDF Books Trace Tools Ecosystem TCP & FAT




Loading

Interrupts and task switches in the ARM7

Posted by Ricky on 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.

RE: Interrupts and task switches in the ARM7

Posted by Dave on June 26, 2008
Cool. The ARM7 port is now one of the older ports maybe could do with a revamp. The PIC32 and Cortex ports (which are newer) are more featured. Thoughts Richard?


[ Back to the top ]    [ About FreeRTOS ]    [ Sitemap ]    [ ]




Copyright (C) Amazon Web Services, Inc. or its affiliates. All rights reserved.

Latest News

FreeRTOS kernel V10 is available for immediate download. Now MIT licensed.


FreeRTOS Partners

ARM Connected RTOS partner for all ARM microcontroller cores

IAR Partner

Microchip Premier RTOS Partner

RTOS partner of NXP for all NXP ARM microcontrollers

STMicro RTOS partner supporting ARM7, ARM Cortex-M3, ARM Cortex-M4 and ARM Cortex-M0

Texas Instruments MCU Developer Network RTOS partner for ARM and MSP430 microcontrollers

OpenRTOS and SafeRTOS