📜 ⬆️ ⬇️

A little more about multitasking in microcontrollers

In the last note, it was about how, according to the author, it is possible to program the usual actions of the microcontroller in real time, dividing them into several independent (or almost independent) tasks.


A microcontroller was chosen, with the core of the very common ARM Cortex M family. Of the many that were familiar, and not just the author, the options with numbers 0,3,4 and 7 were chosen as M4, because it was at hand.


The two considerations that prompted the “invention of the bicycle” to become slippery and wobbly, as some readers wittily remarked, were in fact simple. The first was that we still have to live with these "cortex". And the second is to try not to do something universal (gaining fame and wealth), but to do something more narrowly focused, hoping to achieve efficiency and simplicity. Those who sometimes do something with their hands will easily remember that, as a rule, a specially selected screwdriver fits better than the one taken from the brilliant universal set.


An example of an assembler was given in order to show that no more than 80 clock cycles are spent on switching. And at 72 megahertz clock frequency a little more than 1 microsecond is obtained. So, a tick of 50 microseconds will not be so expensive. Only 2 percent overhead. Therefore, as one of the author’s favorite characters said, “it is desirable to suffer.”


So, we have N tasks, each of which is guaranteed to work a segment (tick T) of time and guaranteed repetition of this segment no later than through (N-1) T ticks plus a delay not exceeding D. This unfortunate delay, fortunately , is limited to the maximum possible size in time, which is equal to the sum of the durations of operation of all allowed interrupts. In other words, the task for which there are any possible interruptions for a given period before the next receipt of a tick is most unlucky. For a longer time, the task cannot be delayed. It will inevitably receive its time slot no later than in (N-1) T + D microseconds. In my opinion, this is called hard real-time.


