📜 ⬆️ ⬇️

STM32 and FreeRTOS. 1. Fun with streams

This cycle of 5 articles is designed for those who have few opportunities of the usual "tinke" and arduynok, but all attempts to switch to more powerful controllers ended in failure or did not bring as much pleasure as they could. All of the following was written by me many times at the “educational program” of the programmers of our studio (who often confessed that the transition from “tinek” to “tmki” opened up so many opportunities that you find yourself in a stupor, not knowing what to grab on). will be all. When reading it is meant that the reader is a curious person and he could find and install Keil, STM32Cube and press the “OK” buttons. For practice, I use the STM32F3DISCOVERY evaluation board, because it is cheap, there is a powerful processor on it and there is a bunch of LEDs.

Each article is designed for "repetition" and "comprehension" somewhere around the evening of one evening, for a house, family or vacation ...




')
Very often (yes there often, almost always) microcontrollers are used in conditions where it is necessary to monitor several parameters at once. Or vice versa, manage multiple devices simultaneously.

Here is a task for an example: we have 4 exits on which it is necessary to output pulses of different duration with different pauses. All we have is a system timer that counts in milliseconds.

Complicate the task in the spirit of “torturing myself on arduino”. Timers are busy with others, PWM is not suitable, because it does not work on all legs, and you will not usually drive it into the necessary modes. A little thought, we sit down and write about this code

//  int time1on=500; // ,   1    int time1off=250; // ,   1    unsigned int now=millis(); .... // -   if(millis()<now+time1on) { port1=ON; } else { port1=OFF; if(millis()>now+time1on+time1off) { now=millis(); } } 


And one way or the other for all 4 ports. It turns out a decent footwoman on several screens, but this footcloth works and works quite quickly, which is important for the microcontroller.

Then suddenly the programmer notices that at each cycle the port is twitching, even if its state does not change. Rules the whole footcloth. Then the number of ports with the same needs is doubled. The programmer spits and rewrites everything into one function of the PortBlink type (int port num).

Happiness almost came, but suddenly it was necessary that at some port along with the “exit” control something was previously read and the port was controlled based on that read. The programmer is cursing again and doing another function, specifically for the port.

Happiness? But nevermind. The customer has attached something and this can easily slow down the process for a second ... The moaning begins, the programmers once again rule the code, finally turning it into unreadable trash, the managers roll out the wild price lists to the customer for adding functionality, the customer curses and decides to never contact again with embedded solutions.

(such as advertising and praise) And why? Because it was originally made the wrong decision about the platform. If possible, we offer a clever platform, even for primitive tasks. By experience, the cost of development and support will later be much lower. And now for control of 8 outputs I will take STM32F3, which can work at 72 MHz. (in a whisper) In fact, I just have a handy payment with him (smail). It was still with L1, but we inadvertently used it in one of the projects.

Open the STM32Cube, select the board, turn on the tick near FreeRTOS and build the project as usual. We do not need anything like that, so we leave everything by default.

What is FreeRTOS? This is an almost real-time operating system for microcontrollers. That is all that you have heard about operating systems such as multitasking, semaphores and other mutexes. Why FreeRTOS? It is simply supported by the STM32Cube ;-). There are a bunch of other similar systems - the same ChibiOS. In essence, they are all the same, only differ in teams and their format. Here I am not going to rewrite a mountain of books and instructions for working with operating systems, I’ll just run through broad strokes on the most interesting things that help programmers greatly in their difficult work.

Okay, I will assume that they read on the Internet and imbued. See what has changed

Somewhere at the beginning of main.c

 static void StartThread(void const * argument); 


and after all initializations

 /* Create Start thread */ osThreadDef(USER_Thread, StartThread, osPriorityNormal, 0, configMINIMAL_STACK_SIZE); osThreadCreate (osThread(USER_Thread), NULL); /* Start scheduler */ osKernelStart(NULL, NULL); 


And an empty StartThread with one infinite loop and osDelay (1);

Surprised? Meanwhile, in front of you are almost 90% of the functionality that you will use. The first two lines create a thread with normal priority, and the last line starts the task scheduler. And all this magnificence fits into 6 kilobytes of flash.

But we need to check the work. Change osDelay to the following code

 HAL_GPIO_WritePin(GPIOE,GPIO_PIN_8,GPIO_PIN_RESET); osDelay(500); HAL_GPIO_WritePin(GPIOE,GPIO_PIN_8,GPIO_PIN_SET); osDelay(500); 


Compile and fill. If everything is done correctly, then we should blink blue LED (on STM32F3Discovery on PE8-PE15 a bunch of LEDs are soldered, so if you have another board, then change the code)

And now we take and replicate the resulting function for each LED.

 static void PE8Thread(void const * argument); static void PE9Thread(void const * argument); static void PE10Thread(void const * argument); static void PE11Thread(void const * argument); static void PE12Thread(void const * argument); static void PE13Thread(void const * argument); static void PE14Thread(void const * argument); static void PE15Thread(void const * argument); 


Add a stream for each LED
 osThreadDef(PE8_Thread, PE8Thread, osPriorityNormal, 0, configMINIMAL_STACK_SIZE); osThreadCreate (osThread(PE8_Thread), NULL); 

