5 steps to design an embedded software architecture, step 1

Some believe that clean software architecture can simply emerge from coding efforts, but that’s like believing that pouring a packet of spaghetti into boiling water will result in cooked lasagna.

Software architecture is the fundamental organization of a system embodied in its components, their relationships to each other and to the environment, and the principles guide its design and evolution [1]. Software architecture is not meant to be created once and set in stone. Instead, software architecture must evolve and change throughout the lifetime of a product. Over the years, I’ve heard engineers and managers discuss how software architecture should magically emerge from coding efforts. Believing in clean, emergent architecture is like believing that pouring a packet of spaghetti into boiling water will result in cooked lasagna.

A thoughtful and then scalable software architecture during implementation has many advantages, such as:

  • Act as a roadmap for what is being built
  • Provide an image of the software that can be used to train engineers and explain the software to management and stakeholders
  • Minimize unnecessary retouching
  • Reduced development costs

A common problem that I often see is that teams struggle to understand how to define their software architecture. Teams can follow five steps to develop and evolve their software architecture:

  • Separate software architecture
  • Identify and trace data assets
  • Break down the system
  • Design of interfaces and components
  • Simulate, iterate and scale

So, let’s explore some steps you can take to design your embedded software architecture.

Step #1 – Separate the Software Architecture

Many embedded software teams view their software architecture as a single cohesive architecture that includes both application code and hardware interactions. Seeing the architecture this way is not surprising because embedded software engineers often look at their system from the hardware. Embedded software is unique because it has to interact with hardware, which is different from all other areas of software engineering. While this is true, a modern software architect will and should separate hardware-dependent and hardware-independent software, as shown in Figure 1.

Figure 1 – An embedded software architecture is separated into hardware-dependent and independent architectures that interact through an abstraction layer. (Image source: Embedded Software Design [2]).

I call this separation a “tale of two architectures”. Traditionally, developers will design and implement their architecture so that the hardware-independent and hardware-dependent layers are tightly coupled. But unfortunately, there are quite a few issues with a tightly coupled architecture.

Issues with Tightly Coupled Architectures

First, they are not very portable. For example, what if a microcontroller suddenly becomes unavailable? (Shortage of chips, anyone?). If the code is tightly coupled, attempting to move application code to run on a new microcontroller becomes a Herculean effort. Application code is tightly coupled to low-level hardware calls on the microcontroller! I know a lot of companies that have suffered from this recently. If they didn’t update their architecture, they had to review all their code and change every line that interacted with the hardware. Companies that have updated their architecture have broken their architecture coupling through abstractions and dependency injection.

Second, unit testing the application in a development environment rather than on the target hardware is nearly impossible. If the application code makes direct calls to hardware, a lot of work will go into the test harness to run that test successfully, or the tests will have to be done in hardware. Hardware testing is slow and often a manual process rather than an automated one. The result I have seen is that the software is not well tested and the overall quality of the system suffers. In addition, software delivery may take longer.

Finally, a tightly coupled architecture will have scalability issues. Tightly coupled systems often share data. As the software system tries to grow and add new features, it becomes more difficult with each new feature to add new code. Interactions between components, the ability to access shared data, and the chances of troublesome faults increase dramatically. As a result, feature development may come to a halt even though the developers are working hard to get the job done.

How splitting the architecture solves the problem

Separating the software architecture into hardware-dependent and independent architectures solves all problems with a tightly coupled architecture. For example, creating an abstraction layer between hardware-dependent and independent architectures allows application code to be moved from one microcontroller to another. The abstraction layer breaks hardware dependencies; in other words, the application does not know or care about the hardware. Instead, the application depends on the interface. New hardware-dependent components simply need to meet interface requirements. This means that if we change hardware, only the hardware modules change! Not the whole code base.

Adding an abstraction layer between the two architectures also solves many problems with unit testing. With the abstraction layer in place, it is easier to create test doubles and simulations that return expected and unexpected data to the application code. You can write all the application code without even having the hardware! I know this sounds silly to many embedded software developers. However, over the past week I’ve added several new features to a product I’m working on and I’ve only activated the hardware once. All my development was done using unit tests on a host computer!

When we keep our architectures separate and focus on minimizing coupling, it becomes much easier to scale software. However, just because we’ve broken down the architecture doesn’t mean we can’t create coupling and cohesion issues in each architecture. We still have to be careful to follow SOLID principles in both architectures. The good news is that it allows us to focus on each architecture independently, which means real-time issues and hardware constraints don’t end up in the core logic of the application.

The last benefit I want to mention is that by separating hardware dependent and independent architectures, we can focus on developing and delivering the application even before the hardware is available. The benefit here is that customers and management can get early access to the app and provide feedback. The ability to then browse the application and ensure that it meets real needs becomes much more manageable. Today, too many teams focus on material preparation first, and the main application is an afterthought. This is no way to design and build a system.

Software Architecture Design Step #1 Conclusions

Software architecture can help teams control their software. Emerging software architectures often result in big mudball and spaghetti code. This does not mean that we are forced to use a waterfall methodology. Successful software architecture is often created through iteration and evolution. The first step in designing a software architecture is to recognize that embedded systems do not have a single architecture. Instead, there are two architectures: a hardware-dependent architecture and an independent architecture. Deliberately separating these architectures allows developers to take advantage of modern software techniques and methodologies to improve time to market, quality, and development costs.

Remarks

[1] https://en.wikipedia.org/wiki/IEEE_1471. IEEE 1471 has been superseded by IEEE 42010. (I just like the IEEE 1471 definition).

[2] Beningo, Jacob. Embedded software design. Press, 2022


Jacob Beningo is an embedded software consultant specializing in real-time systems based on microcontrollers. He actively promotes software best practices through numerous articles, blogs, and webinars on topics such as software architecture design, integrated DevOps, and implementation techniques. Jacob has 20 years of experience in the field and holds three degrees, including a Masters in Engineering from the University of Michigan.

Related content:

For more embedded, subscribe to Embedded’s weekly newsletter.