Nano-Shell is a light but powerful shell designed for embedded systems.
-
with or without an operating system;
-
<main loop mode>
or<react mode>
; -
highly configurable;
-
powerful: command line editing, history record, multi-line input, hot key bind, etc...
-
memory friendly: NO malloc and free;
-
light (build with arm-none-eabi-gcc 7.3.1 20180622, -O3):
.text(1) .rodata .bss(2) .data main loop mode,
all configurations on2.5KB 1.03KB 852B 8B main loop mode,
all configurations off(3)616B 600B 180B 0B react mode,
all configurations on2.52KB 1.03KB 852B 8B react mode,
all configurations off(3)608B 600B 180B 0B 1: include built-in
help
command.2: include
input buffer
(default 128Bytes) andhisroty record buffer
(defaut 650Bytes(5*(128+2)))3: except
CONFIG_SHELL_CMD_BUILTIN_HELP
.
nano-shell has internally bound these hotkeys:
HOT KEY | ASCII/ANSI-Escape Code (Xterm, VT100) |
Function |
---|---|---|
Ctrl-A | 1 | Home Move curosr to the start of line. |
Ctrl-E | 5 | End Move curosr to the end of line. |
Ctrl-P | 16 | Up arrow(-->) Move cursor right one char. |
Ctrl-N | 14 | Down arrow(-->) Move cursor right one char. |
Ctrl-B | 2 | Left arrow(<--) Move cursor left one char. |
Ctrl-F | 6 | Right arrow(-->) Move cursor right one char. |
Ctrl-D | 4 | Delete Delete the character under the cursor. |
Ctrl-K | 11 | Erase forward Clears all characters from the cursor position to the end of the line. |
Ctrl-U | 21 | Erase backword Clears all characters from the cursor position to the start of the line.. |
Ctrl-C | 3 | Kill the line. |
Home | Esc[H | Move curosr to the beginning of line. |
End | Esc[F | Move curosr to the end of line. |
Up Arrow | Esc[A | Get the previous history. |
Down Arrow | Esc[B | Get the next history. |
Left Arrow | Esc[D | Left arrow(<--) Move cursor left one char. |
Right Arrow | Esc[C | Right arrow(-->) Move cursor right one char. |
Delete | Esc[3~ | Delete the character under the cursor. |
Commands are added to nano-shell by creating a new command structure.
This is done by first including command/command.h
, then using the NANO_SHELL_ADD_CMD()
macro to fill in a shell_cmd_t
struct.
NANO_SHELL_ADD_CMD(_name, _func, _brief, _help)
_name
: name of the command. Note: THIS IS NOT a string.
_func
: function pointer: (*cmd)(const shell_cmd_t *, int, int, char *const[])
.
_brief
: brief summaries of the command. This is a string.
_help
: detailed help information of the command. This is a string.
Commands with sub-commands can easily be created with a combination of NANO_SHELL_DEFINE_SUBCMDS
,
NANO_SHELL_SUBCMD_ENTRY
and NANO_SHELL_ADD_CMD
. See examples for more details.
int _do_demo(const shell_cmd_t *pcmd, int argc, char *const argv[])
{
for (int i=0; i<argc; i++) {
shell_printf(" [DEMO] ARGV[%d]: %s\r\n", i, argv[i]);
}
return 0;
}
NANO_SHELL_ADD_CMD(demo,
_do_demo,
"a command demo",
" It's detailed help information of demo command\r\n");
Run demo
in terminal:
Run help
and help demo
in terminal:
It is possible to create commands with sub-commands. More nested command can also be created.
/* Create a bunch of commands to be run as a demo */
int _top_command_fallback_fct(const shell_cmd_t* pCmdt, int argc, char* const argv[])
{
if(argc > 1) {
shell_printf(" '%s' is not a subcommand of %s\r\n", argv[1], argv[0]);
}
else {
shell_printf(" Hey, there is subcommands here, type '%s help' for more info\r\n", argv[0]);
}
return 0;
}
int _do_subcommand1(const shell_cmd_t* pCmdt, int argc, char* const argv[]) {
shell_puts(" This is sub-command 1\r\n");
return 0;
}
int _do_subsubcommand1(const shell_cmd_t* pCmdt, int argc, char* const argv[]) {
shell_puts(" This is sub-sub-command 1\r\n");
return 0;
}
int _do_subsubcommand2(const shell_cmd_t* pCmdt, int argc, char* const argv[]) {
shell_puts(" This is sub-sub-command 2\r\n");
return 0;
}
// Sub-Sub commands group
NANO_SHELL_DEFINE_SUBCMDS(subcommand2_group,
NULL,
NANO_SHELL_SUBCMD_ENTRY(subsubcommand1,
_do_subsubcommand1,
"first sub-sub-command",
""),
NANO_SHELL_SUBCMD_ENTRY(subsubcommand2,
_do_subsubcommand2,
"second sub-sub-command",
""));
// Sub commands group
NANO_SHELL_DEFINE_SUBCMDS(top_command_group,
_top_command_fallback_fct,
NANO_SHELL_SUBCMD_ENTRY(subcommand1,
_do_subcommand1,
"first subcommand",
""),
NANO_SHELL_SUBCMD_ENTRY(subcommand2,
NANO_SHELL_SUBCMDS_FCT(subcommand2_group),
"second subcommand with sub-sub commands",
""));
// Command with sub commands
NANO_SHELL_ADD_CMD(top_command,
NANO_SHELL_SUBCMDS_FCT(top_command_group),
"A command with subcommand",
" This command have 2 sub-commands and one sub-sub-command\r\n");
@file: shell_config.h
-
CONFIG_SHELL_INPUT_BUFFSIZE (127U)
- default:
(127U)
- config the command line input buffer size (in byte).
- default:
-
CONFIG_SHELL_LINE_EDITING
- default:
1(enabled)
- set this to
0
will disable command line editing.
- default:
-
CONFIG_SHELL_KEY_SEQ_BIND
- default:
1(enabled)
- set this to
0
will disable ANSI-Escape-Sequence. nano-shell will not be able to detect Home/End/Delete/Arrow keys. Doesn't affect Ctrl-P, Ctrl-N, etc...
- default:
-
CONFIG_SHELL_MULTI_LINE
-
CONFIG_SHELL_HIST_MIN_RECORD
- default:
(5U)
- set this to
0
will disable history record. - nano-shell will take
CONFIG_SHELL_HIST_MIN_RECORD*(2+CONFIG_SHELL_INPUT_BUFFSIZE)
bytes to record At LeastCONFIG_SHELL_HIST_MIN_RECORD
histroys. The max history records depends on the average length of the input.
- default:
-
CONFIG_SHELL_CMD_BRIEF_USAGE
- default:
1(enabled)
- command structure
shell_cmd_t
has a pointer point to "brief usage information of the command", set this to0
will remove it.
- default:
-
CONFIG_SHELL_CMD_LONG_HELP
- default:
1(enabled)
- command structure
shell_cmd_t
has a pointer point to "detailed help information of the command", set this to0
will remove it.
- default:
-
CONFIG_SHELL_CMD_BUILTIN_HELP
- default:
1(enabled)
- nano-shell provides a built-in
help
command, set this to0
will remove the deaulthelp
command.
- default:
-
CONFIG_SHELL_CMD_MAX_ARGC
- default:
(10U)
- config the max number of arguments, must be no less than 1.
- default:
- CONFIG_SHELL_PROMPT
- default:
"Nano-Shell >> "
- config the shell promot that will displayed at the start of line. If you don't need it, set this to
NULL
or""
.
- default:
- CONFIG_SHELL_PRINTF_BUFFER_SIZE
- default:
(128U)
- config the buffer size of
shell_printf()
.
- default:
2. implement these functions(@file shell_io.h
) in your project:
this file may help: /shell_io/shell_io.c
.
/**
* @brief send a chararcter...
*
*/
extern void shell_putc(char ch);
/**
* @brief send string...
*
*/
extern void shell_puts(const char *str);
/**
* @brief printf() for nano-shell
*
*/
extern int shell_printf(const char *format, ...) __attribute__((format(printf, 1, 2)));
/**
* @brief: Get next character available from stream.
*
* @param ch: Return the character in `ch` if there was...
* @return: Result is non-zero if there was a character, or 0 if there wasn't.
*
*/
extern int shell_getc(char *ch);
Note:
-
int shell_getc(char *ch)
is NOT USED in<react mode>
-
If you run nano-shell in
<main loop mode>
, to avoid losing characters, you'd better use a low layer receive fifo.Take uart for example, you can detect incoming data using interrupts and then store each received character in a first-in-first-out (FIFO) buffer:
void your_uart_interrupt_handler(void) { /* your uart receive code */ char ch = uart_get_char(); /* store character in fifo */ fifo_push(ch); }
then
shell_getc(char *ch)
may be:int shell_getc(char *ch) { if (fifo_empty()) { // if no character in fifo, return 0; // return false } *ch = fifo_pop(); // fifo is not empty, get a character from fifo. return 1; // return true }
I write a simple and lock free fifo based on ring buffer in
@file: /shell_io/static_fifo.h
, maybe helpful...
3. then modify the configuration file: shell_config.h
#include "nano_shell.h"
int main(void)
{
/* system init code... */
/* nano-shell infinite loop. */
nano_shell_loop(NULL);
}
you can use it in interrupt, take UART for example:
void your_uart_interrupt_handler (void)
{
/* your uart receive code */
char ch = uart_get_char();
/* nano-shell isr interface */
nano_shell_react(ch);
}
Note:
nano_shell_react()
is non-blocked (unless there was an infinite loop in your command function), you can call it when get a new character.It is recommended to disable some configurations inshell_config.h
if it was called in interrupt.
#include "nano_shell.h"
int main(void)
{
/* system init code... */
/* create nano_shell task */
TaskHandle_t shellTaskHandle = NULL;
xTaskCreate(nano_shell_loop, "shellTask", <stack_size>, NULL,
<task_priority>, &shellTaskHandle);
/* start rtos task scheduler */
vTaskStartScheduler();
}
Note:
- When determining the stack size for nano-shell, you should consider the memory occupied by commands added in nano-shell.
add these 5 lines to your linker script file:
.nano_shell : {
. = ALIGN(4);
KEEP (*(SORT(.nano_shell*)))
. = ALIGN(4);
} >FLASH