[The Beauty of Programming] A detailed discussion on the modular architecture design of microcontroller firmware

[Guide] Why write this article? Recently, I have encountered some students who are beginners in microcontrollers, who have just started to develop microcontrollers, and have not yet involved the use of RTOS, and it may be difficult to directly use RTOS when they just started. Some use relatively old microcontrollers with limited resources and are not suitable for running RTOS. . Or using RTOS, I am confused on the overall idea and don't know where to start, so this article will talk about some of my thoughts on the overall frame design of the MCU program.

Why discuss architecture

One of the goals of microcontroller system developers is to create firmware in a programming environment to achieve low-cost systems, software reliability, and rapid development iteration time. The best practice to implement this programming environment is to use a unified firmware architecture architecture that acts as a framework during product development and supports "firmware modularization", or subsystems.

If a unified design architecture is not adopted, the coupling relationship between business requirements is complicated, and the methodology of design first-later development is not adopted. If you think about where to write, then the later maintenance of the program will become extremely difficult, and the risk of potential bugs/defects will be introduced It will also greatly increase, and there is no possibility of multi-person collaborative development.

The design architecture structure that can be combined with the correct combination of firmware modularity, testability and compatibility can be applied to any firmware development project to maximize code reusability, speed up firmware debugging and improve firmware portability.

Modular architecture design?

Modular programming decomposes program functions into firmware modules/subsystems. Each module performs a function and contains all the source codes and variables required to complete the function.

Modularization/subsystemization helps to coordinate the parallel work of many people in the team, manage the interdependence between various parts of the project, and enable designers and system integrators to assemble complex systems in a reliable manner. Specifically, it can help designers implement and manage complexity. As applications grow in size and functionality, modularity is needed to separate them into separate parts (whether as "components", "modules" or "subsystems"). Then, each such separated part becomes an element of the modular architecture. In this way, each component can be isolated and accessed using a well-defined interface. In addition, modular programming can improve the readability of the firmware while simplifying the debugging, testing and maintenance of the firmware.

Even if one person develops a project independently, doing so is still the best practice overall strategy in terms of code debugging, readability, and portability. If the code is well designed, it can be easily applied to other projects. Moreover, the module has been tested and verified by the previous project, and the defect risk will be greatly reduced if it is used again in a new project. Therefore, every time you do a project, use this strategy to continuously accumulate module "wheels" components. With the growth of experience, the accumulated "wheels" will become more and more and better. So its advantages are obvious. Otherwise, every project is built from wheels, not to mention the long development time, the development level will not be improved, and the repetitive work will be boring. For example, the non-volatile storage management subsystem mentioned in the previous article, if well designed, becomes a reliable and portable wheel. Please understand this passage in depth and take it away without thanks!

Firmware module principle

The basic concept of modular programming in firmware development is to create firmware modules. Conceptually, modules represent separation of concerns . In computer science, separation of concerns is the process of decomposing computer programs into unique functions that rarely overlap. A focus is any focus or function of the program, and is synonymous with function or behavior. The development of separation of concerns has traditionally been achieved through modularization and encapsulation, which is actually the idea of ​​decoupling.

Firmware modules can be divided into several types:

  • The codes related to many upper-level user modules are implemented as separate firmware modules. Common abstract implementations related to the underlying hardware . For example, hal_adc.c is the firmware module of the ADC user module, and hal_timer.c is the firmware module of the Timer user module.

  • The code for a specific pure software algorithm is implemented as a separate firmware module. For example, alg_filter.c is a firmware module that executes software filters (such as median filter, average filter or weighted average filter, IIR/FIR filter).

  • The code of a specific application is implemented as a separate firmware module. For example, app_battery.c is the firmware module of the battery charger application. The code of a specific tool is implemented as a separate firmware module. For example, debug_print.c is a firmware module used to implement the log printing function.

  • ......

Implement some rules for estimating modular design:

  • All module-related functions should be integrated into a single source file, which is a manifestation of high cohesion .

  • The module provides a header file, which declares all the resources of the module (hardware dependency/macro/constant/variable/function). Try to use struct to lumped and encapsulate closely related variables.

  • Include the self-check code part in the source file to realize all the self-check functions of the module.

  • The interface of the firmware module should be carefully designed and defined.

  • Since the firmware depends on the hardware, the dependency of the hardware needs to be clearly mentioned in the source file header. For example, use macros to transfer hardware dependencies to definitions, or use functions to encapsulate basic operations. In the new architecture system, only need to transplant this part of the implementation can be used.

  • Usually, the firmware module can be used by other team members in other projects. It may involve management changes, defect repair, and the owner should maintain the module. The source file header should contain "author" and "version" information.

  • The firmware depends to some extent on the compiler. The header of the source file should declare based on what development environment the verification has been performed to specify the compiler or IDE-related information.

It should be noted that modular design will introduce some call overhead and may increase the size of firmware. In actual implementation, compromise considerations. Do not over-modulate, so it is recommended to adopt a high cohesion and low coupling implementation strategy. In the previous article, I have talked about the design of the ventilator PB560. After reading the code, I planned to interpret the code design, but after reading it, I found that the design is too modular and does not realize the idea of ​​high cohesion. Many source files of its source code only implement a function, instead of focusing on abstract realization of a type of problem, and then abandoning its code interpretation.

