1. FreeRTOS Multitasking Architecture
1.1 Overview of Task Model
As a lightweight real-time operating system, the core of FreeRTOS is its multitasking capability. In FreeRTOS, applications are organized as a set of independent tasks, each executing in its own context (such as a separate stack space) without dependencies on each other.
Each task can be viewed as an independent program, possessing its own entry function, stack space, and local variables. The scheduler is responsible for starting, stopping, switching in, and switching out tasks.
1.2 Task States and Transitions
Tasks in FreeRTOS can be in the following states:
- • Running: Currently executing on the CPU
- • Ready: Can run but is not currently allocated CPU time
- • Blocked: Waiting for an event or timeout
- • Suspended: Manually suspended via API, not participating in scheduling
- • Deleted: Task has been deleted, resources released
Task state transition diagram:
Create Scheduler Selects
-----> [Ready] ----------> [Running]
^ ^ | |
| | Time Slice Expired | |
| +<----------------+ |
| |
| Wait for Event/Timeout |
+<----------------------+
^ |
| v
Resume| |Manually Suspend
| |
[Suspended] <---------------+
2. Task Creation and Lifecycle Management
2.1 Creating Tasks
In FreeRTOS, tasks are typically created using the<span>xTaskCreate()</span> function:
/* Create Data Collection Task */
void vSensorTask(void *pvParameters)
{
// Task initialization code
float sensorData = 0.0f;
// Task main loop
while(1)
{
// Read sensor data
sensorData = readSensor();
// Send data to processing queue
xQueueSend(dataQueue, &sensorData, portMAX_DELAY);
// Delay 100ms
vTaskDelay(pdMS_TO_TICKS(100));
}
// Task should never reach here, if it does, it should delete itself
vTaskDelete(NULL);
}
// Create task in main function
BaseType_t xReturned;
TaskHandle_t xSensorTaskHandle = NULL;
xReturned = xTaskCreate(
vSensorTask, /* Task function */
"Sensor", /* Task name */
configMINIMAL_STACK_SIZE*2, /* Stack size (words)*/
NULL, /* Task parameters */
tskIDLE_PRIORITY + 1, /* Priority */
&xSensorTaskHandle /* Task handle */
);
if(xReturned != pdPASS)
{
/* Task creation failed, usually due to insufficient memory */
configASSERT(0);
}
2.2 Task Control
2.2.1 Suspending and Resuming Tasks
// Suspend sensor task under specific conditions
if(systemStatus == SYSTEM_ERROR)
{
vTaskSuspend(xSensorTaskHandle);
printf("Sensor task suspended\r\n");
}
// Restart task after system recovery
if(systemStatus == SYSTEM_NORMAL)
{
vTaskResume(xSensorTaskHandle);
printf("Sensor task resumed\r\n");
}
2.2.2 Deleting Tasks
// Delete tasks that are no longer needed
vTaskDelete(xSensorTaskHandle);
xSensorTaskHandle = NULL;
2.2.3 Delaying Tasks
// Simple delay (relative delay)
vTaskDelay(pdMS_TO_TICKS(1000)); // Delay 1 second
// Precise periodic delay (absolute delay)
TickType_t xLastWakeTime = xTaskGetTickCount();
for(;;)
{
// Perform periodic work
doPeriodicWork();
// Fixed period 500ms
vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(500));
}
3. Advanced Task Scheduling Strategies
3.1 Priority Scheduling
FreeRTOS uses a priority scheduling algorithm where high-priority tasks can preempt low-priority tasks:
// Define different priorities
#define PRIORITY_HIGH (configMAX_PRIORITIES - 1)
#define PRIORITY_MEDIUM (configMAX_PRIORITIES / 2)
#define PRIORITY_LOW (tskIDLE_PRIORITY + 1)
// Create tasks with different priorities
xTaskCreate(vEmergencyTask, "Emergency", 1024, NULL, PRIORITY_HIGH, NULL);
xTaskCreate(vNormalTask, "Normal", 1024, NULL, PRIORITY_MEDIUM, NULL);
xTaskCreate(vBackgroundTask,"Background",1024, NULL, PRIORITY_LOW, NULL);
3.2 Time-Slicing
For tasks of the same priority, FreeRTOS uses time-slicing scheduling:
// Create two tasks of the same priority
xTaskCreate(vProcessTask1, "Process1", 1024, NULL, PRIORITY_MEDIUM, NULL);
xTaskCreate(vProcessTask2, "Process2", 1024, NULL, PRIORITY_MEDIUM, NULL);
// These two tasks will execute alternately, each task executing for configTICK_RATE_HZ clock cycles
3.3 Dynamic Priority Adjustment
// Temporarily increase priority based on task importance
void vHandleImportantEvent(void)
{
// Save original priority
UBaseType_t uxOriginalPriority;
uxOriginalPriority = uxTaskPriorityGet(NULL);
// Temporarily raise current task priority
vTaskPrioritySet(NULL, PRIORITY_HIGH);
// Handle important event
processImportantEvent();
// Restore original priority
vTaskPrioritySet(NULL, uxOriginalPriority);
}
4. Inter-Task Communication Mechanisms
4.1 Queue Communication
// Create queue
QueueHandle_t xDataQueue;
xDataQueue = xQueueCreate(10, sizeof(SensorData_t));
// Sender task
void vSensorTask(void *pvParameters)
{
SensorData_t xData;
while(1)
{
// Collect data
xData.value = readSensor();
xData.timestamp = xTaskGetTickCount();
// Send to queue
if(xQueueSend(xDataQueue, &xData, pdMS_TO_TICKS(100)) != pdPASS)
{
// Handle send failure
errorHandler(ERROR_QUEUE_FULL);
}
vTaskDelay(pdMS_TO_TICKS(100));
}
}
// Receiver task
void vProcessTask(void *pvParameters)
{
SensorData_t xReceivedData;
while(1)
{
// Receive data from queue
if(xQueueReceive(xDataQueue, &xReceivedData, portMAX_DELAY) == pdPASS)
{
// Process data
processData(&xReceivedData);
}
}
}
4.2 Semaphores
// Create binary semaphore (mutex)
SemaphoreHandle_t xMutex;
xMutex = xSemaphoreCreateMutex();
// Use mutex to protect shared resource
void vTask(void *pvParameters)
{
while(1)
{
// Attempt to take mutex
if(xSemaphoreTake(xMutex, pdMS_TO_TICKS(500)) == pdTRUE)
{
// Access shared resource
accessSharedResource();
// Release mutex
xSemaphoreGive(xMutex);
}
else
{
// Handle inability to get mutex
handleMutexTimeout();
}
vTaskDelay(pdMS_TO_TICKS(100));
}
}
4.3 Event Groups
// Create event group
EventGroupHandle_t xEventGroup;
xEventGroup = xEventGroupCreate();
// Define event bits
#define EVENT_TEMPERATURE_BIT (1 << 0)
#define EVENT_HUMIDITY_BIT (1 << 1)
#define EVENT_PRESSURE_BIT (1 << 2)
#define ALL_SENSOR_BITS (EVENT_TEMPERATURE_BIT | EVENT_HUMIDITY_BIT | EVENT_PRESSURE_BIT)
// Producer task
void vTemperatureTask(void *pvParameters)
{
while(1)
{
// Read temperature
readTemperature();
// Set corresponding event bit
xEventGroupSetBits(xEventGroup, EVENT_TEMPERATURE_BIT);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
// Consumer task
void vProcessingTask(void *pvParameters)
{
EventBits_t uxBits;
while(1)
{
// Wait for all sensor data to be ready
uxBits = xEventGroupWaitBits(
xEventGroup, // Event group
ALL_SENSOR_BITS, // Bits to wait for
pdTRUE, // Clear bits
pdTRUE, // Wait for all bits
portMAX_DELAY); // Wait indefinitely
// All data ready, process it
processSensorData();
}
}
5. Exception Handling and Troubleshooting in Multitasking
5.1 Common Exception Types
5.1.1 Stack Overflow
// Configure stack overflow hook function
void vApplicationStackOverflowHook(TaskHandle_t xTask, signed char *pcTaskName)
{
// Log stack overflow information
printf("Stack Overflow: Task[%s] has overflowed\r\n", pcTaskName);
// System reset or other handling
NVIC_SystemReset();
}
5.1.2 Priority Inversion
// Use priority inheritance mutex to prevent priority inversion
SemaphoreHandle_t xMutex;
xMutex = xSemaphoreCreateMutex();
// High priority task
void vHighPriorityTask(void *pvParameters)
{
while(1)
{
// Attempt to take mutex
if(xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE)
{
// Access shared resource
accessSharedResource();
// Release mutex
xSemaphoreGive(xMutex);
}
vTaskDelay(pdMS_TO_TICKS(100));
}
}
5.1.3 Deadlock Detection
// Use timeout mechanism to prevent deadlock
void vTask(void *pvParameters)
{
while(1)
{
// Attempt to take mutex with timeout
if(xSemaphoreTake(xMutex, pdMS_TO_TICKS(1000)) == pdTRUE)
{
// Access shared resource
accessSharedResource();
// Release mutex
xSemaphoreGive(xMutex);
}
else
{
// Timeout, possible deadlock
printf("Warning: Possible deadlock\r\n");
// Log system state or restart
}
vTaskDelay(pdMS_TO_TICKS(100));
}
}
5.2 Task Monitoring and Debugging
5.2.1 Watchdog Task
// Define task state monitoring structure
typedef struct {
TaskHandle_t handle;
uint32_t lastCheckpoint;
bool healthy;
} TaskMonitor_t;
TaskMonitor_t taskMonitors[MAX_MONITORED_TASKS];
// Watchdog task implementation
void vWatchdogTask(void *pvParameters)
{
while(1)
{
// Check the state of each task
for(int i = 0; i < MAX_MONITORED_TASKS; i++)
{
if(taskMonitors[i].handle != NULL)
{
// Get task state
eTaskState state = eTaskGetState(taskMonitors[i].handle);
// Check if task has updated its checkpoint
if(taskMonitors[i].lastCheckpoint == 0 ||
(state == eBlocked && taskMonitors[i].healthy == false))
{
// Task may be stuck
handleTaskFailure(i);
}
}
}
// Feed hardware watchdog
HAL_IWDG_Refresh(&hiwdg);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
// Task checkpoint update
#define UPDATE_TASK_CHECKPOINT() do { \
for(int i = 0; i < MAX_MONITORED_TASKS; i++) { \
if(taskMonitors[i].handle == xTaskGetCurrentTaskHandle()) { \
taskMonitors[i].lastCheckpoint = xTaskGetTickCount(); \
taskMonitors[i].healthy = true; \
break; \
} \
} \
} while(0)
5.2.2 Task Statistics
// Define task statistics structure
typedef struct {
char taskName[configMAX_TASK_NAME_LEN];
UBaseType_t priority;
uint32_t stackRemaining;
eTaskState state;
uint32_t runTime; // Percentage * 100
} TaskStats_t;
// Statistics task implementation
void vStatisticsTask(void *pvParameters)
{
TaskStats_t stats[MAX_TASKS];
UBaseType_t uxArraySize, x;
while(1)
{
// Get number of tasks
uxArraySize = uxTaskGetNumberOfTasks();
// Iterate through all tasks
for(x = 0; x < uxArraySize; x++)
{
TaskHandle_t xHandle;
TaskStatus_t xTaskDetails;
// Get next task handle
xHandle = xTaskGetIdleTaskHandle(); // Simplified, actual needs to iterate through all tasks
// Get task details
vTaskGetInfo(xHandle, &xTaskDetails, pdTRUE, eInvalid);
// Fill statistics structure
strncpy(stats[x].taskName, xTaskDetails.pcTaskName, configMAX_TASK_NAME_LEN);
stats[x].priority = xTaskDetails.uxCurrentPriority;
stats[x].stackRemaining = xTaskDetails.usStackHighWaterMark;
stats[x].state = xTaskDetails.eCurrentState;
}
// Print statistics information
printTaskStats(stats, uxArraySize);
vTaskDelay(pdMS_TO_TICKS(5000));
}
}
6. Industrial Application Case Analysis
6.1 Smart Sensor Network System
The following is a simplified implementation of a smart sensor network system, including data collection, processing, storage, and communication tasks:
// System architecture diagram
/*
+---------------+ +---------------+ +---------------+
| Sensor Collection Task | -> | Data Processing Task | -> | Data Storage Task |
+---------------+ +---------------+ +---------------+
| | |
v v v
+-----------------------------------------------+
| Monitoring Task |
+-----------------------------------------------+
^ ^ ^
| | |
+---------------+ +---------------+ +---------------+
| Network Communication Task | <- | Command Processing Task | <- | Alarm Processing Task |
+---------------+ +---------------+ +---------------+
*/
// Main queue and semaphore definitions
QueueHandle_t xRawDataQueue; // Raw data queue
QueueHandle_t xProcessedQueue; // Processed data queue
QueueHandle_t xStorageQueue; // Storage task queue
QueueHandle_t xAlarmQueue; // Alarm queue
QueueHandle_t xCommandQueue; // Command queue
SemaphoreHandle_t xSpiMutex; // SPI bus mutex
SemaphoreHandle_t xI2CMutex; // I2C bus mutex
// Sensor collection task
void vSensorTask(void *pvParameters)
{
SensorData_t xData;
// Task initialization
sensorInit();
while(1)
{
// Get access to SPI bus
if(xSemaphoreTake(xSpiMutex, pdMS_TO_TICKS(100)) == pdPASS)
{
// Read sensor data
xData.temperature = readTemperature();
xData.humidity = readHumidity();
xData.pressure = readPressure();
xData.timestamp = xTaskGetTickCount();
// Release SPI bus
xSemaphoreGive(xSpiMutex);
// Send data to processing queue
xQueueSend(xRawDataQueue, &xData, pdMS_TO_TICKS(10));
// Threshold check
if(xData.temperature > TEMP_HIGH_THRESHOLD)
{
AlarmData_t xAlarm = {
.type = ALARM_HIGH_TEMPERATURE,
.value = xData.temperature,
.timestamp = xData.timestamp
};
xQueueSend(xAlarmQueue, &xAlarm, pdMS_TO_TICKS(10));
}
}
// Periodic delay
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
// Data processing task
void vProcessTask(void *pvParameters)
{
SensorData_t xRawData;
ProcessedData_t xProcessedData;
while(1)
{
// Wait for raw data
if(xQueueReceive(xRawDataQueue, &xRawData, portMAX_DELAY) == pdPASS)
{
// Data filtering and processing
xProcessedData.temperature = applyFilter(xRawData.temperature);
xProcessedData.humidity = applyFilter(xRawData.humidity);
xProcessedData.pressure = applyFilter(xRawData.pressure);
xProcessedData.timestamp = xRawData.timestamp;
// Calculate statistics
calculateStatistics(&xProcessedData);
// Send to storage queue
xQueueSend(xProcessedQueue, &xProcessedData, pdMS_TO_TICKS(100));
}
// Checkpoint update
UPDATE_TASK_CHECKPOINT();
}
}
// Network communication task
void vNetworkTask(void *pvParameters)
{
ProcessedData_t xData;
CommandData_t xCommand;
// Initialize network
networkInit();
while(1)
{
// Check if there is data to send
if(xQueueReceive(xProcessedQueue, &xData, pdMS_TO_TICKS(10)) == pdPASS)
{
// Send data to cloud platform
sendDataToCloud(&xData);
}
// Check for new commands
if(checkForRemoteCommands(&xCommand))
{
// Send to command queue
xQueueSend(xCommandQueue, &xCommand, pdMS_TO_TICKS(10));
}
// Checkpoint update
UPDATE_TASK_CHECKPOINT();
vTaskDelay(pdMS_TO_TICKS(50));
}
}
6.2 Real-Time Control System Case
// Define control system parameters
typedef struct {
float setPoint; // Setpoint
float currentValue; // Current value
float kp, ki, kd; // PID parameters
float integral; // Integral term
float lastError; // Last error
float outputLimit; // Output limit
} PIDController_t;
// Control task
void vControlTask(void *pvParameters)
{
PIDController_t xPID = {
.setPoint = 25.0f,
.kp = 1.2f,
.ki = 0.1f,
.kd = 0.05f,
.integral = 0.0f,
.lastError = 0.0f,
.outputLimit = 100.0f
};
float sensorValue = 0.0f;
float controlOutput = 0.0f;
// Initialize hardware
initActuators();
// Get sampling time
TickType_t xLastWakeTime = xTaskGetTickCount();
const float dt = 0.01f; // 10ms control cycle
while(1)
{
// Read sensor value
if(xQueueReceive(xSensorQueue, &sensorValue, 0) != pdPASS)
{
// If queue is empty, read directly
xSemaphoreTake(xSensorMutex, portMAX_DELAY);
sensorValue = readSensorDirect();
xSemaphoreGive(xSensorMutex);
}
xPID.currentValue = sensorValue;
// Calculate error
float error = xPID.setPoint - xPID.currentValue;
// Calculate PID components
float proportional = xPID.kp * error;
xPID.integral += xPID.ki * error * dt;
// Integral clamping
if(xPID.integral > xPID.outputLimit) xPID.integral = xPID.outputLimit;
if(xPID.integral < -xPID.outputLimit) xPID.integral = -xPID.outputLimit;
float derivative = xPID.kd * (error - xPID.lastError) / dt;
xPID.lastError = error;
// Calculate output
controlOutput = proportional + xPID.integral + derivative;
// Output clamping
if(controlOutput > xPID.outputLimit) controlOutput = xPID.outputLimit;
if(controlOutput < -xPID.outputLimit) controlOutput = -xPID.outputLimit;
// Set actuator output
setActuator(controlOutput);
// Precise periodic control
vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(10));
}
}
7. System Optimization and Performance Tuning
7.1 Memory Optimization
// Use static allocation instead of dynamic memory
// Define stack and control block required for tasks
static StaticTask_t xTaskBuffer;
static StackType_t xStack[configMINIMAL_STACK_SIZE];
// Create task statically
TaskHandle_t xHandle = xTaskCreateStatic(
vTaskCode, // Task function
"NAME", // Task name
configMINIMAL_STACK_SIZE, // Stack depth
NULL, // Parameters
tskIDLE_PRIORITY + 1, // Priority
xStack, // Stack buffer
&xTaskBuffer // Task control block
);
7.2 Stack Size Optimization
// Check stack usage
void vStackUsageTask(void *pvParameters)
{
UBaseType_t uxHighWaterMark;
while(1)
{
// Get high water mark of each task stack
uxHighWaterMark = uxTaskGetStackHighWaterMark(xTask1Handle);
printf("Task1 stack remaining: %lu\r\n", uxHighWaterMark);
uxHighWaterMark = uxTaskGetStackHighWaterMark(xTask2Handle);
printf("Task2 stack remaining: %lu\r\n", uxHighWaterMark);
// Adjust stack size based on high water mark
vTaskDelay(pdMS_TO_TICKS(10000));
}
}
7.3 CPU Utilization Monitoring
// Define runtime statistics variables
volatile uint32_t ulIdleCycleCount = 0;
volatile uint32_t ulTotalCycleCount = 0;
// Idle task hook function
void vApplicationIdleHook(void)
{
ulIdleCycleCount++;
}
// CPU utilization monitoring task
void vCPUMonitorTask(void *pvParameters)
{
uint32_t ulLastTotalCycles, ulLastIdleCycles;
uint32_t ulCurrentTotalCycles, ulCurrentIdleCycles;
while(1)
{
// Save current counts
ulLastTotalCycles = ulTotalCycleCount;
ulLastIdleCycles = ulIdleCycleCount;
// Wait for sampling period
vTaskDelay(pdMS_TO_TICKS(1000));
// Read new counts
ulCurrentTotalCycles = ulTotalCycleCount;
ulCurrentIdleCycles = ulIdleCycleCount;
// Calculate CPU utilization
uint32_t ulTotalCyclesDiff = ulCurrentTotalCycles - ulLastTotalCycles;
uint32_t ulIdleCyclesDiff = ulCurrentIdleCycles - ulLastIdleCycles;
float cpuUsage = 100.0f - ((float)ulIdleCyclesDiff / (float)ulTotalCyclesDiff * 100.0f);
printf("CPU Utilization: %.2f%%\r\n", cpuUsage);
}
}
7.4 Real-Time Performance Optimization Tips
- 1. Set Task Priorities Appropriately
- • Real-time tasks > Communication tasks > Background tasks
- • Avoid too many tasks of the same priority
- • Interrupt disable code should be as short as possible
- • Use critical sections to protect resources
- • Task blocking should have timeout mechanisms
- • Avoid holding mutexes for long periods
- • Reduce unnecessary task suspend/resume operations
- • Merge frequently switching small tasks
- • Task notifications are lighter than semaphores
- • Fast and low memory usage
// Use task notifications instead of semaphores
void vSenderTask(void *pvParameters)
{
while(1)
{
// Process data
processData();
// Notify receiver task
xTaskNotifyGive(xReceiverTaskHandle);
vTaskDelay(pdMS_TO_TICKS(100));
}
}
void vReceiverTask(void *pvParameters)
{
while(1)
{
// Wait for notification
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
// Process data
handleNotification();
}
}
Conclusion
FreeRTOS multitasking development is a complex task that requires comprehensive consideration of system architecture, resource allocation, communication coordination, and exception handling. By designing task structures appropriately, using suitable communication mechanisms, and implementing effective exception handling strategies, efficient and reliable embedded systems can be built.
In practical applications, developers need to flexibly apply these techniques based on specific hardware resources and application requirements to find the best balance between efficiency, resource usage, and real-time performance.