Marco Graziano, Jaime Smith, and Geary Choppoff converted the uC/OS real time kernel to ARM as an exercise to validate the ARM and the development environment under real time constraints, and to get an idea as to the ARM's relative performance. This effort led to the radical rewriting of the DEMON. uC/OS was originally ported to the MC68HC11, but in Jean Labrosse's book "uC/OS" the source code supplied is for the 80186. To use uC/OS, it is compiled separately and then linked into the user application to add real time functionality without have to re-invent it for each project. The current ARM uC/OS implementation should not be thought of as the ultimate version, but as a work in progress. The reader is urged to get the uC/OS book and study both the 80186 code and the ARM code available from the Resource Catalog or from VLSI.
uC/OS supports a small range of basic elements which are necessary for the operation of a real time environment. These are: Tasks, Semaphores, Messages, and Queues. Tasks are defined in a preset number of Task Control Blocks (TCBs) and the other three have a common data structure called an Event Control Block (Events) of which there is also pre-defined number. Interrupts are special tasks that are initiated by the hardware. They may or may not interact with other tasks. A VERY brief description of uC/OS features is in the uC/OS sidebar.
What To DO?
Like many programs of this type, uC/OS is a mixture of assembler and C. Of course the assembler portion needs to be reinvented. The "C" part of the code is also susceptible to change because of the severe architectural differences between the parts. The first thing to do to port a program from the 8086 (of which the 80186 is a family member) is to compare some of the salient features of the two CPUs. Note - converting uC/OS to the 80386 could just as painful, judging from Ed Nisley's gyrations over protected mode.
| ITEM | 80186 | ARM |
| Word Size | 16/8 Bits | 32/8 Bits |
| Address Range |
64KB offset + 1MB Segment |
4Gbyte Linear |
| No. of Regs | 8 + 4 seg regs | 16 + 15 overlapping regs |
| Supervisor Modes | None | Yes and FIQ, IRQ, Abort too |
| Instruction size | 1 to 7 bytes | 4 bytes |
Data structures are the first to be converted as they define the details of the programs that modify them. Because of the difficulty the ARM has with 16 bit variables, it is often better to lengthen them to 32 bits rather than go through the pain of jockying packed 16 bit numbers. All ARM pointers are also converted to 32 bits. ( 8086 far pointers have the same number of bits.) The first data structure to be converted is the task stack image. The 80186 task image was built to take advantage of a combination of POP ES, POPA, and IRET instructions. The ARM's task image uses the structure from a Load Multiple instruction in which most of the registers are restored in one instruction.
uC/OS Task Image that stored on the Process Stack
| Description | 80186(large mem) | size | ARM | size | Description | DATA AREA | Offset of Data | 16 | R15-PC | 32 | Code Pointer |
| Segment of Data | 16 | R14-LR | 32 | R0-R15 | |
| CODE AREA | Offset of Code | 16 | R12 | 32 | restored by |
| Segment of Code | 16 | R11 | 32 | LDMFD SP!,{R0-R12,LR,PC} | |
| Status Word | PSW | 16 | R10 | 32 | |
| CS:IP Restored IP by IRET | (PC) | 16 | R9 | 32 | |
| CS | 16 | R8 | 32 | ||
| Registers are Restored by POPA | AX | 16 | R7 | 32 | |
| CX | 16 | R6 | 32 | ||
| DX | 16 | R5 | 32 | ||
| BX | 16 | R4 | 32 | ||
| SP | 16 | R3 | 32 | ||
| BP | 16 | R2 | 32 | ||
| SI | 16 | R1 | 32 | ||
| DI | 16 | R0 | 32 | Data Pointer | |
| POP ES ES | ES | 16 | PSR->R13 | 32 | stk ptr |
Note: *OSTCBStkPtr points to the bottom of either task structure.
Total 16 items 17 items
TCB structure.
Going along with the ARM preference for 32 bit quanities the TCB is modified for the ARM version. This is not the only possible version, as 4 byte quatities could have been packed into a word..
| ITEM | 80186l | size | ARM | size | DESCRIPTION |
| OSTCBStkPtr | far * | 16+16 | Pointer | 32 | Pointer to task stack image |
| OSTCBStat | UBYTE | 8 | uint | 32 | Task Status |
| OSTCBPrio | UBYTE | 8 | uint | 32 | Task Priority |
| OSTCBDly | UWORD | 16 | uint | 32 | Timeout for delay or wait |
| OSTCBX | UBYTE | 8 | uint | 32 | Priority byte bit position |
| OSTCBY | UBYTE | 8 | uint | 32 | Priority group bit position |
| OSTCBBitX | UBYTE | 8 | uint | 32 | Precalculated bit mask |
| OSTCBBitY | UBYTE | 8 | uint | 32 | Precalculated bit mask |
| *OSTCBEventPtr | Pointer | 16 | Pointer | 32 | Pointer to Event Control Block |
| *OSTCBNext | Pointer | 16 | Pointer | 32 | Pointer to next TCB |
| *OSTCBPrev | Pointer | 16 | Pointer | 32 | Pointer to previous TCB |
How these structures are used can best be illustrated by the uC/OS call OSStart() which starts the multitasking kernel by starting the highest priority task. Listing 3 gives the C code followed by the 80186 assembler support and the ARM assembler support codes.
Although the simple ARM instructions make code easier to read, the fact that the ARM does have processor modes makes some uC/OS functions more complicated. Whenever a uC/OS function requires access to private data structures that must be completed without interrupt, the C code calls OS_ENTER_CRITICAL() and after the sensitive code is finished OS_EXIT_CRITICAL is called. In the 80186, these are simply translated to CLI for clear interrupt enable and STI for set interrupt enable. On the other hand the ARM operating in user mode cannot change the interrupt enables directly, but does this through an operating system call. Thus SWI 0x14 - disable interrupts and SWI 0x13 - enable interrupts are used.
| ITEM | 80186 | ARM | DESCRIPTION |
| OS_ENTER_CRITICAL() | CLI | SWI 0x14 | Disables Interrupts |
| OS_EXIT_CRITICAL() | STI | SWI 0x13 | Enable Interupts |
The processs of going through the SWI call takes many cycles, as the ARM must save user registers, back up R14 to find the SWI code, run through a look up table, and then do the request. Many uC/OS functions call OS_ENTER_CRITICAL() and OS_EXIT_CRITICAL() and place in between their user code to accomplish the function. (see listing 4 on delaying a task) Thus the processor may go through a mode transition 4 times for each call. You can probably sense that the next step in porting uC/OS would be to do a major rewrite, placing all OS functions in Supervisor Mode and operating them from SWI calls. The interrupt enable (for IRQ) is automatically turned off after a SWI instruction, obviating the need for theOS_ENTER and OS_EXIT_CRITICAL functions. This is why I call uC/OS for ARM a work in progress.