TinyTask: A Bare-Metal Task Scheduling Marvel in Embedded Systems
1. Introduction
In the realm of embedded development, efficiently managing tasks and scheduling resources has always been a “thorny issue” for programmers. Faced with resource-constrained microcontrollers, traditional Real-Time Operating Systems (RTOS) are powerful but often daunting due to their complexity and resource appetite. On the other hand, pure bare-metal programming can lead to code that is “hard to untangle and manage.”
Thus, TinyTask emerges! It is a lightweight task scheduling framework designed specifically for resource-constrained microcontrollers. TinyTask draws on the design philosophy of coroutines, achieving efficient task switching and event synchronization with minimal resource overhead through non-preemptive cooperative scheduling.
Next, we will delve into the design principles and core features of TinyTask, revealing its unique charm in embedded development through comparisons with traditional RTOS and coroutines.
2. Core Design Philosophy of TinyTask
The design philosophy of TinyTask can be summarized in two words: “lightweight” and “cooperative.” It is not a “big and comprehensive” operating system, but rather a bare-metal framework focused on providing the most basic and necessary abstractions: bare-metal framework: task management, inter-task communication, and device abstraction.
Note: It is a bare-metal framework. All tasks are executed sequentially, naturally avoiding issues such as priority settings and thread deadlocks that lead to concurrent competition. The experience of using it is akin to logical sequential execution.
2.1 Design Goals
The design goals of TinyTask are clear and focused:
- Extremely low resource usage – Suitable for small microcontrollers with only a few KB of RAM and tens of KB of Flash.
- Simplified task model – Provides a coding style similar to sequential code, reducing development difficulty.
- Efficient inter-task communication – Achieves decoupling between modules through an event mechanism.
- Cross-platform compatibility – Easy to port to different embedded platforms.
2.2 Architecture Overview
TinyTask adopts a layered architecture design:
- Core Layer – Provides core functionalities such as task scheduling and event management.
- Porting Layer – Provides platform-specific adaptations, such as system clock.
- Component Layer – Implements commonly used functional components.
- Application Layer – User-developed specific applications.
3. Comparison of TinyTask and Real-Time Operating Systems
TinyTask fundamentally differs from traditional RTOS (such as FreeRTOS, RT-Thread, etc.). Below, we compare their differences across multiple dimensions:
3.1 Scheduling Mode Comparison
- RTOS uses preemptive scheduling
- Priority-based task scheduling.
- High-priority tasks can preempt low-priority tasks at any time.
- Requires a complete context-switching mechanism.
- Each task requires independent stack space.
- TinyTask uses cooperative scheduling
- Tasks voluntarily yield CPU control.
- No concept of task priority.
- No complex context switching required.
- Tasks share the same stack space.
3.2 Resource Usage Comparison
For an application system containing 10 tasks, the resource usage differences may be as follows:
Resource Type | TinyTask | Typical RTOS (e.g., FreeRTOS) |
---|---|---|
RAM (per task) | ~40 bytes | ~256-512 bytes |
ROM (core) | ~1KB | ~8-10KB |
CPU Overhead | Minimal | 5-10% |
3.3 Applicable Scenarios Comparison
- TinyTask is suitable for
- Extremely resource-constrained environments.
- Applications with low real-time requirements.
- Scenarios with simple control logic.
- RTOS is suitable for
- Relatively resource-rich environments.
- Applications with strict real-time requirements.
- Complex multi-task systems.
4. Comparison of TinyTask and Coroutines
TinyTask draws on the design ideas of coroutines but has its unique aspects compared to standard coroutine implementations.
4.1 Conceptual Comparison
Similar to traditional coroutines, TinyTask’s task functions can “pause” and then “resume” from the breakpoint during the next scheduling. However, to save resources, TinyTask does not use OS-level stack switching but instead relies on state machines and macros to “simulate” coroutine behavior, which is a clever adaptation for embedded development.
4.2 Implementation Mechanism Comparison
// Standard coroutine implementation (pseudo code)
coroutine function example() {
// ...some logic
yield(); // pause execution
// ...resume
}
// TinyTask implementation
void task_function(void *arg) {
TT_TASK_START;
// ...logic part
TT_TASK_DELAY_MS(100); // voluntarily yield control and delay
// ...resume
TT_TASK_END;
}
TinyTask records the execution position using state numbers, with the key macro <span>TT_TASK_DELAY_MS</span>
implemented as follows:
#define TT_TASK_DELAY_MS(ms) \
do { \
task->time = tt_task_get_system_tick_ms(); \
task->state = __LINE__; \
case __LINE__:; \
if (tt_task_get_system_tick_ms() - task->time < ms) \
return; \
} while (0)
Isn’t it a bit like using switch-case to simulate “saving execution points”? It’s simple yet exceptionally efficient!
In fact, this is a “coroutine simulation” using the switch-case method, similar to the logic below:
#include <stdio.h>
void my_coroutine() {
static int state = 0;
switch (state) {
case 0:
printf("Step 1\n");
state = 1;
return;
case 1:
printf("Step 2\n");
state = 2;
return;
case 2:
printf("Step 3\n");
state = 0;
return;
}
}
int main() {
for (int i = 0; i < 3; i++) {
my_coroutine();
}
return 0;
}
4.3 Syntax Experience Comparison
Feature | Standard Coroutine | TinyTask |
---|---|---|
Writing Experience | Smooth, natural | Requires some macro assistance |
Learning Curve | Medium | Very low (beginner-friendly) |
Resource Overhead | Medium | Extremely low |
Cross-Platform Capability | Weak (platform-dependent) | Strong (almost bare-metal) |
In summary: TinyTask is more like a “coroutine simulator in resource-scarce environments,” small and beautiful, with a coding experience akin to playing magic with C.
5. Introduction to Core Modules (Concise Version)
๐ท Core Structure:
<span>tt_task_t</span>
typedef struct {
char *name;
void (*func)(void *arg);
void *arg;
int32_t state;
uint32_t time;
uint32_t event;
...
} tt_task_t;
It serves as the “identity card” of the task, containing state, events, time, and more.
๐ Scheduling Loop Illustration:
6. Real Code Demonstration
6.1 Beginner Example
void task1_func(void *arg)
{
TT_TASK_START;
while (1) {
printf("Hi, I am Task 1: %d\n", tt_task_get_system_tick_ms());
TT_TASK_DELAY_MS(500);
}
TT_TASK_END;
}
6.2 Event-Driven Example
void task1_func(void *arg)
{
TT_TASK_START;
TT_EVENT_REGISTER(TT_EVENT_1);
while (1) {
TT_TASK_WAIT_EVENT(TT_EVENT_1, 5000);
if(task->event & TT_EVENT_1){
printf("Event received!\n");
TT_TASK_CLEAR_EVENT(TT_EVENT_1);
} else {
printf("Waited too long, I'm leaving~\n");
}
TT_TASK_DELAY_MS(100);
}
TT_TASK_END;
}
๐คน Using events is like passing a ball; there’s no need to repeatedly ask, “Are you ready?” – saving CPU and enabling elegant cooperation!
7. Practical Tips & Easter Eggs
๐ฏ Development Tips:
- Each task must “enter and exit quickly”; do not monopolize the CPU for long.
- Use event mechanisms instead of while(1) loops with if polling.
- Do not forget to delay or return; otherwise, the scheduler will “never get a turn”.
๐ What can you do?
- Write a “display-equipped small appliance controller” using TinyTask;
- Or embed it in your DIY remote-controlled car;
- Or use it to quickly validate the control logic of a low-power device.
8. Visual Easter Eggs: Fun Illustrations
Differences in Scheduling between TinyTask and RTOS
๐ง RTOS:
“Are you letting me? I have a high priority! I decide!”
๐ง TinyTask:
“You finish first, then I will speak; I won’t snatch it.”
9. Conclusion: A Small Framework with Great Power
TinyTask is not intended to replace RTOS but to provide a “worry-free and resource-saving” option in scenarios where resources are tight, logic is simple, and real-time requirements are not high. You can easily organize code, manage tasks, and avoid the awkwardness of “blocking the CPU with a single while loop.”
๐ฆ The code is readable, and the structure is clear; once written, it is not easily tangled, and modifications are not daunting.
Next time you write a program, consider trying TinyTask; it might just be the “time management master” for your next project!
Code repository: https://github.com/weynechen/tinytask