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
- Pure C (C89 compliant), optional C++
extern "C"support - Compiled sizes: under 1 KB to ~5 KB depending on features enabled
- 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 - 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 7 KB.
| CPU | Width | Compiler | KEY (bytes) | CLI (bytes) | FULL (bytes) |
|---|---|---|---|---|---|
| AVR (ATtiny85) | 8 | avr-gcc | 1046 | 4270 | 4328 |
| AVR (ATmega328P) | 8 | avr-gcc | 1054 | 4370 | 4428 |
| Z80 | 8 | SDCC | 2121 | 7287 | 7395 |
| 6800 (HC08) | 8 | SDCC | 2471 | 8616 | 8718 |
| MSP430 | 16 | msp430-gcc | 770 | 3486 | 3532 |
| 68HC11 | 16 | m68hc11-gcc | 2369 | 6895 | 6966 |
| Xtensa LX7 (ESP32-S3) | 32 | xtensa-esp-elf-gcc | 576 | 2600 | 2632 |
| ARM Thumb | 32 | arm-none-eabi-gcc | 580 | 2598 | 2642 |
| RISC-V (rv32) | 32 | riscv64-unknown-elf-gcc | 722 | 3100 | 3138 |
| Xtensa LX106 (ESP8266) | 32 | xtensa-lx106-elf-gcc | 723 | 2947 | 2979 |
| m68k | 32 | m68k-linux-gnu-gcc | 728 | 3336 | 3384 |
| ARM32 | 32 | arm-none-eabi-gcc | 980 | 3934 | 3994 |
| x86-32 | 32 | GCC | 1081 | 4919 | 4969 |
| MIPS32 | 32 | mipsel-linux-gnu-gcc | 1296 | 5224 | 5272 |
| PowerPC | 32 | powerpc-linux-gnu-gcc | 1504 | 6066 | 6130 |
| RISC-V (rv64) | 64 | riscv64-linux-gnu-gcc | 756 | 3554 | 3588 |
| x86-64 | 64 | Clang | 1043 | 5269 | 5311 |
| x86-64 | 64 | GCC | 1063 | 5138 | 5187 |
| AArch64 (ARM64) | 64 | aarch64-linux-gnu-gcc | 1324 | 5574 | 5630 |
| MIPS64 | 64 | mips64el-linux-gnuabi64-gcc | 1360 | 5864 | 5928 |
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
- 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
License
BSD 2-Clause — see LICENSE.txt. © 2005–2026 M. A. Chatterjee