Throughout the development process, from hardware prototyping to manufacturing, the need to run test code repeatedly arises. This is crucial for verifying functionality and performing system-level testing.
However, relying on debuggers or restarting the device to trigger events can lead to delays during boot-up and initialization. Moreover, it assumes that manufacturers and non-developers possess the necessary knowledge and tools to navigate these complexities. Thus, it becomes essential for embedded systems to offer a straightforward method for executing commands repeatedly through an accessible interface during both development and manufacturing.
Enter the solution: a command-line interface (CLI). This tool empowers users to exercise various features and functions without requiring expertise in debugging or software intricacies. Testing can be efficiently conducted via the CLI using the most suitable communication interface for the scenario, such as UART, USB, RTT, or Bluetooth.
While custom CLI implementations are certainly an option, a wealth of open-source solutions exists. Our team opted to explore these open-source alternatives first, aiming to identify one that could meet our needs with minimal adjustments. The initial phase of this implementation involves reviewing the available open-source CLI solutions and comparing their features. Once we find a suitable option, we can establish a standard solution to integrate into our template projects for broad adoption.
Why Use a CLI?
A CLI allows developers and manufacturers to exercise various features and functions without the need for debuggers or deep software knowledge. Testing can be performed using the communication interface most convenient to the situation, such as UART, USB, RTT, or Bluetooth.
While it’s possible to write a custom CLI, open-source solutions are available that can often meet your needs with minimal modification. Before embarking on an internal project, it’s worth reviewing available open-source CLI solutions to find one that fits your requirements. Here, we outline the key features to consider when choosing or implementing a CLI for embedded systems.
Requirements
Based on experience with closed-source and internally developed CLIs, here are the key features to prioritize.
Must Haves
| Feature | Description |
|---|---|
| Open Source with Permissive License | The solution should be easily redistributable, ideally under an MIT license. |
| Communication Interface Agnostic | The CLI should abstract input/output, supporting protocols like UART, USB, Bluetooth, etc. |
| Bare Metal Support | The CLI should not rely on any RTOS or other dependencies. |
| Basic Shell Functionality | Includes echoing input, backspace support, and help messages. |
| C or C++ Implementation | Preferably a small, lightweight library in C or C++, with minimal files. |
| Static Memory Allocation | Avoids dynamic allocation, a best practice for embedded systems. |
Nice to Haves
| Feature | Description |
|---|---|
| Autocomplete and History | Allows easier interaction through tab completion and command history recall. |
| Parameter Validation | Ensures correct command arguments, improving usability. |
| Multiple Instances Support | Enables serving multiple CLIs over different interfaces or protocols. |
| Built-in Help | Native support for printing help messages and available commands. |
| Runtime Command Registration | Commands can be registered at runtime, enabling dynamic updates. |
Additional Considerations
Security Considerations
Securing embedded CLIs is crucial, especially in production environments. Best practices include implementing user authentication, encrypting communication interfaces, and restricting access to sensitive commands. For example, you can secure CLI access via password protection or encryption keys, ensuring only authorized users can interact with the system.
Example: A CLI secured with a password protects the system from unauthorized tampering during manufacturing or field use.
Performance Impact
When integrating a CLI into resource-constrained embedded systems. Large command sets and strings, for instance, can increase the footprint and slow down system response times. Reducing string sizes by using a more compact, manual format for commands can help mitigate this. Keeping the CLI lightweight and trimming unnecessary features will also minimize the impact on system resources.
Tip: Slimming down shell commands and minimizing string processing overhead will help keep your system responsive.
Interchangeable CLI Output Formats
A flexible CLI should support both human-readable and machine-readable formats, making it suitable for automated testing and interaction. This feature allows you to toggle between different output formats depending on the situation. For example, you might configure the CLI to output structured data like JSON or XML for machines while retaining plain text for human operators.
USB-CDC Support Example
A practical example would be adding USB-CDC support to your system, enabling two separate USB-CDC classes for different CLI instances—one for human operators and one for automated systems. This setup allows different users or processes to interact with the CLI simultaneously over the same hardware interface.
Hardware-Enabled CLI
In some systems, you might want to control when the CLI is available. For instance, external hardware could be used to determine whether the CLI is enabled at boot. If the hardware (such as a specific jumper) is absent, the CLI module would never load. This method ensures that the CLI isn’t exposed unnecessarily during operation, adding another layer of security.
Binary Protocols as an Alternative
In some cases, the full functionality of a CLI may not be necessary, or the nature of the deployment might not permit its use. A binary protocol could be a viable alternative for applications where you only need basic interaction with the device (e.g., for simple commands or status updates). This reduces the code size and execution overhead while still allowing communication.
Reduced Functionality CLI
Sometimes a more lightweight, reduced functionality CLI is preferable. For example, a CLI that only exposes essential commands and omits advanced features (such as autocomplete and history) might be appropriate for a system with strict resource constraints.
Multiple CLI Instances
Some applications may require multiple CLIs or separate instances of a CLI. For instance, you may need one CLI for handling RX and TX over different protocols or need to create a distinction between userland and kernel interactions. In such cases, a system with support for multiple shells or CLI instances, each managing its own state, would be beneficial.
By considering these additional aspects, you can create a flexible and powerful CLI that suits both your development needs and deployment scenarios, ensuring that testing, manufacturing, and troubleshooting are smooth and efficient.
Conclusion
Based on the analysis we plan to use Funbiscuit’s embedded-cli, the gif included in the repo speaks volumes so we include it here.
The embedded-cli by Funbiscuit ticks every box except for parameter validation but that was only a “nice to have” feature. We even discovered some additional features while integrating this into a template project. There is a hook function pointer that can be set up to be called whenever a command is about to be executed by the CLI library. It lets you print out the command and its arguments. What a great feature for debugging and logging!
Below is a table comparing the size of a project built before and after adding the CLI.
| filename | text | data | bss | dec | hex |
|---|---|---|---|---|---|
| with_cli.elf | 40000 | 664 | 52520 | 93184 | 16c00 |
| without_cli.elf | 36048 | 664 | 50968 | 87680 | 15680 |
Some of that will be the FreeRTOS task that was added to interface with the CLI library. Some optimization could be done to trim buffers and the task’s stack size, but ultimately the increase is worth the benefits – and if the CLI is not needed in production, it can be easily removed.
The Funbiscuit embedded-cli project includes everything we were looking for, and should make a great addition to our projects.DojoFive brings modern tools, techniques, and best practices from the web and mobile development environments, paired with leading-edge innovations in firmware to our customers to help them build successful products and successful clients. We have talented engineers on hand ready to help you with all aspects of your EmbedOps journey. Bring your interesting problems that need solving – we are always happy to help out. You can reach out at any time on LinkedIn or through email!
Sign up to get our content updates!
Unlock the full potential of your embedded projects with our expert insights! Dive into our comprehensive resources to stay ahead in firmware development. Subscribe now to get the latest best practices and guides delivered straight to your inbox.
Sign Up for Updates


