Keine Beschreibung

Satoshi Yoneda e078921456 modified: docs/ vor 1 Monat
.vscode 296f91df9c first commit vor 1 Monat
docs e078921456 modified: docs/ vor 1 Monat
tinykernel f930aad81b Add: ドキュメントを追加 vor 1 Monat
.gitignore 173026cd6c refactor: PendSV化 vor 1 Monat
CMakeLists.txt 0fe49469c5 migrate: デュアルコア対応 vor 1 Monat
LICENSE.md 92fe76cd13 Add: LICENSE.md vor 1 Monat
README.md beb4358042 modified: README.md vor 1 Monat
main.cpp e10e85834a Add: EventHubクラスを新設 vor 1 Monat
pico_sdk_import.cmake 296f91df9c first commit vor 1 Monat

README.md

Raspberry Pi Pico 2 (RP2350) 用プリエンプティブマルチタスクカーネル ~ tinykernel

(English version follows the Japanese section)

Raspberry Pi Pico 2 (RP2350 / ARM Cortex-M33) 向けの、シンプルで実用的なリアルタイム・プリエンプティブマルチタスクカーネルです。


主な特徴

  • デュアルコア(SMP)対応: RP2350 の 2 つの Cortex-M33 コアをフル活用し、タスクを自動で並行実行・マイグレーションします。
  • 完全疎結合型のタスク間通信 (Pub/Sub): 受信側の Actor インスタンスを知らなくても、メッセージ型を指定するだけで安全に配信できるメッセージングシステムを装備。
  • 値コピーによるメモリ安全性: メッセージはキュー内に実体としてコピーされるため、スタック上のローカル変数でもライフタイム(ダングリングポインタ)の心配なく受け渡し可能。
  • CPU 消費ゼロのブロッキング待機: メッセージ待ちやセマフォ/ミューテックス待ちの間はタスクが自動休止 (State::Blocked) し、CPU を他のタスクに明け渡します。
  • ハードウェア FPU の完全保護: 複数コア・複数タスクで浮動小数点演算を同時実行しても、コンテキストスイッチによる FPU レジスタ破壊が起きません。
  • ゼロ動的確保 (No malloc): スタック、タスクスロット、メッセージキューに至るまで静的/固定長メモリで動作し、メモリ断片化を起こしません。

基本的な使い方

tk::create() を使ってタスクを登録します(最大 30 個のユーザータスクを登録可能)。

#include "tinykernel/kernel.hpp"

static void your_task(void *arg) {
    for (;;) {
        // なにか仕事をする
        tk::sleep_ms(200); // スリープでCPUを譲渡
    }
}

int main(void) {
    stdio_init_all();

    // your_task を登録(タスク関数ポインタ、引数、優先度)
    tk::create(your_task, nullptr, tk::PRIO_NORMAL);

    // スケジューラ起動(Core 1 も自動ブート、これ以降は戻らない)
    tk::start();

    for (;;) {}
}

疎結合型タスク間通信 (Actor & EventHub)

メッセージを受け取る側は tk::Actor<MsgType, QueueSize> を継承します。

#include "tinykernel/actor.hpp"

// 1. Actor が受け取るメッセージの構造体を定義
struct YourActionMessage {
    uint32_t msg;
    char str[16];
};

// 2. メッセージを待ち受ける Actor を作成
class YourActor : public tk::Actor<YourActionMessage, 8> {
protected:
    void on_start() override {
        // タスク起動時に1回だけ呼ばれる(GPIOやデバイスの初期化など)
    }

    void on_message(const YourActionMessage& msg) override {
        // メッセージを受信すると自動起床して実行される
        // msg はキューに値コピーされているため安全
    }
};

YourActor g_actor;

int main(void) {
    stdio_init_all();

    // タスクとして登録・起動
    g_actor.start(tk::PRIO_NORMAL);

    tk::start();
}

メッセージを送る側は tk::publish() を呼びます。

static void your_sender(void *arg) {
    for (;;) {
        YourActionMessage msg{1, "Hello!"};

        // YourActionMessage を受け付けるすべての Actor に配信(1対多の同報可能)
        tk::publish(msg);

        tk::sleep_ms(1000);
    } // msg のスコープが外れてスタックが消えても、受信側は壊れない!
}

int main(void) {
    stdio_init_all();

    g_actor.start(tk::PRIO_NORMAL);
    tk::create(your_sender, nullptr, tk::PRIO_NORMAL);

    tk::start();
}

Point: 送信側は Actor のインスタンスや名前を知る必要がありません。メッセージの型情報だけで EventHub が適切な Actor へ直接届けてくれます。


セマフォ / ミューテックス (sync.hpp)

スレッド間の排他制御・同期プリミティブとして以下が用意されています。

#include "tinykernel/sync.hpp"

tk::Mutex g_mutex;
tk::BinarySemaphore g_sem(0, 1);

// RAII による安全なロック(std::lock_guard 互換)
void safe_function() {
    tk::LockGuard<tk::Mutex> lock(g_mutex);
    // クリティカルセクション...
} // スコープを抜けると自動アンロック

待機タスクの中で一番優先度(Priority)が高いタスクから順に起床します。


ペリフェラル通信の保護 (tk::PreemptGuard)

I2C や SPI など、途中でタスク切り替えを起こしたくないペリフェラルトランザクションを行う場合、割り込み(時計やタイマー)を止めずに自コアのタスクスイッチだけを安全に一時抑止できます。

