Questions on ESP32 Dual Core Programming and Tasks

  1. I have researched this and am suprised to have not found anything.
  2. I've been putting off for around a month asking this as it seemed that there would be answers on the internet

MY ENVIRONMENT :
Operating System and Software

  • Windows 7 Professional
  • Arduino IDE Version 2.1.1

HARDWARE

  • ESP32 Wroom
    (Driver - CH340)
    Although the board that i'm using is irrelevant to the question.
    I have CP2102 and CH340 Dev boards , all the drivers are installed and working without issue

Also i won't mention libraries as they are also not relevant to my question

TO THE QUESTION...

Re ESP32 Dual core structure
I already know all this and the internet keeps just repeating it,

// My understanding of the Base skeleton Dual Core Structure is...

// Libraries would go here

// Initiate the TaskCodes
void Task1code( void * pvParameters );  //Initiates Task1code
void Task2code( void * pvParameters );  //Initiates Task2code

// Define the TaskHandlers
TaskHandle_t Task1;  //Assigned to Core 0
TaskHandle_t Task2;  //Assigned to Core 1

void setup() 
{  // OPENS the void setup
/*//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                    TASK DEFINITIONS
*///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/*/////////////////////////////////////////////////////////////////////////////////////////
------------------------------------------ TASK 1 -----------------------------------------
///////////////////////////////////////////////////////////////////////////////////////////
-------------------------------------------------------------------------------------------
 Create Task1 , Executed in Task1code() function , On Core 0 , Priority 1
-------------------------------------------------------------------------------------------
*///  
  xTaskCreatePinnedToCore(
                    Task1code,          /* Task function. */
                    "Task1",/* name of task. */
                    10000,              /* Stack size of task */
                    NULL,               /* parameter of the task */
                    0,                  /* priority of the task */
                    &Task1,             /* Task handle to keep track of created task */
                    0);                 /* pin task to core 0 */                  
  delay(5); 
/*/////////////////////////////////////////////////////////////////////////////////////////
------------------------------------------ TASK 2 -----------------------------------------
///////////////////////////////////////////////////////////////////////////////////////////
 Create Task2 , Executed in Task2code() function , On Core 1 , Priority 1
-------------------------------------------------------------------------------------------
*///   
  xTaskCreatePinnedToCore(
                    Task2code,   /* Task function. */
                    "Task2",     /* name of task. */
                    10000,       /* Stack size of task */
                    NULL,        /* parameter of the task */
                    0,           /* priority of the task */
                    &Task2,      /* Task handle to keep track of created task */
                    1);          /* pin task to core 1 */
  delay(5); 
///////////////////////////////////////////////////////////////////////////////////////////
} // CLOSES the void setup

// Then we execute the Functions  
//-----------------------------------------------------------------------------------------
//Task1code: 
//-----------------------------------------------------------------------------------------     
void Task1code( void * pvParameters )
{
  //Serial.println("STARTING CORE 0 ");
  Serial.print("Task1 running on core ");
  Serial.println(xPortGetCoreID());
    for(;;)
    {
//  A HUGE CHUNK OF CODE IS INSERTED HERE
    }
}

//-----------------------------------------------------------------------------------------
//Task2code: 
//-----------------------------------------------------------------------------------------  
void Task2code( void * pvParameters )
{
  //Serial.println("STARTING CORE 1 ");  
  Serial.print("Task2 running on core ");
  Serial.println(xPortGetCoreID());
    for(;;)
    {

  }
}

void loop()
{  // OPENS the void loop
 
}  // CLOSES the void loop

This is all Text book 101 Stuff and i understand it perfectly and this is the extent
that the internet will tell you

NOW HERE IS WHAT I WANT TO KNOW

Question 1 -
Re this

  xTaskCreatePinnedToCore(
                    Task1code,          /* Task function. */
                    "Task1",/* name of task. */
                    10000,              /* Stack size of task */
                    NULL,               /* parameter of the task */
                    0,                  /* priority of the task */
                    &Task1,             /* Task handle to keep track of created task */
                    0);                 /* pin task to core 0 */                  
  delay(5); 