Tasks must complete tasks and report on performance. To report to? Apparently, to someone in charge, and as a rule there are usually fewer of them than subordinates (to tell the truth, the author met exceptions, when there were four chiefs with three left-handed workers, of whom only one knew and respected the word “ adequacy").


And if “you are many, and I am one”, then this means a queue. Many will begin to push and try to slip. And someone will have to wait, and then be late and explain. Despite the fact that all this looks terrible, it is called beautiful: the struggle for the resource. Queues are a well-known solution. I knew many people who don’t feed them with bread - let them stand in a queue.


But we can not wait! In a sense, a task. They are from difficult real time. Suppose two tasks read the readings once in one second, and the third one should measure something every 10 milliseconds, put them in a pile, and report to the top. And she said: "Abazhzhite, we are not finished with the chefs."


Apparently, it is necessary to turn, to put it mildly, to a not quite real time (soft real-time).


Let us have a special task that knows how to wait and likes to do it. The resource that she will serve will be the communication channel. As you know, you can't push everything into it at once.
But, you can immediately estimate what speed the channel should have so that nothing is lost. To do this, you need to know with what performance all our graphomania, ugh, tasks work. Obviously, it is also necessary to calculate the size of the buffer or buffers, from which all the parcels will be sent up (or to the right).


If the channel is not one, then the essence does not change. A separate task is simply added for each channel, which is designed to wait (and, of course, hope and believe).


A few words about the communication channel with the operator, or more simply, with a person. Here the channel is bidirectional, but the outward direction is most interesting. Immediately make a reservation, there is one circumstance which, even with a strong desire, cannot be excluded. This is channel overload. When testing, we must reach it and must have a mechanism to see its advance. I do not argue, it is not good to deceive, but it is possible to keep back a little. Vaughn, Gerasim, this is generally abused.


Therefore, we will immediately assume that the message to the operator from the task may be lost. And so that a person knows about this, we will number them. This will allow us to determine where and how many times our operator is left with nothing. In the end, you can always podrichtovat something in the code, or add to the calculations, or even attach to the electrical circuit to correct the situation. It seems, for the time being, it will be easier. But, of course, this is not necessary for combat applications. To be honest, the loss of the message does not look like a disgrace only when debugging.


For example, suppose we have a duplex serial interface without handshaking at a speed of 115200 baud. For example, RS422 in the configuration “economy” two wires - there, two - back. Its capacity is approximately 10,000 bytes per second. We take the average message size for a person to be 50 bytes. We receive 200 messages per second or one message in 5 milliseconds. If we have three tasks, they want to communicate something, then let them do it every 15 milliseconds each. On average, of course. And if not on average, it will require serious statistical calculations or a full-scale experiment. Choose the latter. After all, we have learned to detect the loss of messages and see everything on the screen of the terminal emulator.
So, let three tasks create individual messages. Let the messages differ in importance or urgency of the content and our tasks put them in the appropriate buffer. Select these three ring buffers for three levels of urgency as shown in Figure 1.



The fourth task chooses a message from these buffers according to our approved plan and tries to deliver it for dispatch. If while sending is not possible, then the fourth task estimates how much she can sleep and does it. After sleep, she already has the necessary space in the circular buffer for shipment.


In buffers of different urgency, of course, not the messages themselves are stored, but their addresses (links). At the same time, the tasks themselves do not need to wait at all. Fine? No, not at all. It does not work, and here's why. Each of these three ring buffers is a shared resource. Imagine task 1 going to put the address in the middle buffer. She reads the word, checks that the place is empty, that is, the value is zero and (at this moment it is replaced by another task 2, which wants to do the same and it succeeds), the first task, returning, puts the word there, rubbing everything that succeeded second. Here is a colleague asking for words. I seem to know what he will say.
Yes, everything is very simple, it is possible to prohibit interruptions during the test and nothing bad will happen, this is just for a short while.
- Right, not for long, but how many times? How much time do we take the task? And which one of them? I forgot to warn, we never prohibit interruptions; our hard real-time sect forbids us to do this.
-And if you do not prohibit interrupts, then you can ask our task switch to put the address of the message there. He can do it atomically.
Yes, maybe, but then you will want to ask him about something else, then another. And why then did we achieve 72 degrees, then to dilute everything with water? Sorry, I meant 72 clocks on context switching.
Let's try to do it easier, as in Figure 2.



In this case, each task has its own buffer or its own set of buffers, if you want a different urgency, bombast and importance. Personally, I, as a simple operator, still have the same importance for everyone.


Such a scheme does not force to fight for the resource. Now we have a completely working version. One thing I don’t like. But what if the tasks on the left in the picture have nothing to send? Then the task to the right would be wiser to ask her to be woken up when the reason appears, and not to wake up by herself just to set the alarm again. The tasks on the left are more convenient to do this. In addition, the last post mentioned a function that helps wake a friend.


I foresee the rationalization proposal: “Let the interruption from the serial port (UART) itself be engaged in what task 4 is now doing, there will be savings”. This can be done, but I do not think it is good. I will try to explain. The tasks on the left can indeed activate the UART interrupt procedure themselves, and it will start working and will not stop until everything is done. The interrupt procedure should now do everything that task 4 had done before. The time taken to process the interrupt will swell, no task will be able to turn on until the next "spree." And what shall we say to our comrades from the stubborn real-time circle? But we were told that the response to any external interruption should be as short as possible. This is just a good tone. Or, in other words: you have to do well, badly and without you.


Figure 3 explains what the procedure is and where the calls are located.



We now turn to the situation, one might say, a mirror. This is when information comes from outside. Let it be an SPI channel with several gondoliers with gondolas and a small amateur string orchestra. No, it's too early to think about rest, it's not time yet. We leave only the SPI interface and a few chips. For example, atmospheric pressure sensor, accelerometer and stored memory.


I'll tell you right away - a stupid example. Not because of the gondoliero with their eternal “ought to add, sir”. No, stupid, in fact, to mix in one interface such different in importance input data. Indeed, if you need to know the acceleration, then, for sure, in order to quickly estimate when to remove the gas pedal, or turn the flaps, or squeeze my eyes, finally. This information is often needed. But the pressure, it changes slowly and will have to fly down three meters, so that in the lower ranks warmed life.


As for the stored memory, and who even planted it on this SPI? And, is there no second SPI? And not foreseen? Nowhere to go, something needs to be done. Redirect the arrows in the opposite direction in Figure 2 and begin to think.


Task 4 now serves SPI and wakes up only by its signals. Her connection to task 1, which wants to put something in a stored memory, is directed outward and is carried out through a queue. It also requires a mechanism to monitor the ring buffer overflow. Task 4 should provide the extraction of acceleration and pressure values ​​without the participation of two consuming tasks. Just need to spin and have time. Now we can jot down an explanatory picture and write an explanatory note. In Figure 4, these
actions are shown schematically (or block diagram).



Underflow check — these actions help you know if the acceleration value has time to change before the consuming task reads them again. This test is shown by a separate action in Figure 4 only to focus attention on it. In fact, this step occurs along with the reading of the accelerometer value according to the scheme, as shown in Figure 5.



It should be noted that there is a shared resource here, since the place where the result is stored is also an indicator of actions (semaphore). Racing is possible here, speaking the language of circuitry, but for us this is not an omission. After all, slipping into a closing transport door only in life can be counted as luck. Here we will surely be considered late.


Memory handling occurs in portions in order to limit each such step in time. Thus, we will provide a uniform reading of the rapidly changing acceleration value, and in the interim we will have time to take care of the rest.


Well, now it remains to find something suitable from iron and experiment as it should. This, I think, will be the next story.


')

Source: https://habr.com/ru/post/456242/


All Articles