Skip to content

Commit ef1eaba

Browse files
authored
freertos: idiomatic Zig wrappers for the FreeRTOS module (#920)
1 parent 5a6b42e commit ef1eaba

18 files changed

Lines changed: 2070 additions & 111 deletions

File tree

examples/raspberrypi/rp2xxx/build.zig

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,14 @@ pub fn build(b: *std.Build) void {
2525
.{ .target = raspberrypi.pico, .name = "pico_pcf8574", .file = "src/rp2040_only/pcf8574.zig" },
2626
.{ .target = raspberrypi.pico, .name = "pico_i2c_slave", .file = "src/rp2040_only/i2c_slave.zig" },
2727
.{ .target = raspberrypi.pico, .name = "pico_freertos-hello-task", .file = "src/freertos/hello_task.zig" },
28+
.{ .target = raspberrypi.pico, .name = "pico_freertos-queue-demo", .file = "src/freertos/queue_demo.zig" },
29+
.{ .target = raspberrypi.pico, .name = "pico_freertos-multitask-demo", .file = "src/freertos/multitask_demo.zig" },
2830

2931
.{ .target = raspberrypi.pico2_arm, .name = "pico2_arm_multicore", .file = "src/blinky_core1.zig" },
3032
.{ .target = raspberrypi.pico2_arm, .name = "pico2_arm_board_blinky", .file = "src/board_blinky.zig" },
3133
.{ .target = raspberrypi.pico2_arm, .name = "pico2_arm_freertos-hello-task", .file = "src/freertos/hello_task.zig" },
34+
.{ .target = raspberrypi.pico2_arm, .name = "pico2_arm_freertos-queue-demo", .file = "src/freertos/queue_demo.zig" },
35+
.{ .target = raspberrypi.pico2_arm, .name = "pico2_arm_freertos-multitask-demo", .file = "src/freertos/multitask_demo.zig" },
3236

3337
.{ .target = raspberrypi.pico_flashless, .name = "pico_flashless_blinky", .file = "src/blinky.zig" },
3438
.{ .target = raspberrypi.pico_flashless, .name = "pico_flashless_flash-program", .file = "src/rp2040_only/flash_program.zig" },
Lines changed: 30 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,23 @@
1+
//! # FreeRTOS Hello Task Example
2+
//!
3+
//! The simplest FreeRTOS example — creates a single task that prints
4+
//! "Hello from FreeRTOS!" every 500ms via UART. Demonstrates the basic
5+
//! pattern: create a task, start the scheduler, let FreeRTOS manage execution.
6+
//!
7+
//! ## Primitives Used
8+
//! - **Task**: `freertos.task.create` / `freertos.task.delay`
9+
//! - **Scheduler**: `freertos.task.start_scheduler`
10+
//!
11+
//! ## Hardware Tested
12+
//! - XIAO RP2350 (RP2350 ARM Cortex-M33)
13+
//! - Raspberry Pi Pico (RP2040)
14+
115
const std = @import("std");
216
const microzig = @import("microzig");
317

418
const freertos = @import("freertos");
5-
const freertos_os = freertos.OS;
619

720
const rp2xxx = microzig.hal;
8-
const time = rp2xxx.time;
921
const gpio = rp2xxx.gpio;
1022

1123
const uart = rp2xxx.uart.instance.num(0);
@@ -19,6 +31,8 @@ pub const microzig_options = microzig.Options{
1931
},
2032
};
2133

34+
/// Initialize UART logging, create a single FreeRTOS task, and start the scheduler.
35+
/// The scheduler never returns — FreeRTOS takes over execution from this point.
2236
pub fn main() !void {
2337
uart_tx_pin.set_function(.uart);
2438

@@ -28,59 +42,25 @@ pub fn main() !void {
2842

2943
rp2xxx.uart.init_logger(uart);
3044

31-
// Give it large stack because printing is demanding
32-
_ = freertos_os.xTaskCreate(hello_task, "hello_task", freertos_os.MINIMAL_STACK_SIZE * 8, null, freertos_os.MAX_PRIORITIES - 1, null);
33-
34-
freertos_os.vTaskStartScheduler();
45+
// Create a task using the new idiomatic API
46+
_ = try freertos.task.create(
47+
hello_task,
48+
"hello_task",
49+
freertos.config.minimal_stack_size * 8,
50+
null,
51+
freertos.config.max_priorities - 1,
52+
);
53+
54+
// Start the scheduler (never returns)
55+
freertos.task.start_scheduler();
3556
}
3657

58+
/// FreeRTOS task entry point — logs a greeting with an incrementing counter
59+
/// every 500ms. Runs forever; FreeRTOS preempts it as needed.
3760
pub fn hello_task(_: ?*anyopaque) callconv(.c) void {
3861
var i: u32 = 0;
3962
while (true) : (i += 1) {
4063
std.log.info("Hello from FreeRTOS task {}", .{i});
41-
freertos_os.vTaskDelay(500);
64+
freertos.task.delay(500);
4265
}
4366
}
44-
45-
export fn __unhandled_user_irq() callconv(.c) void {
46-
std.log.err("Unhandled IRQ called!", .{});
47-
@panic("Unhandled IRQ");
48-
}
49-
50-
///
51-
/// Some ugly glue code to implement required functions from FreeRTOS and Pico SDK
52-
/// - This can be improved later
53-
/// - Some or even all of these could be implemented in freertos module directly?
54-
/// - Multicore not supported yet - multicore_reset_core1 have to be implemented
55-
export fn panic_unsupported() callconv(.c) noreturn {
56-
@panic("not supported");
57-
}
58-
59-
export fn multicore_launch_core1(entry: *const fn () callconv(.c) void) callconv(.c) void {
60-
microzig.hal.multicore.launch_core1(@ptrCast(entry));
61-
}
62-
63-
export fn multicore_reset_core1() callconv(.c) void {
64-
// TODO: please implement this in microzig.hal.multicore and call it here
65-
}
66-
67-
export fn multicore_doorbell_claim_unused(_: c_uint, _: bool) callconv(.c) c_int {
68-
// TODO: please implement this in microzig.hal.multicore and call it here
69-
return 0;
70-
}
71-
72-
export fn clock_get_hz(_: u32) callconv(.c) u32 {
73-
std.log.info("clock_get_hz called", .{});
74-
// FIXME: this seems to return null
75-
// return microzig.hal.clock_config.sys_freq.?;
76-
return switch (microzig.hal.compatibility.chip) {
77-
.RP2040 => 125_000_000,
78-
.RP2350 => 150_000_000,
79-
};
80-
}
81-
82-
export fn spin_lock_claim(_: c_uint) callconv(.c) void {}
83-
84-
export fn next_striped_spin_lock_num() callconv(.c) c_uint {
85-
return 16;
86-
}
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
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

Comments
 (0)