【嵌入式】开源shell命令行的移植和使用(2)——letter-shell

目录

一 背景说明

二 移植准备

三 移植过程

四 自定义命令

五 实际使用


一 背景说明

        之前使用过一款开源shell工具 nr_micro_shell (【嵌入式】开源shell命令行的移植和使用(1)——nr_micro_shell-CSDN博客),感觉功能还不够强大,故换了一个类似的开源shell工具 letter-shell

        shell是一个C语言编写的,可以嵌入在程序中的嵌入式shell,主要面向嵌入式设备,以C语言函数为运行单位,可以通过命令行调用,运行程序中的函数。相对2.x版本,letter shell 3.x增加了用户管理,权限管理,以及对文件系统的初步支持。此外3.x版本修改了命令格式和定义,2.x版本的工程需要经过简单的修改才能完成迁移。若只需要使用基础功能,可以使用letter shell 2.x版本。

        功能包括:

  • 命令自动补全
  • 快捷键功能定义
  • 命令权限管理
  • 用户管理
  • 变量支持
  • 代理函数和参数代理解析

二 移植准备

【1】letter-shell下载地址——gitee:

letter-shell: letter shell一个嵌入式小型shell,特别适用于单片机系统。

【2】letter-shell下载地址——github:

https://github.com/NevermindZZT/letter-shell

【3】提前准备好芯片UART串口的初始化以及收发接口。

三 移植过程

【1】目录移植:将下载好的letter-shell文件解压到自己的工程目录下,并在IDE中包含所需的src中的源文件以及头文件,另外将demo中的 shell_port.c 以及 shell_port.h 也移到src目录下一并处理:

        此时编译一下,可能会出现报错,自行处理一下。

【2】根据自己需要修改 shell_cfg.h 中的基础配置:

意义
SHELL_TASK_WHILE是否使用默认shell任务while循环
SHELL_USING_CMD_EXPORT是否使用命令导出方式
SHELL_USING_COMPANION是否使用shell伴生对象功能
SHELL_SUPPORT_END_LINE是否支持shell尾行模式
SHELL_HELP_LIST_USER是否在输入命令列表中列出用户
SHELL_HELP_LIST_VAR是否在输入命令列表中列出变量
SHELL_HELP_LIST_KEY是否在输入命令列表中列出按键
SHELL_ENTER_LF使用LF作为命令行回车触发
SHELL_ENTER_CR使用CR作为命令行回车触发
SHELL_ENTER_CRLF使用CRLF作为命令行回车触发
SHELL_EXEC_UNDEF_FUNC使用执行未导出函数的功能
SHELL_COMMAND_MAX_LENGTHshell命令最大长度
SHELL_PARAMETER_MAX_NUMBERshell命令参数最大数量
SHELL_HISTORY_MAX_NUMBER历史命令记录数量
SHELL_DOUBLE_CLICK_TIME双击间隔(ms)
SHELL_QUICK_HELP快速帮助
SHELL_MAX_NUMBER管理的最大shell数量
SHELL_GET_TICK()获取系统时间(ms)
SHELL_USING_LOCK是否使用锁
SHELL_MALLOC(size)内存分配函数(shell本身不需要)
SHELL_FREE(obj)内存释放函数(shell本身不需要)
SHELL_SHOW_INFO是否显示shell信息
SHELL_CLS_WHEN_LOGIN是否在登录后清除命令行
SHELL_DEFAULT_USERshell默认用户
SHELL_DEFAULT_USER_PASSWORD默认用户密码
SHELL_LOCK_TIMEOUTshell自动锁定超时
SHELL_USING_FUNC_SIGNATURE使用函数签名

        如无特殊需要,可以使用默认配置。

【3】修改接口文件 shell_port.c :

        (i)改写 userShellWrite 为自己的串口输出接口

/**
 * @brief 用户shell写
 * 
 * @param data 数据
 * @param len 数据长度
 * 
 * @return short 实际写入的数据长度
 */
short userShellWrite(char *data, unsigned short len)
{
    int i;
	
	for(i=0;i<len;i++){
        Uart_SendDataPollTimeOut(M0P_UART0, (uint8_t)data[i], SystemCoreClock/DBG_PRINTF_BAUDRATE);
	}
 
	return 0;
}

        (ii)串口接收中断中嵌入 shellHandler(&shell, ch) 以接收输入字符

/**************************************************************************
* 函数名称: Uart0_IRQHandler
* 功能描述: 串口接收中断
**************************************************************************/
void Uart0_IRQHandler(void)
{
    uint8_t ch;

    if(Uart_GetStatus(M0P_UART0, UartRC))       //UART0数据接收
    {
        Uart_ClrStatus(M0P_UART0, UartRC);      //清中断状态位
        ch = Uart_ReceiveData(M0P_UART0);       //接收数据字节
        shellHandler(&shell, ch);
    }
    
    if(Uart_GetStatus(M0P_UART0, UartTC))         //UART0数据发送
    {
        Uart_ClrStatus(M0P_UART0, UartTC);        //清中断状态位
    }
}

        (iii)用户shell初始化 userShellInit

/**
 * @brief 用户shell初始化
 * 
 */
void userShellInit(void)
{
    shell.write = userShellWrite;
    shellInit(&shell, shellBuffer, 512);
}

【4】在程序初始化的时候调用串口的初始化以及shell初始化接口(userShellInit()),上电之后即可看到letter_shell的logo,按TAB键即可看到注册的几个命令:

#include "shell_port.h"
 
int main(void)
{
    //串口初始化
    Dbg_Init();
    Dbg_Cfg();
 
    //命令行初始化
    userShellInit();
 
    while(1)
    {
        
    }
}

四 自定义命令

        在 shell.c 中编辑以及自定义命令。自定义命令分为两步,第一步定义命令函数,第二步使用SHELL_EXPORT_CMD 导入命令列表

【1】定义命令函数

        letter shell 3.x同时支持两种形式的函数定义方式,形如main函数定义的func(int argc, char *agrv[])以及形如普通C函数的定义func(int i, char *str, ...),两种函数定义方式适用于不同的场景。

        (i)main函数形式(属性为SHELL_TYPE_CMD_MAIN):使用此方式,一个函数定义的例子如下:

int func(int argc, char *agrv[])
{
    printf("%dparameter(s)\r\n", argc);
    for (char i = 1; i < argc; i++)
    {
        printf("%s\r\n", argv[i]);
    }
}
SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_MAIN), func, func, test);

           终端调用:

letter:/$ func "hello world"
2 parameter(s)
hello world

        (注1:main函数形式下,如果要导入 flaot 类型的参数,可以使用 sscanf 来格式化输入:)

void shellSet(int argc, char *argv[])
{
    float t_vset;

    if(argc > 1)
    {
        if(!strcmp("v", argv[1]))
        {
            if(argc > 2)
            {
                sscanf(argv[2], "%f", &t_vset);
                printf("Change V-USET to %f V!\r\n", t_vset);
            }
        }
    }
}

        (ii)普通C函数形式(属性为SHELL_TYPE_CMD_FUNC):使用此方式,shell会自动对参数进行转化处理,目前支持二进制,八进制,十进制,十六进制整形,字符,字符串的自动处理,如果需要其他类型的参数,请使用代理参数解析的方式(参考代理函数和代理参数解析),或者使用字符串的方式作为参数,自行进行处理,例子如下:

int func(int i, char ch, char *str)
{
    printf("input int: %d, char: %c, string: %s\r\n", i, ch, str);
}
SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC), func, func, test);

           终端调用:

