Traditional computer languages such as BASIC, Pascal and C have ways to model data and functionality, but they don’t have any way to model objects, in the “object oriented” sense of that word. Instead, these languages treat data as dumb blocks of data which need subroutines, procedures or functions to manipulate them.
This historical situation is the reason embedded programmers mostly design systems by analyzing what the functions are supposed to do to the data, and modules are distinguished from each other in terms of what they do, not in terms of what object in the problem domain they represent. In contrast, a C++ object combines functionality and data in a higher level of abstraction than languages like C can achieve. This higher level of abstraction is one reason why C++ is more scalable and suited to large scale projects.
As an example, think of an electronic message delivery system. In C an engineer would create multiple instances of messages based on a struct , and would have functions to create and deliver those messages. But in C++ the Message object would know everything there is to know about messages – what data the message contains, what format that data is in, and also what functionality is associated with that message.
The Message object would know how to create the message, and there’s no reason why the message shouldn’t know how to deliver itself. The message is not just a lump of data, it has become an active agent which interacts with other active agents in the system to create the required behavior of the system.
For example, the Send() function for the Message class, interacting with other classes to obtain its routing information, would look something like this:
This is a very foreign notion to most engineers who have designed systems by functional decomposition, but it’s one reason why object-oriented designs are both more scalable and more reusable. All of the knowledge about each object is encapsulated in the object itself. If you design a Turbine object so that it really captures the essence of a Turbine, then that object can be used in any system which has turbines.
To make it less foreign, think of the objects in your software system as if they were employees in a large corporation. Each employee knows about their own area of expertise, and they know which other employees to interact with to get certain things done. If the employees just sat there until a manager told them what to do then it would be much less efficient.
The principle of decoupling works in a similar way. An engineer in the corporation who wants to sign up for the company’s health insurance knows to contact someone in human resources, but the engineer doesn’t need to know how the human resources person does the paperwork. If every employee in the company needed to know how everything else in the company works then the system would become hopelessly complex.
Putting Object-Oriented C++ Into Practice
The object-oriented analysis and design process is all about modeling a certain domain or part of reality. This domain is the “problem space”, as opposed to the “implementation space” where we do our implementation. It’s this stress on modeling reality which is the single biggest difference between object-oriented and procedural approaches to embedded software development, and the reason why good object-oriented designs have advantages over traditional designs.
The problem space should be kept totally separate from the implementation space, especially early on in the analysis and design process. The OOA process is emphatically not about modeling how we will implement our system. It’s extremely damaging to ask “how are we going to implement this?” before asking “what is this thing we’re implementing?”.
The OOA process is about “discovery” of objects which exist in the problem space. Only late in the OOD process should we get into “invention” of objects which exist only in the implementation space, not the problem space.
Recognizing Objects . There are three types of object in most embedded systems: (1) tangible objects, (2) intangible objects and (3) implementation objects.
Good examples of tangible objects are ICs which require software control, for example, an “Intel82559” ethernet controller. Another good tangible object might be the “RfTransmitter”, which might or might not have ICs with software programmable registers.
An intangible object is something which inherently exists in the domain but can’t be physically touched, such as a “MasterFrequencyList”.
An implementation object is something which doesn’t inherently exist in the domain, but which we need in order to implement the functionality which exists in the system, something like a “stack” or “queue”.
For people new to an object-oriented approach, it’s best to start the OOA by listing the most obvious objects, which are the tangible objects, followed by the next most obvious objects, which are the intangible objects and leave the implementation objects until the OOD stage, to avoid polluting the object-oriented analysis with a procedural analysis.
It might sound like cheating, but actually it’s completely legitimate to propose most of the nouns in the domain description document (which might be a requirements specification) as classes during the OOA.
Since this is a brainstorming session, the only suggestions which should be rejected are for classes of things which obviously don’t belong to the domain. If people new to OOA and OOD are present then implementation classes should also be rejected at this stage, to avoid old “procedural” ways of thinking from dominating the process.
If people suggest a mixture of implementation and non-implementation classes then it’s best to initially accept the implementation classes and only eliminate them at the end of the session; this keeps the brainstorming flow of thought moving.
At the end of the brainstorming you can explain why the classes weren’t appropriate, and reject them then. On the other hand, if most of the classes people are suggesting are implementation classes, then it’s best to stop, explain why they’re implementation classes, and then carry on.
The Seven Step Plan
After your initial brainstorming session or sessions, you can repeatedly apply the following seven step sequence until the design is complete:
(1) add any classes which people think are missing;
(2) delete any classes which people don't think are appropriate;
(3) decide whether to split any classes into 2 or more different classes, based on the five-box diagram (eg, split the existing EEPROM class into an EEPROM class which has all the attributes and functionality of a generic EEPROM device, and a derived MC24C04 class which has all the attributes and functionality which are specific to the MicroChip 24C04 EEPROM part);
(4) decide whether to combine any classes together into a single class, based on the five-box diagram;
(5) go through each class in the domain and fill in the obvious attributes and functionality of that class (eg, what attributes are inherent to an EEPROM class? what functionality should all EEPROM classes support?);
(6) list what other attributes and functionality the system inherently needs, and try to assign those attributes and functionality to existing classes;
(7) brainstorm new objects from whatever items in (6) couldn't be assigned to existing classes.
If you’re at a certain step in this process then it’s OK to go back to an earlier step and then return to where you were, but avoid the temptation to jump ahead to a later step, because each step in this process builds on previous steps.
The Gravitational Pull of Functional Decomposition
The functional decomposition approach to design is so pervasive in the embedded software industry that it takes a lot of effort to change your way of thinking. It’s almost like launching into space from a planet whose gravity is pulling you back. In my experience it takes a full six months of working on a project before it becomes natural and intuitive to think in object-oriented terms.
There are certain issues which you must fight to keep out of the project, otherwise it’s inevitable that you’ll get sucked back in to the old way of thinking. Even if all the names of all your objects are nouns, you can still fall into the trap of “Verbs in Noun’s Clothing”, which means verbs masquerading as nouns. Any object name with “er” at the end should ring alarm bells, and anything with “manager” in its name is almost certainly a result of functional decomposition. “Timer” is OK, “Scheduler” probably isn’t and “TaskManager” definitely isn’t. The name of an object should be as strong and un verb-like as possible – Rs232Link or Rs232Port is better than Rs232Transceiver.
The Downside of Object Oriented Systems
There’s a price to pay for achieving code reuse across projects. There’s no problem if you’re adding new functions to suit the needs of a new project, but if you delete a function or modify its signature then you’ll have to modify all of the projects which use that function. Your library of reuseable classes is a valuable thing, and you need to manage how it’s developed and enhanced.
If you’re so short of processor bandwidth or memory that you’re counting processor cycles or bytes then don’t bother, because of the time and space overhead of virtual functions. I’ve used these techniques successfully on 8-bit processors with 32K of RAM and 32K of ROM, but I’d hesitate if I only had 256 bytes of RAM.
I’m also not sure if OO is the best approach if your application is 90% math. There are categories of problem, such as those which are primarily mathematical, for which a functional decomposition is more appropriate than an object-oriented design, but for the vast majority of embedded systems an object-oriented design is more appropriate.
Originally from New Zealand, Richard Seaman has been working in the United States as a contract software engineer since 1997 in the telecommunications, automotive, wireless audio and medical industries. He does architecting, design and implementation of embedded real-time software systems. He can be contacted at .