Advertisement

Embedded Software Design and Development

Chapter

Abstract

The embedded system software development has the same Software Development Life Cycle (SDLC) just like any other software development, plus special consideration for resource constraints, including CPU, time, memory, operating system, multi-tasking concurrency, and many other non-functional attribute constraints. In order to reduce the time-to-market and guarantee the reliability of the embedded system product, software engineering methodology is recommended for the software design and development. Because the embedded software is not deployed on a general purpose computer system, the embedded software in C/C++ or other high level programming languages must be developed and tested on a cross-platform machine such as a PC, and then loaded to a target microcontroller memory to be tested as shown in Figure 2.1.

For a successful embedded system development, embedded system hardware and software co-design based on the system specification is also very necessary to get the prototype working smoothly. In the co-design phase, software analysts/designers need to work with hardware team members to decide on the type of microcontrollers and microprocessors in accordance with the requirements of the speed and memory, power consumption, the number of I/O devices such as ADC, cost per unit, size and packaging, and others. Also, software development tools must be selected and the partition between software and hardware implementation should be decided in the co-design phase, as well as decisions such as time delay implementation by software program or with a hardware timer.

Keywords

Embed System Finite State Machine Earliest Deadline First Context Switch Embed Software 
These keywords were added by machine and not by the authors. This process is experimental and the keywords may be updated as the learning algorithm improves.

Objectives

  • Use state Chart in embedded software analysis and design

  • Apply time requirement analysis for real-time systems

  • Understand the concepts of polling and interrupt

  • Understand Multi-tasking Design and RTOS

2.1 Overview

The embedded system software development has the same Software Development Life Cycle (SDLC) just like any other software development, plus special consideration for resource constraints, including CPU, time, memory, operating system, multi-tasking concurrency, and many other non-functional attribute constraints. In order to reduce the time-to-market and guarantee the reliability of the embedded system product, software engineering methodology is recommended for the software design and development. Because the embedded software is not deployed on a general purpose computer system, the embedded software in C/C++ or other high level programming languages must be developed and tested on a cross-platform machine such as a PC, and then loaded to a target microcontroller memory to be tested as shown in Figure 2.1.
Fig. 2.1

Embedded system co-designs

For a successful embedded system development, embedded system hardware and software co-design based on the system specification is also very necessary to get the prototype working smoothly. In the co-design phase, software analysts/designers need to work with hardware team members to decide on the type of microcontrollers and microprocessors in accordance with the requirements of the speed and memory, power consumption, the number of I/O devices such as ADC, cost per unit, size and packaging, and others. Also, software development tools must be selected and the partition between software and hardware implementation should be decided in the co-design phase, as well as decisions such as time delay implementation by software program or with a hardware timer.

The SDLC proceeds in phases consisting of system specification, analysis, design, coding, and testing. The dashed lines indicate the required iteration processes when design changes are needed as shown in Figure 2.2.
Fig. 2.2

SDLC

The requirements specification provides an unambiguous description of system functionality and non-functional attribute requirements. It is the basis of the initial modeling and design processes. The modeling process analyzes the requirements specification and presents an abstraction model describing the embedded software structure and behavior. It is very beneficial to large complex embedded software design. Software design process/phase provides a blueprint for development implementation, which divides the whole system into subsystem partitions and specifies the relationships between the subsystems. Non-functional requirements are also concerns in software design. Both the modeling process and design process are independent from coding in term of programming methodology and language implementation. Lack of good software modeling and design processes will result in the repetition of the life cycle and will have significant impact on software quality and the time to market.

2.2 Software Requirement Specification

Compared to general purpose software, the non-functional requirements are especially important to all embedded software due to various constraints in resources and performance. All the quality attributes should be identified in the requirement analysis process. Quality attributes can be categorized into three groups:
  1. 1.
    Implementation attributes (not observable at runtime)
    • Resource availability: This refers to memory space, power supplier, CPU frequency, and RTOS.

    • Maintainability & extensibility: This refers to the ability to modify the system and extend it conveniently.

    • Testability: This refers to the degree to which the system facilitates the establishment of test cases. It usually requires a complete set of documentation accompanied with the system design and implementation.

    • Flexibility: This refers to the ease of modification of a system to cater to different environments or problems for which the system is not originally designed. Systems developed using the component-based architecture or the service-oriented architecture usually possesses this property.

     
  2. 2.

    Runtime attributes (observable at runtime)

    • Availability: This refers to the ability of a system to be available 24 ×7. Availability can be achieved via replication and careful design to cope with failures of hardware, software, or the network

    • Security: This refers to the ability to cope with malicious attacks from outside or inside the system. Security can be improved by installing firewalls and establishing authentication and authorization processes, and using encryption.

    • Performance: This refers to increasing efficiency such as response time, throughput and general resource utilization, which most of the time conflict with each other.

    • Usability: This refers to the level of “satisfaction” from a human perspective in using the system. Usability includes completeness, correctness, compatibility, and user friendliness such as a friendly user interface, complete documentation, and help support.

    • Reliability: This refers to the failure frequency, the accuracy of output results, the mean-time-to-failure (MTTF), the ability to recover from failure, and the failure predictability

     
  3. 3.

    Business attributes

    • Time to market: This refers to the time it takes from requirement analysis to the date the product is released.

    • Cost: This refers to the expense of building, maintaining, and operating the system.

    • Lifetime: This refers to the period of time that the product is “alive” before retirement.

     

2.3 Embedded Software Modeling Analysis and Design

2.3.1 Context Diagram

