Search Knowledge

© 2026 LIBREUNI PROJECT

C Programming Mastery / Real-Time Schedulers

Periodic Task Scheduling

Scheduling Tasks

An embedded application typically needs to execute multiple tasks at different periods (e.g., reading a temperature sensor every 500ms, updating a display every 100ms, and blinking a status LED every 1000ms). How we organize these tasks determines the predictability and stability of the system.

Super Loop vs. Timer-Controlled Dispatcher

1. The Super Loop (Polled Execution)

A super loop executes tasks sequentially in a continuous loop, using delay functions to manage timing:

int main(void) {
    while (1) {
        read_sensor();
        update_display();
        _delay_ms(100); // Wait between loops
    }
}
  • Pros: Easy to understand; does not require complex timer registers, interrupts, or function pointers.
  • Cons: Timing is fragile. If read_sensor() blocks for 50ms (e.g., waiting for an I/O flag), the display update and all subsequent loops are delayed, causing timing jitter.

2. The Timer-Controlled Dispatcher

A dispatcher splits execution into an asynchronous timer interrupt (counting ticks) and a synchronous main loop (executing tasks). This allows multiple independent periodic tasks to run off a single hardware timer.

  • Pros: Precise periodic execution intervals; timing of one task is insulated from minor variations in others.
  • Cons: Overrun hazard—if a task takes longer to execute than the timer period, it can delay other pending tasks.

Example: Scheduler Implementation in C

The scheduler represents each task using a control structure:

typedef struct {
    uint32_t period;    // Target execution period in milliseconds
    void (*task_p)(void); // Pointer to the task function
    uint32_t ticks;     // Decrementing counter tracking when to run
} task_t;

The Tick Decrement (ISR Context)

A hardware timer is configured to fire at a regular interval (e.g., every 10ms, which is 1 tick1\text{ tick}). The ISR loops through all registered tasks and decrements their tick counters:

#define MS_PER_TICK 10

ISR(TIMER1_COMPA_vect) {
    for (uint8_t i = 0; i < task_count; i++) {
        if (task_list[i].ticks > 0) {
            task_list[i].ticks--;
        }
    }
}

The Dispatcher (Main Thread Context)

The dispatcher runs continuously inside the main loop. It scans for tasks whose ticks have reached 0, executes them, and resets their tick countdown:

void dispatcher(void) {
    for (uint8_t i = 0; i < task_count; i++) {
        if (task_list[i].ticks == 0) {
            // 1. Run the task callback
            task_list[i].task_p();
            
            // 2. Reset the tick counter based on the task's period
            task_list[i].ticks = task_list[i].period / MS_PER_TICK;
        }
    }
}

int main(void) {
    timer1_init_10ms_tick();
    scheduler_add_task(100, update_display);
    scheduler_add_task(500, read_sensor);
    
    while(1) {
        dispatcher(); // Poll and run ready tasks
    }
}

In a 10ms tick dispatcher, what happens if a task registered with a 20ms period takes 35ms to execute?

Implement Tick Decrement Loop

// Inside the timer ISR, decrement ticks if they are greater than zero
if (task_list[i].ticks  0) {
    task_list[i].--;
}

References & Further Reading

  • Pont, M. J. (2001). Patterns for Time-Triggered Embedded Systems. Addison-Wesley. Chapters 1–3 (Schedulers).
  • VIA University College: Embedded Software 1 (ESW1) Lecture Notes - Section 10 (Periodic Task Scheduling).