The "Holy Bible" for embedded engineers
Understanding real-time scheduling algorithms, priority-based scheduling, and timing analysis in embedded systems with focus on FreeRTOS implementation and real-time scheduling principles
Scheduling algorithms are like traffic controllers for your CPU. Instead of letting tasks fight over who gets to run, the scheduler makes intelligent decisions about which task should execute when, ensuring everyone gets their turn and critical tasks don’t get stuck in traffic.
In real-time systems, missing a deadline can mean the difference between a safe landing and a crash. Good scheduling ensures that critical tasks (like reading sensors or controlling actuators) always get CPU time when they need it, while less critical tasks (like status updates) wait their turn.
// Task priorities determine execution order
void highPriorityTask(void *pvParameters) {
while (1) {
readCriticalSensor(); // Must happen every 10ms
vTaskDelay(pdMS_TO_TICKS(10));
}
}
void mediumPriorityTask(void *pvParameters) {
while (1) {
processData(); // Can wait a bit
vTaskDelay(pdMS_TO_TICKS(50));
}
}
void lowPriorityTask(void *pvParameters) {
while (1) {
updateStatusLED(); // Not time-critical
vTaskDelay(pdMS_TO_TICKS(100));
}
}
// Create tasks with different priorities
xTaskCreate(highPriorityTask, "High", 128, NULL, 3, NULL);
xTaskCreate(mediumPriorityTask, "Medium", 128, NULL, 2, NULL);
xTaskCreate(lowPriorityTask, "Low", 128, NULL, 1, NULL);
Good scheduling is about making intelligent trade-offs between urgency, importance, and resource efficiency, ensuring your system meets all its timing requirements.
Scheduling algorithms are the heart of real-time operating systems, determining which tasks run when and for how long. Understanding scheduling algorithms is essential for designing embedded systems that can meet real-time requirements, handle multiple concurrent operations, and provide predictable performance under various conditions.
Scheduling algorithms are mathematical methods that determine the order and timing of task execution in a real-time system. They ensure that system resources are used efficiently while meeting real-time constraints such as deadlines and response times.
Scheduling Purpose:
Scheduling Characteristics:
Real-Time Requirements:
Basic Scheduling System:
┌─────────────────────────────────────────────────────────────┐
│ Task Queue │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Task 1 │ │ Task 2 │ │ Task 3 │ │
│ │ (Priority 3)│ │ (Priority 2)│ │ (Priority 1)│ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Scheduler │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Scheduling Algorithm │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ CPU │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Currently Running Task │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
Scheduling Decision Process:
┌─────────────────────────────────────────────────────────────┐
│ Scheduling Cycle │
├─────────────────────────────────────────────────────────────┤
│ 1. Check for new tasks or priority changes │
│ 2. Evaluate scheduling algorithm criteria │
│ 3. Select next task to run │
│ 4. Perform context switch if needed │
│ 5. Execute selected task │
│ 6. Monitor task execution and timing │
└─────────────────────────────────────────────────────────────┘
Effective scheduling is crucial for real-time systems because it directly affects system performance, reliability, and ability to meet timing requirements. Poor scheduling can lead to missed deadlines, system failures, and unpredictable behavior.
Timing Constraints:
Resource Management:
System Reliability:
Performance Metrics:
Quality of Service:
Task Parameters:
Task Classification:
Timing Metrics:
Utilization Metrics:
System Constraints:
Algorithm Constraints:
Basic Principles:
Priority Assignment Strategies:
Priority Configuration:
// Priority configuration
#define configMAX_PRIORITIES 32
#define configUSE_PREEMPTION 1
#define configUSE_TIME_SLICING 1
#define configUSE_TICKLESS_IDLE 0
// Priority levels
#define PRIORITY_CRITICAL 5 // System critical tasks
#define PRIORITY_HIGH 4 // High-priority user tasks
#define PRIORITY_NORMAL 3 // Normal operation tasks
#define PRIORITY_LOW 2 // Background tasks
#define PRIORITY_IDLE 1 // Idle tasks
// Task creation with priorities
void vCreateTasks(void) {
TaskHandle_t xTaskHandle;
// Create critical task with highest priority
xTaskCreate(
vCriticalTask, // Task function
"Critical", // Task name
256, // Stack size
NULL, // Parameters
PRIORITY_CRITICAL, // Priority
&xTaskHandle // Task handle
);
// Create high priority task
xTaskCreate(
vHighPriorityTask, // Task function
"High", // Task name
256, // Stack size
NULL, // Parameters
PRIORITY_HIGH, // Priority
&xTaskHandle // Task handle
);
// Create normal priority task
xTaskCreate(
vNormalTask, // Task function
"Normal", // Task name
256, // Stack size
NULL, // Parameters
PRIORITY_NORMAL, // Priority
&xTaskHandle // Task handle
);
}
Priority Management:
// Dynamic priority changes
void vPriorityManager(void *pvParameters) {
TaskHandle_t xManagedTask = (TaskHandle_t)pvParameters;
UBaseType_t uxCurrentPriority;
while (1) {
// Get current priority
uxCurrentPriority = uxTaskPriorityGet(xManagedTask);
// Adjust priority based on system conditions
if (system_under_load()) {
// Increase priority under load
vTaskPrioritySet(xManagedTask, uxCurrentPriority + 1);
} else {
// Restore normal priority
vTaskPrioritySet(xManagedTask, PRIORITY_NORMAL);
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
// Priority inheritance example
void vResourceTask(void *pvParameters) {
SemaphoreHandle_t xMutex = (SemaphoreHandle_t)pvParameters;
while (1) {
// Take mutex (priority inheritance will occur)
if (xSemaphoreTake(xMutex, portMAX_DELAY) == pdTRUE) {
// Use shared resource
vTaskDelay(pdMS_TO_TICKS(100));
// Release mutex
xSemaphoreGive(xMutex);
}
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
Priority Inheritance:
// Priority inheritance mutex
SemaphoreHandle_t xPriorityInheritanceMutex;
void vHighPriorityTask(void *pvParameters) {
while (1) {
// Wait for resource
if (xSemaphoreTake(xPriorityInheritanceMutex, portMAX_DELAY) == pdTRUE) {
// Critical section
vTaskDelay(pdMS_TO_TICKS(50));
// Release resource
xSemaphoreGive(xPriorityInheritanceMutex);
}
vTaskDelay(pdMS_TO_TICKS(200));
}
}
void vLowPriorityTask(void *pvParameters) {
while (1) {
// Take resource
if (xSemaphoreTake(xPriorityInheritanceMutex, portMAX_DELAY) == pdTRUE) {
// Long critical section
vTaskDelay(pdMS_TO_TICKS(1000));
// Release resource
xSemaphoreGive(xPriorityInheritanceMutex);
}
vTaskDelay(pdMS_TO_TICKS(5000));
}
}
// Initialize priority inheritance mutex
xPriorityInheritanceMutex = xSemaphoreCreateMutex();
Basic Concept:
Rate Monotonic Analysis:
// Rate Monotonic Schedulability Test
typedef struct {
uint32_t period; // Task period in ticks
uint32_t execution; // Worst-case execution time
uint8_t priority; // Assigned priority
} rms_task_t;
bool rms_schedulability_test(rms_task_t tasks[], uint8_t task_count) {
double utilization = 0.0;
// Calculate total utilization
for (uint8_t i = 0; i < task_count; i++) {
utilization += (double)tasks[i].execution / tasks[i].period;
}
// Liu-Layland bound for rate monotonic
double bound = task_count * (pow(2.0, 1.0/task_count) - 1.0);
return utilization <= bound;
}
// Example: Three periodic tasks
rms_task_t rms_tasks[] = {
{100, 20, 3}, // Task 1: 100ms period, 20ms execution, priority 3
{200, 40, 2}, // Task 2: 200ms period, 40ms execution, priority 2
{400, 60, 1} // Task 3: 400ms period, 60ms execution, priority 1
};
void vRateMonotonicExample(void) {
uint8_t task_count = sizeof(rms_tasks) / sizeof(rms_tasks[0]);
if (rms_schedulability_test(rms_tasks, task_count)) {
printf("System is schedulable with Rate Monotonic\n");
} else {
printf("System is NOT schedulable with Rate Monotonic\n");
}
}
Task Creation with RMS:
// Rate Monotonic task creation
void vCreateRMSTasks(void) {
// Sort tasks by period (highest frequency = highest priority)
qsort(rms_tasks, sizeof(rms_tasks)/sizeof(rms_tasks[0]),
sizeof(rms_tasks[0]), compare_period);
// Create tasks with RMS priorities
for (uint8_t i = 0; i < sizeof(rms_tasks)/sizeof(rms_tasks[0]); i++) {
xTaskCreate(
vPeriodicTask, // Task function
"RMS_Task", // Task name
256, // Stack size
&rms_tasks[i], // Parameters
rms_tasks[i].priority, // RMS priority
NULL // Task handle
);
}
}
// Periodic task implementation
void vPeriodicTask(void *pvParameters) {
rms_task_t *task = (rms_task_t*)pvParameters;
TickType_t xLastWakeTime;
// Initialize the xLastWakeTime variable with the current time
xLastWakeTime = xTaskGetTickCount();
while (1) {
// Perform task work
vTaskWork(task);
// Wait for next period
vTaskDelayUntil(&xLastWakeTime, pdMS_TO_TICKS(task->period));
}
}
// Task work function
void vTaskWork(rms_task_t *task) {
printf("Task with period %lu executing for %lu ms\n",
task->period, task->execution);
// Simulate work
vTaskDelay(pdMS_TO_TICKS(task->execution));
}
Basic Concept:
EDF Schedulability Test:
// EDF Schedulability Test
bool edf_schedulability_test(rms_task_t tasks[], uint8_t task_count) {
double utilization = 0.0;
// Calculate total utilization
for (uint8_t i = 0; i < task_count; i++) {
utilization += (double)tasks[i].execution / tasks[i].period;
}
// EDF bound is 100% for independent tasks
return utilization <= 1.0;
}
// EDF task structure with deadlines
typedef struct {
uint32_t period; // Task period
uint32_t execution; // Worst-case execution time
uint32_t deadline; // Task deadline
uint32_t next_deadline; // Next deadline time
} edf_task_t;
// EDF priority calculation
uint32_t edf_calculate_priority(edf_task_t *task) {
// Lower deadline = higher priority
return task->next_deadline;
}
EDF Scheduler:
// EDF scheduler implementation
void vEDFScheduler(void *pvParameters) {
edf_task_t *tasks = (edf_task_t*)pvParameters;
uint8_t task_count = sizeof(tasks) / sizeof(tasks[0]);
uint8_t highest_priority_task = 0;
while (1) {
// Find task with earliest deadline
uint32_t earliest_deadline = UINT32_MAX;
for (uint8_t i = 0; i < task_count; i++) {
if (tasks[i].next_deadline < earliest_deadline) {
earliest_deadline = tasks[i].next_deadline;
highest_priority_task = i;
}
}
// Execute highest priority task
vExecuteTask(&tasks[highest_priority_task]);
// Update deadlines for completed tasks
tasks[highest_priority_task].next_deadline += tasks[highest_priority_task].period;
vTaskDelay(pdMS_TO_TICKS(1));
}
}
// Task execution function
void vExecuteTask(edf_task_t *task) {
printf("Executing EDF task with deadline %lu\n", task->next_deadline);
// Simulate task execution
vTaskDelay(pdMS_TO_TICKS(task->execution));
}
Basic Concept:
Time Quantum Selection:
// Time quantum configuration
#define TIME_QUANTUM_MS 10 // 10ms time quantum
#define TASK_SLICE_TICKS pdMS_TO_TICKS(TIME_QUANTUM_MS)
// Round Robin task structure
typedef struct {
uint8_t priority; // Task priority
uint32_t time_remaining; // Remaining time in current quantum
bool is_running; // Whether task is currently running
} rr_task_t;
// Round Robin scheduler
void vRoundRobinScheduler(void *pvParameters) {
rr_task_t *tasks = (rr_task_t*)pvParameters;
uint8_t task_count = sizeof(tasks) / sizeof(tasks[0]);
uint8_t current_task = 0;
while (1) {
// Find next ready task with same priority
uint8_t next_task = (current_task + 1) % task_count;
// Check if next task has same priority and is ready
if (tasks[next_task].priority == tasks[current_task].priority &&
tasks[next_task].is_running) {
current_task = next_task;
}
// Execute current task for time quantum
vExecuteTaskRR(&tasks[current_task], TASK_SLICE_TICKS);
vTaskDelay(pdMS_TO_TICKS(1));
}
}
Time Slicing Configuration:
// FreeRTOS time slicing configuration
#define configUSE_TIME_SLICING 1
#define configIDLE_SHOULD_YIELD 1
// Round Robin task creation
void vCreateRoundRobinTasks(void) {
// Create tasks with same priority for Round Robin
for (uint8_t i = 0; i < 3; i++) {
xTaskCreate(
vRoundRobinTask, // Task function
"RR_Task", // Task name
256, // Stack size
(void*)i, // Task number
2, // Same priority for all tasks
NULL // Task handle
);
}
}
// Round Robin task implementation
void vRoundRobinTask(void *pvParameters) {
uint8_t task_number = (uint8_t)pvParameters;
while (1) {
printf("Round Robin Task %d executing\n", task_number);
// Simulate work
vTaskDelay(pdMS_TO_TICKS(100));
// Yield to other tasks (optional with time slicing)
taskYIELD();
}
}
Basic RTA:
// Response Time Analysis for fixed priority scheduling
typedef struct {
uint32_t period; // Task period
uint32_t execution; // Worst-case execution time
uint8_t priority; // Task priority
uint32_t response_time; // Calculated response time
} rta_task_t;
uint32_t calculate_response_time(rta_task_t *task, rta_task_t tasks[], uint8_t task_count) {
uint32_t response_time = task->execution;
uint32_t interference = 0;
bool converged = false;
uint32_t iterations = 0;
while (!converged && iterations < 100) {
interference = 0;
// Calculate interference from higher priority tasks
for (uint8_t i = 0; i < task_count; i++) {
if (tasks[i].priority > task->priority) {
interference += ceil((double)response_time / tasks[i].period) * tasks[i].execution;
}
}
uint32_t new_response_time = task->execution + interference;
if (new_response_time == response_time) {
converged = true;
} else {
response_time = new_response_time;
}
iterations++;
}
return response_time;
}
// RTA example
void vResponseTimeAnalysis(void) {
rta_task_t tasks[] = {
{100, 20, 3, 0}, // High priority
{200, 40, 2, 0}, // Medium priority
{400, 60, 1, 0} // Low priority
};
uint8_t task_count = sizeof(tasks) / sizeof(tasks[0]);
// Calculate response times
for (uint8_t i = 0; i < task_count; i++) {
tasks[i].response_time = calculate_response_time(&tasks[i], tasks, task_count);
printf("Task %d: Response time = %lu ms\n", i, tasks[i].response_time);
}
}
Utilization Bound Testing:
// Utilization bound testing
bool test_utilization_bound(rta_task_t tasks[], uint8_t task_count) {
double total_utilization = 0.0;
// Calculate total utilization
for (uint8_t i = 0; i < task_count; i++) {
total_utilization += (double)tasks[i].execution / tasks[i].period;
}
// Rate Monotonic bound
double rms_bound = task_count * (pow(2.0, 1.0/task_count) - 1.0);
// EDF bound
double edf_bound = 1.0;
printf("Total utilization: %.3f\n", total_utilization);
printf("RMS bound: %.3f\n", rms_bound);
printf("EDF bound: %.3f\n", edf_bound);
return total_utilization <= rms_bound;
}
Basic Configuration:
// FreeRTOS scheduler configuration
#define configUSE_PREEMPTION 1
#define configUSE_TIME_SLICING 1
#define configUSE_TICKLESS_IDLE 0
#define configUSE_IDLE_HOOK 0
#define configUSE_TICK_HOOK 0
#define configCPU_CLOCK_HZ 16000000
#define configTICK_RATE_HZ 1000
#define configMAX_PRIORITIES 32
#define configMINIMAL_STACK_SIZE 128
#define configMAX_TASK_NAME_LEN 16
#define configUSE_16_BIT_TICKS 0
#define configIDLE_SHOULD_YIELD 1
#define configUSE_MUTEXES 1
#define configUSE_RECURSIVE_MUTEXES 0
#define configUSE_COUNTING_SEMAPHORES 1
#define configUSE_ALTERNATIVE_API 0
#define configCHECK_FOR_STACK_OVERFLOW 2
#define configUSE_MALLOC_FAILED_HOOK 1
#define configUSE_APPLICATION_TASK_TAG 0
#define configUSE_QUEUE_SETS 1
#define configUSE_TASK_NOTIFICATIONS 1
#define configSUPPORT_STATIC_ALLOCATION 1
#define configSUPPORT_DYNAMIC_ALLOCATION 1
Scheduler Hooks:
// Scheduler hooks
void vApplicationIdleHook(void) {
// Called when idle task runs
// Can be used for power management
__WFI(); // Wait for interrupt
}
void vApplicationTickHook(void) {
// Called every tick
// Can be used for periodic operations
static uint32_t tick_count = 0;
tick_count++;
if (tick_count % 1000 == 0) {
// Every 1000 ticks
printf("System running for %lu seconds\n", tick_count / 1000);
}
}
void vApplicationMallocFailedHook(void) {
// Called when malloc fails
printf("Memory allocation failed!\n");
// Handle memory allocation failure
// Could restart system or free memory
}
void vApplicationStackOverflowHook(TaskHandle_t xTask, char *pcTaskName) {
// Called when stack overflow detected
printf("Stack overflow in task: %s\n", pcTaskName);
// Handle stack overflow
// Could restart system or task
}
Scheduler Control Functions:
// Scheduler control
void vSchedulerControl(void *pvParameters) {
while (1) {
// Suspend scheduler
vTaskSuspendAll();
// Perform critical operations
vCriticalOperation();
// Resume scheduler
xTaskResumeAll();
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
// Critical operation
void vCriticalOperation(void) {
// Operations that must not be interrupted
printf("Performing critical operation...\n");
// Simulate critical work
for (volatile uint32_t i = 0; i < 1000000; i++) {
// Critical work
}
printf("Critical operation completed\n");
}
System Initialization:
// System initialization with scheduling
void vSystemInit(void) {
// Create system tasks with different priorities
xTaskCreate(vSystemMonitorTask, "SysMon", 256, NULL, 5, NULL);
xTaskCreate(vCommunicationTask, "Comm", 512, NULL, 4, NULL);
xTaskCreate(vDataProcessingTask, "DataProc", 1024, NULL, 3, NULL);
xTaskCreate(vBackgroundTask, "Background", 128, NULL, 2, NULL);
xTaskCreate(vIdleTask, "Idle", 64, NULL, 1, NULL);
// Start scheduler
vTaskStartScheduler();
}
// Main function
int main(void) {
// Hardware initialization
SystemInit();
HAL_Init();
// Initialize peripherals
MX_GPIO_Init();
MX_USART1_UART_Init();
// Initialize system
vSystemInit();
// Should never reach here
while (1) {
// Error handling
}
}
Common Scenarios:
Solutions:
Overhead Sources:
Optimization Strategies:
Fragmentation Causes:
Mitigation:
Priority Assignment:
Task Design:
Scheduling Efficiency:
Resource Management:
Objective: Understand how task priorities affect execution order Steps:
Expected Outcome: Higher priority tasks get more CPU time and execute more frequently
Objective: Implement and observe RMS behavior Steps:
Expected Outcome: All tasks meet their deadlines with proper priority assignment
Objective: Measure scheduling overhead and performance Steps:
Expected Outcome: Understanding of scheduling overhead and optimization opportunities
This enhanced Scheduling Algorithms document now provides a comprehensive balance of conceptual explanations, practical insights, and technical implementation details that embedded engineers can use to understand and implement robust RTOS scheduling systems.