The analysis process starts with the system context analysis, system key tasks, and identification of their relationship. The focus is given to the reactive behaviors of the embedded software. The event behavior features include: periodic or a-periodic events, parallel or serial data processing, synchronous or asynchronous communication, hard real-time or soft real-time response reaction, and others. Based on the system modeling, the design process produces an implementation guideline for developers such as multi-tasking processing architecture.

The context diagram generation may be the first step in the embedded software modeling process. The context diagram is derived from the requirements specification. It provides a preliminary draft of the embedded software and its environment, which shows all events the system interacts with. It specifies elements of a system, data and data flow around the system, and the system internal and external communication.

Here is an example of a multiple room temperature control system specification. The system provides centralized and distributed temperature control functions. A local desired temperature can be set by a room control panel in each individual room or a global temperature can be set at the central control panel. The temperature sensors are installed in each room to monitor the current room temperature. The A/C and furnace are turned on or off by the controller depending on the desired temperature and the current temperature. Each room has a vent driven by a motor to control the air flow so that different rooms may reach different desired temperatures.

Based on Figure 2.3(a), we can derive a preliminary software partition context diagram shown in Figure 2.3(b), which consists of the controller module, keypad module, sensor module, LCD module, vent motor control module, A/C unit control module, furnace control module, etc. This context diagram clearly depicts the input events for this embedded software and the actuators controlled by this embedded software. At this stage, the most important issue is to fully understand the system requirement (functional and non-functional) so that we can decide on the selection of microcontroller, communication mode (serial or parallel communication) with devices, adoption of RTOS, and modeling of the embedded software in the co-design phase.
Fig. 2.3

(a) HVDC system (b) Context Diagram

2.3.2 Finite State Machine (FSM) and State Chart

As we know, system modeling presents an abstraction of the system (a high level description) in software aspects, which helps understanding of the functional requirements in block diagram form, and helps to identify all required software elements and tasks. There are many modeling tools available for modeling the behaviors of embedded system software, such as Universal Modeling Language (UML) and Petri Net. The state Chart diagram is a very popular analysis modeling method for real-time embedded software, which is derived from Finite State Machine (FSM).

A FSM consists of a finite number of states, transitions between the states, and actions to be performed. A state represents a certain behavior which lasts for a period of time while performing tasks or being idle waiting for events. Figure 2.4 is the state notation where you can specify the entrance actions, state actions, and exit actions.
Fig. 2.4

State Notation

A transition indicates a state change guided by conditions or events. It is a response to an event with a set of attributes that moves the system from one state to another state. There may be many transition links from one state to other states with different events or with the same event but different guide conditions. An event can refer to either an internal event (such as timer timeout overflow) or external event (a car detected by sensor). A condition can refer to a set of variable values, and a reaction can refer to reassignment of these variables or to new event creation. Figure 2.5 shows a transition link where the guide condition and reaction are optional.
Fig. 2.5

Transition Link

A FSM diagram can describe many software behaviors, such as a microwave or glucose monitor. Here is a simplified FSM diagram for a glucose monitor which is a typical embedded system.

When the test strip is inserted into the monitor, the system moves to a ready state where it is waiting for the blood sample, and the system moves back to the idle state when the strip is removed. At this time, the patient can set the strip code in two digits and it is saved in the data store. If the blood sample is not enough, the error message is displayed in the display state and system goes back to the ready state to wait for a new blood sample. If the blood sample is valid, it moves to the computation state to analyze the glucose level with the code reference from the data store and reports the result. A FSM can be easily implemented by a program shown next.
Fig. 2.6

Glucose Meter FSM

The following fragment describes the behavior of the FSM in Figure 2.7 above:
Fig. 2.7

A Simple FSM

init state S0;

while(1)

{

switch(state)

{

S0: action0;

if (cond1) state = S1;

else (cond2) state =...;

break;

S1: action1;

if (cond3) state=... ;

else if (cond4) state =... ;

else state = S0;

break;

S2: action2;

if (...)...

}

}

However, a FSM cannot represent multiple states that are simultaneously active nor describe any nested states within states in a hierarchical structure. It can only be used for a simple system with few states. A simple flat FSM for a counter or validation task is shown in Figure 2.8 where S1-S4 takes inputs and validates them. If the inputs are not valid then the system moves to state S5 to handle exceptions, and then goes back to initial state to start over again. It will be too complicated if the number of states becomes very large.
Fig. 2.8

Flat FSM

Figure 2.9 Group these states to make a single nested state S’ so that the transition between S’ and S is greatly simplified.
Fig. 2.9

Nested State Chart

In this way, a state can be refined into a set of sub-states until each state is simple enough to have its own responsibility. Figure 2.10 shows the init state (AC-off), final state, and AC-On state in a state Chart diagram.
Fig. 2.10

A complete State Chart

The following diagram, Figure 2.11, demonstrates two concurrent tasks that are active at same time.
Fig. 2.11

A simple State Chart

Two tasks can be running concurrently with links between them. The dashed links indicate synchronization and message passing communication between tasks, such that one task can notify another task by a signal of job completion or other event. There may also be an external event notification such as an interrupt request from any external event resource.

More complicated logical relationships can be represented by the combination of logic AND (concurrency) and logic OR (selection) in a state Chart. The following diagram exhibits the concurrency behavior and hierarchy structure in a state Chart.

The state A is a super state of the state B and state C, while both states B and C are nested in state A. The state B and state C are separated by a vertical bar, which specifies the concurrency of B and C. The states D and E inside C are also two parallel states, just like the states B and C in state A. The logical relationship between the pair (B, C) and the pair (D, E) is a logical AND, which indicates B and C will be active at same time after state A is entered. It is the same for the states of D and E after state B is entered. Once the state C is entered, the default state is F, and the state F and the state G will be alternately active, so the relationship between F and G is logical OR. This means only one of these two states can be active at any time. The tree on the right side shows the logical relationship in term of concurrency between sibling states and alternate relationship between super-sub states.

