Skip to content

Basic Example — C

Source: apps/c/example/src/app.c | WASM Demo{:target="_blank"}

The C example demonstrates the oveRTOS unified C API, using a producer-consumer pattern with optional LVGL display output. The same source code compiles unchanged in both heap and zero-heap modes across all supported backends — no #ifdef CONFIG_OVE_ZERO_HEAP is needed.

Unified C API

The _create() / _destroy() API works identically in both heap and zero-heap modes:

ove_queue_create(&counter_queue, sizeof(uint32_t), 8);
ove_mutex_create(&value_mutex);
ove_timer_create(&ui_timer, ui_timer_cb, NULL, 200, 0);

struct ove_thread_desc desc = {
    .name = "producer",
    .entry = producer_thread,
    .arg = NULL,
    .priority = OVE_PRIO_NORMAL,
};
ove_thread_create(&thread_handle, 4096, &desc);

In heap mode, _create() allocates from the RTOS heap. In zero-heap mode, each _create() call site becomes a GCC statement-expression macro that auto-generates static storage.

Alternative allocation strategies

For file-scope auto-initialized declarations, the OVE_*_DEFINE_STATIC() macros declare a handle and initialize it before main():

OVE_QUEUE_DEFINE_STATIC(counter_queue, sizeof(uint32_t), 8);
OVE_MUTEX_DEFINE_STATIC(value_mutex);
OVE_THREAD_DEFINE_STATIC(prod_thread, 4096, producer_thread, NULL,
                          OVE_PRIO_NORMAL, "producer");

For explicit storage control, use _init() / _deinit() with caller-supplied storage buffers.

Producer thread

static void producer_thread(void *arg)
{
    uint32_t count = 0;
    OVE_LOG_INF("Producer started");

    while (1) {
        ++count;
        int ret = ove_queue_send(counter_queue, &count, 1000);
        if (ret != OVE_OK) {
            OVE_LOG_WRN("Producer: queue full, dropped %u", count);
        }
        ove_thread_sleep_ms(500);
    }
}

ove_queue_send blocks for up to 1000 ms if the queue (8 slots) is full. A warning is logged and the value is dropped when the timeout expires.

Consumer thread

static void consumer_thread(void *arg)
{
    uint32_t val = 0;

    while (1) {
        int ret = ove_queue_receive(counter_queue, &val, OVE_WAIT_FOREVER);
        if (ret == OVE_OK) {
            ove_mutex_lock(value_mutex, OVE_WAIT_FOREVER);
            last_value = val;
            ove_mutex_unlock(value_mutex);

            if (val % 5 == 0) {
                OVE_LOG_INF("Consumer: count = %u", val);
            }
        }
    }
}

The consumer blocks indefinitely on ove_queue_receive. The mutex protects last_value from the UI timer callback.

LVGL UI timer callback

static void ui_timer_cb(ove_timer_t timer, void *user_data)
{
    ove_mutex_lock(value_mutex, OVE_WAIT_FOREVER);
    uint32_t val = last_value;
    ove_mutex_unlock(value_mutex);

    ove_lvgl_lock();
    snprintf(buf, sizeof(buf), "Count: %u", (unsigned int)val);
    lv_label_set_text(count_label, buf);
    lv_bar_set_value(bar, (int32_t)(val % 101), LV_ANIM_ON);
    ove_lvgl_unlock();
}

ove_lvgl_lock() / ove_lvgl_unlock() serialise LVGL widget access with the graphics thread.

Entry point

void ove_main(void)
{
    ove_queue_create(&counter_queue, sizeof(uint32_t), 8);
    ove_mutex_create(&value_mutex);

    /* Create threads */
    struct ove_thread_desc desc = {
        .name = "producer", .entry = producer_thread,
        .priority = OVE_PRIO_NORMAL,
    };
    ove_thread_create(&thread_handle, 4096, &desc);

    /* ... consumer, graphics threads similarly ... */

    ove_lvgl_init();
    ove_lvgl_lock();
    create_ui();
    ove_lvgl_unlock();

    ove_run();  /* starts the scheduler */
}

Key APIs demonstrated

API Purpose
ove_queue_create / ove_queue_destroy Create/destroy a fixed-size FIFO queue
ove_queue_send / ove_queue_receive Inter-thread data transfer
ove_mutex_create / ove_mutex_destroy Create/destroy a mutex
ove_mutex_lock / ove_mutex_unlock Shared state protection
ove_timer_create / ove_timer_start Create and arm a periodic timer
ove_thread_create / ove_thread_sleep_ms Thread lifecycle
OVE_LOG_INF / OVE_LOG_WRN Compile-time filtered logging
ove_lvgl_lock / ove_lvgl_unlock Safe multi-threaded LVGL access
ove_run Start the RTOS scheduler

How to build

make host.posix.example_c    # or wasm.posix.example_c
make configure && make download && make && make run