Файл: Real-time processing with the Philips LPC ARM mcu using GCC and uCOS II RTOS (D.W. Hawkins, 2006).pdf

ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 15.06.2025

Просмотров: 485

Скачиваний: 2

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

AR1803

May 10, 2006

/* (8) Change to IRQ mode and disable IRQ */ msr cpsr_c, #IRQ_MODE|IRQ_DISABLE

/* (9) Restore the SPSR */ msr spsr, r4

/* (10) Acknowledge the VIC */ mov r0, #0

str r0, [r5]

/* (11) Restore IRQ context and return from interrupt */ ldmfd sp!, {r0-r6, ip, pc}^

Comparing this to the non-nested IRQ ISR

nonnested_irq_isr: sub lr, lr, #4

stmfd sp!, {r0-r3, ip, lr} bl irq_handler

ldmfd sp!, {r0-r3, ip, pc}^

clearly shows the additional steps required to implement nesting!

The main application in Example 10 implements the same EINT[0:3] generation sequence as Example 9(b), but does not use the interrupt keyword in its handler definitions (as demonstrated by Example 9(c)). Example 10(a) links against an IRQ ISR that does not implement interrupt nesting (essentially repeating Example 9(b)), while Example 10(b) links against an IRQ ISR that implements nesting (i.e., Example 10(b) does not call the function irq_handler()). Figure 10 shows the waveforms from the two tests. Figure 10(b) shows that successive higher-priority interrupts successively interrupt the currently executing handler.

37

AR1803

May 10, 2006

(a)

(b)

Figure 10: LPC2138 nested interrupt handling. Example 10 starts by generating a software interrupt on EINT3, and then each IRQ handler generates a software interrupt on the next higher-priority interrupt (except for EINT0, that handler leaves it to main to restart the process). Figure (a) used a IRQ ISR that does not implement interrupt nesting, while Figure (b) used a IRQ ISR that implements interrupt nesting.

38

AR1803

May 10, 2006

4 µCOS-II RTOS

The MicroC/OS-II or µCOS-II real-time operating system (RTOS) was developed by Jean Labrosse for use in embedded systems such as microcontrollers and DSPs. The RTOS and methods for writing device drivers for it are covered in his two books; MicroC/OS-II: The Real-Time Kernel [6], and Embedded Systems Building Blocks: Complete and Ready-to-Use Modules in C [8]. The second edition of the RTOS book covers version 2.52 of the RTOS. The books contain the RTOS source code, and the RTOS can be used free-of-charge in university projects. The current commercial release of the RTOS is version 2.7x. The web site www.micruim.com contains additional resources, and ports for various processors.

Porting µCOS-II version 2.52 is covered in Chapter 13 of MicroC/OS-II: The Real-Time Kernel, 2nd Ed [6]. A µCOS-II port requires the definition of the data types on the processor, assembly language routines for critical section protection, interrupt handling, and context switching, and the definition of C coded hook functions. Table 13.1 on p289 [6] summarizes the porting requirements. The main e ort involved in porting µCOS-II is to determine the processor programming model, the calling conventions of the compiler, and servicing of interrupts. Earlier sections of this document developed this knowledge, so the port of µCOS-II is now straightforward.

4.1ARM-GCC port description

A task in µCOS-II is defined as a function call of the form;

void task(void *pdata);

where pdata is a pointer that can be used to pass information to a task. Tasks start out life as if they were just interrupted at the entry of a call to their task function. The port-specific C-function OSTaskStkInit() is responsible for creating an appropriate initial stack.

Figure 11 shows the µCOS-II ARM task initial stack context. The ordering of registers on the stack is as per the load/store multiple instruction format; registers with lower numbers are stored at lower addresses (higher on the stack in the figure) (pp60-63 of Furber’s book has a nice description of the load/store multiple instructions [5]). Given this stack layout, with the processor in user/system mode, with the user/system mode stack pointer set to the task context, the state of the task can be restored using the sequence;

/* Copy the task CPSR to the CPSR register */ ldmfd sp!, {r0}

msr cpsr, r0

/* Restore task state (using load multiple) */ ldmfd sp!, {r0-r12, lr, pc}

Note that unlike the sequence used to return from an interrupt, this load multiple instruction does not end with a caret, ‘^’, so the SPSR is not copied to the CPSR when the program counter is loaded (in fact, since you are in system mode, there is no SPSR and so that form of the instruction is illegal (p131 [5])). Another consequence of this choice of return sequence is that since the CPSR is loaded prior to loading the task registers, FIQ and IRQ interrupts will be enabled just prior to restoring the task state (this is fine though).

Most of the registers shown in Figure 11 can take on arbitrary values when an initial task context switch occurs. To aid in debugging, the registers are loaded with hexidecimal values that match their decimal register register numbers packed into a byte, and repeated four times (since debuggers often display the register contents in hexidecimal). The task argument pdata is placed on the stack at the location of r0, while the address of the task function is placed on the task stack at the location of

39


AR1803

