An interactive command-line interface for STM32 that processes commands over UART. Built to understand embedded parsers and driver architecture.
After building Serial-JSON-Bridge, I needed a way to interact with embedded systems during development. Instead of hardcoding test commands or reflashing firmware for every debug change, I built a proper CLI that lets me configure, test, and debug hardware interactively through a serial terminal.
This is the foundation for building more complex embedded tools—like remote device management, diagnostic interfaces, or even the command interface for the HTTP-Ethernet-Assistant project.
The Goal: Build a reusable CLI framework that works across projects without modifying core code.
Type commands in a serial terminal → STM32 processes them → Executes actions → Responds back
Example Commands:
help
set led on
get status
Can also accept JSON format for structured commands.
graph LR
A[Terminal] -->|UART RX| B[STM32<br/>Interrupt]
B --> C[CLI Parser<br/>Command Split]
C --> D[Command<br/>Handler]
D -->|UART TX| E[Response<br/>Terminal]
style A fill:#e1f5e1
style C fill:#e1e5ff
style D fill:#ffe1f0
style E fill:#e1f5e1
Flow Details:
Clean 3-Layer Design:
✅ Interrupt-driven UART (non-blocking)
✅ Command registration system
✅ Built-in commands: help, set, get
✅ JSON parsing support with JSMN
✅ Zero dynamic memory allocation
✅ Error detection & recovery
Hardware: STM32G0 + USB-UART adapter
# Clone repo
git clone https://github.com/BlackWiz/STM32-Serial-Shell.git
# Build and flash to STM32
# Connect serial terminal
screen /dev/ttyUSB0 9600
Try it:
> help
> set test 123
> get test
// Define handler
base_type my_cmd_handler(char *out, size_t len, const char *cmd) {
sprintf(out, "Custom response\r\n");
return CLI_FALSE;
}
// Define command
const cli_command_definition_t my_cmd = {
"mycmd",
"My custom command",
my_cmd_handler,
0 // parameter count
};
// Register in main
cli_register_command(&my_cmd);
Edge Case Handling: Users type incomplete commands, send garbage data, or mash enter repeatedly. The parser needed to handle all of this without crashing or corrupting state. Spent significant time building robust input validation and buffer boundary checking.
Command Registration Design: Building an extensible system where commands can be added without modifying core CLI code. Took several iterations to get the function pointer design right—balancing flexibility with memory constraints on STM32G0.
Buffer Management: Balancing buffer sizes—too small and you lose data during rapid typing, too large and you waste precious RAM. Ended up profiling actual user input patterns to size buffers appropriately.
Concurrent TX/RX: UART transmit and receive happening simultaneously via interrupts. Had to ensure the CLI could respond to commands while still accepting new input without data corruption.
Memory Footprint:
Tested on real hardware with edge cases:
UART State Machine:
Memory Safety:
Command Parser:
Extensibility vs Simplicity: The command registration system uses function pointers and structs—more complex than a giant switch statement, but infinitely more maintainable when you have 10+ commands.
User Input is Chaos: Never assume users will type correctly. Built the parser to gracefully handle every edge case: empty input, too-long commands, special characters, rapid typing, partial commands.
Building Reusable Frameworks: This CLI code now works across multiple projects (Serial-JSON-Bridge, HTTP-Ethernet-Assistant) without modification—just register different commands. That’s the power of good abstraction.
Integration with Serial-JSON-Bridge: This project extends the UART driver from Serial-JSON-Bridge, demonstrating how to build layered embedded systems—driver at the bottom, application framework on top.
This project builds on concepts from:
| **STM32G0 | UART (9600 baud) | Bare-metal C | No simulation lies** |
Built by Sri Hari Shankar Sharma. Foundation for interactive embedded systems development.