| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189 |
- #include <stdio.h>
- #include "hardware/gpio.h"
- #include "pico/stdlib.h"
- #include "tinykernel/kernel.hpp"
- #include "tinykernel/actor.hpp"
- #include "tinykernel/sync.hpp"
- #include "pico/multicore.h"
- // Atomic printf helper: stdio is shared, so wrap each call with the kernel lock.
- #define P(...) \
- do { \
- bool _lk = tk::lock(); \
- printf("[C%u] ", (unsigned)get_core_num()); \
- printf(__VA_ARGS__); \
- tk::unlock(_lk); \
- } while (0)
- // ---------------------------------------------------------------------------
- // Actor demonstration: 1-to-N Pub/Sub via EventHub.
- // Senders call tk::publish(msg) without needing to know any Actor instance!
- // All actors subscribed to AppMessage receive their own copy in their queues.
- struct AppMessage {
- uint32_t seq;
- char text[32]; // Embedded payload held safely by the queue!
- };
- class LedActor : public tk::Actor<AppMessage, 8> {
- protected:
- void on_start() override {
- gpio_init(25);
- gpio_set_dir(25, GPIO_OUT);
- gpio_put(25, 0);
- P("LedActor: initialized GPIO 25 in on_start()\n");
- }
- void on_message(const AppMessage& msg) override {
- bool state = !gpio_get(25);
- gpio_put(25, state);
- P("LedActor: seq=%lu text=\"%s\" -> LED %s, now=%llu ms\n",
- (unsigned long)msg.seq, msg.text, state ? "ON" : "OFF", (unsigned long long)tk::now_ms());
- }
- };
- // Second actor subscribed to the exact same AppMessage type!
- class LogActor : public tk::Actor<AppMessage, 8> {
- protected:
- void on_message(const AppMessage& msg) override {
- P("LogActor: [AUDIT] seq=%lu text=\"%s\", now=%llu ms\n",
- (unsigned long)msg.seq, msg.text, (unsigned long long)tk::now_ms());
- }
- };
- static LedActor g_led_actor;
- static LogActor g_log_actor;
- // Sender task: publishes messages anonymously (without knowing who the actors are!)
- static void task_sender(void* arg) {
- (void)arg;
- uint32_t seq = 0;
- for (;;) {
- tk::sleep_ms(800);
- // Local variable allocated on task_sender's stack
- AppMessage msg{};
- msg.seq = seq++;
- snprintf(msg.text, sizeof(msg.text), "Ping #%lu", (unsigned long)msg.seq);
- // Anonymous 1-to-N publish!
- size_t n = tk::publish(msg);
- P("Sender: published text=\"%s\" to %lu subscriber(s)!\n", msg.text, (unsigned long)n);
- }
- }
- // ---------------------------------------------------------------------------
- // Mutex demonstration: Two tasks contend for a shared counter protected by Mutex
- static tk::Mutex g_counter_mutex;
- static uint32_t g_shared_counter = 0;
- static void task_mutex_worker(void* arg) {
- const char* tag = static_cast<const char*>(arg);
- for (;;) {
- {
- tk::LockGuard<tk::Mutex> guard(g_counter_mutex);
- g_shared_counter++;
- P("[%s] holds mutex: counter=%lu\n", tag, (unsigned long)g_shared_counter);
- volatile uint32_t n = 0;
- while (n < 100000u) ++n;
- } // guard destructor automatically calls unlock()
- tk::sleep_ms(350);
- }
- }
- // ---------------------------------------------------------------------------
- // Semaphore demonstration: Signaler wakes up Waiter task
- static tk::BinarySemaphore g_demo_sem(0, 1); // Initially 0 (blocked)
- static void task_sem_waiter(void* arg) {
- (void)arg;
- for (;;) {
- g_demo_sem.acquire(); // Blocks until release() is called
- P("SemWaiter: >>> WOKEN UP by semaphore! now=%llu ms\n", (unsigned long long)tk::now_ms());
- }
- }
- static void task_sem_signaler(void* arg) {
- (void)arg;
- for (;;) {
- tk::sleep_ms(1000);
- P("SemSignaler: releasing semaphore...\n");
- g_demo_sem.release();
- }
- }
- // ---------------------------------------------------------------------------
- // Static task: FPU self-check. Runs the same deterministic float/double sequence
- // twice; any corruption of FPU state across context switches shows up as a
- // mismatch or a drifting result.
- static float fpu_run_f(float v) {
- for (int i = 0; i < 10000; ++i) v = v * 1.000001f + 0.5f;
- return v;
- }
- static double fpu_run_d(double v) {
- for (long i = 0; i < 10000; ++i) v = v * 1.0000000002 + 0.5;
- return v;
- }
- void task_fpu(void* arg) {
- (void)arg;
- for (;;) {
- float fa = fpu_run_f(1.5f);
- float fb = fpu_run_f(1.5f);
- double da = fpu_run_d(2.5);
- double db = fpu_run_d(2.5);
- if (fa != fb || da != db) {
- P("FPU FAIL f: %8.6f vs %8.6f d: %.9f vs %.9f\n", (double)fa, (double)fb, da, db);
- } else {
- P("FPU ok f: %8.6f d: %.9f\n", (double)fa, da);
- }
- tk::sleep_ms(500);
- }
- }
- // ---------------------------------------------------------------------------
- // Preemption Guard demonstration: Simulates an atomic peripheral transaction (e.g. I2C)
- // During PreemptGuard, no task switch occurs even if time slices expire or interrupts fire.
- static void task_preempt_demo(void* arg) {
- (void)arg;
- for (;;) {
- tk::sleep_ms(2000);
- {
- tk::PreemptGuard guard;
- uint64_t t0 = tk::now_ms();
- P("PreemptDemo: >>> START atomic section (preemption disabled), t0=%llu ms\n", (unsigned long long)t0);
- // Busy wait 60ms (exceeds the 20ms time slice, but preemption is blocked!)
- // Meanwhile SysTick interrupt keeps running, so now_ms() keeps updating!
- while (tk::now_ms() - t0 < 60) {
- }
- P("PreemptDemo: <<< END atomic section, now=%llu ms (SysTick kept advancing!)\n", (unsigned long long)tk::now_ms());
- } // guard destructor calls preempt_enable() -> deferred task switches happen now!
- }
- }
- int main() {
- stdio_init_all();
- printf("tinykernel: registering dynamic tasks...\n");
- g_led_actor.start(tk::PRIO_NORMAL);
- g_log_actor.start(tk::PRIO_NORMAL);
- auto h_sender = tk::create(task_sender, nullptr, tk::PRIO_NORMAL);
- auto h_mtx_a = tk::create(task_mutex_worker, const_cast<char*>("Mtx-A"), tk::PRIO_NORMAL);
- auto h_mtx_b = tk::create(task_mutex_worker, const_cast<char*>("Mtx-B"), tk::PRIO_NORMAL);
- auto h_waiter = tk::create(task_sem_waiter, nullptr, tk::PRIO_HIGH);
- auto h_sig = tk::create(task_sem_signaler, nullptr, tk::PRIO_NORMAL);
- auto h_preempt = tk::create(task_preempt_demo, nullptr, tk::PRIO_NORMAL);
- if (!g_led_actor.handle().is_valid() || !g_log_actor.handle().is_valid() ||
- !h_sender.is_valid() ||
- !h_mtx_a.is_valid() || !h_mtx_b.is_valid() ||
- !h_waiter.is_valid() || !h_sig.is_valid() || !h_preempt.is_valid()) {
- printf("tinykernel: task creation FAILED\n");
- for (;;) {
- }
- }
- printf("tinykernel: starting scheduler (preemptive, dual-core M33)\n");
- tk::start(); // never returns
- }
|