Skip to content

Inter-Thread Communication

oveRTOS provides three IPC primitives for coordinating work across threads and interrupt service routines. Each is optimised for a distinct communication pattern:

Primitive Best for Data model
Queue Passing discrete, fixed-size items between threads N typed items, FIFO
EventGroup Signalling state transitions with bit flags Bitmask of boolean flags
Stream Moving a continuous byte stream with a trigger threshold Circular byte buffer

All three follow the same allocation pattern as the rest of oveRTOS: _init() / _deinit() consume caller-supplied static storage (work in zero-heap mode), while _create() / _destroy() allocate from the heap (only when OVE_HEAP_* is defined). The OVE_*_DEFINE_STATIC macros in ove/storage.h combine declaration and init into one statement.

Choosing a Primitive

flowchart TD
    Q{What are you\ntransferring?}
    Q -->|"Fixed-size structs\nor pointers"| QUE["Use Queue\nove_queue_*"]
    Q -->|"Boolean state flags\nor event notifications"| EVG["Use EventGroup\nove_eventgroup_*"]
    Q -->|"Raw bytes / serial data\nor audio samples"| STR["Use Stream\nove_stream_*"]

    QUE --> QISR{Need to send\nfrom an ISR?}
    QISR -->|Yes| QISRN["ove_queue_send_from_isr()"]
    QISR -->|No| QTHRD["ove_queue_send()"]

    EVG --> EVGWAIT{Wait for all\nbits or any?}
    EVGWAIT -->|All| EVGALL["OVE_EG_WAIT_ALL flag"]
    EVGWAIT -->|Any| EVGANY["flags = 0 (default)"]

    STR --> STRISR{Producer is\nan ISR?}
    STRISR -->|Yes| STRISRN["ove_stream_send_from_isr()"]
    STRISR -->|No| STRTHRD["ove_stream_send()"]

Queue

A thread-safe FIFO that transfers fixed-size items by value. The sender copies the item into the queue; the receiver copies it back out. Both blocking and non-blocking variants are available, and ISR-safe variants allow interrupt handlers to enqueue items without blocking.

graph LR
    subgraph Threads
        PROD["Producer Thread"]
        ISR["ISR"]
        CONS["Consumer Thread"]
    end

    subgraph Q["Queue  (depth N, item size S)"]
        direction LR
        SLOT0["[item 0]"]
        SLOT1["[item 1]"]
        SLOTN["[item N-1]"]
        SLOT0 --> SLOT1 --> SLOTN
    end

    PROD -->|"ove_queue_send()\n(blocks if full)"| Q
    ISR  -->|"ove_queue_send_from_isr()\n(never blocks)"| Q
    Q    -->|"ove_queue_receive()\n(blocks if empty)"| CONS

    style PROD fill:#4a9,stroke:#333,color:#fff
    style ISR  fill:#a54,stroke:#333,color:#fff
    style CONS fill:#48b,stroke:#333,color:#fff

API

Function Signature Description
ove_queue_init int (ove_queue_t *q, ove_queue_storage_t *storage, void *buffer, size_t item_size, unsigned int max_items) Initialise a queue from caller-supplied storage and data buffer.
ove_queue_deinit void (ove_queue_t q) Release the queue. Static storage is not freed.
ove_queue_create int (ove_queue_t *q, size_t item_size, unsigned int max_items) Heap-allocate storage and data buffer. Requires OVE_HEAP_QUEUE.
ove_queue_destroy void (ove_queue_t q) Free a heap-allocated queue.
ove_queue_send int (ove_queue_t q, const void *data, uint64_t timeout_ns) Copy an item into the queue. Blocks up to timeout_ns if full (use OVE_WAIT_FOREVER for indefinite). Returns OVE_OK, OVE_ERR_TIMEOUT, or OVE_ERR_QUEUE_FULL.
ove_queue_receive int (ove_queue_t q, void *buf, uint64_t timeout_ns) Copy the oldest item out of the queue. Blocks if empty.
ove_queue_send_from_isr int (ove_queue_t q, const void *data) ISR-safe non-blocking send. Returns OVE_ERR_QUEUE_FULL if no space.
ove_queue_receive_from_isr int (ove_queue_t q, void *buf) ISR-safe non-blocking receive.

Static-storage macro: OVE_QUEUE_DEFINE_STATIC(name, item_sz, max) (in ove/storage.h).

Example: IR Load Requests

#include <ove/ove.h>

#define IR_REQ_DEPTH 4

struct ir_request {
    uint8_t  slot;          /* IR preset slot 0–7 */
    uint32_t file_offset;   /* byte offset in flash */
    uint32_t length;        /* IR length in samples */
};

/* Heap mode: allocate at startup */
static ove_queue_t g_ir_req_q;

void app_init(void)
{
    ove_queue_create(&g_ir_req_q, sizeof(struct ir_request), IR_REQ_DEPTH);
}

/* Or, zero-heap mode: one-shot static helper */
/* OVE_QUEUE_DEFINE_STATIC(g_ir_req_q, sizeof(struct ir_request), IR_REQ_DEPTH); */

