Programming Style for Embedded Application and Real-time Systems
© Stan Bleszynski, 9-July-2006
S&M Process Control Technology Inc.
The thesis of this article is that the Function-Oriented modular programming style is more appropriate in the medium and higher levels, for most embedded and real-time applications than the Object-Oriented (OO) programming.
Historically, the Function-Oriented (FO) and the Data-Oriented (DO) programming styles were first to emmerge in the “prehistoric” times. The data oriented style has been typically confined to some special type of applications, for example data bases, list processing engines and some graphic engines. On the other hand, the function-oriented style has been the most popular and widely used in application software until the widespread incursion of object-oriented (OO) programming languages like C++.
Since object-oriented languages allow integration of data and functions in a single entity (object=data+functions), it is often assumed that it should superceed the older programming styles. Unfortunately due to certain factors, it appears generally not to be the case. To the contrary – the careless usage of OO style can result in programs that are more complicated for a human reader and thus harder to maintain. While it is possible to use OO style in a similar fashion to the old style, i.e. to emulate either FO or DO due to the superior power of the objects, it is nevertheless rarely done. It requires a certain discipline not to abuse the features, while it is tempting to utilize the full power of the OO language. Unfortunately, doing so results usually in the program structure that tends to grow too vertical. Such structure often does not seem to fit the nature of the typical real life physical problem being modelled. The most commonly abused OO style seems in fact to resemble the situation with the old DO style that is generally unsuitable to typical applications (except perhaps databases). This is caused by the ease with which an exceedingly vertical hierarchical structure of objects can be constructed in OO languages, particulary in C++. It takes a certain discipline and foresight to design a structure of objects on the same level sharing a common interface (module) without undue complications and in such way as to maintain a sufficient clarity and readability.
The second problem with OO (apart from “verticality”) is that it is too easy to make mistakes early in the project by assigning object the wrong way. It is much more difficult to correct it later on in the OO than in the FO style. For example, when modelling a DSP board, OO style works best when all objects have a direct correspondence with the actual major chips on-board rather than with physical functions or operations. Software objects are meant to resemble material objects, not actions. In other words:
Software Objects <–> Physical Objects – correct!
Software Object <–> Data Structure (under old DO style) – correct!
Software Object <–> Physical Action – wrong!
Helper Function <–> Physical Action – correct!
Member Func. of one common global class <–> Physical Action – correct!
In the ideal world, OO headers and libraries should in fact be supplied by the chip vendors. Such objects should act as low level primitives. Most of the module should consist of either the classless helper functions corresponding to the physical actions being modelled, or of the member functions of one common global object called for example “MyBigMachine”. This way the horizontal nature of action-oriented program modules can be easily maintained. Very rarely there should be a need to declare separate objects in the middle layer. For example there is no advantage of declaring some high or middle level classes such as FFTcalculations, FIRfilter, Calibrate, Interpolate, etc. A more appropriate way is to have a bunch of local and global helper functions all in a module/file called “MathCalc”.
On the other hand, the objects do have an advantage when designing low level generic primitives: the hardware-mimicking primitives at the chip level as well as the primitives for certain mathematical constructs, for example: list, vector, matrix, complex numbers, multidimensional arrays, variant type etc.
In other words, there is a good rationale for using mostly the pure function-oriented style on the high and medium levels. At the same time there is also a good reason for using object-oriented constructs (classes) for modelling the primitives, on the lower level.
Another issue is the project splitup into a hierarchy of modules. That selection/division should, unlike the division into classes, reflect solely the nature of a physical action or a process (not data) that the module is supposed to handle. For example, the lowest level module that is directly talking to the hardware should be called by the name reflecting a basic action it does, that is: HardwareIO, NetworkComm, etc. The middle level modules should reflect computational or data processing processes, for example FFT, MathCalc, ImageProc etc. The highest level typically handles the operations related to GUI, interface and error processing, and should use the clearest and the least complicated software constructs and language. The modules should be designed such that the functions may be tightly coupled with each other inside the module (locally) but only very loosely depend on (or call) other functions outside of the module. It is recommended that a module may call functions only from lower level modules but not from the higher level. Note: the only module that ought to be allowed to access all levels of hierarchy directly, should be the error processing module.
To summarize the above, one could formulate the following 3 guidelines:
1. Use Object Oriented programming style in the low-level modules!
Application software should be split on the lowest level into classes (objects) based on mimicking the primitive physical (material) objects and mathematical data primitives. If the language does not support objects then the data-oriented approach may be utilized, with encapsulation of the local data through primitive global functions.
2. Use Function-Oriented programming style at the medium and high levels!
Upper layers should be split based on physical action or computational operations into appropriate modules and should use mostly the function-oriented approach. Functions within a module should be tightly coupled but modules should be only loosely coupled with each other and mostly in one direction (high to low).
3. Minor exceptions to 1 and 2 should be tolerated.