Simulating Embedded Software: 3 Simple Tips for Better Results

Simulating embedded software is a technique that most teams underutilize or completely ignore. You’ve probably worked on a product before where the embedded software had to be started immediately, but the hardware was months away from being ready. It’s not uncommon for teams to start development with a franken-board. Basically, a development board is torn apart with wires extruding everyone connecting break-out boards for various sensors, actuators, and the like. Other times, the software team doesn’t even know that a product is in development until a few weeks before the prototype hardware arrives, and a mad dash occurs to develop firmware to test it.

An example franken-board that while useful, can be replaced with simulation techniques.

In both situations, you’re not off to a great start. In fact, from my experiences, if you start as I’ve just described, you’re already on a path toward project failure. First, firmware and application software need to begin as soon as possible. The sooner you can get something in your customer’s hands and get feedback, the better. While a franken-board seems like the best way to do this, it will likely lead you and your team down the path of being tightly coupled to the hardware, a slower development cycle, and debugging loose connections.

Simulating embedded software is an alternative to franken-boards and mad dash firmware development. Simulation allows teams to write their application code off-target without their hardware. Almost immediately, teams can develop their application code and test it on the host, get customer feedback, save time from cross-compilation and on-target debugging, and write portable, reusable, and decoupled code, among many other benefits. Let’s explore a few tips to help you transition to using simulation techniques in your embedded software development.

Tips #1 – Simulation does not require a simulator

The first step to leveraging simulation in your embedded software development is recognizing that you don’t need a simulator. If you explore simulation and emulation for embedded systems online, you’ll find exciting tools like QEMU, Renode, and many others. While these tools are excellent for simulating code execution on your target, you don’t need these tools to simulate your application code, which honestly is what you care about. Your application code is the value added to your customer, the secret sauce, not the microcontroller hardware. As much as your silicon vendor wants you to believe your product is better with their parts, your application could run as successfully on any of them in most cases.

All you need to start simulating your embedded software before the hardware arrives is the ability to compile your application on your host machine and run it. But Jacob, I don’t have my low-level hardware, so how should I run my code without it? Your application doesn’t need low-level microcontroller hardware; it just needs something that implements the behavior on the host machine.

For example, I can just as quickly write application code that blinks an LED on my Mac as on an embedded target. The difference is that when my application calls an LED API on my host, it might just print a message in a log or send a TCP/IP message rather than flip the memory bit on the microcontroller that will turn the LED on or off. All I need to simulate my embedded system is some supporting software on my host that makes my application believe the microcontroller is there, even though it isn’t.

Tip #2 – You must use hardware abstraction layers (HALs)

II’ve probably been writing about using HALs for the last decade; in fact, my first book Reusable Firmware Development, focuses heavily on HALs and APIs. They form the foundation for embedded software that is reusable, portable, and simulatable (Yes, I guess that’s a made-up word, but you get the idea). If you want the advantages of simulation, you need to decouple the application code from the hardware so that you can swap out the hardware layer with something that acts like the hardware. Let’s look at an example.

Most embedded teams today are using C rather than C++. You might say you can’t create interfaces like in C++. In C++, a developer can create a GPIO interface using a pure abstract class as follows:

Example C++ Interface for Simulating Embedded Software

template <typename PortWidth = std::uint32_t>

class dio_base {

public:

    typedef PortWidth dioPort_t;

    typedef PortWidth dioPin_t;

    // Constructors and Destructors

    dio_base() = default;

    virtual ~dio_base() = default;

    // Class methods

    virtual void write(dioPort_t port, dioPin_t pin, dioState_t state) = 0;

    virtual dioState_t read(dioPort_t port, dioPin_t pin) = 0;

You can see where we have virtual functions that a derived class must implement because they are pure (= 0). You might say we can’t do this in C, but you can!

I can write C code that does the same thing using a structure and function pointers:

Example C Interface

typedef struct {

    void            (*init)     (DioConfig_t const * const Config);

    void            (*write) (dioPort_t const port, dioPin_t const pin, dioState_t const state);

    dioState_t (*read)  (dioPort_t const port, dioPin_t const pin);

} dio_base;

When I create a variable of type dio_base, I need to assign a function to the function pointer that implements the desired behavior. In C, I can assign the pointers to functions that implement microcontroller-based memory mapping and behaviors. I can also assign functions that run on the host that simulate and pretend to do what the microcontroller hardware does! I can then pick and choose whether I’m compiling the application to run on the target or whether it will run on my host. Then, on the host, I can test that application code and make sure that it behaves the way the customer wants before I ever have my hardware.

Note: Simulation and testing on the host is a significant time and cost saver; however, you’ll still need to test on your target hardware. There may be behaviors you can’t test on the host, like performance, that can only be done on target.

Tips #3 Leverage modern software to visualize your hardware

When you use a HAL to break the application’s dependency on the hardware, you can substitute any function on the host to get the desired behavior. One cool trick that I’ve used is to replace a microcontroller function with a module that talks TCP/IP so that I can use sockets and web technologies to drive application testing and visualization. For example, instead of writing a module that simulates an LED state by writing to a log file or printing to a terminal, you send a message to a web GUI that turns an LED on or off.

Using a technique like this is relatively straightforward and not very time-consuming. It allows developers to get visual feedback on what their system would be doing, but more importantly, it drives the possibility of driving automating testing. Can you imagine being able to feed your application code faults that are difficult or nearly impossible to generate at will on a microcontroller? How much more robust would your code become? Could you imagine running those tests nightly to ensure the new code didn’t break already tested software? What feedback could you get from your customer early that would drive changes to save time later in the development cycle?

Embedded Software Simulation Conclusions

You don’t need your hardware to write your application code. Likewise, you don’t need a simulator for your embedded target to write your application code. You can simulate your embedded software on your host machine with only a HAL and a few hooks to get the desired behavior. Simulation can help you get started on your embedded software sooner and decrease costs and time to market while also allowing you to build out automated testing for your application.

Simulation techniques are one of the most underutilized techniques by embedded software developers. The reason is that we often think we need the physical hardware. Target simulators and emulators often are challenging to use or don’t support the device you want to use. Therefore, developers just skip simulation and focus on their franken-boards. Franken boards are great but don’t lend themselves well to automated testing. If you embrace simulation, you’ll discover that the benefits and possibilities are nearly endless once you get past the learning curve.

Share >