{
    tk::PreemptGuard guard;

    // 割り込み(SysTick やタイマー)は通常通り動き続け、他コアも影響を受けない。
    // しかし、自コアのタスクスイッチだけは確実にブロックされる!
    i2c_write_blocking(i2c0, addr, data, len, false);

} // スコープを抜けると自動でタスクスイッチが再開(保留中のPendSVが即発火)

プロジェクトへの導入方法

独自の Pico プロジェクトの CMakeLists.txt で tinykernel ディレクトリを追加し、リンクするだけで即座に利用できます。

add_subdirectory(tinykernel)

target_link_libraries(your_project_name
    pico_stdlib
    tinykernel_lib
)

English Description

A simple and practical real-time preemptive multitasking kernel for the Raspberry Pi Pico 2 (RP2350 / ARM Cortex-M33).

Key Features

  • SMP Support (Dual Core): Fully utilizes both Cortex-M33 cores of the RP2350 to automatically execute tasks in parallel and migrate them between cores.
  • Loosely Coupled Inter-Task Communication (Pub/Sub): Equipped with a messaging system that allows safe delivery by specifying only the message type, without needing to know the receiver's Actor instance.
  • Memory Safety via Pass-by-Value: Messages are copied into the queue by value. This eliminates concerns about lifetimes or dangling pointers, even when passing local variables from the stack.
  • Zero-CPU Blocking Wait: Tasks automatically enter a blocked state (State::Blocked) while waiting for messages, semaphores, or mutexes, yielding CPU time to other tasks.
  • Full Hardware FPU Protection: Prevents FPU register corruption during context switches, even when multiple cores and tasks perform floating-point operations simultaneously.
  • Zero Dynamic Allocation (No malloc): All stacks, task slots, and message queues use static or fixed-length memory to prevent memory fragmentation.

Basic Usage

Register tasks using tk::create() (up to 30 user tasks can be registered).

#include "tinykernel/kernel.hpp"

static void your_task(void *arg) {
    for (;;) {
        // Do some work here
        tk::sleep_ms(200); // Yield CPU by sleeping
    }
}

int main(void) {
    stdio_init_all();

    // Register your_task (Task function pointer, argument, priority)
    tk::create(your_task, nullptr, tk::PRIO_NORMAL);

    // Start the scheduler (Core 1 is booted automatically; execution does not return from here)
    tk::start();

    for (;;) {}
}

Loosely Coupled Communication (Actor & EventHub)

To receive messages, inherit from tk::Actor<MsgType, QueueSize>.

#include "tinykernel/actor.hpp"

// 1. Define the message structure to be received by the Actor
struct YourActionMessage {
    uint32_t msg;
    char str[16];
};

// 2. Create an Actor that waits for messages
class YourActor : public tk::Actor<YourActionMessage, 8> {
protected:
    void on_start() override {
        // Called once when the task starts (e.g., GPIO or device initialization)
    }

    void on_message(const YourActionMessage& msg) override {
        // Automatically woken up and executed upon receiving a message
        // Safe because 'msg' is copied by value into the queue
    }
};

YourActor g_actor;

int main(void) {
    stdio_init_all();

    // Register and start as a task
    g_actor.start(tk::PRIO_NORMAL);

    tk::start();
}

To send messages, call tk::publish().

static void your_sender(void *arg) {
    for (;;) {
        YourActionMessage msg{1, "Hello!"};

        // Broadcast to all Actors that accept YourActionMessage (one-to-many)
        tk::publish(msg);

        tk::sleep_ms(1000);
    } // Even after 'msg' goes out of scope and its stack is reclaimed, the receiver remains safe!
}

int main(void) {
    stdio_init_all();

    g_actor.start(tk::PRIO_NORMAL);
    tk::create(your_sender, nullptr, tk::PRIO_NORMAL);

    tk::start();
}

Point: The sender does not need to know the instance or name of the Actor. The EventHub delivers messages directly to the appropriate Actors based solely on the message type.

Semaphores / Mutexes (sync.hpp)

The following primitives are provided for mutual exclusion and synchronization between threads:

#include "tinykernel/sync.hpp"

tk::Mutex g_mutex;
tk::BinarySemaphore g_sem(0, 1);

// Safe locking using RAII (compatible with std::lock_guard)
void safe_function() {
    tk::LockGuard<tk::Mutex> lock(g_mutex);
    // Critical section...
} // Automatically unlocked when the scope ends

Waiting tasks are woken up in order of highest priority.

Peripheral Communication Protection (tk::PreemptGuard)

When performing peripheral transactions (such as I2C or SPI) where you want to prevent task switching, tk::PreemptGuard allows you to temporarily suppress task switches on the current core without disabling interrupts (like system clocks or timers).

{
    tk::PreemptGuard guard;

    // Interrupts (SysTick, Timers) continue to run normally, and other cores are unaffected.
    // However, task switching on this specific core is guaranteed to be blocked!
    i2c_write_blocking(i2c0, addr, data, len, false);

} // Task switching resumes automatically when the scope ends (pending PendSV is triggered immediately)

Integration

To integrate tinykernel into your Pico project, simply add the directory to your CMakeLists.txt and link it:

add_subdirectory(tinykernel)

target_link_libraries(your_project_name
    pico_stdlib
    tinykernel_lib
)