EmbeddedRelated.com
Blogs
The 2024 Embedded Online Conference

Lightweight hardware abstraction

Gene BrenimanJanuary 31, 2012

Some lessons are tougher than others to master.  You would think that hard fought battles would be easier to remember, but sometimes it just does not work that way.  Recently, I was asked to pick-up a project that had been managed by another employee.  The project was yet another cost reduction project.  The hardware group was tasked with updating a currently shipping product, to reduce the existing failure rate, while at the same time to remove cost from the product.  The redesign caused the engineer to shuffle around pin assignments on the microcontroller in order to free up some of the analog inputs.  There was also a strong desire to make as little change as possible to the existing firmware for this device, so as to limit the scope of revalidating the product.

My first impulse was to jump right in and hack in the changes and move on to something more interesting.  I isolated the changed signals, and started searching through the code for occurrences of the effected pin names.  I was quite surprised when I found over 100 references to one of the pin names.  Fighting against my better judgment, I jumped in and proceeded to go instance by instance and modified the code with #ifdef statements to change all of the reference of one signal for another.  In a day's time, I had the code converted and soon it was happily running on the new prototype.  Not my best work, or my proudest moment, but the work was done and I was free to move back to my real interesting work.

For example:

    if(ready)
    {
#ifdef OLD
        PORTB.4 = 1;            // Turn LED off
#else
        PORTB.7 = 1;            // Turn LED off
#endif
    }

Soon, it was decided that a few more hardware changes were needed.  A new version of the hardware was developed, with even further pin changes.  We had shipped some of the earlier prototypes off to a remote engineering location, where some testing was being done on both versions of the prototype.  Now I needed to maintain multiple versions of the firmware to support the two different prototypes.  This was already beginning to get ugly quickly.  Now the already messy code was getting even messier.

With the new changes in place the code now looked like this:

    if(ready)
    {
#ifdef OLD
        PORTB.4 = 1;            // Turn LED off
#elif PROTO_ONE
        PORTB.7 = 1;            // Turn LED off
#else
        PORTC.2 = 0;            // Turn LED off
#endif
    }

One simple change would not have looked so bad.  But take multiple changes, multiplied by a varying number of occurrences and soon the code is really a mess.  But wait, what could I have done differently?  The simple answer is abstraction. From webopedia, I found the following definition for abstraction:

The process of picking out (abstracting) common features of objects and procedures. A programmer would use abstraction, for example, to note that two functions perform almost the same task and can be combined into a single function. Abstraction is one of the most important techniques in software engineering and is closely related to two other important techniques -- encapsulation and information hiding. All three techniques are used to reduce complexity.

Here the idea would be to come up with a way to represent the required functions into a single include file for each version of the hardware.  In this way, a single include statement could be changed in order to build a unique version of the firmware for each of the hardware versions.  In the simplest form,  each portion of the statement in the example could be defined in concept and the actions could be abstracted from the code.

The modified code would then read:

    if(ready)
    {
        MODE_LED = LED_ON;
    }

In order for this to work, we need to create include files that contain the actions that have been abstracted from the code.  Three include files are need: old.h, proto1.h and proto2.h.  Each of these files would need the necessary defines to represent the higher level functions or representations of MODE_LED and LED_ON.

For old.h:

#define MODE_LED        (PORTB.4)
#define TURN_ON         1

For proto1.h:

#define MODE_LED        (PORTB.7)
#define TURN_ON         1

For proto2.h:

#define MODE_LED        (PORTC.2)
#define TURN_ON         0

Now by including the correct include file all of the necessary changes can be hidden, leaving the code cleaner and far more readable.  Again, I had learned this lesson before, but somehow when I was faced with a similar problem, my first choice was the wrong one.

May your first choices serve you better.



The 2024 Embedded Online Conference

To post reply to a comment, click on the 'reply' button attached to each comment. To post a new comment (not a reply to a comment) check out the 'Write a Comment' tab at the top of the comments.

Please login (on the right) if you already have an account on this platform.

Otherwise, please use this form to register (free) an join one of the largest online community for Electrical/Embedded/DSP/FPGA/ML engineers: