In embedded software development, including microcontroller development, software architecture is a critical consideration for developers.
Software architecture is crucial for the overall stability and reliability of the system. A suitable software architecture not only has a clear structure but also facilitates development.
I believe that in the early stages of embedded or microcontroller software development, most developers adopt a simple sequential execution architecture (I did too). In embedded software development, program architecture is mainly divided into three types, which this article will explain in detail.
The Significance of Software Architecture
A good program architecture can be a dividing line between an experienced engineer and a novice. Software architecture is friendly to developers; you can specify what tasks to execute first, what tasks to execute next, or what tasks to execute at a certain time point, and so on. The specific methods to solve these problems vary under different software architectures.
The greatest help of software architecture to developers is: it helps them grasp the framework of the entire project. Once you become proficient in a particular program architecture, you will definitely be able to quickly locate and resolve bugs that arise in the system. Of course, I recommend selecting a suitable software architecture based on needs, and the specific reasons will be introduced later in the article.
In-Depth Introduction to Three Different Program Architectures
The three commonly used software architectures are: sequential execution front-and-back system, time-slice polling system, and multitasking operating system. To give everyone a clearer understanding, I will introduce an example using these three software architectures. This example includes four tasks: key scanning, sound and light alarm, screen refresh, and ultrasonic distance measurement. The specific function of this example is to set a threshold for measuring distance via keys, and when the measured distance falls below the set threshold, it triggers the sound and light alarm and displays the measured distance in real-time on the screen (this application is a specific embodiment of a car reversing radar).
1. Sequential Execution Front-and-Back System
In the sequential execution front-and-back system, I will place the keyboard scanning in a while(1) loop using polling, while the screen refresh and ultrasonic distance measurement use interrupts. After obtaining the measured distance in the interrupt service function, it will be displayed, while key detection and sound and light processing will also be placed in the main loop. Thus, the entire program executes in a synchronized manner using variable flags in the main loop and background interrupts. The corresponding program code is shown below:
The above code is the main function of the sequential execution front-and-back system.
The above code is the interrupt service function of the sequential execution front-and-back system.
The advantage of this architecture is its simplicity and ease of understanding, while the disadvantage is that if each task occupies too much CPU time, it can lead to poor real-time performance, such as in key detection.
2. Time-Slice Polling System and Multitasking Operating System
Time-slice polling actually usually appears in operating systems, meaning it belongs to operating systems, but here it refers to time-slice polling based on the front-and-back system.
The essence of time-slice polling is to select a timer, increment the count value each time a timer interrupt occurs, and execute tasks in the main loop based on this count value, which represents the time slice for task polling.
In this example, if a time-slice polling system is adopted, the first step is to select any timer of the main control chip, with the timer’s timing period determined by us. To ensure real-time performance and operational efficiency, this value is typically set to 10ms, 30ms, 50ms, etc. I would set the key scanning polling value to 20ms, as the key bounce duration is generally around 20ms, achieving both debouncing and ensuring key detection is not missed.
The screen refresh is set to 30ms, and if you feel the refresh response is slow, you can modify this polling value for improvement; the ultrasonic distance measurement polling value is set to 100ms, meaning it triggers measurement once every 100ms, which is sufficient for most situations.
The program code is as follows:
The above code is the main function of the time-slice polling system.
The above code is the timer interrupt function of the time-slice polling system.
It can be seen that the time-slice polling method has significant advantages compared to sequential execution, possessing both the advantages of sequential execution and some advantages of operating systems.
3. Multitasking Operating System
The operating system itself is quite complex, and the underlying management and scheduling of tasks is intricate and challenging.
However, we generally consider the operating system as a tool or platform; our goal is to utilize its functions rather than develop an operating system.
I have used small real-time operating systems like uCOS and FreeRTOS, as well as larger operating systems like Linux. Having an operating system significantly improves both program stability and development efficiency.
When using an operating system, we need to learn and understand its scheduling and communication methods more.
In reality, there are not many who can truly utilize an operating system; rather, the majority run bare-metal applications, which also relates to specific product requirements, as many simple systems only require bare-metal solutions.
I won’t delve deeply into the operating system itself here, as it is quite complex. The code in the following illustration shows the program structure for creating a key-controlled LED on/off function in FreeRTOS, which you can compare:
The above shows the main function in the FreeRTOS multitasking system.
The above shows the task callback function in the FreeRTOS multitasking operating system.
How to Choose the Right Software Architecture
I have worked with various MCUs for project development, such as STM32, STC15, NXP, etc., and have encountered complex design requirements, such as vehicle intelligent systems and smart homes. When returning to bare-metal development, I naturally begin to contemplate the design issues of the complete system’s software architecture, and I believe that most readers also engage in bare-metal development.
I believe there is no best software architecture (program architecture), only the most suitable one. Different application scenarios require different program designs, and merely comparing which program architecture is the best holds little practical significance.
Next, let’s analyze specific application scenarios:
-
In some logically clear and functionally simple systems, it is very suitable to choose a sequential execution front-and-back architecture. This software architecture can often meet most of our needs, such as rice cookers, induction cookers, and voice-controlled light bulbs;
-
In situations where resources are scarce in microcontrollers and high reliability is required, this method is very suitable, as the system consumes relatively little, sacrificing only one timer. However, choosing this program architecture requires careful consideration of time-slice division;
-
Finally, in systems with complex functions and difficult logical control, it is suitable to choose a multitasking operating system, such as video surveillance systems, drones, and other application scenarios.
As embedded software engineers, mastering these three software architectures is essential, as they provide us with more choices and considerations when designing programs. Each different program architecture has its own advantages and disadvantages, which we need to practice diligently to appreciate its intricacies.