TinyTask: A Bare-Metal Task Scheduling Marvel in Embedded Systems

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: A Bare-Metal Task Scheduling Marvel in Embedded Systems

TinyTask adopts a layered architecture design:

  1. Core Layer – Provides core functionalities such as task scheduling and event management.
  2. Porting Layer – Provides platform-specific adaptations, such as system clock.
  3. Component Layer – Implements commonly used functional components.
  4. 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

TinyTask: A Bare-Metal Task Scheduling Marvel in Embedded Systems

  • 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: A Bare-Metal Task Scheduling Marvel in Embedded Systems

  • 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

TinyTask: A Bare-Metal Task Scheduling Marvel in Embedded Systems

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:

TinyTask: A Bare-Metal Task Scheduling Marvel in Embedded Systems

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 &amp; 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.”

TinyTask: A Bare-Metal Task Scheduling Marvel in Embedded Systems

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

TinyTask: A Bare-Metal Task Scheduling Marvel in Embedded Systems

Leave a Comment