Embedded Software Architecture Design – Establishing Infrastructure

Scan to FollowLearn Embedded Together, learn together, grow together

Embedded Software Architecture Design - Establishing Infrastructure

Hello everyone, today I am sharing an article related to embedded software architecture design.

Embedded Software Architecture Design – Establishing Abstraction Layers

Software architecture is a topic of much debate, with various viewpoints. In my opinion, software architecture is the fundamental structure of a software system, including its components, the relationships between components, the rules of component design and evolution, and the infrastructure that embodies these rules.

Software architecture has never been an easy task; it runs through the entire product lifecycle and requires all team members to comply and exercise self-discipline to embody architectural ideas in software. New engineers, due to their limited project experience, often struggle to see the big picture and understand software architecture from a global perspective. But is software architecture really just the domain of senior engineers? Not necessarily.

In ancient times, writers emphasized the importance of intention. Today, engineers should also prioritize intention in their projects and products. This intention refers to having a high perspective. If engineers can approach software issues from the height of software architecture, their understanding of software will likely be more profound. Therefore, I have summarized six steps of software architecture for embedded engineers to refer to.

Last time, I discussed the first step of embedded software architecture, which is the abstraction layer. The purpose of establishing an abstraction layer (HAL or DAL) is to isolate hardware, making the code independent of hardware. Even if the entire project code is completed by one engineer, the abstraction layer is still necessary.

But this time we want to discuss a unified infrastructure, which is relevant for multiple people collaborating on one project or multiple projects sharing the same system architecture.

If your current project does not involve collaboration with others and there will be no subsequent related projects, you can skip the software infrastructure step.

Infrastructure is divided into hardware infrastructure and software infrastructure. Hardware infrastructure includes commonly used component libraries, packaging libraries, schematic libraries, and hardware reference designs, etc.; whereas today, we will focus on software infrastructure. Software infrastructure includes the following content:

  • • Basic data structures.

  • • Low-level libraries. Such as C standard libraries, cryptographic libraries, checksum libraries, utility libraries, etc.

  • • Operating systems/scheduling mechanisms. Including operating systems and scheduling-related services.

  • • Middleware. Such as file systems, protocol stacks, databases, etc.

  • • Frameworks and mechanisms. Such as message communication mechanisms, event-driven mechanisms, state machine frameworks, behavior tree frameworks.

  • • Tool support.

  • • Unified programming toolchain.

  • • Unified coding style and programming specifications.

In some small companies with a loose development model, there are no regulations on the software platform, hardware platform, and tools that engineers depend on; it is left to engineers’ discretion. Many engineers prefer this free-spirited development model, believing that only in such an environment can they unleash their creativity. This perception is flawed, and we will find opportunities to discuss this in detail later.

As the R&D capabilities of small companies improve, it is almost inevitable to impose constraints and regulations on software infrastructure. This is because the essence that differentiates software from other technologies lies in its reusability.

The higher the degree of reusability of software, the higher its quality, and the greater the improvement in development efficiency and quality. From both the perspective of cost reduction and efficiency improvement for the company and scientific management, there is a strong motivation to unify software infrastructure. Once the software infrastructure is unified, the following advantages will arise:

  • • Improved software quality, with a high degree of consistency in style.
  • • Software reusability will be elevated to a new level.
  • • Reusable functionalities should be abstracted to the infrastructure layer as much as possible, reducing software redundancy and improving development efficiency.
  • • Providing constraints and discipline for higher-level modules.
  • • Beneficial for the technical accumulation and inheritance of the team.
  • • Beneficial for technical training within the team.
  • • It is a prerequisite for the team to conduct unit testing, test-driven development, and cross-platform development.

Therefore, whether to unify is not a controversial issue; how to unify is the focus of our discussion today.

1. Basic Types and Macros

The premise of a unified software infrastructure is to declare unified basic data types and macros to overcome the differences between different hardware platforms and compilers.

For example, below is some code I extracted from the open-source project EventOS, which may not be complete but represents my needs in the project.

