|
| 1 | +//! FreeRTOS Multitask Demo — Sensor Dashboard |
| 2 | +//! |
| 3 | +//! Showcases queues, mutexes, timers, event groups, notifications, and |
| 4 | +//! semaphores working together across 4 cooperating tasks: |
| 5 | +//! |
| 6 | +//! sensor_task — produces fake readings → queue + notification |
| 7 | +//! logger_task — consumes the queue, prints data under mutex |
| 8 | +//! watchdog_task — waits on event-group bit set by a periodic timer |
| 9 | +//! stats_task — periodically reports task count and tick count |
| 10 | +//! |
| 11 | +//! A binary semaphore acts as a startup gate so every task begins in sync. |
| 12 | + |
| 13 | +const std = @import("std"); |
| 14 | +const microzig = @import("microzig"); |
| 15 | +const freertos = @import("freertos"); |
| 16 | + |
| 17 | +const rp2xxx = microzig.hal; |
| 18 | +const gpio = rp2xxx.gpio; |
| 19 | + |
| 20 | +const uart = rp2xxx.uart.instance.num(0); |
| 21 | +const uart_tx_pin = gpio.num(0); |
| 22 | + |
| 23 | +pub const microzig_options = microzig.Options{ |
| 24 | + .log_level = .debug, |
| 25 | + .logFn = rp2xxx.uart.log, |
| 26 | + .cpu = .{ |
| 27 | + .ram_vector_table = true, |
| 28 | + }, |
| 29 | +}; |
| 30 | + |
| 31 | +// ── Shared Primitives ─────────────────────────────────────────────────── |
| 32 | + |
| 33 | +/// Queue carrying sensor readings from sensor_task → logger_task. |
| 34 | +var sensor_queue: freertos.queue.Queue(u32) = undefined; |
| 35 | + |
| 36 | +/// Mutex protecting serial output (shared by logger_task and stats_task). |
| 37 | +var uart_mutex: freertos.mutex.Mutex = undefined; |
| 38 | + |
| 39 | +/// Event group: timer callback sets WATCHDOG_BIT, watchdog_task waits on it. |
| 40 | +var watchdog_events: freertos.event_group.EventGroup = undefined; |
| 41 | + |
| 42 | +/// Binary semaphore used as a startup gate — all tasks block until released. |
| 43 | +var startup_gate: freertos.semaphore.Semaphore = undefined; |
| 44 | + |
| 45 | +/// Handle to the logger task, used for direct-to-task notifications. |
| 46 | +var logger_handle: freertos.config.TaskHandle = undefined; |
| 47 | + |
| 48 | +/// Periodic 3-second heartbeat timer. |
| 49 | +var heartbeat_timer: freertos.timer.Timer = undefined; |
| 50 | + |
| 51 | +const WATCHDOG_BIT: freertos.config.EventBits = 0x01; |
| 52 | + |
| 53 | +// ── Main ──────────────────────────────────────────────────────────────── |
| 54 | + |
| 55 | +/// Initialize hardware, create all shared FreeRTOS primitives (queue, mutex, |
| 56 | +/// event group, semaphore, timer), spawn the four tasks, release the startup |
| 57 | +/// gate, and hand control to the FreeRTOS scheduler (which never returns). |
| 58 | +pub fn main() !void { |
| 59 | + // Hardware setup |
| 60 | + uart_tx_pin.set_function(.uart); |
| 61 | + uart.apply(.{ .clock_config = rp2xxx.clock_config }); |
| 62 | + rp2xxx.uart.init_logger(uart); |
| 63 | + |
| 64 | + std.log.info("[main] Starting FreeRTOS multitask demo...", .{}); |
| 65 | + |
| 66 | + // Create shared primitives |
| 67 | + sensor_queue = try freertos.queue.create(u32, 10); |
| 68 | + uart_mutex = try freertos.mutex.create(); |
| 69 | + watchdog_events = try freertos.event_group.create(); |
| 70 | + startup_gate = try freertos.semaphore.create_binary(); |
| 71 | + |
| 72 | + // 3-second auto-reload timer that fires the heartbeat callback |
| 73 | + heartbeat_timer = try freertos.timer.create( |
| 74 | + "heartbeat", |
| 75 | + 3000, |
| 76 | + true, |
| 77 | + null, |
| 78 | + heartbeat_timer_callback, |
| 79 | + ); |
| 80 | + |
| 81 | + // Create the four tasks (highest priority first) |
| 82 | + _ = try freertos.task.create(sensor_task, "sensor", 2048, null, 3); |
| 83 | + logger_handle = try freertos.task.create(logger_task, "logger", 2048, null, 2); |
| 84 | + _ = try freertos.task.create(watchdog_task, "watchdog", 2048, null, 1); |
| 85 | + _ = try freertos.task.create(stats_task, "stats", 2048, null, 1); |
| 86 | + |
| 87 | + std.log.info("[main] All tasks created, releasing startup gate", .{}); |
| 88 | + |
| 89 | + // Release the startup gate — tasks cascade through take/give |
| 90 | + startup_gate.give() catch {}; |
| 91 | + |
| 92 | + // Hand control to FreeRTOS (never returns) |
| 93 | + freertos.task.start_scheduler(); |
| 94 | +} |
| 95 | + |
| 96 | +// ── Sensor Task ───────────────────────────────────────────────────────── |
| 97 | +/// Simulates reading a sensor every 500 ms. Sends the reading to a queue |
| 98 | +/// and pings logger_task via a lightweight notification. |
| 99 | +fn sensor_task(_: ?*anyopaque) callconv(.c) void { |
| 100 | + wait_for_startup_gate(); |
| 101 | + |
| 102 | + var reading: u32 = 0; |
| 103 | + while (true) : (reading +%= 1) { |
| 104 | + // Enqueue the reading for the logger |
| 105 | + sensor_queue.send(&reading, freertos.config.max_delay) catch continue; |
| 106 | + |
| 107 | + // Lightweight signal — wake the logger |
| 108 | + freertos.notification.give(logger_handle) catch {}; |
| 109 | + |
| 110 | + // Log the reading under mutex |
| 111 | + uart_mutex.acquire(freertos.config.max_delay) catch {}; |
| 112 | + std.log.info("[sensor] reading: {}", .{reading}); |
| 113 | + uart_mutex.release() catch {}; |
| 114 | + |
| 115 | + freertos.task.delay(500); |
| 116 | + } |
| 117 | +} |
| 118 | + |
| 119 | +// ── Logger Task ───────────────────────────────────────────────────────── |
| 120 | +/// Waits for a notification, then drains the sensor queue and prints each |
| 121 | +/// reading under the UART mutex. |
| 122 | +fn logger_task(_: ?*anyopaque) callconv(.c) void { |
| 123 | + wait_for_startup_gate(); |
| 124 | + |
| 125 | + while (true) { |
| 126 | + // Block until sensor_task sends a notification |
| 127 | + _ = freertos.notification.take(true, freertos.config.max_delay) catch continue; |
| 128 | + |
| 129 | + // Drain every pending reading from the queue |
| 130 | + while (true) { |
| 131 | + const value = sensor_queue.receive(0) catch break; |
| 132 | + uart_mutex.acquire(freertos.config.max_delay) catch {}; |
| 133 | + std.log.info("[logger] logged sensor data: {}", .{value}); |
| 134 | + uart_mutex.release() catch {}; |
| 135 | + } |
| 136 | + } |
| 137 | +} |
| 138 | + |
| 139 | +// ── Watchdog Task ─────────────────────────────────────────────────────── |
| 140 | +/// Blocks on an event-group bit that the heartbeat timer callback sets |
| 141 | +/// every 3 seconds, then logs a heartbeat message. |
| 142 | +fn watchdog_task(_: ?*anyopaque) callconv(.c) void { |
| 143 | + wait_for_startup_gate(); |
| 144 | + |
| 145 | + // Arm the heartbeat timer now that the scheduler (and its timer queue) is running. |
| 146 | + heartbeat_timer.start(0) catch |err| { |
| 147 | + std.log.err("[watchdog] timer start failed: {}", .{err}); |
| 148 | + }; |
| 149 | + |
| 150 | + var beat: u32 = 0; |
| 151 | + while (true) { |
| 152 | + // Wait for the timer to signal |
| 153 | + _ = watchdog_events.wait_bits(WATCHDOG_BIT, .{ |
| 154 | + .clear_on_exit = true, |
| 155 | + .wait_for_all = false, |
| 156 | + .timeout = freertos.config.max_delay, |
| 157 | + }) catch continue; |
| 158 | + |
| 159 | + beat += 1; |
| 160 | + |
| 161 | + uart_mutex.acquire(freertos.config.max_delay) catch {}; |
| 162 | + std.log.info("[watchdog] heartbeat #{} (tick: {})", .{ |
| 163 | + beat, |
| 164 | + freertos.task.get_tick_count(), |
| 165 | + }); |
| 166 | + uart_mutex.release() catch {}; |
| 167 | + } |
| 168 | +} |
| 169 | + |
| 170 | +// ── Stats Task ────────────────────────────────────────────────────────── |
| 171 | +/// Every 5 seconds, reports the number of active tasks, uptime, and |
| 172 | +/// current queue depth. |
| 173 | +fn stats_task(_: ?*anyopaque) callconv(.c) void { |
| 174 | + wait_for_startup_gate(); |
| 175 | + |
| 176 | + while (true) { |
| 177 | + freertos.task.delay(5000); |
| 178 | + |
| 179 | + uart_mutex.acquire(freertos.config.max_delay) catch {}; |
| 180 | + std.log.info("[stats] tasks: {}, uptime: {} ticks, queue depth: {}/10", .{ |
| 181 | + freertos.task.get_count(), |
| 182 | + freertos.task.get_tick_count(), |
| 183 | + sensor_queue.messages_waiting(), |
| 184 | + }); |
| 185 | + uart_mutex.release() catch {}; |
| 186 | + } |
| 187 | +} |
| 188 | + |
| 189 | +// ── Timer Callback ────────────────────────────────────────────────────── |
| 190 | +/// Runs in the timer-daemon context; sets the watchdog event bit so |
| 191 | +/// watchdog_task unblocks. |
| 192 | +fn heartbeat_timer_callback(_: freertos.config.TimerHandle) callconv(.c) void { |
| 193 | + _ = watchdog_events.set_bits(WATCHDOG_BIT); |
| 194 | +} |
| 195 | + |
| 196 | +// ── Startup Gate Helper ───────────────────────────────────────────────── |
| 197 | +/// Every task calls this once at entry. The binary semaphore cascades: |
| 198 | +/// each task takes → gives back, so the next one can proceed. |
| 199 | +fn wait_for_startup_gate() void { |
| 200 | + startup_gate.take(freertos.config.max_delay) catch return; |
| 201 | + startup_gate.give() catch {}; |
| 202 | +} |
0 commit comments