...
This section describes how µC/OS-II has been ported to the Intel 80x86 series of processors that provides a Floating-Point Unit (FPU). Some of the processors that can make use of this port are the Intel 80486™, Pentiums™ (all models), Xeon™, AMD Athlon™, K6™-series, ElanSC520™ and more. The port assumes the Borland C/C++ compiler V4.51 and was setup to generate code for the large memory model. The processor is assumed to be running in real mode. The code for this port is very similar to the one presented in Chapter 14 and 80x86 Port with Emulated FP Support and in some cases, I will only be presenting the differences.
...
Figure 15.1 shows the programming model of an 80x86 processor running in real mode. The integer registers are identical to those presented in Chapter 14in 80x86 Port with Emulated FP Support. In fact, they are saved and restored using the same technique. The only difference between this port and the one presented in Chapter 14 is that section is that we also need to save and restore the FPU registers which is done by using the context switch hook functions.
Anchor | ||||
---|---|---|---|---|
|
Panel | ||||
---|---|---|---|---|
| ||||
Development Tools
As in the section 80x86 Port with Emulated FP Support, I used the Borland C/C++ V4.51 compiler along with the Borland Turbo Assembler for porting and testing. This compiler generates reentrant code and provides in-line assembly language instructions that can be inserted in C code. The compiler can be directed to generate code specifically to make use of the FPU. I tested the code on a 300 MHz Pentium-II-based computer running the Microsoft Windows 2000 operating system. In fact, I configured the compiler to generate a DOS executable which was run in a DOS window.
Finally, you can also adapt the port provided in this chapter section to other 80x86 compiler as long as they generate real-mode code. You will most likely have to change some of the compiler options and assembler directives if you use a different development environment.
Table 15.1 shows the Borland C/C++ compiler V4.51 options (i.e., flags) supplied on the command line. These settings were used to compile the port as well as example 4 provided in Chapter 1 Getting Started with µC/OS-II.
Anchor | ||
---|---|---|
|
|
Panel | ||||
---|---|---|---|---|
| ||||
|
...
Directories and Files
The installation program provided on the companion CD installs the port for the Intel 80x86 (real mode, large model with FPU support) on your hard disk. The port is found under the:
\SOFTWARE\uCOS-II\Ix86L-FP\BC45
directory. The directory name stands for Intel 80x86 real mode, Large model with hardware Floating-Point instructions and is placed in the Borland C++ V4.5x directory. The source code for the port is found in the following files: OS_CPU.H
, OS_CPU_C.C
, and OS_CPU_A.ASM
.
INCLUDES.H
Listing 15.1 shows the contents of INCLUDES.H
for this 80x86 port. It is identical to the one used in Chapter 14.
INCLUDES.H
is not really part of the port but is described here because it is needed to compile the port files.
OS_CPU.H
OS_CPU.H
contains processor- and implementation-specific #defines constants, macros, and typedefs. OS_CPU.H
for the 80x86 port is shown in Listing 15.2. Most of OS_CPU.H
is identical to the OS_CPU.H
of Chapter 14.
OS_CPU.H, OS_ENTER_CRITICAL() and OS_EXIT_CRITICAL()
OS_CPU.H, Stack Growth
OS_CPU.H, OS_TASK_SW()
OS_CPU.H, Tick Rate
I also decided (see Chapter 14 for additional details) to change the tick rate of the PC from the standard 18.20648Hz to 200Hz (i.e., 5ms between ticks).
OS_CPU.H, Floating-Point Functions
This port defines three special functions that are specific to the floating-point capabilities of the 80x86. In other words, I had to add three new functions to the port to handle the floating-point hardware.
OS_CPU_C.C
As mentioned in Chapter 13 and 14, µC/OS-II port requires that you write ten fairly simple C functions:
OSTaskStkInit()
OSTaskCreateHook()
OSTaskDelHook()
OSTaskSwHook()
OSTaskIdleHook()
OSTaskStatHook()
OSTimeTickHook()
OSInitHookBegin()
OSInitHookEnd()
OSTCBInitHook()
µC/OS-II only requires OSTaskStkInit
. The other nine functions must be declared but generally donít need to contain any code. However, this port will make use of OSTaskCreateHook
, OSTaskDelHook
, OSTaskSwHook
and OSInitHookEnd
.
The #define constant OS_CPU_HOOKS_EN
(see OS_CFG.H
) should be set to 1.
OSTaskStkInit()
This function is called by OSTaskCreate
and OSTaskCreateExt
and is identical to the OSTaskStkInit
presented in section 14.01.01. You may recall that OSTaskStkInit
is called to initialize the stack frame of a task so that it looks as if an interrupt has just occurred and all of the processor integer registers were pushed onto it. Figure 15.2 (identical to Figure 14.3) shows what OSTaskStkInit
puts on the stack of the task being created. Note that the diagram doesnít show the stack frame of the code calling OSTaskStkInit
but rather, the stack frame of the task being created. Also, the stack frame only contains the contents of the integer registers, nothing about the floating point registers. Iíll discuss how we handle the FPU registers shortly.
For reference, Listing 15.3 shows the code for OSTaskStkInit
which is identical to the one shown in Chapter 14 (Listing 14.3).
OSFPInit()
OSFPInit
is called by OSInitHookEnd
when OSInit
is done initializing µC/OS-IIís internal structures (I will discuss OSInitHookEnd
later). OSFPInit
is basically used to initialize the floating-point context switching mechanism presented in this chapter. OSFPInit
assumes that you enabled µC/OS-IIís memory management functions (i.e., you must set OS_MEM_EN
to 1 in OS_CFG.H
). The code for OSFPInit
is shown in Listing 15.4.
You should be careful that your code doesnít generate any floating-point exception (e.g. divide by zero) because µC/OS-II will not do anything about them. Run-time exceptions can, however, be avoided by adding range testing code to your application. In fact, you should make it a practice to check for possible divide by zero and the like.
OSTaskCreateHook()
Listing 15.5 shows the code for OSTaskCreateHook
. Recall that OSTaskCreateHook
is called by OS_TCBInit
(which in turn is called by OSTaskCreate
or OSTaskCreateExt
).
Figure 15.3 shows the relationship between some of the data structures after OSTaskCreateHook
has executed.
OSTaskDelHook()
You may recall that OSTaskDelHook
is called by OSTaskDel
to extend the functionality of OSTaskDel
. Because we allocated a memory block to hold the contents of the floating-point registers when the task was created, we need to deallocate the block when the task is deleted. Listing 15.6 shows how this is accomplished by OSTaskDelHook
.
OSTaskSwHook()
OSTaskSwHook
is used to extend the functionality of the context switch code. You may recall that OSTaskSwHook
is called by OSStartHighRdy
, the task-level context switch function OSCtxSw
and, the ISR context switch function OSIntCtxSw
. Listing 15.7 shows how OSTaskSwHook
is implemented.
OSTaskIdleHook()
OS_CPU_C.C
doesnít do anything in this function.
OSTaskStatHook()
OS_CPU_C.C
doesnít do anything in this function. See Example 3 in Chapter 1 for an example on what you can do with OSTaskStatHook
.
OSTimeTickHook()
OS_CPU_C.C
doesnít do anything in this function either.
OSInitHookBegin()
OS_CPU_C.C
doesnít do anything in this function.
OSInitHookEnd()
OSInitHookEnd
is called just before OSInit
returns. This means that OSInit
initialized µC/OS-IIís memory partition services (which you should have to use this port by setting OS_MEM_EN
to 1 in OS_CFG.H
). OSInitHook
simply calls OSFPInit
(see section 15.04.02) which is responsible for setting up the memory partition reserved to hold the contents of floating-point registers for each task. The code for OSInitHookEnd
is shown in Listing 15.12.
OSTCBInitHook()
OS_CPU_C.C
doesnít do anything in this function.
OS_CPU_A.ASM
A µC/OS-II port requires that you write four assembly language functions:
OSStartHighRdy()
OSCtxSw()
OSIntCtxSw()
OSTickISR()
This port adds two functions called OSFPSave
and OSFPRestore
and are found in OS_CPU_A.ASM
. These functions are responsible for saving and restoring the contents of floating-point registers during a context switch, respectively.
OSStartHighRdy()
This function is called by OSStart
to start the highest priority task ready to run. It is identical to the OSStartHighRdy
presented in Chapter 14 (see section 14.05.01). The code is shown again in Listing 15.14 for your convenience but will not be discussed since you can review it from section 14.05.01.
OSCtxSw()
A task-level context switch is accomplished on the 80x86 processor by executing a software interrupt instruction. The interrupt service routine must vector to OSCtxSw
. The sequence of events that leads µC/OS-II to vector to OSCtxSw
begins when the current task calls a service provided by µC/OS-II, which causes a higher priority task to be ready to run. At the end of the service call, µC/OS-II calls the function OS_Sched
, which concludes that the current task is no longer the most important task to run. OS_Sched
loads the address of the OS_TCB
of the highest priority task into OSTCBHighRdy
, then executes the software interrupt instruction by invoking the macro OS_TASK_SW
. Note that the variable OSTCBCur
already contains a pointer to the current taskís task control block, OS_TCB
. The code for OSCtxSw
which is identical to the one presented in Chapter 14 is shown in Listing 15.15. OSCtxSw
will be discussed again because of the added complexity of the floating-point context switch.
Figure 15.4 shows the stack frames as well as the FPU storage areas of the task being suspended and the task being resumed.
Note that interrupts are disabled during OSCtxSw
and also during execution of OSTaskSwHook
.
OSIntCtxSw()
OSIntCtxSw
is called by OSIntExit
to perform a context switch from an ISR (Interrupt Service Routine). Because OSIntCtxSw
is called from an ISR, it is assumed that all the processorís integer registers are already properly saved onto the interrupted taskís stack.
The code is shown in Listing 15.16 and is identical to the OSIntCtxSw
presented in Chapter 14. The floating-point registers are handled by OSTaskSwHook
. Figure 15.5 shows the context switch process from OSIntCtxSw
í s point of view.
As in Chapter 14, let's assume that the processor receives an interrupt. Letís also supposed that interrupts are enabled. The processor completes the current instruction and initiates an interrupt handling procedure.
Your ISR then needs to either call OSIntEnter
or, increment the global variable OSIntNesting
by one. At this point, we can assume that the task is suspended and we could, if needed, switch to a different task.
The ISR can now start servicing the interrupting device and possibly, make a higher priority task ready. This occurs if the ISR sends a message to a task by calling either OSFlagPost
, OSMboxPost
, OSMboxPostOpt
, OSQPostFront
, OSQPost
or OSQPostOpt
. A higher priority task can also be resumed if the ISR calls OSTaskResume
, OSTimeTick
or OSTimeDlyResume
.
Assume that a higher priority task is made ready to run by the ISR. µC/OS-II requires that an ISR calls OSIntExit()
when it has finished servicing the interrupting device. OSIntExit
basically tell µC/OS-II that itís time to return back to task-level code if all nested interrupts have completed. In other words, when OSIntNesting is decremented to 0 by OSIntExit
, OSIntExit
would return to task level code.
When OSIntExit
executes, it notices that the interrupted task is no longer the 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.
Note that interrupts are disabled during OSIntCtxSw
and also during execution of OSTaskSwHook
.
OSTickISR()
As mentioned in section 15.03.05, Tick Rate, the tick rate of an RTOS should be set between 10 and 100Hz. On the PC, however, the ticker occurs every 54.93ms (18.20648Hz) and is obtained by a hardware timer that interrupts the CPU. Recall that I reprogrammed the tick rate to 200Hz because it was a multiple of 18.20648Hz. The ticker on the PC is assigned to vector 0x08 but µC/OS-II redefined it so that it vectors to OSTickISR
instead. Because of this, the PCís tick handler is saved [see PC.C, PC_DOSSaveReturn
] in vector 129 (0x81). To satisfy DOS, however, the PCís handler is called every 54.93ms. OSTickISR
for this port is identical to the OSTickISR
presented in section 14.05.04 and thus, there is no need to repeat the description here. I did, however, include the code in Listing 15.17 for your convenience.
OSFPSave()
OSFPSave
is not normally part of a µC/OS-II port. OSFPSave
basically takes the contents of the floating-point registers and saves them at the address passed to OSFPSave
. OSFPSave
is called from C but is written in assembly language because it must execute an FPU instruction which is not available from C. OSFPSave
is called by the C functions OSFPInit
, OSTaskCreateHook
and OSTaskSwHook
as follows:
OSFPSave((void *pblk);
Where pblk
is the address of a storage area large enough to hold the FPU context and, must be at least 108 bytes.
Listing 15.18 shows the code for OSFPSave
.
OSFPRestore()
OSFPRestore
is also not normally part of a µC/OS-II port. OSFPRestore
basically loads the FPU registers with the contents of a memory buffer pointed to by the address passed to OSFPRestore
. OSFPRestore
is called from C but is written in assembly language because it must execute an FPU instruction which is not available from C. OSFPRestore
is only called by OSTaskSwHook
as follows:
OSFPRestore((void *pblk);
Where pblk
is the address of a storage area large enough to hold the FPU context and, must be at least 108 bytes.
Listing 15.19 shows the code for OSFPRestore
.
Memory Usage
...
|
Table 15.2 shows the Borland Turbo Assembler V4.0 options (i.e., flags) supplied on the command line. These settings were used to assemble OS_CPU_A.ASM
.
Anchor | ||||
---|---|---|---|---|
|
Panel | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
|
Directories and Files
The installation program provided on the companion CD installs the port for the Intel 80x86 (real mode, large model with FPU support) on your hard disk. The port is found under the:
\SOFTWARE\uCOS-II\Ix86L-FP\BC45
directory. The directory name stands for Intel 80x86 real mode, Large model with hardware Floating-Point instructions and is placed in the Borland C++ V4.5x directory. The source code for the port is found in the following files: OS_CPU.H
, OS_CPU_C.C
, and OS_CPU_A.ASM
.
INCLUDES.H
Listing 15.1 shows the contents of INCLUDES.H
for this 80x86 port. It is identical to the one used in 80x86 Port with Emulated FP Support.
INCLUDES.H
is not really part of the port but is described here because it is needed to compile the port files.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <conio.h>
#include <dos.h>
#include <math.h>
#include <setjmp.h>
#include "os_cpu.h"
#include "os_cfg.h"
#include "ucos_ii.h"
#include "pc.h" |
OS_CPU.H
OS_CPU.H
contains processor- and implementation-specific #defines constants, macros, and typedefs. OS_CPU.H
for the 80x86 port is shown in Listing 15.2. Most of OS_CPU.H
is identical to the OS_CPU.H
of the section 80x86 Port with Emulated FP Support.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
#ifdef OS_CPU_GLOBALS
#define OS_CPU_EXT
#else
#define OS_CPU_EXT extern
#endif
typedef unsigned char BOOLEAN; (1)
typedef unsigned char INT8U;
typedef signed char INT8S;
typedef unsigned int INT16U;
typedef signed int INT16S;
typedef unsigned long INT32U;
typedef signed long INT32S;
typedef float FP32; (2)
typedef double FP64;
typedef unsigned int OS_STK; (3)
typedef unsigned short OS_CPU_SR; (4) |
Panel | ||
---|---|---|
| ||
(1) If you were to consult the Borland compiler documentation, you would find that an int and a short are 16 bits and a long is 32 bits. (2) Floating-point data types are included because itís assumed that you will be performing floating-point operations in your tasks. However, µC/OS-II itself doesnít make use of floating-point numbers. (3) A stack entry for the 80x86 processor running in real mode is 16 bits wide; thus, (4) The status register (also called the processor flags) on the 80x86 processor running in real mode is 16 bits wide. The |
OS_CPU.H, OS_ENTER_CRITICAL() and OS_EXIT_CRITICAL()
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
#define OS_CRITICAL_METHOD 2 (5)
#define OS_ENTER_CRITICAL() asm {PUSHF; CLI} (6)
#define OS_EXIT_CRITICAL() asm POPF |
Panel | ||
---|---|---|
| ||
(5) For this port, the prefered critical method #2 because itís directly supported by the compiler. (6) |
OS_CPU.H, Stack Growth
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
#define OS_STK_GROWTH 1 (7) |
Panel | ||
---|---|---|
| ||
(7) The stack on an 80x86 processor grows from high to low memory, which means that |
OS_CPU.H, OS_TASK_SW()
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
#define uCOS 0x80 (8)
#define OS_TASK_SW() asm INT uCOS (9) |
Panel | ||
---|---|---|
| ||
(9) To switch context, (8) I tested the code on a PC and I decided to use interrupt number 128 (0x80). |
OS_CPU.H, Tick Rate
I also decided (see 80x86 Port with Emulated FP Support for additional details) to change the tick rate of the PC from the standard 18.20648Hz to 200Hz (i.e., 5ms between ticks).
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
OS_CPU_EXT INT8U OSTickDOSCtr; (10) |
Panel | ||
---|---|---|
| ||
(10) This statement declares an 8-bit variable ( |
OS_CPU.H, Floating-Point Functions
This port defines three special functions that are specific to the floating-point capabilities of the 80x86. In other words, I had to add three new functions to the port to handle the floating-point hardware.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
void OSFPInit(void); (11)
void OSFPRestore(void *pblk); (12)
void OSFPSave(void *pblk); (13) |
Panel | ||
---|---|---|
| ||
(11) A function has been added to initialize the floating-point handling mechanism described in this port. (12) (13) |
OS_CPU_C.C
As mentioned in Porting µC/OS-II and in 80x86 Port with Emulated FP Support, µC/OS-II port requires that you write ten fairly simple C functions:
OSTaskStkInit()
OSTaskCreateHook()
OSTaskDelHook()
OSTaskSwHook()
OSTaskIdleHook()
OSTaskStatHook()
OSTimeTickHook()
OSInitHookBegin()
OSInitHookEnd()
OSTCBInitHook()
µC/OS-II only requires OSTaskStkInit
. The other nine functions must be declared but generally donít need to contain any code. However, this port will make use of OSTaskCreateHook
, OSTaskDelHook
, OSTaskSwHook
and OSInitHookEnd
.
The #define constant OS_CPU_HOOKS_EN
(see OS_CFG.H
) should be set to 1.
OSTaskStkInit()
This function is called by OSTaskCreate
and OSTaskCreateExt
and is identical to the OSTaskStkInit
presented in section 14.01.01. You may recall that OSTaskStkInit
is called to initialize the stack frame of a task so that it looks as if an interrupt has just occurred and all of the processor integer registers were pushed onto it. Figure 15.2 (identical to Figure 14.3) shows what OSTaskStkInit
puts on the stack of the task being created. Note that the diagram doesnít show the stack frame of the code calling OSTaskStkInit
but rather, the stack frame of the task being created. Also, the stack frame only contains the contents of the integer registers, nothing about the floating point registers. Iíll discuss how we handle the FPU registers shortly.
Anchor | ||||
---|---|---|---|---|
|
Panel | ||||
---|---|---|---|---|
| ||||
For reference, Listing 15.3 shows the code for OSTaskStkInit
which is identical to the one shown in 80x86 Port with Emulated FP Support (Listing 14.3).
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
OS_STK *OSTaskStkInit (void (*task)(void *pd),
void *pdata,
OS_STK *ptos,
INT16U opt)
{
INT16U *stk;
opt = opt;
stk = (INT16U *)ptos;
*stk-- = (INT16U)FP_SEG(pdata);
*stk-- = (INT16U)FP_OFF(pdata);
*stk-- = (INT16U)FP_SEG(task);
*stk-- = (INT16U)FP_OFF(task);
*stk-- = (INT16U)0x0202;
*stk-- = (INT16U)FP_SEG(task);
*stk-- = (INT16U)FP_OFF(task);
*stk-- = (INT16U)0xAAAA;
*stk-- = (INT16U)0xCCCC;
*stk-- = (INT16U)0xDDDD;
*stk-- = (INT16U)0xBBBB;
*stk-- = (INT16U)0x0000;
*stk-- = (INT16U)0x1111;
*stk-- = (INT16U)0x2222;
*stk-- = (INT16U)0x3333;
*stk-- = (INT16U)0x4444;
*stk = _DS;
return ((OS_STK *)stk);
} |
OSFPInit()
OSFPInit
is called by OSInitHookEnd
when OSInit
is done initializing µC/OS-IIís internal structures (I will discuss OSInitHookEnd
later). OSFPInit
is basically used to initialize the floating-point context switching mechanism presented in this section. OSFPInit
assumes that you enabled µC/OS-IIís memory management functions (i.e., you must set OS_MEM_EN
to 1 in OS_CFG.H
). The code for OSFPInit
is shown in Listing 15.4.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
#define OS_NTASKS_FP (OS_MAX_TASKS + OS_N_SYS_TASKS - 1) (1)
#define OS_FP_STORAGE_SIZE 128 (2)
static OS_MEM *OSFPPartPtr; (3)
static INT32U OSFPPart[OS_NTASKS_FP][OS_FP_STORAGE_SIZE / sizeof(INT32U)]; (4)
void OSFPInit (void)
{
INT8U err;
#if OS_TASK_STAT_EN
OS_TCB *ptcb;
void *pblk;
#endif
OSFPPartPtr = OSMemCreate(&OSFPPart[0][0], (5)
OS_NTASKS_FP,
OS_FP_STORAGE_SIZE,
&err);
#if OS_TASK_STAT_EN && OS_TASK_CREATE_EXT_EN
ptcb = OSTCBPrioTbl[OS_STAT_PRIO]; (6)
ptcb->OSTCBOpt |= OS_TASK_OPT_SAVE_FP; (7)
pblk = OSMemGet(OSFPPartPtr, &err); (8)
if (pblk != (void *)0) { (9)
ptcb->OSTCBExtPtr = pblk; (10)
OSFPSave(pblk); (11)
}
#endif
} |
Panel | ||
---|---|---|
| ||
(1) Although not actually part of (2) The 80x86 FPU requires 108 bytes of storage. I decided to allocate 128 bytes for future expansion. If you are tight on memory, you could save 20 bytes per task by setting this value to 108. (3) We will be using a µC/OS-II memory partition for the storage of all the FPU contexts. (4)
bytes of RAM (i.e., data space) for this partition. Because (5) (6) I decided to change the attributes of (7) The (8) I get a storage buffer that will hold the contents of the floating-point registers for (9) Always prudent to check for an invalid pointer. (10) The pointer to the FPU storage area is saved in the (11) The function |
You should be careful that your code doesn't generate any floating-point exception (e.g. divide by zero) because µC/OS-II will not do anything about them. Run-time exceptions can, however, be avoided by adding range testing code to your application. In fact, you should make it a practice to check for possible divide by zero and the like.
OSTaskCreateHook()
Listing 15.5 shows the code for OSTaskCreateHook
. Recall that OSTaskCreateHook
is called by OS_TCBInit
(which in turn is called by OSTaskCreate
or OSTaskCreateExt
).
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
void OSTaskCreateHook (OS_TCB *ptcb)
{
INT8U err;
void *pblk;
if (ptcb->OSTCBOpt & OS_TASK_OPT_SAVE_FP) { (1)
pblk = OSMemGet(OSFPPartPtr, &err); (2)
if (pblk != (void *)0) { (3)
ptcb->OSTCBExtPtr = pblk; (4)
OSFPSave(pblk); (5)
}
}
} |
Panel | ||
---|---|---|
| ||
(1) If you create a task that will perform floating-point calculations, you must set the (2) Because we are creating a task that will use the FPU, we need to allocate storage for the FPU registers. (3) Again, itís a good idea to validate the pointer. (4) The pointer to the storage area is saved in the (5) Again, the function |
Figure 15.3 shows the relationship between some of the data structures after OSTaskCreateHook
has executed.
Anchor | ||||
---|---|---|---|---|
|
Panel | ||||
---|---|---|---|---|
| ||||
OSTaskDelHook()
You may recall that OSTaskDelHook
is called by OSTaskDel
to extend the functionality of OSTaskDel
. Because we allocated a memory block to hold the contents of the floating-point registers when the task was created, we need to deallocate the block when the task is deleted. Listing 15.6 shows how this is accomplished by OSTaskDelHook
.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
void OSTaskDelHook (OS_TCB *ptcb)
{
if (ptcb->OSTCBOpt & OS_TASK_OPT_SAVE_FP) { (1)
if (ptcb->OSTCBExtPtr != (void *)0) { (2)
OSMemPut(OSFPPartPtr, ptcb->OSTCBExtPtr); (3)
}
}
} |
Panel | ||
---|---|---|
| ||
(1) (2) We first need to confirm that we allocated a memory block that was used for floating-point context storage. (3) The memory block is returned to the its proper memory partition. |
OSTaskSwHook()
OSTaskSwHook
is used to extend the functionality of the context switch code. You may recall that OSTaskSwHook
is called by OSStartHighRdy
, the task-level context switch function OSCtxSw
and, the ISR context switch function OSIntCtxSw
. Listing 15.7 shows how OSTaskSwHook
is implemented.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
void OSTaskSwHook (void)
{
INT8U err;
void *pblk;
if (OSRunning == TRUE) { (1)
if (OSTCBCur->OSTCBOpt & OS_TASK_OPT_SAVE_FP) { (2)
pblk = OSTCBCur->OSTCBExtPtr;
if (pblk != (void *)0) { (3)
OSFPSave(pblk); (4)
}
}
}
if (OSTCBHighRdy->OSTCBOpt & OS_TASK_OPT_SAVE_FP) { (5)
pblk = OSTCBHighRdy->OSTCBExtPtr;
if (pblk != (void *)0) { (6)
OSFPRestore(pblk); (7)
}
}
} |
Panel | ||
---|---|---|
| ||
(1) When (2) If (3) Just to be sure, we also check the contents of the (4) As usual, we call (5) We then check to see if the task to be ëswitched-iní (i.e., the higher priority task) was created with the floating-point option. In other words, it checks whether you told (6) Just to be sure, we also check the contents of the (7) The function |
OSTaskIdleHook()
OS_CPU_C.C
doesn't do anything in this function.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
void OSTaskIdleHook (void)
{
} |
OSTaskStatHook()
OS_CPU_C.C
doesn't do anything in this function. See Example 3 in Getting Started with µC/OS-II for an example on what you can do with OSTaskStatHook
.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
void OSTaskStatHook (void)
{
} |
OSTimeTickHook()
OS_CPU_C.C
doesn't do anything in this function either.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
void OSTimeTickHook (void)
{
} |
OSInitHookBegin()
OS_CPU_C.C
doesn't do anything in this function.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
void OSInitHookBegin (void)
{
} |
OSInitHookEnd()
OSInitHookEnd
is called just before OSInit
returns. This means that OSInit
initialized µC/OS-IIís memory partition services (which you should have to use this port by setting OS_MEM_EN
to 1 in OS_CFG.H
). OSInitHook
simply calls OSFPInit
(see section 15.04.02) which is responsible for setting up the memory partition reserved to hold the contents of floating-point registers for each task. The code for OSInitHookEnd
is shown in Listing 15.12.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
void OSInitHookEnd (void)
{
OSFPInit();
} |
OSTCBInitHook()
OS_CPU_C.C
doesnít do anything in this function.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
void OSTCBInitHook (void)
{
} |
OS_CPU_A.ASM
A µC/OS-II port requires that you write four assembly language functions:
OSStartHighRdy()
OSCtxSw()
OSIntCtxSw()
OSTickISR()
This port adds two functions called OSFPSave
and OSFPRestore
and are found in OS_CPU_A.ASM
. These functions are responsible for saving and restoring the contents of floating-point registers during a context switch, respectively.
OSStartHighRdy()
This function is called by OSStart
to start the highest priority task ready to run. It is identical to the OSStartHighRdy
presented in 80x86 Port with Emulated FP Support. The code is shown again in Listing 15.14 for your convenience but will not be discussed since you can review it from the section 80x86 Port with Emulated FP Support.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
_OSStartHighRdy PROC FAR
MOV AX, SEG _OSTCBHighRdy
MOV DS, AX
;
CALL FAR PTR _OSTaskSwHook
;
MOV AL, 1
MOV BYTE PTR DS:_OSRunning, AL
;
LES BX, DWORD PTR DS:_OSTCBHighRdy
MOV SS, ES:[BX+2]
MOV SP, ES:[BX+0]
;
POP DS
POP ES
POPA
;
IRET
_OSStartHighRdy ENDP |
OSCtxSw()
A task-level context switch is accomplished on the 80x86 processor by executing a software interrupt instruction. The interrupt service routine must vector to OSCtxSw
. The sequence of events that leads µC/OS-II to vector to OSCtxSw
begins when the current task calls a service provided by µC/OS-II, which causes a higher priority task to be ready to run. At the end of the service call, µC/OS-II calls the function OS_Sched
, which concludes that the current task is no longer the most important task to run. OS_Sched
loads the address of the OS_TCB
of the highest priority task into OSTCBHighRdy
, then executes the software interrupt instruction by invoking the macro OS_TASK_SW
. Note that the variable OSTCBCur
already contains a pointer to the current taskís task control block, OS_TCB
. The code for OSCtxSw
which is identical to the one presented in 80x86 Port with Emulated FP Support is shown in Listing 15.15. OSCtxSw
will be discussed again because of the added complexity of the floating-point context switch.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
_OSCtxSw PROC FAR (1)
;
PUSHA (2)
PUSH ES
PUSH DS
;
MOV AX, SEG _OSTCBCur
MOV DS, AX
;
LES BX, DWORD PTR DS:_OSTCBCur (3)
MOV ES:[BX+2], SS
MOV ES:[BX+0], SP
;
CALL FAR PTR _OSTaskSwHook (4)
;
MOV AX, WORD PTR DS:_OSTCBHighRdy+2 (5)
MOV DX, WORD PTR DS:_OSTCBHighRdy
MOV WORD PTR DS:_OSTCBCur+2, AX
MOV WORD PTR DS:_OSTCBCur, DX
;
MOV AL, BYTE PTR DS:_OSPrioHighRdy (6)
MOV BYTE PTR DS:_OSPrioCur, AL
;
LES BX, DWORD PTR DS:_OSTCBHighRdy (7)
MOV SS, ES:[BX+2]
MOV SP, ES:[BX]
;
POP DS (8)
POP ES
POPA
;
IRET (9)
;
_OSCtxSw ENDP |
Figure 15.4 shows the stack frames as well as the FPU storage areas of the task being suspended and the task being resumed.
Anchor | ||||
---|---|---|---|---|
|
Panel | ||||
---|---|---|---|---|
| ||||
Panel | ||
---|---|---|
| ||
The notes below apply both and simultaneously to Listing 15.15 and Figure 15.4. When reading each numbered note, refer to both the listing and the figure. (1) F15.4 (1) L15.15() - On the 80x86 processor, the software interrupt instruction forces the SW register to be pushed onto the current taskís stack followed by the return address (segment and then offset) of the task that executed the INT instruction [i.e., the task that invoked (2) F15.4 (2) L15.15() - The remaining CPU registers of the task to suspend are saved onto the current taskís stack. (3) F15.4 (3) L15.15() - The pointer to the new stack frame is saved into the taskís (4) F15.4 (5) F15.4 (4) L15.15() - The task switch hook (5) L15.15() - Upon return from (6) L15.15() - Also, (6) F15.4 (7) L15.15() - At this point, (7) F15.4 (8) L15.15() - The remaining CPU registers are pulled from the new taskís stack. (8) F15.4 (9) L15.15() - An IRET instruction is executed to load the new taskís program counter and status word. After this instruction, the processor resumes execution of the new task. |
Note that interrupts are disabled during OSCtxSw
and also during execution of OSTaskSwHook
.
OSIntCtxSw()
OSIntCtxSw
is called by OSIntExit
to perform a context switch from an ISR (Interrupt Service Routine). Because OSIntCtxSw
is called from an ISR, it is assumed that all the processorís integer registers are already properly saved onto the interrupted taskís stack.
The code is shown in Listing 15.16 and is identical to the OSIntCtxSw
presented in 80x86 Port with Emulated FP Support. The floating-point registers are handled by OSTaskSwHook
. Figure 15.5 shows the context switch process from OSIntCtxSw
í s point of view.
As in 80x86 Port with Emulated FP Support, let's assume that the processor receives an interrupt. Letís also supposed that interrupts are enabled. The processor completes the current instruction and initiates an interrupt handling procedure.
Anchor | ||||
---|---|---|---|---|
|
Panel | ||||
---|---|---|---|---|
| ||||
Panel | ||
---|---|---|
| ||
(1) The 80x86 automatically pushes the processorís SW register followed by the return address of the interrupted task onto the stack. The CPU then vectors to the proper ISR. µC/OS-II requires that your ISR begins by saving the rest of the processorís integer registers. Once the registers are saved, µC/OS-II requires that you also save the contents of the stack pointer in the taskís |
Your ISR then needs to either call OSIntEnter
or, increment the global variable OSIntNesting
by one. At this point, we can assume that the task is suspended and we could, if needed, switch to a different task.
The ISR can now start servicing the interrupting device and possibly, make a higher priority task ready. This occurs if the ISR sends a message to a task by calling either OSFlagPost
, OSMboxPost
, OSMboxPostOpt
, OSQPostFront
, OSQPost
or OSQPostOpt
. A higher priority task can also be resumed if the ISR calls OSTaskResume
, OSTimeTick
or OSTimeDlyResume
.
Assume that a higher priority task is made ready to run by the ISR. µC/OS-II requires that an ISR calls OSIntExit()
when it has finished servicing the interrupting device. OSIntExit
basically tell µC/OS-II that itís time to return back to task-level code if all nested interrupts have completed. In other words, when OSIntNesting is decremented to 0 by OSIntExit
, OSIntExit
would return to task level code.
When OSIntExit
executes, it notices that the interrupted task is no longer the 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.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
_OSIntCtxSw PROC FAR
;
CALL FAR PTR _OSTaskSwHook (1)
;
MOV AX, SEG _OSTCBCur
MOV DS, AX
;
MOV AX, WORD PTR DS:_OSTCBHighRdy+2 (2)
MOV DX, WORD PTR DS:_OSTCBHighRdy
MOV WORD PTR DS:_OSTCBCur+2, AX
MOV WORD PTR DS:_OSTCBCur, DX
;
MOV AL, BYTE PTR DS:_OSPrioHighRdy (3)
MOV BYTE PTR DS:_OSPrioCur, AL
;
LES BX, DWORD PTR DS:_OSTCBHighRdy (4)
MOV SS, ES:[BX+2]
MOV SP, ES:[BX]
;
POP DS (5)
POP ES
POPA
;
IRET (6)
;
_OSIntCtxSw ENDP |
Panel | ||
---|---|---|
| ||
The notes below apply both and simultaneously to Listing 15.16 and Figure 15.5. When reading each numbered note, refer to both the listing and the figure. (2) (3) (1) The first thing (2) Upon return from (3) (4) (4) At this point, (5) (5) The remaining CPU registers are pulled from the stack. (6) (6) An IRET instruction is executed to load the new taskís program counter and status word. After this instruction, the processor resumes execution of the new task. |
Note that interrupts are disabled during OSIntCtxSw
and also during execution of OSTaskSwHook
.
OSTickISR()
As mentioned in section 15.03.05, Tick Rate, the tick rate of an RTOS should be set between 10 and 100Hz. On the PC, however, the ticker occurs every 54.93ms (18.20648Hz) and is obtained by a hardware timer that interrupts the CPU. Recall that I reprogrammed the tick rate to 200Hz because it was a multiple of 18.20648Hz. The ticker on the PC is assigned to vector 0x08 but µC/OS-II redefined it so that it vectors to OSTickISR
instead. Because of this, the PCís tick handler is saved [see PC.C, PC_DOSSaveReturn
] in vector 129 (0x81). To satisfy DOS, however, the PCís handler is called every 54.93ms. OSTickISR
for this port is identical to the OSTickISR
presented in section 14.05.04 and thus, there is no need to repeat the description here. I did, however, include the code in Listing 15.17 for your convenience.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
_OSTickISR PROC FAR
;
PUSHA
PUSH ES
PUSH DS
;
MOV AX, SEG(_OSIntNesting)
MOV DS, AX
INC BYTE PTR DS:_OSIntNesting
;
CMP BYTE PTR DS:_OSIntNesting, 1
JNE SHORT _OSTickISR1
MOV AX, SEG(_OSTCBCur)
MOV DS, AX
LES BX, DWORD PTR DS:_OSTCBCur
MOV ES:[BX+2], SS
MOV ES:[BX+0], SP
;
_OSTickISR1:
MOV AX, SEG(_OSTickDOSCtr)
MOV DS, AX
DEC BYTE PTR DS:_OSTickDOSCtr
CMP BYTE PTR DS:_OSTickDOSCtr, 0
JNE SHORT _OSTickISR2
;
MOV BYTE PTR DS:_OSTickDOSCtr, 11
INT 081H
JMP SHORT _OSTickISR3
_OSTickISR2:
MOV AL, 20H
MOV DX, 20H
OUT DX, AL
;
_OSTickISR3:
CALL FAR PTR _OSTimeTick
;
CALL FAR PTR _OSIntExit
;
POP DS
POP ES
POPA
;
IRET
;
_OSTickISR ENDP |
OSFPSave()
OSFPSave
is not normally part of a µC/OS-II port. OSFPSave
basically takes the contents of the floating-point registers and saves them at the address passed to OSFPSave
. OSFPSave
is called from C but is written in assembly language because it must execute an FPU instruction which is not available from C. OSFPSave
is called by the C functions OSFPInit
, OSTaskCreateHook
and OSTaskSwHook
as follows:
OSFPSave((void *pblk);
Where pblk
is the address of a storage area large enough to hold the FPU context and, must be at least 108 bytes.
Listing 15.18 shows the code for OSFPSave
.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
_OSFPSave PROC FAR
;
PUSH BP (1)
MOV BP,SP
PUSH ES
PUSH BX
;
LES BX, DWORD PTR [BP+6] (2)
;
FSAVE ES:[BX] (3)
;
POP BX (4)
POP ES
POP BP
;
RET (5)
;
_OSFPSave ENDP |
Panel | ||
---|---|---|
| ||
(1) (2) The pointer passed to (3) The FPU instruction (4) The temporary registers are retrieved from the stack. (5) |
OSFPRestore()
OSFPRestore
is also not normally part of a µC/OS-II port. OSFPRestore
basically loads the FPU registers with the contents of a memory buffer pointed to by the address passed to OSFPRestore
. OSFPRestore
is called from C but is written in assembly language because it must execute an FPU instruction which is not available from C. OSFPRestore
is only called by OSTaskSwHook
as follows:
OSFPRestore((void *pblk);
Where pblk
is the address of a storage area large enough to hold the FPU context and, must be at least 108 bytes.
Listing 15.19 shows the code for OSFPRestore
.
Anchor | ||||
---|---|---|---|---|
|
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
_OSFPRestore PROC FAR
;
PUSH BP (1)
MOV BP,SP
PUSH ES
PUSH BX
;
LES BX, DWORD PTR [BP+6] (2)
;
FRSTOR ES:[BX] (3)
;
POP BX (4)
POP ES
POP BP
;
RET (5)
;
_OSFPRestore ENDP |
Panel | ||
---|---|---|
| ||
(1) (2) The pointer passed to (3) The FPU instruction (4) The temporary registers are retrieved from the stack. (5) |
Memory Usage
The only code that changed in 80x86 Port with Hardware FP Support from the code provided in 80x86 Port with Emulated FP Support was OS_CPU_A.ASM
, OS_CPU_C.C
and OS_CPU.H
. These files add only an additional 164 of code space (ROM).
...
The spreadsheet for this port is found on the companion CD (\SOFTWARE\uCOS-II\Ix86L-FP\BC45\DOC\80x86L-FP-ROM-RAM.XLS
). You need Microsoft Excel for Office 2000 (or higher) to use this file. The spreadsheet allows you to do ìwhat-ifî scenarios based on the options you select. You can change the configuration values (in RED) and see how they affects µC/OS-IIís ROM and RAM usage on the 80x86. For the ???_EN
values, you MUST use either 0 or 1.
As with Chapter 14, I with 80x86 Port with Emulated FP Support, I setup the Borland compiler to generate the fastest code. The number of bytes shown are not meant to be accurate but are simply provided to give you a relative idea of how much code space each of the µC/OS-II group of services require.
...