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 an Abstraction Layer

Software architecture is a topic with various opinions. In my view, software architecture is the basic structure of a software system, encompassing its components, the relationships between components, the rules for component design and evolution, and the infrastructure that embodies these rules.

Software architecture has never been an easy task; it spans the entire lifecycle of a product and requires all team members to adhere to and be disciplined in order to implement architectural concepts in software. New engineers, due to their limited project experience, often struggle to see the big picture and understand software architecture from a holistic perspective. But is software architecture really just the domain of senior engineers? Not necessarily.

In ancient times, writing emphasized the importance of intent. Today, engineers working on projects and products should also start with intent. This intent refers to having a high-level perspective. If engineers approach software issues from the height of software architecture, their understanding of software will likely become deeper. Therefore, I have summarized six steps of software architecture for embedded engineers’ reference.

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

This time, we will discuss a unified infrastructure, which is relevant for collaborative projects involving multiple people or when multiple projects share 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 device libraries, packaging libraries, schematic libraries, and hardware reference designs, etc.; whereas today, we will focus primarily on software infrastructure. Software infrastructure includes the following:

  • • Basic data structures.

  • • Low-level libraries, such as the C standard library, encryption 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 toolchains.

  • • Unified coding styles and programming standards.

In some small companies with a loose development model, there are no regulations on the software platforms, hardware platforms, and tools that engineers rely on, and engineers make their own decisions. Many engineers also enjoy this freewheeling development model, believing that only in such an environment can they unleash their creativity. This perception is flawed, and we will discuss this in detail later.

As small companies’ R&D capabilities improve, it is almost inevitable that constraints and regulations on software infrastructure will be established. The essence of software, which distinguishes it from other technologies, lies in its reusability.

The higher the degree of reuse in software, the higher the quality, and the greater the improvement in development efficiency and quality. From the perspective of cost reduction and efficiency increase or scientific management, there is a strong motivation to unify software infrastructure. Once the software infrastructure is unified, the following advantages will emerge:

  • • Improved software quality with a high level of consistency in style.
  • • Software reusability will reach a new level.
  • • Reusable functions should be abstracted to the infrastructure layer as much as possible to reduce software redundancy and improve development efficiency.
  • • Provide constraints and discipline for higher-level modules.
  • • Facilitate the accumulation and transmission of technology within the team.
  • • Facilitate technical training within the team.
  • • It is a prerequisite for unit testing, test-driven development, and cross-platform development.

Therefore, whether to unify is not a controversial issue; how to unify is our focus 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 a code snippet 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 macro definitions. Using macros to shield compiler differences 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 among multiple modules, and therefore need to be elevated to the infrastructure level 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 definitions below.

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 insufficient resources to run an operating system. Generally, these chips also cannot establish a rigorous embedded software architecture, which we will discuss separately in the upcoming “Software Development Platforms for Low-Resource Chips.” Here, we only discuss chips.

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

Among existing operating systems, FreeRTOS and domestic RT-Thread support a wide range of different hardware architecture chips and can be considered as 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 problem:

  • • When writing high-level modules, use macro definitions and conditional compilation to select the corresponding RTOS API. This is generally used for cases where the operating systems used are limited, such as 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 between 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; FreeRTOS and RT-Thread have supported POSIX standards to varying degrees; in the embedded field, CMSIS_OS is also aimed at establishing a unified interface for operating systems; however, the extent of support for POSIX and CMSIS_OS varies among different RTOS, so if we need to establish a rigorous embedded software architecture in our product, we still need to create our own OSAL to shield the differences caused by different operating systems.

3. Middleware

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

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

  • • FatFS

  • • LwIP

  • • FlashDB

  • • uC/Modbus

  • • CAN Festival

  • • letter-shell

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

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

4. Frameworks and Mechanisms

When developing embedded software across different products, many products require support from various frameworks in addition to RTOS. Common frameworks include: peripheral and driver frameworks, device frameworks, message frameworks, state machine frameworks, and behavior tree frameworks.

The use of these frameworks is related to product characteristics and is determined by the product and its requirements. For example, in home service robots, state machine frameworks and behavior tree frameworks are needed to deal with complex application layer logic, while products with simpler application layer logic do not require the use of state machines and behavior trees.

Relationship Between Software Infrastructure and Hardware

Embedded software has an important characteristic that distinguishes it from other software domains: it directly relies on hardware. Many aspects of software infrastructure also need to be embodied and supported by hardware. For example, when specifying a particular source code, such as FatFS as its file system solution, the accompanying hardware drivers and recommended hardware designs are often solidified to be reused in the next project, saving time.

For some important and complex software infrastructures, such as file systems and networks, due to the time-consuming 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 hardware projects, such as IO and ADC, can be allocated later.

Conclusion

Embedded software infrastructure is very important, and its content varies depending on the project and product. Generally, at the project initiation stage, some software infrastructure components, such as RTOS, protocol stacks, and file systems, will be initially selected.

It should be noted that software infrastructure is not static; it evolves with product development, with new components and elements continually being added and older components potentially being removed, 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 source of this article is the internet, and the copyright belongs to the original author. If there is any infringement, please contact us for deletion.

Follow our public account, star it, reply1024 to get study materials, and improve a little every day.

Statement:

The original articles and images of this account are owned by the original author. If there is any infringement, please contact us for deletion.

Embedded Software Architecture Design: Establishing InfrastructureFollow, like, view, and share to support quality content!Embedded Software Architecture Design: Establishing Infrastructure

Leave a Comment

×