
Ultra-lightweight RTOS for IoT with preemptive scheduling, TLS/DTLS, MQTT, CoAP, POSIX compatibility, and MPU-based memory protection. Kernel under 10 KB.
An ultra-lightweight real-time operating system for resource-constrained IoT and embedded devices.
Kernel footprint under 10 KB, 2 KB minimum RAM, preemptive priority-based scheduling.
| Category | Details |
|---|---|
| Kernel | Preemptive priority-based scheduling (256 levels), round-robin within same priority, O(1) priority lookup via bitmap, priority inheritance |
| Synchronization | Mutex (with priority inheritance), semaphore, condition variable, event groups, message queues |
| Software Timers | One-shot and auto-reload, millisecond precision, period change at runtime |
| Memory | First-fit allocator with immediate coalescing (8 KB heap, 8-byte aligned), stack overflow detection, per-task high-water mark |
| Shell | VT100 interactive shell — 23 built-in commands, command history (↑↓), tab completion, full line editor |
| POSIX Compatibility | pthreads (create/join/detach/exit, mutex, cond var) · BSD socket API (socket/bind/listen/accept/connect/send/recv, inet_pton/ntop, htons/htonl) |
| File System | Journaling block-device FS (WAL, crash recovery), COW block sharing, atomic snapshots, POSIX-like API |
| Network | Ethernet, IPv4, ICMP, UDP, TCP, HTTP client/server, DNS |
| TLS / DTLS | TLS 1.2/1.3 over TCP, DTLS 1.2 over UDP (mbedTLS backend) |
| MQTT | Full MQTT 3.1.1 — QoS 0/1/2 with in-flight retry table, offline queue, auto-reconnect with exponential back-off |
| CoAP | RFC 7252 compliant client/server, observe pattern |
| OTA | A/B partition firmware updates, CRC32 verification, rollback |
| Watchdog | Hardware and software watchdog, per-task timeout monitoring |
| Power | Idle / Sleep / Deep-sleep modes, tickless idle, CPU frequency scaling |
| Security | MPU-based memory protection, secure boot support |
| HAL | Generic Hardware Abstraction Layer — ARM Cortex-M / RISC-V / AVR; compile-time arch selection, peripheral op-tables |
| Architecture | Examples |
|---|---|
| ARM Cortex-M (M0/M0+/M3/M4/M7) | STM32, nRF52, Raspberry Pi Pico |
| RISC-V (RV32I) | ESP32-C3 |
| AVR (experimental) | ATmega |
# ARM cross-compiler (required)
sudo apt-get install -y gcc-arm-none-eabi binutils-arm-none-eabi
# QEMU ARM emulator (optional — for running without hardware)
sudo apt-get install -y qemu-system
Verify installation:
arm-none-eabi-gcc --version # 10.x or later
qemu-system-arm --version # 6.x or later
# Default example (blink_led) — ARM Cortex-M4
make
# Target a different architecture (auto-selects toolchain and HAL)
make ARCH=cortex-m0 # Cortex-M0/M0+
make ARCH=cortex-m7 # Cortex-M7
make ARCH=riscv32 # RISC-V RV32I (uses riscv32-unknown-elf-gcc)
make ARCH=avr5 # AVR ATmega (uses avr-gcc)
# Specific example
make EXAMPLE=blink_led # LED blink + task scheduler demo
make EXAMPLE=event_groups # Event group AND/OR/NOT/SYNC demo
make EXAMPLE=iot_sensor # Multi-sensor IoT node
make EXAMPLE=shell_demo # Interactive UART shell
make EXAMPLE=mqtt_demo # MQTT publish/subscribe
make EXAMPLE=condition_variable # Producer/consumer
# Convenience aliases
make example-blink
make example-events
make example-shell
make example-mqtt
make example-iot
# Build output
make size # Print ROM/RAM usage
Build artifacts are placed in build/:
| File | Description |
|---|---|
build/tinyos.elf | ELF image with debug symbols |
build/tinyos.bin | Raw binary for flashing |
build/tinyos.map | Linker map (symbol sizes) |
TinyOS runs on the QEMU mps2-an385 target (ARM Cortex-M3, 4 MB flash, 4 MB RAM):
# Run indefinitely (Ctrl-A X to quit)
qemu-system-arm \
-machine mps2-an385 \
-cpu cortex-m3 \
-nographic \
-kernel build/tinyos.elf
# Run for a fixed duration (e.g. 10 seconds)
timeout 10 qemu-system-arm \
-machine mps2-an385 \
-cpu cortex-m3 \
-nographic \
-kernel build/tinyos.elf
# Debug: trace interrupts
qemu-system-arm \
-machine mps2-an385 \
-cpu cortex-m3 \
-nographic \
-d int \
-kernel build/tinyos.elf
Expected output from the interrupt trace: repeated successful exception return lines confirm the scheduler is running, SysTick is ticking, and PendSV context switches are completing cleanly.
# OpenOCD (STM32 example)
openocd -f interface/stlink.cfg -f target/stm32f4x.cfg \
-c "program build/tinyos.bin verify reset exit 0x08000000"
# pyOCD (generic ARM Cortex-M)
pyocd flash --target cortex_m build/tinyos.bin
TLS support is enabled automatically when mbedTLS is present at ~/mbedtls.
To use a different path:
# Clone and build mbedTLS
git clone https://github.com/Mbed-TLS/mbedtls ~/mbedtls
make -C ~/mbedtls
# Build TinyOS with TLS
make MBEDTLS_DIR=~/mbedtls
Minimal task example:
#include "tinyos.h"
void my_task(void *param) {
while (1) {
/* work */
os_task_delay_ms(100);
}
}
int main(void) {
tcb_t task;
os_init();
os_task_create(&task, "my_task", my_task, NULL, PRIORITY_NORMAL);
os_start();
}
os_task_create(tcb, name, entry, param, priority)
os_task_delete(task)
os_task_suspend(task) / os_task_resume(task)
os_task_delay(ticks) / os_task_delay_ms(ms)
os_task_set_priority(task, priority)
os_task_get_stats(task, stats)
os_task_get_stats_by_index(index, stats) /* iterate all tasks by index */
os_task_find_by_name(name) /* returns tcb_t*, NULL if not found */
os_get_system_stats(stats)
os_get_memory_stats(&free, &used, &allocs, &frees)
os_mutex_init(mutex) / os_mutex_lock(mutex, timeout) / os_mutex_unlock(mutex)
os_semaphore_init(sem, count) / os_semaphore_wait(sem, timeout) / os_semaphore_post(sem)
os_cond_init(cond) / os_cond_wait(cond, mutex, timeout)
os_cond_signal(cond) / os_cond_broadcast(cond)
os_event_group_set_bits(eg, bits) / os_event_group_wait_bits(eg, bits, opts, out, timeout)
os_queue_init(q, buf, item_size, max) / os_queue_send(q, item, timeout)
os_queue_receive(q, item, timeout) / os_queue_peek(q, item, timeout)
os_timer_create(timer, name, type, period_ms, callback, param)
os_timer_start(timer) / os_timer_stop(timer) / os_timer_reset(timer)
os_timer_change_period(timer, ms) / os_timer_get_remaining_ms(timer)
/* Register custom commands before calling shell_start() */
shell_register_cmd(name, handler_fn, help_text)
/* Provide UART I/O callbacks and start the shell task */
shell_io_t io = { .getc = uart_getc, .puts = uart_puts };
shell_start(&io)
/* Change the prompt at any time */
shell_set_prompt("mydevice> ")
/* Execute a single line programmatically */
shell_exec(line)
Custom command example:
static int cmd_led(int argc, char *argv[]) {
if (argc < 2) return 1; /* non-zero → prints usage */
bool on = (strcmp(argv[1], "on") == 0);
gpio_write(LED_PIN, on);
return 0;
}
/* In main(), before shell_start(): */
shell_register_cmd("led", cmd_led, "led <on|off> Toggle LED");