An Analysis of the Core Concepts and Advantages of Modular Design in Embedded Development
Release time:
2023-02-17 10:09
In embedded development, the principles and advantages of modular design are crucial. For beginners who are new to microcontroller development, it can be overwhelming—especially when they try to jump straight into using an RTOS. Some microcontrollers have limited resources and are not well-suited for running an RTOS directly. Even so, using an RTOS still requires a clear understanding of the overall system architecture.
This article aims to share some insights and experiences on the overall framework design of microcontroller programs. So, why do we need to discuss architecture? The goal of microcontroller system developers is to create firmware that achieves low cost, high reliability, and rapid iteration. The best practice for achieving this goal is to adopt a unified firmware architecture—a structural framework that serves as a backbone throughout the development process and supports “firmware modularity.”
Without a unified design architecture, the coupling relationships among business requirements will become complex. Lacking a methodological framework that emphasizes design before development will lead to difficulties in later-stage program maintenance, increase the risk of introducing latent bugs, and prevent multi-person collaborative development. However, by integrating a design architecture that emphasizes firmware modularity, testability, and compatibility, we can apply this approach to any firmware development project, thereby maximizing code reusability, accelerating firmware debugging, and enhancing firmware portability.
So, what is modular architectural design? Simply put, it involves breaking down program functionality into firmware modules or subsystems, with each module performing a specific function and containing all the source code and variables necessary to accomplish that function.
Modularity and subsystemization play a crucial role in coordinating parallel team efforts, managing dependencies among various project components, and enabling design and system integration personnel to reliably assemble complex systems. Not only do they help designers cope with and manage complexity, but they also allow applications to be logically divided into discrete parts—such as “components,” “modules,” or “subsystems”—as the scale and functionality of the application grow. Each of these parts becomes an integral element of the modular architecture, with clearly defined interfaces that ensure isolation and controlled access among components. Moreover, modular programming not only enhances the readability of firmware but also simplifies the processes of debugging, testing, and maintenance.
Even when developing a project independently, adhering to this modular strategy remains crucial. Well-designed code not only enhances readability and portability but also makes it easy to reuse in other projects. Moreover, when tested and verified modules are applied to new projects, the risk of defects is significantly reduced.
Therefore, as we accumulate more project experience, the “module” components we continuously develop will become increasingly numerous and of ever-higher quality. By contrast, if we don’t adopt a modular approach, each project would likely start from scratch—resulting not only in long development cycles and difficulty in improving development standards, but also in tedious, repetitive work. For instance, a well-designed non-volatile storage management subsystem can become a reliable and portable “module,” providing strong support for subsequent projects.
Code used to implement specific pure-software algorithms, such as alg_filter.c, focuses on executing software filter functions, including median, mean, or weighted-mean filters, as well as IIR and FIR filtering. Similarly, code for specific applications, such as app_battery.c, is dedicated to implementing battery charger applications. In addition, code for specific tools, such as debug_print.c, focuses on implementing log-printing functionality.
When implementing modular design, it is essential to follow several key rules. First, all functions related to a module should be integrated into a single source file to achieve high cohesion. Second, each module should provide a header file that declares all of its resources, such as hardware dependencies, macros, constants, variables, and functions. In addition, closely related variables should, whenever possible, be grouped together and encapsulated within a struct.
Each source file should include a self-check code section to implement all self-check functions for the module. Meanwhile, the interfaces of firmware modules must be carefully designed and defined to ensure loose coupling among modules. Since firmware is closely tied to hardware, the source file headers must explicitly indicate this hardware dependency—for example, by using macro definitions to escape hardware dependencies or by encapsulating basic operations within functions. In this way, in a new architectural framework, only the relevant implementations need to be ported to achieve reuse.
Typically, firmware modules can be reused by other team members in different projects. This may involve managing changes and fixing defects. Therefore, the module owner is responsible for maintaining the module and including “author” and “version” information in the header of the source files to track changes. In addition, firmware development is to some extent dependent on the compiler. Hence, the header of the source files should specify the development environment in which the module was verified, indicating the compiler used or any IDE-related information.
It is worth noting that modular design introduces certain overhead in terms of invocation and may also increase the size of the firmware. Therefore, when implementing such a design in practice, it is essential to weigh the pros and cons carefully. To avoid excessive modularity, it is recommended to adopt an implementation strategy characterized by high cohesion and low coupling. When splitting modules, ensure that each module focuses on handling a single type of problem and implements only the relevant functionality.
In engineering development, demand-driven approaches are indispensable. The primary task is to gain a deep understanding of the requirements and, based on this understanding, design a reasonable framework. To clearly illustrate our objectives, I’ve adopted an intuitive, graphical approach to outline our overall design strategy.
In engineering development, the primary task is to gain a deep understanding of the requirements and, based on this understanding, design a reasonable framework. To clearly articulate our objectives, I’ve adopted an intuitive, graphical approach to outline our overall design strategy. Next, we need to identify the core functionalities of the project and pinpoint their origins. These may stem from actual market demands or arise from our own DIY projects and creative ideas. Regardless of where the requirements come from, they must first be carefully analyzed and organized.
So, what aspects typically comprise these requirements? First, there are hardware I/O interface requirements, such as digital input, ADC sampling, and I2C/SPI communication. Second, there are business logic requirements—for example, tasks with high cohesion, like collecting sensor data or controlling heating devices. In addition, there are also algorithm-related technical requirements, such as signal filtering or frequency-domain analysis. At the same time, it’s also necessary to consider whether there are requirements for external communication protocols, historical storage of business data, and power-loss protection for device parameters.
Based on the principles of firmware modules and relevant guidelines, we abstract highly correlated requirements into a series of modules. These modules, when combined with the implementation of specific high-correlation business requirements, form a subsystem. Under the scheduling of main.c, multiple subsystems work in coordination to collectively fulfill the product’s overall functionality.
For certain applications that do not use an RTOS, we can adopt the following framework for integrated scheduling:
void main(void) { /* Initialize each module */ init_module_1(); init_module_2();
// ... while (1) { /* Implement a timed scheduling policy */ if (timer50ms) { timer50ms = 0;
app_module_1(); } if (timer100ms) { timer100ms = 0; app_module_2(); } /*
Asynchronous request handling, such as interrupt-driven background processing */ if (flag1) { communication_handler(); } // ... }}
As for the integrated implementation based on an RTOS, we can adopt the following example framework:
void task1(void) { /* Handle subsystem-related initialization */ init_task1(); // ...
In engineering development, modular design and its overall architecture are of paramount importance. Not only does it enhance code readability and maintainability, but it also ensures low coupling among modules, thereby simplifying the entire development process. To achieve the goal of high cohesion and low coupling, we need to follow a set of guiding principles: for instance, concentrating module-related functionality within a single source file, providing unified header file declarations for external access, and including self-checking code in the source files themselves. Moreover, carefully designed and well-defined firmware module interfaces are indispensable. By adopting these measures, we can effectively build an engineering architecture that is clear, robust, and easy to manage. Since firmware is closely tied to hardware, it’s essential to explicitly indicate the hardware dependencies at the beginning of each source file. This can be accomplished either by using macros to define hardware dependencies or by encapsulating basic operations within functions. With this new architectural approach, simply porting this portion of code allows us to reuse functionality across different projects. Typically, firmware modules are utilized by other team members in various projects. This may involve managing changes, fixing defects, and performing other related tasks; therefore, the module owner should take responsibility for maintaining these source files. At the beginning of each source file, it’s crucial to include “author” and “version” information for tracking and identification purposes. Additionally, the firmware development environment is somewhat influenced by the compiler. Thus, it’s vital to declare the verified development environment at the head of the source file—this includes specifying the particular compiler used or relevant information about the integrated development environment (IDE). We recommend adopting a design-first development approach, minimizing step-by-step debugging and ad-hoc coding. Although the incremental iterative method can help beginners quickly accumulate experience, in the long run, sound design principles are far more critical. Of course, the choice ultimately depends on individual learning styles and progress. I hope that through careful reading and thoughtful reflection, you’ll gain valuable insights and improvements from the design philosophy itself. If you find this article helpful, feel free to give it a thumbs up or share it with others so that more people can benefit from it. As for monetary appreciation, that’s entirely up to you—it’s not mandatory.
Related News
The Winds Are Changing for Embedded Systems: Key Trends Revealed at Embedded World 2026 in Nuremberg
Infineon Leads the MCU Market, While Industry Giants Like TI, NXP, and ST Showcase Cutting-Edge AI Technologies: From Humanoid Robots to Wi-Fi 7 Solutions—The 2026 Embedded Systems Exhibition Unveils Ten Major Technology Trends.
Efficiently deploy OpenClaw to unlock full-scenario applications for edge AI agents!
Special Recommendation: The LeaKin Technology RS3588S Smart Box is a high-performance edge computing product independently developed by our company, specifically designed for AI edge deployment. For more product details or business procurement inquiries, please feel free to contact our sales team directly by phone. We will provide you with professional technical support!
The five core exchange engineers attending the meeting posed for a commemorative photo in front of the company’s iconic wall. In the background, the words “LeaKin Technology” were beautifully juxtaposed with a pair of red Spring Festival couplets; some raised their thumbs in approval, while others clenched their fists in encouragement—no stiff, staged poses here, just the genuine, down-to-earth smiles that tech professionals wear after solving a problem. As we’ve always believed: the true value of technology lies in its application, in collaboration, and in every sincere act of openness and learning. Walking alongside innovators and embedding technology within real-world scenarios—this is not only Lingkun Technology’s daily routine, but also the posture we adopt as we move forward.
On January 27, 2026, the R&D center of Shenzhen LeaKin Technology Co., Ltd. hosted a professional technical training session. Mr. Xie, the head of the Shenzhen office of Jiangsu NCT, served as the keynote speaker and provided the LeaKin Technology technical team with an in-depth explanation of the processes and applications of precision alloy resistors. This training session not only facilitated technical exchange but also marked the official launch of a deepened collaboration between the two companies.