Migrating from Zephyr
If your existing code calls Zephyr's k_* APIs directly (and you want to keep that code portable to FreeRTOS / NuttX), this page is the translation table.
The tables below are written against the C surface of oveRTOS to mirror Zephyr's C-first idiom. The same operations are available with idiomatic ergonomics through the C++, Rust, and Zig bindings, which is the recommended path for new code — the binding choice and the kernel choice are independent.
Three notes up front:
- You can run oveRTOS on the Zephyr backend —
CONFIG_OVE_RTOS_ZEPHYR=ykeeps the Zephyr kernel underneath but presents the oveRTOS API on top. So the binary still runs on the Zephyr scheduler you know. - Timeouts are nanoseconds, not
k_timeout_t. Use theOVE_MS(n)/OVE_SEC(n)helpers fromove/types.h, orOVE_WAIT_FOREVERfor an unbounded wait. Plain milliseconduint32_ts appear only on duration-style APIs (ove_thread_sleep_ms,ove_timer_createperiod). - Priorities are named, not numeric. Zephyr uses negative priorities for cooperative tasks; oveRTOS abstracts this with named priorities (
OVE_PRIO_IDLE,OVE_PRIO_LOW,OVE_PRIO_NORMAL,OVE_PRIO_HIGH,OVE_PRIO_CRITICAL). The mapping happens inside the backend.
Threads
| Zephyr | oveRTOS |
|---|---|
K_THREAD_DEFINE(id, stack, fn, p1,p2,p3, prio, opts, delay) |
OVE_THREAD_DEFINE_STATIC(id, stack, fn, arg, prio, "id") (one arg only — bundle as struct if you need more) |
k_thread_create(&th, stack, sz, fn, p1, p2, p3, prio, opts, delay) |
ove_thread_create(&h, name, fn, arg, prio, sz) |
k_thread_abort(h) |
ove_thread_destroy(h) |
k_sleep(K_MSEC(ms)) |
ove_thread_sleep_ms(ms) |
k_yield() |
ove_thread_yield() |
k_current_get() |
ove_thread_get_self() |
k_thread_priority_set(h, p) |
ove_thread_set_priority(h, p) |
There is no portable getter for priority — keep your own copy if you need to read it back.
Mutexes
| Zephyr | oveRTOS |
|---|---|
K_MUTEX_DEFINE(m) |
OVE_MUTEX_DEFINE_STATIC(m) |
k_mutex_init(&m) |
ove_mutex_create(&m) (heap) or implicit via the macro |
k_mutex_lock(&m, K_FOREVER) |
ove_mutex_lock(m, OVE_WAIT_FOREVER) |
k_mutex_lock(&m, K_MSEC(ms)) |
ove_mutex_lock(m, OVE_MS(ms)) |
k_mutex_unlock(&m) |
ove_mutex_unlock(m) |
Zephyr's k_mutex is recursive by default; the plain ove_mutex_* is not. Use ove_recursive_mutex_* if you rely on recursion.
Semaphores
| Zephyr | oveRTOS |
|---|---|
K_SEM_DEFINE(s, init, max) |
OVE_SEM_DEFINE_STATIC(s, init, max) |
k_sem_init(&s, init, max) |
ove_sem_create(&s, init, max) |
k_sem_take(&s, K_MSEC(ms)) |
ove_sem_take(s, OVE_MS(ms)) |
k_sem_take(&s, K_FOREVER) |
ove_sem_take(s, OVE_WAIT_FOREVER) |
k_sem_give(&s) |
ove_sem_give(s) |
ove_sem_give has no ISR-safe variant — use ove_event_signal_from_isr (single-bit) or ove_eventgroup_set_bits_from_isr (bitmask) for ISR→thread signalling. There is no portable k_sem_count_get analogue.
Message queues
Zephyr has two queue types — k_msgq for fixed-size and k_queue / k_fifo for linked-list. oveRTOS exposes only the fixed-size variant (the linked-list one is less portable across the FreeRTOS / NuttX backends).
| Zephyr | oveRTOS |
|---|---|
K_MSGQ_DEFINE(q, item_sz, depth, align) |
OVE_QUEUE_DEFINE_STATIC(q, item_sz, depth) |
k_msgq_put(&q, &item, K_NO_WAIT) |
ove_queue_send(q, &item, 0) |
k_msgq_put(&q, &item, K_MSEC(ms)) |
ove_queue_send(q, &item, OVE_MS(ms)) |
k_msgq_get(&q, &item, K_FOREVER) |
ove_queue_receive(q, &item, OVE_WAIT_FOREVER) |
For k_fifo / k_lifo patterns, port to a queue of pointer-sized items.
Timers
ove_timer_create() takes the timer object directly — no name, no expiry/stop callback pair (only an expiry callback). The "one-shot" toggle is a plain int (non-zero → one-shot, zero → periodic). Period is in milliseconds.
| Zephyr | oveRTOS |
|---|---|
K_TIMER_DEFINE(t, expiry, stop) |
OVE_TIMER_DEFINE_STATIC(t, expiry, user_data, period_ms, 0 /* periodic */) |
k_timer_init(&t, expiry, stop) |
ove_timer_create(&t, expiry, user_data, period_ms, 0) |
k_timer_start(&t, K_MSEC(d), K_MSEC(p)) |
ove_timer_start(t) — start delay is not separately exposed; the timer fires after the first period elapses |
k_timer_stop(&t) |
ove_timer_stop(t) |
The expiry_fn(struct k_timer *) signature becomes void cb(ove_timer_t t, void *user_data).
Events / event bits
Zephyr's k_event maps onto ove_eventgroup. The naming uses the _bits suffix and timeouts are nanoseconds:
| Zephyr | oveRTOS |
|---|---|
K_EVENT_DEFINE(e) |
OVE_EVENTGROUP_DEFINE_STATIC(e) |
k_event_post(&e, mask) |
ove_eventgroup_set_bits(e, mask) |
k_event_clear(&e, mask) |
ove_eventgroup_clear_bits(e, mask) |
k_event_wait(&e, mask, reset, T) |
ove_eventgroup_wait_bits(e, mask, flags, OVE_MS(ms), &got) |
k_event_test(&e, mask) |
(ove_eventgroup_get_bits(e) & mask) |
flags is OVE_EVENT_WAIT_ALL or OVE_EVENT_WAIT_ANY, optionally | OVE_EVENT_CLEAR_ON_EXIT. There is no atomic "overwrite" variant; use _clear_bits followed by _set_bits if you need set-with-reset semantics.
Workqueues
Each work item is submitted to a specific workqueue; oveRTOS does not expose a global "system workqueue". Create your own with ove_workqueue_create (heap) or OVE_WORKQUEUE_DEFINE_STATIC.
| Zephyr | oveRTOS |
|---|---|
K_WORK_DEFINE(w, handler) |
OVE_WORK_DEFINE_STATIC(w, handler) |
k_work_init(&w, handler) |
ove_work_init(&w, handler) (heap) or ove_work_init_static(&w, &storage, handler) |
k_work_submit_to_queue(&q, &w) |
ove_work_submit(wq, w) |
k_work_submit(&w) (system queue) |
not exposed — submit to a workqueue you've created |
Delayable work (k_work_delayable) is not a separate type — use ove_work_submit_delayed(wq, w, delay_ms).
Stack-based memory (slabs, pools)
Zephyr's k_mem_slab / k_heap / k_mem_pool have no portable equivalent. For your port:
- Fixed-size object pools → static array + a free-list, or a queue of pointers.
k_heap_alloc→ use heap mode and rely on_create()constructors; in zero-heap mode, redesign to use caller-owned storage.
This is the part of a Zephyr port that takes the most thought.
Devices and drivers
Zephyr's DEVICE_DT_GET(...) / device-tree node IDs do not carry over. Each oveRTOS module has its own typed handle and constructor:
| Zephyr | oveRTOS |
|---|---|
const struct device *uart = DEVICE_DT_GET(DT_NODELABEL(usart1)); |
ove_uart_t uart; ove_uart_create(&uart, &cfg); (heap) or ove_uart_init(&uart, &storage, …) |
uart_poll_in(uart, &c) |
ove_uart_read(uart, &c, 1, 0, NULL) |
gpio_pin_configure_dt(&spec, GPIO_INPUT) |
ove_gpio_configure(port, pin, &cfg) |
i2c_write_dt(&spec, buf, len) |
ove_i2c_write(i2c, addr, buf, len, OVE_WAIT_FOREVER) |
The board's board.yaml (oveRTOS-side) plays a role similar to the device tree: it declares which peripherals exist and how they're wired, which the BSP turns into the right OVE_*_DEV_* constants.
Networking
Zephyr ships its own networking stack; oveRTOS uses lwIP under FreeRTOS, NuttX's networking under NuttX, and Zephyr's stack under Zephyr — but the API is the same across all three:
| Zephyr | oveRTOS |
|---|---|
zsock_socket(AF_INET, SOCK_STREAM, 0) |
ove_socket_create(&sock, OVE_AF_INET, OVE_SOCK_STREAM) (heap) or ove_socket_open(&sock, &storage, …) |
zsock_connect(s, &addr, len) |
ove_socket_connect(sock, &addr, OVE_WAIT_FOREVER) |
zsock_send(s, buf, len, 0) |
ove_socket_send(sock, buf, len, &sent) |
zsock_recv(s, buf, len, 0) |
ove_socket_recv(sock, buf, len, &received, OVE_WAIT_FOREVER) |
tls_credential_add(...) |
populate ove_tls_config_t and pass to ove_tls_handshake(tls, sock, &cfg) |
Logging
| Zephyr | oveRTOS |
|---|---|
LOG_MODULE_REGISTER(name, LOG_LEVEL_INF) |
OVE_LOG_MODULE_REGISTER(name) (level from Kconfig) |
LOG_ERR("...", ...) |
OVE_LOG_ERR("...", ...) |
LOG_WRN("...", ...) |
OVE_LOG_WRN("...", ...) |
LOG_INF("...", ...) |
OVE_LOG_INF("...", ...) |
LOG_DBG("...", ...) |
OVE_LOG_DBG("...", ...) |
Hex-dump and instance-level logging are not currently part of the portable surface.
See also
- Architecture Overview
- API Reference
- Backends → Zephyr — what oveRTOS does and doesn't expose from Zephyr internals