A concurrent state can contain one history state and one deep history state. A transition to the history state will resume the state it last had in this state region. A typical application of the history state is for the Interrupt Service Routine (ISR) return, which returns the control to where it left. A history state can have multiple incoming events/actions. It can have at most one outgoing event/action, which points to the “resume” state if the transition occurs for the first time without any history. The regular history state only applies to the level in which it appears, while the deep history state entrance applies to the most recently visited state in the deepest level. It is noted as H.
Fig. 2.12

Hierarchies and Concurrency in State Chart

After completion of interrupts, the control will return to where it left off, as noted by the H states, e.g., a timer can trigger a timeout event which interrupts a task, an ISR handles the event, and the interrupted task resumes afterwards.
Fig. 2.13

History State

The FSM is good for control-oriented system analysis which monitors inputs, controls the transitions by stimulus/events, and takes actions on the states. The state Chart is multitasking-oriented, which overcomes this shortcoming to support the real-time multi-tasking software analysis modeling. Similar to FSM, the state Chart consists of states (including initial and final states), signals (events), transitions with triggers (event and data conditions), and actions (e.g., emitting signals). In addition, it supports state hierarchy, i.e., nested states, and various communications between two active states.

When the state Chart is used, we can assign a concurrent task (a concurrent state) for each important device control so that we can easily divide the whole software design into multiple partitions.

There are many state Chart simulation development tools available in the market. StateMate, SpeedChart, StateFlow, BetterState, VisualVHDL, StateVision, and LabView are well known among these tools. Some other state Chart development tools such as Rational Rose and Rhapsody can even convert the state Chart into actual C or VHDL code.

In summary, the state Chart provides a formal model for reactive dynamic behavior of embedded system, especially for the concurrency behavior. It is a good fit for large system modeling and design, because it has eliminated the state explosion problem.

2.4 Time Requirement Analysis for Real-Time Systems

As you know that most embedded software are real-time systems, a waiting task is guaranteed to be given the CPU when an external event occurs. Real-time systems are designed to control devices such as industrial robots, which require timely processing. Such systems must meet timing requirements regardless if they are hard real-time embedded systems or soft real-time embedded systems. Almost all embedded software are multitasking oriented, in which multiple tasks, also known as processes, share common processing resources such as a CPU. With a single CPU, only one task is said to be running at any point in time, i.e., the CPU is actively executing an instruction for that task while all others are waiting. The act of reassigning a CPU from one task to another one is called a context switch. Running multiple tasks concurrently can make maximum use of CPU time, reduce the CPU idle time to a minimum degree, and handle urgent requests immediately. A task represents an activity such as a process or a thread. Threads are lightweight processes which share an entire memory space. Thus, threads are basically processes that run in the same memory context. Context switches between threads does not involve changing the memory context. A task can even have multiple threads. The term task in this book represents both a process and thread. The multi-tasking design and implementation can separate the design concerns, and divide and conquer the problem domains, but interaction and resource sharing between tasks still bring many design and implementation complexities.

Each task must keep track of its own concerns, and has its own state including its own status, CPU register status, program count, its own memory space and stack, so that the CPU can switch back and forth between these processes/tasks.

What is the timing requirement? When an event request occurs in a reactive embedded system, the target task must respond to the event within a preset time. The Worst-Case Execution Time (WCET) of a task tells us the maximum length of time the task could take to execute. A task often shows a certain variation of execution times depending on the input data or behavior of the environment. The actual response time may be shorter that the WCET. Knowing worst-case execution times is of prime importance for the timing analysis of hard real-time systems. There is also a deadline for each task to complete for either periodic events or a-periodic events.

Understanding life cycle of a task is very important for time requirement analysis. Let us examine the multi-tasking 8051 RTX-51. It recognizes four task states; a process is always in one of these states at any given time:
  1. 1.

    READY Tasks which are READY to be run by the CPU are in this state.

     
  2. 2.

    RUNNING (ACTIVE) Task which is currently being executed by CPU processor. Only one task can be in this state at any time.

     
  3. 3.

    BLOCKED (WAITING) Task waits for an event or resource.

     
  4. 4.

    SLEEPING Tasks before they are started or after termination are in this state.

     
This diagram shows the life cycle of a task in a multitasking environment, where it moves from one state to another state, and completes other tasks for CPU. The task selection for CPU (running state) is determined by a scheduler (also called dispatcher).
Fig. 2.14

RTX51 Tasks Status Diagram

The RTX_51 scheduler follows the following rules:
  1. 1.

    The task with the highest priority of all tasks in the READY state is executed first.

     
  2. 2.

    If several tasks of the same priority are in the READY state, the task that has been ready the longest will be the next to execute.

     
  3. 3.

    Exception: round-robin scheduling to be discussed soon.

     

2.4.1 Non-Preemptive Scheduling

Non-preemptive multitasking is a simple task scheduling method for periodic time requirement systems. Such systems are either statically scheduled which most often are periodic systems, or in the form of cooperative multitasking, in which case the tasks can self-interrupt and voluntarily give control to other tasks. Cooperativemultitasking is a type of multitasking in which the process currently controlling the CPU must offer control to other processes. It is called “cooperative” because all tasks must work cooperatively. In non-preemptive scheduling, once a task started, it will hold the CPU until it completes, or it may give up the CPU by itself due to lack of resources. It does not need any context switch from a running task to another task. The context switch takes a lot of time to save its current status, including its stack and Program Count (PC) address, so that it can resume its task after it comes back to run on the CPU.

A simplified modeling for a non-preemptive scheduling method can be a cyclic scheduling method where the tasks can be scheduled in a fixed static table off-line.

