How to choose the right firmware architecture for your IoT device

By Steven L. Kelsey, CTO at LocoLabs

Choosing an operating environment for your IoT device is a crucial task that will affect your firmware development approach and the success of your overall project.

In this article, I provide an overview of various operating environments, specific factors you should consider in making your choice, and recommendations in the form of examples.

As the Internet of Things (IoT) takes off, making the right choice is more important than ever.

I cover the following styles of operating environments:

  1. Bare metal, both super loop and event-driven
  2. Embedded and full-featured operating systems
  3. Virtual and abstracted environments

Background

IoT encompasses devices ranging in size and complexity from tiny to large, serving markets from consumer to commercial to industrial to infrastructure.

These devices vary in their purpose and differ in their:

  • Sensors
  • Actuators
  • Data
  • Communications
  • Power
  • Packaging
  • Deployment

But what do all of these devices have in common? They all depend on software.

IoT devices need embedded software to operate the hardware, gather and process data from sensors, control actuators, and communicate with other devices and the cloud.

I’ve spent the last 30+ years designing electronics-based products, both the hardware and the software, and some of the fun stuff in between.

In almost every case, software played a crucial role in realizing (or limiting) the hardware’s potential.

Whether you call the software running on a given processor firmware, software, or a combination of the two, it’s ultimately software in the end.

Software running on an IoT device is often called firmware. Complex devices using feature-rich operating systems (e.g. Linux) can have many software components, including multiple applications, and often still bundle firmware for low-level hardware control and booting of the OS.

Software running on an IoT device needs to have a reasonable and definable structure to enable efficient development and maintenance.

As shown in Figure 1, we can think of software in terms of layers and components. The components at the lowest layers talk directly to the hardware.

At higher layers, components implement increasingly abstract functionality. The top-most layer is aptly called the application layer, even in IoT devices, as it implements the device’s highest-level logic and glues together the rest of the components and layers.

I call the overall structure of the software its architecture, and I call the depicted style of interacting with the hardware and allowing for concurrent tasks the operating environment.

The two are interdependent, but I use the term operating environment because it is more general than the term operating system and it allows for situations with little or no software services. I think of operating systems as a subset of operating environments.

Type of Operating Environments

There are several styles of operating environments in wide use today. They range from what is called “bare metal” to full-featured operating systems like Linux. I give a brief summary of the following representative styles across the spectrum:

  • Bare metal
  • Embedded OS
  • Full-featured OS
  • Virtual or abstracted environment

Bare Metal

This style of operating environment is on the lighter end of the spectrum. Bare metal implies that that firmware is running directly on the processor’s hardware, without the benefit of an existing OS of some sort.

There are multiple architectures common to bare metal designs, two of which I’ll mention here: super loop and event-driven.

Super Loop

The super loop architecture is a forever-loop that executes the various tasks in the application in sequence. The order of tasks can be statically defined or dynamically chosen at runtime, though this style of concurrency is completely cooperative (not preemptive).

There is only a single thread of execution running. All work, with the exception of specific interrupt processing, is done in turn in this sequence of tasks.

In this environment, tasks can be coded as straightforward (and possibly lengthy) sequential algorithms, but only if the latency can be tolerated.

If not, tasks can be implemented as software state machines, and thus decomposed into smaller segments of code with additional state data, which are executed in multiple shorter periods.

Often the algorithms implemented by state machines are more challenging to implement, read, and maintain – but the resulting latency can be significantly reduced.

Event Driven

A purely event-driven architecture for bare metal environments is pretty much the opposite of the super loop. All work is performed in direct response to hardware interrupts, with little or no code running in the main loop.

The event-driven style does not lend itself to the straightforward style of coding tasks and algorithms. However, it’s excellent for state-machine based design.

Though often more challenging to design and implement, event-driven style can lead to superior latency and power performance. It works well for small devices with limited features, with demanding power and latency requirements.

The biggest advantage is the processor can spend most of its time in a low power mode with execution suspended, waiting for events to occur.

