Skip to main content

By clicking Submit, you agree to the developerWorks terms of use.

The first time you sign into developerWorks, a profile is created for you. Select information in your developerWorks profile is displayed to the public, but you may edit the information at any time. Your first name, last name (unless you choose to hide them), and display name will accompany the content that you post.

All information submitted is secure.

  • Close [x]

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerworks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

By clicking Submit, you agree to the developerWorks terms of use.

All information submitted is secure.

  • Close [x]

Model-Driven Development of Resource-Constrained Embedded Applications

Ton JanssenOcé Technologies B.V.
Biography to be posted.
William GrahamKlocwork Inc.
Biography to be posted.

Summary:  Many embedded systems have memory and/or computational processing power constraints. The use of models to generate embedded applications requires thoughtful attention to these constraints. This article investigates the use of Unified Modeling Language (UML) modeling tools to generate resource-constrained applications, as illustrated by a small case study of an embedded system.

Date:  14 Nov 2005 (Published 09 Jun 2004)
Level:  Introductory

Activity:  8057 views
Comments:  

Introduction

Embedded software applications are typically more difficult to design and build, both because of the problem domain and the constraints placed on them by the target environment. Embedded systems usually have limited memory and CPU processing power; these constraints are likely due to size, cost, power efficiency, and heat generation limitations. In addition, studies have shown that many embedded projects either fail altogether, or fail to meet the requirements set for them (see the surveys under "Resources"). Given such a situation, you can benefit from attempting new techniques for software construction.

One technique that may at first seem inappropriate for the embedded domain is modeling and Model-Driven Development (MDD). Modeling is frequently used in other engineering domains as a way to verifying design decisions before committing large resources to constructing or manufacturing a product. Models allow you to perform thought experiments on a design, and to try different approaches to solving the problem at a higher level of abstraction than the raw medium of the constructed product. In software this is not typically the case: although modeling tools have been around for decades, the desire, capability, and modeling tool maturity have not been sufficient. This is no longer the case, and using models -- not only to think about the problem but also to drive the design and construction of software -- is a reality. MDD tools can take models at a high level of abstraction and generate executable and efficient source code. Models (in particular visual ones) provide rich information on the structure and behaviour of the system. Sharing and working with models on a software development team is now more efficient and less error-prone than working only in source code.

Embedded systems vary in size and complexity, and might include such divergent things as:

  • Small applications embedded in a microcontroller running the interior lights in a car
  • Large, multiprocessor telephone switches requiring millions of lines of code

Although the use of models is beneficial to both types, there are certain trade-offs required when using MDD on such systems. Small and resource-constrained systems may have such limited memory that using a Real-Time Operating System (RTOS) becomes prohibitive. In such cases, operating system services like threads, processes, or tasks are not available. Modeling tools that provide MDD capabilities need to take these diverse requirements into account. This article discusses the use of MDD techniques on various resource-constrained embedded systems, and looks at the trade-offs required in order to make MDD beneficial.

The Unified Modeling Language

