main.cpp 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. #include <stdio.h>
  2. #include "hardware/gpio.h"
  3. #include "pico/stdlib.h"
  4. #include "tinykernel/kernel.hpp"
  5. #include "tinykernel/actor.hpp"
  6. #include "tinykernel/sync.hpp"
  7. #include "pico/multicore.h"
  8. // Atomic printf helper: stdio is shared, so wrap each call with the kernel lock.
  9. #define P(...) \
  10. do { \
  11. bool _lk = tk::lock(); \
  12. printf("[C%u] ", (unsigned)get_core_num()); \
  13. printf(__VA_ARGS__); \
  14. tk::unlock(_lk); \
  15. } while (0)
  16. // ---------------------------------------------------------------------------
  17. // Actor demonstration: Receiver Actor waiting for typed messages by-value.
  18. // The message (including the embedded string payload) is copied directly into the
  19. // actor's queue, so sender tasks can pass local/stack variables safely without
  20. // lifetime or dangling pointer issues.
  21. struct AppMessage {
  22. uint32_t seq;
  23. char text[32]; // Embedded payload held safely by the queue!
  24. };
  25. class LedActor : public tk::Actor<AppMessage, 8> {
  26. protected:
  27. void on_start() override {
  28. // Hardware initialization within task context
  29. gpio_init(25);
  30. gpio_set_dir(25, GPIO_OUT);
  31. gpio_put(25, 0);
  32. P("LedActor: initialized GPIO 25 in on_start()\n");
  33. }
  34. void on_message(const AppMessage& msg) override {
  35. bool state = !gpio_get(25);
  36. gpio_put(25, state);
  37. P("LedActor: received seq=%lu text=\"%s\" -> LED %s, now=%llu ms\n",
  38. (unsigned long)msg.seq, msg.text, state ? "ON" : "OFF", (unsigned long long)tk::now_ms());
  39. }
  40. };
  41. static LedActor g_led_actor;
  42. // Sender task: periodically posts messages to LedActor
  43. static void task_sender(void* arg) {
  44. (void)arg;
  45. uint32_t seq = 0;
  46. for (;;) {
  47. tk::sleep_ms(600);
  48. // Local variable allocated on task_sender's stack
  49. AppMessage msg{};
  50. msg.seq = seq++;
  51. snprintf(msg.text, sizeof(msg.text), "Ping #%lu", (unsigned long)msg.seq);
  52. P("Sender: posting text=\"%s\"...\n", msg.text);
  53. g_led_actor.post(msg); // Copied by-value into g_led_actor's queue!
  54. } // msg on stack is destroyed here, but g_led_actor safely holds the copy!
  55. }
  56. // ---------------------------------------------------------------------------
  57. // Mutex demonstration: Two tasks contend for a shared counter protected by Mutex
  58. static tk::Mutex g_counter_mutex;
  59. static uint32_t g_shared_counter = 0;
  60. static void task_mutex_worker(void* arg) {
  61. const char* tag = static_cast<const char*>(arg);
  62. for (;;) {
  63. {
  64. tk::LockGuard<tk::Mutex> guard(g_counter_mutex);
  65. g_shared_counter++;
  66. P("[%s] holds mutex: counter=%lu\n", tag, (unsigned long)g_shared_counter);
  67. volatile uint32_t n = 0;
  68. while (n < 100000u) ++n;
  69. } // guard destructor automatically calls unlock()
  70. tk::sleep_ms(350);
  71. }
  72. }
  73. // ---------------------------------------------------------------------------
  74. // Semaphore demonstration: Signaler wakes up Waiter task
  75. static tk::BinarySemaphore g_demo_sem(0, 1); // Initially 0 (blocked)
  76. static void task_sem_waiter(void* arg) {
  77. (void)arg;
  78. for (;;) {
  79. g_demo_sem.acquire(); // Blocks until release() is called
  80. P("SemWaiter: >>> WOKEN UP by semaphore! now=%llu ms\n", (unsigned long long)tk::now_ms());
  81. }
  82. }
  83. static void task_sem_signaler(void* arg) {
  84. (void)arg;
  85. for (;;) {
  86. tk::sleep_ms(1000);
  87. P("SemSignaler: releasing semaphore...\n");
  88. g_demo_sem.release();
  89. }
  90. }
  91. // ---------------------------------------------------------------------------
  92. // Static task: FPU self-check. Runs the same deterministic float/double sequence
  93. // twice; any corruption of FPU state across context switches shows up as a
  94. // mismatch or a drifting result.
  95. static float fpu_run_f(float v) {
  96. for (int i = 0; i < 10000; ++i) v = v * 1.000001f + 0.5f;
  97. return v;
  98. }
  99. static double fpu_run_d(double v) {
  100. for (long i = 0; i < 10000; ++i) v = v * 1.0000000002 + 0.5;
  101. return v;
  102. }
  103. void task_fpu(void* arg) {
  104. (void)arg;
  105. for (;;) {
  106. float fa = fpu_run_f(1.5f);
  107. float fb = fpu_run_f(1.5f);
  108. double da = fpu_run_d(2.5);
  109. double db = fpu_run_d(2.5);
  110. if (fa != fb || da != db) {
  111. P("FPU FAIL f: %8.6f vs %8.6f d: %.9f vs %.9f\n", (double)fa, (double)fb, da, db);
  112. } else {
  113. P("FPU ok f: %8.6f d: %.9f\n", (double)fa, da);
  114. }
  115. tk::sleep_ms(500);
  116. }
  117. }
  118. // ---------------------------------------------------------------------------
  119. // Preemption Guard demonstration: Simulates an atomic peripheral transaction (e.g. I2C)
  120. // During PreemptGuard, no task switch occurs even if time slices expire or interrupts fire.
  121. static void task_preempt_demo(void* arg) {
  122. (void)arg;
  123. for (;;) {
  124. tk::sleep_ms(2000);
  125. {
  126. tk::PreemptGuard guard;
  127. uint64_t t0 = tk::now_ms();
  128. P("PreemptDemo: >>> START atomic section (preemption disabled), t0=%llu ms\n", (unsigned long long)t0);
  129. // Busy wait 60ms (exceeds the 20ms time slice, but preemption is blocked!)
  130. // Meanwhile SysTick interrupt keeps running, so now_ms() keeps updating!
  131. while (tk::now_ms() - t0 < 60) {
  132. }
  133. P("PreemptDemo: <<< END atomic section, now=%llu ms (SysTick kept advancing!)\n", (unsigned long long)tk::now_ms());
  134. } // guard destructor calls preempt_enable() -> deferred task switches happen now!
  135. }
  136. }
  137. int main() {
  138. stdio_init_all();
  139. printf("tinykernel: registering dynamic tasks...\n");
  140. g_led_actor.start(tk::PRIO_NORMAL);
  141. auto h_sender = tk::create(task_sender, nullptr, tk::PRIO_NORMAL);
  142. auto h_mtx_a = tk::create(task_mutex_worker, const_cast<char*>("Mtx-A"), tk::PRIO_NORMAL);
  143. auto h_mtx_b = tk::create(task_mutex_worker, const_cast<char*>("Mtx-B"), tk::PRIO_NORMAL);
  144. auto h_waiter = tk::create(task_sem_waiter, nullptr, tk::PRIO_HIGH);
  145. auto h_sig = tk::create(task_sem_signaler, nullptr, tk::PRIO_NORMAL);
  146. auto h_preempt = tk::create(task_preempt_demo, nullptr, tk::PRIO_NORMAL);
  147. if (!g_led_actor.handle().is_valid() || !h_sender.is_valid() ||
  148. !h_mtx_a.is_valid() || !h_mtx_b.is_valid() ||
  149. !h_waiter.is_valid() || !h_sig.is_valid() || !h_preempt.is_valid()) {
  150. printf("tinykernel: task creation FAILED\n");
  151. for (;;) {
  152. }
  153. }
  154. printf("tinykernel: starting scheduler (preemptive, dual-core M33)\n");
  155. tk::start(); // never returns
  156. }