New to Arduino & freeRTOS - are calls to Serial.print() task safe?

I'm new to both Arduino & freeRTOS, but exploring both to get my feet wet.

I took the example sketch AnalogRead_DigitalRead and when it didn't work startd to strip it down to its bare bones to narrow down what was and wasn't working.

So far it seems output via serial (USB) from the program shows up correctly on the IDE's serial monitor from the setup() function, which is where I also launch a single task.

However I cannot see any sign that the task ever runs. I stripped out evderything from the task function to just a forever loop, a Serial.print() call and a 10 second vTaskDelay() call.

This made me wonder, could the Serial.print() function call not be task safe or otherwise incompatible? It also occurs to me that it may also be a blocking function, though not necessarily so.

Any one have a read on what I'm doing wrong here?

Also is there a good list of Arduino runtime calls that are safe to be called from freeRTOS tasks and which ones block?

My source code follows below. The only output I get from the serial monitor is the following:


23:28:08.952 -> Creating task
23:28:09.056 -> Task created
23:28:09.158 -> Ready to run


#include <Arduino_FreeRTOS.h>

// define two Tasks for DigitalRead & AnalogRead
void TaskDigitalRead( void *pvParameters );

// the setup function runs once when you press reset or power the board
void setup() {

  // initialize serial communication at 9600 bits per second:
  Serial.begin(9600, SERIAL_8N1);

  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB, on LEONARDO, MICRO, YUN, and other 32u4 based boards.
  }

    Serial.print("Creating task\n");
    delay(100);

  if(    xTaskCreate( TaskDigitalRead,  "DigitalRead",  128,  NULL,  2,  NULL ) 
      == pdPASS )
      {
      Serial.print("Task created\n");
      delay(100);
      }
      else
      {
      Serial.print("Error creating task\n");
      delay(100);
      }
    
    Serial.print("Ready to run\n");
    delay(100);
}

void loop()
{
    Serial.print("In loop()\n");
}

/*--------------------------------------------------*/
/*---------------------- Tasks ---------------------*/
/*--------------------------------------------------*/

void TaskDigitalRead( void *pvParameters __attribute__((unused)) )  // This is a Task.
{
  for (;;) // A Task shall never return or exit.
  {
    Serial.print("In TaskDigitalRead\n");
    vTaskDelay(10000 / portTICK_PERIOD_MS);  // one tick delay (15ms) in between reads for stability
  }
}

So.. What is all this "freeRTOS" excitement supposed to buy you?

-jim lee

I find an RTOS to be indispensible for embedded work, though one does have to watch their bytes on such a small target. At its simplest it decouples the various timing needs of a project to make it much easier to develop and maintain.

Which Arduino board are you using ?

For the moment I tried it on an Arduino Nano and on two different Arduino Unos, and they all behave the same.

I'm sure I'm comitting a Newbie mistake somewhere, but for the life of me can't figure out what. Should my code work, or is there a flaw I'm not seeing?

I tried it on an Arduino Nano and on two different Arduino Unos, and they all behave the same.

No surprise there as they all use the same processor

Personally I think that I will forgo the "pleasures" of an RTOS and stick to using my own time slicing code that I understand and have control of

UKHeliBob:
No surprise there as they all use the same processor

Not quite as the Nano I'm using if in the 2.x series using an ATmega168, though yeah, same architecture. But do you think this is a processor-specific issue? That'd surprise me somehow, but then again I'm a Newbie at both Arduino & freeRTOS...

As to RTOS I can highly recommend to all who have not used one before to give it a try sometime. They're incredibly liberating to use and solves many thorny issues, though can have some fascinating quirks all their own - (priority inversion, deadlocks, etc...) It does, though, introduce an entirely new paradigm of programming, so might take a while to get used to some of its concepts.

But do you think this is a processor-specific issue? That'd surprise me somehow, but then again I'm a Newbie at both Arduino & freeRTOS...

I don't know, but if the board had a different processor, say an ESP32, then it would need a different version of the library which might have implementations

Actually the ESP32 would not need to use the library as it has 2 cores, one of which runs an RTOS

akatayama:
This made me wonder, could the Serial.print() function call not be task safe or otherwise incompatible? It also occurs to me that it may also be a blocking function, though not necessarily so.

