【嵌入式】开源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_LENGTH | shell命令最大长度 |
SHELL_PARAMETER_MAX_NUMBER | shell命令参数最大数量 |
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_USER | shell默认用户 |
SHELL_DEFAULT_USER_PASSWORD | 默认用户密码 |
SHELL_LOCK_TIMEOUT | shell自动锁定超时 |
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之后,问题解决。具体原因不明,希望有大神看到能够帮忙解惑。