English | 简体中文
CherrySH is a tiny shell specifically designed for embedded applications.
- Support TAB completion, including command and path completion
- Support command history, navigable with the
↑and↓arrow keys - Support environment variables using the
$prefix, e.g.,$PATH - Support setting username, hostname, and path/prompt
- Support non-blocking mode; compatible with bare-metal and RTOS
- Support cursor left/right movement and
HOME/ENDcursor positioning - Support key combinations, including
Ctrl + <key>,Alt + <key>,F1-F12, etc. - Support signal handling: capture and handle signals such as
Ctrl+C(SIGINT) andCtrl+Z(SIGTSTP) to interrupt the currently running shell task - Support user login; requires implementing a hash function (default:
strcmp) - Support adding, modifying, deleting, and reading environment variables
- Support filesystems: FatFS, FileX, LittleFS, RomFS, etc. (TODO)
- Support an
exitmechanism to terminate command execution, return context, and call a configured handler; can be implemented withsetjmpfor bare-metal (TODO) - Support job control: run commands in foreground or background and manage them with
fg,bg,jobs(TODO) - Support multi-user command permissions (TODO)
Taking the example of the HPMicro hpm5301evklite board.
Command lookup uses the GCC section, so we need to modify the linkerscript file to add the relevant section, for example in gcc ld:
.text : {
.....
. = ALIGN(4);
__fsymtab_start = .;
KEEP(*(FSymTab))
__fsymtab_end = .;
. = ALIGN(4);
__vsymtab_start = .;
KEEP(*(VSymTab))
__vsymtab_end = .;
. = ALIGN(4);
}
// Include header file
#include "csh.h"
// Create a shell instance
static chry_shell_t csh;// Character output function, directly outputs characters to the serial port
static uint16_t csh_sput_cb(chry_readline_t *rl, const void *data, uint16_t size)
{
uint16_t i;
(void)rl;
for (i = 0; i < size; i++) {
if (status_success != uart_send_byte(HPM_UART0, ((uint8_t *)data)[i])) {
break;
}
}
// Return the number of successfully output characters
return i;
}// Character input function, reads characters directly from the serial port
// If the baud rate is high, a FIFO input buffer can be added
static uint16_t csh_sget_cb(chry_readline_t *rl, void *data, uint16_t size)
{
uint16_t i;
(void)rl;
for (i = 0; i < size; i++) {
if (status_success != uart_receive_byte(HPM_UART0, (uint8_t *)data + i)) {
break;
}
}
// Return the number of successfully read characters
return i;
}// Shell initialization
int shell_init(void)
{
// Structure for configuring initialization parameters
chry_shell_init_t csh_init;
// Set the character input and output functions (must be implemented)
csh_init.sput = csh_sput_cb;
csh_init.sget = csh_sget_cb;
// Symbols defined in the linkscript, used to store exported commands and variables
extern const int __fsymtab_start;
extern const int __fsymtab_end;
extern const int __vsymtab_start;
extern const int __vsymtab_end;
// Configure the start and end addresses of the function and variable tables (must be implemented)
csh_init.command_table_beg = &__fsymtab_start;
csh_init.command_table_end = &__fsymtab_end;
csh_init.variable_table_beg = &__vsymtab_start;
csh_init.variable_table_end = &__vsymtab_end;
// Define a prompt buffer
static char csh_prompt_buffer[128];
// Configure the prompt buffer (optional)
// Depends on whether the editable prompt feature is enabled (CONFIG_CSH_PROMPTEDIT)
csh_init.prompt_buffer = csh_prompt_buffer;
csh_init.prompt_buffer_size = sizeof(csh_prompt_buffer);
// Define a history buffer
static char csh_history_buffer[128];
// Configure the history buffer (optional)
// Depends on whether the history record feature is enabled (CONFIG_CSH_LNBUFF_STATIC)
csh_init.history_buffer = csh_history_buffer;
csh_init.history_buffer_size = sizeof(csh_history_buffer);
// Define a line input buffer
static char csh_line_buffer[128];
// Configure the line input buffer (optional)
// Depends on whether the static line buffer feature is enabled (CONFIG_CSH_LNBUFF_STATIC)
// If the static line buffer feature is not enabled, the line buffer will exist on the stack,
// and non-blocking mode (CONFIG_CSH_NOBLOCK) must be set to 0
csh_init.line_buffer = csh_line_buffer;
csh_init.line_buffer_size = sizeof(csh_line_buffer);
// Default user count is 1
csh_init.uid = 0; // Default user ID
csh_init.user[0] = "cherry"; // Username for user ID 0
csh_init.hash[0] = NULL; // Password hash value for user ID 0
csh_init.host = "hpm5301evklite"; // Host name
// Initialization
int ret = chry_shell_init(&csh, &csh_init);
if (ret) {
return -1;
}
return 0;
}// Main function of the shell, needs to be called in a while(1) loop
int shell_main(void)
{
int ret;
restart:
ret = chry_shell_task_repl(&csh);
if (ret == -1) {
// Execution failed or encountered an issue
return -1;
} else if (ret == 1) {
// Non-blocking mode: Insufficient characters read during character input, return to perform other user tasks
return 0;
} else {
// Execution successful, restart
goto restart;
}
}// Used to avoid competition between printf and shell for the same serial port, called before printf
void shell_uart_lock(void)
{
chry_readline_erase_line(&csh.rl);
}
// Used to avoid competition between printf and shell for the same serial port, called after printf
void shell_uart_unlock(void)
{
chry_readline_edit_refresh(&csh.rl);
}int main(void)
{
board_init();
board_init_led_pins();
shell_init();
uint32_t freq = clock_get_frequency(clock_mchtmr0);
uint64_t time = mchtmr_get_count(HPM_MCHTMR) / (freq / 1000);
while (1) {
shell_main();
// Print every 5 seconds
uint64_t now = mchtmr_get_count(HPM_MCHTMR) / (freq / 1000);
if (now > time + 5000) {
time = now;
// Call the lock to avoid printf interfering with shell input
shell_uart_lock();
printf("other task interval 5S\r\n");
shell_uart_unlock();
}
}
return 0;
}// Implement the export of a command
// write_led 1 - Turn on the LED
// write_led 0 - Turn off the LED
static int write_led(int argc, char **argv)
{
if (argc < 2) {
printf("usage: write_led <status>\r\n\r\n");
printf(" status 0 or 1\r\n\r\n");
return -1;
}
board_led_write(atoi(argv[1]) == 0);
return 0;
}
CSH_CMD_EXPORT(write_led, );