A combination of the two styles is also possible. Sometimes, that can be the right balance of performance and development complexity.

For example, the main loop can be used primarily for event dispatch, calling tasks in response to queued events generated by both hardware interrupts and software tasks.

Embedded OS

There are many embedded OSs available today, both commercial and open-source. They range in functionality from simple task schedulers to feature-rich OSs with memory management, messaging, device driver frameworks, protocol stacks, etc.

Some embedded OSs also support real-time capabilities, allowing for guaranteed response times to events (though this often still takes significant work on the part of the firmware developers to achieve).

Note that the term RTOS, the acronym for Real-Time Operating System, is often used to represent any embedded OS, not just those suited for real-time use.

Many embedded OSs are designed to run on very small processors, including 8-bit architectures with extremely limited memory.

With care, these can allow preemptive multitasking of well-coded tasks and algorithms, with low latency and power efficiency, outperforming bare metal techniques but requiring a little more memory.

Some additional skills are required to develop a thread-safe preemptive multitasking application, but the results can justify the burdens.

Other embedded OSs are designed to run on larger processors (e.g. 32-bit architectures) with substantial memory. Chances are, you would not want to program one of these processors using one of the bare metal styles, as code size and complexity tend to correlate with functionality required and thus with the scale of processor resources needed.

No matter the size or complexity of the processor and the embedded OS, there are benefits to having a task scheduler and other basic services. You can typically implement most, if not all, of your required functionality as straightforward tasks, promoting ease of development and maintenance.

Performance requirements can still be challenging to meet, so some specialized routines and structures, including interrupt handlers and priority mechanisms, may be needed, though these will typically constitute a very small portion of your overall code base.

Full-featured OS

On the larger side of the IoT scale, a full-blown OS like Linux can be used.

If your requirements are such that you need the processing power and memory footprint of a modern application processor, you can run a full-featured OS and take advantage of the rich services provided.

On desktop or server class hardware, you can run pretty much anything, including Windows and Unix variants, like FreeBSD.

Though many embedded OSs and RTOSes have network protocol stacks available, once you can run an OS like Linux, you inherit a massive amount of open source software supporting all kinds of hardware devices, communication protocols, and services.

There is often additional work required to integrate a larger OS into your device, but the benefits can be worth it. The more your hardware resembles a typical computer or a well-supported reference platform, the easier the integration will be.

Virtual or Abstracted Environment

It is also worth mentioning that you may find yourself operating in an even higher-level environment, one that simplifies (and restricts) your development efforts.

This happens when you choose a key hardware component whose processor is preloaded with firmware that offers you an abstracted environment, allowing you to develop your application using high-level functions and services.

This might come in the form of a virtual machine (e.g. the Java VM) or a language interpreter (e.g. Python). I have seen several offerings in the market, such as cellular radio modules, that use this approach.

It can also come in the form of a domain-specific virtual environment. I have created a few such virtual environments for deeply embedded devices, each with its own compact virtual machine, custom programming language and compiler, and supporting development tools.

Deployed in specific domains such as robotics and audio/video products, each instance has offered the end-product developers a much simpler and quicker path to market.

Some Questions to Ask Yourself

Choosing the right operating environment for your IoT project requires asking the right questions: 

  • How much code do you need to develop?
  • How complex are the algorithms involved?
  • How easy will it be to develop the code in the first place?
  • How hard will it be to maintain that code later on?

Choosing the One Right for You

How do you choose the best operating environment for your IoT device firmware? The simple answer is, it depends.

Some of the main factors to consider are your required functionality and performance, the processor architecture and memory resources of your target hardware, availability of existing software components, and ease of development and maintenance.

Required Functionality and Performance

The more functions your device must perform, the richer you’ll likely want your operating environment to be.

As functionality increases, so does the size and complexity of the code base, and generally along with it the need for additional internal communications and coordination.

Performance-related requirements such as battery life, power usage, and hard or soft real-time response must also be considered.

