Programming lesson
Building a Non-Preemptive Round-Robin Scheduler on Arduino Mega: A Step-by-Step Lab Guide
Learn to implement round-robin schedulers, task control blocks, and ISR-based timing on Arduino Mega. This guide walks through Lab 3 tasks with real-time examples and code snippets.
Introduction to Embedded Scheduling
In embedded systems, multitasking is essential for handling multiple peripherals without dedicated operating systems. This lab focuses on implementing non-preemptive round-robin (RR) schedulers on an Arduino Mega. You'll learn three scheduler types: basic RR, synchronized RR with ISR (SRRI), and a Task Control Block (TCB)-based scheduler. These concepts are fundamental for real-time systems and are used in applications like smart home controllers, drone flight controllers, and even AI edge devices that juggle sensor data and communication tasks.
Understanding the Tasks
You will integrate five tasks into specific configurations. Task 1 flashes an external LED (250 ms ON, 750 ms OFF). Task 2 plays the Mario theme song using timer manipulation (no tone()), then sleeps for 4 seconds. Task 3 drives a 7-segment display counting up every 100 ms. Task 4 combines and modifies Tasks 2 and 3: during silence, the display shows a countdown to the next music play; during music, it shows the current tone frequency in Hz. Task 5 handles a final smiley display and shutdown sequence.
Round-Robin Scheduler Basics
The simplest scheduler is a while(1) loop that calls tasks in order. Add a delay to set the overall period. For example:
void loop() {
while(1) {
task1();
task2();
task3();
delayMicroseconds(1000);
}
}This non-preemptive approach ensures each task runs to completion before the next starts. It's like a turn-based game where each player (task) gets a fixed time slot.
Synchronized Round-Robin with ISR (SRRI)
SRRI uses a hardware timer interrupt to synchronize task scheduling. You'll create an array of function pointers (up to 10 tasks) and a corresponding state array for each task: READY, RUNNING, SLEEPING. A sleep_474(t) function puts a task to sleep for t milliseconds. Every 2 ms, the ISR decrements sleep times and wakes tasks. The ISR sets a volatile flag sFlag to DONE, and a special schedule_sync() task waits for this flag, updates sleep times, and resets the flag. This ensures precise 2 ms scheduling cycles.
volatile int sFlag = PENDING;
ISR(TIMER1_COMPA_vect) {
sFlag = DONE;
}
void schedule_sync() {
while (sFlag == PENDING);
// update sleep times
for (int i = 0; i < numTasks; i++) {
if (state[i] == SLEEPING) {
sleepTime[i] -= 2;
if (sleepTime[i] <= 0) {
state[i] = READY;
}
}
}
sFlag = PENDING;
}This is like a precise timer in a sports match—each tick advances the game clock and updates player status.
Task Control Block (TCB) Scheduler
For more control, each task gets a TCB containing a unique ID, name (up to 20 chars), and a start count. The scheduler loops through TCBs and runs tasks based on their status. Functions task_self_quit() and task_start(TCB *task) allow tasks to manage themselves and others. This mimics process control in operating systems, where each process has a PCB.
typedef struct {
uint8_t id;
char name[20];
uint8_t status; // READY, RUNNING, SLEEPING, STOPPED
uint16_t sleepTime;
uint16_t startCount;
void (*taskPtr)();
} TCB;
TCB taskList[10];
void scheduler() {
for (int i = 0; i < numTasks; i++) {
if (taskList[i].status == READY) {
taskList[i].status = RUNNING;
taskList[i].taskPtr();
taskList[i].status = READY;
}
}
}Implementing the Tasks
Task 1: LED Flasher
Use digitalWrite to toggle an LED with precise timing. Since the scheduler period is 2 ms, you can count cycles to achieve 250 ms ON and 750 ms OFF.
Task 2: Mario Theme
Directly manipulate Timer 2 to generate PWM at specific frequencies for each note. Store the theme as an array of (frequency, duration) pairs. After playing, call sleep_474(4000).
Task 3: 7-Segment Counter
Use a 4-digit 7-segment display with multiplexing. Increment a counter every 100 ms (50 scheduler cycles) and display the value.
Task 4: Combined Display
Modify Task 2 to signal when music plays. During silence, countdown from 40 (4 seconds in tenths) to 0. During music, display the frequency of the current note.
Task 5: Shutdown Sequence
After Task 2 plays twice, start a 3-second countdown, then play the theme once more, display a smiley for 2 seconds, and stop all tasks except Task 1.
Testing and Debugging
Start with the basic RR scheduler and verify each task individually. Then integrate SRRI and TCB schedulers. Use the Serial monitor to print state changes and timing. Remember to keep ISR code minimal—just set a flag and increment a time counter.
Real-World Connections
These scheduling techniques are used in modern AI-powered IoT devices that must process sensor data, run machine learning models, and communicate wirelessly—all within strict timing constraints. For example, a smart speaker juggles audio playback, voice recognition, and network requests using similar round-robin and interrupt-driven methods.
Conclusion
By completing this lab, you've built three types of non-preemptive schedulers and learned how to manage multiple tasks with precise timing. These skills are directly applicable to embedded systems development in robotics, automotive, and consumer electronics.