How to work with a Real Time Operating System and is it any good? (FreeRTOS, ESP32)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
Using a real operating system to simplify  programming with the Arduino IDE.   Is this possible and how?  Let s have a closer look!  Gr ezi YouTubers. Here is the guy with the Swiss  accent. With a new episode and fresh ideas around   sensors and microcontrollers. Remember: If you  subscribe, you will always sit in the first row.  Operating systems were invented to simplify our  lives. But, because they need a lot of resources,   they only run on reasonable computers like the  Raspberry Pi or a PC. Right? Wrong. Nowadays,   we also get operating systems running on our small  MCUs. Particularly interesting in this respect is   the ESP32 because it has enough power and memory  to accommodate such an additional burden. And the   best: It already runs a version of FreeRTOS with  all our Arduino sketches, and it is easier than   you think. Is this useful or a pain in the a ? To answer this question,  - We will start with a short introduction to  multitasking and real-time operating systems  - Then we will use a real project to understand  how to structure projects using freeRTOS   and where the differences are to standard sketches - We will have a look at the three most important   concepts: Task scheduling, queues, and mutexes - We will see the advantages as   well as the caveats of using RTOS Let s start with multitasking operating systems.  Back in the day, computers were costly. This is  why engineers were asked to find ways to share   these machines among many users. So they invented  a thing called the multi-user operating system.   No, resource sharing was not invented by Airbnb  and the sharing economy. Time-sharing already   existed in the sixties of the last century. The computer time was chopped into slots, and   different slots were assigned to different users.  Because these slots were very short, each user   did not know that its program was interrupted by  other users. At least when response times were ok.   So, a computer was able to run  several user tasks like in parallel.  Operating systems had to ensure that the  shared resources were managed correctly   and that data of the different users were held  separately. Shared resources were, for example,   printers. If two users wanted to print to  the same printer, the operating system had to   schedule one printout after the other. Otherwise,  it would have printed a mixture of two documents.   But we do not want that many users can work  with one ESP32! Why do I tell you this story?  If you allow one user to get more than one time  slot, he can attach different tasks to slots.   Then he has a multitasking operating system  that runs many tasks in parallel. Exactly   what we need because most of our projects  have some tasks running in parallel.  Then came multi-processor computers that  allowed the operating system to distribute   tasks not only to time-slots but also to different  processors. Current computers and Smartphones   all run multitasking and multi-processor  operating systems to allow us to use many   applications in parallel in an optimized way. If you type htop on your Raspberry Pi, you see   how this is done: A ton of different tasks run  in parallel also if you are the only user. And   you see that these tasks are distributed to all  four cores. This is because, more and more, the   operating systems execute tasks like networking  in the background. In the background means, their   priority is lower, and they only run whenever  the higher priority tasks wait for something.  If you want to control machines, a fast reaction  time is needed, and waiting for the next time slot   is not an option. Your computer has to react in  real-time. These specialized operating systems are   called real-time operating systems or RTOS. They  are multitasking but often not multi-user systems.   Standard Linux or Windows, BTW, are not  real-time operating systems and should   not be used for time-critical applications. And now we come to our Arduinos. They have   a single CPU or core, do not use an operating  system, and we only can run one sketch at a time.   At least, using interrupts, they can react nearly  real-time to events. But basically, they are where   computers were in 1960. Not satisfactory!  As usual on this channel, we want more!  Of course, we can write sketches that behave  like multitasking systems. To achieve this goal,   we have to write non-blocking code. This means  we must not use delay() statements because they   would block the Arduino from executing a parallel  task. Also while loops are hazardous because they   can loop for a long time and block other tasks. Normal Universe, for example, shows us in a video   how to do that using a state machine . It needs a  lot of additional code, and the sketches are hard   to read because they are a mixture of tasks and  operating system. . For this programming style,   your thinking has to be very different. I compare  writing such code with turning the inside of a   t-shirt out. It works, but it does not look good. https://www.youtube.com/watch?v=v8KXa5uRavg  As said before, the ESP32 offers  a much better possibility:   We can use the built-in FreeRTOS.  And we still can use our Arduino IDE.   A well-kept secret for many Makers. In video #168,  I shortly used it but did not mention the fact.  From our historical trip, we already  know what an RTOS has to offer:  - Time slot management to run tasks in parallel - Protection that tasks are kept separated  - Task-to-task communication - Sharing of common resources like a display  - Fast reaction time using interrupts - Background process execution using priorities  FreeRTOS is open source, runs on many different  MCU architectures, and offers a lot of those   functions. I leave a link to a much deeper  tutorial from Digikey if you are interested.  Fortunately, we can build quite complex  programs using only a handful functions.   So, which are these essential  functions, and how are they used?  BTW: Why does ESP32s have a built-in RTOS?  Because WiFi and networking are time-critical,   Espressif decided to use freeRTOS to manage those  tasks. It runs on Core0, and our Arduino sketches   run on core 1. We all remember that the ESP8266  crashed if we did not put yield() commands in the   right places. Because the ESP8266 had no RTOS  and only one core, our code could block WiFi   and crash the MCU. Adding a yield() statement here  and there gave WiFi the possibility to take over.  With the ESP32, WiFi always runs in parallel to  our sketches, and we do not have to care. Our   code can block as much as we want, and WiFi still  works a significant advantage because of FreeRTOS.  Enough theory. Let s build  something to see how it works.  I use my trusted Morse Trainer to show you  how to build a project using RTOS. BTW:   This trainer is the root of this channel because  it was featured in the first episodes. But why do   I dig this example out? It probably would have  been better for my image if I kept it buried!  After the specification of the project, you  will understand. As Albert Einstein said:   If I had an hour to solve a problem, I d  spend 55 minutes thinking about the problem   and five minutes thinking about solutions. Here  you see that he was a physicist, not an engineer.   We usually do it the other way round. My Morse Trainer consists of a loudspeaker,   a box, and a keyboard. Three things have to run  in parallel: A generator produces Morse signals   for the loudspeaker and is time-critical  to the millisecond. If not executed well,   it could result in a harsh reaction. More than  100 years ago, they invented Q-codes to abbreviate   common sentences and save transmission time.  QTH? For example means, What is your position?   And QLF means "Are you sending with your  left foot? Invented for poorly timed signals.   Don t tell me these guys had no humor. So this task for sure has to run   separated from the rest. We call it morseTask. A receiver task has to wait for keyboard entries:   As soon as the trainee hears a letter in Morse, he  has to press the correct key on the keyboard. This   task then has to decide if the entry was right or  wrong and, based on this knowledge, adapt training   in real-time to make the trainee always sweat  a little but keep his or her motivation high.   It is the drill sergeant. If the trainee makes  many mistakes, the training speed has to be   lowered and, without errors, the speed has to  be increased. If a letter was not or wrongly   recognized, the trainer increases its occurrence. A genuinely individualized training!   Increasing training speed is the only compliment  this trainer can give. Everybody who was in   military service knows what I am talking  about. If you are good, you get more work!  This is the second task. It has  to react fast on keyboard entries.  A third coordinator task oversees the training  from start to end and generates new letters.   This task is not time-critical. You see, a ton of problems to   solve. How can we solve them using RTOS? We need three tasks which have to run in parallel.   And we have to have streams of letters between the  different tasks a perfect application for queues.   One queue is between the Morse task  and the receiver task, and a second   one between the Coordinator and the Morse task. The trainer has several states like initializing,   training, traineeLost, and endOfTraining. These  states can be changed by two tasks, and therefore   are a shared resource that has to be protected. Do  we have other shared resources? As we saw before,   the keyboard will be used by the receiver and the  coordinator task. The same applies to the display.  So we need three tasks, two  queues, and three shared resources.  Now we can start programming. I decided  to use Visual Micro for this project   because I want to use a hardware debugger. But  you can use the Arduino IDE without problems   for your RTOS projects. It uses the same code. Our sketch looks like all other sketches: Setup   and loop. There is a secret we never cared  about: Setup and loop also are RTOS tasks.  Our three tasks are created here as the last  step in setup(). They immediately start to run.  Now, our ESP32 runs more than five  tasks in parallel: The network process,   Setup, Loop, Coordinator, Receiver, and Morse. Our three tasks get the same priority,   a decent amount of private stack memory, and  are pinned to core 1. I do not want to fight   with the WiFi tasks and therefore leave core  0 altogether to RTOS. We will later see that   the ESP32 is fast enough to run the whole  application on one core without problems.  Why did I assign the same  priority to all my tasks?   To reduce complexity. I strongly suggest assigning  the same priority to tasks if one task can   be executed during one time slot. Otherwise,  you quickly get nasty things like deadlocks.  As said before, the RTOS scheduler  chops the processor time in slices   and assigns it to tasks. It uses the round-robin  concept, where each ready task gets its slot. One   after the other if they have the same priority.  If not, the task with the higher priority   always gets the next available slot. Important:  Unless it gives control back to the scheduler,   a task runs during its entire slot. The duration  of a slot on the ESP32 is one millisecond, BTW.  Now comes the best: You can treat each task  as it would run alone on an ESP32. Even if   your code seems to block like in this while loop  waiting for a keyboard entry, the scheduler will   interrupt it and assign the next slot to another  task. This leads to simple and readable programs   and is very convenient for the programmer. Let s look at the Morse task. It reads a letter   from the morse queue and switches the Morse signal  on or off. Let s assume it has to send an E at a   speed of 60 WPM. This means it has to switch the  signal on and wait for the next 66 milliseconds.   Because we can program as if it would be the only  sketch running on the ESP32, we use delay(66) and   jump back to the beginning, where it automatically  switches the signal off and waits for another 66   ms. This creates no harm because the receiver  task still gets its time to read the keyboard.  In RTOS, we use vTaskdelay() because it signals to  the RTOS scheduler that the Morse task will not be   ready for the next 65 ms. So the scheduler can  use these 65ms for other tasks. Very efficient!  As soon as the scheduler gets control, it  selects the next ready task. For example,   the coordinator task that creates a  new word and stuffs it into the queue.   After its execution, there is no need to wait,  and it gives control back to the scheduler using   taskYIELD(). The next task is the receiver task  that waits for the keyboard. And so on. When the   66 ms are up, the Morse task gets a slot again. From a user s perspective, all three tasks run   in parallel. Which is precisely  what we want. And the programmer   wrote three nearly independent programs.  FreeRTOS does the rest. How cool is that?  What we nearly forgot: The scheduler also  gives slots to the WiFi and other tasks on   core 0. And also to loop(), which also runs  on core1 and just handles OTA in our case.  Now we know how to deal with tasks and can go on  with queues. They conveniently pass information   from task to task. They usually are FIFO, first  in, first out, and have a defined maximum length.  Let s look at morseQueue between  the Coordinator and the Morse task.  How does the Morse task read  the queue? With this command.   morseQueue is the queue's name, letterForMorse  the letter to read, and portMAX_DELAY means   that the sketch waits forever if the queue  is empty. Again, blocking code without harm.  And how can the Coordinator fill the queue?  With this command. Again we see the queue name,   the letter, and portMAX_DELAY. Here, the  task has to wait when the queue is full.   You also see a significant advantage of these  queues: We can fill a whole word at a time if   there is enough space and do not have to care  about queue management. Really convenient!  But if we have to transfer more than one  variable from one task to the other? For example,   I want to transfer the sent letter as well as its  transmit time from the Morse to the Receiver task.   Do we need two parallel queues? No, this is not  necessary if we use a structure. Such a structure   behaves like one variable and can contain as many  other variables as you want. You can read and   write the internal variables by using this format. Passing data from one task to another is really   simple because FreeRTOS does all the queue  management. Also our code is very readable.  Next: How do we protect shared  resources or data from being   used or changed by two tasks in parallel? One example is the OLED. The Coordinator and the   Receiver task use the display. Because these tasks  run in parallel, the chance for gibberish in the   display is high if we do not make sure that the  display can only be used by one task at a time.  The same applies to the variable trainerStatus,  which also can be updated by two tasks.   Also here, we have to avoid that two tasks  change the variable without coordination.  We use so-called semaphores used as mutexes  to give exclusive access to a shared resource.   Mutex means: Mutual exclusive. Here you see how they are defined.   We need one per shared resource. As soon as a task wants exclusivity,   it takes the mutex. xSemaphoreTake(KEYBOARDMutex, portMAX_DELAY);  Also here, using portMAX_DELAY forces the  program to block till the mutex is available.   As soon as the mutex is granted, this task has  exclusive access to the protected resource.   Until it gives the mutex back: xSemaphoreGive(KEYBOARDMutex);  Now another task can use it. Keep in mind: Not  the resource itself is protected; the mutex helps   us protecting it. Mutexes, therefore, only work if  you protect all accesses to a particular resource.  AFAIK you only need to protect shared  variables if several tasks update them.   If they are updated by only one task and used by  other tasks, this seems ok without protection.  A remark: If you use RTOS in  interrupt service routines,   you have to use slightly different commands. The last thing we have to know: How do we protect   critical sections which must not be interrupted  at all? We have to disable interrupts which also   disables the scheduler for a short moment. Commands are:  portENTER_CRITICAL(&timerMux); portEXIT_CRITICAL(&timerMux);  or taskENTER_CRITICAL(&timerMux);  taskEXIT_CRITICAL(&timerMux); They work like other mutexes   but are called spinlocks because they keep  control inside the task. So please pay attention   when you use them and keep the protected  section short. They block everything else.  BTW: On the ESP32, it does not matter  which of the two commands you use.   Both call the same function, which  blocks interrupts on both cores.  So you know the essential commands. If  you need more, you can still go to the   FreeRTOS documentation and search for it. Now comes, of course, the critical question:   When do we use RTOS, and when a standard  sketch is ok? And are there caveats?  I suggest using an RTOS: - If several things have   to run in parallel. A typical sign is  having problems with delay() statements   or while loops in your conventional sketch - If you want to use both cores,   you have to use RTOS tasks - If you need data streams between different parts   of your code and would have to program your own  queue management, it is much easier to use an RTOS  Many things are easier with RTOS tasks and  queues. But there are also caveats: Because   everything runs in parallel, you have to pay more  attention to the synchronization between tasks.   This is why it is so important to find  shared resources and protect them.  Debugging can sometimes be more challenging  because the reason for a problem can be   hidden in another task. But, after  a while, you learn to live with it.  A hardware debugger can help a lot and,  if you want to be even more sophisticated,   a logic analyzer shows you how your code  moves through the different tasks. Maybe   you watch video #76 if you want to know more. Here the logic analyzer shows us, for example,   that the Morse code is blocked for 66 seconds,  and the receiver task runs every millisecond.  This was all for today. Maybe you share your  experience with FreeRTOS in the comments?  As always, you find all the  relevant links in the description.  I hope this video was useful or at  least interesting for you. If true,   please consider supporting the channel to  secure its future existence. Thank you! Bye
Info
Channel: Andreas Spiess
Views: 60,756
Rating: 4.9694924 out of 5
Keywords: AliExpress, arduino, arduino project, diy, do-it-yourself, eevblog, electronics, esp32, esp32 datasheet, esp32 project, esp32 tutorial, esp8266 datasheet, Field Effect Transistor, greatscott, guide, hack, hobby, how to, iot, lora, lorawan, n-channel, nodemcu, npn, p-channel, pnp, project, simple, smart home, ttgo, tutorial, wemos, wifi, Raspberry Pi, Zoom, Microsoft, Teams, Home Assistant, Docker, Aliexpress, Amazon, Banggood, RTOS tutorial, rtos, freertos, freertos tutorial, freertos esp32
Id: 684KSAvYbw4
Channel Id: undefined
Length: 23min 35sec (1415 seconds)
Published: Sun Apr 25 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.