"They're incredibly liberating to use and solves many thorny issues"

Not sure I understand how querying whether very basic functions, such as Serial.print(), are safe to use, is 'liberating'

#include <Arduino_FreeRTOS.h>

// define two Tasks for DigitalRead & AnalogRead
void TaskDigitalRead( void *pvParameters );

// the setup function runs once when you press reset or power the board
void setup() {

  // initialize serial communication at 9600 bits per second:
  Serial.begin(9600, SERIAL_8N1);

  while (!Serial) {
    ; // wait for serial port to connect. Needed for native USB, on LEONARDO, MICRO, YUN, and other 32u4 based boards.
  }

    Serial.print("Creating task\n");
    delay(100);

  if(    xTaskCreate( TaskDigitalRead,  "DigitalRead",  128,  NULL,  2,  NULL )
      == pdPASS )
      {
      Serial.print("Task created\n");
      delay(100);
      }
      else
      {
      Serial.print("Error creating task\n");
      delay(100);
      }
    
    Serial.print("Ready to run\n");
    delay(100);
}

void loop()
{
    Serial.print("In loop()\n");
}

/*--------------------------------------------------*/
/*---------------------- Tasks ---------------------*/
/*--------------------------------------------------*/

void TaskDigitalRead( void *pvParameters __attribute__((unused)) )  // This is a Task.
{
  for (;;) // A Task shall never return or exit.
  {
    Serial.print("In TaskDigitalRead\n");
    vTaskDelay(10000 / portTICK_PERIOD_MS);  // one tick delay (15ms) in between reads for stability
  }
}

I run freeRTOS on a ESP32 where Serial.print... works fine.

First off

void loop() should be empty under freeRTOS. Under freeRTOS void loop() is used as a clean up object and runs when the task manager says there is nothing else to do.

Next on an Uno / Mega freeRTOS loads as a library and consumes a lot of RAM. on an ESP32 freeRTOS is initialized as a library but is already built into the system and does not consume more RAM to load the library.

The task DigitislRead is assigned a small amount of stack space ram, try giving it 1000 bytes.

Please reconsider running freeRTOS on an UNO, a UNO just does not have enough oomph to run a RTOS.

The DUE runs uMT which is a full blown RTOS. Loading freeRTOS on a STM32, works but consumes nearly 1/2 the RAM. freeRTOS is built into the ESP32 and does not consume any RAM to load the library.

Why use freeRTOS? Multi-threading and, with a ESP32, multi-tasking. And makes running a state machine quite easy.

UKHeliBob:
Actually the ESP32 would not need to use the library as it has 2 cores, one of which runs an RTOS

A freeRTOS task on an ESP32, can be assigned to either core.

I have a few projects where there are only 2 tasks running. One task running on core0 and one task running on core1 and I use a message queue to do multi-processor communications.

Thanks all for the input.

Re. loop(), yes, I kept loop() empty other than a call to Serial.print() just as a check to see if my build was actually launching the task scheduler, which, since it never returns, should never get to calling loop().

I did try increasing the stack to 512 and then again to 640 w/no luck. I didn't go any further as this Nano (v 2.3) only has 1k of RAM.

Speaking of which, does anyone know where to find the output files from the linker? I feel I'm flying blind without seeing its output and the space consumed by static variables in the image.

