Follow+Star Public Account Number, don’t miss wonderful content
Source | Embedded Guest House
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 fast development iteration times.The best practice for achieving such a programming environment is to use a unified firmware architecture that serves as a framework during product development and supports “firmware modularization,” also known as subsystems.
Modular Architecture Design?
Modular programming decomposes program functions into firmware modules/subsystems, where each module performs a function and contains all the source code and variables needed to complete that function.

Firmware Module Principles
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 a computer program into distinct features with minimal overlap.Concerns are any focus or functionality of the program and are synonymous with features or behaviors.The development tradition of separation of concerns is traditionally achieved through modularization and encapsulation, which is essentially the idea of decoupling.
-
Code related to many upper-level user modules is implemented as separate firmware modules. Common examples include low-level hardware-related abstractions. For example, hal_adc.c is the firmware module for the ADC user module, while hal_timer.c is the firmware module for the Timer user module. -
Code for specific pure software algorithms is implemented as separate firmware modules. For example, alg_filter.c is the firmware module that implements software filters (such as median filters, mean filters, or weighted mean filters, IIR/FIR filters). -
Code for specific applications is implemented as separate firmware modules. For example, app_battery.c is the firmware module for the battery charger application. Code for specific tools is implemented as separate firmware modules. For example, debug_print.c is the firmware module for implementing logging functionality. -
……
-
All functions related to a module should be integrated into a single source file, which reflects high cohesion. -
The module should provide an interface header file that declares all resources of the module (hardware dependencies/macros/constants/variables/functions). Try to encapsulate closely related variables using struct. -
Include a self-check code section in the source file to implement all self-check functions of the module. -
The interfaces of firmware modules should be carefully designed and defined. -
Since firmware depends on hardware, it is necessary to explicitly mention hardware relevance in the source file header. For example, using macros to define hardware dependencies or encapsulating basic operations in functions. In the new architecture, only this part of the implementation needs to be ported for use. -
Generally, firmware modules can be used by other team members in other projects. This may involve managing changes, defect fixes, and the owner should maintain the module. The source file header should include “author” and “version” information. -
Firmware depends on the compiler to some extent. The source file header should declare the development environment on which it has been validated to specify compiler or IDE-related information.
How to Split Modules?
In engineering development, it must be demand-driven.The first thing is to have a clear understanding of the requirements before designing a reasonable framework.What do we need to achieve?The general design process strategy I basically adopt is shown in the following diagram (I prefer drawing, as it can make things more intuitive).

-
What are the hardware IO interface requirements, such as switch inputs, ADC sampling, I2C/SPI communication, etc. -
What are the business logic requirements, such as collecting data from a sensor, controlling a heating device, which are high cohesion requirements. -
What are the algorithm-related technical requirements, such as which signals in the product need filtering, which need frequency domain analysis, etc. -
Are there any external communication protocol requirements? -
Are there any business data that need historical storage, or device parameters that need to be saved during power loss? -
Is there a need for logging requirements? -
…….. -
And so on.
How to Integrate Scheduling
For applications that do not use RTOS, the following framework can be used:
void main(void){ /* Initialize each module */ init_module_1(); init_module_2(); .... while(1) { /* Implement a timing scheduling strategy */ if(timer50ms) { timer50ms = 0; app_module_1(); } if(timer100ms) { timer100ms = 0; app_module_2(); } /* Asynchronous request processing, such as interrupt background processing */ if(flag1) { communication_handler(); } ..... }
void task1(void){ /* Initialize related subsystems */ init_task1(); while(1) { /* Application-related calls */ task1_mainbody(); .... }}....void taskn(void){ /* Initialize related subsystems */ init_taskn(); while(1) { /* Application-related calls */ taskn_mainbody(); .... }}void main(void){ /* Some basic hardware-related initialization, such as IO, clock, OS tick timer, etc. */ init_hal(); ...... /* Some basic RTOS initialization */ init_os(); /* Task creation */ os_creat("task1",task1, stack settings, priority, ...); ...... os_creat("taskn",taskn, stack settings, priority, ...); /* Start OS scheduler to manage application tasks */ os_start();}
In Summary
This article discusses the need for modular design architecture, the benefits of doing so, some specific guiding principles, and how to implement them in practice, as well as how to achieve high cohesion and low coupling, providing some personal experiences and thoughts from my work.
At the same time, two demos of the overall framework for bare-metal programs and RTOS-based integration frameworks are provided, which can basically solve most framework thought problems.Some principles I personally advocate from the previous text are summarized in bold:
-
All functions related to modules should be integrated into a single source file, which reflects high cohesion. -
The module should provide an interface header file that declares all resources of the module (hardware dependencies/macros/constants/variables/functions). Try to encapsulate closely related variables using struct. -
Include a self-check code section in the source file to implement all self-check functions of the module. -
The interfaces of firmware modules should be carefully designed and defined. -
Since firmware depends on hardware, it is necessary to explicitly mention hardware relevance in the source file header. For example, using macros to define hardware dependencies or encapsulating basic operations in functions. In the new architecture, only this part of the implementation needs to be ported for use. -
Generally, firmware modules can be used by other team members in other projects. This may involve managing changes, defect fixes, and the owner should maintain the module. The source file header should include “author” and “version” information. -
Firmware depends on the compiler to some extent. The source file header should declare the development environment on which it has been validated to specify compiler or IDE-related information.


