Skip to content

Commit 1983998

Browse files
author
Daniel Mironov
committed
freertos: add comprehensive module documentation
README.md with features overview, quick start guide, full API tables for all 8 sub-modules, platform support matrix, FreeRTOSConfig.h configuration guide, known limitations, and links to examples.
1 parent 28a2a8d commit 1983998

1 file changed

Lines changed: 328 additions & 0 deletions

File tree

modules/freertos/README.md

Lines changed: 328 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,328 @@
1+
# FreeRTOS Module for MicroZig
2+
3+
Idiomatic Zig wrappers for the [FreeRTOS](https://www.freertos.org/) real-time operating system kernel, integrated into the [MicroZig](https://github.com/ZigEmbeddedGroup/microzig) embedded framework. Provides type-safe tasks, queues, semaphores, mutexes, timers, event groups, and task notifications — all with Zig error handling instead of raw C return codes.
4+
5+
Hardware-tested on **Seeed XIAO RP2350** (ARM Cortex-M33).
6+
7+
## Features
8+
9+
-**Tasks** — create, delete, suspend, resume, delay, priority management
10+
-**Queues** — generic `Queue(T)` with compile-time type safety
11+
-**Semaphores** — binary and counting
12+
-**Mutexes** — standard (with priority inheritance) and recursive
13+
-**Software Timers** — auto-reload and one-shot with callback support
14+
-**Event Groups** — multi-bit synchronization and rendezvous/barrier patterns
15+
-**Task Notifications** — lightweight direct-to-task signaling
16+
-**ISR variants** — all primitives provide `*FromIsr` functions with wake flags
17+
-**Raw C escape hatch**`freertos.c.*` for anything not yet wrapped
18+
19+
## Quick Start
20+
21+
```zig
22+
const std = @import("std");
23+
const freertos = @import("freertos");
24+
25+
pub fn main() !void {
26+
// ... hardware init ...
27+
28+
_ = try freertos.task.create(
29+
hello_task,
30+
"hello",
31+
freertos.config.minimal_stack_size * 8,
32+
null,
33+
freertos.config.max_priorities - 1,
34+
);
35+
36+
// Start the scheduler — never returns
37+
freertos.task.startScheduler();
38+
}
39+
40+
fn hello_task(_: ?*anyopaque) callconv(.c) void {
41+
var i: u32 = 0;
42+
while (true) : (i += 1) {
43+
std.log.info("Hello from FreeRTOS task {}", .{i});
44+
freertos.task.delay(500); // 500 ms at default 1 kHz tick rate
45+
}
46+
}
47+
```
48+
49+
## API Reference
50+
51+
### Tasks (`freertos.task`)
52+
53+
Task creation, deletion, scheduling, and runtime queries.
54+
55+
| Function | Description |
56+
|---|---|
57+
| `create(function, name, stack_depth, params, priority)` | Create a task → `Error!TaskHandle` |
58+
| `destroy(handle)` | Delete a task (`null` = self) |
59+
| `delay(ticks)` | Block for `ticks` ticks |
60+
| `delayUntil(prev_wake_time, increment)` | Periodic delay → `bool` |
61+
| `suspend(handle)` | Suspend a task (`null` = self) |
62+
| `resume(handle)` | Resume a suspended task |
63+
| `resumeFromIsr(handle)` | Resume from ISR → `bool` (context switch needed) |
64+
| `abortDelay(handle)` | Force-unblock a task → `bool` |
65+
| `setPriority(handle, priority)` | Change a task's priority |
66+
| `getPriority(handle)` | Get current priority |
67+
| `getCurrentHandle()` | Handle of the running task |
68+
| `getHandle(name)` | Look up task by name → `?TaskHandle` |
69+
| `getName(handle)` | Task name string |
70+
| `getState(handle)` |`.running`, `.ready`, `.blocked`, `.suspended`, `.deleted` |
71+
| `getStackHighWaterMark(handle)` | Minimum free stack (words) since creation |
72+
| `getTickCount()` | Current system tick |
73+
| `getCount()` | Total number of tasks |
74+
| `startScheduler()` | Start FreeRTOS — **never returns** |
75+
| `endScheduler()` | Stop FreeRTOS |
76+
| `getSchedulerState()` |`.not_started`, `.running`, `.suspended` |
77+
| `suspendAll()` / `resumeAll()` | Disable/enable task switching |
78+
79+
### Queues (`freertos.queue`)
80+
81+
Type-safe FIFO queues for inter-task communication. `Queue(T)` is a generic — the item type is checked at compile time and `receive()` returns `T` directly.
82+
83+
```zig
84+
var q = try freertos.queue.create(u32, 10); // Queue(u32), capacity 10
85+
defer q.destroy();
86+
87+
try q.send(&@as(u32, 42), freertos.config.max_delay);
88+
const value = try q.receive(freertos.config.max_delay); // value: u32
89+
```
90+
91+
| Function | Description |
92+
|---|---|
93+
| `create(T, length)` | Create a `Queue(T)` with given capacity → `Error!Queue(T)` |
94+
| `q.send(item, timeout)` | Send to back → `Error!void` (`QueueFull` on timeout) |
95+
| `q.sendToFront(item, timeout)` | Send to front |
96+
| `q.overwrite(item)` | Overwrite single-item queue (never blocks) |
97+
| `q.receive(timeout)` | Receive and remove → `Error!T` (`QueueEmpty` on timeout) |
98+
| `q.peek(timeout)` | Read front item without removing |
99+
| `q.sendFromIsr(item)` | ISR send → `IsrResult` |
100+
| `q.receiveFromIsr()` | ISR receive → `?IsrReceiveResult(T)` |
101+
| `q.messagesWaiting()` | Number of items in queue |
102+
| `q.spacesAvailable()` | Free slots remaining |
103+
| `q.reset()` | Flush the queue |
104+
105+
### Semaphores (`freertos.semaphore`)
106+
107+
Binary and counting semaphores for synchronization and signaling between tasks/ISRs.
108+
109+
| Function | Description |
110+
|---|---|
111+
| `createBinary()` | Create binary semaphore (starts empty) → `Error!Semaphore` |
112+
| `createCounting(max, initial)` | Create counting semaphore → `Error!Semaphore` |
113+
| `s.take(timeout)` | Acquire → `Error!void` (`Timeout`) |
114+
| `s.give()` | Release → `Error!void` (`Failure` if not taken) |
115+
| `s.giveFromIsr()` | Release from ISR → `IsrResult` |
116+
| `s.takeFromIsr()` | Acquire from ISR → `IsrResult` |
117+
| `s.getCount()` | Current count (or 1/0 for binary) |
118+
| `s.destroy()` | Free the semaphore |
119+
120+
### Mutexes (`freertos.mutex`)
121+
122+
Standard and recursive mutexes with **priority inheritance** to prevent priority inversion. Use mutexes to protect shared resources; use semaphores for signaling.
123+
124+
```zig
125+
var mtx = try freertos.mutex.create();
126+
defer mtx.destroy();
127+
128+
try mtx.acquire(freertos.config.max_delay);
129+
defer mtx.release() catch {};
130+
131+
// ... access shared resource ...
132+
```
133+
134+
| Function | Description |
135+
|---|---|
136+
| `create()` | Create a standard mutex → `Error!Mutex` |
137+
| `createRecursive()` | Create a recursive mutex → `Error!Recursive` |
138+
| `m.acquire(timeout)` | Lock → `Error!void` (`Timeout`) |
139+
| `m.release()` | Unlock → `Error!void` (`Failure` if not held) |
140+
| `m.getHolder()` | Task holding the mutex → `?TaskHandle` |
141+
| `m.destroy()` | Free the mutex |
142+
143+
Recursive mutexes can be acquired multiple times by the same task — each `acquire` must be paired with a `release`.
144+
145+
### Event Groups (`freertos.event_group`)
146+
147+
Multi-bit synchronization primitives. Tasks can wait on any combination of event bits, enabling rendezvous-style coordination.
148+
149+
```zig
150+
var events = try freertos.event_group.create();
151+
defer events.destroy();
152+
153+
// Task A sets bit 0
154+
_ = events.setBits(0x01);
155+
156+
// Task B waits for bits 0 AND 1
157+
const bits = try events.waitBits(0x03, .{ .wait_for_all = true, .timeout = 1000 });
158+
```
159+
160+
| Function | Description |
161+
|---|---|
162+
| `create()` | Create an event group → `Error!EventGroup` |
163+
| `e.setBits(bits)` | Set bits → returns new value |
164+
| `e.clearBits(bits)` | Clear bits → returns previous value |
165+
| `e.getBits()` | Read current bits |
166+
| `e.waitBits(bits, opts)` | Wait for bit pattern → `Error!EventBits` (`Timeout`) |
167+
| `e.sync(set, wait, timeout)` | Barrier: set bits then wait for others → `Error!EventBits` |
168+
| `e.setBitsFromIsr(bits)` | Set bits from ISR → `IsrResult` |
169+
| `e.destroy()` | Free the event group |
170+
171+
`WaitOptions` fields: `clear_on_exit` (default `true`), `wait_for_all` (default `false`), `timeout` (default `max_delay`).
172+
173+
### Software Timers (`freertos.timer`)
174+
175+
Periodic or one-shot timers that execute a callback in the timer daemon task context.
176+
177+
```zig
178+
var tmr = try freertos.timer.create("heartbeat", 1000, true, null, my_callback);
179+
try tmr.start(0);
180+
defer tmr.destroyBlocking();
181+
```
182+
183+
| Function | Description |
184+
|---|---|
185+
| `create(name, period, auto_reload, id, callback)` | Create a timer → `Error!Timer` |
186+
| `t.start(cmd_timeout)` | Start/restart the timer |
187+
| `t.stop(cmd_timeout)` | Stop the timer |
188+
| `t.reset(cmd_timeout)` | Reset countdown from now |
189+
| `t.changePeriod(new_period, cmd_timeout)` | Change period and restart |
190+
| `t.destroy(cmd_timeout)` | Delete timer (can fail if queue full) |
191+
| `t.destroyBlocking()` | Delete timer, wait forever (safe for `defer`) |
192+
| `t.isActive()` | Check if running → `bool` |
193+
| `t.getName()` / `t.getPeriod()` / `t.getExpiryTime()` | Timer properties |
194+
| `t.getId()` / `t.setId(ptr)` | User-defined ID pointer |
195+
| `t.getAutoReload()` / `t.setAutoReload(bool)` | Auto-reload mode |
196+
| `t.startFromIsr()` / `t.stopFromIsr()` / `t.resetFromIsr()` | ISR variants → `IsrResult` |
197+
| `pendFunctionCall(fn, p1, p2, timeout)` | Defer a function call to the timer daemon |
198+
199+
> ⚠️ **Implementation note:** The C macros `xTimerStart`, `xTimerStop`, etc. pass untyped `NULL` that Zig's `@cImport` can't coerce. The wrapper calls `xTimerGenericCommandFromTask` / `FromISR` directly, which is functionally identical.
200+
201+
### Task Notifications (`freertos.notification`)
202+
203+
Lightweight direct-to-task notifications — faster and smaller than semaphores or event groups. Each task has a 32-bit notification value per index.
204+
205+
```zig
206+
// Lightweight binary semaphore pattern:
207+
freertos.notification.give(task_handle) catch {}; // sender
208+
_ = freertos.notification.take(true, freertos.config.max_delay) catch {}; // receiver
209+
```
210+
211+
| Function | Description |
212+
|---|---|
213+
| `notify(handle, value, action)` | Send notification at index 0 |
214+
| `notifyIndexed(handle, index, value, action)` | Send at specific index |
215+
| `notifyAndQuery(handle, value, action)` | Send and get previous value → `Error!u32` |
216+
| `give(handle)` | Increment notification (lightweight semaphore give) |
217+
| `notifyFromIsr(handle, value, action)` | Send from ISR → `IsrResult` |
218+
| `giveFromIsr(handle)` | Increment from ISR → `IsrResult` |
219+
| `wait(clear_entry, clear_exit, timeout)` | Wait for notification → `Error!u32` |
220+
| `waitIndexed(index, clear_entry, clear_exit, timeout)` | Wait at specific index |
221+
| `take(clear_on_exit, timeout)` | Binary/counting semaphore pattern → `Error!u32` |
222+
| `clearState(handle)` | Clear pending state → `bool` |
223+
| `clearBits(handle, bits)` | Clear bits in notification value → previous value |
224+
225+
`Action` enum: `.none`, `.set_bits`, `.increment`, `.set_value_overwrite`, `.set_value_no_overwrite`
226+
227+
### Error Handling
228+
229+
All fallible operations return `freertos.config.Error`:
230+
231+
```zig
232+
pub const Error = error{
233+
OutOfMemory, // Heap exhausted (create functions)
234+
Timeout, // Operation timed out
235+
QueueFull, // Queue send failed
236+
QueueEmpty, // Queue receive failed
237+
Failure, // Generic pdFAIL
238+
};
239+
```
240+
241+
Two helper functions convert C return codes to Zig errors:
242+
243+
```zig
244+
// Check pdPASS/pdFAIL return codes
245+
try freertos.config.checkBaseType(rc);
246+
247+
// Convert nullable C handle → Zig error (null → error.OutOfMemory)
248+
const handle = try freertos.config.checkHandle(c.TaskHandle_t, raw_handle);
249+
```
250+
251+
### Raw C Access
252+
253+
For anything not wrapped, use `freertos.c` to access the full FreeRTOS C API directly:
254+
255+
```zig
256+
const freertos = @import("freertos");
257+
258+
// Direct C call
259+
freertos.c.vTaskDelay(100);
260+
261+
// Access C constants
262+
const pass = freertos.c.pdPASS;
263+
```
264+
265+
## Examples
266+
267+
| Example | Description |
268+
|---|---|
269+
| [`hello_task.zig`](../../examples/raspberrypi/rp2xxx/src/freertos/hello_task.zig) | Minimal — one task, UART logging, periodic delay |
270+
| [`queue_demo.zig`](../../examples/raspberrypi/rp2xxx/src/freertos/queue_demo.zig) | Producer/consumer pattern with type-safe `Queue(u32)` |
271+
| [`multitask_demo.zig`](../../examples/raspberrypi/rp2xxx/src/freertos/multitask_demo.zig) | 4 cooperating tasks using queues, mutexes, timers, event groups, notifications, and semaphores |
272+
273+
## Platform Support
274+
275+
| Platform | Chip | Status | Notes |
276+
|---|---|---|---|
277+
| RP2040 (Pico) | ARM Cortex-M0+ | ✅ Builds | Not yet hardware-tested |
278+
| RP2350 ARM (Pico 2) | ARM Cortex-M33 | ✅ Tested on hardware | XIAO RP2350 |
279+
| RP2350 RISC-V | Hazard3 | ❌ Blocked | Linker symbol issues with RISC-V port |
280+
| ESP32 | Xtensa/RISC-V | 🔲 Planned ||
281+
| STM32 | ARM Cortex-M | 🔲 Planned ||
282+
283+
## Configuration
284+
285+
Each platform has its own `FreeRTOSConfig.h` under `config/<platform>/`:
286+
287+
```
288+
modules/freertos/config/
289+
├── RP2040/FreeRTOSConfig.h
290+
└── RP2350_ARM/FreeRTOSConfig.h
291+
```
292+
293+
### Key settings (defaults)
294+
295+
| Setting | Value | Notes |
296+
|---|---|---|
297+
| `configTICK_RATE_HZ` | 1000 | 1 ms tick resolution |
298+
| `configMAX_PRIORITIES` | 32 | Priority levels 0–31 |
299+
| `configMINIMAL_STACK_SIZE` | 256 | Words (1 KB on 32-bit) |
300+
| `configTOTAL_HEAP_SIZE` | 128 KB | FreeRTOS heap (heap_4) |
301+
| `configCHECK_FOR_STACK_OVERFLOW` | 2 | Full stack painting check — traps via `@trap()` |
302+
| `configSUPPORT_DYNAMIC_ALLOCATION` | 1 | Dynamic only (static not yet supported) |
303+
| `configNUMBER_OF_CORES` | 1 | Single-core only |
304+
305+
### Pico SDK interop (disabled)
306+
307+
`configSUPPORT_PICO_SYNC_INTEROP` and `configSUPPORT_PICO_TIME_INTEROP` are **disabled**. The FreeRTOS RP2xxx port relies on `.init_array` constructors to initialize spin-locks and event groups before `main()`. MicroZig does not process `.init_array`, so enabling these causes a NULL dereference and Usage Fault on scheduler start. Re-enable only after MicroZig adds `.init_array` support.
308+
309+
## Known Limitations
310+
311+
- **Static allocation**`configSUPPORT_STATIC_ALLOCATION` is off; `xTaskCreateStatic` etc. are not wrapped
312+
- **Stream/message buffers** — not yet wrapped (use `freertos.c` as a workaround)
313+
- **Multicore SMP**`configNUMBER_OF_CORES` is 1; FreeRTOS SMP support is not yet integrated
314+
- **`.init_array` constructors** — not processed by MicroZig startup, blocking Pico SDK interop
315+
- **RISC-V** — RP2350 RISC-V port has unresolved linker symbols
316+
317+
## Contributing
318+
319+
FreeRTOS integration is tracked in [**issue #880**](https://github.com/ZigEmbeddedGroup/microzig/issues/880).
320+
321+
Areas that need work:
322+
323+
- Static allocation variants (`*CreateStatic`)
324+
- Stream and message buffer wrappers
325+
- Multicore SMP support
326+
- `.init_array` processing in MicroZig startup
327+
- Additional platform ports (ESP32, STM32)
328+
- RISC-V port linker fixes

0 commit comments

Comments
 (0)