|
|
FreeRTOS Support Archive
The FreeRTOS support forum is used to obtain active support directly from Real
Time Engineers Ltd. In return for using our top quality software and services for
free, we request you play fair and do your bit to help others too! Sign up
to receive notifications of new support topics then help where you can.
This is a read only archive of threads posted to the FreeRTOS support forum.
The archive is updated every week, so will not always contain the very latest posts.
Use these archive pages to search previous posts. Use the Live FreeRTOS Forum
link to reply to a post, or start a new support thread.
[FreeRTOS Home] [Live FreeRTOS Forum] [FAQ] [Archive Top] [June 2008 Threads] Interrupts and task switches in the ARM7Posted 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 ARM7Posted 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?
Copyright (C) Amazon Web Services, Inc. or its affiliates. All rights reserved.
|