Assume that the embedded software consists of a set of tasks and all of them have a fixed period. A-periodic events can be estimated by their worst case interval gap between two consecutive task events.

Also, assume that all tasks are independent and WCET are known in advance so that the deadline is at the end of WCET.

You can also schedule the higher priority task before the tasks with lower priority. The advantage of such cyclic scheduling is simple: zero overhead for context switches between processes/tasks; the disadvantage is the inflexibility.

If there are n tasks and the WCET of ith task is ci, then the period of any task Ti must satisfy
$$\begin{array}{rcl}{ \mathrm{T}}_{\mathrm{i}} >=\ \sum \nolimits \!{\mathrm{c}}_{\mathrm{j}}(\mathrm{j} = 1,2,\ldots ,\mathrm{n})& & \\ \end{array}$$

Otherwise some task may miss its deadline.

Therefore, software analysis and design should make each task as shorter as possible. For these long tasks you may need to break it into many small pieces.

Here is a cyclic schedule example:

You can find that the period of T1 (50ms) > c1(20ms) + c2(25ms).

Task

Execution Time

Period

 

Task1

20ms

50ms

 

Task2

25ms

100ms

 

Open image in new window

The following C code fragment shows the template for the non-preemptive cyclic scheduling implementation. Assume there is a timer set on the period of 50 ms.

while(1)

{

Wait_for_50ms_timer_interrupt;

do task1;

Wait_for_50ms_timer_interrupt;

do task1;

do task2;

}

Round-robin (RR) is one of the simplest time-shared cooperative scheduling algorithms which assigns prefixed time slices to each process in order, handling all processes without priority (equal priority). The task with CPU control will either voluntarily yield the CPU to other task or be preempted through an internal timer interrupt. Round-robin scheduling is both simple and easy to implement, and starvation-free.

Many RTOS support RR scheduling.

Let’s take look at an RR example in RTX51 RTOS. Assume there are two counting tasks: job1 and job2 as listed below.

int c1, c2;

//Round robin scheduling

void job0(void) _task_ 0 //starting task by default

{

os_create (1); //make task 1 ready

while(1)

c1 ++;

}

void job1(void) _task_ 1

{

while(1)

c2 ++;

}

The task0 creates task1, and the RTOS will switch CPU between task0 and task1 after task0 starts.

2.4.2 Pre-emptive Scheduling

In reality, a reactive embedded system must respond and handle external or internal events with different urgency. Some events have hard real-time constraints, while the others may only have soft real-time constraints. In other words, some task should be assigned higher priority than other tasks. An important design aspect of real-time software systems is priority-based scheduling, which ensures it will meet critical timing constraints such as deadline and response time. In priority-driven scheduling, the CPU always goes to the highest priority process that is ready. There are many priority-based scheduling algorithms to assign priority to each process in the multi-tasking real-time system. The popular fixed-priority scheduling algorithms are static timing scheduling, Round-robin scheduling and Rate Monotonic Scheduling(RMS), and its deadline based analysis is called Rate Monotonic Analysis(RMA), while the popular dynamic priority-based scheduling is Earliest Deadline First(EDF) scheduling that assigns priorities at runtime, based on upcoming execution deadlines where the priority is time varying rather than fixed.

2.4.3 RMS

Rate Monotonic Scheduling (RMS) is a priority-based static scheduling method for preemptive real-time systems. It is a popular static scheduling algorithm for such systems where the round-robin scheduling analysis fails to meet task deadlines all the time. It can guarantee the time requirement and maximize the schedulability as long as the CPU utilization is below 0.7. It has been proved that the RMA isoptimal among all static priority scheduling algorithms. The rate-monotonic analysis assigns shorter period/deadline processes higher priority at design time, assuming that processes have the following properties:
  • No resource sharing

    (processes do not share resources, e.g., a hardware resource, a queue, or any

    kind of semaphore blocking or busy wait non-blocking)

  • Deterministic deadlines are exactly equal to periods

  • Context switch times are free and have no impact on the model

Once the priority of a task is assigned, they will remain constant for the lifetime of the task.

Let’s discuss a RMS scenario. First, look at the scenario with two tasks.

The task1 must meet its deadline of 50, 100, 150, … and task2 must meet its deadline at 100, 200, 300, \(\ldots \). Since task1 has a shorter period, it is assigned higher priority such that once it starts it will not be preempted until it completes. The static schedule is shown as follows.

Task

Execution Time

Period(Deadline)

Priority

Utilization

 

Task1

20ms

50ms

2(high)

40%

 

Task2

45ms

100ms

1(low)

45%

 

Open image in new window

At time 50, task2 is preempted by task1 because task1 is ready every 50ms and task1 has higher priority than task2. This schedule guarantees all tasks meet their deadline.

If task2 gets higher priority over task1, then task 1 will miss its deadline at time 50 as seen below.

Open image in new window

Here is an example of three tasks (processes) listed in the table below.

Because T(1) < T(2) < T(3) (where T(i) is the period of task i) ,

Task

Execution Time

Period(Deadline)

Priority

Utilization

 

Task1

4ms

10ms

3(high)

40%

 

Task2

5ms

15ms

2(medium)

33%

 

Task3

6ms

25ms

1(low)

24%

 

Therefore P(1) > P(2) > P(3) (where P(i) is the priority of task i).

Also notice that CPU utilization is \(4/10 + 5/15 + 6/25 =.40 +.33 +.24 =.94 < 100\%\).

Let’s schedule these three tasks according to their priorities:

Open image in new window

What happens? At the time of 25ms, task3 miss its deadline by 3ms. Why? It is due to the fact that total utilization rate is 94% which is far beyond the schedulable bound 70%. The schedulable bound is given later.

