project report of ustos

23
HKUST COMP355 Project Report of a Small Real Time Operating System – USTOS - 1 - HKUST COMP355 Embedded Systems Software Project Report of a Small Real Time Operating System – USTOS Instructor: Prof. Jogesh K. Muppala Student Name: XIAO, Jianxiong Student Email: [email protected] Student ID: 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 today’s 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.

Upload: murali-mc-mirle

Post on 21-Jun-2015

1.648 views

Category:

Technology


5 download

TRANSCRIPT

Page 1: Project report of ustos

HKUST COMP355 Project Report of a Small Real Time Operating System – USTOS

- 1 -

HKUST COMP355 Embedded Systems Software

Project Report of a Small Real Time Operating System – USTOS

Instructor: Prof. Jogesh K. Muppala

Student Name: XIAO, Jianxiong

Student Email: [email protected]

Student ID: 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 today’s 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.

Page 2: Project report of ustos

HKUST COMP355 Project Report of a Small Real Time Operating System – USTOS

- 2 -

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

world 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

Page 3: Project report of ustos

HKUST COMP355 Project Report of a Small Real Time Operating System – USTOS

- 3 -

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 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];

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 registersStep 1: PUSH all registersStep 1: PUSH all registersStep 1: PUSH all registers

PUSH ACC

PUSH B

PUSH DPH

PUSH DPL

PUSH PSW

PUSH 0x00

Page 4: Project report of ustos

HKUST COMP355 Project Report of a Small Real Time Operating System – USTOS

- 4 -

PUSH 0x01

PUSH 0x02

PUSH 0x03

PUSH 0x04

PUSH 0x05

PUSH 0x06

PUSH 0x07

Step 2: Save the Stack PointerStep 2: Save the Stack PointerStep 2: Save the Stack PointerStep 2: Save the Stack Pointer

OS_TaskStack[OS_RunningTask]=SP;

}

void OS_LoadContext(void)

{

Step 1Step 1Step 1Step 1: : : : Recover Recover Recover Recover the Stack Pointerthe Stack Pointerthe Stack Pointerthe Stack Pointer

SP = OS_TaskStack[OS_RunningTask];

Step 2: POPStep 2: POPStep 2: POPStep 2: POP all registers all registers all registers 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 3Step 3Step 3Step 3: : : :

OS_EXIT_CRITICAL();

Step 4Step 4Step 4Step 4: : : : Start to RunStart to RunStart to RunStart 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.

Page 5: Project report of ustos

HKUST COMP355 Project Report of a Small Real Time Operating System – USTOS

- 5 -

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, let’s 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.

Page 6: Project report of ustos

HKUST COMP355 Project Report of a Small Real Time Operating System – USTOS

- 6 -

2.4 Interrupt & Context Switching

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

void my_ISR(void) interrinterrinterrinterruptuptuptupt 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();

Page 7: Project report of ustos

HKUST COMP355 Project Report of a Small Real Time Operating System – USTOS

- 7 -

OS_LoadContext_INT(); // Gone and never return

}else

{

Os_Enter_Sum--; // = 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() Disable_Interrupt() , Os_Enter_Sum++

#define OS_EXIT_CRITICAL() 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();

}

Page 8: Project report of ustos

HKUST COMP355 Project Report of a Small Real Time Operating System – USTOS

- 8 -

void function_B()

{

Disable_Interrupt ();

…… � Interrupt is disabled

Enable_Interrupt();

� Interrupt is enabled

}

2.7 Timing Service

USTOS provides the timing service by the API “void OS_TaskDelay(uint8 nTicks)void OS_TaskDelay(uint8 nTicks)void OS_TaskDelay(uint8 nTicks)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++)

{

Page 9: Project report of ustos

HKUST COMP355 Project Report of a Small Real Time Operating System – USTOS

- 9 -

if ( OS_RemainTick[i]!=0 )

{ OS_RemainTick[i]--;

if(OS_RemainTick[i]==0)

OS_TaskStatus = OS_TaskStatus | (0x01<<i);

}

}

}

OS_TimeTick(void)OS_TimeTick(void)OS_TimeTick(void)OS_TimeTick(void) 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)void OS_TaskDelay(uint8 nTicks)void OS_TaskDelay(uint8 nTicks)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.

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

}

Page 10: Project report of ustos

