Object-Oriented firmware

OO is a mind-set, a design style. We can design OO firmware, as long as our style favours minimalism rather than the baroque. It’s desirable for our implementation language to offer native support for OO, but it isn’t essential. We can implement an OO firmware design in assembly language if necessary. OO is just a way of working.

The interesting thing about OO firmware is how we implement it. Our team chose not to use C++ because, at the time we were making these decisions, it was ten years since I’d written C++ professionally, and I was the only member of the team with any C++ experience at all.

C++ is the most complicated language I’ve ever encountered. It’s a high-ish level OO language that can still connect directly to the hardware, which is important in firmware. But it takes a lot of time and effort to learn to use C++ well. Read this book to see what I mean. [It’s a Very Good Book, by the way.]

We chose to stick with C, and I call our approach classes-in-C. Simplicity is a wonderful thing in firmware, and classes-in-C is nothing if not simple. The OO features we really need for firmware are easily implemented in C, so this simplicity comes quite cheaply.

Each .c file encodes a class, and usually the one and only instance too. Private class data is implemented as static class variables. [Static variables are persistent, so they continue to exist, and retain their values, in between invocation of the class task(s).] Private class methods are declared and defined within the .c file. The class API comprises the methods defined in the class header file, where we also include any necessary typedefs and symbolic constants.

Our RTOS used co-operative multi-tasking, which executes each task to completion before executing the next. This allows a task owned by one class to call the API methods of another class. Pre-emptive multi-tasking simulates continuous execution of all tasks, so a call to an API method could occur while a non-interruptible operation is taking place within the class/instance. This is not an insuperable problem, but it is a difficult one, and it can be completely avoided by using the simpler and quicker method of task-switching. We only need to ensure that no task runs for too long, closing out the others.

Identifying classes in firmware is often quite straightforward. We need a suite of peripheral driver classes, one for each device type. We made a rule that each peripheral device is owned by exactly one class. This has the happy side-effect of encapsulating interrupt processing inside the driver class, where the thread-safety aspects are handled, contained and hidden. [In the unusual event that two or more classes needed access to the same peripheral device, we just added a new node (class) to arbitrate incoming access requests, and share the peripheral device out as required.]

An API method and a table of function pointers serves to implement the Command pattern quite effectively. We have no real need of polymorphism. And so we found it for the other OO features that OO languages support natively. As simple as our implementation is, we found that we could use any software concept or convention that was truly appropriate in a firmware context. Using C did not seem to place any significant constraints on our designs, or on our choice to use an object-oriented design approach.

I have seen some quite clever schemes to emulate more native OO support in C than we had. They all seemed to rely heavily on macros, which I don’t like because a symbolic debugger can’t execute a macro. We all try to avoid debugging at this level, but sometimes we have to, and it isn’t good if you can’t follow the execution of the code properly. You can guarantee that the rogue write-operation you’re searching for will happen in the macro code, where we can’t see it! Have you had any success with schemes like this?

This post may be edited to correct it, or to expand on points made in comments. Please leave your own, and join our discussion!

Object-Oriented firmware

7 thoughts on “Object-Oriented firmware

  1. I like your comment that OO design is a philosophy. I have had many discussion with other embedded engineers about using OO techniques in embedded C and nearly every one with start discussing the probelm with new and heap management.
    Like you I have built ‘classes’ to encapsulate peripherals, sensors, and even radio interfaces. I usually created structures to define ‘objects’ with function pointers to be used as their methods.

    Liked by 1 person

    1. Right! You can certainly use an OO approach in C without using the heap (which is the biggest issue for most people). Proper encapsulation — e.g. using static to indicate “private” members — goes a long way to preventing spaghetti code.

      In my experience though, as you add more and more of your own custom “object machinery” (function pointers, simulated inheritance) it seems like it might have been better just to use C++.

      Like

      1. Agreed, Matt … provided that you can support and use C++ without introducing too many new problems! 🙂 It didn’t suit our team, for a number of reasons. The smallest target we supported was an MSP430 with only 256 bytes of memory. We squeezed in our (very simple) RTOS, but C++ would’ve been a step too far! 😉 Also, along with more technical reasons — and this is truly important — there was no enthusiasm for using C++ within our team.

        And we never found the need for inheritance; we used composition instead, as is often recommended. It worked for us. YMMV 😉

        Like

  2. That’s a little different from me, but only a little. We seem to have homed in on the same way of doing things. [I had no problem with new and heap management; see next blog post.] Thanks for commenting! 🙂

    Like

  3. mattchernosky says:

    “Each .c file encodes a class, and usually the one and only instance too.”

    Nice. I usually find that the effort to make something support multiple instances isn’t worth the effort. It bugs me to see the work a lot of developers do up front to support this, even though there is no reason we’d ever create another instance.

    I’d love to see a more detailed examination of your use of the command pattern!

    Like

    1. Supporting multiple instances is as simple as gathering the class variables into a newly defined struct type, and then creating and using an array of them. We rarely needed this, but when we did, it worked fine. 🙂

      Like

Leave a comment