Am i correct in understanding that Stack "Size" is measured in "WORDS" and 1 Word = 2 Bytes.
so what then does "10000" Mean ? I mean i'm guessing and i shouldn't have to but...
10kB ?? so 5 Kilo Words ?

What do we do with "NULL Parameter of the task" what does this even mean
As for priority, 0 = the lowest, but then no other values are explained
is this 0 -1 or 0 - 100 ??

as for Task handle to keep track of tasks, i have seen this as &Task1 and NULL, I assume
i can change this to whatever i want right ?

QUESTION 2 -
Now RE Defining the Task Handlers
Usually you get told
TaskHandle_t Task1; //Assigned to Core 0
TaskHandle_t Task2; //Assigned to Core 1

Task 1 is for Core 0 and Task 2 for Core 1
But that can't be right because you can assign a task to a core

so i guess what i'm asking here is this...
Instead of just having 2 Tasks

TaskHandle_t Task1;  //Assigned to Core 0
TaskHandle_t Task2;  //Assigned to Core 1

Can i have , Say 20 ?

TaskHandle_t Task1;  //Assigned to Core 0
TaskHandle_t Task2;  //Assigned to Core 1
// All the way to 20
TaskHandle_t Task19;  //Assigned to Core 1
TaskHandle_t Task20;  //Assigned to Core 1

and then if i understand this correctly
My code would then look like this (as a Skeleton Structure)

// Libraries would go here

//  Initiates the 20 TaskCodes
void Task1code( void * pvParameters );  //Initiates Task1code
void Task2code( void * pvParameters );  //Initiates Task2code
// All the way to 20
void Task19code( void * pvParameters );  //Initiates Task19code
void Task20code( void * pvParameters );  //Initiates Task20code

// Initiates the 20 TaskHandlers
TaskHandle_t Task1;  //Assigned to Core 0
TaskHandle_t Task2;  //Assigned to Core 1
// All the way to 20
TaskHandle_t Task19;  //Assigned to Core 1
TaskHandle_t Task20;  //Assigned to Core 1