How to split the module?

Engineering development must be demand-driven. The first thing is to have a clearer understanding of the requirements before you can design a more reasonable framework. What do we need to achieve? Roughly the overall design process strategy, my basic idea is as shown in the figure below (I prefer drawing, which will make people more intuitive)

The first question to ask yourself is: What are the main functions of this project? Where does this come from? If it is actual product development, it may come from market demand. If it is your own DIY project, you will definitely come up with a general idea? In short, no matter where it comes from, the demand must be sorted out first. So what are the requirements in a general sense?

  • What are the hardware IO interface requirements, such as switch input, ADC sampling, I2C/SPI communication, etc.

  • What are the business logic requirements, such as collecting data from a sensor and controlling a heating device, then this is a high cohesion requirement.

  • Which are the technical requirements related to the algorithm, such as which signals in the product need to be filtered, which ones need to be analyzed in the frequency domain, and so on.

  • Are there any external communication protocol requirements?

  • Whether there is business data that needs to be stored in history, or equipment parameters need to be saved after power-off

  • Is there a log printing requirement?

  • ........

  • There are so many.

Combining the principles of firmware modules and related guiding principles, then the highly relevant requirements are abstractly implemented in a series of modules . After this series of modules cooperate to achieve a certain highly relevant business requirements, these modules become A subsystem . Multiple subsystems coordinate to complete the overall functions of the product under the scheduling of main.c.

How to integrate scheduling

For some applications that do not use RTOS, the following framework can be used:

void main(void)
{
   /*各模块初始化*/
   init_module_1();
   init_module_2();   
   ....
   while(1)
   {
       /*实现一个定时调度策略*/
       if(timer50ms)
       {
           timer50ms = 0;
           app_module_1();
       }
       if(timer100ms)
       {
           timer100ms = 0;
           app_module_2();
       }
       
       /*异步请求处理,如中断后台处理*/
       if(flag1)
       {
           communication_handler();
       }
       .....
   }
}

Examples for RTOS-based integration implementation:

void task1(void)
{
    /*处理子系统相关的初始化*/
    init_task1();
    while(1)
    {
       /*应用相关调用*/
       task1_mainbody();
       ....
    }
}
....
void taskn(void)
{
    /*处理子系统相关的初始化*/
    init_taskn();
    while(1)
    {
       /*应用相关调用*/
       taskn_mainbody();
       ....
    }
}

void main(void)
{
    /*一些基本硬件相关初始化,比如IO,时钟,OS tick定时器等*/
    init_hal();
    ......
    
    /*一些基本RTOS初始化*/
    init_os();
    
    /*任务创建*/
    os_creat("task1",task1,栈设置,优先级,...);
    ......
    os_creat("taskn",taskn,栈设置,优先级,...);
    
    /*启动OS调度器,交由OS调度管理应用任务*/
    os_start();
}

Different RTOSs have different function names, but the general idea is generally the same.

in conclusion

This article provides some personal experience and ideas from why the overall structure of modular design is needed, to the benefits of doing so, and some specific guidelines, to how to achieve in practice, how to achieve high cohesion and low coupling . At the same time, two demos were made for the overall framework of bare metal programs and the integrated framework based on RTOS, which can basically solve most of the framework ideas. Some of the principles recommended by individuals in the previous article are summarized in bold:

  • All module-related functions should be integrated into a single source file, which is a manifestation of high cohesion.

  • The module provides a header file, which declares all the resources of the module (hardware dependency/macro/constant/variable/function). Try to use struct to lumped and encapsulate closely related variables.

  • Include the self-check code part in the source file to realize all the self-check functions of the module.

  • The interface of the firmware module should be carefully designed and defined.

  • Since the firmware depends on the hardware, the dependency of the hardware needs to be clearly mentioned in the source file header. For example, use macros to transfer hardware dependencies to definitions, or use functions to encapsulate basic operations. In the new architecture system, only need to transplant this part of the implementation can be used.

  • Usually, the firmware module can be used by other team members in other projects. It may involve management changes, defect repair, and the owner should maintain the module. The source file header should contain "author" and "version" information.

  • The firmware depends to some extent on the compiler. The header of the source file should declare based on what development environment the verification has been performed to specify the compiler or IDE-related information.

It is strongly recommended to adopt the design first-later development model. It is more taboo to debug gradually, and write where you think. Of course, for novices, the latter mode can be iterated gradually, or gain experience relatively quickly. Of course, the choice depends entirely on personal wishes.

I believe that if you read it in-depth and experience it carefully, you should get some insights from the design ideas and improve it. If I can help you, I am very relieved, and I have worked so hard to code so many words. Of course, if you think the article is valuable, please help me read it, or share it. Let more friends be able to see, of course, for the MCU development god, the views in the article seem rather superficial. As for appreciation, just do whatever you want.

Guess you like

Origin blog.csdn.net/u012846795/article/details/108544332