Let’s reduce the utilization rates as follows:

The total CPU utilization is 56% at this time, so let’s re-schedule it.

Task

Execution time

Period(Deadline)

Priority

Utilization

 

Task1

3ms

10ms

3(high)

20%

 

Task2

4ms

15ms

2(medium)

20%

 

Task3

4ms

25ms

1(low)

16%

 

Open image in new window

The same pattern will continue after time 30.

One major limitation of fixed-priority scheduling is that it is not always possible to fully utilize the CPU. It has a worst-case schedulable bound of:
$$\begin{array}{rcl} \mathrm{Un} = \sum \nolimits \mathrm{Ci}/\mathrm{Ti}(\mathrm{i} = 1..\mathrm{n}) <={ \mathrm{n}}^{{_\ast}}({2}^{1/\mathrm{n}} - 1)& & \\ \end{array}$$

where n is the number of tasks in a system. As the number of tasks increases, the schedulable bound decreases, eventually approaching its limit of ln2 = 69. 3%.

Liu & Layland (1973) proved that for a set of n periodic tasks with unique periods, a feasible schedule that will meet deadlines exists if: CPU Utilization < Un.

In general, RMS cases can meet all the deadlines if CPU utilization is 70%. The other 30% of the CPU can be dedicated to lower-priority non real-time tasks.

The context switch cost for the RMS is very high, although its CPU utilization is not perfect.

In order to fully make use of CPU time and also to meet all deadlines, we need to use a dynamic priority scheduling algorithm.

2.4.4 Dynamic scheduling with EDF

You know that priority is fixed in the static priority-based scheduling, but the priority of a process can change in a dynamic priority-based scheduling method to increase the CPU utilization and to allow all tasks meet their deadline. The Earliest Deadline First (EDF) assigns higher priority to these tasks that are closer to their deadline at run time. The process closest to its deadline is assigned with the highest priority. The EDF can be applied to scheduling for both periodic and a-periodic tasks if deadlines are known in advance. The advantage of it is its superior CPU utilization, but its disadvantages including high costs on context switches and no guarantees on all deadline requirements.

EDF is not very practical in many cases due to its complexity and is not as popular as RMS.

EDF must recalculate the priority of each process at every context switch time (preemption time). This is another overhead cost in addition to the cost of context switches.

Let’s explore a multi-tasking case as follows:

The total CPU utilization = 87%

Task

Execution Time(Ci)

Deadline (Period)

Utilization

 

Task1

4ms

10ms

40%

 

Task2

4ms

15ms

27%

 

Task3

5ms

25ms

20%

 

Open image in new window

Assume tasks t1, t2, t3 are ready at time 0.

The deadline of t2 are at the time of 10, 20, 30, 40, …, the deadlines for t2 are at the time of 15, 30, 45, and the deadlines for t3 are at the time of 25, 50, 75, ….

At time 0, t1 has the highest priority, because its next deadline is shorter than the other two.

At time 4, context switch time, t1 is just finished and is not ready, and t2 is closer to its next deadline than t3, so t2 goes first.

At time 8, only t3 is available and so t3 gets its 2 units until time 10, when the t1 is ready again.

At time 10, t1 gets back the CPU and completes at time 14.

At time 14, only t3 is ready and t2 will be ready at time 15, therefore t3 gets another 1 unit CPU time.

At time 15, t2 must run CPU and completes at time 19. T3 gets another one unit CPU time and completes before its deadline of time 25.

At time 20, t1 gets the CPU and runs 4 units time, and then t3 gets another one unit time to complete its execution time.

At time 25, neither t1 nor t2 is ready but t3 is ready for the next round, therefore t3 runs 5 units time.

At the time 30, the cycle will start over again.

Compared to RMS, you can see much more frequent context switches take place during the concurrent executions.

2.5 Multi-Tasking Design Methodology

Almost all real-time embedded systems are real-time reactive, which must react to external events or internal timer events within an expected time limit. After the system time requirement analysis and system modeling, we can start the software design, which will provide a guideline for software coding.

There are two classical modeling patterns for real-time embedded systems. One is a simple explicit loop controlled state Chart for soft real-time operating systems shown in Figure 2.15.
Fig. 2.15

Simple Loop Architecture

The shortcoming is that when the taski is waiting for an unavailable resource, the taski + 1 cannot precede it and so some other tasks may fail to meet the response deadline requirement. There is no priority preference among the tasks. The advantage is its simplicity and that no RTOS support is needed.

There are many different ways to schedule and design a multi-tasking real-time system due to the system complexities and time constraint requirements. You can write a task scheduler on your own by polling external events or using external and internal timer interrupts, or use a commercial RTOS.

2.5.1 Polling

The simplest looping approach is to have all functional blocks including the event polling functions in a simple infinite loop like a Round-Robin loop.

main()

{

while(1)

{

function1();

function2();

function3();

}

}

The functions function1 and function2 may check the external data every 50 ms. The function3 may store the collected data and make some decisions and take actions based on the collected data.

The question is how to control the timing? Without a timer control interrupt due to various reasons such as not enough ports or interrupts available, you can design a time_delay function,

void time_delay(unsigned int delay)

{

unsigned int i,j;

for(i=0; i<=delay; i++)

{

for (j=0; j<=100; j++);

}

}

A function call of time_delay(1) will produce approximately 1 ms for an 8051 running at 12MHz.

You can estimate it in this way: The 8051 runs at 1MIPS, the inner loop has 10 assembly machine instructions (by View − > Disassemble window in μvision) and 100 iterations takes about 100 ×10μs = 1 ms.