void setup() 
{  // OPENS the void setup
/*//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                    TASK DEFINITIONS
*///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/*/////////////////////////////////////////////////////////////////////////////////////////
------------------------------------------ TASK 1 -----------------------------------------
///////////////////////////////////////////////////////////////////////////////////////////
-------------------------------------------------------------------------------------------
 Create Task1 , Executed in Task1code() function , On Core 0 , Priority 1
-------------------------------------------------------------------------------------------
*///  
  xTaskCreatePinnedToCore(
                    Task1code,          /* Task function. */
                    "Task1",/* name of task. */
                    10000,              /* Stack size of task */
                    NULL,               /* parameter of the task */
                    0,                  /* priority of the task */
                    &Task1,             /* Task handle to keep track of created task */
                    0);                 /* pin task to core 0 */                  
  delay(5); 
/*/////////////////////////////////////////////////////////////////////////////////////////
------------------------------------------ TASK 2 -----------------------------------------
///////////////////////////////////////////////////////////////////////////////////////////
 Create Task2 , Executed in Task2code() function , On Core 1 , Priority 1
-------------------------------------------------------------------------------------------
*///   
  xTaskCreatePinnedToCore(
                    Task2code,   /* Task function. */
                    "Task2",     /* name of task. */
                    10000,       /* Stack size of task */
                    NULL,        /* parameter of the task */
                    0,           /* priority of the task */
                    &Task2,      /* Task handle to keep track of created task */
                    1);          /* pin task to core 1 */
  delay(5); 
///////////////////////////////////////////////////////////////////////////////////////////

// All the way to 20

/*/////////////////////////////////////////////////////////////////////////////////////////
------------------------------------------ TASK 19 -----------------------------------------
///////////////////////////////////////////////////////////////////////////////////////////
-------------------------------------------------------------------------------------------
 Create Task19 , Executed in Task19code() function , On Core 1 , Priority 1
-------------------------------------------------------------------------------------------
*///  
  xTaskCreatePinnedToCore(
                    Task19code,   /* Task function. */
                    "Task19",     /* name of task. */
                    10000,       /* Stack size of task */
                    NULL,        /* parameter of the task */
                    0,           /* priority of the task */
                    &Task19,      /* Task handle to keep track of created task */
                    0);          /* pin task to core 0 */                  
  delay(5);
/*/////////////////////////////////////////////////////////////////////////////////////////
------------------------------------------ TASK 20 -----------------------------------------
///////////////////////////////////////////////////////////////////////////////////////////
-------------------------------------------------------------------------------------------
 Create Task20 , Executed in Task20code() function , On Core 1 , Priority 1
-------------------------------------------------------------------------------------------
create a task that will be executed in the Task1code() function, 
with priority 1 and executed on core 0
*///  
  xTaskCreatePinnedToCore(
                    Task20code,   /* Task function. */
                    "Task20",     /* name of task. */
                    10000,       /* Stack size of task */
                    NULL,        /* parameter of the task */
                    0,           /* priority of the task */
                    &Task20,      /* Task handle to keep track of created task */
                    0);          /* pin task to core 0 */                  
  delay(5);
/*/////////////////////////////////////////////////////////////////////////////////////////
 } // CLOSES the void setup

// Then we execute the Functions  
//-----------------------------------------------------------------------------------------
//Task1code: 
//-----------------------------------------------------------------------------------------     
void Task1code( void * pvParameters )
{
  //Serial.println("STARTING CORE 0 ");
  Serial.print("Task1 running on core ");
  Serial.println(xPortGetCoreID());
    for(;;)
    {
//  A HUGE CHUNK OF CODE IS INSERTED HERE
    }
}

//-----------------------------------------------------------------------------------------
//Task2code: 
//-----------------------------------------------------------------------------------------  
void Task2code( void * pvParameters )
{
  //Serial.println("STARTING CORE 1 ");  
  Serial.print("Task2 running on core ");
  Serial.println(xPortGetCoreID());
    for(;;)
    {
//  A HUGE CHUNK OF CODE IS INSERTED HERE
  }
}

// All the way to 20

//-----------------------------------------------------------------------------------------
//Task19code: 
//-----------------------------------------------------------------------------------------  
void Task19code( void * pvParameters )
{
  //Serial.println("STARTING CORE 1 ");  
  Serial.print("Task19 running on core ");
  Serial.println(xPortGetCoreID());
    for(;;)
    {
//  A HUGE CHUNK OF CODE IS INSERTED HERE
  }
}

//-----------------------------------------------------------------------------------------
//Task20code: 
//-----------------------------------------------------------------------------------------  
void Task20code( void * pvParameters )
{
  //Serial.println("STARTING CORE 1 ");  
  Serial.print("Task2 running on core ");
  Serial.println(xPortGetCoreID());
    for(;;)
    {
//  A HUGE CHUNK OF CODE IS INSERTED HERE
  }
}

void loop()
{  // OPENS the void loop
 
}  // CLOSES the void loop


Have i understood that correctly ?

QUESTION 3 - Can i then somewhat eliminate , Switch / Case / Break ?

so if i have a remote control and i want to create LED Strip Sequences for example

I was thinking i could assign 1 Task for the I.R. Receiver to listen for the I.R. Signal
and then the remote will have 10 Buttons and each one control a light sequence

Could i then Instead of creating a CASE for each button
Could i just create a Task for each button ?

These are the things that i'm unclear on because there is nothing on the internet that explains anything beyond Task1 and Task 2

and Lastly, is there any reason why this

  xTaskCreatePinnedToCore(
                    Task1code,          /* Task function. */
                    "Task1",/* name of task. */
                    10000,              /* Stack size of task */
                    NULL,               /* parameter of the task */
                    0,                  /* priority of the task */
                    &Task1,             /* Task handle to keep track of created task */
                    0);                 /* pin task to core 0 */                  
  delay(5); 

Can't look like this

xTaskCreatePinnedToCore(
  Task1code,          /* Task function. */
  "Task1",/* name of task. */
  10000,              /* Stack size of task */
  NULL,               /* parameter of the task */
  0,                  /* priority of the task */
  &Task1,             /* Task handle to keep track of created task */
  0);                 /* pin task to core 0 */                  
  delay(5); 

and i am aware that i can do this as well if i want

xTaskCreatePinnedToCore(Task1code, "Task1", 10000, NULL, 1, NULL,  0); 

Thanks ahead of time