Thanks for the two recommendations to consider the ESP32. I just happened to have the three Arduinos around (2 Uno's and a Nano) and thought I'd give them a spin. Still think it should be possible to fit one or two very small tasks, but may also consider the other targets. (I actually have no specific project in mind. This is all an exploration to familiarize myself well before a future project materializes.)

For the time being any other possibilities aside from this tiny build breaking its resource limits? Any others run into issues calling Serial.print() from task level running under freeRTOS?

...and in general are all Arduino run time calls task safe, and if not which ones should one stay away from?

Many thanks in advance!

akatayama:
Thanks all for the input.

Re. loop(), yes, I kept loop() empty other than a call to Serial.print() just as a check to see if my build was actually launching the task scheduler, which, since it never returns, should never get to calling loop().

I did try increasing the stack to 512 and then again to 640 w/no luck. I didn't go any further as this Nano (v 2.3) only has 1k of RAM.

Speaking of which, does anyone know where to find the output files from the linker? I feel I'm flying blind without seeing its output and the space consumed by static variables in the image.

Thanks for the two recommendations to consider the ESP32. I just happened to have the three Arduinos around (2 Uno's and a Nano) and thought I'd give them a spin. Still think it should be possible to fit one or two very small tasks, but may also consider the other targets. (I actually have no specific project in mind. This is all an exploration to familiarize myself well before a future project materializes.)

For the time being any other possibilities aside from this tiny build breaking its resource limits? Any others run into issues calling Serial.print() from task level running under freeRTOS?

...and in general are all Arduino run time calls task safe, and if not which ones should one stay away from?

Many thanks in advance!

Yes, there are a few non-thread safe calls for the ESP32. See the ESP32 API: API Reference - ESP32 - — ESP-IDF Programming Guide latest documentation

Good luck with getting freeRTOS to run on a Uno. Did you get the freeRTOS examples to work?

Yes, that's what I started with - the example that came w/the freeRTOS library. When that didn't work that's when I started stripping it down to its core, which is the example I posted here.

BTW thank you for the ESP32 reference for thread-safe calls. Will be taking a look at it - I'm sure the same should generally hold for the ATmega's...

You are unable to make a GPIO pin go high for 1 second and low for one second, driving an led?

Cool. I knew freeRTOS for the UNO was a bust.

As a note malloc() is neither thread or multi processor safe. Espressif has made malloc() wrappers that are thread safe, ps_malloc or one of its family members.


Does this print?

void TaskDigitalRead( void *pvParameters __attribute__((unused)) )  // This is a Task.
{
Serial.println( "the thing be done" );
  for (;;) // A Task shall never return or exit.
  {
    Serial.print("In TaskDigitalRead\n");
    vTaskDelay(10000 / portTICK_PERIOD_MS);  // one tick delay (15ms) in between reads for stability
  }
}

Well I think I found the culprit - just quite randomly I attached the Arduino to another USB port on my computer and all started working just as expected - the task now runs.

Was also able to trim down the task's stack down to 128 bytes after having seen that from another working example online.

At the time I was running the Uno, so will have to repeat the experiment on the Nano running on the other USB port as well.

Well thanks again for all your help and input, and am glad to find out that freeRTOS can run on such a tiny target.

Will now build-out the program to see how much I can actually fit in this little ant of a uController; it should be fun!

Just an update w/my most recent findings, while I was able to call the serial Arduino API from a freeRTOS task, I later found out that it was dependent on performing a sufficiently long vTaskDelay() afterwards. That, as they say, is a "code smell"...

I did a bit more testing and quickly found out that even if one could get nominal operation by using a long enough vTaskDelay(), it's performance was unreliable and given sufficent time could see that it would eventually run-down the stack. So there's a stochastic element to this as well, which isn't all that surprising.

So my conclusion is that the serial run-time calls in Arduino's API cannot reliably be used from a task if predictability and reliability are important. I suspected as much when I first started this post, but only now have some evidence pointing to this. Again not surprising, but thought I'd share my findings...

So my conclusion is that the serial run-time calls in Arduino's API cannot reliably be used from a task if predictability and reliability are important.

Meanwhile, back in the land of BlinkWithoutDelay Serial output continues to work predictably and reliably

UKHeliBob:
Meanwhile, back in the land of BlinkWithoutDelay Serial output continues to work predictably and reliably

Understood - right you are - though I'm trying to check out an RTOS and suspecting this is an RTOS-specific issue. Not being able to run serial from task-level, or at least w/o adding a lot of "open loop" managing with accompanying vTaskDelay()'s, is going to be a real pain point.

Really intrigued by the ESP32 - in terms of resources it's like stepping from famine to feast! Having grown up doing assembler on much less I don't mind the occasional resource challenge, but wouldn't want it be a fight every single time either.

OTOH I think I also need to consider FlashForth - that could be a good RTOS fit for the Arduino - or as I've done several times in the past, roll my own state machine compiler/interpreter. Did a lot of resource-limited embedded projects using the latter approach.

In the meantime I welcome anyone who actually has used the freeRTOS/Arduino combo to chime in w/their experience, and particularly on a Nano...

1 Like