Assume that all function execution times are very short and can be ignored. You can insert a time_delay(50) at the end of each cycle to make the program poll the I/O ports every 50 ms.

main()

{

while(1)

{

function1();

function2();

function3();

time_delay(50);

}

}

Here we ignore the execution time of all functions. If the total execution time of these three functions is 10ms, then we can adjust the time delay to 40 ms. In reality, you don’t see this implementation very often, because the time control is not accurate and it is not appropriate for any hard real-time systems. For very simple applications with limited timers and interrupt ports, you can still use this design style.

2.5.2 Interrupts

A popular design pattern for a simple real-time system is a division of a background program and several foreground interrupt service functions. For example, an application has a time critical job which needs to run every 10ms and several other soft time constrained functions such as interface updating, data transferring, and data notification. The C51 program template is given as follow:

Foreground:

void critical_control

interrupt INTERUPT_TIMER_1_OVERFLOW

{

// This ISR is called every 10 ms by timer1

}

Background:

main()

{

while(1)

{

function1();

function2();

function3();

}

}

This simple background loop with foreground interrupt service routine pattern works fine as long as the ISR itself is short and runs quick. However, this pattern is difficult to scale to a large complex system. For example, the critical time control ISR function itself needs to wait for some data to be available, or to look up a large table, or to perform complex data transformation and computation. In this situation, the ISR itself may take more than 10ms and will miss the time deadline and also breach the time requirements for other tasks.

An alternative solution is to have a flag control variable to mark the interrupt time status and to split the ISR into several sub states as follows.

int timerFlag; // global data needs a protection

void isr_1 interrupt INTERRUPT_TIMER_1_OVERFLOW

{

timerFlag = 1;

// This ISR is called every 10 ms by timer1 set

}

int states ;

critical-control()

{

static states next_state = 0;

switch(next_state)

{

case 0:

process1();

next_state=1;

break;

case 1:

process2();

next_state=2;

break;

case 2:

process3();

next_state=0;

break;

}

}

main()

{

Init();

while(1)

{

if (timerFlag)

{

critical_control();

timerFlag=0;

}

function1();

function2();

function3();

}

}

A rule of thumb for the timer interval is always to make the interval short enough to ensure the critical functions get serviced at the desired frequency.

So far, the discussion is given on simple real-time applications. For a large and complex real-time system with more than dozen concurrent tasks, you need to use a RTOS to make priority-based schedule for multi-tasking jobs.

2.5.3 RTOS

RTOS makes complex, hard real-time embedded software design much easier. The RTOS-based model diagram is shown in Figure 2.16. The links between tasks can represent the synchronization signals, data exchanges, or even a timeout notification between tasks.
Fig. 2.16

Parallel Architecture

A RTOS is a background program which controls and schedules executions and communications of multiple time constrained tasks, schedules resource sharing, and distributes the concerns among tasks. There are a variety of commercial RTOS available for various microcontrollers, such as POSIX (Portable Operating System Interface for Computing Environments) and CMX-RTX. A RTOS is widely used in complex, hard real-time embedded software. Assume you have an init master task that starts two tasks that need to run forever. These two tasks are relatively independent, e.g., the task1 is a main control task and task2 monitors an external event and notifies it to the task1. You can design these two tasks in a parallel mode in a state Chart diagram shown in Figure 2.17.
Fig. 2.17

A simple state Chart diagram

Here is the 8051 RTX51 implementation of the above application model for you to get your first taste of the RTOS.

#include <reg51.h>

#include <rtx51.h>

void task1 (void) _task_ 1

//task1 is assigned a priority 0 by default

//4 priority levels: 0-3 in RTX51

{

while(1)

{

os_wait(K_SIG,0,0);// wait in blocked state

// for signal to be activated

// to a ready state

proc1();

os_wait(K_TMO,30,0); // wait for timeout

os_send_signal(2); // send signal to task 2

}

}

void task2 (void) _task_ 2 //Task 2

{

while(1)

{

os_wait(K_SIG,0,0);

proc2();

os_wait(K_TMO,30,0);

os_send_signal(1); //send a signal to task1

}

}

void start_task (void) _task_ 0 _priority_ 1

//task 0 with higher priority 1

{

system_init();

os_create_task(1);//make task1 ready

os_create_task(2);//make task2 ready

os_delete_task(0);//make task0 itself sleep

}

void main (void)

{

os_start_system (0);//start up task0

}

The detailed discussion will be given in the RTOS  chapter 5.

2.6 Software Design Issues

2.6.1 Task Interactions

In many cases, synchronization and inter-process communication are very necessary. Most tasks have to interact with other tasks in their executions. They can interact simply by exchanging messages synchronously with parameter message passing or asynchronously with message queues; they can coordinate themselves by synchronization in such way that task A waits until it receives a signal from task B before proceeding; they can interact implicitly through resource sharing, such as global data so that one task can get data updated by another task.

Due to the concurrency situation, the shared data must be protected by mutual exclusion access control in order to prevent any malfunction on the shared data.

In other words, the relationship between concurrent tasks may be independent, cooperative, or competitive.

2.6.2 Resource Sharing

2.6.2.1 Data Sharing

Many processes/tasks need to share same data items. For example,

int c =0;

//Task a:

while(1)

{

int i;

i=c; //line 1

i++; //line 2

c-i; //line 3

}

//Task b:

while(1)

{

int i;

i=c; //line 4

i--; //line 5

c=i; //line 6

}

