How to Establish Inter-communication between the Cores/Tasks of ESP32?

In the following working sketch, I have assigned Core 0 to acquire/display temperature signal from a LM35 sensor. Now, I wish that Core 0 would pass the temperature signal to Core 1 for onward submission to the Serial Monitor. As I am still learning the basics of the FreeRTOS of the ESP32 Platform, it would be helpful in my learning if someone provides the necessary codes (or gives some hints or even a test sketch) that I will integrate with my posted sketch.

TaskHandle_t task0Handle;

void setup()
{
  pinMode(2, OUTPUT);
  Serial.begin(115200);

  //create a task on core 0 that will be execute task1Func() with priority 10
  xTaskCreatePinnedToCore
  (
    task0,        /* Task function. */
    "Task1",      /* name of task. */
    10000,        /* Stack size of task */
    nullptr,      /* parameter of the task */
    5,            /* priority of the task */
    &task0Handle, /* Task handle to keep track of created task */
    0
  );           /* pin task to core 0 */
}

void task0( void *pvParameters ) //Core 0's task
{
  for (;;)
  {
    float myTemp = 100 * (3.3 / 4096.0) * analogRead(34);
    Serial.print("Temperature reading from LM35 Sensor: ");
    Serial.println(myTemp, 1);
    delay(1000);
  }
}

void loop()  //Core 1 testing
{
  digitalWrite(2, LOW);
  delay(250);
  digitalWrite(2, HIGH);
  delay(250);
}

Don't think so much in terms of "cores" but in terms of "tasks". Read the reference document that I linked in your other thread. Study topics such as Queues, Notifications, Semaphores, and Mutexes.

Rather than running your "Core 1" code in the loop function. Spin up an appropriate FreeRTOS task for it.

1 Like

I have downloaded your referred documents and have a quick glance to see if they contain any functions and they do. At this old age (68+), I prefer to have some working codes/examples on the state-of-art and then learn the underlying theory through reverse engineering.

There is plenty of example code in "Mastering the FreeRTOS Real Time Kernel - a Hands On Tutorial Guide". And the theory behind it is explained. You must first understand the concepts of time slicing, task priority, and inter-task communication.

1 Like

should probably look like this..

TaskHandle_t task0Handle;

float tempCore0;
float tempCore1;
static portMUX_TYPE my_spinlock = portMUX_INITIALIZER_UNLOCKED;

void setup()
{
  pinMode(2, OUTPUT);
  Serial.begin(115200);

  //create a task on core 0 that will be execute task1Func() with priority 10
  xTaskCreatePinnedToCore
  (
    task0,        /* Task function. */
    "Task1",      /* name of task. */
    10000,        /* Stack size of task */
    nullptr,      /* parameter of the task */
    5,            /* priority of the task */
    &task0Handle, /* Task handle to keep track of created task */
    0
  );           /* pin task to core 0 */
}

void task0( void *pvParameters ) //Core 0's task
{
  for (;;)
  {
    float myTemp = 100 * (3.3 / 4096.0) * analogRead(34);

    taskENTER_CRITICAL(&my_spinlock);
    tempCore0 = myTemp;
    taskEXIT_CRITICAL(&my_spinlock);
    delay(1000);
  }
}

void loop()  //Core 1 testing
{
  digitalWrite(2, LOW);
  delay(250);
  digitalWrite(2, HIGH);
  delay(250);
  taskENTER_CRITICAL(&my_spinlock);
  tempCore1 = tempCore0;
  taskEXIT_CRITICAL(&my_spinlock);
  Serial.println(tempCore1);

}

a critical section is used to protect access to a shared variable..
critical sections should be as short as possible..

reference material.. FreeRTOS (ESP-IDF)

have fun.. ~q

Once Core 0/Task 0 has processed the temperature signal, it will store the temperature into shared variable (tempCore0) and then Core 0 will notify Core 1 through interrupt.

In Task 0 of post #5, I don't see this code for interrupting the Core 1. After interruption, the Core 1 will jump to the ISR(), collect the temperature signal, and show it onto Serial Monitor. This is the idea I have conceived from my assembly language level working experience with 8086/8087 multi-processor system many years ago.

The current problem with mine is that I don't know how to transform these ideas into codes using functions of the FreeRTOS Library for which I have to study/consult the referred book @gfvalvo.

Anyway, I am taking @qubits-us's sketch as a building block and trying to add codes as needed based on my progress on FreeRTOS.

Well now, that's completely different from post 1..
good luck.. ~q

Here a queue is used to send a random int from loop0 which is running on core 1 to loop1 which is running on core 0. The blink routine runs on core 0 blinking the on board led and displaying free memory. You can modify the tasks to run on any core you desire, status is displayed on the serial monitor so you don't neccessarily need to connect the led's but they are nice to have. The main point is the demonstration of using a queue.

TaskHandle_t Task0;
TaskHandle_t Task1;
TaskHandle_t flash;
QueueHandle_t queue;

#define LED0 27
#define LED1 25
#define LED2 2
size_t xx=0;
size_t xPortGetFreeHeapSize( void );

void loop0(void * parameter) {
	for (;;) {

		// Use Arduino implementation for a number between limits
		int rndNumber = random(1, 9);
    Serial.print("\t\t\t\t\t\t\t\tLoop 0 - Running on core: ");
		Serial.println(xPortGetCoreID());
		// Add to the queue - wait forever until space is available
		Serial.println("\t\t\t\t\tMgr 0 - Adding " + String(rndNumber) + " to queue");
		xQueueSend(queue, &rndNumber, portMAX_DELAY);

		// Artificial wait here
		digitalWrite(LED0, HIGH);
		delay(200);
		digitalWrite(LED0, LOW);
		delay(500);

	}
}

