• 沒有找到結果。

OSIntCtxSw() OSTickISR()

在文檔中 µC/OS-II Goals Preface (頁 183-188)

OS_TASK_SW()

would simply call

OSCtxSw()

instead of vector to it. The Z80 is a processor that has been ported to µC/OS and thus would be portable to µC/OS-II.

8.04 OS_CPU_A.ASM

A µC/OS-II port requires that you write four fairly simple assembly language functions:

OSStartHighRdy() OSCtxSw()

OSIntCtxSw()

The pseudo code of what needs to be done by

OSCtxSw()

is shown in listing 8.2. This code must be written in assembly language because you cannot access CPU registers directly from C.

void OSCtxSw(void) {

Save processor registers;

Save the current task’s stack pointer into the current task’s OS_TCB:

OSTCBCur->OSTCBStkPtr = Stack pointer;

Call user definable OSTaskSwHook();

OSTCBCur = OSTCBHighRdy;

OSPrioCur = OSPrioHighRdy;

Get the stack pointer of the task to resume:

Stack pointer = OSTCBHighRdy->OSTCBStkPtr;

Restore all processor registers from the new task’s stack;

Execute a return from interrupt instruction;

}

Listing 8.2, Pseudo code for OSCtxSw()

You should note that interrupts are disabled during

OSCtxSw()

and also during execution of the user definable function

OSTaskSwHook()

.

8.04.03 OS_CPU_A.ASM, OSIntCtxSw()

OSIntCtxSw()

is a function that is called by

OSIntExit()

to perform a context switch from an ISR.

Because

OSIntCtxSw()

is called from an ISR, it is assumed that all the processor registers are properly saved onto the interrupted task’s stack. In fact, there are more things on the stack frame than we need.

OSIntCtxSw()

will thus have to clean up the stack so that the interrupted task is left with just the proper stack frame content.

To understand what needs to be done in

OSIntCtxSw()

, lets look at the sequence of events that leads µC/OS-II to call

OSIntCtxSw()

. You may want to refer to figure 8-2 to help understand the following description. We will assume that interrupts are not nested (i.e. an ISRs will not be interrupted), interrupts are enabled, and the processor is executing task level code. When an interrupt arrives, the processor completes the current instruction, recognizes the interrupt and initiates an interrupt handling procedure. This generally consist of pushing the processor status register and the return address of the interrupted task onto the stack F8-2(1). The order and which registers are pushed onto the stack is irrelevant.

Processor Status Word Interrupt Return Address

LOW MEMORY

HIGH MEMORY

Stack Growth

SP must be adjusted to point here.

This new SP is saved into the preempted task's OS_TCB.

Saved Processor Registers Return address to caller of OSIntExit() Return address to caller of OSIntCtxSw()

Processor Status Word

SP Points Here!

(1) (2) (3) (4) (5)

(6)

(7)

Figure 8-2, Stack contents during an ISR

The CPU then vectors to the proper ISR. µC/OS-II requires that your ISR begins by saving the rest of the processor registers F8-2(2). Once the registers are saved, µC/OS-II requires that you either call

OSIntEnter()

or, that you increment the global variable

OSIntNesting

by one. At this point, the interrupted task’s stack frame only contains the re gister contents of the interrupted task. The ISR can now start servicing the interrupting device and possibly, make a higher priority task ready. This would occur if the ISR sends a message to a task (by calling

OSMboxPost()

or

OSQPost()

), resume a task (by calling

OSTaskResume()

), invoke

OSTimeTick()

or

OSTimeDlyResume()

. Let us assume that a higher priority task is made ready to run.

µC/OS-II requires that your ISR calls

OSIntExit()

when the ISR completes servicing the interrupting device.

OSIntExit()

basically tell µC/OS-II that it’s time to return back to task level code. The call to

OSIntExit()

causes the return address of the caller to be pushed onto the interrupted task’s stack F8-2(3).

OSIntExit()

starts by disabling interrupts because it needs to execute critical code. Depending on how

OS_ENTER_CRITICAL()

is implemented (see section 8.03.02), the processor’s status register could be pushed onto the interrupted task’s stack F8-2(4).

OSIntExit()

notices that the interrupted task is no longer t he task that needs to run because a higher priority task is now ready. In this case, the pointer

OSTCBHighRdy

is made to point to the new task’s

OS_TCB

and

OSIntExit()

calls

OSIntCtxSw()

to perform the context switch. Calling

OSIntCtxSw()

causes the return address to be pushed onto the interrupted task’s stack F8-2(5).

As we are switching context, we only want to leave items F8-2(1) and F8-2(2) on the stack and ignore items F8-2(3), F8-2(4) and F8-2(5). This is accomplished by adding a ‘constant’to the stack pointer F8-2(6). The exact amount of stack adjustment must be known and this value greatly depends on the processor being ported (an address can be 16-bit, 32-bit or 64-bit), the compiler used, compiler options, memory model, etc. Also, the proces sor status word could be 8, 16, 32 or even 64-bit wide and,

