The "Holy Bible" for embedded engineers
Understanding task management through concepts, not just API calls. Learn why tasks matter and how to think about concurrent execution.
Concept: Tasks are like workers in a factory, each with a specific job to do. The RTOS is like a smart manager that ensures each worker gets the right amount of time and resources to complete their work on schedule.
Why it matters: Without proper task management, your embedded system becomes like a factory with no manager - workers bump into each other, resources get wasted, and deadlines get missed. Good task management ensures everything runs smoothly and predictably.
Minimal example: A simple system with three tasks: one blinks an LED every 100ms, another reads a sensor every 500ms, and a third sends data every 1000ms. Each task runs independently but the system coordinates them all.
Try it: Start with a single task, then add more tasks and observe how the RTOS manages them. Change priorities and see how it affects execution order.
Takeaways: Task management is about designing independent workers that can cooperate effectively, with the RTOS handling the complex coordination so you can focus on what each task should do.
A task is an independent unit of work that runs concurrently with other tasks. Think of it as a separate program that can run at the same time as other programs.
┌─────────────────────────────────────────────────────────────┐
│ What is a Task? │
├─────────────────────────────────────────────────────────────┤
│ │
│ Traditional Program: │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ main() { │ │
│ │ while(1) { │ │
│ │ blink_led(); │ │
│ │ read_sensor(); │ │
│ │ send_data(); │ │
│ │ delay(100); │ │
│ │ } │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────┘ │
│ ❌ Everything runs in sequence │
│ ❌ Can't prioritize different operations │
│ ❌ Hard to make operations independent │
│ │
│ RTOS with Tasks: │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Task 1: │ │ Task 2: │ │ Task 3: │ │
│ │ Blink LED │ │ Read Sensor │ │ Send Data │ │
│ │ Every 100ms │ │ Every 500ms │ │ Every 1000ms│ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ ✅ Each task runs independently │
│ ✅ Different priorities possible │
│ ✅ Easy to add/remove/modify tasks │
└─────────────────────────────────────────────────────────────┘
Single Loop Problems:
┌─────────────────────────────────────────────────────────────┐
│ Single Loop Problems │
├─────────────────────────────────────────────────────────────┤
│ │
│ while(1) { │
│ // What if sensor reading takes too long? │
│ sensor_value = read_sensor(); │
│ │
│ // LED blinking gets delayed! │
│ blink_led(); │
│ │
│ // What if data sending fails? │
│ send_data(); │
│ │
│ // Everything waits for everything else │
│ delay(100); │
│ } │
│ │
│ ❌ One slow operation delays everything │
│ ❌ Can't prioritize critical operations │
│ ❌ Hard to handle failures gracefully │
│ ❌ Timing becomes unpredictable │
└─────────────────────────────────────────────────────────────┘
Task-Based Benefits:
┌─────────────────────────────────────────────────────────────┐
│ Task-Based Benefits │
├─────────────────────────────────────────────────────────────┤
│ │
│ Task 1 (High Priority): │
│ while(1) { │
│ blink_led(); │
│ vTaskDelay(100); │
│ } │
│ │
│ Task 2 (Medium Priority): │
│ while(1) { │
│ sensor_value = read_sensor(); │
│ vTaskDelay(500); │
│ } │
│ │
│ Task 3 (Low Priority): │
│ while(1) { │
│ send_data(); │
│ vTaskDelay(1000); │
│ } │
│ │
│ ✅ Each task runs at its own pace │
│ ✅ Critical operations (LED) aren't delayed │
│ ✅ Failures in one task don't affect others │
│ ✅ Timing is predictable and reliable │
└─────────────────────────────────────────────────────────────┘
Tasks move through different states during their lifetime:
┌─────────────────────────────────────────────────────────────┐
│ Task State Machine │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Created │───▶│ Ready │───▶│ Running │ │
│ └─────────┘ └─────────┘ └────┬────┘ │
│ │ │
│ ▼ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │Deleted │◄───│ Blocked │◄───│ │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
│ State Transitions: │
│ • Created → Ready: Task is ready to run │
│ • Ready → Running: Scheduler selects this task │
│ • Running → Ready: Task yields or is preempted │
│ • Running → Blocked: Task waits for something │
│ • Blocked → Ready: What task was waiting for happens │
│ • Any → Deleted: Task is removed from system │
└─────────────────────────────────────────────────────────────┘
Created: Task exists but isn’t scheduled yet Ready: Task is ready to run, waiting for CPU time Running: Task is currently executing on the CPU Blocked: Task is waiting for something (delay, data, resource) Deleted: Task has been removed and won’t run again
Each task should have one clear job to do:
┌─────────────────────────────────────────────────────────────┐
│ Good vs Bad Task Design │
├─────────────────────────────────────────────────────────────┤
│ │
│ ❌ Bad: One task does everything │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ void vDoEverythingTask(void *pvParameters) { │ │
│ │ while(1) { │ │
│ │ // Too many responsibilities! │ │
│ │ read_sensors(); │ │
│ │ process_data(); │ │
│ │ control_motors(); │ │
│ │ update_display(); │ │
│ │ send_telemetry(); │ │
│ │ vTaskDelay(100); │ │
│ │ } │ │
│ │ } │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ✅ Good: Each task has one job │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Sensor Task │ │ Control │ │ Display │ │
│ │ Read data │ │ Task │ │ Task │ │
│ │ every 100ms │ │ Process & │ │ Update UI │ │
│ └─────────────┘ │ control │ │ every 50ms │ │
│ │ every 50ms │ └─────────────┘ │
│ └─────────────┘ │
│ │
│ Benefits of good design: │
│ • Easier to understand and debug │
│ • Can optimize each task independently │
│ • Easier to test individual components │
│ • More flexible and maintainable │
└─────────────────────────────────────────────────────────────┘
Tasks should be neither too big nor too small:
Too Big (Coarse Granularity):
Too Small (Fine Granularity):
Just Right:
Priorities determine which task runs when multiple tasks are ready:
┌─────────────────────────────────────────────────────────────┐
│ Priority System │
├─────────────────────────────────────────────────────────────┤
│ │
│ Priority 5: Emergency Stop (highest) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ██████████████████████████████████████████████████ │ │
│ │ Emergency stop - must run immediately! │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Priority 4: Safety Monitoring │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ██████████████████████████████████████████████████ │ │
│ │ Safety checks - critical for system safety │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Priority 3: Control Loop │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ██████████████████████████████████████████████████ │ │
│ │ Main control - keeps system running │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Priority 2: Data Logging │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ██████████████████████████████████████████████████ │ │
│ │ Logging - important but not critical │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Priority 1: Status Updates (lowest) │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ██████████████████████████████████████████████████ │ │
│ │ Status - nice to have but not essential │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ ⚠️ Higher priority tasks can interrupt lower ones! │
└─────────────────────────────────────────────────────────────┘
High Priority (4-5):
Medium Priority (2-3):
Low Priority (1):
Each task needs its own stack space:
┌─────────────────────────────────────────────────────────────┐
│ Task Stack Allocation │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ System Memory │ │
│ ├─────────────────────────────────────────────────────┤ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ Task 1 │ │ Task 2 │ │ Task 3 │ │ │
│ │ │ Stack │ │ Stack │ │ Stack │ │ │
│ │ │ 1KB │ │ 2KB │ │ 512B │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ ├─────────────────────────────────────────────────────┤ │
│ │ Heap (shared) │ │
│ ├─────────────────────────────────────────────────────┤ │
│ │ Global Variables │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
│ Stack Size Considerations: │
│ • Too small: Stack overflow crashes │
│ • Too large: Wastes memory │
│ • Monitor actual usage with stack checking │
│ • Consider function call depth and local variables │
└─────────────────────────────────────────────────────────────┘
Static Allocation:
Dynamic Allocation:
Objective: Understand basic task creation and execution.
Setup: Create a single task that performs a simple operation.
Steps:
Expected Outcome: Understanding of basic task creation and how tasks run independently.
Objective: Learn how multiple tasks work together.
Setup: Create two tasks that need to coordinate their work.
Steps:
Expected Outcome: Understanding of task communication and coordination.
Objective: Learn how priorities affect task execution.
Setup: Create multiple tasks with different priorities.
Steps:
Expected Outcome: Understanding of priority-based scheduling and its effects.