Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 14 Next »

Real Mode, Large Model with Hardware Floating-Point Support

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 in some cases, I will only be presenting the differences.

This port assumes that you enabled code generation for OSTaskCreateExt (by setting OS_TASK_CREATE_EXT_EN to 1 in OS_CFG.H) and that you enabled µC/OS-IIís memory management services (by setting OS_MEM_EN to 1 in OS_CFG.H). Of course, you must set OS_MAX_MEM_PART to at least 1. Finally, tasks that will perform floating-point operations MUST be created using OSTaskCreateExt and set the OS_TASK_OPT_SAVE_FP option.

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 14. 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 we also need to save and restore the FPU registers which is done by using the context switch hook functions.

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 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.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.

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.

Figure 15.4 80x86 stack frames and FPU storage during a task-level context switch.

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

The only code that changed in Chapter 15 from the code provided in Chapter 14 was OS_CPU_A.ASM, OS_CPU_C.C and OS_CPU.H. These files add only an additional 164 of code space (ROM).

You MUST include the code for OSTaskCreateExt (set OS_TASK_CREATE_EXT to 1 in OS_CFG.H) and the memory management services (set OS_MEM_EN to 1 in OS_CFG.H) because this port would not work without them.

With respect to data space, this port requires a memory buffer of 128 bytes (although we only need 108 bytes) for each task that will perform floating-point operations.

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 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.

The spreadsheet also shows you the difference in code size based on the value of OS_ARG_CHK_EN in your OS_CFG.H. You donít need to change the value of OS_ARG_CHK_EN to see the difference.

The Data column is not as straightforward. Notice that the stacks for both the idle task and the statistics task have been set to 1,024 bytes (1Kb) each. Based on your own requirements, these number may be higher or lower. As a minimum, µC/OS-II requires about 3,500 bytes of RAM for µC/OS-II internal data structures if you configure the maximum number of tasks (62 application tasks). I added an entry that specifies the number of tasks that will be doing floating-point operations. Remember that each such task requires a buffer of 128 bytes. One buffer is always allocated because I changed the statistic task to allow floating-point.

If you use an 80x86 processor, you will most likely not be too restricted with memory and thus, µC/OS-II will most likely not be the largest user of memory.

  • No labels