Guidelines for using C++ as an alternative to C in embedded designs: Part 2 - Embedded.com

Guidelines for using C++ as an alternative to C in embedded designs: Part 2

Click here to see Part 1.

An object oriented language such as C++ enables data and code to beencapsulated into objects, which can then be utilized without anyknowledge of their internal workings.

This approach is particularly attractive when a large software teamis used for a project. Different members of the team will havedifferent knowledge and capabilities and it is the encapsulation ofthat expertise which is so useful.

This perspective is strongly emphasized in embedded softwaredevelopment, where there may be a wide spread of expertise from the”embedded expert”, who is used to working close to the hardware withdrivers etc., to the applications specialist, who knows little aboutthe ways that embedded systems differ from desktop computers.

If the expertise of the embedded expert can be encapsulated into C++objects, the applications specialists can proceed efficiently andsafely. In the case study that follows, a classic embedded systemproblem will be addressed: the handling of a write-only port.

Case Study: A Write-only Port
It is not uncommon for an embedded system to have a device (a port) towhich a data value may be written, but from which no data may be read “a write-only port.

Although this sounds like a joke played upon software engineers bythe hardware designers, it is really just a design which makes economicuse of hardware design elements. Handling a write-only port in softwareis reasonably straightforward ” it is just a matter of keeping a”shadow” copy of the last data written.

The matter becomes more complex when, as is often the case, the bitsin the port have a selection of functionally unconnected purposes. Thismeans that a number of parts of the software need to use the portcorrectly, which presents some challenges.

A Solution in CIt is entirely possible to implement a solution for write-only ports inC. Here is some possible code:

extern int ports[10];
int shadow[10];

void wop_set(int port, int bit)
{
        int     val;
        val = 1 << bit;
        shadow[port] |= val;
        ports[port] = shadow[port];
}

void wop_clear(int port, int bit)
{
       int     val;
       val = ~(1 << bit);
       shadow[port]     &= val;
       ports[port] = shadow[port];
}

Although this code will do the job, it suffers a number ofshortcomings:

1) There is no provision forinitializing the data. Something (the linker probably) would need tomap ports[] onto a sequence of write-only ports. At some point, aninitial values need to be written to the ports and to shadow[].

2) There is no mechanismcompelling programmers to use these functions ” accessing ports[] andshadow[] directly is a possibility, even if it is ill-advised.

3) The code is notreentrant, which would be a problem if a real-time operating system(RTOS) is in use or even if interrupt service routines might need toaccess the ports. Reentrancy could be implemented, but would be veryapplication specific.

4) This bundle of code anddata is not encapsulated, so distribution is not readily controllable.

An Initial C++ Solution
There is no single solution to a problem such as this, so a series ofapproaches will be investigated. Here is a first attempt at awrite-only port (wop) class:

