BriscoeTech / Arduino-FreeRTOS-SAMD21

A port of FreeRTOS that runs on Arduino Samd21 boards
63 stars 19 forks source link

Added possibility to run Arduino loop() as dedicated task #18

Closed sergiotomasello closed 2 years ago

sergiotomasello commented 5 years ago

So loop can contain delay and blocking instructions.

godario commented 5 years ago

I implemented the ability to run the loop() as dedicated task and not as simply freeRTOS idle hook function. This because I want to give the user the possibility of using delay and/or blocking functions in the loop; in this way the backward compatibility with non-freertos sketch is certainly greater. To make the loop () a dedicated task it is sufficient to set configUSE_IDLE_HOOK to 0. otherwise, if configUSE_IDLE_HOOK is set to 1, loops will work again as the idle hook of freertos (as implemented by you).

sergiotomasello commented 4 years ago

Hi @BriscoeTech any news on that? Regards Sergio

drewfish commented 4 years ago

I think the trick is that we don't currently have a way for the sketch to adjust the settings in FreeRTOSConfig.h such that it affects the compilation of the source files in this library. I've figured out a way to do that when using the platformio.org toolset but not when using the Arduino IDE.

BriscoeTech commented 4 years ago

Hello Everyone,

Got a chance to sit down and think about this pr :-) I checked out the code and spent some time running it.

So I wrote a loooong writup on this, and realized I was all wrong after running the test code on the master branch, and had to throw it out the window lol.

After doing some testing, it appears that the delay() command can be used in the rtos assuming it is only being used in the lowest priority instance (thread or idle task). The scheduler works a little differently than I thought, and it looks like higher priority threads can interrupt an arduino delay (gasp!). I think the vNopDelayMS I implemented in the rtos is actually useless (ooops!) and can be replaced by a delay() as long as you are carefull and follow the rule above. Every thread that is not the lowest priority will have to implement proper rtos delays, or they will starve all equal or lower priority tasks from running.

I used the Basic_RTOS_Example.ino to test it out, but changed the idle loop to be below:

//*****************************************************************
// This is now the rtos idle loop
// No rtos blocking functions allowed!
//*****************************************************************
void loop() 
{
    // Optional commands, can comment/uncomment below
    delay(10000); //only print a period every ten seconds
    SERIAL.print("."); //print out dots in terminal, we only do this when the RTOS is in the idle state
}

It looks like everything runs as expected wether you use the current master branch or as the pr branch. You can run into timing issues, lockup the rtos, or starve a task if you use delays in a thread that is not the lowest priority in the system.

I want to give the user the possibility of using delay and/or blocking functions in the loop;

As far as blocking functions go for the idle hook, you just wont be able to use rtos blocking functions, since you will crash the rtos if you call them not from a non thread instance, like pending on a semaphore.

So as of right now, it does not look like the feature is needed, you can technically run a non-rtos sketch with delays as the lowest priority idle thread and everything might be ok. I am leery to say it will always work, there might be a case I haven't thought about that might throw a monkey wrench into everything and mess up the timing.

I did do alot of thinking about the benefits of the loop() running in the context of a thread, and here is what I originally wrote before I discovered everything above:

I think the trick is that we don't currently have a way for the sketch to adjust the settings in FreeRTOSConfig.h such that it affects the compilation of the source files in this library

`#if ( configUSE_IDLE_HOOK == 0) xTaskCreate(loopTask, "loop", 512, ( void * ) NULL, ( tskIDLE_PRIORITY | portPRIVILEGE_BIT ), NULL);

endif`

When I was looking trough the code, I noticed that the thread has a hard coded 512 bytes of ram for the stack size, and a fixed priority. I agree with @drewfish since the compiler and ide does not allow for the overriding of typedefs or constants, the stack size and thread priority of the thread is really application dependent and has to be set by the user. Depending on how complex the code is, I have had to make threads as much as 1024 or 2048 in order to make things work, but I am also taking that away from the heap. Every user will have to play that game of how much stack is good enough. I suppose this could be a global variable in the rtos that gets set by a function (like what I did with the blinking error led) but my gut tells me this is turning into a user defined, application specific setup, not necessarily a feature of the rtos.

If I was to implement the arduino loop as a thread, I think I would do exactly what you did (turning the idle thread off in FreeRTOSConfig.h), and then add the code to the arduino project .ino instead of in the rtos library. Now the user has full control over the stack size and thread priority. This could be a nice example program to include into the rtos library example folder, with a full writup in the top comments on what has to be changed in FreeRTOSConfig.h and what this could buy the user. The user will also have to think about the delays, and if they make it a highter priority task, convert the delay()'s to rtos delays.

Let me know if you find a case where the loop() has to be a thread and not the idle loop, I would find that really interesting to test :-)

godario commented 4 years ago

Hi @BriscoeTech, My hesitation in using delay() within the current (master) loop() basically comes from the following comment before calling vApplicationIdleHook(); in task.c:

      NOTE: vApplicationIdleHook() MUST NOT, UNDER ANY CIRCUMSTANCES,
      CALL A FUNCTION THAT MIGHT BLOCK.

However, in the past I actually ran into a case where very expensive code within the loop() would block FreeRTOS, however, at the moment, I can't replicate that circumstance anymore (was it a false alarm?).

Following your advice I thought about the implementation that you find in my last commit.

Without editing any configuration files, if a user want to execute the loop() as a dedicated task and want to set the stack size and priority, he could call the runLoopAsTask(uint16_t stack, uint16_t priority) function before the scheduler starts, as example: runLoopAsTask(256, tskIDLE_PRIORITY);.

trlafleur commented 4 years ago

You may want to look at how this is implemented in the Arduino ESP32 base code as "loop" runs as a task, delay function calls vTaskDelay to keep it all withing FreeRTOS.

ESP32 runs ver 8.2.x, I believe with some 9.x code...

https://github.com/espressif/arduino-esp32
https://github.com/espressif/esp-idf/tree/master/components/freertos