If two tasks are running concurrently with such scheduling: Line 1, 4, 2, 5, 3, 6, the value of global variable c will be -1 instead of 0, because the effect of line 3 is overridden by line 6. This indicates that the global data c should be protected, which can be done by a lock or semaphore in order to guarantee mutual exclusion and avoid such a race condition. The mutual exclusion only allows one process to get access to the shared data at a time, and all other processes are locked out until the current one leaves the critical section and unlock the lock. The implementation technology includes low level system supported primitives: semaphores or high level synchronization primitives: monitors, or others. Message passing in explicit data transferring through send and receive primitives is another way.

A semaphore is a protected entity token for restricting access to shared resources in a multitasking environment. By the semaphore mechanism, resources can be shared free of conflicts between the individual tasks. If the resource is already in use, the requesting task is blocked until the token is returned to the semaphore by its current owner.

A simple binary semaphore is called a mutex. A counting semaphore is a counter for a set of available resources, rather than a locked/unlocked flag of a single resource. The synchronization resource sharing by semaphores are often called wait and signal, or acquire and release, or pend and post a semaphore.

Semaphores can only be accessed using two atomic operations (request and release the shared resource) which should not be interrupted once they are started. Almost all RTOS support semaphore mechanisms in the following ways: the atomic p operator requests the semaphore of a resource, and the atomic v operator releases the semaphore as follows.

p(Semaphore s) // Acquire Resource

{

wait until s > 0, then s -= 1;

}

p(Semaphore s) // Release Resource

{

s += 1;

}

init(Semaphore s, int v)

{

s := v; // v is the number of available resources.

// If there is only one resource, s is

// initialized as 1.

}

2.6.2.2 Code Sharing

In the embedded software, many subprograms such as interrupt service routines are invocated by multiple tasks concurrently, and these programs may be called recursively, which require such programs to be reentrant. A reentrant function can safely be called from multiple threads simultaneously, the execution of the function can be interrupted, and it can be called again, e.g., by another task (program), without affecting each other. That is, the function can be re-entered while it is already running.

If a function contains static variables or accesses global data, then it is not reentrant.

The static variables of a function maintain their values between invocations of the function. When concurrent multiple tasks invoke this function, a race condition occurs. The same race condition may take place when multiple tasks invoke the same function. Each attempts to modify that global variable, which is not protected.

A function is considered reentrant if the function cannot be changed while in use. Reentrant code avoids race conditions by removing references to global variables and modifiable static data. In other word, all data with a reentrant function must be atomic so that the code can be shared by multiple concurrent tasks without a race condition. In addition to the atomic data requirement, a reentrant function should work only on the data provided to it by the caller, should not call any non-reentrant functions, and should not return any address to a static or global data.

In the following example, neither functions f1 nor f2 are reentrant.

int global = 0;

int f1()

{

static s1 =1;

global += 1;

s1 += 1;

return global + s1;

}

int f2()

{

return f() + 1;

}

The function f1 depends on a global variable global and a static variable s1. Thus, if two tasks execute it and access these two variables concurrently, then the result will vary depending on the timing of the execution due to the race condition. Hence, f1 is not reentrant, and neither is f2 because it calls the non-reentrant function f1.

int f3(int i)

{

int a;

a = i + 1;

return a;

}

int f4(int i)

{

return f(i) + 1;

}

Now both f3 and f4 are re-entrant functions at this time, because the f3 function only uses either atomic variables or the data provided by the caller and the f4 function calls the reentrant function f3.

If any non-reentrant function needs to share the common data in the concurrent execution, mutual exclusion needs to be enforced. The semaphore for critical sections is one of the common practices.

The RTX51 RTOS provides up to eight binary semaphores. The semaphores allow the following pair of operations:
  • Wait for token:os_wait()

  • Return (send) token: os_send_token()

E.g., here is a C51 fragment using semaphores

os_wait (K_MBX + SEM_1, 255, 0);// Wait for semaphore

// put critical section code here; It may be a non-

// reentrant function or code accessing a shared data

os_send_token (SEM_1); //release semaphore

os_send_token (SEM_1);

It is also used to initialize the semaphore token for the multi-tasking environment initialization where the semaphore SEM_1 is defined as an integer of value 1-8.

2.7 Lab Practice: A Traffic Light Control System Modeling and Design

In this lab, we will model and design a simplified automated traffic light control system in the street intersection. The C51 code of a similar system is included in the Keil development kit. We will focus on the system modeling and design process in this lab. Its implementation will be in the RTOS chapter.

There are three lights: Red, Green, and Yellow in sequence. The time duration of each light is predetermined. For modeling and design purposes, we have one light group (R, G, Y) for each street direction and one pedestrian self-service button and walk permit indicator in each direction. A pedestrian can push the button to request the walk permit and system will switch the walk light from stop to walk. There are two car detection sensors: one in each direction to sense for approaching car. If a coming car approaches in one direction and no car in the other direction, it will notify the system to switch the light to let the approaching car go.

Figure 2.18 shows the environment of the traffic light control system in one direction.
Fig. 2.18

Street Environment of the Traffic Light Controller

The following context diagram only lists the components in the direction of north bound and east bound. The sensor1 and sensor2 are used to detect car arrivals in these two directions and trigger the interrupts. The button1 and button2 will make the walk permit request via external interrupts. The two kinds of actuators are the walk/stop light and the Red-Yellow-Green traffic light. You can derive your state Chart diagram from the context diagram based on the input devices where the embedded system gets data or signal from and the actuators on which the system must have control over and take action upon.

After you analyze the context diagram, you may decide to have one task for button input and one task for car detect input, but neither of them can decide the light changes by themselves, because the system must consolidate the traffic lights and walk permit lights. You can have a third task called lights task, which has an infinite loop in the sequence of Red = > Green = > Yellow with predefined intervals in case of no car arrival request or pedestrian crossing request. Once the light task gets requests from the other two tasks, it can alter the normal light display sequence to satisfy the requests. The state Chart diagram in Figure 2.20 is one of the models you may get. The implementation of this model will be given in  chapter 5.
Fig. 2.19