letter:/$ func 666 'A' "hello world"
input int: 666, char: A, string: hello world

        (注2:普通C函数形式下,如果要导入 flaot 类型的参数,可以使用代理函数命令导出宏定义命令:

void test(int a, float b, int c, float d)
{
    printf("%d, %f, %d, %f \r\n", a, b, c, d);
}
SHELL_EXPORT_CMD_AGENCY(SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC),
test, test, test,
p1, SHELL_PARAM_FLOAT(p2), p3, SHELL_PARAM_FLOAT(p4));

        (iii)letter_shell还提供了诸如变量使用在函数中获取当前shell对象执行未导出函数等其他应用,具体可以参考gitee中的使用说明自行发掘。

        

【2】使用 SHELL_EXPORT_CMD 导入命令列表,其参数定义如下:

参数

描述

_attr

命令属性

_name

命令名

_func

命令函数

_desc

命令描述
/**
     * @brief shell 命令定义
     * 
     * @param _attr 命令属性
     * @param _name 命令名
     * @param _func 命令函数
     * @param _desc 命令描述
     * @param ... 其他参数
     */
    #define SHELL_EXPORT_CMD(_attr, _name, _func, _desc, ...) \
            const char shellCmd##_name[] = #_name; \
            const char shellDesc##_name[] = #_desc; \
            SHELL_USED const ShellCommand \
            shellCommand##_name SHELL_SECTION("shellCommand") =  \
            { \
                .attr.value = _attr, \
                .data.cmd.name = shellCmd##_name, \
                .data.cmd.function = (int (*)())_func, \
                .data.cmd.desc = shellDesc##_name, \
                ##__VA_ARGS__ \
            }

        至此,移植结束。

五 实际使用

        实际使用的效果如下:

        补充:自己调试的时候遇到一个问题:实际使用shell的时候,有时候按空格或者退格键会使得程序直接重启,更换了串口波特率9600为115200之后,问题解决。具体原因不明,希望有大神看到能够帮忙解惑

  • 25
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
FreeRTOS Letter-shell 是一个基于 FreeRTOS 的命令行界面工具,可以方便地在嵌入式系统中进行调试和控制。下面是 Letter-shell 的代码实现。 首先,需要定义一些宏和数据结构: ```c #define MAX_COMMAND_LENGTH 50 #define MAX_NUM_PARAMS 10 #define MAX_OUTPUT_LENGTH 200 typedef struct { char command[MAX_COMMAND_LENGTH]; char params[MAX_NUM_PARAMS][MAX_COMMAND_LENGTH]; int num_params; } command_t; ``` 其中 `command_t` 结构体用于保存用户输入的命令及其参数。 接着,需要定义一个任务函数来处理用户输入。该任务函数会不断地从串口读取用户输入,并解析出命令及其参数。然后根据命令执行相应的操作,并将结果输出到串口。 ```c void letter_shell_task(void *pvParameters) { char input[MAX_COMMAND_LENGTH]; command_t command; char output[MAX_OUTPUT_LENGTH]; while(1) { // 从串口读取用户输入 read_input(input, MAX_COMMAND_LENGTH); // 解析命令及其参数 parse_command(input, &command); // 根据命令执行相应的操作,将结果存储在 output 中 execute_command(&command, output, MAX_OUTPUT_LENGTH); // 输出结果到串口 write_output(output); } } ``` 其中 `read_input()` 函数用于从串口读取用户输入,`parse_command()` 函数用于解析命令及其参数,`execute_command()` 函数用于执行命令并生成输出结果,`write_output()` 函数用于将输出结果输出到串口。 最后,需要在 FreeRTOS 中创建 Letter-shell 任务。 ```c void main() { // 初始化串口 init_uart(); // 创建 Letter-shell 任务 xTaskCreate(letter_shell_task, "Letter-shell", 1024, NULL, 1, NULL); // 启动 FreeRTOS 调度器 vTaskStartScheduler(); } ``` 以上就是 FreeRTOS Letter-shell 的代码实现。开发者可以根据自己的需求修改代码,增加自定义命令及其操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值