The first FreeRTOS check-in station is open: Application scenario station, closing time is August 14
[Copy link]
Activity Overview: Click here to view (including activity encouragement and activity learning content)
Check-in start and end time for this site: August 12-August 14 (3 days)
Check-in tasks:
1. Read Cruelfox's first article: FreeRTOS Learning Notes (1) Application Scenarios
2. Please imagine a specific application that is suitable for implementation with a single-chip microcomputer, describe the software components that can become "tasks", and analyze what benefits can be achieved by introducing a real-time operating system into the software of this application.
FreeRTOS Learning Notes (1) Application Scenarios (Author: cruelfox)
The following content is copied and pasted from cruelfox's " FreeRTOS Learning Notes (1) Application Scenarios ". For your convenience, I copied it directly. Although it is the first article, the whole article is concise and requires you to read it carefully.
I was forced to take a look at FreeRTOS during the NXP KW41 competition. I later planned to systematically learn it and try to apply it to my own DIY projects. FreeRTOS is just one of many RTOS (literally meaning real-time operating system). Because it is widely used and open source, it is a good choice for learning. I have roughly read its documentation, and now I am going back to sort it out and study the implementation details of some of them while writing this series.
When can FreeRTOS be used?
Do microcontrollers also need operating systems? If we use Windows, Linux, BSD, or even DOS, which are used in daily life, to represent operating systems, it is a very absurd idea to run operating systems on microcontrollers - because for most microcontrollers, RAM is too little. FreeRTOS is not intended to provide a platform for running software on microcontrollers, installing software one by one on it, and allowing users to choose what to run. It has no user interface; it is not a housekeeper, nor does it have any hardware drivers, nor does it provide file system services. FreeRTOS is just an operating system kernel, which first provides the most important feature of the operating system: task scheduling.
In other words, with FreeRTOS, it will be easier to implement multitasking on microcontrollers. There are at least two meanings here. One is whether multitasking must be implemented with RTOS? Of course not. For microcontroller development, all system resources are yours, and it is not difficult to handle different tasks in different interrupt services. Second, is there no need for RTOS without multitasking? This also depends on the specific situation, how to define the concept of "task", and a complex thing may also be divided into several tasks in the program to handle.
Let's give a few examples.
1. SD card MP3 music player
Let’s ignore the user interface for now. In the playback state, the player works like this from the data flow (somewhat simplified):
I2S interface DMA buffer is idle
Fill the buffer with remaining PCM data, request to decode the next block of MP3 data
Request to read the next section of the MP3 file
Locate the sector position to be read on the SD card
SDIO controller sends read command
SDIO controller receives data and completes interrupt
Fill the file data buffer
Decode MP3 data and write PCM data buffer
Fill the DMA buffer of the I2S interface
This process involves four key software parts:
If the software design idea is from top to bottom, the I2S device driver is awakened at a fixed beat to fill the buffer with PCM data, so it is necessary to call the MP3 decoding program regularly. The MP3 decoder decides whether to access the file system based on the result of the previous decoding operation (because the amount of PCM audio data generated by MP3 decoding a block of data cannot be exactly the size requested by the I2S device driver), and how many bytes of MP3 file content need to be read, and the decoded data that is not used temporarily should also be saved for next time. When it comes to the file system, the location and length of the file requested to be read may not be exactly a sector on the SD card, so there is also a cache, and it is also necessary to track the index of the file on the SD card. The SDIO device driver reads the SD card according to the request of the file system, and returns after waiting for the operation to complete. Note that it is different from the previous module in that there is a hardware IO waiting time in which nothing is done.
The nested call relationship is as follows:
Note that each function call represents a complete operation: filling a buffer, decoding a piece of MP3 data, reading a piece of file, and reading a piece of data from the SD card. The subroutine at the lower level is called, and after completion, it returns to the program at the upper level to continue execution. In the absence of an exception (interrupt), the program will not leave this nested call relationship.
It should also be noted that although the main functions listed above are complete in one call operation, their internal states may not be the same after each operation. In C language, these functions need to have static local variables, or use some global variables to remember what the state was after the last call. A typical example is the file system read_file(). After such a function is called, it needs to remember the position of the file pointer so that it can continue reading next time.
The music player implementation described above has an important disadvantage: during the waiting time of the SD card read operation, the CPU can only stay in the SD card access function and cannot be used for MP3 decoding operations, so the processing power is wasted. During the time when the buffer of the I2S device is full and needs to be refilled next time, the CPU is also in an idle state. In fact, these two idle times can be used to do other things, such as pre-decoding part of the MP3 data. So the question is, how to jump out of a function to execute other functions and then jump back during the execution of a function? Use interrupts, yes, but what to put in the interrupt?
2. USB mass storage device
It is to use the USB on-chip device of the microcontroller to simulate a U disk. The main content of making such a device is to respond to various requests from the USB host. When the USB host sends a request to the device, the USB hardware will generate an IRQ, and then the USB interrupt service function (IRQ handler) will be executed. Usually, the USB driver library provides some callback function interfaces, which are functions written by the user for the USB IRQ handler to call when needed.
As a USB mass storage device, the callback functions that need to be provided must have functions such as reading storage devices and writing storage devices to generate actual disk data for the USB and receive disk data required by the USB to be written. When these functions are called, the application cannot foresee because they are in the response process after the USB interrupt occurs. In this way, the callback function handles the transactions simulated by the USB disk, and the main program does not need to care about it, and can handle other unrelated tasks, thus achieving multitasking!
However, executing user code in an interrupt environment does not seem to be a good choice, because interrupts with the same or lower priority cannot be responded to at this time. In actual USB mass storage devices, the data of the USB disk needs to be read from an off-chip storage device, such as NAND Flash, and the situation will be bad - reading data requires waiting, and this waiting occurs entirely in the USB IRQ handler, and the USB host's request cannot be responded to for the time being. If the tasks in the main program also require hardware I/O interrupts, it may be affected.
Well, for an application that only simulates a USB flash drive and does not perform other tasks, this does not seem to matter: it is all waiting anyway. The performance of the USB flash drive device under USB full speed (12Mbps) that I have personally tested, the transmission speed is up to more than 500 kB, which is much worse than 12Mbps. Why?
Looking at the figure above, there is no data transmission during the USB IRQ interrupt response, so part of the 12Mbps effective bandwidth is wasted, and the throughput of the USB flash drive naturally cannot reach the theoretical value. When the callback function is waiting for I/O, the USB host is also waiting for the response of the USB device. After the USB flash drive data is ready, the USB hardware sends the data, and the CPU has nothing to do until the next request arrives. A more reasonable design is to use the USB TX time to pre-read the I/O of the actual storage device, that is, guess that the USB will continue to request to read the data after the last read, then read the data into the memory first. Once the guess is correct, the response time of the next request can be shortened, and the read throughput of the simulated USB flash drive is improved. To achieve this, you cannot simply handle I/O tasks in the callback function.
3. Storage management of data recording devices
An application needs to perform the following operations on a remote device (connected via UART): ReadChipID, ReadStatus, ReadData, EraseData, and WriteData to achieve data management. In terms of specific implementation, these operations all send command data in a pre-defined format via UART, and then parse the returned data content received by UART to determine whether the operation is successful and receive valid data.
If we focus on sending commands via UART, the structure of the program may be as follows:
Such a design assumes that the remote device will send out a response as expected, but has almost no fault tolerance for the UART interaction process. Once an unexpected event occurs during the interaction, it is impossible to recover from the error state and can only start over.
Once the possible exceptions in UART communication are taken into account, the writing of the program becomes less straightforward. One way is to write the function of parsing the data received by UART into a state machine, and determine the state of program execution based on whether the data conforms to the predetermined format and the identification of the returned data. When certain states are reached, commands are sent from UART. However, it is difficult to see the intention of program execution from the perspective of code reading. The operation process and communication error correction are mixed together, which is not conducive to code maintenance.
How to integrate communication status identification and error handling mechanisms while keeping the program flow intuitive? According to general thinking, this is a single-task (thread) program, and the program is executed sequentially without the need for interrupts. The program is operating according to a process, but it also has to handle UART communication anomalies that are not closely related to the process. These two tasks are staggered in time. What if it is assumed that these are two tasks in progress, but they are switching between each other in a predictable way? It seems that this makes simple things complicated again...
The above three examples may all use "task scheduling" to achieve more effective runtime allocation. In my opinion, FreeRTOS provides a new way of organizing programs - tasks, which divides complex things into independent small blocks and writes them separately; at the same time, it provides some mechanisms for these small blocks to work together to achieve the overall purpose. FreeRTOS tasks are written in C language functions, which provide a feature that allows several functions to appear to be executed "simultaneously". Of course, when there is only one CPU, they are actually executed in turns, but this is also a major difference from ordinary C language programs.
We know that C language functions can be nested and recursively called. For example, funcA() calls funcB(), and then funcB() calls funcC(), or even funcC() calls funcA(). However, funcA() must wait until funcB() returns before it can continue to execute the content behind it. When funcB() calls funcC(), it also waits for funcC() to complete before returning control.
For FreeRTOS tasks, this is not the case. TaskA() can actively or passively hand over control during execution. TaskB() can get control at this time, but it does not start from the beginning, but continues to execute from the place where it handed over control last time. At some point, taskB() hands over control, and the system chooses to execute taskC(). Then, when taskC() hands over control, taskA() resumes execution.
The above diagram is a little simplified. In fact, when jumping out of a task function and continuing to execute in another task function, a section of FreeRTOS kernel code must be executed in the middle. In other words, the kernel is responsible for scheduling which task to execute next, how to suspend the current task, and how to resume another task.
Does multi-tasking have to be implemented with a certain RTOS? Not really. Using RTOS will help, but the resource overhead will be a little higher. For example, using interrupts to switch tasks, as mentioned above, can already implement simple multi-tasking. For another example, decompose a task into multiple steps, each step corresponds to a function, and then choose which step of which task to execute each time in a large loop. This kind of multi-tasking is non-preemptive, while multi-tasking implemented with interrupts is preemptive.
Please note that regardless of whether a certain RTOS is used to implement it, the characteristic of multi-tasking operation is that as long as each task is not completed, its state (including private data) must be completely saved. Once the function representing the task (such as an interrupt service routine or a step of a task) returns, the scope of the local variable disappears and cannot be used to save the state. Therefore, the task state must be saved using either global variables, static local variables, or dynamically allocated storage. The FreeRTOS method is to suspend the execution of the task function without returning. Multiple such functions without returning can exist at the same time, and then any one of them can be selected to resume execution. How to implement this feature? Please listen to my analysis in the next article.
After reading, you are welcome to communicate with this post and reply to the questions: Please imagine a specific application suitable for implementation by a single-chip microcomputer, describe the software components that can become "tasks", and analyze what benefits can be brought by introducing a real-time operating system into the software of this application.
|