In embedded development, when facing multicore processors (such as the popular ESP32 dual-core chip), how can we efficiently allocate tasks and avoid resource contention? Today, we will introduce the <span>xTaskCreatePinnedToCore</span> function, which is FreeRTOS’s “task scheduling magic tool” tailored for multicore scenarios. We will guide you through a complete project example to help you fully understand its usage and essence.
1. What is xTaskCreatePinnedToCore?
In simple terms, <span>xTaskCreatePinnedToCore</span> is a task creation function provided by FreeRTOS, but it has an “extra ability”—to specify which CPU core the task runs on.
For example, in a dual-core device like the ESP32, you can bind tasks with high real-time requirements (such as sensor data collection) to one core, while placing network communication, logging, and other tasks on the other core, thus eliminating the hassle of “core resource contention” and allowing the system to run more smoothly!
2. Complete Project Example: Dual-Core Task Division
Below, we will use the ESP32 as an example to implement a “temperature and humidity collection + network reporting” dual-core task system. Core 0 is responsible for low-priority network reporting, while Core 1 handles high-priority sensor data collection, achieved through <span>xTaskCreatePinnedToCore</span> for core binding.
Project Structure
esp32_dual_core_demo/
├── main.c // Main program entry
├── sensor.c // Sensor collection logic
├── network.c // Network reporting logic
└── CMakeLists.txt // Build configuration
Core Code Implementation
1. main.c (Task Creation Entry)
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "sensor.h"
#include "network.h"
// Task handles (global variables for later task control)
TaskHandle_t sensor_task_handle = NULL;
TaskHandle_t network_task_handle = NULL;
void app_main(void) {
// Initialize sensor and network
sensor_init();
network_init();
// Create sensor collection task (bound to core 1, high priority)
xTaskCreatePinnedToCore(
sensor_task, // Task entry function
"sensor_task", // Task name
4096, // Stack size (4096 words = 16KB)
NULL, // Parameters (no parameters needed here)
10, // Priority (high, 10)
&sensor_task_handle,// Task handle
1 // Run on core 1
);
// Create network reporting task (bound to core 0, low priority)
xTaskCreatePinnedToCore(
network_task, // Task entry function
"network_task", // Task name
8192, // Stack size (8192 words = 32KB, larger stack needed for network operations)
NULL, // Parameters (no parameters needed here)
5, // Priority (low, 5)
&network_task_handle,// Task handle
0 // Run on core 0
);
}
2. sensor.c (Sensor Collection Task)
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/gpio.h"
#include "esp_log.h"
static const char* TAG = "sensor";
float g_temperature = 0.0f; // Global temperature variable (for network task to read)
float g_humidity = 0.0f; // Global humidity variable
void sensor_init(void) {
// Initialize sensor hardware (e.g., DHT11)
gpio_set_direction(GPIO_NUM_4, GPIO_MODE_INPUT_OUTPUT);
ESP_LOGI(TAG, "Sensor initialized");
}
// Sensor task entry function
void sensor_task(void* arg) {
while (1) {
// Simulate sensor collection (replace with actual reading logic in real projects)
g_temperature += 0.1f;
g_humidity = (g_temperature * 1.5f) + 30;
ESP_LOGI(TAG, "Core %d: Temp=%.1f°C, Hum=%.1f%%",
xPortGetCoreID(), // Get the current running core ID
g_temperature,
g_humidity);
vTaskDelay(pdMS_TO_TICKS(100)); // Collect every 100ms (high frequency)
}
}
3. network.c (Network Reporting Task)
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "sensor.h" // Reference global temperature and humidity variables
static const char* TAG = "network";
void network_init(void) {
// Initialize network (e.g., WiFi, MQTT)
ESP_LOGI(TAG, "Network initialized");
}
// Network task entry function
void network_task(void* arg) {
while (1) {
// Simulate network reporting (replace with actual HTTP/MQTT sending logic in real projects)
ESP_LOGI(TAG, "Core %d: Upload - Temp=%.1f°C, Hum=%.1f%%",
xPortGetCoreID(), // Get the current running core ID
g_temperature,
g_humidity);
vTaskDelay(pdMS_TO_TICKS(1000)); // Report every 1s (low frequency)
}
}
3. Detailed Explanation of Function Parameters: Understanding 7 Parameters at a Glance
Combining the example above, we will break down the 7 parameters of <span>xTaskCreatePinnedToCore</span> in detail:
xTaskCreatePinnedToCore(
Task entry function, // Parameter 1
Task name, // Parameter 2
Stack size, // Parameter 3
Parameters, // Parameter 4
Priority, // Parameter 5
Task handle, // Parameter 6
CPU core number // Parameter 7
);
1. Task Entry Function (Parameter 1)
This is where the “core logic” of the task resides, and it must be a function of type <span>void(*)(void*)</span> (such as the <span>sensor_task</span> and <span>network_task</span> in the example). The function typically contains an infinite loop, yielding CPU resources through <span>vTaskDelay</span>.
2. Task Name (Parameter 2)
This is a string identifier (e.g., <span>"sensor_task"</span>) used for debugging (to view the task list through <span>freeRTOS</span> tools). It is recommended to name it clearly, with a length not exceeding 16 characters.
3. Stack Size (Parameter 3)
The unit is “words” (1 word = 4 bytes in ESP32):
- • In the example, the stack size for
<span>sensor_task</span>is<span>4096</span>words =<span>4096×4 = 16KB</span>(simple task); - • The stack size for
<span>network_task</span>is<span>8192</span>words =<span>32KB</span>(larger stack needed for network operations).
Note: A stack that is too small can cause task crashes. You can check the remaining stack space using the <span>uxTaskGetStackHighWaterMark</span> function (the return value is in “words”).
4. Parameters Passed to the Task (Parameter 4)
This is used to pass data to the task function (as in the example, it is not used, so it is filled with <span>NULL</span>). If parameters need to be passed, refer to the following example:
// Example of passing a structure parameter
typedef struct {
int id;
char name[20];
} TaskParams;
TaskParams params = {1, "task1"};
xTaskCreatePinnedToCore(task_func, "task", 4096, ¶ms, 5, NULL, 0);
// Receiving parameters in the task function
void task_func(void* arg) {
TaskParams* p = (TaskParams*)arg;
ESP_LOGI(TAG, "ID: %d, Name: %s", p->id, p->name);
}
5. Task Priority (Parameter 5)
The higher the value, the higher the priority (the range is configured by <span>configMAX_PRIORITIES</span>, default 0~31):
- • In the example, the priority of
<span>sensor_task</span>is 10 (high, ensuring quick response); - • The priority of
<span>network_task</span>is 5 (low, not affecting sensor collection).
6. Task Handle (Parameter 6)
This is the unique identifier for the task (such as <span>sensor_task_handle</span> in the example), used for later task control:
// Suspend task
vTaskSuspend(sensor_task_handle);
// Resume task
vTaskResume(sensor_task_handle);
// Delete task
vTaskDelete(sensor_task_handle);
7. CPU Core Number (Parameter 7)
This specifies which core the task runs on (ESP32 supports 0 and 1):
- • In the example, the sensor task is bound to core 1 (high real-time requirement);
- • The network task is bound to core 0 (to reduce the load on core 1).
You can obtain the current running core ID in the task using the <span>xPortGetCoreID()</span> function (as shown in the example’s log output).
4. Usage Scenarios: Why Bind Cores?
<span>xTaskCreatePinnedToCore</span> is best suited for multicore processor scenarios, especially when “isolating tasks” is required:
- • Bind tasks with high real-time requirements (such as sensor collection, motor control) to one core to avoid interference from other tasks;
- • Bind time-consuming but low real-time requirement tasks (such as logging, network requests) to another core to prevent resource occupation;
- • In multi-task cooperation, reduce task switching overhead by fixing cores, improving system efficiency.
5. Common Issues and Solutions When Using xTaskCreatePinnedToCore
1. Task Creation Failure (returns pdFAIL)
- • Possible Reasons:
- • Stack space set too large, causing insufficient system memory;
- • Task priority exceeds
<span>configMAX_PRIORITIES</span>configuration (default maximum 32, priority 0~31); - • Incorrect core number (e.g., ESP32 filled in 2 or negative numbers).
- • Solutions:
- • Reduce stack size, or optimize memory allocation using
<span>heap_caps_malloc</span>; - • Lower priority to within
<span>configMAX_PRIORITIES - 1</span>; - • Check core number range (ESP32 can only be 0 or 1).
2. Task Crashes or Data Corruption During Execution
- • Possible Reasons:
- • Stack overflow (using many local variables or recursive calls in the task);
- • Shared global variables among multiple tasks without locking (e.g., sensor task and network task operating on
<span>g_temperature</span>simultaneously); - • Task function exits (forgetting to write an infinite loop, causing the task to end unexpectedly).
- • Solutions:
- • Increase stack space, or check for stack overflow using
<span>uxTaskGetStackHighWaterMark</span>; - • Use mutexes to protect shared resources:
// Create mutex SemaphoreHandle_t mutex = xSemaphoreCreateMutex(); // Lock before accessing shared resource xSemaphoreTake(mutex, portMAX_DELAY); // Operate on shared variable... xSemaphoreGive(mutex); // Release lock - • Ensure the task function contains an
<span>while(1)</span>infinite loop.
3. Core Load Imbalance (One Core Overloaded)
- • Possible Reasons:
- • Multiple high-priority tasks bound to the same core;
- • Long blocking operations in tasks (e.g., not setting timeouts for
<span>xQueueReceive</span>). - • Solutions:
- • Distribute high-priority tasks across different cores;
- • Set timeout for blocking operations to avoid permanent core occupation:
// Set timeout for queue receive (100ms) xQueueReceive(queue, &data, pdMS_TO_TICKS(100));
6. Conclusion
<span>xTaskCreatePinnedToCore</span> is a core tool for multicore development in FreeRTOS, allowing flexible creation and binding of tasks to specified CPU cores through 7 parameters. The complete project example in this article demonstrates how to implement dual-core task division on the ESP32, mastering its usage can make your embedded system more efficient and stable in multitasking.
Next time you develop a multicore device, consider using it to manage tasks, allowing each core to “perform its duties”!