HKUST COMP355 Project Report of a Small Real Time Operating System – USTOS

- 10 -

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

Page 11: Project report of ustos

HKUST COMP355 Project Report of a Small Real Time Operating System – USTOS

- 11 -

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)void OS_TaskDelay(uint8 nTicks)void OS_TaskDelay(uint8 nTicks)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 void OS_TaskDelay(uint8 void OS_TaskDelay(uint8 void OS_TaskDelay(uint8

nTinTinTinTicks)cks)cks)cks)”.

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();

Page 12: Project report of ustos

HKUST COMP355 Project Report of a Small Real Time Operating System – USTOS

- 12 -

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)

Page 13: Project report of ustos

HKUST COMP355 Project Report of a Small Real Time Operating System – USTOS

- 13 -

{

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

Page 14: Project report of ustos

HKUST COMP355 Project Report of a Small Real Time Operating System – USTOS

- 14 -

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

Page 15: Project report of ustos

HKUST COMP355 Project Report of a Small Real Time Operating System – USTOS

- 15 -

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

Page 16: Project report of ustos

HKUST COMP355 Project Report of a Small Real Time Operating System – USTOS

- 16 -

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 8051’s 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 compiler’s 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

Page 17: Project report of ustos

HKUST COMP355 Project Report of a Small Real Time Operating System – USTOS

- 17 -

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

OSInit(void)OSInit(void)OSInit(void)OSInit(void) and void OSStart(void)void OSStart(void)void OSStart(void)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 uint8 OSVersion();

02 uint8 OSRunningTaskID();

03 void OS_INT_ENTER(void);

04 void OS_INT_EXIT(void);

05 void OSStart(void);

06 void OSInit(void);

07 void OS_TaskDelay(uint8 nTicks);

08 void OS_MsgQCreate(uint8 TaskID,uint8* pMsgQ,uint8 uSize);

09 void OS_MsgQPost(uint8* pMsgQ,uint8 Msg);

10 void OS_MsgQPend(uint8* pMsgQ,uint8* pRetV);

11 void OS_MailboxCreate(uint8 TaskID,Mailbox_t* pMailbox);

12 void OS_MailboxSend(Mailbox_t* pMailbox,uint16 Msg);

13 void OS_MailboxReceive(Mailbox_t* pMailbox,uint16* pRetV);

14 void OS_BinSemCreate(BinSem_t* pBinSem);

15 void OS_BinSemV(BinSem_t* pBinSem);

16 void OS_BinSemP(BinSem_t* pBinSem);

API functions of USTOS

3.2 USTOS Configuration

Page 18: Project report of ustos

HKUST COMP355 Project Report of a Small Real Time Operating System – USTOS

- 18 -

// Functional Configuration 1 = Enable 0 = Disable

#define OS_CONFIG_EN_MAILBOX 1 Support for mailbox ?

#define OS_CONFIG_EN_MSGQ 1 Support for message queue ?

#define OS_CONFIG_EN_BIN_SEMAPHORE 1 Support for binary semaphore ?

#define OS_CONFIG_EN_SYSTEM_TIMER 1 Support for system timing service?

#define OS_CONFIG_EN_INT_NESTING 1 Support for interrupt nesting?

//Timer Setup

#define OS_CONFIG_TICKS_CNT 32 How many times of interrupt convert

to one system ticks?

//Task Information Setup

#define OS_MAX_TASKS 4 Number of Tasks

#define OS_TASK_STACK_SIZE 20 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();

Page 19: Project report of ustos

HKUST COMP355 Project Report of a Small Real Time Operating System – USTOS

- 19 -

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;

Page 20: Project report of ustos

HKUST COMP355 Project Report of a Small Real Time Operating System – USTOS

- 20 -

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

Page 21: Project report of ustos

HKUST COMP355 Project Report of a Small Real Time Operating System – USTOS

- 21 -

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 Keil’s 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

Page 22: Project report of ustos

HKUST COMP355 Project Report of a Small Real Time Operating System – USTOS

- 22 -

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.

Page 23: Project report of ustos

HKUST COMP355 Project Report of a Small Real Time Operating System – USTOS

- 23 -

5. Conclusions

5.1 Summary

This project is challenging as an undergraduate course’s 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: User’s Guide of Cx51 Compiler, http://www.keil.com/