“There are many of you, but I am alone!” Is the classic phrase of the saleswoman, who was terrorized by the buyers with the questions “Is there ...?”. Here and in microcontrollers completely similar situations occur, when several threads require attention from some slow thing, which is simply not physically able to serve everyone at once.
Let us take the most vivid and problem-rich example in which most inexperienced programmers “fall down”. There is a powerful and fairly fast microcontroller. On one side, the com-port adapter is connected to it, through which the user issues commands and receives results, and on the other side, a stepping motor that rotates at some angle according to these commands. And of course, a cool button, which also means something for the user. Where can I catch problems? ')
Let's go by the user. Com-port, or USART (universal synchronous / asynchronous receiver / transmitter) is a very delicate and capricious thing. The main whim is that it can not be left without attention. For one simple reason - to save conclusions, in 99% of cases, only transmit and receive signals are output, leaving transmit and receive permission signals (rtr / rts, dtr, cts, etc.) overboard. And it is worth the microprocessor to hesitate just a little, as the character will be lost. Judge for yourself: the magic numbers 9600 / 8N1 tell us that 9600 baud flies per second, and one character is encoded with 10 (1 starting, 8 data and 1 stop) pulses. As a result, the maximum transmission rate is 9600/10 = 960 bytes per second. Or just over one millisecond per byte. And if on TZ the transmission rate is 115200? But the microcontroller must also process this data. It seems that everything is bad, but in reality, a lot of devices work and do not cause problems. What solutions apply?
The most common (and correct) solution is to relocate all the work on receiving or transmitting characters on the shoulders of the microcontroller. Now everyone has learned to make a hardware usart port, so a typical solution in most cases looks like this:
Where is the problem? First, the problem is in calling startProcessing. It is worth this function at least a little delay in the work, as the next character will be lost. We take STM32L1 (which at the minimum frequency manages to process only 84 commands in 1 ms) and with more or less razysyushchem logic the resulting construction will lose characters.
After a friendly pat on the head in our studio, the programmer rewrites the code so that instead of calling startProcessing, the semaphore rises, which would start the processing of the received data. And then he got the following problem in full growth: while startProcessing is grinding something out of the buffer, the processor of the new symbol starts to overwrite the buffer that has not yet been processed. After the next torn sweater and faded book on the buffers, the programmer implements a ring buffer with pointers, than he digs into the problem even deeper.
Usually somewhere in this place I notice the surprised eyes of the people and the indignant cries in the spirit of "well, such algorithms work in a bunch of projects and nothing, no problems." Yes, they work. But in what projects? Only in those where all communication with the controller can be transferred to half-duplex mode, as in radios. Here is an example of user (P) and controller (K) dialog.
P: ATZ (Controller, are you alive?) K: OK (Yeah, I'm here) P: M340.1 (Do something). (there may be a pause, sometimes very large) K: ok (made type)
Where are the problems? First, you cannot send multiple teams in a row. This means either it will be necessary to change the logic or make complex commands with several parameters. Secondly, there can be no other requests and responses between the request and the response. But what if the user presses a button while moving? What to do? There is only one way out - to use the native nature of the port, namely, its asynchrony. As a result, the dialogue between the user and the controller looks like this.
P: ATZ (alive?) K: OK (aha) P: M340.1 K: K1 = 1 (button pressed) : 333,25,2 (silence) K: E = 1 (task E completed) K: M = 1 (task M completed)
Of course, the logic of processing such a stream becomes a bit more complicated, but thanks to such asynchrony we immediately get many advantages over the “classical school”.
First, the responsiveness of the user interface dramatically increases. Well, a motor works there somewhere or something is considered, but this is not a reason to “freeze” or “slow down”. And when the customer realizes that the size of the brakes practically does not depend on the power of the controller, he has reasonable questions ... Secondly, this behavior is closer to the behavior of a small boss in real life. And copying this behavior is very easy - everyone saw it, everyone participated. Finally, we divide the flow of control into “command” and “status”. As a result, the implementation of any indicators of the volume of the task or turn angles in real time is not difficult.
And now with all these ideas take a look at the opposite side - on the side of the performer. It is slow enough to simply not have time to process the commands in order. And the start-stop time of the motor is very large, so it would be good to do a primitive optimization in the spirit of "if two teams arrived on a turn in one direction, then turn it at a time."
What does a regular programmer do? Since he read the previous articles and a handful of books, he draws logic, setting the semaphores as necessary, and uses mutexes to block simultaneous access to the little motor. All is well, but the code is cumbersome and hard to read. What to do? At studiovsemoe.com we use Sharikov’s recipe: “In line, you sons of bitches! In queue!". Again, the concept of the queue is absorbed almost with mother's milk, so there are no problems with understanding.
In this example, you can simply create three queues. The first is the commands received from the user. Everything is thrust into it (well, or after a minimal check), which is accepted from all input ports. The second is the data that must be given to the user. The state of the buttons, the results of calculations and so on. And finally, the third stage is used for tasks for a motor / reader / someone else. And between these queues there are threads for converting data from the user into tasks for the reader. Since FreeRTOS has official functions for “peeping” inside the queue, it is easy to make optimization for cases when the next action continues / repeats the current one.
So, only three turns, and a wild bunch of problems solved. First, there is not even the potential problem of losing or rewriting the buffer of received characters. True, the result will be a little more memory consumption, but this is an acceptable price. Secondly, there are no problems with the conclusion. All processes are simply written in one queue, but how to deduce, in what format, etc., is no longer their concern. It is necessary to arrange the output in a frame - we rewrite one function, but not all that can output something. Again, there are no problems with simultaneous / overlapping output (when one thread prints 12345 and the other qwerty, but the user gets something like 1qw234er5ty). Finally, thanks to this approach, it is very easy to divide the tasks into smaller ones and scatter them into threads / cores of the microprocessor. And all this means faster development and lower support costs. Everyone is happy, everyone is happy.
In order not to depart from the practice of flashing LEDs, we will make the following demonstration: we will create a queue from which an element will be picked up once a second (imitation calculation). At the press of a button, an item will be placed in the queue twice a second (control commands). Well, the LED will show the queue load. Again, I warn you that the code to improve readability is written without error handling, so be careful.
Somewhere in the beginning of the code define the queue
xQueueHandle q;
The ignition code of the LEDs will change according to the principle “there are more N elements in the queue? we light, if not, then no