📜 ⬆️ ⬇️

STM32 and FreeRTOS. 2. Semaforim black

Part one, about streams

In real life, it often happens that some events occur with different periodicity (or they may not occur at all). For example, ordering juice at McDonalds, pressing a button by the user or ordering skis at the box office. And our powerful microcontroller should handle all this. But how to do it most conveniently?


')
Let's observe the process of ordering juice in McDuck. The buyer says “I want juice”, one seller punches the cost into the check, and the other looks at the small screen and goes to pour the juice. Poured, brought and again looking at the screen, what to bring. And so it goes on endlessly or until the end of the shift. We replace human labor with machine, so we automate it!

An ordinary programmer, having read the previous article, happily sits down and writes approximately such code.

bool sok=false; void thread1(void) { for(;;) if(user_want_juice()) sok=true; } void thread2(void) { for(;;) if(sok) {prinesi_sok(); sok=false; } } 


I think the logic is clear: one thread controls the situation and sets the flag when it is necessary to bring juice. And the other controls this flag and brings juice if needed. Where are the problems?

The first problem is that the one who brings the juice constantly asks “should I bring the juice?”. This annoys the seller and violates the first rule of the programmer of embedded devices "if functions have nothing to do, then give control to the scheduler." It would seem, well, stick an osDelay (1), so that other tasks would work out or just lower the priority and let it spin, because the iron piece will withstand ... That's just the point that it won't. Overheating, lack of resources and so on and so forth ... These are not big computers - sometimes the whole board is less than the smallest computer cooler.

And the second problem is buried much deeper. The point is compilers, optimizations and multithreading. Here's an example: we have a juice picker so smart that it can serve three sellers at once. In the end, it can easily turn out to be a classic “race”, when the processes are distorted among themselves.

Seller1 (hereinafter P1) “I need juice!” Sok = true;
Sokonovets (hereinafter C) (waking up) "Oh! Juice need go
P2 “I need juice too” sok = true;
P3 "Me too!" Sok = true;
C (brought juice), P1 - on you juice. sok = false;

P2 and P3 in sadness.


The programmer thinks, thinks and comes up with a counter instead of a logical flag. Looks what happened.

P1 “Soku!” Sok ++;
C "Wait"
P2 “Soku!” Sok ++
P3 “I also have two juices!” Sok ++; sok ++;
S -P1 "on the juice!" Sok--;
C (sok> 0?) "Oh, still necessary!"
C - P2 "hold" sok--;
C "and still need to?"
C-P3 "Wellcome" sok--;
C - and I wait a second
C-P3 "pzhlsta" sok--;
C "Oh, no one needs juice anymore, I'll go to sleep."


The code works beautifully, clearly and in principle, there should be no errors. The programmer closes the task and proceeds to the next. In the end, before the next build of the project, there is a shortage of resources and someone tricky (or smart;) turns on optimization or simply changes the controller to multi-core. And that's all: the task above stops working as expected. And that the most disgusting, sometimes it works as expected, and under the debugger in 99% of cases, it generally behaves perfectly. The programmer cries, sobs and begins to follow the shaman's advice like "declare a variable as static or volatile".

And what happens in reality? Let me drink a little.

When the juice bearer performs the operation sok--; in reality, the following happens:

1. Take the temporary variable value sok
2. Reduce the value of the time variable by 1
3. Write the value of the temporary variable in sok

And now we recall that we have a multi-threaded (and possibly multi-core) system. Threads can actually run in parallel. And therefore, in reality, and not under the debugger, the following happens (or the seller and sokonocets simultaneously turned to one variable)

C. Take the temporary variable value sok
P. We take in the temporary variable 2 the value of sok;
C. Decrease the value of the time variable by 1
P. Increase the value of the time variable 2 by 1.
P. Write the value of the time variable 2 in sok
C. Write the value of the time variable in sok


As a result, in sok we have absolutely not the value that we expected. Having discovered this fact, the programmer tears on a sweater with deer, exclaiming something like “I read about it, how could I forget!” And wraps the code for working with a variable in a wrapper, which prohibits multi-threaded access to this variable. Usually people insert a task switching ban and other similar things like mutexes. Starts optimization - everything works fine. But here comes the architect of the project (or the main techie at studiovsemoe, that is, I) and gives the programmer the head, because all such prohibitions and others very much squander the performance and derail almost everything that is tied to time gaps.

It would seem an almost hopeless situation, because you will have to rewrite a bunch of code, and even not to violate the first rule of the programmer ... But I usually suddenly get good and tell what you are reading now.

In any decent OS there are semaphores. There are two types of semaphores in FreeRTOS - “binary” and “countable”. All of their differences in that binary is a special case of the counting, when the counter is 1. Plus, the binary is very dangerous to use in high-speed systems.

What is the advantage of a semaphore versus a regular variable?

First, you can ask the task scheduler not to allocate resources to the stream until the semaphore changes its state. That is, the juice bearer will no longer terrorize the seller “do they need juice?”, But will sleep somewhere in the back room until someone needs juice.

And secondly, the task scheduler performs all the necessary checks to preserve the integrity of the semaphore and its values. That is, it is possible to run several juice bearers on one vendor, who haul juice at different speeds and they will not distort themselves and bring more juice than necessary.

What is the problem with high-speed systems and binary semaphores? In principle, the situation is completely analogous to the first example about the juice beetles, only turned upside down.

Imagine that the controller in McDuck took the braked boy (hereinafter referred to K). His task is simple - to calculate how much juice ordered.

