Key Takeaways
• You can run preemptive multitasking on tiny Arm Cortex-M chips without a big RTOS.
• PendSV exceptions and SysTick timers handle task switching in simple code.
• This method uses lightweight, interrupt-driven scheduling for fast response.
• Developers gain control over timing and memory in IoT or robotics projects.
• Sample code is available on GitHub for hands-on learning.
Preemptive Multitasking Made Simple
Microcontrollers often need to run many tasks at once. For example, a robot might read sensors, drive motors, and log data all at the same time. A full real-time operating system can handle this work. Yet, on small devices, an RTOS can be too big. Jonathan Pallant shows how to use preemptive multitasking on Arm Cortex-M chips without a full OS. His approach uses just PendSV exceptions and the SysTick timer.
First, let’s look at why preemptive multitasking matters. Then, we’ll see how to use PendSV and SysTick timers to switch tasks. Finally, we’ll guide you through building and testing a simple scheduler from scratch.
Why Preemptive Multitasking Matters
Embedded systems often juggle several jobs at once. One moment a device must read a temperature sensor. The next moment it must send data over a network. If code waits too long, we lose data or slow down motors. Preemptive multitasking takes control away from any one task. Instead, the scheduler forces a switch at regular intervals. In turn, each task gets time to run.
For example, you might read a sensor every millisecond. Meanwhile, you also need to blink an LED. Without preemptive multitasking, slow code could block your LED blink. With it, the scheduler interrupts that slow code. Then it blinks the LED on time.
How Preemptive Multitasking Works on Cortex-M
Interrupts are at the heart of this trick. Specifically, the Cortex-M family has a special exception called PendSV. A second timer, called SysTick, fires at a steady rate. Every time SysTick triggers, it sets PendSV as pending. Then, the processor jumps into PendSV. In PendSV, you save the current task’s state. Next, you restore the state of the next task. Finally, you return to normal code. That switch only takes a few dozen instructions. As a result, you barely lose any time.
Key Components of Preemptive Multitasking
• SysTick Timer: Triggers at a fixed interval. It sets the PendSV flag.
• PendSV Exception: Runs a small routine to save and load task states.
• Task Control Block: Holds the stack pointer and other state info.
• Simple Scheduler: Chooses which task runs next.
Step-by-Step Guide to Building a Lightweight Scheduler
Setting Up the SysTick Timer
First, you program SysTick to fire at your chosen interval. A common value is 1 millisecond. At that rate, your scheduler can switch quickly enough for many tasks. Elsewhere, you could pick longer or shorter intervals. It all depends on how fast your tasks need to respond.
Configuring PendSV
Next, you configure PendSV to run at the lowest priority. That way, real interrupts like UART or SPI can still preempt your scheduler. You simply set the PendSV priority register to the highest numeric value. Remember, on Cortex-M a higher number means lower priority.
Creating Task Control Blocks
Each task needs a small data structure called a Task Control Block. It stores:
• The task’s stack pointer
• A placeholder for the stack data
• An index or ID for easy selection
Your scheduler keeps an array of these blocks. When it switches tasks, it moves the stack pointer from one block to another.
Writing the PendSV Handler
Inside the PendSV routine, you do three things:
• Save the current CPU registers onto the current task’s stack
• Save the stack pointer into its Task Control Block
• Load the next task’s stack pointer and registers
Because the code runs in handler mode, it can safely touch process stacks. You keep the assembly code short. This ensures quick context switching.
Implementing the Scheduler Logic
The scheduler chooses which task runs next. You can use a simple round-robin method. For example, if you have three tasks, you cycle through them in order. For more control, you might add priorities or time slices. However, keep it lightweight. Too much logic slows down the switch.
Testing Your Scheduler in Hardware
For real testing, you need an Arm Cortex-M board like an STM32 or an NXP LPC. First, flash your code onto the board. Next, add simple tasks to blink LEDs at different rates. For instance, let task A blink every 100 ms and task B blink every 200 ms. If both LEDs blink accurately, your scheduler works.
Moreover, you can add a UART log task. It might send a character each time it runs. That way, you see the exact switch sequence on your PC. This helps confirm each task runs at the right time.
Handling Edge Cases and Improvements
Even basic preemptive multitasking needs some care. For example, if a task disables interrupts for too long, you block the scheduler. Therefore, you should keep critical sections short. Also, watch out for stack overflow. Each task needs enough stack space. You can detect overflow by adding a known pattern at the stack end. If the pattern changes, you know you ran out of space.
For more advanced needs, you can add dynamic task creation and deletion. However, that requires extra memory management. For most IoT gadgets or robots, the simple static approach works fine.
Why This Approach Shines in IoT and Robotics
IoT sensors often sleep most of the time. When they wake, they handle a sensor read, maybe send a packet, then sleep again. Preemptive multitasking lets you handle network events, sensor reads, and user input in parallel. All without the overhead of a full RTOS.
In robotics, you need to react quickly to changes. For example, if a proximity sensor detects an obstacle, you must stop motors fast. Your scheduler can interrupt slow tasks to handle this event. That boosts safety and performance.
Moreover, you learn exactly how your scheduler works. You see how registers move and stacks grow. This insider knowledge helps you debug and optimize.
Wrapping Up
Jonathan Pallant’s method shows you how to build a basic preemptive multitasking system on Cortex-M chips. It uses only SysTick and PendSV. You get fast swaps and low memory use. You also gain full control of your task timing. Best of all, the code is public on GitHub. You can download it, study it, and adapt it to your own project. Whether you build an IoT sensor node or a mini robot, you’ll learn how multitasking really works.
Try it today and bring responsive behavior to your embedded system. You’ll impress friends, peers, and perhaps even future employers.
Frequently Asked Questions
Can I use preemptive multitasking without an RTOS?
Yes. By using PendSV and SysTick, you can switch tasks without a full operating system. This keeps your code small and fast.
How many tasks can I run with this approach?
You can run as many tasks as your memory allows. Each task needs a stack and a small control block. Just be mindful of available RAM.
Is this method suitable for battery-powered devices?
Absolutely. The scheduler runs only on interrupts, so it uses almost no extra power. Your tasks still sleep when they wait.
How hard is it to add priorities to tasks?
It is doable. You would adjust your scheduler logic to pick higher-priority tasks first. However, keep the code simple to maintain speed.