May 10, 2006

Lower

addresses

Initial

values

SP

CPSR

SYS_MODE

R0

pdata

R1

0x01010101

R2

0x02020202

R3

0x03030303

R4

0x04040404

R5

0x05050505

Stack

R6

0x06060606

R7

0x07070707

growth

R8

0x08080808

R9

0x09090909

R10

0x10101010

R11/FP

0x11111111

R12/IP

0x12121212

R14/LR

0x14141414

Higher

R15/PC

task

addresses

Figure 11: µCOS-II ARM task initial stack context.

the program counter pc. The current program status register (CPSR) value is set to system mode, with FIQ and IRQ interrupts enabled.

The return address located at the link register location in the stack frame shown in Figure 11 would be used if the task function was ever returned from. Given the link-register value shown in Figure 11, the processor will almost certainly abort, so it can be useful to place the address of an exit handler on the stack in that location. The exit handler can log the fact that a task exited (when it probably should not have).

The Micrium web site hosts a number of ports for the ARM processor. The ports page can be accessed from the Micrium home page at http://www.micrium.com. The address of the ARM ports page was http://www.micrium.com/contents/products/ucos-ii/ports-arm.html when this document was written. The Micrium ARM-mode µCOS-II port is described in Application note AN-1011 (revision D). The port is for the IAR compiler. Several other application notes apply the generic ARM port to specific processors. A port of the AN-1011 port to the GCC compiler is provided with the source code associated with this document.

The Micrium ARM ports do not support interrupt nesting; even for the case of an FIQ interrupt occurring during IRQ interrupt processing (the IRQ interrupt service routine, OS_CPU_IRQ_ISR, in os_cpu_a.s calls the handler function with both IRQ and FIQ interrupts disabled).

The ARM port described in the following sections implements interrupt nesting of IRQ interrupts by FIQ interrupts, and higher-priority IRQ interrupts. The assembler implementation of the port manipulates registers specific to the ARM vectored interrupt controller (VIC). Porting this code for a di erent interrupt controller would be fairly simple.

The only di erence between the AN1011 source code and the code for this port is in the assembly file. The two versions of the assembler files are laid very similarly (in the style of the original AN1011 source). The following sections describe the port files.

Note that the µCOS-II source code is not free, so it is not supplied with this document. The version of the the µCOS-II source code used to test this port was version 2.52; the source provided with the 2nd edition of the Labrosse book.

40


AR1803

May 10, 2006

4.1.1Port header; os cpu.h

Critical section protection

The ARM-GCC port uses OS critical section protection method #3; it defines a function for saving the processor status while disabling FIQ and IRQ interrupts, and another to restore the processor status. The function declarations and critical section macros are located in os_cpu.h;

OS_CPU_SR OS_CPU_SR_Save(void);

void OS_CPU_SR_Restore(OS_CPU_SR cpu_sr);

#define OS_ENTER_CRITICAL() {cpu_sr = OS_CPU_SR_Save();} #define OS_EXIT_CRITICAL() {OS_CPU_SR_Restore(cpu_sr);}

and the function implementations are in os_cpu_a.s. The implementation of the OS_CPU_Save_SR() function is based on the recommendations in Atmel’s ARM processor application note Disabling Interrupts at Processor Level [4].

Task-level context switch

