源碼地址:https://github.com/NevermindZZT/letter-shell
1 Letter shell簡(jiǎn)介
熟悉Linux的朋友的都知道,shell包裹在內(nèi)核之外的人機(jī)交互界面,用于用戶和內(nèi)核之間打交道的功能,類似于windows CMD。 通過(guò)Shell將輸入的命令與內(nèi)核通訊,好讓內(nèi)核可以控制硬件開(kāi)正確無(wú)誤的操作工作。Shell有著不同的分類,比如Bourne shell(sh),Korn shell(ksh)、C shell (csh)、Bourne-again shell(bash)、tcsh。其中最常用的有csh和bash。Shell本身是一個(gè)用C語(yǔ)言編寫(xiě)的程序,它是用戶使用Unix/Linux的橋梁,用戶的大部分工作都是通過(guò)Shell完成的。
然而在嵌入式中,由于資源有限,自然很少使用shell,但隨著MCU的資源越來(lái)越豐富,一些適用于嵌入式的shell工具也就問(wèn)世了,本問(wèn)將要介紹的是Letter shell,Letter shell是一個(gè)體積極小的嵌入式shell,當(dāng)前最新版本是3.X。
Letter shell有如下功能:
- 命令自動(dòng)補(bǔ)全,使用TAB鍵補(bǔ)全命令
- 命令幫助,使用help [command]顯示命令幫助
- 幫助補(bǔ)全,輸入命令后雙擊TAB鍵補(bǔ)全命令幫助指令
- 快捷鍵,支持使用CTRL+A~Z組合按鍵直接調(diào)用函數(shù)
- shell變量,支持在shell中查看和修改變量值,支持變量作為命令參數(shù)
- 登錄密碼,支持在shell中使用登錄密碼,支持超時(shí)自動(dòng)鎖定
2 Letter shell移植
Shell是一個(gè)命令行交互式形式存在,那最常規(guī)的就是使用MCU的串口資源了,當(dāng)然也可使用USB模擬的虛擬串口。
Letter shell的移植比較簡(jiǎn)單,既然需要占用串口資源,那么首先要準(zhǔn)備一個(gè)裸機(jī)工程,該工程需要事先串口的收發(fā),關(guān)于串口的實(shí)現(xiàn)請(qǐng)參看逼著文章:
標(biāo)準(zhǔn)庫(kù):https://bruceou.blog.csdn.net/article/details/79341769
HAL庫(kù):https://bruceou.blog.csdn.net/article/details/109190370
筆者本文以標(biāo)準(zhǔn)庫(kù)為例講解。
1.復(fù)制源碼
首先下載letter-shell,然后在工程中新建Letter_shell目錄,將letter-shell目錄下的src的文件復(fù)制到工程目錄Middlewares/Letter_shell中。
2.新建接口文件
在工程用戶目錄下新建shell_port.c和shell_port.h文件,當(dāng)然也可以放在User目錄。
3.配置工程
打開(kāi)Keil,添加相應(yīng)的文件。
然后添加相應(yīng)的頭文件路徑。
接下來(lái)就是實(shí)現(xiàn)Letter shell的收發(fā)。
發(fā)送代碼如下:
/**
* @brief 用戶shell寫(xiě)
*
* @param data 數(shù)據(jù)
*/
void userShellWrite(char data)
{
USART_SendData(USART1, (uint8_t) data);
/* 等待發(fā)送完畢 */
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;
//調(diào)用shell處理數(shù)據(jù)的接口
shellHandler(&shell, ch);
}
}
還需要實(shí)現(xiàn)Letter shell初始化接口。
/**
* @brief 用戶shell初始化
*
*/
void userShellInit(void)
{
shell.write = userShellWrite;
shellInit(&shell, shellBuffer, 512);
}
最后在主函數(shù)中初始化即可。
/**
* @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等工具,打印信息如下:
很簡(jiǎn)單吧。
3 Letter shell應(yīng)用
3.1 Letter shell宏定義
在開(kāi)發(fā)Letter shell應(yīng)用前,需要知道Letter shell的宏定義,其宏定義在shell_cfg.h文件。
#ifndef __SHELL_CFG_H__
#define __SHELL_CFG_H__
/**
* @brief 是否使用默認(rèn)shell任務(wù)while循環(huán),使能宏`SHELL_USING_TASK`后此宏有意義
* 使能此宏,則`shellTask()`函數(shù)會(huì)一直循環(huán)讀取輸入,一般使用操作系統(tǒng)建立shell
* 任務(wù)時(shí)使能此宏,關(guān)閉此宏的情況下,一般適用于無(wú)操作系統(tǒng),在主循環(huán)中調(diào)用`shellTask()`
*/
#define SHELL_TASK_WHILE 1
/**
* @brief 是否使用命令導(dǎo)出方式
* 使能此宏后,可以使用`SHELL_EXPORT_CMD()`等導(dǎo)出命令
* 定義shell命令,關(guān)閉此宏的情況下,需要使用命令表的方式
*/
#define SHELL_USING_CMD_EXPORT 1
/**
* @brief 是否使用shell伴生對(duì)象
* 一些擴(kuò)展的組件(文件系統(tǒng)支持,日志工具等)需要使用伴生對(duì)象
*/
#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 是否在輸出命令列表中展示命令權(quán)限
*/
#define SHELL_HELP_SHOW_PERMISSION 1
/**
* @brief 使用LF作為命令行回車觸發(fā)
* 可以和SHELL_ENTER_CR同時(shí)開(kāi)啟
*/
#define SHELL_ENTER_LF 1
/**
* @brief 使用CR作為命令行回車觸發(fā)
* 可以和SHELL_ENTER_LF同時(shí)開(kāi)啟
*/
#define SHELL_ENTER_CR 1
/**
* @brief 使用CRLF作為命令行回車觸發(fā)
* 不可以和SHELL_ENTER_LF或SHELL_ENTER_CR同時(shí)開(kāi)啟
*/
#define SHELL_ENTER_CRLF 0
/**
* @brief 使用執(zhí)行未導(dǎo)出函數(shù)的功能
* 啟用后,可以通過(guò)`exec [addr] [args]`直接執(zhí)行對(duì)應(yīng)地址的函數(shù)
* @attention 如果地址錯(cuò)誤,可能會(huì)直接引起程序崩潰
*/
#define SHELL_EXEC_UNDEF_FUNC 0
/**
* @brief shell命令參數(shù)最大數(shù)量
* 包含命令名在內(nèi),超過(guò)8個(gè)參數(shù)并且使用了參數(shù)自動(dòng)轉(zhuǎn)換的情況下,需要修改源碼
*/
#define SHELL_PARAMETER_MAX_NUMBER 8
/**
* @brief 歷史命令記錄數(shù)量
*/
#define SHELL_HISTORY_MAX_NUMBER 5
/**
* @brief 雙擊間隔(ms)
* 使能宏`SHELL_LONG_HELP`后此宏生效,定義雙擊tab補(bǔ)全help的時(shí)間間隔
*/
#define SHELL_DOUBLE_CLICK_TIME 200
/**
* @brief 管理的最大shell數(shù)量
*/
#define SHELL_MAX_NUMBER 5
/**
* @brief shell格式化輸出的緩沖大小
* 為0時(shí)不使用shell格式化輸出
*/
#define SHELL_PRINT_BUFFER 128
/**
* @brief shell格式化輸入的緩沖大小
* 為0時(shí)不使用shell格式化輸入
* @note shell格式化輸入會(huì)阻塞shellTask, 僅適用于在有操作系統(tǒng)的情況下使用
*/
#define SHELL_SCAN_BUFFER 0
/**
* @brief 獲取系統(tǒng)時(shí)間(ms)
* 定義此宏為獲取系統(tǒng)Tick,如`HAL_GetTick()`
* @note 此宏不定義時(shí)無(wú)法使用雙擊tab補(bǔ)全命令help,無(wú)法使用shell超時(shí)鎖定
*/
#define SHELL_GET_TICK() 0
/**
* @brief shell內(nèi)存分配
* shell本身不需要此接口,若使用shell伴生對(duì)象,需要進(jìn)行定義
*/
#define SHELL_MALLOC(size) 0
/**
* @brief shell內(nèi)存釋放
* shell本身不需要此接口,若使用shell伴生對(duì)象,需要進(jìn)行定義
*/
#define SHELL_FREE(obj) 0
/**
* @brief 是否顯示shell信息
*/
#define SHELL_SHOW_INFO 1
/**
* @brief 是否在登錄后清除命令行
*/
#define SHELL_CLS_WHEN_LOGIN 1
/**
* @brief shell默認(rèn)用戶
*/
#define SHELL_DEFAULT_USER "letter"
/**
* @brief shell默認(rèn)用戶密碼
* 若默認(rèn)用戶不需要密碼,設(shè)為""
*/
#define SHELL_DEFAULT_USER_PASSWORD ""
/**
* @brief shell自動(dòng)鎖定超時(shí)
* shell當(dāng)前用戶密碼有效的時(shí)候生效,超時(shí)后會(huì)自動(dòng)重新鎖定shell
* 設(shè)置為0時(shí)關(guān)閉自動(dòng)鎖定功能,時(shí)間單位為`SHELL_GET_TICK()`單位
* @note 使用超時(shí)鎖定必須保證`SHELL_GET_TICK()`有效
*/
#define SHELL_LOCK_TIMEOUT 0 * 60 * 1000
#endif