The standard for modeling software systems is called the Unified Modeling Language (UML). The specification of UML is owned by the Object Management Group (http://www.omg.org.) and was adopted in November, 1997. UML is a language that uses a visual syntax to represent a blueprint of software. These blueprints, much like architectural drawings of buildings, use a standard set of icons (that have much more semantic meaning than a typical programming language statement) to represent accepted concepts in software engineering. UML defines a set of diagrams that are used to show relationships between objects in a system. The best source of additional information on UML is Grady Booch, Ivar Jacobsen, and Jim Rumbaugh's Unified Modeling Language User's Guide . Here is a short summary of these diagrams:

Structural diagrams

  • Class diagram: This shows the relationships between a set of classes. This is the most common diagram in UML, and is a structural view of how classes are related.
  • Collaboration diagram: This shows the structural organization of objects that communicate with each other.
  • Component diagram: This shows the relationship between components. A component is a physical manifestation of software, and may be a library, executable, DLL, and so on. Components are built and combined to form applications.
  • Deployment diagram: This shows the relationship between physical entities like a CPU, printer, or workstation. It can also show where components are deployed.

Behavioural diagrams

  • Use Case Diagram: This captures functional requirements. It shows the relationships between actors and use cases. Actors are entities external to the system, such as users and other systems. A use case is an end-to-end sequence of actions (including variants) that result in an observable and useful result.
  • Sequence Diagrams: This is an interaction diagram that shows the communication between objects in a time-ordered fashion.
  • State Diagram: This contains a finite state machine that describes the behavior of a class. State machines are an excellent way to describe event-driven behavior.
  • Activity Diagram: This shows the flow of control through activities in a system.

MDD with UML

To a large extent, developers have traditionally used models for documentation at the beginning of a project's lifecycle. This is mainly due to the fact that there was no tool support enabling the use of these models to drive the design and development of the software. MDD means the use of models to generate correct, executable, and useable applications. The UML model is augmented with additional information in order to create all of the pieces needed for an application. Typically, the structure and behavioral diagrams of the UML provide about 80% of the code.

The additional 20% comes from user input into the model: in other words, you generate all of the code from the model, but fill in certain details to support this code generation. In some cases you can use an action language to provide an abstract way to describe detailed actions. In other cases, use a programming language such as C++ or Java as the action language. In this case study, the action language is the target language, C.

Active objects--modeling concurrency in UML

UML 1.x did not provide an easy way to represent a composite object (that is, one that contains other objects in a containment relationship). Real systems are typically made of smaller subsystems, and these subsystems are connected together in a unique way in order to satisfy the system requirements. UML 2.0 -- the next major revision of the UML standard -- has addressed this by providing structured classes (see the "Resources" section following for more detail). UML 2.0 provides a powerful building block for system design through structured classes.

In particular, the structured class concept can be specialized to model active objects -- with their own thread of control -- that communicate with other active objects, asynchronously, using message queues. These active objects address a key need of the embedded and real-time developer: the ability to model complex and concurrent objects (such as interfaces and devices) in the problem domain.

Contained parts of a structured class communicate via ports. Ports allow for two-way communication between objects that consist of required (incoming) and provided (outgoing) interfaces. Ports are wired together using a connector; once wired together, objects can communicate via the defined interfaces. Ports also allow objects to be decoupled and plugged in anywhere a compatible set of connections is available. Active objects, furthermore, extend this capability by formalizing the communication. The message "format" that is used by ports on active objects is defined using protocols, which are the signals that are accepted (required) by and sent (provided) on a port. The use of signals is analogous to events in the modeled system, and therefore useful for modeling the event-driven domain of embedded and real-time software.

Active objects have their own thread of control: they have an encapsulation boundary that protects the inner behaviour and state and an external interface defined by ports (see Figure 1). Active objects also communicate using messages rather than operation (method) calls. All communication in and out of the active object is through ports. This decouples the object, so it can be used in different places in the system. Moreover, active objects can contain other objects and represent any arbitrarily large subsystem (or the system itself). The use of active objects as basic building blocks is a powerful technique for building concurrent, event-driven, complex systems. This approach is similar to hardware design, where engineers build systems from components (for example, logic chips, ASICs, or micro-controllers) that have established interfaces (pin outs), and connect them together on a schematic diagram.


Figure 1. The active object pattern
The active object pattern

State machines as behaviour models

You can define the behaviour of an object -- that is, the way it reacts to external stimulus -- with Hierarchical Finite State Machines (HFSMs). State machines are also common in hardware design, and prove useful for building reactive and event-driven software. State diagrams define the behavior of objects by specifying how they react to events or operations.

For active objects -- as discussed previously -- events arrive via the object's ports, and the set of available events is defined by the protocol for the port. When a message arrives at the port of the active object, it is dispatched to a contained active object (as appropriate) and handled by the object's state machine. If a trigger is defined for the event, then that trigger is fired for the state machine. The state machine may then define an appropriate action to take, which may involve the execution of code and subsequent change of state.

Figure 2 shows a state machine with two states. The construction of the object puts it in state first, as the Initial transition is fired upon construction. The arrival of event1 triggers the transition event1, which also causes the state machine to transition from state first to second. If event2 arrives when in state first, the transition event2 is fired but the state remains first (this is known as a self-transition). State machines are an excellent way for you to describe object behaviour, and are especially useful when describing event-driven behaviour typical of the problem domain.


Figure 2. A state machine diagram
A state machine diagram

State machines are not limited to active objects; in fact, they can be used for any type of object. So-called passive objects do not have their own thread of control, and thus react to external stimulus only when specifically asked to do so by some external thread of control. Most typical classes that C++ or Java™ programmers are used to are passive classes, meaning that they only react to function or method calls from an external caller. You can use state machines to model the behaviour of passive objects in the same way as active objects, except that the source of events is not messages on a port but rather calls on the object's methods or functions. In addition, you can use function or method calls as triggers for the object's state machine: when the function is called, the appropriate transition fires on the state machine (more code could execute and a change of state is possible). Use of passive objects can be highly efficient in cases where the use of active objects is not possible due to resource constraints.

In this article, the use of the term passive retains the distinction between active and passive classes. Passive classes are regular classes that are familiar to most programmers.

Case study

Océ undertook an investigation of an MDD method to determine if such an approach was appropriate when designing very resource-constrained applications on 16- or even 8-bit micro controllers. If it is possible to design such systems using UML models with full code generation, you will see a significant reduction in development effort compared to coding in C by hand.

The biggest advantage in using UML and MDD is working at a higher level of abstraction. For example, compare working with a state diagram with seven states to possibly hundreds of lines of C code required to implement this object's behaviour, and further imagine that additional functionality is required three months after the initial implementation. By then, in many cases, you might have forgotten what the code does in detail. You'd then have to reread the code (and associated include files and called functions) in order to understand the behaviour, effectively reverse-engineering the design from the code each time you had to do this. Visual models -- such as state machine diagrams -- allow for quick understanding of the object's implementation, and equally quick modification to satisfy new requirements.

This case study makes use of IBM® Rational Rose® RealTime™ (Rational Rose RealTime), an MDD tool developed specifically to design complex, event-driven, and concurrent systems. Rational Rose RealTime provides direct support for active objects, ports, protocols, and state machines as described earlier, and can generate efficient source code from UML models containing these elements.

In addition, Rational Rose RealTime provides runtime services as a library (known as the TargetRTS) that supports the runtime environment needed for active objects, communication between active objects, and remote debugging of these objects on the target system. The TargetRTS is portable, and you can configure it with as many or as few optional components as needed. This means that you can configure it in a minimum fashion to support constrained target platforms. Rational Rose RealTime also supports code generation for passive classes where you expect neither RTOS nor any TargetRTS. This allows the tool to generate code for a range of embedded platforms.


Modeling software structure and behaviour

The case study investigated three possible configurations, with each one targeted for specific platform capabilities. The use of MDD for embedded systems is not an all-or-nothing proposition. Models are still applicable for resource-constrained devices if the use of models and subsequent code generation can provide productivity benefits.

The case study illustrates the trade-offs needed to support three classes of embedded systems. The following sections discuss the details of modeling software structure and behavior with Rational Rose RealTime, while gradually minimizing the required memory footprint and performance. This is done in three steps:

  • Configuration 1: Large, complex systems using C++, multithreaded RTOS
  • Configuration 2: Constrained systems using C, with no RTOS, single-threaded
  • Configuration 3: Severely constrained systems using C, with no active objects (passive objects only)

Configuration 1: Large, complex systems

The best approach for designing large and complex systems (with several hundred active objects) is to use Rational Rose RealTime and C++ code generation, with a threaded runtime system. The usage of C++ gives you all the benefits of classes and inheritance (class, public, friend, and private), while the model code generator can make use of these C++ features to provide the most flexible modeling constructs (for instance, dynamic structure and multiple containment). You can therefore create abstract and derived objects, minimizing the design and implementation effort. In this configuration, the use of active objects, TargetRTS, and underlying RTOS allows the designer to take full advantage of the MDD tooling.

At Océ, the embedded software of a complex printer was designed using this configuration, and consisted of more than 1,000 active objects controlling some 500 I/O signals and spread over about 20 threads. The hardware platform was an Intel 486 processor running on 33 MHz with 16Mb RAM. This configuration has not been benchmarked for this case study. Océ has used this kind of configuration successfully for several years.

Configuration 2: Constrained systems

When memory footprint and raw processor performance decreases, you must find a way to get the most out of your software development environment while minimizing the overhead in code execution, data size, and overhead. One way to achieve this is to get rid of the RTOS and use your own time-sliced scheduler or, potentially, a simple Basic Input/Output System (BIOS) which provides minimal services. Another significant reduction is achieved by leaving the C++ environment and using the C TargetRTS instead. Using C runtime services and code generation is better suited to resource constrained devices, since it has a smaller footprint and avoids dynamic memory allocation common in the C++ version. The starting point for this approach is the NoRTOS target environment in the Rational Rose RealTime toolset (NoRTOS is the name of the target platform, which provides compilation and linking support but does not require OS services).

Porting the TargetRTS

The first step to take is to port the TargetRTS to the target environment. This process is documented in the IBM publication Adapting Rational Rose Real-Time for Target Environments, as well as a section in the online help. In this case, an Infineon XC167 target was used in combination with the Keil C compiler. Porting details are presented in Table 1, following.

The External Port Mechanism

The external port is a mechanism that was introduced in Rational Rose RealTime V2003. It is a very simple but powerful method to inject an event into a Rational Rose RealTime port from an external piece of software. This external event is translated into a signal that is transmitted to a port on an active object which can then process this new message with its state machine. External does not necessarily mean not in Rational Rose RealTime. It actually means not from an active object. For example, you could use the external port from a passive class to report some sort of condition (timer fired, sensor active, motor speed reached, new action available, and so on) to an active class. The RTPort pointer could be passed beforehand using a publisher-subscriber design pattern. For example:

this->v_TimerId = timer_registerTimer( &this->myTimer);
/* start 1 second timer */
timer_informIn(1000000, this->v_TimerId );

The instance attribute myTimer in this active class is actually a port of type CExternal. When the timer fires, the myTimer port receives a signal event. Note that data can not be sent over an external port. The idea is that you should call a function of the passive class to get your parameters after you receive the event signal.

There was one major drawback when we tried to use the external port as described earlier: it did not work in a NoRTOS target. This problem, however, was easily solved by some small adaptations to the TargetRTS, which can be added or removed from the TargetRTS by the EXTERNAL_PORT macro. See Table 1, later, for more detail.

External Scheduler

The TargetRTS for a Rational Rose RealTime-generated application usually manages the scheduling of the application via the RTSoleController class. Rational Rose RealTime handles all scheduling with this approach. In many cases this is not desirable, so you can make the TargetRTS a slave to an external scheduler. A sequence diagram explaining the interaction between the external scheduler and the model is shown in Figure 3. See Table 1 for the implementation details.


Figure 3. Changing Thread of Control
Changing Thread of Control

The external scheduler has control, and lets the Rational Rose RealTime TargetRTS process events one by one (at whatever time the scheduler decides). The model code contains all of the event-driven applications that are designed in Rational Rose RealTime. The scheduler shown above also calls external application functions that are designed outside the toolset (if applicable). An example of such an application could be a motor control loop, which is actually more frequency-driven than event-driven; the correct functioning depends on an exact scheduling frequency (for example, once per two ms) rather than a guaranteed maximum latency while reacting to an asynchronous event.

System Decomposition

When decomposing a system with active classes on resource-constrained targets, you must find a balance regarding the percentage of active classes in the system. Active classes, although useful, do have memory and performance overhead associated with their use. A number of system services and basic applications at the bottom layers of a system model usually don't have to be active at all. Most engineers design application modules implementing the main functionality of a system, which should benefit from active objects due to the improved productivity from their use. Many of the details of concurrency, data access, and communication are accomplished using this paradigm. In Rational Rose RealTime, each state transition trigger for an active object must be triggered by an asynchronous event. Examples of such events, which are typically sent from the bottom layers of your system model, include:

  • Action available
  • End speed motor controller reached
  • A simple sensor became active
  • Analog sensor value higher than a certain threshold

These events could arrive from other active classes, but also from passive classes using the external port described earlier (as shown in Figure 4). You should distinguish between active and passive classes early in the system design. The advantage of using passive classes is an improved memory footprint and better performance, and that they can also use state machines to describe their behavior. A common pitfall in using passive classes, on the other hand, is the fact that you may need to design some low-level services. For example a timer service is provided by the TargetRTS for active classes, but it is not available for passive classes. If a timing service for passive classes were required, you would have to design it.


Figure 4. System decomposition example
System decomposition example

Using the scheme above, active and passive objects can be combined in a single system. Figure 5, following, illustrates an example message sequence chart showing the interaction scenario.


Figure 5. Interaction scenario between active and passive class
Interaction scenario between active and passive class

Another possibility is to let the time-sliced scheduler inject asynchronous events into your object on a timely basis, using the customer scheduler described above, and then use the guard condition of this trigger to check whether a state transition must be performed. In fact, this would be a polling timer. The frequency of this timer is a compromise between the application's timing requirement and the performance loss in the scheduler (it has to call RTPort_raiseExternal to inject the event).

Experiments

Using the configuration described previously, the case study team created two example models on an Infineon XC167 target to gain experience concerning memory footprint and performance. The first model was designed to create a blinky LED application (LED blinks on and off) using a custom clock, timer, and scheduler service. The state and structure diagrams are shown in Figure 6.


Figure 6. Blinky model
Blinky model

Because performance was another important issue, the team created a MessageSpeed model to measure the time required to send a message and the time spent dispatching a message to a receiving object. See Table 3, later, for the benchmark results.

Table 1. Overview of adaptations for Configuration 2

Action Details Comments
Adapt RTTarget.h

#define MULTIPLE_PRIORITIES 0
#define INTERNAL_LAYER_SERVICE 0
#define MESSAGE_DEFERRAL 0
#define TIMING_SERVICE 0
#define RTS_MEMORY_POLICY NEVER_ALLOCATE
#define RTS_CLEANUP_MECHANISM 0
#define RTUseFloatingPoint 0

Remove anything you don't need in your application. We used our own timing service.
Additional RTTarget.h settings

#define RTMESSAGE_PAYLOAD_SIZE 2
#define RTMESSAGE_BLOCKING_FACTOR 5
#define DEFAULT_FREE_MSGQ_SIZE 5
#define MINIMUM_FREE_MSGQ_SIZE 2


#define EXTERNAL_PORT 1
#define EXTERNAL_SCHEDULER 1

Minimizing footprint for the case-study and 2 new defines (EXTERNAL_)
LibsetAdapted libset.mk for Keil 
Libset wrappersCreated wrappers (like c166wrap.exe) to call the Keil compiler, linker and library managerRequired because of the non-standard calling syntax
External port for NoRTOS environmentExternal port for NoRTOS environment Adapted MANIFEST.C (this file determines which files are compiled based on the target flags) EXTERNAL_PORT flag added Added for Port: enable, disable, raise and unload. For ProtDesc: External.
 Port\raise.cc: if USE_THREADS != 1 call
RTController_receive
No Peer controller in NoRTOS env.
 Port\unload.c: if USE_THREADS != 1 skip mutex part.No Mutexes in NoRTOS env.
External scheduler for NoRTOS environmentMain\main.c: Call a 'startup' external functionExternal application startup
 SoleCont\mainLoop.c: Export a new function RTSoleController_oneLoop and call external 'startExternalScheduler' based on new flag.Call external scheduler in stead of Rose RealTime while loop.

Once you know the memory footprint and performance results, you can decide to use this approach to design applications on small systems like 16-Bit micro controllers. It is possible -- and in some cases it will turn out to be a very effective approach -- although it does generate some overhead. The desire to reduce this overhead even further leads us to the next step in complexity and footprint reduction: using only passive classes.

Configuration 3: Severely constrained systems

When creating executables in Rational Rose RealTime using passive classes only, you are actually writing C programs (because there are no real classes in C). The Rational Rose RealTime code generator allows the designer to work in OO using classes, but generates C code that approximates the constructs used in OO programming. The big advantage compared to writing code by hand is that you can create state diagrams in passive classes which are translated to (very straightforward and efficient) C code.

Modeling with passive classes

A passive class does not have its own thread of control, and only has operations which can be called (synchronously) by other code. Consequently, passive classes cannot be connected through ports, and you must design communication using operation calls (as do most C and C++ programs). MDD can still be used for these classes, but modeling is limited to class, interaction, and state machine diagrams. Even with this restriction, you can generate useful code from the structure defined in class diagrams (not structure diagrams) and the behaviour defined in the state diagrams. There is, however, no runtime support in such a model, and you must provide many OS-like features.

Passive Class State Diagram

Rational Rose RealTime provides the capability to model class behaviour using state machines, and you can use these state machines to generate code for the class. In order to provide the equivalent concept to an event in these classes, state transitions are triggered by a synchronous function call. These functions are identified by the trigger stereotype. A trigger function:

  • Must return void
  • Must be an 'instance' function (first parameter is 'this')
  • Does not have explicit implementation code

The code generator generates the code for the trigger function -- it becomes the location of the state machine logic. The generated code is the sum of the guard checks and transition code segments for all transitions which are triggered by this function, depending on the current state.

The Passive Class Scheduling Paradigm

As discussed earlier, passive classes do not have a separate thread of control. This means that some external thread of control must call the passive class in order to execute the generated code. State transitions in a passive class are triggered by operations with the stereotype trigger. One useful technique is to use a single trigger function for all transitions in a state machine, but determine the firing of the transition by guard conditions. Transitions with guard conditions are only triggered when the guard condition is met. By defining a single trigger operation (for instance <classname>_execute) for all state transitions of classname, calling this function will result in state diagram execution for each transition, as appropriate, based on satisfying each guard condition. Figure 7 shows a simple class (pcBlinky) with two states.


Figure 7. pcBlinky passive class state diagram
pcBlinky passive class state diagram

The diagram shows the transition name, the trigger operation, and the guard conditions (explained in the Guard Conditions section). The following code snippet calls the constructor and then adds the execute function as an application in slot 1 of a time sliced scheduler.

pcBlinky_construct( &myBlinky);
scheduler_addApplication( 1, pcBlinky_execute,
        (void *)&myBlinky);

Guard conditions

As you can see in the state diagram in Figure 7, the transition trigger is taken if the function execute is called, and if the guard returns true at that time. The guard condition in this case checks if the timer has fired. The combination of a synchronous execute function (which is called on a regular basis) and the guard condition can be interpreted as an asynchronous event in a synchronous environment. You can specify any logical condition in the guard section. The resulting C code generated for the execute function, shown in Figure 8, shows the effectiveness of this approach. Note that all C style comments generated by Rational Rose RealTime are removed here.


Figure 8. Code generated for a passive class
Code generated for a passive class

The code in Figure 8 shows that in the case statement, if the guard condition returns false the trigger is evaluated in the Parent State until the TOP state is reached. This is the expected behaviour, as defined in UML for state machines.

Handling multiple outgoing and border transitions and priorities

If there are multiple border transitions or outgoing transitions from a state, then all of these transitions are executed by a single execute trigger operation. Therefore, there is no way to tell the sequence of the executed guard checks. Rational Rose RealTime will place these checks sequentially after each other in a first drawn, first executed order, so in fact the order is arbitrary. If you wish to ensure the order of checked guard conditions, you can use a second trigger operation (called, for example, executePanic). You should call this function before the standard execute function in the scheduling loop. In the following example a (public instance function) wrapper called <classname>_main will call the two trigger operations in the required order.

void genSetup_main( struct genSetup * const this )
{
        /* first check the panic transitions, then the normal */
        genSetup_executePanic(this);
        genSetup_execute(this);
}

So, the general rule would be for you to create as many execute<prio> trigger operations as you have required priorities. Minimizing the number of priorities you require will avoid complexity.

Another simple option available to control the sequence of the executed guard checks for a specific state is to create a substate as shown in Figure 9.


Figure 9. Create a substate to force a guard check sequence
Create a substate to force a guard check sequence

Controlling the CPU budget

In an application driven by a time-sliced scheduler without pre-emption, each application should return from the execute function before the scheduler gets to the next slot. This is especially true for motor control algorithms (frequency-driven applications) where timing is critical for correct operation. If there is a lot of transition code in one of the transitions in a state diagram, and the execution time exceeds the maximum CPU budget, then you could add a transitory state and transition with the same trigger function and a default TRUE guard. This option is illustrated in Figure 10.


Figure 10. Create a substate to force a guard check sequence
Split up long transitions

Now the execution of the code of the long transition is split over 2 consecutive execute calls from the scheduler. Note that this approach violates the run-to-completion concept in some sense, for instance when combined with the executePanic function described in the previous section. In that case it might be possible that the code in longTransition_Part1 is executed and the code in longTransition_Part2 is skipped because a high-priority trigger is taken.

Experiments

Using the configuration described earlier, the team created an example model on both an 8- and a 16-bit microcontroller to gain information on memory footprints required for this approach. Also, a fairly complex Hardware Control experiment was performed: controlling an automatic document feeder (ADF) for a set of A4 simplex originals, using this configuration on an Infineon 16-bit microcontroller. See Table 3, later, for the benchmark results.

The approach proved to be very successful, especially concerning the latter ADF control experiment. In this experiment the team designed a controlSheetFlow procedure in a single passive class containing 44 states, including sub-states. This class was instantiated three times to be able to control three sheets simultaneously. The team created a lightweight solution to synchronisation issues between the three instances using Class-scoped variables and an Instance-scope pointer to the next instance. We named this approach the relay design pattern. The resulting C code from this class contained about 1,250 executable lines of code, while the TOP state in the state diagram could be viewed easily with minimal scrolling in the Rose RealTime state editor window. When adapting the application from the state diagram, the required transition is only a couple of mouse-clicks away. The implementation of this procedure into Rational Rose RealTime took about two man-weeks.

Three different motors were controlled using a single motor control loop designed outside the toolset. The motor control loop could also have been designed in Rational Rose RealTime, but a tested C version was already available.

Recommended approaches based on system constraints

For severely constrained devices the passive classes approach (C, no RTS) is recommended. If the system is constrained but more complex (a lot of objects with their own thread of control and dependent on each other), you should consider the NoRTOS approach with active objects. For less constrained systems, which are very complex, then use all of the features of the Rational Rose RealTime tooling with a full-featured TargetRTS. See Table 2 for a feature comparison between the three configurations:

Table 2. Feature comparison

Feature Configuration 1:
Large complex systems, C++, Threaded
Configuration 2:
Constrained systems, C, NoRTOS
Configuration 3:
Severely constrained systems, C, passive classes
State DiagramsYesYesYes
Asynchronous events & PortsYesYesno
Structure DiagramsYesYesno
Target observabilityYeslimitedno
InheritanceYeslimitedvery limited
Relative Performancegood (when used on 32-bit targets)good (when used on 16-bit targets)very good (on 8 and 16-bit targets)
FootprintDepends on RTOS (Minimal model is typically about 50 kB)Minimal: about 5KbMinimal: about 1Kb

Productivity, quality, and performance

High-level abstractions such as active objects are more productive, yet they require more resources. State machine abstraction is the same for both approaches and provides productivity gain because of the visual description of software behavior. On the other hand, the lack of structured classes in passive class models means more work sorting out the inter-class communication and managing concurrency issues such as scheduling.

The use of active objects introduces overhead with respect to the sending and dispatching of messages. The big advantage, however, is that the application is only triggered if there is work to do (an event-driven system should be idle when no events are being processed). If message traffic is high in your application, it might take a while before the object's behavior is called, due to message scheduling latencies.

In the passive class approach with a time-sliced scheduler, in contrast, the class function (execute) gets called from the scheduler every x msecs. However, most of the time the guard function will return false and this wastes the CPU time in your scheduling slot. Cyclic approaches such as this are clearly wasteful in terms of CPU utilization, but they simplify the design of time-critical period software.

Software quality in either case is high because of the use of a common code pattern: each class and each state machine in the model is generated into the code the same way. For a large team with a large application, this means that the generated code is similar for each developer, and the resulting pattern is well-understood and tested. It is recommended that you document common solutions for the application domain into example models before software engineers start to write the code. The whole development team can then share these example patterns.

Table 3. Benchmark results

Configuration Hardware Results Comments
Configuration 2:
Constrained systems, C, NoRTOS,
Infineon XC167 target @ 40Mhz. 16-Bit micro-controller. 128Kb ROM, 6Kb internal RAM. Blinky model: footprint: 4,9Kb ROM, 0,5Kb RAMSMALL memory model
  MessageSpeed Model: footprint 5,6Kb ROM 
  MessageSpeed Model: Performance: RTPort_send: 20µs, RTSoleController: dispatch: 20µs No message data
Configuration 3:
Severely constrained systems, C, passive classes
PIC18f452 target @ 40Mhz. 8-Bit micro-controller. 32Kb ROM, 1,5Kb RAM.Blinky model: footprint: 1,1Kb ROMClock, timer and scheduler are included.
 Infineon XC167 target @ 40Mhz. 16-Bit micro-controller. 128Kb ROM, 6Kb internal RAM.ADF control experiment: 44 states in main procedure; 3 motors, 23Kb ROM, 4,5Kb RAMSMALL memory model, Stacks not optimised

Conclusion

The use of MDD with passive classes is practical for very resource-constrained targets (down to an 8-bit microcontroller). The resulting state diagram code is very simple and efficient. Moreover, it's easy to introduce a new target platform because you don't have to port the TargetRTS. If the level of concurrency required to satisfy the requirements is limited, this approach can be very effective. In addition, if you keep the target-specific code to a minimum (with the use of #ifdef, for example) you can easily generate the same executable for a different configuration (using a host desktop to simulate the required behaviour).

Using active objects can further simplify your application design effort, because of the advantages of creating structure diagrams and objects connected through ports. The additional memory and CPU processing requirements are small. You can apply some simple extensions to the TargetRTS to incorporate a custom scheduling algorithm into the Rational Rose RealTime runtime. In this case, you will require design guidelines (minimizing the number of messages per time frame) in order to ensure that your latency requirements can be met.

Complex systems with numerous concurrent objects are best designed using active objects and the TargetRTS. This gives all the benefits of inheritance (abstract and derived classes), structure diagrams and ports, unwired ports, Target observability, and much more.

The common factor between all three methods is the use of state diagrams to generate code. This capability provides high-level modeling for the designer, and useable and efficient code for the system. State machines are a natural notation for dealing with event-driven systems, and many designers are comfortable using them.


Resources

  • Electronics Market Forecasters, April 2001

  • Embedded Developer Systems Survey, Summer 2001

  • For some great information regarding UML, you can purchase The Unified Modeling Language User Guide (by G. Booch, J. Rumbaugh, and I. Jacobson, Addison-Wesley, 1999) at a discount from the IBM® developerWorks® bookstore.

  • Object Management Group, Unified Modeling Language: Superstructure, version 2.0, Document # ad/2003-04-01, April 10, 2003.

About the authors

Biography to be posted.

Biography to be posted.

Report abuse help

Report abuse

Thank you. This entry has been flagged for moderator attention.


Report abuse help

Report abuse

Report abuse submission failed. Please try again later.


developerWorks: Sign in


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Select information in your developerWorks profile is displayed to the public, but you may edit the information at any time. Your first name, last name (unless you choose to hide them), and display name will accompany the content that you post.

Choose your display name

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


Rate this article

Comments

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Rational
ArticleID=84811
ArticleTitle=Model-Driven Development of Resource-Constrained Embedded Applications
publish-date=11142005
author1-email=none
author1-email-cc=
author2-email=none
author2-email-cc=

Next steps from IBM

Rational Modeler is a free, UML 2.1 based environment that helps users to improve communication by specifying, visualizing and documenting their system, architecture and software designs using a standard graphical language.


Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

For articles in technology zones (such as Java technology, Linux, Open source, XML), Popular tags shows the top tags for all technology zones. For articles in product zones (such as Info Mgmt, Rational, WebSphere), Popular tags shows the top tags for just that product zone.

For articles in technology zones (such as Java technology, Linux, Open source, XML), My tags shows your tags for all technology zones. For articles in product zones (such as Info Mgmt, Rational, WebSphere), My tags shows your tags for just that product zone.

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Try IBM PureSystems. No charge.

Special offers