#include <stdbool.h>

typedef unsigned int                    eos_u32_t;
typedef signed int                      eos_s32_t;
typedef unsigned short                  eos_u16_t;
typedef signed short                    eos_s16_t;
typedef unsigned char                   eos_u8_t;
typedef signed char                     eos_s8_t;
typedef bool                            eos_bool_t;

#define EOS_NULL                        ((void *)0)

#define EOS_U32_MAX                     (0xffffffffU)
#define EOS_U32_MIN                     (0U)
#define EOS_U16_MAX                     (0xffffU)
#define EOS_U16_MIN                     (0U)
#define EOS_U8_MAX                      (0xffU)
#define EOS_U8_MIN                      (0U)

Compiler-related macros. Using macros to shield the differences in compilers will

/* Compiler Related Definitions */
#if defined(__ARMCC_VERSION)           /* ARM Compiler */

    #define eos_section(x)              __attribute__((section(x)))
    #define eos_used                    __attribute__((used))
    #define eos_align(n)                __attribute__((aligned(n)))
    #define eos_weak                    __attribute__((weak))
    #define eos_inline                  static __inline

#elif defined (__GNUC__)                /* GNU GCC Compiler */

    #define eos_section(x)              __attribute__((section(x)))
    #define eos_used                    __attribute__((used))
    #define eos_align(n)                __attribute__((aligned(n)))
    #define eos_weak                    __attribute__((weak))
    #define eos_inline                  static __inline

#elif defined (__IAR_SYSTEMS_ICC__)     /* for IAR Compiler */

    #define eos_section(x)              @ x
    #define eos_used                    __root
    #define eos_align(n)                PRAGMA(data_alignment=n)
    #define eos_weak                    __weak
    #define eos_inline                  static inline

#else
    #error "The current compiler is not supported. "
#endif

Some commonly used data structures. These data structures are independent of hardware and compilers and are frequently used in code, shared across multiple modules. It is necessary to elevate them to the level of infrastructure support to avoid data conversion issues caused by different definitions of the same data type across modules.

These data structures are closely related to products, and different product types have their own variations. For example, the following definitions.

typedef struct eos_date
{
    eos_u32_t year               : 16;
    eos_u32_t month              : 8;
    eos_u32_t day                : 8;
} eos_date_t;

typedef struct eos_time
{
    eos_u32_t hour               : 8;
    eos_u32_t minute             : 8;
    eos_u32_t second             : 6;
    eos_u32_t ms                 : 10;
} eos_time_t;

typedef struct eos_imu_data
{
    float acc[3];
    float gyr[3];
    float mag[3];
} eos_imu_data_t;

2. Operating Systems

Some chips have limited resources and cannot run operating systems. Generally speaking, these chips also cannot establish a rigorous embedded software architecture. We will discuss this separately in “Software Development Platforms for Low-Resource Chips” later. Here, we only discuss chips.

Different chips can run different operating systems. However, if we want to establish a software infrastructure, we should try to choose the same operating system.

Among the existing operating systems, FreeRTOS and domestic RT-Thread widely support various hardware architectures and can be considered the first choice for RTOS.

When the product line is exceptionally rich, especially when using niche chips or operating systems provided by chip manufacturers, it becomes impossible to establish a unified software infrastructure. In this case, there are two ways to solve this issue:

  • • When writing higher-level modules, use macro definitions and conditional compilation to select the corresponding RTOS API. This is generally used when the number of operating systems in use is small, for example, only two or three.
static void *task_handler = NULL;

static void task_func_module_one(void *parameter);

void module_one_init(void)
{
    /* Newly creating a task to run the module. */
#if (EOS_RTOS_NAME == EOS_RTOS_NAME_FREERTOS)
    xTaskCreate(task_func_module_one,
                "TaskModule", 2048, NULL, 2,
                (TaskHandle_t *)&amp;task_handler);
#elif (EOS_RTOS_NAME == EOS_RTOS_NAME_RTTHREAD)
    task_handler = rt_thread_create("led1", task_func_module_one, NULL,
                                    2048, 2, 20);
#else
    eos_assert(false);
#endif

    eos_assert(task_handler != NULL);
}