The task-level context switch macro, OS_TASK_SW(), is defined as a call to OSCtxSw() (see the

OS_CPU_A.ASM section.

4.1.2Port C-functions; os cpu c.c

The only C function the port needed to define was OSTaskStkInit() to initialize the stack as shown in Figure 11.

4.1.3Port assembler-functions; os cpu a.s

A port requires the implementation of OSCtxSw (task-level context switch), (time-tick ISR).

four assembler routines; OSStartHighRdy (start multi-tasking), OSIntCtxSw (interrupt-level context switch), and OSTickISR

Start multi-tasking

OSStartHighRdy() is called at the end of OSStart() (in µCOS-II source file OS_CORE.C), and is the exit point from main()’s context into the RTOS. OSStartHighRdy() implements the context restore of the registers shown in Figure 11. The function starts by ensuring that the processor is in user/system mode with FIQ and IRQ interrupts disabled (although having the interrupts disabled is not critical, as there should be no interrupt generating sources setup at this point). The OSTaskSwHook() function is then called, and the OSRunning flag set to true. The user/system mode stack pointer is then changed to that of the highest-priority (and only) task. The task CPSR is then copied into the CPSR register (which happens to enable FIQ/IRQ interrupts), and the task register context is restored.

Task-level context switch

OS_Sched() (OS_CORE.C) calls OS_TASK_SW() to implement a task-level context switch from inside a critical section (so both FIQ and IRQ are disabled when this function is called). The macro OS_TASK_SW() is a call to OSCtxSw() in this port, so on entry to the context switch function, the link register will contain the task return address. The job of OSCtxSw() is to save the current task context, switch over to the higher-priority task, and then restore context. The code saves the current tasks registers onto its stack as shown in Figure 11; the contents of link register is saved to both the link register and the program counter locations on the stack. The task stack-pointer is then saved to its task control block, the OSTaskSwHook() function is called, the higher-priority task stack is loaded, and the context of the higher-priority task is restored.

41

AR1803

May 10, 2006

Interrupt-level context switch

The FIQ and IRQ ISRs start by saving the processor context, incrementing the OSIntNesting counter (and saving the current value of the stack pointer if required), and the IRQ ISR then reenables IRQ interrupts. The ISR then calls handler code (written in C). When the handler returns, the ISR calls OSIntExit(), and then restores the processor state.

OSIntExit() (OS_CORE.C) checks to see if interrupt nesting is over, and then if a higherpriority task is ready. If interrupts are still nested, or the same task has the highest priority, then OSIntExit() returns, and the ISR runs to completion (i.e., performs the context restore of the task or interrupt it interrupted). If however, interrupt nesting is over, and a higher-priority task has been made ready, then a switch to the new task is required; that is the job of OSIntCtxSw(). OSIntExit() calls OSIntCtxSw() inside a critical section, so interrupts are disabled when this function is called.

The interrupt-level context switch code is similar to the task-level context switch code, except that the ISR has already done the work of saving the processor context to the task stack. OSIntCtxSw() starts by calling the OSTaskSwHook(), the higher-priority task stack is then loaded, and the context of the higher-priority task is restored.

Interrupt service routines (ISRs)

The FIQ and IRQ ISRs are setup to call C-coded handlers. It is up to the board-support package to decide where to call the OS function OSTimeTick(). For example, timer 0 can be setup to generate clock ticks and the VIC can be setup to generate an FIQ (for testing), or as an IRQ (a vectored interrupt would be recommended).

When an FIQ interrupt occurs, the ISR performs a partial context save (since the stack pointer is currently that of the FIQ, not the system mode task stack), and the processor is placed into system mode with interrupts disabled. The task context is then saved to the task stack. The interrupt nesting counter is then incremented, and if this is the first layer of nesting, the current value of the stack-pointer is saved to the task control block. The processor is then changed back to FIQ mode with interrupts disabled, and the FIQ handler function is called. After the handler returns, the processor is moved back to system mode, OSIntExit() is called, and the task context is restored. FIQ interrupts are not nested.

When an IRQ interrupt occurs, the ISR performs a partial context save (since the stack pointer is currently that of the IRQ, not the system mode task stack), and the processor is placed into system mode with interrupts disabled. The task context is then saved to the task stack. The interrupt nesting counter is then incremented, and if this is the first layer of nesting, the current value of the stack-pointer is saved to the task control block. The VIC vector address register is then read. The VIC vector address register returns the address of the IRQ handler, and triggers the VIC priority logic to only allow IRQ interrupts of higher-priority to interrupt the processor core. FIQ and IRQ interrupts are then enabled (with the processor left in system mode), and the handler function read from the VIC is called. After the handler returns, FIQ and IRQ interrupts are disabled, and the VIC is acknowledged by writing to the VIC vector address register. OSIntExit() is called, and the task context is restored.

4.1.4Board-support package; BSP.H,.C

The port assembly language file os_cpu_a.s defines an FIQ interrupt service routine that calls a C-coded handler; that handler needs to be supplied as part of the board-support package, or the function needs to be defined in the user application. The minimal form of the handler is;

void OS_CPU_FIQ_ISR_Handler(void)

{

return;

}

42


AR1803

May 10, 2006

ISR

posts semaphore

Task

Task

sets I/O pin high

pend completes

sets EINT bit

sets I/O pin low

pends on semaphore

sets EINT bit

pends on semaphore

(a)

Task A

Task A

sets I/O pin high

pend completes

posts semaphore A

sets I/O pin low

pends on semaphore B

posts semaphore A

pends on semaphore B

Task B

Task B

Task B

pends on semaphore A

pend completes

pend completes

sets I/O pin high

sets I/O pin low

posts semaphore B

posts semaphore B

pends on semaphore A

pends on semaphore A

(b)

Figure 12: µCOS-II ARM port testing. (a) task-to-ISR context switching, and (b) task-to-task context switching.

The port of the AN-1011 code requires a similar function defined for the IRQ ISR handler. The port described by this document accesses the IRQ ISR from the VIC vector address register, so does not require linking with an IRQ handler function. The BSP should also contain an initialization routine to setup a timer that generates an FIQ or IRQ interrupt, and a corresponding handler that calls the

OSTimeTick() routine.

The example programs supplied with this document contain a minimal board support package containing timer setup, and LED control functions.

4.2Port testing

This section presents test results from the AN-1011 ARM µCOS-II port and the ARM nestedinterrupts version presented in this document.

4.2.1Test 1: Task-to-IRQ context switching

Figure 12(a) shows the sequence of a task-to-ISR test. A test application was written containing a single task, and an interrupt handler for EINT0. The task sets an I/O pin high, triggers an EINT0

43