/* Control task — posts a load request */
struct ir_request req = { .slot = 2, .file_offset = 0x8000, .length = 1024 };
ove_queue_send(g_ir_req_q, &req, OVE_WAIT_FOREVER);

/* DSP task — drains requests between audio cycles */
struct ir_request rx;
while (ove_queue_receive(g_ir_req_q, &rx, 0) == OVE_OK) {
    hiroic_load_ir(rx.slot, rx.file_offset, rx.length);
}

EventGroup

An EventGroup holds a small bitmask of independent boolean bits (the exact width is ove_eventbits_t). Any thread (or ISR) can set or clear bits; waiting threads are unblocked when a target bit pattern is satisfied. EventGroups are ideal for broadcasting state transitions without copying data.

graph TB
    subgraph Setters
        T1["Load Task"]
        T2["Bypass Task"]
        T3["Clip Detect ISR"]
    end

    subgraph EG["EventGroup"]
        B0["bit 0: IR_LOADED"]
        B1["bit 1: BYPASS_ACTIVE"]
        B2["bit 2: CLIP_DETECTED"]
    end

    subgraph Waiters
        DSP["DSP Task\n(waits for IR_LOADED)"]
        UI["UI Task\n(waits for BYPASS or CLIP)"]
    end

    T1 -->|"ove_eventgroup_set_bits(eg, BIT_IR_LOADED)"| B0
    T2 -->|"ove_eventgroup_set_bits(eg, BIT_BYPASS)"| B1
    T3 -->|"ove_eventgroup_set_bits_from_isr(eg, BIT_CLIP)"| B2

    B0 -->|"OVE_EG_WAIT_ALL\nbit 0 satisfied"| DSP
    B1 -->|"any bit"| UI
    B2 -->|"any bit"| UI

    style T1 fill:#4a9,stroke:#333,color:#fff
    style T2 fill:#4a9,stroke:#333,color:#fff
    style T3 fill:#a54,stroke:#333,color:#fff
    style DSP fill:#48b,stroke:#333,color:#fff
    style UI  fill:#48b,stroke:#333,color:#fff

Flags

Flag Value Behaviour
OVE_EG_WAIT_ALL 0x01 Block until all bits in the mask are set simultaneously
OVE_EG_CLEAR_ON_EXIT 0x02 Atomically clear the waited bits when unblocked

Combining both flags (OVE_EG_WAIT_ALL | OVE_EG_CLEAR_ON_EXIT) is the recommended pattern for one-shot synchronisation barriers: the thread wakes exactly once and the bits are cleared automatically.

API

Function Signature Description
ove_eventgroup_init int (ove_eventgroup_t *eg, ove_eventgroup_storage_t *storage) Initialise from caller-supplied storage.
ove_eventgroup_deinit void (ove_eventgroup_t eg) Release the EventGroup.
ove_eventgroup_create int (ove_eventgroup_t *eg) Heap-allocate. Requires OVE_HEAP_EVENTGROUP.
ove_eventgroup_destroy void (ove_eventgroup_t eg) Free a heap-allocated EventGroup.
ove_eventgroup_set_bits ove_eventbits_t (ove_eventgroup_t eg, ove_eventbits_t bits) Set one or more bits (bitwise OR). Returns the new bit value after the operation. Unblocks matching waiters.
ove_eventgroup_clear_bits ove_eventbits_t (ove_eventgroup_t eg, ove_eventbits_t bits) Clear one or more bits. Returns the previous bit value.
ove_eventgroup_get_bits ove_eventbits_t (ove_eventgroup_t eg) Read current bit mask without blocking.
ove_eventgroup_wait_bits int (ove_eventgroup_t eg, ove_eventbits_t bits, uint32_t flags, uint64_t timeout_ns, ove_eventbits_t *result) Block until bit pattern satisfied. Writes the bits value at wake-time to *result if non-NULL.
ove_eventgroup_set_bits_from_isr ove_eventbits_t (ove_eventgroup_t eg, ove_eventbits_t bits) ISR-safe set.

Static-storage macro: OVE_EVENTGROUP_DEFINE_STATIC(name) (in ove/storage.h).

Example: Coordinating Load and Bypass State

#include <ove/ove.h>

#define BIT_IR_LOADED     (1u << 0)
#define BIT_BYPASS_ACTIVE (1u << 1)
#define BIT_CLIP_DETECTED (1u << 2)

static ove_eventgroup_t g_state_eg;

void app_init(void)
{
    ove_eventgroup_create(&g_state_eg);
}

/* Load task — signal that a new IR is ready */
hiroic_load_ir_blocking(slot);
ove_eventgroup_set_bits(g_state_eg, BIT_IR_LOADED);

/* DSP task — wait for first IR before processing audio */
ove_eventgroup_wait_bits(g_state_eg, BIT_IR_LOADED,
                         OVE_EG_WAIT_ALL, OVE_WAIT_FOREVER, NULL);