P1-S "Juice!"
K - “Oh, ordered juice, you need to draw one!”
P2-S "Juice!"
P3-S "Soku!"
(all this time K, sticking his tongue out, draws a one)
K - So, the one painted, waiting for the next order.


As you know, the data at the end of the day do not converge at all. And that is why in our studio it is forbidden to use binary semaphores as a class - the speeds of the controllers are always different (the difference in speed is STM32L1 / MSP430 at the minimum frequency and STM32F4 at the maximum more than two orders of magnitude, and the code works alone), and catching such errors is very difficult.

How do countable semaphores work? Take, for example, all the same McDuck, sandwich making department (or what is the word called hamburgers with big maks?). On the one hand, there are a lot of sellers who sell big maks. Bigmaks are sold one, two, or ten at a time. In general, you can not guess. On the other hand, there is a bigmaker. He may be alone, young and inexperienced, or he may be full-grown and fast. Or all at once. The seller does not care for this - he needs a big Mac and who will make him anyway. As a result:

P1 "Need 1 bigmak" (puts the semaphore in 1ku)
D1 "Ok, I can do 1 bimak." (young, closer, removes semaphore to 0)
P2 "Need 3 bigmak" (increases the semaphore by 3)
D2 “Ok, I can make 1 more Big Mac” (following in the waiting line. Semaphore in 2)
(here comes D1)
D1 "made bigmak, another one I can do" (semaphore 1)
D2 “OK, I made mine, I'll make one more.” Semaphore 0
(comes back, he is fast)
D2 “Still need Bigmaki? I'll wait for 10 ticks, if not, I'll leave. "
D1 “Everything, did. Wake up how it will be necessary "(the scheduler slows down the thread)
D2 "Why not? Well, I left. I'll take a look at Nts tikov "


As a result, one person can do bigmaks, or 10 people can - the difference will be only in the number of bigmaks produced per unit of time. Okay, enough about Big Macs and McDonalds, you need to implement all this in code. Again, we take the fee and the code from the last example. We have 8 LEDs that flash differently, at different speeds. So let there be one made “syrg” equal to one “sandwich”. There is a user button on the board, so let's make it so that one click would require 1 “sandwich”. And if we hold the button, let them demand 5 “sandwiches” per second.

Somewhere in the "global" code create a semaphore.

 xSemaphoreHandle BigMac; 


In the StartThread thread code, we initialize the semaphore

 BigMac = xSemaphoreCreateCounting( 100, 0 ); 


That is, the maximum we can order 100 bigmaks, and now they need 0

And change the code of the infinite loop to the next

 if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0)==GPIO_PIN_SET) { xSemaphoreGive(BigMac); } osDelay(200); 


That is, if the button (and it is on the board is aimed at PA0) is pressed, then every 200ms we issue one semaphore / require Big Mac.

And for each blinking code LED add

 xSemaphoreTake( BigMac, portMAX_DELAY); HAL_GPIO_WritePin(GPIOE,GPIO_PIN_15,GPIO_PIN_RESET); ... 


That is, we are waiting for the BigMac semaphore before opupeniya (portMAX_DELAY). You can set any number of ticks or use the portTICK_PERIOD_MS macro to set the number of milliseconds to wait.

Attention! In the code, I did not introduce any checks to improve its readability.

Compile, run. The longer we hold the button, the more LEDs flash. Letting go - stop. But one, the fastest (and the longest in the queue) I did not blink - he simply does not have orders. Ok, I increase the speed to 50ms for each sandwich. Now orders are enough for everyone and everyone is blinking. You release the button - they continue to flash for a while while making orders. What would have been absolutely good, I allowed to order bigmaks as much as 60 thousand (it is possible before unsigned long) and the order period set 10ms.

Now everything became quite beautiful - clicked, LEDs blinked. The longer you hold the button, the longer the LEDs flash after you release. Complete analogy of real life.

To continue the analogy with real life, remember that there are always some sandwich pickers in McDuck. That is, the seller can, without turning around, give up “you need a sandwich” and someone will make it. And if this is the usual dining room in non-lunch time? There, the cashier can even fan himself - no one will simply see, for everyone except her is watching another series. The cashier needs to understand what the visitor who wanders at an inappropriate time wants and shout something like "Tatyana Vasilyevna, please go out, you need to pour the soup here."

For such address cases, it makes no sense to use semaphores. In the old versions of FreeRTOS, it was possible to simply wake up the task through the API (“there is soup there”), and the new ones have a vTaskNotify call (the only difference is in the passed parameter “there is soup of borsch class”), which is completely analogous to semaphores, but targeted. Compared with the usual promise of a wild performance increase, but at the moment we have not carried out large-scale tests.

There is another subspecies of semaphores - mutexes (mutex), but these are the same binary semaphores. And recursive mutexes are countable semaphores. They are made absolutely in the same way, they work in exactly the same way, only “it is possible to do” the state of them is not “more than zero”, like in ordinary ones, but “only zero”. Used to separate to resources and variables. I propose to come up with examples of application themselves (For some reason, everybody comes up with a story about the toilet and a key. Never was it about the “flag of the leader” or “company seal.” Apparently, the specifics :)

The result of the code is easier to show on the video than to describe in words.



At this stage, people start to argue about the applicability of the semaphores in the already written code, and usually comes to the fact that in FreeRTOS event flags / bits / group is called. After a short googlezh on this topic, programmers diverge contented and peaceful :)

As usual, the full code with updates from the post can be found here kaloshin.ru/stm32/freertos/stage2.rar

The next part, about the queue

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


All Articles