void loop1(void * parameter) {
	for (;;) {
		// Get the number of flashes required
		int flashTotal;
		xQueueReceive(queue, &flashTotal, portMAX_DELAY);

    Serial.print("\t\t\t\t\t\t\t\tLoop 1 - Running on core: ");
		Serial.println(xPortGetCoreID());
		Serial.println("Worker - reading " + String(flashTotal));

		// Flash that number
		for (int cnt = 0; cnt < flashTotal; cnt++) {
			digitalWrite(LED1, HIGH);
			delay(150);
			digitalWrite(LED1, LOW);
			delay(150);
		}

	}
}


void blink(void * parameter) {
	for (;;) {

    xx=xPortGetFreeHeapSize( );   
		Serial.println(xx);
			digitalWrite(LED_BUILTIN, HIGH);
			delay(500);
			digitalWrite(LED_BUILTIN, LOW);
			delay(500);
	

	}
}

void setup()
{
	Serial.begin(115200);
  delay(1000);
	Serial.println("Setup started.");
  Serial.print("###");
  int cpuSpeed = getCpuFrequencyMhz();
  Serial.println(cpuSpeed);
	pinMode(LED0, OUTPUT);
	pinMode(LED1, OUTPUT);
  pinMode(LED_BUILTIN, OUTPUT);
	
  digitalWrite(LED2, HIGH);
	// Create the queue with 5 slots of 2 bytes
	queue = xQueueCreate(5, sizeof(int));

	xTaskCreatePinnedToCore(
			loop0, /* Function to implement the task */
			"Task0", /* Name of the task */
			1000, /* Stack size in words */
			NULL, /* Task input parameter */
			0, /* Priority of the task */
			&Task0, /* Task handle. */
			1); /* Core where the task should run */

	xTaskCreatePinnedToCore(
			loop1, /* Function to implement the task */
			"Task1", /* Name of the task */
			1000, /* Stack size in words */
			NULL, /* Task input parameter */
			0, /* Priority of the task */
			&Task1, /* Task handle. */
			0); /* Core where the task should run */

  xTaskCreatePinnedToCore(
			blink, /* Function to implement the task */
			"flash", /* Name of the task */
			1000, /* Stack size in words */
			NULL, /* Task input parameter */
			0, /* Priority of the task */
			&flash, /* Task handle. */
			0); /* Core where the task should run */

	Serial.println("Setup completed.");
}

void loop()
{
  Serial.println("Setup completed.");
	vTaskDelete (NULL);
}

I don't recall where I got this from if I could I would credit the original programmer, I believe it was from a YouTube article

EDIT. I included the blink routine to see what effect "delay" had on the other tasks, from what I had read a delay in RTOS is automatically treated as a "millis" delay in other words you can use "delay" as non blocking code. If anyone can confirm or deny please let us know.

I have tested your sketch of post #5, and it is working. This is the output:

19.90
19.90
20.22
20.22
20.62
20.62

Now, I wish to know how Core 0/task 0 is notifying Core 1 that the temperature signal is available in the shared variable, tempCore0. Or there is no notification/interruption at all?

@GolamMostafa
How should I express it. You are on a wrong track.

See a task as an activity to be done.
from the activity point of view it doesn't matter if it will be performed on core0 ore core1.
tasks on core0 and core1 could run in real parallel. That's because there are two cores.
If tasks are running, RTOS cares about how much time each task gets to perform it's task.
So if you have variables which are changed in several tasks you need measures, that this changes are atomic.
The same applies to hardware resources like a Serial output (read about semaphore and MUTEX).
If you send data from several tasks to Serial, you must ensure, that the first task has completed its activities before you use the Serial in another task.
Use Semaphore/MUTEX for that.

@GolamMostafa ... I hope this makes any sense for you...

1 Like

For a simple model of Sender and Receiver tasks, a Queue is probably the best structure for inter-task synchronization and data transfer.

1 Like

Interesting sketch in post #8! I will test it.

I hope to go in the implementations of both "Queued Approach" and "Interrupt Approach" while the later receives my interest to reveal the fact that there is indeed "interconnecting physical wire (s)" between the two processors/cores.

I heard the names and definitions of these words in course work about 30 years ago, which must had been faded in the meantime. Now, facing the real applications of these concepts (Mutex = Mutual Exclusion Lock and Semaphore = Signaling) being in the Arduino Forum in context of task scheduling in Dual-Core ESP32. Without any reservation, learning spans from cradle to grave!!!

Your "Interrupt Approach" is wrong headed for the RTOS environment you're programming in.

Are the cores not connected by physical interrupt lines?

I've already told you the proper way to establish inter-task synchronization and data transfer. I will not be participating in your folly as you travel down the path of your imaginary solution.

1 Like

Do you think that I am fooled by the following information?

"The cores in the ESP32 are indeed connected by physical interrupt lines. Each core has its own hardware interrupt controller, and these interrupt controllers are connected to a central interrupt controller called the "Interrupt Controller Unit" (IPU). The IPU is responsible for managing and distributing interrupts among the cores.

The ESP32's IPU allows interrupts to be sent between cores using a mechanism called "Inter-Processor Interrupts" (IPIs). These IPIs are used for inter-core communication, but they are not the typical hardware-based interrupts you would use for peripherals or external devices.

When one core wants to interrupt the other core, it can send an IPI request to the target core using the xPortTriggerCoreIPI() function from the FreeRTOS API. The target core will then execute the corresponding interrupt service routine (ISR) for the IPI."

Please post the source of that quote and the documentation for the xPortTriggerCoreIPI() function.

The source is ChatGPT. I searched here in the referred Book/Manual; but, the said function is not found.