/* Bypass button ISR — toggle bypass bit */
bool bypass = !!(ove_eventgroup_get_bits(g_state_eg) & BIT_BYPASS_ACTIVE);
if (bypass) {
    ove_eventgroup_clear_bits(g_state_eg, BIT_BYPASS_ACTIVE);
} else {
    ove_eventgroup_set_bits_from_isr(g_state_eg, BIT_BYPASS_ACTIVE);
}

Stream

A Stream is a ring buffer that transfers an unframed byte sequence from a producer to a consumer. A configurable trigger lets the consumer sleep until a minimum number of bytes are available, reducing task wake-up overhead for bursty producers.

graph LR
    subgraph Producer
        SER["Serial / DMA ISR"]
    end

    subgraph RingBuf["Stream  (capacity C bytes)"]
        direction LR
        WP(["write\npointer"])
        BYTES["… bytes …"]
        RP(["read\npointer"])
        WP --> BYTES --> RP
    end

    subgraph Consumer
        THRESH{{"available ≥\ntrigger?"}}
        TASK["Consumer Task"]
    end

    SER  -->|"ove_stream_send_from_isr()\nbyte-by-byte or burst"| RingBuf
    RingBuf -->|"ove_stream_bytes_available()"| THRESH
    THRESH -->|"yes, unblock"| TASK
    THRESH -->|"no, sleep"| THRESH

    style SER    fill:#a54,stroke:#333,color:#fff
    style THRESH fill:#888,stroke:#333,color:#fff
    style TASK   fill:#48b,stroke:#333,color:#fff

API

Function Signature Description
ove_stream_init int (ove_stream_t *stream, ove_stream_storage_t *storage, void *buffer, size_t size, size_t trigger) Initialise from caller-supplied storage and byte buffer.
ove_stream_deinit void (ove_stream_t stream) Release the stream.
ove_stream_create int (ove_stream_t *stream, size_t size, size_t trigger) Heap-allocate. Requires OVE_HEAP_STREAM.
ove_stream_destroy void (ove_stream_t stream) Free a heap-allocated stream.
ove_stream_send int (ove_stream_t stream, const void *data, size_t len, uint64_t timeout_ns, size_t *bytes_sent) Send bytes; blocks up to timeout_ns if there is no space. Actual bytes written written to *bytes_sent (may be NULL).
ove_stream_receive int (ove_stream_t stream, void *buf, size_t len, uint64_t timeout_ns, size_t *bytes_received) Receive bytes; blocks until trigger bytes are available.
ove_stream_send_from_isr int (ove_stream_t stream, const void *data, size_t len, size_t *bytes_sent) ISR-safe non-blocking send.
ove_stream_receive_from_isr int (ove_stream_t stream, void *buf, size_t len, size_t *bytes_received) ISR-safe non-blocking receive.
ove_stream_reset int (ove_stream_t stream) Discard all buffered bytes.
ove_stream_bytes_available size_t (ove_stream_t stream) Bytes currently readable.

Static-storage macro: OVE_STREAM_DEFINE_STATIC(name, buf_sz, trigger) (in ove/storage.h).

Example: Serial Data Buffering

#include <ove/ove.h>

#define STREAM_CAP    512   /* ring buffer capacity in bytes  */
#define STREAM_TRIG    64   /* wake consumer once ≥64 bytes   */

static ove_stream_t g_uart_stream;

void app_init(void)
{
    ove_stream_create(&g_uart_stream, STREAM_CAP, STREAM_TRIG);
}

/* UART RX ISR — one byte at a time. oveRTOS internally requests a
 * context switch when a higher-priority waiter is unblocked. */
void USART1_IRQHandler(void)
{
    uint8_t byte = USART1->RDR;
    ove_stream_send_from_isr(g_uart_stream, &byte, 1, NULL);
}

/* Consumer task — process frames in bulk */
static void uart_task(void *arg)
{
    uint8_t buf[128];
    size_t got;
    for (;;) {
        if (ove_stream_receive(g_uart_stream, buf, sizeof(buf),
                               OVE_WAIT_FOREVER, &got) == OVE_OK && got > 0) {
            process_frame(buf, got);
        }
    }
}

Kconfig Options

Option Default Description
CONFIG_OVE_QUEUE n Enable Queue primitive.
CONFIG_OVE_EVENTGROUP n Enable EventGroup primitive.
CONFIG_OVE_STREAM n Enable Stream primitive (adds ring-buffer memory overhead).

When the corresponding CONFIG_OVE_* is unset, every function in that subsystem becomes a static inline stub returning OVE_ERR_NOT_SUPPORTED.

Headers

Header Contents
ove/queue.h Queue handle type, init/deinit, create/destroy, send/receive (incl. ISR variants).
ove/eventgroup.h EventGroup handle, ove_eventbits_t, bit-manipulation API, wait flags.
ove/stream.h Stream handle, init/deinit, create/destroy, send/receive, reset, bytes_available.
ove/storage.h OVE_QUEUE_DEFINE_STATIC, OVE_EVENTGROUP_DEFINE_STATIC, OVE_STREAM_DEFINE_STATIC.