You are on page 1of 23

HKUST COMP355

Project Report of a Small Real Time Operating System USTOS

HKUST COMP355 Embedded Systems Software

Project Report of a Small Real Time Operating System USTOS


Instructor: Student Name: Student Email: Student ID: Prof. Jogesh K. Muppala XIAO, Jianxiong cs_xjx@ust.hk 05556262

Abstract: This report describes the details about the design and implementation of a small real time operating system USTOS. Its major point focuses on explaining the context switching by tricky usage of stack and synchronization mechanism. Keywords: RTOS, Context switch, 8051, C51, Synchronization mechanism

1. Introduction
1.1 Project Introduction
It is a trend that RTOS is used more and more in the embedded systems. In todays market of RTOS, uC/OS, VxWork are two most popular ones among them. While at the same time, there is large percentage of embedded system market for 8051 series microcontroller. Up to now, 8051 series microcontroller is still the most popular MCU all around the world. Because of the important role that 8051 series MCU are playing, some researchers and engineers have been trying to develop or transplant RTOS that can be run on it. Keil, the manufacture of the most famous and popular C compiler for 8051, develop one RTOS called RTX51 Real-Time Kernel. But the ROM space needed for RTX51 is more than 6K which is pretty large comparing with the 4K ROM in 80C51 and 8K Flash ROM in 89S52 etc. RTX51 also need the 8051 MCU to have foreign RAM to save data. Of course, Keil also offer another solution called RTX Tiny which needs only 0.9K footprint in ROM. However, RTX Tiny does not support priority based scheduling and interrupt manage. At the same time, both of RTX51 and RTX51Tiny do not offer any source code which is impossible for the application program developers to modify them according to their special requirement. Lots of embedded engineers are trying to transplant the existing open-source RTOS into 8051 MCU. But because of the lack of RAM and low speed of 8051 serious MCU, there are many difficulties and too high footprint to want to port most of RTOS into 8051 MCU. uC/OS, the most popular RTOS around the world, is ported on 8051 successfully. But because of its large footprint and its requirement that all functions should be reentrant functions, it is not suitable enough for real application.

-1-

HKUST COMP355

Project Report of a Small Real Time Operating System USTOS

To solve all these problems, the USTOS (pronounced use toss), comes out. USTOS is a full fledged real time operating system based on priority. It supports priority based task scheduling, nesting interrupt supporting, timer functions as well as synchronizing mechanisms including critical section, binary semaphore, mailbox and message queue. In this project, I work out the kernel of USTOS as well as some examples of the usage of USTOS.

1.2 Usage of This Report


This report serves two purposes. Of course, the first one is to elaborate the details of my project in order to get a good grade. Second, it is a very good tutorial for beginner of operating or embedded system to know how the concepts are realized in practice and how a real RTOS runs. As Prof. Jogesh always said, to practice is the only good way to learn. But because of the complexity of the hardware of desktop machines, it is time consuming and difficult to write a desktop OS or even to understand how a desktop OS works. (You can imagine how difficult to understand the codes of Linux.) So, USTOS will be the savior for learners.

1.3 Prerequisite Knowledge for Beginners


In case you are not the professor or TAs, if you want to understand what this report is talking about, you need to know the principle of programming language or compiler (COMP251), general concept of computer organization (COMP180), operating and embedded system (COMP252 + COMP355), 8051 hardware architecture and assembly language (ELEC254). And C51 language experience is preferable. Of course, even if you have all these prerequisite knowledge, you may still have to spend some effort to understanding USTOS since a realworld OS is not a simple thing to understand.

1.4 Report Structure


In the second part of this report, I will elaborate the details of the design in USTOS. Some ugly stuffs and details of hardware are unavoidable while implementing a real-world run-able RTOS. In the third part, I will talk something about the architecture of 8051 MCU, and interesting aspects of the Keil C compiler, as well as how to use USTOS for your program development.

2. Design and Implementation


USTOS contains priority based task scheduling, nesting interrupt supporting, timer functions as well as synchronizing mechanisms including critical section, binary semaphore, mailbox and message queue. Please notice all the designs are served for the purpose of real application. Because of the extreme lack of hardware resource for 8051 MCU, some specific designs and implementations are invented and may not be useful for other hardware architecture.

2.1 Task & Static Memory Allocation

-2-

HKUST COMP355

Project Report of a Small Real Time Operating System USTOS

As in other RTOS, a task in USTOS is simply a never return function which loops forever. Besides, every task has its own stack which is defined by the application programmer. Since the low speed of 8051 MCU and only 256 Byte RAM (for AT89S52), dynamically allocation of memory costs too much. So, memory allocations of USTOS are done in compile time. The task stack is defined explicitly as an array. Please pay more attention to the size of the stack. As we know, there is only very little RAM space for data, we cannot allocation too much for tasks stack. But there is a lower bound of the stack size because interrupt may happen during the executing time of the task and nested interrupt may even happened. ISRs may be called many times and they will push registers into the stack of the task that is running. For 8051, there are at most two levels of nesting interrupts may happen. Each ISR will push 13 Bytes registers and 2 Bytes of Return address into the stack. So in case you want to support nesting interrupt, you should at least allocate (13+2)*2 = 30 Bytes for the stacks of tasks.

