Saturday, July 1, 2017

Demo 25: How to configure ESP32 Dual core - Multicore in Arduino ESP32

1. Introduction
- ESP32 is a big improvement of ESP8266 (after taking time to play with it, I see that it is faster, more stable than ESP8266). One of special features of ESP32 is that it support dual core. This demo will show you how to configure ESP32 Multicore using Arduino ESP32.
2. Demo
- We will re-use Queue tutorial. In this demo, we create 2 tasks, 1 task called "sendTask" and 1 task called "receiveTask". The "sendTask" is pinned on core 0. The "receiveTask" is pinned on core 1. The "sendTask" will send data to the "receiveTask" every second through Queue.
- In order to pin the task to a specific core we will use the FreeRTOS API function xTaskCreatePinnedToCore instead of using xTaskCreate. The API xTaskCreatePinnedToCore has the last argument is the core that the task will be pinned to. Beside that we can use the API function xTaskGetAffinity to know which core the task was pinned to. This function has one argument. That is the task handler which is created using xTaskCreatePinnedToCore.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
/* structure that hold data*/
typedef struct{
  int sender;
  char *msg;
}Data;

/* this variable hold queue handle */
xQueueHandle xQueue;
TaskHandle_t xTask1;
TaskHandle_t xTask2;
void setup() {

  Serial.begin(112500);
  /* create the queue which size can contains 5 elements of Data */
  xQueue = xQueueCreate(5, sizeof(Data));

  xTaskCreatePinnedToCore(
      sendTask,           /* Task function. */
      "sendTask",        /* name of task. */
      10000,                    /* Stack size of task */
      NULL,                     /* parameter of the task */
      1,                        /* priority of the task */
      &xTask1,                /* Task handle to keep track of created task */
      0);                    /* pin task to core 0 */
  xTaskCreatePinnedToCore(
      receiveTask,           /* Task function. */
      "receiveTask",        /* name of task. */
      10000,                    /* Stack size of task */
      NULL,                     /* parameter of the task */
      1,                        /* priority of the task */
      &xTask2,            /* Task handle to keep track of created task */
      1);                 /* pin task to core 1 */   
}

void loop() {

}

void sendTask( void * parameter )
{
  /* keep the status of sending data */
  BaseType_t xStatus;
  /* time to block the task until the queue has free space */
  const TickType_t xTicksToWait = pdMS_TO_TICKS(100);
  /* create data to send */
  Data data;
  /* sender 1 has id is 1 */
  data.sender = 1;
  for(;;){
    Serial.print("sendTask run on core ");
    /* get the core that the task was pinned to */
    Serial.print(xTaskGetAffinity(xTask1));
    Serial.println(" is sending data");
    data.msg = (char *)malloc(20);
    memset(data.msg, 0, 20);
    memcpy(data.msg, "hello world", strlen("hello world"));
    /* send data to front of the queue */
    xStatus = xQueueSendToFront( xQueue, &data, xTicksToWait );
    /* check whether sending is ok or not */
    if( xStatus == pdPASS ) {
      /* increase counter of sender 1 */
      Serial.println("sendTask sent data");
    }
    /* we delay here so that receiveTask has chance to receive data */
    delay(1000);
  }
  vTaskDelete( NULL );
}

void receiveTask( void * parameter )
{
  /* keep the status of receiving data */
  BaseType_t xStatus;
  /* time to block the task until data is available */
  const TickType_t xTicksToWait = pdMS_TO_TICKS(100);
  Data data;
  for(;;){
    /* receive data from the queue */
    xStatus = xQueueReceive( xQueue, &data, xTicksToWait );
    /* check whether receiving is ok or not */
    if(xStatus == pdPASS){
      Serial.print("receiveTask run on core ");
      /* get the core that the task was pinned to */
      Serial.print(xTaskGetAffinity(xTask2));
      /* print the data to terminal */
      Serial.print(" got data: ");
      Serial.print("sender = ");
      Serial.print(data.sender);
      Serial.print(" msg = ");
      Serial.println(data.msg);
      free(data.msg);
    }
  }
  vTaskDelete( NULL );
}
Figure: multicore on ESP32

6 comments:

isuru iot said...

can we run udp server as a task.......

iotsharing dotcom said...

yes you can

Yves De Saedeleer said...

There seems to be a way where one can use a esp_intr_alloc() function to "pin a external interrupt" to a core of choice ...
But i can nowhere find a example of the use of "esp_intr_alloc() and creating a task pinned to a core.
Can i get some way to point me to the end of the tunnel overhere?
Grtz

Lynxxde said...
This comment has been removed by the author.
Lynxxde said...

Hi Yves,
There is a function which returns the cpu used for the interrupt in esp32\tools\sdk\include\esp32\esp_intr_alloc.h:
--------------------------------------------------------------------------
/**
* @brief Get CPU number an interrupt is tied to
*
* @param handle The handle, as obtained by esp_intr_alloc or esp_intr_alloc_intrstatus
*
* @return The core number where the interrupt is allocated
*/
int esp_intr_get_cpu(intr_handle_t handle);
--------------------------------------------------------------------------


This function is only used twice, once in esp32\cores\esp32\esp32-hal-gpio.c __attachInterrupt
where it enables int1 or 4 depending on which cpu the interrupt was created:
--------------------------------------------------------------------------
...
if(esp_intr_get_cpu(gpio_intr_handle)) { //APP_CPU
GPIO.pin[pin].int_ena = 1;
} else { //PRO_CPU
GPIO.pin[pin].int_ena = 4;
}
...
--------------------------------------------------------------------------

More interresting for you is probably esp32\cores\esp32\esp32-hal-timer.c timerAttachInterrupt
where it's used in this way:
intr_matrix_set(esp_intr_get_cpu(intr_handle), intr_source, esp_intr_get_intno(intr_handle));

This function is defined in esp32\tools\sdk\include\esp32\rom\ets_sys.h
--------------------------------------------------------------------------
/**
* @brief Attach an CPU interrupt to a hardware source.
* We have 4 steps to use an interrupt:
* 1.Attach hardware interrupt source to CPU. intr_matrix_set(0, ETS_WIFI_MAC_INTR_SOURCE, ETS_WMAC_INUM);
* 2.Set interrupt handler. xt_set_interrupt_handler(ETS_WMAC_INUM, func, NULL);
* 3.Enable interrupt for CPU. xt_ints_on(1 << ETS_WMAC_INUM);
* 4.Enable interrupt in the module.
*
* @param int cpu_no : The CPU which the interrupt number belongs.
*
* @param uint32_t model_num : The interrupt hardware source number, please see the interrupt hardware source table.
*
* @param uint32_t intr_num : The interrupt number CPU, please see the interrupt cpu using table.
*
* @return None
*/
void intr_matrix_set(int cpu_no, uint32_t model_num, uint32_t intr_num);
--------------------------------------------------------------------------

esp32\tools\sdk\include\esp32\esp_intr_alloc.h defines the esp_intr_get_intno:
--------------------------------------------------------------------------
/**
* @brief Get the allocated interrupt for a certain handle
*
* @param handle The handle, as obtained by esp_intr_alloc or esp_intr_alloc_intrstatus
*
* @return The interrupt number
*/
int esp_intr_get_intno(intr_handle_t handle);
--------------------------------------------------------------------------

Have phun. :D

Anonymous said...

Don Warr Calgary AB, Canada..

This tut helped in my time of need,,, Thank you so much.
Your About page say's "Share your knowledge. It is a way to achieve immortality."
This is true, But Helping Others, Helps gets you to Heaven too... LoL
Keep up the wonderful work, not everyone can do it..