The Context Diagram

Fig. 2.20

The State Chart Diagram for Traffic Light system

2.8 Summary

This chapter discussed the embedded software modeling analysis and design. The state Chart diagram is one of the graphic modeling tools to explore the embedded system’s behavior and presents an abstraction model for the software design. The non-functional requirements such as time constraints for real-time embedded software must be satisfied due to the functional requirements. A simple, real-time embedded system can be scheduled by Round-Robin scheduling, but many hard real-time embedded software with multiple concurrent tasks need more advanced scheduling algorithm supported by RTOS. The RMS is a popular time analysis method for static priority-based multitasking scheduling where the shortest task is always assigned with highest priority in the pre-emptive scheduling. Multiple tasks need to share resources, and shared resource protection becomes an embedded software design concern. The semaphore token used for concurrent access to shared resources are discussed to avoid the race condition. The reentrant functions shared by multiple tasks to allow recursive invocation are another common issue in the embedded software design. In the software SDLC, a sound embedded software analysis and design build a solid foundation for real-time embedded software development coding and testing.

2.9 Review Questions

  1. 1.

    The transition link in a state Chart can specify the event, condition, and action.

    1. a.

      True

       
    2. b.

      False

       
     
  2. 2.

    Two states with logic AND can only be run CPU one after another.

    1. a.

      True

       
    2. b.

      False

       
     
  3. 3.

    Two states with logic OR can run CPU concurrently.

    1. a.

      True

       
    2. b.

      False

       
     
  4. 4.

    A history state can be used to represent the interrupt resume point.

    1. a.

      True

       
    2. b.

      False

       
     
  5. 5.

    The action noted in a state is same as the action noted in the transition.

    1. a.

      True

       
    2. b.

      False

       
     
  6. 6.

    In Round-robin scheduling each task can be assigned with different time slice.

    1. a.

      True

       
    2. b.

      False

       
     
  7. 7.

    RMS is used to analyze the dynamic priority-based multi-tasking scheduling.

    1. a.

      True

       
    2. b.

      False

       
     
  8. 8.

    EDF is used to analyze the static priority-based multi-tasking scheduling.

    1. a.

      True

       
    2. b.

      False

       
     
  9. 9.

    The priority is based on the task execution time in RMS.

    1. a.

      True

       
    2. b.

      False

       
     
  10. 10.

    In EDF the priority of a task is determined on how close the task is to its deadline at run time.

    1. a.

      True

       
    2. b.

      False

       
     
  11. 11.

    There is no context switch cost in RR scheduled multi-tasking.

    1. a.

      True

       
    2. b.

      False

       
     
  12. 12.

    Each task module in embedded software model must be an infinite loop.

    1. a.

      True

       
    2. b.

      False

       
    • Answers:

    • 1. a  2. b  3. b  4. a 5. b  6. b  7. b  8. b

    • 9. a  10. a  11. b  12. b

     

2.10 Exercises

  1. 1.

    Give a complete analysis modeling and design of the simplified temperature controller with RED and GREEN LED indicators and keypad for high and low bound setting project in this chapter.

     
  2. 2.
    Redesign the traffic light control system project given in this chapter using state Charts. Add following features:
    • The system allows you to set a time range for a normal daily working period. (ex: 6:00am –10:00pm)

    • Beyond this time period, the traffic lights blink yellow in both directions.

     
  3. 3.

    List all non-functional requirements for the simplified temperature LED system.

     
  4. 4.

    List all non-functional requirements for the traffic light controller system.

     
  5. 5.

    Perform RMS analysis on this time requirement:

    Task1: execution time: 25 ms, period: 50 ms.

    Task2: execution time: 40 ms, period: 100 ms.

     
  6. 6.

    Perform RMS analysis on this time requirement:

    Task1: execution time: 3 ms, period: 10 ms.

    Task2: execution time: 4 ms, period: 15 ms.

    Task3: execution time: 5 ms, period: 20 ms.

     
  7. 7.

    Perform EDF analysis on this time requirement:

    Task1: execution time: 3 ms, deadline: 10 ms.

    Task2: execution time: 4 ms, deadline: 15 ms.

    Task3: execution time: 5 ms, deadline: 20 ms.

     
  8. 8.

    Run the Ex1 RTX51Tiny example provided by the Keil development kit.

     
  9. 9.

    Draw a state Chart for the above example.

     
  10. 10.

    Run the Ex2 RTX51Tiny example provided by the Keil development kit.

     
  11. 11.

    Draw a state Chart for the above example.

     

References

  1. 1.
    D. Harel, State Charts: A visual Formalism for Complex Systems, Science of Computer Programming, 1987Google Scholar
  2. 2.
    Rajesh Gupta, mesl.ucsd.edu - /gupta/cse237b-f07/Lectures/, 2007Google Scholar
  3. 3.
    Michael Point, Embedded C, Addison Wesley, 2002Google Scholar
  4. 4.
  5. 5.
    Embedded software design tutorial, www.freertos.org/tutorial/solution1.html, 2007
  6. 6.
    RTX51 Tiny User’s Guide, http://www.keil.com/support/man/docs/tr51/
  7. 7.
    μVision®; User’s Guide, http://www.keil.com/support/man/docs/uv3/

Copyright information

© Springer Science+Business Media, LLC 2009

Authors and Affiliations

  1. 1.Dept. of Computer ScienceSouthern Polytechnic UniversityMariettaUSA
  2. 2.Atronix Engineering Inc.NorcrossUSA
  3. 3.University of Texas at AustinAustinUSA

Personalised recommendations