Embedded Software Architecture: Overview

Embedded Software Architecture: Overview

Software architecture serves as a plan or framework for creating embedded systems. It defines how various software components should be structured and organized across different levels of abstraction. The main objectives of a software architecture include enabling hardware independence, facilitating code reusability, and ensuring consistent component behaviour across various purpose-specific embedded computer platforms. The architecture also ensures that the software components can be easily transferred between different embedded computer platforms without requiring significant modifications or rewrites.

Firmware is a type of low-level software stored in the non-volatile memory of an embedded system, such as ROM, EEPROM, or flash memory. The firmware provides basic control over the hardware components of the system. It is responsible for initializing the hardware and performing tasks such as setting system configuration, enabling peripheral devices, and providing basic I/O operations.

Layered Software Architecture

A layered software architecture can create a clear separation between different software parts. Layering is a technique in which the software is structured as a series of layers, each performing a specific function and communicating with other layers through well-defined interfaces. This approach makes it easier to port firmware from one platform to another and reuse software components across different projects.

For instance, the low-level driver code that makes a microcontroller work can be separated from the application code, which contains algorithms that may be reused in other products. This makes the software more flexible and easier to reuse in future projects. On the other hand, if the low-level code is mixed with the application code, it will be almost impossible to reuse the code. Also, the layered architecture gives room for software abstraction. Abstraction is a design methodology used to hide hardware architecture details from the application software domain by the isolation and encapsulation of relevant parameters that describe the behaviour of a specific hardware entity to facilitate software component reusability and portability.

In resource-constrained processors such as 8-bit MCUs, the firmware is often custom-written for the specific embedded system. It is tightly coupled with the hardware, leaving no room for portability or reusability. Layering is often not an option in such platforms due to their resource-constrained nature. However, with the increasing availability and declining cost of relatively high-performance processors such as 32-bit microcontrollers, it has become possible to employ a layered software architecture to enhance the portability and reusability of the firmware. By using a layered architecture, developers can achieve greater flexibility and efficiency and build more complex and sophisticated embedded systems that are easier to maintain and update over time.

Models of Layered Software Architecture

Two-layer model

The simplest layered architecture includes two layers, as shown above: a driver layer and an application layer, which operate on the hardware. The driver layer is responsible for all the code required to get the microcontroller and other associated board hardware, like sensors and buttons, up and running. This layer interacts directly with the hardware and handles the low-level details of managing the hardware. On the other hand, the application layer contains no driver code but has access to the low-level hardware through a driver-layer interface. The interface provides a level of abstraction that hides the hardware details from the application developer while still allowing them to perform valuable functions. This separation makes it easier to reuse the application code across multiple projects or platforms without worrying about the underlying hardware.

Three-layer model

A developer could choose to implement a three-layer model, as shown above. This model is the same as the one discussed previously, with the addition of an extra layer: the Application Programming Interface(API) layer. An API is a set of functions, protocols, and tools that define how different software components can interact with each other.

The API layer bridges the driver layer and the application layer. Its primary purpose is to create an interface between the two layers, allowing them to interact without knowing the details of each other's implementation. This separation of concerns enables the driver layer to be modified or replaced without affecting the application layer and vice versa. The API layer also offers an abstraction layer that defines a set of functions, protocols, and tools that are exposed to the application developer for use in their software. This makes it easier for developers to create and manage their applications, as they can focus on the higher-level functions and features of the application.

N-layer model

The layers of software architecture may be extended by adding software components such as middleware, network stacks, Board Support Packages (BSPs), Hardware Abstraction Layers (HALs), and so on.

Middleware typically refers to software components that sit between the application layer and low-level implementations, providing standard services that multiple applications can use. Middleware may include various components, such as Database Management Systems(DBMS), Real-Time Operating systems (RTOS), etc. A Board Support Package (BSP) is a collection of software components and drivers that provide an interface between an embedded system's hardware and software components. For example, the BSP for the STM32F4 microcontroller provides drivers for the various peripherals and hardware components on the microcontroller, such as the timers, ADC, GPIOs, etc. These drivers abstract the underlying hardware and provide a standard interface for the higher-level software to interact with the hardware. While the BSP provides low-level access to hardware, the Hardware Abstraction Layer (HAL), on the other hand, provides a standardized interface for communicating with a software component or service of an embedded system. The HAL provides a high-level interface for accessing the hardware and abstracts away the low-level details of the hardware, making it easier for the software to interact with the hardware.

Custom Layered Model

There is hardly a standard for layering software in embedded development. The kind and number of layers in a software architecture are ultimately up to the developer and depend on the application's specific requirements. While the three-layer model is a common starting point, some developers may find it useful to add more layers to create a more refined architecture.

For example, a developer may choose to create additional layers for specific hardware components or to separate certain functionality into its layer. It's also possible to create pathways that allow high-level layers to bypass certain layers and directly access lower-level software components, although this can add complexity to the architecture. It's important to note that layering software can add complexity and increase development time, so developers should carefully evaluate their options before choosing a particular architecture. As with any software design decision, it's important to carefully consider the pros and cons of different approaches and choose the one that best meets the application's needs.

In the following series of articles, we use a slightly modified three-layer model discussed previously (shown above) to illustrate the concept of embedded software layering. As shown above, the driver layer has been further split into two functional units. The device vendor always provides the HAL component. HAL abstracts away the complexity of hardware and provides a consistent interface for interacting with hardware. It mostly contains macros holding peripheral registers addresses, function and bitfield definitions, and various data structures for performing low-level functions. In subsequent developments, we begin with the low-level peripheral drivers (based on HAL) and develop relevant top-level APIs for accessing predefined peripheral functions in our application code.

Further Reading