uint8 uint8 uint8 uint8

idata idata idata idata

Stack0[OS_TASK_STACK_SIZE]; Stack1[OS_TASK_STACK_SIZE]; Stack2[OS_TASK_STACK_SIZE]; StackIdle[OS_TASK_STACK_SIZE];

void Task0(void); void Task1(void); void Task2(void); CodePointer_t OS_TaskCode [OS_MAX_TASKS]={Task0,Task1,Task2,OS_IdleTask}; DataPointer_t OS_TaskStack[OS_MAX_TASKS]={Stack0,Stack1,Stack2,StackIdle};

In order to save memory and speed up the code, USTOS use static tasks creation mechanism. It is the application programmers job to put the address of the entry code for each tasks into the OS_TaskCode array. Also, as in many RTOS, USTOS does not support tasks killing and assumes that each task will loop for ever.

2.2 Context Switching & Stacks


When context switching happens, it is necessary to push all register into the task stack. Also the stack pointer will be saved in USTOS which will retrieve back to resume the right information. The pseudo-code of context switching is shown in the following.

void OS_SaveContext(void) { Step 1: PUSH all registers PUSH ACC PUSH B PUSH DPH PUSH DPL PUSH PSW PUSH 0x00

-3-

HKUST COMP355

Project Report of a Small Real Time Operating System USTOS

PUSH 0x01 PUSH 0x02 PUSH 0x03 PUSH 0x04 PUSH 0x05 PUSH 0x06 PUSH 0x07 Step 2: Save the Stack Pointer OS_TaskStack[OS_RunningTask]=SP; } void OS_LoadContext(void) { Step 1: 1: Recover the Stack Pointer SP = OS_TaskStack[OS_RunningTask]; Step 2: POP all registers POP 0x07 POP 0x06 POP 0x05 POP 0x04 POP 0x03 POP 0x02 POP 0x01 POP 0x00 POP PSW POP DPL POP DPH POP B POP ACC Step 3: 3: OS_EXIT_CRITICAL(); Step 4: 4: Start to Run RETI or RET // Return from ISR or Functions }

The trick technique is the fuction OS_LoadContext. This function will not return the CPU to the callee. It is a never return function because it completely changes the stack pointer. After popping back all registers, the CPU instruction RET (for task fuctions) or RETI (for ISRs) will pop the data in the stack which SP stack pointer is pointing to into the PC register and change the program running flow into the newly ready tasks or ISR (for nesting ISR). In fact, this tricky technique is used not only in RTOS but also Desktop OS such as Linux and Minix etc. It is one easy implementation of context switches which almost all beginners are puzzled. In fact, context switching is not a fantastic miracle but just changing the stack pointer and RET or RETI.

-4-

HKUST COMP355

Project Report of a Small Real Time Operating System USTOS

Another important thing is that all the above procedures must be guarded by OS_ENTER_CRITICAL() & OS_EXIT_CRITICAL() of the callee since any interrupt interference will cost disaster error here.

2.3 Priority Based Scheduling & Distributed TCB


In fact, the scheduler of tasks is very simple. It just need to check bit by bit whether a task is in ready state from higher priority to lower priority ones. The real codes is shown here.

void OS_Scheduler(void) { uint8 i; for(i=0;i<OS_MAX_TASKS;i++) { if ( OS_TaskStatus & (0x01<<i) ) { OS_RunningTask = i; break; } } }

An interesting tricky thing in the implementation of USTOS is that there is no explicit Task Control Block in the data structure of OS. So where are the TCBs? First, lets recall the functions of the TCB. TCB must contain at least three things: 1. Task PC Value 2. Task Stack Pointer Value 3. Task Registers Value 4. Task State So if we can save all these in somewhere, there is no need for explicitly TCB storage. In USTOS, we store them like this: 1. Task PC Value In the stack (PUSH inexplicitly by hardware when MCU is executing CALL instruction or hardware interrupt happens) 2. Task Stack Pointer Value In the array OS_TaskStack[] 3. Task Registers Value In the stack (PUSH explicitly by assembly language) 4. Task State In the word OS_TaskStatus

So, the so-called distributed TCB storage is implemented and save lots of RAM space and maintaining high speeds for context switching.

-5-

HKUST COMP355

Project Report of a Small Real Time Operating System USTOS

2.4 Interrupt & Context Switching


Keil C51 compiler extends ANSI C to support a so-called interrupt function like:

void my_ISR(void) interrupt interrupt N

The interrupt function attribute, when included in a declaration, specifies that the associated function is an interrupt function. The interrupt attribute takes as an argument an integer constant in the 0 to 31 value range. Expressions with operators and the interrupt attribute are not allowed in function prototypes. In addition, the Cx51 compiler generates the interrupt vector automatically.

#pragma disable void userISR(void) interrupt N { OS_INT_ENTER(); //user ISR code here OS_INT_EXIT(); }

Every user ISR must be embraced by OS_INT_ENTER() and OS_INT_EXIT() between the user ISR code. The definition is like the following. Also, Keil C compiler will generate the PUSH registers automatically at the beginning of the ISR. So OS_SaveContext(void) do not need to push the registers into the stacks. At the same time, at the end of the ISR, instead of calling RET, we need to run the instruction RETI.

void OS_INT_ENTER() { OSIntNesting++; OS_SaveContext_INT(); Enable_Interrupt(); } void OS_INT_EXIT(void) { OS_ENTER_CRITICAL(); OSIntNesting--; if(OSIntNesting==0) { OS_Scheduler();

-6-

HKUST COMP355

Project Report of a Small Real Time Operating System USTOS

OS_LoadContext_INT(); }else { Os_Enter_Sum--; } }

// Gone and never return

// = OS_EXIT_CRITICAL() + Disable_Interrupt()

2.5 Nested Interrupt Service Routine


Nested interrupts is supported by recording the nesting count in OSIntNesting. Then when the ISR call OS_INT_EXIT before return, the OS will make use of OSIntNesting to determine whether performing scheduling algorithm, load new context and switch to the new task or just simply come back to the low priority level ISR. So you may see that nesting interrupt support is not so difficult to implement as you may imagine. A tricky technique is used to reduce the Critical count Os_Enter_Sum while still keep on disabling interrupt before POP back all registers. The Keil C51 compiler then will generate the code to pop back all register and after that a SETB EA to enable the interrupt again. If the code enable interrupt by calling OS_EXIT_CRITICAL() , the interrupt is enable when the CPU is poping back the registers. If at that time, another interrupt occurs, unpredictable and disaster result will come out. (This may not happen in 8051 MCU since there are at most 2 levels of priority for interrupts and no same priority interrupt will occurred by the hardware. But it will happen if USTOS is transplanted into other hardware.)

2.6 Critical Section


Critical Section is used for protect the shared data by disabling the interrupt.

#define OS_ENTER_CRITICAL() #define OS_EXIT_CRITICAL()

Disable_Interrupt() , Os_Enter_Sum++ if (--Os_Enter_Sum==0) Enable_Interrupt()

But why do we need the variable Os_Enter_Sum to record the levels of Critical Section the CPU enter? This is used to solve the potential problem that is mentioned at the bottom of Page 99 of the reference book [1]. A simple example to illustrate the problem by just using en/disable interrupt mechanism for critical sections is like this:

void function_A() { Disable_Interrupt(); Interrupt is disabled function_B(); Interrupt is enabled!!!! ERROR!!!! Enable_Interrupt(); }

-7-

HKUST COMP355

Project Report of a Small Real Time Operating System USTOS

void function_B() { Disable_Interrupt (); Enable_Interrupt(); }

Interrupt is disabled Interrupt is enabled

2.7 Timing Service


USTOS provides the timing service by the API void OS_TaskDelay(uint8 nTicks) which will delay the called task but nTicks numbers of Ticks. The unit of nTicks, Tick, includes OS_CONFIG_TICKS_CNT numbers of happening of OSTimer0_ISR(). This mechanism is to prevent too frequent calling the OS_TimeTick() functions which may waste lots of time, i.e., to slow down the timer in case you do not need it to run so quickly.

uint8 Current_Tick; #pragma disable void OSTimer0_ISR(void) interrupt 1 { Current_Tick = (Current_Tick + 1) % OS_CONFIG_TICKS_CNT; if (Current_Tick == 0) { OS_INT_ENTER(); OS_TimeTick(); OS_INT_EXIT(); } }

void OS_TaskDelay(uint8 nTicks) { if(nTicks==0) // prevent dead lock happen return; OS_ENTER_CRITICAL(); OS_RemainTick[OS_RunningTask] = nTicks; OS_TaskStatus = OS_TaskStatus & (~(0x01<<OS_RunningTask)); OS_SaveContext(); OS_Scheduler(); OS_LoadContext(); //Never return }

void OS_TimeTick(void) { for(i=0;i<OS_MAX_TASKS;i++) {

-8-

HKUST COMP355

Project Report of a Small Real Time Operating System USTOS

if ( OS_RemainTick[i]!=0 ) { OS_RemainTick[i]--; if(OS_RemainTick[i]==0) OS_TaskStatus = OS_TaskStatus | (0x01<<i); } } }

is used to awake the sleeping blocked tasks if the time for them to wake up arrives. It reduces the count in the OS_RemainTick[] array and check whether it is equal to zero. If yes, it will change the state of the that task into ready state. API function void OS_TaskDelay(uint8 nTicks) is used to set the remaining time in the OS_RemainTick[] array and change the running task into ready state. You may see that this function in fact is a reentrant function since nTicks is a local variable and other parts are embraced by critical section protection mechanism. Why it is written like this way? I will explain it in Section 2.8.

OS_TimeTick(void)

2.8 Basic Principles of Synchronization Mechanisms


All the synchronization includes three parts: 1. Create: which performs some initialization of the RAM where storages the information. 2. Receive: which may cause the task to wait for some condition and become block. 3. Send: which may cause the condition that other task is waiting for to become true and let that task to be available to run. The pseudo-code of the major skeleton framework is shown here.

Receive(Data_Pointer address, ) { OS_ENTER_CRITICAL(); if (not need to block what you need already exist) { //handling the return value and maintain structure OS_EXIT_CRITICAL(); return; } //Let it become not blocked OS_TaskStatus = OS_TaskStatus& (~(0x01<<OS_RunningTask)); //Decide whether or not to schedule OS_SaveContext(); OS_Scheduler(); OS_LoadContext(); //Never return }

-9-

HKUST COMP355

Project Report of a Small Real Time Operating System USTOS

Send(Data_Pointer address, ) { OS_ENTER_CRITICAL(); if (no need to block no one is waiting ) { //maintain structure OS_EXIT_CRITICAL(); return; } //Let it become not blocked OS_TaskStatus = OS_TaskStatus | (0x01<<((uint8 *)pMailbox)[3]); if(OSIntNesting==0) { OS_SaveContext(); OS_Scheduler(); OS_LoadContext(); //Never return }else { OS_EXIT_CRITICAL(); } }

You may need some time to read this pseudo-code and to figure out the mechanism of this common framework. With this skeleton code, all codes for semaphore etc become very simple. A special tricky part that needs more attention is the OSIntNesting problem. As we know, the OS cannot block the ISR routine or perform context switching before return back from the ISR. Be precisely, David E. Simon points out two rules that an ISR must obeys on Page 199 of the reference book [2]:

Rule 1: An interrupt routine must not call any RTOS function that might block the caller. Rule 2: An interrupt routine must not call any RTOS function that might cause the RTOS to switch tasks unless the RTOS knows that an interrupt routine, and not a task is executing.

And since every ISR will call OS_INT_ENTER() which will let OS knows that now Send() function is called by ISR or Tasks. The OS then can judge from this to know whether to perform context switching or not. Another place that needs to pay attention is the addressing of the data for synchronization, such as the place to save the semaphore, mailbox, or message queue. I have mentioned in Section 2.1 that USTOS uses static memory allocation. So the application programmer will define a place to put the data and pass an address pointer to the Receive() and Send() function. There is

- 10 -

HKUST COMP355

Project Report of a Small Real Time Operating System USTOS

not explicitly mapping between task ID and the data used for synchronization such as semaphores. And example of this is shown here.

Mailbox_t Mailbox1; OS_MailboxCreate(0,&Mailbox1); OS_MailboxSend(&Mailbox1,0x1234); OS_MailboxReceive(&Mailbox1,&rcvMsg);

As I mention is Section 2.7, the API function void OS_TaskDelay(uint8 nTicks) is much like the Receive() function here, which may cause the task to wait for some condition and become block. So the major skeleton is the same useful for the function void OS_TaskDelay(uint8 nTicks) nTicks).

2.9 Binary Semaphore


Binary semaphore is the most simple synchronization mechanism in USTOS. At the same time, it is also the most efficient one in USTOS. It will cost only 1 Byte RAM. The usage example:

BinSem_t BinSem1; OS_BinSemCreate(&BinSem1); OS_BinSemV(&BinSem1); OS_BinSemP(&BinSem1);

Let me elaborate the details about the implementation of the binary semaphore. The type definition of binary semaphore is here:

typedef uint8 BinSem_t;

One bit of the semaphore is used by indicating whether the corresponding task is waiting for that semaphore or not. If yes, the corresponding bit will be set to 1. Since the Idle Task will not try to get the semaphore, so the most significant bit which is corresponding to the idle task is used to indicate the value of the semaphore. The other things are similar as the skeleton code.

void OS_BinSemCreate(BinSem_t* pBinSem) { *pBinSem = 0x80;} void OS_BinSemP(BinSem_t* pBinSem) { OS_ENTER_CRITICAL();

- 11 -

HKUST COMP355

Project Report of a Small Real Time Operating System USTOS

if( (*pBinSem) == 0x80 ) { (*pBinSem)=0; OS_EXIT_CRITICAL(); return; } (*pBinSem) |= (0x01<<OS_RunningTask); // The same as the skeleton code } void OS_BinSemV(BinSem_t* pBinSem) { OS_ENTER_CRITICAL(); if( ( (*pBinSem) & 0x7f ) == 0 ) { *pBinSem = 0x80; OS_EXIT_CRITICAL(); return; } for(i=0;i<7;i++) // Find the tasks that is waiting and has the highest priority { if(((*pBinSem) & (0x01<<i))!=0) { (*pBinSem) = (*pBinSem) & (~(0x01<<i)) & 0x7f; OS_TaskStatus = OS_TaskStatus | (0x01<< i); break; } } // The same as the skeleton code }

2.10 Mailbox
The usage example of mailbox is already shown in Section 2.8. A mailbox needs 4 Bytes to store the information which contains 2 Bytes for message content, 1 Byte for the pointer of return address, and 1 Byte for saving the task ID of the task that is waiting.

typedef uint32 Mailbox_t; void OS_MailboxCreate(uint8 TaskID,Mailbox_t* pMailbox) { *pMailbox=0; ((uint8*)pMailbox)[3]=TaskID; } void OS_MailboxReceive(Mailbox_t* pMailbox,uint16* pRetV) { OS_ENTER_CRITICAL(); if(((uint16*)pMailbox)[0]!=0)

- 12 -

HKUST COMP355

Project Report of a Small Real Time Operating System USTOS

{ *pRetV=((uint16*)pMailbox)[0]; ((uint16*)pMailbox)[0]=0; ((uint8 *)pMailbox)[2]=0; OS_EXIT_CRITICAL(); return; } ((uint8*)pMailbox)[2] = (uint8)pRetV; // The same as the skeleton code } void OS_MailboxSend(Mailbox_t* pMailbox,uint16 Msg) { OS_ENTER_CRITICAL(); if(((uint8*)pMailbox)[2]==0) { ((uint16*)pMailbox)[0]=Msg; OS_EXIT_CRITICAL(); return; } *((uint16*)(((uint8*)pMailbox)[2])) = Msg; ((uint16*)pMailbox)[0]=0; ((uint8 *)pMailbox)[2]=0; // The same as the skeleton code }

2.11 Message Queue


The usage example is like here:
#define OS_MSGQ_OFFSET 5 uint8 MsgQ1[OS_MSGQ_OFFSET+4]; OS_MsgQCreate(2,MsgQ1,4); OS_MsgQPend(MsgQ1,&Msg2); OS_MsgQPost(MsgQ1,P1);

The message queue is kind of RAM consuming mechanism. At least 6 Bytes are needed to implement a message queue. The message size for message queue is 1 Bytes which is a half of the message size in mailbox. The data structure of message queue is like this:

Byte 1 Byte 2 Byte 3 Byte 4 Byte 5 Byte 6 Byte 7 Byte 8 .... [TOTAL_SIZE] [Pointer] [TaskID] [Return Address] [CurrentSize] [Data0] [Data1] [Data2] ....

- 13 -

HKUST COMP355

Project Report of a Small Real Time Operating System USTOS

TOTAL_SIZE: Record the length of the array Date[0] to Date[TOTAL_SIZE-1] Pointer: Use to refer to the Data[Pointer] is the head of next out-queue message TaskID: The task ID of the waiting task Return Address: the pointer to RAM about where to put the message CurrentSize: The total size of the non-empty messages slots Date[0] to Date[TOTAL_SIZE-1]: The place to save the message. The specific code to manipulate this structure is shown below. It looks complex. But in fact, if you can understand the mailbox, it is easy to read through it. It is just some ugly detail stuffs to record lots of information, so you may not even need to read this since it will not help too much for your understanding. Why I show it here is just for completeness.

void OS_MsgQCreate(uint8 TaskID,uint8* pMsgQ,uint8 uSize) { pMsgQ[0]=uSize; pMsgQ[1]=0; pMsgQ[2]=TaskID; pMsgQ[3]=0; pMsgQ[4]=0; } void OS_MsgQPend(uint8* pMsgQ,uint8* pRetV) { OS_ENTER_CRITICAL(); if(pMsgQ[4]!=0) { *pRetV=pMsgQ[OS_MSGQ_OFFSET+pMsgQ[1]]; pMsgQ[1] = (pMsgQ[1]+1) % pMsgQ[0]; pMsgQ[4]--; pMsgQ[1]++; OS_EXIT_CRITICAL(); return; } pMsgQ[3] = (uint8)pRetV; // The same as the skeleton code } void OS_MsgQPost(uint8* pMsgQ,uint8 Msg) { OS_ENTER_CRITICAL(); if(pMsgQ[3]==0) { //No one is waiting for message pMsgQ[4] ++; //Increase size if(pMsgQ[4]>pMsgQ[0]) //Full now pMsgQ[4] --; //Never block, but the last msg will be over-written pMsgQ[ OS_MSGQ_OFFSET + ( pMsgQ[1] + pMsgQ[4] -1 ) % pMsgQ[0] ] = Msg; //Save the message

- 14 -

HKUST COMP355

Project Report of a Small Real Time Operating System USTOS

OS_EXIT_CRITICAL(); return; } //Some one is waiting. *((uint8*)(pMsgQ[3])) = Msg; //Return the message content pMsgQ[3]=0; //Clear return address, Indicate that no one is waiting // The same as the skeleton code }

2.12 Idle Tasks


This is the simplest part of USTOS. The only attention needed is that since idle task is doing nothing, some part of USTOS make use of this and do not save any values while context switching in order to speed up the system.

void OS_IdleTask(void)

{ while(1){ /* Save Power and Statistic*/; } }

2.13 Event Group


I have tried for several hours and found that Event Group needs so many RAM space to store all the information needed. So it is unpractical and unfeasible to implement it for 8051 which has very small RAM (128Byte + 128Byte). In case USTOS is transplanted into other hardware which has large RAM, it is feasible to implement it. The Data Structure of EventGroup may be like this:

[EventValue] [EventMatch] [Waiting?] [Task1_Mask] [Task2_Mask] [Task2_Mask] ... BitIndicator 0=Not Waiting Not 0 = Waiting For AND/OR Higher Priority Lower Priority

2.14 EDF Scheduling


Earliest deadline first scheduling is optimal and efficient. If a dynamic priority schedule exists, EDF will produce a feasible schedule. A dynamic priority schedule exists if and only if utilization is no greater than 100%. But EDF need too many extra RAM space in order to record the all information that EDF needed. This is the disaster reason for 8051 series MCU. And at the same time, the application programmer must have some way to point out when is the deadline for all tasks. And the OS have to maintain this information. Thus, EDF is complicated enough to have unacceptable overhead. After considering the RAM limit of 8051

- 15 -

HKUST COMP355

Project Report of a Small Real Time Operating System USTOS

and the information needed to store for EDF, it seems impossible to implement it in 8051. So I do not try to implement it. This is the trade off of engineering.

2.15 Keil C51 Compiler's Colorizing Memory Allocation


Because the limit size of 8051s RAM, Keil C extent the ANSI C to generate better codes which require less RAM and run faster. One extension is to let the compiler not to push all local register into memory stacks when a function is calling the other functions. This means that Keil C51 compiler allocates the local variables into fixed-addressing RAM. In another word, Keil C51 compiler let the local variables become global variable. This is a special case needed more attention. In the other side, it is easy to show that the RAM may have higher probability to run out if the program keeps on running different functions. Keil C51 compiler cures this by the way of Data Overlaying Optimizing. It will be further discussed in Section 4.2.

2.16 Keil C51 Compiler's Unpredictable Behaviors


In Keil C51 compiler, for interrupt service routine, the contents of the R0 to R7, SFR ACC, B, DPH, DPL, and PSW, when required, are saved on the stack at function invocation time. This means when unnecessary, they will not be saved. So there is no way for USTOS know how many and which registers are pushed into the stack. In order to make them all push the same registers when interrupts happen, and deal with the over-clever Keil C51 compilers such unpredictable behaviors, I have to add several lines of useless code in order to make the code more complex and force the Keil C51 Compiler push all registers for all ISRs. Such useless codes are put at the beginning of the OS_INT_ENTER(void).

void OS_INT_ENTER(void) { //The following code is used for prevent the Keil C51's stupid optimization uint8* pointer=0x02; *pointer = 0xff; DPL =0x00; DPH =0x00; B =0x00; PSW =0x00; ACC =0x00; .. }

2.17 Other Ugly Stuffs

- 16 -

HKUST COMP355

Project Report of a Small Real Time Operating System USTOS

Other things that need to handle is the initial value of the system variables and hardware registers for RTOS and the task stacks initialization. Such codes are put in the fuction void OSInit(void) and void OSStart(void). Another problem is that for RTOS, there are always many source files. Some users keep on complaining the linking errors. In order to prevent such errors and make everything become simpler, I write all the USTOS code in one file containing the users source code. The application programmer is expected to divide their source codes into two parts. One part is the global data definition and the other part is the running code. Then they put the two parts into the place that the source file of USTOS instructed. This will greatly enhance the probability of successfully linking of the source code.

3. How to Use USTOS


3.1 USTOS API
There are 16 API functions for USTOS. Most of them are already mentioned in part 2.

01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16

uint8 OSVersion(); uint8 OSRunningTaskID(); void OS_INT_ENTER(void); void OS_INT_EXIT(void); void OSStart(void); void OSInit(void); void OS_TaskDelay(uint8 nTicks); void OS_MsgQCreate(uint8 TaskID,uint8* pMsgQ,uint8 uSize); void OS_MsgQPost(uint8* pMsgQ,uint8 Msg); void OS_MsgQPend(uint8* pMsgQ,uint8* pRetV); void OS_MailboxCreate(uint8 TaskID,Mailbox_t* pMailbox); void OS_MailboxSend(Mailbox_t* pMailbox,uint16 Msg); void OS_MailboxReceive(Mailbox_t* pMailbox,uint16* pRetV); void OS_BinSemCreate(BinSem_t* pBinSem); void OS_BinSemV(BinSem_t* pBinSem); void OS_BinSemP(BinSem_t* pBinSem); API functions of USTOS

3.2 USTOS Configuration

- 17 -

HKUST COMP355

Project Report of a Small Real Time Operating System USTOS

// Functional Configuration #define OS_CONFIG_EN_MAILBOX #define OS_CONFIG_EN_MSGQ #define OS_CONFIG_EN_BIN_SEMAPHORE #define OS_CONFIG_EN_SYSTEM_TIMER #define OS_CONFIG_EN_INT_NESTING //Timer Setup #define OS_CONFIG_TICKS_CNT

1 = Enable 0 = Disable 1 Support for mailbox ? 1 Support for message queue ? 1 Support for binary semaphore ? 1 Support for system timing service? 1 Support for interrupt nesting?

32

How many times of interrupt convert to one system ticks?

//Task Information Setup #define OS_MAX_TASKS #define OS_TASK_STACK_SIZE

4 20

Number of Tasks Stack size of Tasks (Unit: Byte)

3.3 Examples
This is a simple example of using semaphores, mailbox, as well as messages queue and system timing service.
//--------------------------------------------------------// Part I: User config //--------------------------------------------------------uint8 idata Stack0[OS_TASK_STACK_SIZE]; uint8 idata Stack1[OS_TASK_STACK_SIZE]; uint8 idata Stack2[OS_TASK_STACK_SIZE]; uint8 idata StackIdle[OS_TASK_STACK_SIZE]; //Static Create Tasks void Task0(void); void Task1(void); void Task2(void); CodePointer_t OS_TaskCode [OS_MAX_TASKS]={Task0,Task1,Task2,OS_IdleTask}; DataPointer_t OS_TaskStack[OS_MAX_TASKS]={Stack0,Stack1,Stack2,StackIdle}; //Static Create Mailbox Mailbox_t Mailbox1; BinSem_t BinSem1; uint8 MsgQ1[OS_MSGQ_OFFSET+4]; //--------------------------------------------------------// Part II: User code //--------------------------------------------------------#pragma disable void myISR1(void) interrupt 0 { OS_INT_ENTER();

- 18 -

HKUST COMP355

Project Report of a Small Real Time Operating System USTOS

OS_MailboxSend(&Mailbox1,0x5678); OS_INT_EXIT(); } #pragma disable void myISR2(void) interrupt 2 { OS_INT_ENTER(); OS_MsgQPost(MsgQ1,P1); OS_INT_EXIT(); } void Task0(void) { uint16 rcvMsg; while(1) { OS_MailboxReceive(&Mailbox1,&rcvMsg); P1=rcvMsg/256; P0=rcvMsg%256; } } void Task1(void) { uint8 myflag=1; while(1) { if(myflag) { myflag=0; OS_MailboxSend(&Mailbox1,0x1234); }else { OS_BinSemP(&BinSem1); OS_BinSemP(&BinSem1); OS_TaskDelay(1); } } } void Task2(void) { uint8 Msg2; while(1) { OS_MsgQPend(MsgQ1,&Msg2); OS_BinSemV(&BinSem1); } } void main(void) { OSInit(); OS_MailboxCreate(0,&Mailbox1); OS_BinSemCreate(&BinSem1); OS_MsgQCreate(2,MsgQ1,4); PX0=0; IT0=1; PX1=1; IT1=1; //Extern Interrupt TMOD = (TMOD & 0XF0) | 0X01; //System Timer Setup TH0 = 0x0; TL0 = 0x0; //Time interval = 0xFFFF TR0 = 1; ET0 = 1; PT0 = 1;

- 19 -

HKUST COMP355

Project Report of a Small Real Time Operating System USTOS

OSStart(); }

4. Hardware/software Requirements
4.1 Hardware
I choose Intel 8051 microcontroller as the hardware platform in this project. This is because it is cheap, and slow. Slow speed means that the resource is really limited and the main characteristics of embedded system are shown most explicitly. So it is a good time to practice the principles of embedded system. The big picture of the architecture of 8051 MCU is like the following (It is from the reference [6]). If you need further information, you are encouraged to read the materials in the reference [6].

4.2 Software
The tool chain that I plan to use is like following: Cross-compiler: Small Device C Compiler. Simulator: 8051 Microcontroller Series Simulator Cross-compiler: KeilC51 Compiler IDE Downloader: AT89S PC Based Programmer

- 20 -

HKUST COMP355

Project Report of a Small Real Time Operating System USTOS

But after trying all of them, I found that the SDCC compiler and the 8051 Microcontroller Series Simulator are too weak in functionality and difficult and inefficient for coding and debugging. While at the same time, Keil C51 complier together with uVision IDE, the most popular developing environment around the world for 8051, offers an excellent programming & debugging environment, as well as very precise simulator. Please notice that Keil C51 compiler is NOT free software. But you may get an evaluation edition at the Keils Website: http://www.keil.com/demo/. Since the special characteristic of this compiler, I cannot guarantee USTOS can be compiled successfully on all the versions of the compiler. The version of my complier and simulator are Keil C51 V7.50 and uVision V2.40. The target MCU is the most popular 8051 series MCU -- Atmel AT89S52. A real world RTOS is not just to be used by one hardware platform. So I planed to divide the USTOS into two separate parts. One is hardware platform independent part which is all written in C language. The other is hardware dependent part written in both C and assembly language. But when I was implementing, I realize that I almost do not need any assembly language at all since Keil C51 compiler have all kinds of functions to help. And at the same time, the USTOS V1.0 makes use of lots of characteristic of Keil C51 compiler and 8051 hardware architecture in order to speed up and save RAM. So it is very difficult and inefficient in run time if I divide USTOS into two separate parts by brute force. And since there is no packet support package for 8051 series microcontroller, I start all my codes from scratch under the help of some description about the hardware architecture of 8051 series microcontroller.

4. 3 Compiler Setup
Keil C51 Compiler has 10 levels of optimization. Each higher optimization level contains all of the characteristics of the preceding lower optimization level. USTOS supports Level 0 to Level 7 optimization. Please read the reference [7] for more details. Level 0 Constant Folding: The compiler performs calculations that reduce expressions to numeric constants, where possible.This includes calculations of run-time addresses. Simple Access Optimizing: The compiler optimizes access of internal data and bit addresses in the 8051 system. Jump Optimizing: The compiler always extends jumps to the final target. Jumps to jumps are deleted. Level 1 Dead Code Elimination: Unused code fragments and artifacts are eliminated. Jump Negation: Conditional jumps are closely examined to see if they can be streamlined or eliminated by the inversion of the test logic. Level 2

- 21 -

HKUST COMP355

Project Report of a Small Real Time Operating System USTOS

Data Overlaying: Data and bit segments suitable for static overlay are identified and internally marked. The BL51 Linker/Locator has the capability, through global data flow analysis, of selecting segments which can then be overlaid. Level 3 Peephole Optimizing: Redundant MOV instructions are removed. This includes unnecessary loading of objects from the memory as well as load operations with constants. Complex operations are replaced by simple operations when memory space or execution time can be saved. Level 4 Register Variables: Automatic variables and function arguments are located in registers when possible. Reservation of data memory for these variables is omitted. Extended Access Optimizing: Variables from the IDATA, XDATA, PDATA and CODE areas are directly included in operations. The use of intermediate registers is not necessary most of the time. Local Common Sub expression Elimination: If the same calculations are performed repetitively in an expression, the result of the first calculation is saved and used further whenever possible. Superfluous calculations are eliminated from the code. Case/Switch Optimizing: Code involving switch and case statements is optimized as jump tables or jump strings. Level 5 Global Common Sub expression Elimination: Identical sub expressions within a function are calculated only once when possible. The intermediate result is stored in a register and used instead of a new calculation. Simple Loop Optimizing: Program loops that fill a memory range with a constant are converted and optimized. Level 6 Loop Rotation: Program loops are rotated if the resulting program code is faster and more efficient. Level 7 Extended Index Access Optimizing: Uses the DPTR for register variables where appropriate. Pointer and array access are optimized for both execution speed and code size.

- 22 -

HKUST COMP355

Project Report of a Small Real Time Operating System USTOS

5. Conclusions
5.1 Summary
This project is challenging as an undergraduate courses project within such short period of time. But through this project, I know the principles of the embedded system software more deeply and USTOS will be a small enough OS for other beginner to read and learn.

5.2 Future Work


Because this is just a UG course project and not a research project, no new scheduling method is invented. And thus it emphasizes the implementation but not the creativity. If it is good to have new scheduling algorithm, I will add it to USTOS. Another possible improvement is to implement the Priority Inheritance Protocol to solve to Priority Inversion Problem in USTOS. Some other possible development may contain transplanting USTOS into other hardware. Nevertheless, USTOS will be an open-source RTOS for anyone to use and study. Hopefully my work can be used by industry or by education.

6. References
[1] Jogesh K. Muppala, HKUST COMP355 Embedded System Software Lecture Notes and Course Materials, http://www.cs.ust.hk/%7Emuppala/comp355/ [2] David E. Simon, An Embedded Software Primer, Addison-Wesley, 1999. [3] Raj Kamal, Embedded Systems: Architecture, Programming and Design, McGraw-Hill, 2003. [4] Dreamtech Software Team, Programming for Embedded Systems: Cracking the Code, Wiley, 2002. [5] Atmel Corporation: Manuals of AT89S52 chip [6] Tim K. T. WOO, HKUST ELEC 254 Course Materials, http://course.ee.ust.hk/elec254/ [7] Keil Software Corporation: Users Guide of Cx51 Compiler, http://www.keil.com/

- 23 -

You might also like