SAST weekly is a technology series launched by the student association of the Department of Electronic Engineering, covering multiple aspects such as popular science in the field of information technology, introduction to cutting-edge research hotspots, and exploration of technological news, helping students to broaden their horizons and gain knowledge. It is updated weekly, welcome to follow, and students who are willing to share knowledge can submit articles to [email protected]
What is embedded?
An embedded system is a system embedded within a mechanical or electrical system that has a specific function and real-time computing performance. Unlike commonly used general-purpose computer systems, it typically only has specific functions and processes specific tasks, but correspondingly, it is also smaller in size and lower in power consumption.
Whether it is medical electronics closely related to our lives, smart homes, logistics management, or the seemingly high-end aerospace satellites and intelligent driving, most of the hardware cores can be categorized as embedded systems.
Simply put, embedded development is about programming microprocessors and building circuits to achieve functions such as movement and sensing. For example, Arduino, STM32, Raspberry Pi, and 8051 that everyone has played with in Chip Movement Project, Hardware Design, Electrical Design are all part of embedded system development.
What? You haven’t heard of it?Then how will you fix computers for others in the future?
The core of embedded systems—embedded processors includes four types: Microcontroller (MCU), Microprocessor (MPU), System on Chip (SoC), and DSP. This article mainly discusses the embedded architecture design related to the microcontroller STM32.(Hey, it’s because the electrical design requires it (#`O′)
Why discuss software architecture?
Isn’t it obvious that it’s to make it easier for the big shots to debug for you!
A good software architecture allows you to clearly plan the functions and interfaces of each module and the dependencies between the modules while writing code. This can make your code more maintainable, readable, and extensible—simply put: easy to debug, easy for the big shots to help you debug, easy for the boss to change requirements without having to delete the database and run away.
However, software architecture is more of an empirical content, with some useful design patterns and design principles (Here I recommend Effective C++ and More Effective C++), but they are not taught in class, and if you don’t work on large projects regularly, you won’t consider them, and if you do large projects, you won’t bother to think about them, so they are often overlooked.
Yes! Look at how the Student class inherits from the Time class, things that are completely unrelated are forced together; several classes depend on each other, making it a chaotic mess; the main function body is written in several hundred or even thousands of lines; reusable code is copied and pasted dozens of times. These are all divine operations! (Please make sure to identify with it, side-eye smile
So, I want to simply share some experiences in software architecture.
The first half of this article (Chapters 1 to 4) mainly prepares the hierarchical division from the bottom hardware to the upper application in the embedded development process. The second half (Chapter 5: Overview of Design Patterns, Chapter 6: Practical Design Patterns) is the architecture design of software business logic, applicable to both software development and embedded development.
The main text is more experience-oriented, with less code content. The first four chapters may be hard to understand without a basic knowledge of embedded systems, the editor might get a salary deduction(but actually, I don’t even have a salary); the later chapters can be understood as long as you have learned programming as long as you are from the electronic department.
Given that the first few chapters might scare everyone away, I can freely reveal my true self in the later content.
One
Overall Architecture of Embedded Software
When designing an embedded system, the work we are actually doing is building a bridge from hardware to software: (hardware→) hardware driver code→ module driver code→ operating system→ actual application and business. Just like the electronic department needs to build a bridge from basic physics→ circuits→ processors→ algorithms→ networks.
The main idea here is the commonly used “abstraction/modularization” concept in practical engineering That’s right, it’s the one glgg talked about. For example, in electronics, we encounter network port abstraction, lumped circuit abstraction, digital circuit abstraction, etc. What we do is to encapsulate the underlying content in a small black box first, so that when expanding the system externally, we do not need to focus on the specific implementation of the underlying content, thus achieving simplification.
For embedded systems based on STM32, we usually divide them into four layers: the bottom Hardware Abstraction Layer (HAL) is responsible for directly configuring and controlling the basic peripherals of STM32 (such as GPIO, USART, I2C, SPI, interrupts, etc.); Functional Module Layer uses the interfaces provided by the HAL layer to control the modules (such as HC05 Bluetooth module, MPU6050 gyroscope module, L298N motor driver module, etc.); Operating System Layer is essential for high-level professionals, when the complexity of the system is very high, the operating system layer can help you manage processes effectively; the upper layers are Business Logic Layer and Application Layer, following the software design rules learned in normal classes.
Two
Hardware Driver Layer
For the lower layer, we mainly have four development approaches: Register Library (STM32Snippets), Standard Peripheral Libraries (STD Library), HAL Library, and LL Library. The register library is generally not used due to its low development efficiency (if you see someone writing a register library, you can probably judge the time gap between you); the STD library is helpful for beginners to understand STM32 and Keil (see the tutorial of the department head on SAST Weekly | How to Pretend to Be Familiar with STM32 Smart Cars Before Electrical Design), but later on, you will find that its support for code portability is very poor; the HAL library and LL library are based on STM32Cube, both of which have higher portability, readability, code efficiency, etc. than the first two, with HAL library being particularly common.
The abstraction hierarchy of the four libraries is shown above. We know that the simpler the implementation method, the lower the running efficiency. Otherwise, complex solutions will be eliminated in the wheels of history. In fact, this is true, except for the register library which is useless, the HAL library, which is easier to write, has poor optimization, so when applying it in actual projects, some trade-offs need to be made.
In addition to considering development efficiency and running efficiency, we often need to consider code reusability and portability. Taking the most commonly used STD library and HAL library as examples, the STD library has different library functions for different series of STM32, which leads to great headaches when porting functionalities. The HAL library is much more convenient, as it is universally applicable to all series of STM32.
For example, if a team member suddenly replaces STM32F103 with STM32F407 while doing hardware, and you happen to be using the STD library, then a friendship might quickly break down. Or if you feel that writing nearly ten lines for the configuration of a peripheral every time is too much, and you write a more general configuration library, you will be in a fix when you switch boards.
If you change the series, it won’t work, I’m done for!
Three
Functional Module Layer
For some very small projects (practice-oriented), the code might all be crammed into the main function; when the project gets a bit longer, you might define some functions; when it gets even longer, you will implement the basic functions (hardware abstraction layer) and call them in a modular way. Actually, at this point, your code might only be a few hundred lines long. But as your project expands and the number of hardware modules increases, you will find that the simple two-layer structure of hardware abstraction layer + software application layer is no longer sufficient, at which point you need to add a layer of encapsulation beyond the standard peripheral abstraction—the module abstraction, which encapsulates how each module reads and controls, thus creating the functional module layer.
This layer’s construction is both simple and complex—simple in that you just need to code each module, complex in that you need to code every module, and debug any bugs that arise, so it is a process of continuously expanding your library functions. The specific operations mainly rely on practice, so I won’t elaborate here.
Four
Operating System Layer
When your project becomes more complex—thanks to the presence of the hardware driver layer and the functional module layer, the code does not seem difficult to read, but overlapping multiple tasks (controlling motors, obtaining data from sensors, requesting network information, processing exceptions, and handling upper computer interrupts, etc.) can make timing management very troublesome.
At this point, if we continue to use the original blocking sequential execution + interrupt strategy, you may find that the interrupts can confuse you. This is when you need the help of RTOS.
Common systems include FreeRTOS and uCOS, and a detailed introduction to FreeRTOS can be found in another department head’s article SAST Weekly | An Introduction to FreeRTOS.
So what are the benefits of FreeRTOS? What are the benefits of Jinkela? Whoever gets it right will get it! Simply put, the most direct benefits are:
-
Interrupts still run but are much easier to configure.
-
When delaying, there is no need to run ineffective for(;;); What? You ask me why not write while(1);?The system will automatically execute other processes during periodic waiting.
-
Each function is implemented in its own process; mom no longer has to worry about my main function being a mess.
Of course, the benefits of running a real-time operating system are not limited to these, but since it is quite in-depth and very few students will deeply engage with the operating system layer of embedded architecture, I won’t delve further here.
Now, above all, those who have never played with STM32 really won’t understand the hardware part. Next, let’s talk about the architectural design rules of the software once the lower layer is built.
Five
Overview of Design Patterns: Open-Closed Principle
The open-closed principle means: Open for extension, closed for modification. When the program needs to be extended, the original code cannot be modified, achieving a hot-plugging effect.
This is like saying you can become charming, knowledgeable, and change your mental outlook and dressing style to win your girlfriend’s favor, but once your sincere affection is established, it cannot be changed.In fact, this analogy is inappropriate because first, you have to have a girlfriend.
In fact, there are six main principles in commonly mentioned design patterns, including the Liskov Substitution Principle (When you praise a girl, you must also be able to praise your girlfriend, otherwise you will have to go home and kneel on the keyboard)), Dependency Inversion Principle (The status of girls must always be higher than yours), Interface Segregation Principle, Least Knowledge Principle (Don’t go out and flirt), and Composite Reuse Principle (Be frugal and buy, eat, and play for your girlfriend).
Have you noticed? In all the completely inappropriate analogies above, all “you” refer to “concrete”, and all “girlfriend” refers to “abstraction“. Do you understand? This is how painful the realization is for programmers !
In summary, the overall principle of design patterns “open-closed principle” is roughly about abstraction/modularization, ensuring that once the modularized content is encapsulated, it will not be directly modified internally. This is similar to the circuit abstraction that glgg talks about, and now you can better appreciate glgg’s profound and broad wisdom.
Six
Design Pattern Practice: Singleton and MVC
Finally, let’s discuss software architecture design and reflect on how to avoid writing the kind of code that young me wrote in my freshman year that makes people sigh in regret, unreadable, useless program design assignments. Great ones, please accept my bow orz
Note: Strictly speaking, the concepts of framework, architecture, and design patterns have certain differences. A framework is about code reuse, while design patterns are about design reuse, and architecture is more of a mixed concept. However, since the author is not an expert, I won’t specifically differentiate them.
In fact, those who are attentive can find that the image on the left is actually
the logical relationship diagram of different software design patterns
Anyway, I’m quite confused.
Design patterns are diverse, and the first three categories in the figure below mainly include design patterns for specific functionalities (such as how to create an object, how to make information exchange between objects, etc.), while the fourth category, J2EE patterns, is more like overall system design principles. In this article, I will simply select a few more useful familiar, simple, and understandable patterns to share.
Singleton Pattern
The constraints of Singleton are as follows:
-
There can only be one instance.
-
Must create its own unique instance.
-
Must provide this instance to all other objects.
For example, the Manager derived class and Salesmanager derived class in the Employee class in the freshman programming assignment should follow the singleton pattern. Similarly, a GUI of a window program, or login information for database access, should also follow the singleton principle.
In C++, the specific implementation of singleton mainly involves creating a Heap-Only Class (a class that can only be instantiated on the heap), using the technique of private constructor. Don’t tell mesjs/hyf saidthat constructors can only be declared public; this kind of thinking is very dangerous.
MVC Pattern
After introducing a specific functional design pattern, we will introduce a holistic framework design pattern. The MVC pattern is widely used in large projects, dividing all functionalities into three parts:
-
Model: Responsible for data, including fetching data from the database, processing data to meet business requirements, etc.
-
View: Responsible for display.
-
Controller: Responsible for interacting with the user, processing user input from the View, controlling data acquisition and CRUD in the Model, and feeding back new information to the user through the View.
The advantage of this approach is that it greatly decouples the most entangled logical relationships in tasks into MVC three parts, allowing for independent development and debugging of each part. Therefore, designing the interfaces for each part becomes a challenge at the architectural level.
I can give an example: if we want to do a program design assignment project with a UI in front and a database in the back. In it, the front end needs to call the backend user login, the user login needs a database connection, and during the user login and database operation, the front end interface needs to display debug information. In this case, these three functional module classes need to reference each other and be interdependent; your code will be full of bugs and your mindset will explode.
At this point, you can use MVC, where the View only handles front-end displays and provides a debug information display interface to the Controller; the database connection and user login are only implemented in the Model and called indirectly through the Controller to the View’s debug interface. This way, your code architecture is as clear as that of the big shots.
Note: In fact, the three components mentioned here—front-end display, database connection, user login—are all excellent examples of the Singleton pattern from the previous chapter.
But! But! It is very difficult to completely separate M and V in MVC, so if your project is not large (a few thousand lines), completely adhering to MVC is like moving stones to hit your own feet—self-inflicted trouble (this idiom really doesn’t match this pairing). In this case, you can make some modifications.
For example, for all singleton objects, provide a public reference to the class object pointer in the Controller part, which will not occupy too much space and can effectively build a bridge between M and V.
Some common #define and typedef are still in the header files of the Controller, achieving global sharing.
Since the Model is responsible for data management as well as data processing, I will further divide the Model into two layers: the lower layer only interacts with the database, providing interfaces for searching and CRUD; the upper layer is responsible for implementing business logic.
Final Words
In fact, the use of architecture and design patterns is not like learning a new library or language; it does not have rigid commands, only some empirical and directional summaries. In this process, what is needed more is continuous comparison, modification, and summarization in practice. Anyways, I hope everyone can slowly explore an architecture that satisfies them and their own design principles.
References
-
STM32 Embedded Software Overview:
https://www.st.com/en/embedded-software/stm32-embedded-software.html
-
Introduction to Design Patterns | Runoob Tutorial:
https://www.runoob.com/design-pattern/design-pattern-intro.html
-
SAST Weekly | How to Pretend to Be Familiar with STM32 Smart Cars Before Electrical Design
-
SAST Weekly | An Introduction to FreeRTOS
Author: Zhang Yichi
Reviewer: Zhong Hongtao Wu Junchen
Leave a Comment
Your email address will not be published. Required fields are marked *