源码地址:https://github.com/NevermindZZT/letter-shell
1 Letter shell简介
熟悉Linux的朋友的都知道,shell包裹在内核之外的人机交互界面,用于用户和内核之间打交道的功能,类似于windows CMD。 通过Shell将输入的命令与内核通讯,好让内核可以控制硬件开正确无误的操作工作。Shell有着不同的分类,比如Bourne shell(sh),Korn shell(ksh)、C shell (csh)、Bourne-again shell(bash)、tcsh。其中最常用的有csh和bash。Shell本身是一个用C语言编写的程序,它是用户使用Unix/Linux的桥梁,用户的大部分工作都是通过Shell完成的。
然而在嵌入式中,由于资源有限,自然很少使用shell,但随着MCU的资源越来越丰富,一些适用于嵌入式的shell工具也就问世了,本问将要介绍的是Letter shell,Letter shell是一个体积极小的嵌入式shell,当前最新版本是3.X。
Letter shell有如下功能:
- 命令自动补全,使用TAB键补全命令
- 命令帮助,使用help [command]显示命令帮助
- 帮助补全,输入命令后双击TAB键补全命令帮助指令
- 快捷键,支持使用CTRL+A~Z组合按键直接调用函数
- shell变量,支持在shell中查看和修改变量值,支持变量作为命令参数
- 登录密码,支持在shell中使用登录密码,支持超时自动锁定
2 Letter shell移植
Shell是一个命令行交互式形式存在,那最常规的就是使用MCU的串口资源了,当然也可使用USB模拟的虚拟串口。
Letter shell的移植比较简单,既然需要占用串口资源,那么首先要准备一个裸机工程,该工程需要事先串口的收发,关于串口的实现请参看逼着文章:
标准库:https://bruceou.blog.csdn.net/article/details/79341769
HAL库:https://bruceou.blog.csdn.net/article/details/109190370
笔者本文以标准库为例讲解。
1.复制源码
首先下载letter-shell,然后在工程中新建Letter_shell目录,将letter-shell目录下的src的文件复制到工程目录Middlewares/Letter_shell中。
2.新建接口文件
在工程用户目录下新建shell_port.c和shell_port.h文件,当然也可以放在User目录。
3.配置工程
打开Keil,添加相应的文件。
然后添加相应的头文件路径。
接下来就是实现Letter shell的收发。
发送代码如下:
/**
* @brief 用户shell写
*
* @param data 数据
*/
void userShellWrite(char data)
{
USART_SendData(USART1, (uint8_t) data);
/* 等待发送完毕 */
while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
}
接收采用中断的方式,代码如下:
/**
* @brief This function handles USART1 Handler.
* @param None
* @retval None
*/
void USART1_IRQHandler(void)
{
uint8_t ch; //接收中断缓冲
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
ch = USART_ReceiveData(USART1);
//ch = USART1- >DR;
//调用shell处理数据的接口
shellHandler(&shell, ch);
}
}
还需要实现Letter shell初始化接口。
/**
* @brief 用户shell初始化
*
*/
void userShellInit(void)
{
shell.write = userShellWrite;
shellInit(&shell, shellBuffer, 512);
}
最后在主函数中初始化即可。
/**
* @brief mian
* @param None
* @retval int
*/
int main(void)
{
/* 配置SysTick 为10us中断一次 */
SysTick_Init();
/* USART1 配置模式为 115200 8-N-1,中断接收 */
USART1_Config();
userShellInit();
for(;;)
{
Delay_ms(50);
}
}
好了,这就移植完成了,编译、下载,连接串口1,使用xshell等工具,打印信息如下:
很简单吧。
3 Letter shell应用
3.1 Letter shell宏定义
在开发Letter shell应用前,需要知道Letter shell的宏定义,其宏定义在shell_cfg.h文件。
#ifndef __SHELL_CFG_H__
#define __SHELL_CFG_H__
/**
* @brief 是否使用默认shell任务while循环,使能宏`SHELL_USING_TASK`后此宏有意义
* 使能此宏,则`shellTask()`函数会一直循环读取输入,一般使用操作系统建立shell
* 任务时使能此宏,关闭此宏的情况下,一般适用于无操作系统,在主循环中调用`shellTask()`
*/
#define SHELL_TASK_WHILE 1
/**
* @brief 是否使用命令导出方式
* 使能此宏后,可以使用`SHELL_EXPORT_CMD()`等导出命令
* 定义shell命令,关闭此宏的情况下,需要使用命令表的方式
*/
#define SHELL_USING_CMD_EXPORT 1
/**
* @brief 是否使用shell伴生对象
* 一些扩展的组件(文件系统支持,日志工具等)需要使用伴生对象
*/
#define SHELL_USING_COMPANION 0
/**
* @brief 支持shell尾行模式
*/
#define SHELL_SUPPORT_END_LINE 0
/**
* @brief 是否在输出命令列表中列出用户
*/
#define SHELL_HELP_LIST_USER 0
/**
* @brief 是否在输出命令列表中列出变量
*/
#define SHELL_HELP_LIST_VAR 0
/**
* @brief 是否在输出命令列表中列出按键
*/
#define SHELL_HELP_LIST_KEY 0
/**
* @brief 是否在输出命令列表中展示命令权限
*/
#define SHELL_HELP_SHOW_PERMISSION 1
/**
* @brief 使用LF作为命令行回车触发
* 可以和SHELL_ENTER_CR同时开启
*/
#define SHELL_ENTER_LF 1
/**
* @brief 使用CR作为命令行回车触发
* 可以和SHELL_ENTER_LF同时开启
*/
#define SHELL_ENTER_CR 1
/**
* @brief 使用CRLF作为命令行回车触发
* 不可以和SHELL_ENTER_LF或SHELL_ENTER_CR同时开启
*/
#define SHELL_ENTER_CRLF 0
/**
* @brief 使用执行未导出函数的功能
* 启用后,可以通过`exec [addr] [args]`直接执行对应地址的函数
* @attention 如果地址错误,可能会直接引起程序崩溃
*/
#define SHELL_EXEC_UNDEF_FUNC 0
/**
* @brief shell命令参数最大数量
* 包含命令名在内,超过8个参数并且使用了参数自动转换的情况下,需要修改源码
*/
#define SHELL_PARAMETER_MAX_NUMBER 8
/**
* @brief 历史命令记录数量
*/
#define SHELL_HISTORY_MAX_NUMBER 5
/**
* @brief 双击间隔(ms)
* 使能宏`SHELL_LONG_HELP`后此宏生效,定义双击tab补全help的时间间隔
*/
#define SHELL_DOUBLE_CLICK_TIME 200
/**
* @brief 管理的最大shell数量
*/
#define SHELL_MAX_NUMBER 5
/**
* @brief shell格式化输出的缓冲大小
* 为0时不使用shell格式化输出
*/
#define SHELL_PRINT_BUFFER 128
/**
* @brief shell格式化输入的缓冲大小
* 为0时不使用shell格式化输入
* @note shell格式化输入会阻塞shellTask, 仅适用于在有操作系统的情况下使用
*/
#define SHELL_SCAN_BUFFER 0
/**
* @brief 获取系统时间(ms)
* 定义此宏为获取系统Tick,如`HAL_GetTick()`
* @note 此宏不定义时无法使用双击tab补全命令help,无法使用shell超时锁定
*/
#define SHELL_GET_TICK() 0
/**
* @brief shell内存分配
* shell本身不需要此接口,若使用shell伴生对象,需要进行定义
*/
#define SHELL_MALLOC(size) 0
/**
* @brief shell内存释放
* shell本身不需要此接口,若使用shell伴生对象,需要进行定义
*/
#define SHELL_FREE(obj) 0
/**
* @brief 是否显示shell信息
*/
#define SHELL_SHOW_INFO 1
/**
* @brief 是否在登录后清除命令行
*/
#define SHELL_CLS_WHEN_LOGIN 1
/**
* @brief shell默认用户
*/
#define SHELL_DEFAULT_USER "letter"
/**
* @brief shell默认用户密码
* 若默认用户不需要密码,设为""
*/
#define SHELL_DEFAULT_USER_PASSWORD ""
/**
* @brief shell自动锁定超时
* shell当前用户密码有效的时候生效,超时后会自动重新锁定shell
* 设置为0时关闭自动锁定功能,时间单位为`SHELL_GET_TICK()`单位
* @note 使用超时锁定必须保证`SHELL_GET_TICK()`有效
*/
#define SHELL_LOCK_TIMEOUT 0 * 60 * 1000
#endif