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 on 32-bit targets and runs on 8-bit microcontrollers to 64-bit hosts with code sizes from under 1 KB to ~12 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
- Pure C (C89 compliant), optional C++
extern "C"support - Compiled sizes: under 1 KB to ~5 KB on 32-bit targets (larger on 8/16-bit)
- No
malloc, no globals — all state in an instance struct - Bring your own functions — register any C function as a CLI command with a one-line table entry, callable interactively or from scripts
- Multiple independent instances on different serial ports, BLE, USB CDC
- Char-at-a-time parsing — feed from UART, ISR, or any byte stream
- Command history — UP/DOWN arrow recall of previous commands
(optional,
XELP_ENABLE_HISTORY) - Built-in tokenizer with quoted strings, escape sequences,
#comments - argc/argv parsing —
XELP_PARSE_ARGVmacro for standard C-style argument handling with null-terminated tokens (optional,XELP_ENABLE_ARGV) - Scriptable — run multi-command const strings from ROM or RAM
- Zero dependencies (no stdio.h, string.h, etc.)
CLI in action
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
| Mode | Description | Compile flag |
|---|---|---|
| CLI | Command line with prompt, backspace, command dispatch. Type commands, press ENTER. | XELP_ENABLE_CLI |
| KEY | Single key press → immediate action. For menus and quick toggles, no ENTER needed. | XELP_ENABLE_KEY |
| THR | Pass-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 12 KB.
| CPU | Width | Compiler | KEY (bytes) | CLI (bytes) | FULL (bytes) |
|---|---|---|---|---|---|
| AVR (ATtiny85) | 8 | avr-gcc | 990 | 4302 | 5697 |
| AVR (ATmega328P) | 8 | avr-gcc | 998 | 4402 | 5811 |
| Z80 | 8 | SDCC | 1969 | 7378 | 9354 |
| 6800 (HC08) | 8 | SDCC | 2095 | 8648 | 11170 |
| MSP430 | 16 | msp430-gcc | 782 | 3532 | 4726 |
| 68HC11 | 16 | m68hc11-gcc | 2169 | 6918 | 9902 |
| ARM Thumb | 32 | arm-none-eabi-gcc | 600 | 2630 | 3463 |
| Xtensa LX7 (ESP32-S3) | 32 | xtensa-esp-elf-gcc | 620 | 2652 | 3553 |
| m68k | 32 | m68k-linux-gnu-gcc | 746 | 3372 | 4543 |
| RISC-V (rv32) | 32 | riscv64-unknown-elf-gcc | 746 | 3140 | 4182 |
| Xtensa LX106 (ESP8266) | 32 | xtensa-lx106-elf-gcc | 747 | 3003 | 3968 |
| ARM32 | 32 | arm-none-eabi-gcc | 1008 | 4006 | 5327 |
| x86-32 | 32 | GCC | 1099 | 4985 | 6349 |
| MIPS32 | 32 | mipsel-linux-gnu-gcc | 1312 | 5288 | 6832 |
| PowerPC | 32 | powerpc-linux-gnu-gcc | 1536 | 6146 | 7799 |
| RISC-V (rv64) | 64 | riscv64-linux-gnu-gcc | 780 | 3594 | 4684 |
| x86-64 | 64 | Clang | 1069 | 5355 | 7212 |
| x86-64 | 64 | GCC | 1084 | 5183 | 6544 |
| AArch64 (ARM64) | 64 | aarch64-linux-gnu-gcc | 1336 | 5626 | 6987 |
| MIPS64 | 64 | mips64el-linux-gnuabi64-gcc | 1376 | 5928 | 7744 |
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
50 test units, 693 test cases, 100% line coverage of xelp.c.
Documentation
- Tutorial — step-by-step introduction
- Examples — annotated code for various platforms
- API Reference — all public functions and macros
- Configuration Guide — compile-time options in xelpcfg.h
- Porting Guide — how to bring up xelp on a new platform
- Release History
- Contributing
- AGENTS.md — AI coding agent reference (function signatures, parser internals, setup patterns)
- llms.txt — project overview for LLMs (llmstxt.org format)
License
BSD 2-Clause — see LICENSE.txt. © 2005–2026 M. A. Chatterjee