萬(wàn)字干貨:?jiǎn)纹瑱C實(shí)現Shell控制臺保姆級教程
玩過(guò)Linux的朋友, 是不是對Linux無(wú)所不能的串口Shell命令控制臺羨慕不已, 要是自己做的STM32F系列低檔次的MCU也有這種控制交互能力。
會(huì )給調試/維護和配置省下多少麻煩事呀, 比如啟動(dòng)/關(guān)閉調試或自檢模式, 打印調試信息, 配置系統參數, 傳輸文件等等, 也有相當多的朋友憑借自己出色的編程能力可以實(shí)現這些功能, 這里提出我的這個(gè)解決方案, 以作交流.
本平臺(xc_shell)具備以下性能特點(diǎn):
大量主要代碼, 和具體硬件無(wú)關(guān), 移植性強,代碼文件少.
只有在處理用戶(hù)的輸入命令時(shí), 才占用CPU資源, 且代碼可裁剪到1KB SRAM和4KB Flash;
用戶(hù)可以非常靈活的添加按模板編寫(xiě)的命令腳本文件, 自定義擴張能力強.
支持操作系統和非操作系統兩種場(chǎng)景應用.
支持Ymodem文件傳輸協(xié)議
支持將Flash的扇區開(kāi)辟為參數區, 可實(shí)現本地/遠程升級。
實(shí)用Led燈信號管理, 可將65535虛擬信號燈選擇輸出到1個(gè)實(shí)體LED燈上, 調試時(shí)序和狀態(tài)非常有用
擁有基礎的LED管理, 調試模式設置, 命令幫助指令, 復位指令等基礎功能
功能越多設計會(huì )越復雜, 為了解釋清楚代碼, 先向大家解釋一下以上功能的基礎實(shí)現原理, 并提供一個(gè)最小的的源碼工程。
類(lèi)比Linux會(huì )發(fā)現, 設備的硬件接口往往會(huì )被虛擬成一個(gè)文件(驅動(dòng)), 而Linux內核完全與硬件系統無(wú)任何字節關(guān)聯(lián), 不同平臺驅動(dòng)不同而已, 故而本xc_shell的串口驅動(dòng)也采用了相似的思路:
1) 串口驅動(dòng)用一個(gè)結構體描述, 這樣只需在xc_shell.c中用指針指向這個(gè)TTYx_HANDLE結構體對象就可以將串口(tty)硬件與內核聯(lián)系在一起, 聰明的朋友可能會(huì )想到, 假如我將帶網(wǎng)絡(luò )的開(kāi)發(fā)板按此結構體,虛擬一個(gè)TTY對象, 豈不是就可以實(shí)現一個(gè)網(wǎng)絡(luò )遠程控制臺了! 這點(diǎn)確實(shí)是可以的!
2) 當然諸如多TTY串口實(shí)現接口互換等, 都是一個(gè)指針和step2中的注入回調處理交換的問(wèn)題。
3)用戶(hù)在使用api_TxdFrame或api_TxdByte時(shí)”bsp_ttyX.c“,會(huì )驅動(dòng)具體MCU的串口將數據發(fā)送出去, 收到一幀數據后,若用戶(hù)設置了inj_RcvFrame回調處理方法,則會(huì )在中斷中執行用戶(hù)的回調處理。
/*---------------------* * 指正函數定義 *----------------------*/ typedef void (*pvFunDummy)(void); //輸入整行,輸出邏輯 typedef void (*pvFunVoid) (void); typedef void (*pvFunBool) (bool bVal); typedef void (*pvFunChar) (uint8_t cVal); typedef void (*pvFunShort)(uint16_t sVal); typedef void (*pvFunWord) (uint32_t wVal); //輸入整行,輸出邏輯 typedef bool (*pbFunVoid) (void); typedef bool (*pbFunBool) (bool bVal); typedef bool (*pbFunChar) (uint8_t cVal); typedef bool (*pbFunShort)(uint16_t sVal); typedef bool (*pbFunWord) (uint32_t wVal); //輸入整形指針,輸出邏輯 typedef bool (*pbFun_pVoid) (void * pVoid); typedef bool (*pbFun_pChar) (uint8_t * pStr); typedef bool (*pbFun_pShort)(uint16_t * pShor); typedef bool (*pbFun_pWord) (uint32_t * pWord); //輸入數據幀,輸出邏輯 typedef bool (*pbFun_Buffx)(void * pcBuff, uint16_t len ); typedef bool (*pbFun_Bytex)(uint8_t * pcByte, uint16_t len ); /*---------------------* * TTYx 句柄結構 *----------------------*/ typedef struct TTYx_HANDLE_STRUCT { const char * const name; //驅動(dòng)器名 const uint16_t rxSize; //接收大小 const uint16_t txSize; //發(fā)送大小 //------------------------------------------------------ //step1: 用戶(hù)可用API const pvFunWord init; //初始化. const pbFun_Bytex api_TxdFrame; //發(fā)送數據幀. (發(fā)送幀) const pbFunChar api_TxdByte; //發(fā)送數據字節 //------------------------------------------------------ //step2: 注入回調函數 pbFun_Bytex inj_RcvFrame; //(ISR)接收數據幀. (接收幀) pvFunDummy inj_TxdReady; //(ISR)發(fā)送完畢回調 //------------------------------------------------------ //step3: 接收回調函數 struct TTYx_HANDLE_STRUCT * pvNext; //連接到下一個(gè)指令 }TTYx_HANDLE;
- 可注入的命令腳本(CLI)實(shí)現
命令CLI也是一個(gè)結構體對象:
/*---------------------* * CLI指令 *----------------------*/ typedef struct { const char * const pcCmdStr; //指令字符串(只能為小寫(xiě)字母) const char * const pcHelpStr; //指令描述,必須以:"rn結束". 比如:"help: Returns a listrn". const pFunHook pxCmdHook; //指向回調函數的指針,處理成功返回真否者返回0; uint8_t ucExpParam; //指令期望的參數個(gè)數 const MEDIA_HANDLE *phStorage; //指向存儲介質(zhì),沒(méi)有的話(huà)填充N(xiāo)ULL }Cmd_Typedef_t;
各位朋友可能會(huì )使用到非常多的自定義CLI命令, 格式諸如這個(gè)網(wǎng)卡的命令:
const Cmd_Typedef_t CLI_WizMsg= { //識別關(guān)鍵字 .pcCmdStr = "wiz", //幫助內容 .pcHelpStr = "[WIZ contorls]rn" " wiz helprn" " wiz rd inforn" " wiz resetrn" " wiz wr ip <D0>.<D1>.<D2>.<D3>rn" " wiz wr mask <D0>.<D1>.<D2>.<D3>rn" " wiz wr way <D0>.<D1>.<D2>.<D3>rn" " wiz wr mac <H0>-<H1>-<H2>-<H3>-<H4>-<H5>rn" " wiz wr port <udp> <bak> <vol> <pic>rn" " wiz wr sip <D0>.<D1>.<D2>.<D3> <port>rn" " wiz wr cip <D0>.<D1>.<D2>.<D3> <port>rn" " wiz load defaultrn" "[WIZ Test mode]rn" " wiz loop openrn" " wiz loop closern" "rn", //處理函數 .pxCmdHook = &Shell_WIZ_Service, //見(jiàn)實(shí)體函數 //附帶數據 .ucExpParam = 0, #ifdef SHELL_USE_YMODEM //存儲介質(zhì) .phStorage = NULL, #endif }; /*---------------------* * CLI 鏈表節 *----------------------*/ Cmd_List_t WizList = { &CLI_WizMsg, NULL,}; //Shell指令的頭
如配置IP地址輸入“wiz wr ip 192.168.1.25
3.1 硬件開(kāi)發(fā)環(huán)境
- STM32F103系列開(kāi)發(fā)板一塊, 帶USART1接口。
- USB轉串口線(xiàn)一根。
- MDK4.72或以上
- SecureCRT串口超級終端
在xc_shell使用“/r/n”作為命令的結束符, 而SecureCRT按下Enter不是輸入“/r/n”故而需要按下圖設置:此外在《終端》/仿真/高級中選取【本地回顯】
4代碼介紹
4.1 目錄結構
□ XC_SHELL ├──┬── BSP_LIB BSP庫,硬件相關(guān)驅動(dòng)代買(mǎi) │ ├──── bsp_ledx.c 基礎LED驅動(dòng) │ └──── bsp_tty0.c 調試串口驅動(dòng) │ ├──┬──MDK-ARM 工程文件 │ └──── Project.uvproj │ ├──┬──SHELL_CFG SHELL配置頭文件 │ └──── user_eval.h │ ├──┬──SHELL_CORE SHELL內核文件 │ ├──── xc_shell.c SHELL內核文件 │ ├──── xc_ymodem.c Ymodem傳輸協(xié)議(默認關(guān)閉,在xc_shell.h中啟動(dòng)) │ ├──── xc_iap.c Flash的IAP操作,需要bsp_flash.c驅動(dòng)支持 │ └──── shell_iap.c shell的用戶(hù)腳本模板 │ ├──┬──SHELL_INC SHELL頭文件 │ ├──── bsp_type.h 驅動(dòng)結構定義 │ ├──── xc_shell.h SHELL頭文件 │ └──── xconfig.h 硬件無(wú)關(guān)配置文件 │ ├──┬──STM32F10x_StdPeriph_Lib_V3.5.0 STM32的標準外設庫 │ └──── ...... │ └──┬──USER 用戶(hù)文件 ├─ ..... └──── main.c main文件4.2 工程設置要點(diǎn)
1) 設置使用微庫:
2)配置包含和路徑,注意使用了“--c99”標準,如圖
3) 編譯工程,無(wú)錯誤警告后燒寫(xiě)程序到開(kāi)發(fā)板運行。
4.3 最終效果按圖輸入一下指令,SHELL平臺會(huì )回復相關(guān)信息。其中輸入“l(fā)ed set 0=1”會(huì )將信號1分配到物理LED0上;輸入“l(fā)ed set 0=2”會(huì )將信號2分配到物理LED0上。這樣用戶(hù)編寫(xiě)程序代碼時(shí)相當于擁有了超級多的LED信號可用,調試時(shí)序非常有用。
5添加自己的腳本
5.1 源代碼示例
假設我要編寫(xiě)一個(gè)自己的指令腳本, 來(lái)讀取MCU的關(guān)鍵信息,關(guān)鍵字為mcu, 文件命名為shell_mcu.c;當輸入“mcu rd 0”時(shí)顯示MCU的FLASH大小,輸入“mcu rd 1”時(shí)讀取MCU的唯一ID信息。shell_mcu.c源代碼:
/*********************************Copyright (c)********************************* ** ** FIVE工作組 ** **---------------------------------File Info------------------------------------ ** File Name: shell_mcu.c ** Last modified Date: 2017/9/17 15:13:57 ** Last Version: V1.0 ** Description: shell測試 ** **------------------------------------------------------------------------------ ** Created By: wanxuncpx ** Created date: 2017/9/17 15:14:08 ** Version: V1.0 ** Descriptions: none **------------------------------------------------------------------------------ ** HW_CMU: STM32F103 ** Libraries: STM32F10x_StdPeriph_Lib_V3.5.0 ** version V3.5 *******************************************************************************/ /****************************************************************************** 更新說(shuō)明: ******************************************************************************/ /****************************************************************************** ********************************* 編 譯 控 制 ******************************** ******************************************************************************/ #define MCU_SHELL //注釋掉時(shí)屏蔽iap shell功能 #include "xc_shell.h" //Shell支持文件,含bool,uint8_t..以及串口數據收發(fā)操作 /****************************************************************************** ********************************* 文件引用部分 ******************************** ******************************************************************************/ /*---------------------* * 模塊驅動(dòng)引用 *----------------------*/ //#include "net_w5500.h" #ifdef MCU_SHELL /****************************************************************************** ********************************** Shell實(shí)例 ********************************** ******************************************************************************/ /*---------------------* * CLI指令服務(wù) *----------------------*/ extern bool Shell_MCU_Service(void * pcBuff, uint16_t len ); /*---------------------* * CLI 結構 *----------------------*/ const Cmd_Typedef_t CLI_McuMsg= { //識別關(guān)鍵字 "mcu", //幫助內容 "[mcu contorls]rn" " mcu rd <d>tt- Read FLASH information.rn" "rn", //處理函數 &Shell_MCU_Service, //附帶數據 0, #ifdef SHELL_USE_YMODEM //存儲介質(zhì) NULL, #endif }; /*---------------------* * CLI鏈表節(輸出) *----------------------*/ Cmd_List_t McuList = {&CLI_McuMsg ,NULL}; //IAP指令鏈表 /****************************************************************************** ********************************* 函 數 聲 明 ********************************* ******************************************************************************/ /****************************************************************************** / 函數功能:STM32F103控制函數 / 修改日期:2015/7/14 20:22:02 / 輸入參數:none / 輸出參數:none / 使用說(shuō)明:需要執行約10s ******************************************************************************/ static bool FLASH_ioctl(uint8_t cmd,void * param) { #define UID_ADDR 0x1FFFF7E0 //閃存容量寄存器,值對應KB單位 #define MAC_ADDR 0x1FFFF7E8 //MCU的唯一ID號,共12個(gè)字節 #define UID_SIZE 2 //UID的字節數 #define MAC_SIZE 12 //MAC的字節數 //step1: 檢查參數 if(!param)return false; //step2: 處理數據 switch(cmd){ case 0 : { //獲取FLASH的的UID uint16_t * ptDst = (uint16_t *)((uint32_t)param+1); *ptDst = *(uint16_t *)UID_ADDR; *(uint8_t *)param = UID_SIZE; return true; } case 1 : { //獲取芯片的MAC地址 uint32_t * ptDst = (uint32_t *)((uint32_t)param+1); uint32_t * ptSrc = (uint32_t *)MAC_ADDR; *ptDst++ = *ptSrc++; *ptDst++ = *ptSrc++; *ptDst++ = *ptSrc++; *(uint8_t *)param = MAC_SIZE; return true; } default:return false; } } /****************************************************************************** / 函數功能:文件系統Shel指令處理 / 修改日期:2013/9/10 19:04:15 / 輸入參數:輸入當前的程序版本 / 輸出參數:none / 使用說(shuō)明:none ******************************************************************************/ bool Shell_MCU_Service(void * pcBuff, uint16_t len ) { uint8_t *ptRxd; //用于接收指令處理 int i; uint16_t retval; uint8_t buff[32]; //處理指令 //-------------------------------------------------------------------------- ptRxd = (uint8_t *)pcBuff; if(StrComp(ptRxd,"rd ")) //讀取FLASH信息 { int wval; if(1 != sscanf((void *)ptRxd,"%*s%d",&wval) )return false; if( wval>2 )return false; if(0==wval) { FLASH_ioctl(0,buff); retval = *(uint16_t *)(buff+1) ; printf("->Flash:t%dKBrn",retval); return true; } else if(1==wval) { FLASH_ioctl(1,buff); printf("->MAC:t "); for(i=0; i<buff[0]-1; i++){printf("%02X-",buff[i+1]);} printf("%02Xrn",buff[i+1]); return true; } else return false; } else if(StrComp(ptRxd,"helprn")) //指令幫助 { shell_SendStr((void *)CLI_McuMsg.pcHelpStr); return true; } else return false; } /****************************************************************************** *********************************** END ************************************ ******************************************************************************/ #endif5.2 實(shí)現步驟
1) 將該文件添加到工程下。
2) 在main.c中用extern 引用McuList,源代碼為:
/*---------------------* * Shell指令鏈表 *----------------------*/ extern Cmd_List_t McuList;
3)在main.c初始化時(shí)添加:
//---------------------------------------------------------- //step1: shell初始化 shell_Init(115200,ledx_cfg); //初始化shell接口 CLI_AddCmd(&McuList); //添加模塊指令到鏈表
4)編譯工程文件。
5)下載到開(kāi)發(fā)板運行即可在終端下看到新支持的CLI指令:
來(lái)源:https://dablelv.blog.csdn.net/article
*博客內容為網(wǎng)友個(gè)人發(fā)布,僅代表博主個(gè)人觀(guān)點(diǎn),如有侵權請聯(lián)系工作人員刪除。