And move the code there to ignite the LED
 static void PE8Thread(void const * argument) { for(;;) { HAL_GPIO_WritePin(GPIOE,GPIO_PIN_8,GPIO_PIN_RESET); osDelay(500); HAL_GPIO_WritePin(GPIOE,GPIO_PIN_8,GPIO_PIN_SET); osDelay(500); } } 


In general, everything is the same.

Compile, fill ... and get a fig. Complete. No LED flashes.

Using terrible debugging using the commenting method, we find out that 3 threads are working, and 4 are no longer. What is the problem? The problem is in the allocated memory for the Sheduler and the stack.

We look in FreeRTOSConfig.h

 #define configMINIMAL_STACK_SIZE ((unsigned short)128) #define configTOTAL_HEAP_SIZE ((size_t)3000) 


3000 bytes for all and every task 128 bytes. Plus, somewhere else you need to store information about the task and other useful things. That's why, if you do nothing, the scheduler does not even start when there is a shortage of memory.

Judging by the fakam, if you enable full optimization, FreeRTOS itself will take 250 bytes. Plus, each task has 128 bytes for the stack, 64 for the internal list and 16 for the task name. We count: 250 + 3 * (128 + 64 + 16) = 874. Even to the kilobyte does not reach. And we have 3 ...

What is the problem? The FreeRTOS version supplied with the STM32Cube is too old (7.6.0) to have vTaskInfo, so I go to the side:

Before and after creating the stream, I set the following (fre is the usual size_t)

 fre=xPortGetFreeHeapSize(); 


We stick breakpoints and we get the following numbers: before the creation of the task there were 2376 free bytes, and after 1768. That is, 608 bytes go to one task. Checking yet. We get the numbers 2992-2376-1768-1160. The figure is the same. By simple logical reasoning, we understand that those figures from the fak are taken for some dead processor, with optimizations turned on and all sorts of modules turned off. We look further and understand that the start of the scheduler eats about another 580 bytes.

In general, we accept for calculations 610 bytes per task with a minimum stack and another 580 bytes for the OS itself. Total in TOTAL_HEAP_SIZE should be written 610 * 9 + 580 = 6070. Let us round and give 6100 bytes - let him eat.

Compile, fill and watch all the LEDs blink at once. We try to reduce the stack to 6050 - again, nothing works. So we calculated correctly :)

Now you can indulge and give each LED its own “pulse” and “pause” intervals. In principle, if you update FreeRTOS or conjure in the code, then it is easy to give an accuracy of 0.01ms (by default, 1 tick - 1ms).

Agree, working with 8 tasks alone is much more pleasant than one with 8 at a time? In reality, in our projects, usually 30-40 threads are spinning. How many programmers would be deaths, if I cram all their processing into one function, I’m even afraid to count them :)

The next step we need to deal with priorities. As in real life, some tasks are “more equal” than others and they need more resources. To begin with, we will replace one flasher with a flasher, but made wrong, when the pause is made not by the OS means, but by a simple cycle.

That is, instead of osDelay (), such a horror is inserted.

 unsigned long c; for(int i=0;i<1000000;i++) { c++; } 


The number of cycles is usually chosen experimentally (for if there are several such delays, then a lot of headaches in the calculations are provided). Aesthetes can read the execution time of commands.

Replace, compile, run. The LEDs are still flashing, but somehow sluggish. Viewing with an oscilloscope makes it clear that instead of even borders (such as 50ms are on fire and 50ms are not on fire), the borders began to float for 1-2ms (the eye, oddly enough, it notices). Why? Because FreeRTOS is not a real-time system and can afford such liberties.

And now let's raise the priority of this task by one small step, before osPriorityAboveNormal. Run and see a lonely flashing LED. Why?

Because the scheduler assigns tasks to priorities. What does he see? That high priority task constantly requires a processor. What is the result? The remaining tasks time to work does not remain.

And now we will lower the priority one step from normal to osPriorityBelowNormal. As a result, the scheduler, giving normal tasks to work, gives the remaining resources "bad."

From here you can easily derive the first rule of the programmer: if the functions have nothing to do, then give control to the scheduler.

FreeRTOS has two options: “wait.”

The first option is “just wait for N ticks”. The usual pause, without any frills: as they said to wait, so much we wait. This is vTaskDelay (osDelay is just a synonym). If you look at the time at runtime, there will be something like the following (we assume that the useful task is performed 24ms):

... [0ms] - transfer control - work [24ms] pause in 100ms [124ms] - transfer control - work [148ms] pause in 100ms [248ms] ...

It is easy to see that, because of the time required for work, the transfer of control does not occur every 100ms, as it might be initially assumed. For such cases, there is vTaskDelayUntil. With it, the time line will look like this.

... [0ms] - transfer of control - work [24ms] pause in 76ms [100ms] - transfer of control - work [124ms] pause in 76ms [200ms] ...

As you can see, the task gets control in clearly defined time intervals, which is what we needed. To check the accuracy of the scheduler in one of the streams, I asked to make pauses of 1ms each. In the picture you can estimate the accuracy of work with 9 threads (we don’t forget about StartThread)



At this point, I usually finish, because people are so immersed in the game with priorities and finding out when it breaks, that it’s easier to shut up and have fun.

Fully assembled project with all sources can be found at kaloshin.ru/stm32/freertos/stage1.rar

Continued Part 2. About semaphores

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


All Articles