2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
We have discussed the basics of pointers and structures before. In this article, we will start to apply these basics to some complex applications, such as queues.
In fact, in 2018, I recorded a set of videos on program architecture, which included a step-by-step tutorial on how to write queues and a series of practical high-level programming thinking and techniques, which were well received and recognized by many fans and friends.
However, since the tutorial was recorded earlier, the sound quality is poor, and some details are not perfect. So this thorn has always been in my heart. In order to make the students of the Wuji MCU Special Training Camp more efficient and better understand when learning our project, I recently planned to re-sort these basic contents and make them into a series of software architecture 2.0 graphic/video tutorials.
When I was a R&D engineer, I often came across some communication products, such as industrial control boards, PDUs, and IoT products.
Generally speaking, when making this kind of product, it is quite a headache to write the receiving data stream, whether it is serial communication or wireless communication.
For example, STM32 receives serial port data stream.
In the early days, I defined an array and an array index variable to process the received data stream. The code is as follows:
This approach has many problems and increases the complexity of code writing for engineers.
Code maintenance is difficult
Because you have to manually check the array buffer boundaries to avoid out-of-bounds errors, arrays are not as easy to expand and maintain as queues when you need to process more complex data streams or add new data sources.
Data is easily confused
Directly manipulating the array in the interrupt service (ISR) may cause resource competition with the main program. If multiple tasks access the same array, additional synchronization mechanisms (such as mutex locks) are required to avoid data race conditions and inconsistencies.
If data reception and processing are not synchronized, using arrays may cause data order confusion, resulting in data packet loss caused by program problems. I have been troubled by this problem before. Extra code is needed to solve this problem, which increases the complexity of the program. In addition, I have no experience and the result is still unstable after much effort.
This problem troubled me for a long time, until I changed jobs and read the code written by other engineers, I realized that queues can solve these pain points. From that time on, the way I handle data flow has become as follows:
Does it feel much simpler? In fact, the algorithm for queue data processing is not simple. It is just to use the queue as a general template for data processing. The next time you encounter similar requirements, you can use it directly. In professional terms, the code is more portable and reusable.
This is just one application of queues. The essence of a queue is data cache, and data entry and dequeue follow the first-in-first-out rule.
That is, store the data first, and then take it out for processing when the CPU has idle time or certain conditions of the program are met.
Based on this feature, many practical applications can be derived, especially in applications that need to ensure the order of data.
I have summarized a few of the places I use most often.
When a microcontroller receives data through a serial port, it usually uses a queue to buffer the received bytes, which ensures that the data is not lost before being processed by the main program.
In audio playback or recording devices, queues are used to buffer audio sample data to achieve rotational playback or recording. For example, in Project 6 of our Wuji MCU Training Camp, the WiFi & 4G alarm host has a voice prompt function. For example, when you press the away-home arming button, the "away-home arming" voice will be played, and when you press the at-home arming button, the "at-home arming" voice will be played.
If I press these two buttons quickly, in order to ensure that the voice can be played completely, I can throw the key event into the queue cache first, so that the voice can be played completely in sequence.
In a system using RTOS, queues are used for message passing and synchronization between tasks, supporting complex task scheduling.
After a key event is detected, it can be put into a queue first. The main program can process these events in sequence to prevent the key action from being too fast and causing the key event to be lost. Our current project uses this approach.
The ADC data we collect can be thrown into the queue after certain processing so that it can be processed or analyzed at an appropriate time.
The data interaction of firmware upgrade is relatively large, which is very suitable for using queues to ensure data integrity. Our project 6 also uses it. During the firmware upgrade process, the downloaded firmware data blocks can be put into the queue and then written to the flash memory in sequence. There are many similar applications. In short, the queue solves many of my difficult problems.
A queue is a linear data structure that follows the FIFO (First In First Out) principle, meaning that the data that enters the queue first is the first to be removed. In a queue, data is usually added to one end, called the tail, and removed from the other end, called the head. This structure makes queues very suitable for situations where data needs to be processed in an orderly manner.
We can imagine a queue as putting a ping-pong ball into a two-way pipe. We put the ping-pong ball into the pipe from the left, and this action is called enqueue. We take the ping-pong ball out of the pipe from the right, and this action is called dequeue.
The ping pong balls in the pipe will line up in a queue. The first ping pong ball that goes in will come out first. This is the first-in-first-out rule in the queue.
If ping-pong balls are compared to data, then the pipeline is the cache for storing data. The number of ping-pong balls that the pipeline can accommodate represents the amount of data that the cache can store. To put it simply, it is the size of the array. The queue in the above picture can store 4 data, which is equivalent to Buff[4].
The program implementation of the queue is through an array of fixed size, a head pointer and a tail pointer. The array is responsible for storing data. The head pointer is responsible for which address to take the data out of the queue. The tail pointer is responsible for which address to store the data into the queue. Therefore, the operations of enqueuing and dequeuing are two pointers, playing the first-in-first-out algorithm of data in the array.
Different engineers have different codes for implementing queues. If you don't have rich project experience or have never used queues, don't force yourself to write a queue algorithm.
At the beginning, I directly transplanted other people's queue programs and used them in my own projects. After I became proficient in using them in several projects, I studied the detailed code for the queue algorithm implementation and wrote it a few more times before I understood it.
Therefore, our friends in the special training camp should not write it by themselves at the beginning. Learn to use it first, draw inferences from it, apply it to different scenarios and projects, and then try to write it by yourself after you are familiar with it. This is a very important learning order.
Taking the queue program of our Wuji MCU Project Training Camp as an example, there are a total of 4 functions.
QueueEmpty(x)
Clear the queue function. Each time before using the queue, you must clear the queue. In the clear function, the head pointer and tail pointer will point to a valid address by default, which is the first element of the array. Otherwise, it will cause a pointer address exception.
Parameter description: x - is a queue structure variable
QueueDataIn(x,y,z)
The data enqueue function is to throw one or more bytes of data into the specified queue.
Parameter description:
x - Queue structure variable
y - data address
z - the amount of data to enqueue, in bytes.
Notice:
①. The data entered into the column can only be of unsigned char type.
②. If the queue is full, if you continue to enter data, the data will be overwritten starting from the position of the data that was first entered.
QueueDataOut(x,y)
The data dequeue function takes a byte of data out from the specified queue.
Parameter description: x - queue structure variable y - the address where the data is to be stored
Note: Our dequeue function can only take one byte of data at a time.
QueueDataLen(x)
Clear all data in the specified queue. Parameters: x - queue structure variable
The following content involves some codes and video explanations, which are not convenient to edit. You can ask me to arrange Feishu for a better reading experience.
Recently, many fans asked me how to learn MCU. Based on my ten years of experience, I spent a month to carefully compile a "MCU"
The best learning path for microcontrollers + microcontroller introductory to advanced tutorials + toolkits",All are shared for free to the loyal fans!!!
In addition, I will share with you my secret treasure.22 popular open source projects,IncludeSource code + schematic + PCB + documentation, let youQuickly advance to become a master!
Tutorial package and detailed learning path can be found at meThe beginning of the following article。
《MCU entry to advanced learning path (with tutorials + tools)》
《MCU entry to advanced learning path (with tutorials + tools)》
《MCU entry to advanced learning path (with tutorials + tools)》