/* The task function of the module one. */
static void task_func_module_one(void *parameter)
{
    (void)parameter;

    /* Initialization. */

    while (1)
    {
        /* Add the task function. */
    }
}
  • • Establish an Operating System Abstraction Layer (OSAL) to shield the differences in operating systems, allowing higher-level modules to depend on OSAL. This situation is suitable for resource-rich scenarios.
  • The famous POSIX standard was established to create OSAL, and both FreeRTOS and RT-Thread support POSIX standards to varying degrees; in the embedded field, CMSIS_OS is also intended to establish a unified interface for operating systems; however, the level of support for POSIX and CMSIS_OS varies across RTOS. Therefore, if we need to establish a rigorous embedded software architecture in our products, we still need to create our own OSAL to shield the differences brought by different operating systems.

3. Middleware

There are many types of middleware, including file systems, various protocol stacks, databases, logging modules, shell modules, etc. However, in most cases, these also fall under the category of software infrastructure.

Once we choose a certain middleware, generally speaking, there is no need to replace it. Due to its stability, middleware can also be included in the software infrastructure category. Below are some open-source middleware that I frequently use:

  • • FatFS

  • • LwIP

  • • FlashDB

  • • uC/Modbus

  • • CAN Festival

  • • letter-shell

Open-source middleware only accounts for a small part. In actual products, most middleware is proprietary code for products or projects. What I mainly use include: logging module, data acquisition module, communication transport layer protocol, communication application layer protocol, file transfer protocol, OTA functionality, and time synchronization.

Middleware occupies a significant portion of software infrastructure. In product development, the increasing reusability of software is largely due to the accumulation of middleware.

4. Frameworks and Mechanisms

In developing embedded software for different products, many products require support from certain frameworks in addition to RTOS. Common frameworks include: peripheral and driver frameworks, device frameworks, message frameworks, state machine frameworks, behavior tree frameworks, and event-driven frameworks.

The use of these frameworks is related to the characteristics of the product and is determined by the product and requirements. For example, in a home service robot, state machine frameworks and behavior tree frameworks are needed to address complex application layer logic. However, for products with relatively simple application layer logic, there is no need to use state machines and behavior trees.

The Relationship Between Software Infrastructure and Hardware

Embedded software has an important characteristic that distinguishes it from other software domains: it directly depends on hardware. Many aspects of software infrastructure also need hardware to manifest and support. For example, when specifying a certain source code, such as FatFS, as its file system solution, the accompanying hardware driver and recommended hardware design are often solidified to facilitate reuse in the next project and save time.

For some important and complex software infrastructures, such as file systems and networks, due to the time-consuming nature of debugging and testing, it is generally recommended to solidify the hardware design. Hardware engineers should prioritize allocating hardware resources for these important and complex software infrastructures, while other engineering tasks, such as IO and ADC, can be allocated later.

Conclusion

Embedded software infrastructure is crucial, and its content varies depending on the project and product. Generally, at the start of a project, some software infrastructure content, such as RTOS, protocol stacks, file systems, etc., will be preliminarily selected.

It should be noted that software infrastructure is not immutable; rather, it evolves with product development, constantly incorporating new components and elements while possibly shedding old components, much like biological metabolism.

The metabolism of software infrastructure should be gentle and relatively stable, with additions and deletions executed with caution.

Original Article: https://zhuanlan.zhihu.com/p/601075563

The article is sourced from the internet, and the copyright belongs to the original author. If there is any infringement, please contact for removal.

Embedded Software Architecture Design - Establishing Infrastructure

Scan to join the high-quality embedded exchange group

Embedded Software Architecture Design - Establishing Infrastructure

Follow me【Learn Embedded Together】, learn together, grow together.

If you think the article is good, click “Share“, “Like“, or “Looking“!

Leave a Comment

Your email address will not be published. Required fields are marked *