Skip to content

CherryShell is a tiny and multifunction shell specifically designed for embedded applications

License

Notifications You must be signed in to change notification settings

cherry-embedded/CherrySH

Repository files navigation

CherryShell

中文版

Brief

CherryShell is a tiny shell specifically designed for embedded applications.

Features

  • Path completion support, with tab for path completion
  • Command completion support, with tab for command completion
  • Environment variable support, using $ as a prefix, e.g., $PATH
  • Automatic command search in specified directories using the $PATH environment variable
  • History record support, with up and down arrows
  • Compatibility with VT100 and Xterm key values
  • Support for setting username, hostname, and path
  • Non-blocking mode support
  • User login support, requiring implementation of a hash function (default: strcmp)
  • Shortcut key support for Ctrl + <key>, Alt + <key>, F1-F12 to invoke commands
  • Key function remapping support for Ctrl + <key>, Alt + <key>, F1-F12
  • Support for exec function family to directly call commands
  • File system support, including FatFS, FileX, LittleFS, RomFS, etc.
  • Support for adding, modifying, deleting, and retrieving environment variables
  • Signal handling support, capturing and handling different signals such as Ctrl+C SIGINT and Ctrl+Z SIGTSTP
  • Support for exit function to terminate command execution, return to the specified handler, using setjmp (barebonel)
  • Input/output redirection support
  • Multiple user command permission support
  • Job control support, allowing commands to run in the foreground or background, and using control commands (e.g., fg, bg, jobs) to manage and manipulate jobs

Demo

test1

test2

Porting

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, );