Processor Architecture and Memory Resources

A processor’s architecture, including its word size, memory organization, and programming model, can play an important role in your choice.

A simple 8-bit processor may not have the facilities to efficiently support certain operating environments or software architectures.

A modern 32- or 64-bit processor opens up a wide range of architectural freedom but may pull you towards a more fully featured OS due to complexity.

Not to be outdone by the processor itself, memory resources can be key, especially on the smaller side of the spectrum. The more memory (RAM and Flash) your processor has access to, the more options you have in choosing an operating environment.

One more point to make here: the more complex the processor and hardware design, the more likely you are to choose an operating environment with rich support for your processor and chosen hardware devices and interfaces.

Available Software Components

Why start from scratch when you can benefit from existing code? Most programmable electronic components available today exist within ecosystems of support.

From sample driver code to abstraction layers to direct OS support to full Board Support Packages (BSPs), you may find your choices guided by what is already well supported.

This is especially true for the more complex devices. Very small devices may still lead you to a bare metal implementation, though look for that sample code or reference driver set.

Ease of Development and Maintenance

These questions should be asked and answered in the decision-making process:

  • How much code do you need to develop?
  • How complex are the algorithms involved?
  • How easy will it be to develop the code in the first place?
  • How hard will it be to maintain that code later on?

The most appropriate software architecture for a given application will depend on all the same factors, plus the chosen operating environment. This may seem a bit cyclic, and it is.

Your choice of an operating environment will impact your options for software architectures, and your choice of a software architecture will often limit your options for operating environments.

Considering the tradeoffs and making a choice based on a compromise is just part of the normal process.

Examples

Here are some fairly specific examples that will help further explain what I’ve covered:

Example 1

Let’s say your hardware device has at its core a simple 8-bit microcontroller (e.g. PIC, AVR, 8051) with very limited memory (e.g. only tens or hundreds of bytes of RAM) and your required functionality is limited to periodically reading and reporting some sensor readings over a serial communications channel.

If there are no demanding latency requirements, then a simple bare metal super loop with very straightforward tasks will do.

Add a few interrupt driven device drivers for peripheral functions like timers, UARTs, external stimuli, and such, and you are off to the races. Your code can be developed and maintained with relative ease.

Example 2

Given the same hardware and functionality as described in Example 1, but adding some demanding timing requirements, a bare metal event-driven architecture is likely warranted. Implementing the required functionality in state machines and ensuring proper event dispatch and state transitions will be more challenging, as will maintaining the code base over time. The benefit, though, is an efficient implementation that meets performance requirements on a very cost-effective processor.

Example 3

Now let’s look at a processor with an ARM Cortex-M core, tens of kilobytes of RAM, and quite a bit more required functionality than in our previous examples.

You are likely going to want to structure your implementation via a myriad of tasks, not all of which are easy to code as state machines. This really calls for use of an embedded OS of some sort.

The more functionality your application has to perform, the more services you look for in the OS. The more stringent your performance requirements, the more likely you will look towards preemptive (versus cooperative) scheduling and ultimately an RTOS (for its real-time capabilities).

Example 4

As a final example, suppose your requirements include high bandwidth data processing and communications, compatibility with a variety of protocols and services, and a rich user interface.

At some point as the scope of requirements increases, a full-featured OS, such as Linux, becomes a good choice. Your hardware has to have the processing capacity, the memory resources, and good driver support from the vendor, but once you have all this, development looks pretty much like it does on desktop and server hardware.

Conclusion

You made it to the end, thank you! I hope I’ve provided you with some good insight or cleared up a question or two you may have had. IoT devices represent a huge and expanding market.

Choosing an appropriate operating environment for your device’s firmware can greatly impact your time to market and getting your product to market quickly will help ensure its success.

At LocoLabs, we have been helping folks do exactly that for more than 20 years. We’d love to help you too. Contact us and let’s set up a time to chat about your project.

LET'S DO THIS

LAUNCH YOUR PROJECT