A command line interpreter and script engine for embedded systems.

Version License CI Coverage PlatformIO Arduino ESP Component

xelp is an interactive command line interpreter, script engine, and single-key menu system for embedded C projects. It compiles to under 5 KB with all features enabled and runs on targets from 8-bit microcontrollers to 64-bit hosts with code sizes from under 1 KB to ~5 KB. Register your own C functions as commands and call them from the prompt or from scripts stored in ROM. No OS, no dynamic memory, no standard library dependencies.

Features

CLI in action

Key functions [?] show help [l] toggle LED CLI functions help show help echo echo args back led led <0|1> divmod divmod <a> <b> R1=a/b R2=a%b pr print all registers xelp> echo "hello world" hello world xelp> led 1 LED ON xelp> divmod 17 5 17 / 5 = 3 remainder 2 xelp> pr R0=0 R1=3 R2=2 R3=0 xelp> _

Every command above is a plain C function registered with a one-line table entry:

XELPCLIFuncMapEntry commands[] = {
    { &cmd_echo,   "echo",   "echo args back"              },
    { &cmd_led,    "led",    "led <0|1>"                   },
    { &cmd_divmod, "divmod", "divmod <a> <b> R1=a/b R2=a%b" },
    XELP_FUNC_ENTRY_LAST
};

Modes

ModeDescriptionCompile flag
CLICommand line with prompt, backspace, command dispatch. Type commands, press ENTER.XELP_ENABLE_CLI
KEYSingle key press → immediate action. For menus and quick toggles, no ENTER needed.XELP_ENABLE_KEY
THRPass-through to another peripheral (modem, debug port). All keys forwarded.XELP_ENABLE_THR

Quick start

Add three files to your project: xelp.c, xelp.h, xelpcfg.h. Then:

#include "xelp.h"

void uart_putc(char c) { UART_TX = c; }

XELPRESULT cmd_hello(XELP *ths, const char *args, int len) {
    XelpOut(ths, "Hello!\n", 0);
    return XELP_S_OK;
}

XELPCLIFuncMapEntry commands[] = {
    { &cmd_hello, "hello", "say hello" },
    XELP_FUNC_ENTRY_LAST
};

XELP cli;

void main(void) {
    XelpInit(&cli, "My Device v1.0");
    XELP_SET_FN_OUT(cli, &uart_putc);
    XELP_SET_FN_CLI(cli, commands);

    for (;;) {
        if (uart_rx_ready())
            XelpParseKey(&cli, uart_getc());
    }
}

Compiled sizes

Compiled .text section sizes with -Os. Three configurations: KEY (single-key dispatch only), CLI (typical interactive use), FULL (all features). Even the largest full build is under 7 KB.

CPUWidthCompilerKEY (bytes)CLI (bytes)FULL (bytes)
AVR (ATtiny85)8avr-gcc104642704328
AVR (ATmega328P)8avr-gcc105443704428
Z808SDCC212172877395
6800 (HC08)8SDCC247186168718
MSP43016msp430-gcc77034863532
68HC1116m68hc11-gcc236968956966
Xtensa LX7 (ESP32-S3)32xtensa-esp-elf-gcc57626002632
ARM Thumb32arm-none-eabi-gcc58025982642
RISC-V (rv32)32riscv64-unknown-elf-gcc72231003138
Xtensa LX106 (ESP8266)32xtensa-lx106-elf-gcc72329472979
m68k32m68k-linux-gnu-gcc72833363384
ARM3232arm-none-eabi-gcc98039343994
x86-3232GCC108149194969
MIPS3232mipsel-linux-gnu-gcc129652245272
PowerPC32powerpc-linux-gnu-gcc150460666130
RISC-V (rv64)64riscv64-linux-gnu-gcc75635543588
x86-6464Clang104352695311
x86-6464GCC106351385187
AArch64 (ARM64)64aarch64-linux-gnu-gcc132455745630
MIPS6464mips64el-linux-gnuabi64-gcc136058645928

x86-64 GCC row is measured directly; others from cross-compilation via tools/Dockerfile.crossbuild.

Building and testing

make validate     # tests + build all examples (the everyday check)
make tests        # unit tests + coverage only
make examples     # build all examples (no interactive launch)
make example      # build and run the posix ncurses demo (interactive)
make coverage     # tests + coverage summary
make fuzz         # fuzz testing with libFuzzer (requires clang)
make clean-all    # remove all build artifacts

47 test units, 598 test cases, 100% line coverage of xelp.c.

Documentation

License

BSD 2-Clause — see LICENSE.txt. © 2005–2026 M. A. Chatterjee