OSIntExit()

may allocate local variables. Some processors allow you to directly add a constant to the stack pointer, others don’t. In the latter case, you can simply execute the appropriate numb er of pops instructions to one of the processor registers to accomplish the same thing. Once the stack is adjusted, the new stack pointer can be saved into the

OS_TCB

of the task being switched out F8-2(7).

OSIntCtxSw()

is the only function in µC/OS-II (and also µC/OS) that is compiler specific and has generated more e -mail than any other aspect of µC/OS. If your port crashes after a few context switches then, you should suspect that the stack is not being properly adjusted in

OSIntCtxSw()

.

The pseudo code in listing 8.3 shows what needs to be done by

OSIntCtxSw()

. This code must be written in assembly language because you cannot access CPU registers directly from C. If your C compiler supports in -line assembly, you can put the code for

OSIntCtxSw()

in

OS_CPU_C.C

instead of

OS_CPU_A.ASM

. As

you can see, except for the first line, the code is identical to

OSCtxSw()

. You can thus reduce the amount of code in the port by ‘jumping’to the appropriate section of code in

OSCtxSw()

.

void OSIntCtxSw(void) {

Adjust the stack pointer to remove calls to:

OSIntExit(),

OSIntCtxSw() and possibly the push of the processor status word;

Save the current task’s stack pointer into the current task’s OS_TCB:

OSTCBCur->OSTCBStkPtr = Stack pointer;

Call user definable OSTaskSwHook();

OSTCBCur = OSTCBHighRdy;

OSPrioCur = OSPrioHighRdy;

Get the stack pointer of the task to resume:

Stack pointer = OSTCBHighRdy->OSTCBStkPtr;

Restore all processor registers from the new task’s stack;

Execute a return from interrupt instruction;

}

Listing 8.3, Pseudo code for OSIntCtxSw()

8.04.04 OS_CPU_A.ASM, OSTickISR()

µC/OS-II requires that you provide a periodic time source to keep track of time delays and timeouts. A ‘tick’should occur between 10 and 100 times per second, or Hertz. To accomplish this, you can either dedicate a hardware timer, or obtain 50/60 Hz from an AC power line.

You MUST enable ticker interrupts AFTER multitasking has started, i.e. after calling

OSStart()

. In other word s, you should initialize and tick interrupts in the first task that executes following a call to

OSStart()

. A common mistake is to enable ticker interrupts between calling

OSInit()

and

OSStart()

as shown in listing 8.4:

void main(void) {

. .

OSInit(); /* Initialize µC/OS -II */

. .

/* Application initialization code ... */

/* ... Create at least on task by calling OSTaskCreate() */

. .

Enable TICKER interrupts; /* DO NOT DO THIS HERE!!! */

. .

OSStart(); /* Start multitasking */

}

Listing 8.4, Incorrect place to start the tick interrupt.

What could happen (and it has happened) is that the tick interrupt c ould be serviced before µC/OS-II starts the first task.

At this point, µC/OS-II is in an unknown state and will cause your application to crash.

The pseudo code for the tick ISR is shown in listing 8.5. This code must be written in assembly language because you cannot access CPU registers directly from C. If your processor is able to increment

OSIntNesting

with a single instruction then, there is no need for you to call

OSIntEnter()

. Incrementing

OSIntNesting

is much

quicker than going through the overhead of the function call and return.

OSIntEnter()

only increments the

OSIntNesting

, while protecting that increment in a critical section.

void OSTickISR(void) {

Save processor registers;

Call OSIntEnter() or increment OSIntNesting;

Call OSTimeTick();

Call OSIntExit();

Restore processor registers;

Execute a return from interrupt instruction;

}

Listing 8.5, Pseudo code for Tick ISR

8.05 OS_CPU_C.C

A µC/OS-II port requires that you write six fairly simple C functions:

OSTaskStkInit() OSTaskCrea teHook() OSTaskDelHook() OSTaskSwHook() OSTaskStatHook() OSTimeTickHook()

The only function that is actually necessary is

OSTaskStkInit()

. The other five functions MUST be declared but don’t need to contain any code inside them.

8.05.01 OS_CPU_C.C, OSTaskStkInit()

This function is called by

OSTaskCreate()

and

OSTaskCreateExt()

to initialize the stack frame of a task so that the stack looks as if an interrupt just occurred and all the processor registers were pushed onto that stack.

Figure 8-3 shows wha t

OSTaskStkInit()

will put on the stack of the task being created. Note that figure 8-3 assumes a stack growing from high-memory to low-memory. The discussion that follows applies just as well for a stack growing in the opposite direction.

Processor Status Word Interrupt Return Address

LOW MEMORY

HIGH MEMORY

Stack Growth

Saved Processor Registers

'pdata' Task start address

在文檔中 µC/OS-II Goals Preface (頁 183-188)