class wop {
       int shadow;             // not accessible
       int* address;           // to user public:        wop(long);              // constructor
       ~wop();                   // destructor        void or(int);            // “operator”
       void and(int);          // functions };

This hides the address of the port itself (address) and the shadowcopy of the data (shadow) in the private part of the class, which canonly be accessed by member functions.

A constructor initializes the member shadow value and the portitself with an arbitrary initial value (of zero):

wop::wop(long port) {
       address = (int*) port;
       shadow = 0;             //initial value
       *address = 0;
}

The destructor would probably simply write zero to the port toreturn it to its initial state.

Two further member functions provide the user with a means ofperforming OR and AND operations on the port, which facilitates settingand clearing of individual bits:

void wop::or(int val)
{
        shadow |= val;                 // setbit(s) in copy
        *address = shadow;        // update port
}

void wop::and(int val)
{
        shadow &= val;               // clear bit(s) in copy
        *address = shadow;          // update port
}

Inboth cases, the shadow data is maintained automatically. Theapplications programmer can create wop objects and use them thus:

main()
{
        wop out(0x10000);

       out.or(0x30);                    // set bits 4and 5
out.and(~7);                            // clearbits 0, 1 and 2
};

Overloaded Operators
This C++ solution may be improved in a variety of ways. To start with,the use of the two member functions to perform OR and AND operations isnot “natural”. It would be much better if intuitive operators whereavailable. This can be accommodated by overloading the |= and &=operators thus:

class wop {
       int shadow;
       int* address;
public:        wop(long);                        // constructor        ~wop();                            // destructor        void operator|=(int);         //overloaded
       void operator&=(int);        // operators };

The way these operators perform should be exactly as any C/C++programmer would expect. They may be implemented like this:

void wop::operator|=(int val)
{
            shadow |= val;             // setbit(s) in copy
            *address =shadow;     // update port
}

void wop::operator&=(int val) {
           shadow &= val;            // clear bit(s) in copy            *address = shadow;      //update port
}

Interestingly, this code is identical to the previous memberfunctions, with just a change of function names. The new operators maybe used thus:

main()
{
        wop            out(0x10000);

       out |= 0x30;                         // set bits 4 and 5
       out &= ~7;                           // clear bits 0, 1 and 2 }

<>Initialization
With the wop class above, there is a rational strategy forinitialization ” zero is written to the port (and saved in the shadow).However, it may be useful to allow the user to specify a custom valuefor each object. This requires a small change to the class definitionand the constructor function, thus:

class wop {
           int          shadow;
           int*         address;            int          initval;                    // stored initial value
public:            wop(long, int);                        // constructor            ~wop();                                  // destructor
           void operator|=(int);               // overloaded            void operator&=(int);            // operators };

wop::wop(long port, int init=0) {
           address = (int*) port;            initval = init;            shadow = initval; // initial value            *address = initval; }

The constructor takes an additional parameter, which specifies theinitialization value, which may be applied like this:

main()
{
        wop    out(0x10000, 0x0f);

       out |= 0x30;                               // set bits 4and 5
       out &= ~7;                                // clear bits 0, 1 and 2 }

Note that this second parameter has a default value of zero, whichwould be used if the constructor were called with a single parameter.This provides useful backwards compatibility with the previous versionof the wop class.

The initial value is also retained in a private member variable(initval) which might be utilized by a destructor:

wop::~wop() {
       *address = initval;                    // shutdownvalue
}

Reentrancy
As mentioned earlier, there is a possible issue with the reentrancy ofcode. The wop class, as it stands, is vulnerable if objects of thisclass are likely to be accessed by multiple program threads ” i.e.tasks in a multi-tasking system or interrupt service routines as wellas mainline code. If the interrupt occurred between updating of theshadow data and writing to the port, problems would result.

As is common in many real-time programming situations, impartingreentrancy is a matter of locking and unlocking a resource. To do thiswe need to extend the class slightly by adding two further (private)member functions:

class wop {
           int shadow;
           int* address;
           int initval;
           void lock()                { };     // dummyfunctions
           void unlock()            { };
public:            wop(long, int);                   // constructor
           ~wop(); // destructor            void operator|=(int);           // overloaded            void operator&=(int);        //     operators };

For the moment the two functions ” lock() and unlock() ” are dummiesand do nothing. It is possible to imagine how they might really work.Perhaps they would simply disable and re-enable interrupts. Somepossibilities will be discussed later.

These functions are used to make the two operator functionsreentrant, thus:

void wop::operator|=(int val)
{
            lock();
            shadow |= val;                    // set bit(s) in copy
            *address =shadow;             //update port
            unlock();
}

void wop::operator&=(int val) {
           lock();
           shadow &= val;                   // clear bit(s) in copy            *address = shadow;              // update port            unlock();
}

To implement these functions in the most flexible way, it ispossible to take advantage of the object oriented capabilities of C++.If the functions are made replaceable (virtual), new variants may bedeployed without further changes to the class:

class wop {
       int shadow;
       int* address;
       int initval;
       virtual void lock();                  // replaceable functions        virtual void unlock();
public:        wop(long);                               // constructor
       ~wop();                                    //destructor
       void operator|=(int);                // overloaded        void operator&=(int);             // operators };

A new class – rwop – may be derived from the wop base class, whichinherits all of its characteristics, but adds function lockingfunctions:

class rwop : public wop {
       int         flag;        void lock()                // replacement functions         {                while (flag)                       ;
               flag = 1;        };
       void unlock()
        {                flag = 0; }; public:        rwop(long, int);                // constructor };

The lock() and unlock() functions now use a private member variable(flag) to control access to the object. (This is not strictlyreentrant, but serves to illustrate the point for the moment.) A newconstructor is required:

rwop::rwop(long port,    int init=0)   :  wop(port,  init)
{
       unlock();
}

This simply executes the wop constructor code and calls unlock().Nowthe application programmer can use the rwop class in exactly the sameway as wop was used and does not really need to know that anything hasbeen changed:

main()
{
        rwop out(0x10000, 0x0f);

       out |= 0x30;                    // set bits 4and 5
       out &= ~7;                    // clear bits 0, 1 and 2 }

Using an RTOS
If reentrancy is needed because an RTOS is in use, it is logical to usethe services provided by the kernel to create a reentrant derivative ofthe wop class:

class vwop : public wop {
       int* mbox;
       int err;
       void lock()             //replacement functions
        {                sc_pend(&mbox, 0, &err);        };
       void unlock()
        {                sc_post(&mbox, (char*)1,&err);
};public:
       vwop(long, int); // constructor
};

In this case a mailbox is used to store a token. A binary semaphoreis another – perhaps more likely – way to implement the flagging. Againthe application programmer can use the vwop class in an identical wayto the earliest wop class.

The vwop class successfully encapsulates two separate, mutuallyexclusive fragments of expertise: knowledge of the write-only porthardware and RTOS programming experience. The application programmerneeds no awareness of either.

Other Possibilities
The ideas presented here may be developed further to add additionalcapabilities or to address different, but similar, problems.Possibilities include:

* Add an exclusive-OR operator: ^=

* Provide a means of retrieving the shadow data (perhaps byoverloading the int casting operator)

* Treat a port as an array of bits (overloading the [] operator)

* Accommodate arrays of ports (at consecutive addresses)

* Apply the same ideas to handle indexed ports, where the “address”of a device's internal register needs to be written first, followed bythe actual data reading or writing

C++ and an RTOS
When an embedded software application is being developed using areal-time operating system, there are some distinct benefits to begained from the careful use of C++.

The two clear advantages are: application programmers do not need tohave detailed knowledge of the RTOS programming interface and theapplication code is likely to be portable to an alternate RTOS, if achange is necessitated.This topic is quite large and only a few aspectsmay be considered here:

Task/ObjectCreation. Most RTOSes have a conventional C function librarywhich serves as an application program interface (API). This may beproprietary or may conform to a standard (like POSIX, for example). Ineither case, many API calls may be quite complex, with numerousparameters. This is an ideal opportunity for encapsulation ofexpertise.

Consider thecreation of tasks. A base class could be designed whichcharacterizes a generic task. The key components of the class would bea constructor (which creates the task), a destructor (which deletes thetask) and an empty virtual (i.e. replaceable) main() function.

An application task could then be defined by deriving a new class,which replaces the main() function with the actual task code and addsin other requirements (like the task stack perhaps).

Instances of the task could then be created by simply instantiatingobjects from the class. Note the separation of task definition(creating the class) and the actual creation of tasks (objectinstantiation).

The same kind of approach may be taken to the handling of other RTOSobjects like mailboxes, event flags, semaphores, timers, queues, pipesetc.

Thread LocalObjects. C++, like C, has no in-built concept of a thread or atask ” the language essentially assumes a single thread of execution.By creating a class that represents a task, this shortcoming iseffectively overcome. This brings with it some useful benefits. A keyone is the ability to have thread local objects (and variables).

When using an RTOS conventionally, it may be quite common to createa number of identical tasks, each of which operates on different data,while sharing code. This is a challenge as there is no straightforwardway to have conventional variables that are specific to a particulartask (i.e. thread local variables).

By adding member variables to the task definition class, eachresulting object has its own copy of each variable (unless they need tobe shared, in which case they need to be declared static).

An extension of this idea is thread local variants of other RTOSobjects. If the task definition class includes a declaration of asemaphore (for example), that object will be created specifically foreach task instance and be inaccessible elsewhere.

PairedOperations. It is a common requirement in real time applications(with or without an RTOS) to define paired operations: two actions thatcomplement one another and are executed at the beginning and end of acode sequence.

For example, if a set of instructions must be executed without anypossibility of interruption, it may be preceded by a disable interruptsand succeeded by an enable interrupts. It may be problematic to ensurethat both operations are performed, particularly if the code logic iscomplex.

An object oriented “trick” can provide a simple way out. All that isrequired is a simple class which only contains a constructor and adestructor. The constructor disables interrupts and destructor enablesthem again.

<>Then it is simply a matter of instantiating an instance of this class(an object) at the head of a block. Interrupts will be turned off onentry to the block and turned on again when the object goes out ofscope ” i.e. when execution leaves the block by whatever means. Thestructure of the code looks like this:

{ object declaration (constructor disables interrupts)
(destructor re-enables interrupts) }

This approach has numerous applications. Others include: lockingresources, turning on/off an RTOS scheduler and providing pre-amble andpost-amble for an ISR.

Here is some possible code for a class to provide device lockingusing an RTOS semaphore:

class Device_Locked {
private:        static Semaphore device;         // NBstatic
public:
        Device_Locked()
       { device.acquire(); };
       ~ Device_Locked()
       { device.release(); };
};

To read Part 1, go to: Why is C++not more widely used?

Colin Walls has over twenty-five years experience in theelectronics industry, largely involved with embedded software. Afrequent presenter at conferences and seminars including the EmbeddedSystems Conference he is a member of the marketing team of the EmbeddedSystems Division of Mentor Graphics.

Click here to see Part 1.

1 thought on “Guidelines for using C++ as an alternative to C in embedded designs: Part 2

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.