Total noob with freertos and still sorting through lots of docs…
I’m not sure how to state this, so…
(what I’m saying is how I “think” things are…I’m happy to be corrected. )
In a vanilla application using the Arduino IDE the setup() function is called at reset, it returns, and then loop() is called. Theoretically, loop() never exits.
This is mimicked when the arduino is being used in the Arduino IDE. (pretty much the same in VSC/PIO from what I can tell, but not yet sure where ESP-IDF fits in… )
In multiple cases I’ve seen libraries available on github, etc. where the examples use the same setup()/loop() structure but include the libraries of the github offering. And, in the included libraries one or more freertos tasks are created. Then loop() proceeds to do stuff (most notably with info received by a task).
(I’ve also seen ESP-IDF examples where there is a main.c/cpp where there are no setup()/loop() and everything is a task.
I guess i’m trying to visualize what going on and thinking there is mixing of approaches going on here that I don’t as yet grok.
Any pointers, discussion, etc. gratefully excepted.
I’m afraid I’m not familiar with Arduino beyond tinkering a little bit without FreeRTOS, so I don’t know how the FreeRTOS frameworks that work with the Arduino
software are structured. However the setup()/loop() semantics are specific to Arduino and not how you would normally use FreeRTOS.
In FreeRTOS you can create tasks (threads) before or after the scheduler is started. After the scheduler has started the tasks are executed according to the
priority assigned to them by the application writer. Each task has its own loop, but the loops do not have dependencies on each other (each task is autonomous).
Sorry rushing out the door now - but main point is not to confuse Arduino’s own way of structuring a non-multithreaded application with how a FreeRTOS (or any
other RTOS for that matter) application would be structure. The IDF is not related to Arduino, unless you are referring to a specific IDF version.
In Arduino, you can create your tasks within the setup method. Since each task runs in a loop, as Richard mentioned, you don’t include anything in the loop function. The loop method is synchronous and you wouldn’t get the prioritization or interrupts you get by using FreeRTOS and tasks.
I definitely have a problem sorting what’s Arduino IDE, what’s VSC/PIO/ESP-IDF, etc.
(I’m trying to stay away from the Arduino IDE and favor VSC/PIO/ESP-IDF when using ESP32.)
In the application I’m looking at I think would consist of a the following tasks:
read channel numbers (in this case the first channel of a 4 channel DMX dimmer) This task would run once, i.e. to get the current channel set by a thumbwheel by shifting in the values of 3 thumbwheels.
configure the ESP32 PWM module and start it. This task runs once since the PWM module free runs once its enabled.
a task that runs forever reading the DMX input on the second serial port on the ESP32 and filling an array containing the values of the 4 channels
a task that reads the values of the four channels received from the array and updates the PWM duty cycle to the new value
My current understanding of freertos would basically be a main() that started each of these tasks.
Apparently, when #include Arduino.h is used in VSC/PIO it handles starting the freertos scheduler after setup() returns. So, I think I need to try to do some vanilla test code not using the arduino framework…
I have a working first pass of this application. Now I’m wondering about my limited experience and the implementation.
I have a couple of start up tasks that run and then use VTaskDelete to delete themselves. They need only be executed at startup. Is this the proper way to implement these?
Because I’ve simply copied other code, the stack size is set to 4096 and priority to 5. These tasks need to complete before the final two task begin. Is there a way to be sure this happens?
I have two tasks that run forever. One gets received values from the uart. The other checks those values to see if they have changed and updates the PWM duty cycle as needed.
Is it better to have more tasks than less? For example rather than checking all 4 dmx values in one task, would it be better to have a task for each of the 4 dmx channels?
Again, these two task are set to the 4096 stack and the priority of 5. Is this best practice?
in both of these tasks I use vTaskDelay(10). I was triggering the watchdog, so I did some reading and looking at other code. Is this the preferred way?
How do I tell if this needs to be more or could be less?
You could create the other two tasks after the two initialisation tasks complete - or from within the initialisation tasks before they delete themselves - or ideally just re-purpose the initialisation tasks once they have completed their initialisation activities so you don’t need to delete or create any new tasks.
There is no single answer to that - like most engineering answers - “it depends on the application”. If you are running on a small device though I would imagine having fewer tasks is best as each task needs its own stack and stacks consume RAM.
No, setting stack sizes and priorities randomly is not best practice ;o) The priorities should be set relative to any other task priority to ensure your application goals are met (which might be to meet real time behaviour, or low power characteristics, or whatever) - you will need to determine what that is. As far as stack sizes go you want it to be as small as possible - which means leaving a decent buffer to avoid stack overflows. There are various methods of determining the required stack size, but I am fairly pragmatic in this and tend to do it by experimentation. You can view the stack high water mark (that is, the most stack ever used by a task) in various IDE plug-ins or by using the APIs provided (Google “freertos stack high watermark”) - while also ensuring you have a stack overflow hook defined in case you make the stack too small (Google “freertos stack overflow hook”).
The best way to do what? You want to kick the watchdog ever 10 ticks? If so, first don’t measure time in ticks, but in ms if you can - that means you can change the tick frequency without breaking the code. (Google “freertos pdMS_TO_TICKS” to find a portable way of doing this). Second if this is for the watchdog then it is best to have the code that kicks the watchdog first check the system status to make sure it is healthy - for example has a lower priority task got stuck in a loop? If so its best not to kick the watchdog. You can have all tasks report their status and the number of iterations of their implementing loops they have performed. All the examples in the FreeRTOS download do that.
If I understand what I read (always questionable with me… ) the daemon task startup hook runs once when the daemon starts. The hook code is simply a function called by the daemon and should not do any task delete, but, could start other tasks. Does the daemon run before other task I create? (i.e. how do I ensure the other tasks don’t run until the hook has finished?)
(Note: it also says to set a value in FreeRTOSConfig.h. I ned to see if that is set currently. In fact, since I’m working in the ESP-IDF, I don’t see the FreeRTOS startup (at least, I haven’t gone looking for it, but, the tasks run…).
So, one task could initialize as necessary once, then enter the actual task endless loop? (i.e. since it is now in and endless loop, when the task executes after the first execution it will return to the loop and not re-execute the init stuff.)
Is an ESP-32 considered a small device? I don’t have a feel for the priority. (I haven’t found if ESP-IDF sets the max, if it is set, is 5 high or low, etc.) From a design requirements point of view, the 4 incoming values have the potential to change every 22ms, so I guess the uart service needs a pretty high priority…I don’t know what pretty high means from a time perspective. If the value of any of the 4 has changed then the PWM duty cycle needs to change, probably in the 10’s of milliseconds range, but, incandescent lamps have lag both getting bright and dim (probably longer for dimming). I guess this will require some test runs to see how smooth the brightness change can happen…
So, setting up some tests with vTaskGetInfo() and running a bit to see what the minimum can be is probably the best approach.
Well, put the vTaskDelay(10) in because the I got reports that the task was not giving up it’s slot (I forget the exact message…). It was just in a tight loop, so, to be expected.
In fact, I probably should figure out how to only have the PWM duty cycle update task run only when the value changes rather than having it run al the time. In fact I could then have a task for each channel that sleeps most of the time and only wakes when it needs to…currently beyond my experience at this moment. More research.
The way to make sure that other tasks don’t run to early is to either not create them until things are ready for them to run, or to have them begin with a blocking action on some sort of signal to tell them that things are ready for them to go (a direct-to-task notification, semaphore or event).
The daemon startup hook is just a function called at the start of the daemon task, typically the daemon is given a high priority, so it will tend to run early, but if you have other tasks at the same or higher priority the can run before this, or if you do something in the hook to block it (probably not advisable, as timers won’t start acting until you return from the hook function). I don’t know any reason it couldn’t delete tasks, but it is unlikely you are going to want to delete a task at this time.
Can’t say I have an intuitive feel for semaphores at this point. I get the concept, but when I look at code examples the getting, taking, what happens when, etc. quickly confuse me.
OK, this is as I thought. Just a function called from the daemon. As for the deleting tasks, I meant that the hook shouldn’t delete itself, because, it’s just a function call that should return, probably as fast as possible.
It turns out to be a moot issue. I just discovered this was added in version 9 and the ESP-IDF is based on version 8.
Obviously what I need to do was handled some how back then, guess I’ll have to try to glean how from the Espressif examples…though my initial work was based on their code.
Someone recently posted a link to an article giving a basic description of how they work, but here is a very simple model. Think of a binary semaphore as a simple flag holder. When someone calls the Give function (or GiveFromISR) then the holder will have a flag in it (but no more than one). When someone calls the Take function, if the flag is in the holder, it is removed and the task continues. If the flag isn’t in the holder, then the task is blocked and it waits for the flag. When a flag gets put in the holder, the highest priority task waiting for a flag, gets it, and the rest continue to wait. A task also has an option to specify a maximum time to wait, and if that time expires the block ends, and the take will return FALSE to indicate the failure.
There are several common patterns to use a semaphore. One is as a protection over a resource (this use might be better done with a Mutex, but to a large extent it works the same, just with some extra feature and restrictions to handle some subtle issues with this). When a task want the resource it takes the flag, When it has the flag it ‘owns’ the resource and can use it knowing that no one else will. When it is done it gives the flag back, so someone else can take it to use the resource.
A second use is to synchronize a task, so it can wait for something to be ready before processing it. The task tries to take the semaphore and blocks waiting for the flag. When it comes time for it to do its work, the thing that decides that gives the semaphore, allowing the task to resume.
As Mr. Barry has commented, even without the daemon task callback, as long as you are creating other tasks in your system, you can generally slip your initialization code at the beginning of one or more of them, and a task that you don’t want doing its normal work until this initialization is done is an ideal spot for that initialization, as you naturally won’t get into the task loop until the initialization before it is run.
First, to Give the semaphore a flag, you don’t need one of your own. In one sense the flag holder always keeps the flag, but the flag is raised (and available) or down (and unavailable). A semaphore doesn’t need to keep track of who has the flag, and it generally isn’t really meaningful (A Mutex, on the other hand, gives its flag to a specific task, and that task is the one which is supposed to return it).
The semaphore is sort of like the train signal, where the flag is down to indicate that the track is not ready for the train to pass, and is raised to mark the track ready for the train.
The act of creating the semaphore creates the flag holder, depending on the function used to create teh semaphore, it might create it with the flag available or not (which you want is sort of dependant on how you plan to use the semaphore)