This document describes the design and implementation of a small real-time operating system called USTOS for 8051 microcontrollers. USTOS supports priority-based task scheduling, nested interrupts, timers, and synchronization mechanisms using limited RAM. Context switching is implemented by pushing registers to stacks and changing the stack pointer. Interrupts are supported by tracking nesting levels and scheduling tasks on exit. Critical sections are implemented using an interrupt disable counter to prevent reenabling interrupts too early. Timers are implemented using periodic interrupts to tick a counter and wake delayed tasks.
Mastering MySQL Database Architecture: Deep Dive into MySQL Shell and MySQL R...
Project report of ustos
1. 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: cs_xjx@ust.hk
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.
2. 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
3. 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
4. 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:::: RecoverRecoverRecoverRecover the Stack Pointerthe Stack Pointerthe Stack Pointerthe Stack Pointer
SP = OS_TaskStack[OS_RunningTask];
Step 2: POPStep 2: POPStep 2: POPStep 2: POP all registersall registersall registersall 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.
5. 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.
6. 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();
7. 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();
}
8. 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++)
{
9. 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
}
10. 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
11. 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(uint8void OS_TaskDelay(uint8void OS_TaskDelay(uint8void 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();
12. 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)
13. 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] ....
14. 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
15. 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
16. 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
17. 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 voidvoidvoidvoid
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
18. 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();
20. 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
21. 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
22. 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.
23. 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/