It means 10k, not 10M. For word/byte consult your controller manual.

Yes, you can have any number of tasks. At least as many tasks as there exist events for which all these tasks are waiting most of the time.

I don't think that binding tasks to cores is a good idea, at least not for beginners.

I can not imagine a way to do that. For IR commands a switch is the natural solution for calling dedicated functions or raising events.

Proper indentation helps in reading and understanding code. Parameters should be indented vs. statements.

1 Like

sorry of course it does... Brain fart moment

Do you have these attached books?
FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide.pdf (4.4 MB)
FreeRTOS_Reference_Manual_V10.0.0.pdf (2.4 MB)

ESP32 supports "Single-core Multi-tasking" and "Dual-core Multi-tasking" Operating Environment with the functions of the FreeRTOS Library.

1 Like

Excellent that's what i thought, the internet doesn't make that clear,
thank you

I Agree
I'm not a beginner

I agree
i was talking about how they pertain to LED Sequences more so

Cool

Subject to the size of the off-chip Flash Memory installed in ESP32 which is 4 MB.

1 Like

No i do not Happen to have them :stuck_out_tongue:
Thanks i will download and read them
Should be interesting

Needed to Master FreeRTOS Programming using ESP32 Architecture.

Would reading be enough?

1 Like

Consider concurrency. Multiple LED patterns can not run at the same time on the same LED strip, no need to create a task for each sequence.

thanks mate you've actually answered most of the questions
and what about these 2

What do we do with "NULL Parameter of the task" what does this even mean?

As for priority, 0 = the lowest, but then no other values are explained
is this 0 -1 or 0 - 100 ??

as for Task handle to keep track of tasks, i have seen this as &Task1 and NULL, I assume
i can change this to whatever i want right ?

Do you know the answers to those ?

Of course, Understood.

Must comply with the specifications of FreeRTOS Manual.

1 Like

I have no meaning on these. Don't know about ESP nor FreeRTOS in detail.

Do you know how core priority works in the sense of what other values other than 0 can be used to indicate higher priority
is it 0-10 or what ?

Cool

What do you mean exactly ?

I Understand, but also understand this.
When creating this question i was largely vague on the LED topic as a few factors are relevant that i'm aware of

  • First i absolutely love Switch / Case / Break i think it's awesome

  • I do understand that within certain tasks Switch / Case / Break will still have a benefit to be used

  • I also understand that it doesn't just have to be LED Sequences it can be anything, Relays , Servo's whatever

My focus was on the dual core structure and clarification of it , Not so much...
What should i do to light up a strip or whatever , i've got that part sorted.

it's just that you'd think there would already be a tonne of info on this, Seems a basic question but no one seemed to get beyond Task1 and Task 2

Of course Multiple sequences cannot run on 1 LED Strip at the same time as
a Diode can only be 1 or 0 at any given point in time... Absolutely

Re
Real time hands on kernel

2023-08-20 18_50_06-FreeRTOS_Real_Time_Kernel-A_Hands-On_Tutorial_Guide.pdf

LOL
Awwww I can see i'm gonna enjoy this and my wife is gonna give me crap tomorrow for pulling an all nighter :stuck_out_tongue:

Ok thanks, that's handy to know, I'll pay attention to that when digesting the manual

@GolamMostafa
Do you happen to know the answer to this question

If you want to know how to choose a stack size then as far as I can tell you have to do an experiment.
Essentially you start with the default recommended stack size, then you run your program for a time and use uxTaskGetStackHighWaterMark() to see how close it gets to the limit. If it gets too close then you revise the stack size upward and repeat the experiment.

Over time you will get an idea of the actual stack sizes that you need. That enables you to choose appropriate values that don't waste lots of memory.

This is part of the diagnostics in one of my programs that shows how memory is being used.

 Serial.print("heap ");
 Serial.print(ESP.getFreeHeap());
 Serial.print(" loop ");
 Serial.println(uxTaskGetStackHighWaterMark(NULL));
 Serial.print(" scanb ");
 Serial.print(uxTaskGetStackHighWaterMark(scanbutTaskHandle));
 Serial.print(" ota ");
 Serial.print(uxTaskGetStackHighWaterMark(otaTaskHandle));
1 Like