博客專欄

EEPW首頁 > 博客 > 萬字干貨:單片機實現(xiàn)Shell控制臺保姆級教程

萬字干貨:單片機實現(xiàn)Shell控制臺保姆級教程

發(fā)布人:xiaomaidashu 時間:2023-11-10 來源:工程師 發(fā)布文章
萬字干貨:單片機實現(xiàn)Shell控制臺保姆級教程
小麥大叔 2023-11-08 12:34 發(fā)表于浙江


1前言

玩過Linux的朋友, 是不是對Linux無所不能的串口Shell命令控制臺羨慕不已, 要是自己做的STM32F系列低檔次的MCU也有這種控制交互能力。

會給調(diào)試/維護和配置省下多少麻煩事呀, 比如啟動/關(guān)閉調(diào)試或自檢模式, 打印調(diào)試信息, 配置系統(tǒng)參數(shù), 傳輸文件等等, 也有相當多的朋友憑借自己出色的編程能力可以實現(xiàn)這些功能, 這里提出我的這個解決方案, 以作交流.

本平臺(xc_shell)具備以下性能特點:

  1. 大量主要代碼, 和具體硬件無關(guān), 移植性強,代碼文件少.

  2. 只有在處理用戶的輸入命令時, 才占用CPU資源, 且代碼可裁剪到1KB SRAM和4KB Flash;

  3. 用戶可以非常靈活的添加按模板編寫的命令腳本文件, 自定義擴張能力強.

  4. 支持操作系統(tǒng)和非操作系統(tǒng)兩種場景應用.

  5. 支持Ymodem文件傳輸協(xié)議

  6. 支持將Flash的扇區(qū)開辟為參數(shù)區(qū), 可實現(xiàn)本地/遠程升級。

  7. 實用Led燈信號管理, 可將65535虛擬信號燈選擇輸出到1個實體LED燈上, 調(diào)試時序和狀態(tài)非常有用

  8. 擁有基礎(chǔ)的LED管理, 調(diào)試模式設置, 命令幫助指令, 復位指令等基礎(chǔ)功能

功能越多設計會越復雜, 為了解釋清楚代碼, 先向大家解釋一下以上功能的基礎(chǔ)實現(xiàn)原理, 并提供一個最小的的源碼工程。


2xc_shell平臺介紹2.1 如何與硬件無關(guān)

類比Linux會發(fā)現(xiàn), 設備的硬件接口往往會被虛擬成一個文件(驅(qū)動), 而Linux內(nèi)核完全與硬件系統(tǒng)無任何字節(jié)關(guān)聯(lián), 不同平臺驅(qū)動不同而已, 故而本xc_shell的串口驅(qū)動也采用了相似的思路:

1) 串口驅(qū)動用一個結(jié)構(gòu)體描述, 這樣只需在xc_shell.c中用指針指向這個TTYx_HANDLE結(jié)構(gòu)體對象就可以將串口(tty)硬件與內(nèi)核聯(lián)系在一起, 聰明的朋友可能會想到, 假如我將帶網(wǎng)絡的開發(fā)板按此結(jié)構(gòu)體,虛擬一個TTY對象, 豈不是就可以實現(xiàn)一個網(wǎng)絡遠程控制臺了!  這點確實是可以的!

2) 當然諸如多TTY串口實現(xiàn)接口互換等, 都是一個指針和step2中的注入回調(diào)處理交換的問題。

3)用戶在使用api_TxdFrame或api_TxdByte時”bsp_ttyX.c“,會驅(qū)動具體MCU的串口將數(shù)據(jù)發(fā)送出去, 收到一幀數(shù)據(jù)后,若用戶設置了inj_RcvFrame回調(diào)處理方法,則會在中斷中執(zhí)行用戶的回調(diào)處理。

/*---------------------* 
*     指正函數(shù)定義
*----------------------*/
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);
 
//輸入數(shù)據(jù)幀,輸出邏輯
typedef bool    (*pbFun_Buffx)(void * pcBuff, uint16_t len );
typedef bool    (*pbFun_Bytex)(uint8_t * pcByte, uint16_t len );
/*---------------------* 
*    TTYx 句柄結(jié)構(gòu)
*----------------------*/
typedef struct TTYx_HANDLE_STRUCT 
{
    const char  * const name;       //驅(qū)動器名
    const uint16_t      rxSize;     //接收大小
    const uint16_t      txSize;     //發(fā)送大小
    
    //------------------------------------------------------
    //step1: 用戶可用API
    const pvFunWord     init;           //初始化.
    const pbFun_Bytex   api_TxdFrame;   //發(fā)送數(shù)據(jù)幀. (發(fā)送幀)
    const pbFunChar     api_TxdByte;    //發(fā)送數(shù)據(jù)字節(jié)
    
    //------------------------------------------------------
    //step2: 注入回調(diào)函數(shù)
    pbFun_Bytex         inj_RcvFrame;   //(ISR)接收數(shù)據(jù)幀. (接收幀)
    pvFunDummy          inj_TxdReady;   //(ISR)發(fā)送完畢回調(diào)
    
    //------------------------------------------------------
    //step3: 接收回調(diào)函數(shù)
    struct TTYx_HANDLE_STRUCT * pvNext; //連接到下一個指令 
}TTYx_HANDLE;
  1. 可注入的命令腳本(CLI)實現(xiàn)

命令CLI也是一個結(jié)構(gòu)體對象:

/*---------------------* 
*       CLI指令
*----------------------*/
typedef struct
{
 const char * const  pcCmdStr;     //指令字符串(只能為小寫字母)
 const char * const  pcHelpStr;     //指令描述,必須以:"rn結(jié)束". 比如:"help: Returns a listrn".
 const pFunHook      pxCmdHook;     //指向回調(diào)函數(shù)的指針,處理成功返回真否者返回0;
 uint8_t             ucExpParam;     //指令期望的參數(shù)個數(shù)
 const MEDIA_HANDLE *phStorage;      //指向存儲介質(zhì),沒有的話填充NULL  
}Cmd_Typedef_t;


各位朋友可能會使用到非常多的自定義CLI命令, 格式諸如這個網(wǎng)卡的命令:

const Cmd_Typedef_t CLI_WizMsg=
{
    //識別關(guān)鍵字
    .pcCmdStr   = "wiz",
    //幫助內(nèi)容
    .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",
 
 //處理函數(shù)
 .pxCmdHook  = &Shell_WIZ_Service,        //見實體函數(shù)
 
 //附帶數(shù)據(jù)
 .ucExpParam = 0,
 
  #ifdef SHELL_USE_YMODEM  
    //存儲介質(zhì)
    .phStorage  = NULL,
  #endif
};
/*---------------------* 
*       CLI 鏈表節(jié)
*----------------------*/
Cmd_List_t  WizList   = { &CLI_WizMsg,   NULL,}; //Shell指令的頭

如配置IP地址輸入“wiz wr ip 192.168.1.25


3環(huán)境準備
3.1 硬件開發(fā)環(huán)境
  • STM32F103系列開發(fā)板一塊, 帶USART1接口。
  • USB轉(zhuǎn)串口線一根。
3.2 軟件開發(fā)環(huán)境
  • MDK4.72或以上
  • SecureCRT串口超級終端
3.3 軟件配置

在xc_shell使用“/r/n”作為命令的結(jié)束符, 而SecureCRT按下Enter不是輸入“/r/n”故而需要按下圖設置:此外在《終端》/仿真/高級中選取【本地回顯】 


4代碼介紹
4.1 目錄結(jié)構(gòu)


□ XC_SHELL
├──┬── BSP_LIB    BSP庫,硬件相關(guān)驅(qū)動代買
│  ├──── bsp_ledx.c   基礎(chǔ)LED驅(qū)動      
│  └──── bsp_tty0.c   調(diào)試串口驅(qū)動
│
├──┬──MDK-ARM     工程文件
│  └──── Project.uvproj
│
├──┬──SHELL_CFG   SHELL配置頭文件
│  └──── user_eval.h
│
├──┬──SHELL_CORE  SHELL內(nèi)核文件
│  ├──── xc_shell.c   SHELL內(nèi)核文件
│  ├──── xc_ymodem.c  Ymodem傳輸協(xié)議(默認關(guān)閉,在xc_shell.h中啟動)
│  ├──── xc_iap.c     Flash的IAP操作,需要bsp_flash.c驅(qū)動支持
│  └──── shell_iap.c  shell的用戶腳本模板
│
├──┬──SHELL_INC   SHELL頭文件
│  ├──── bsp_type.h   驅(qū)動結(jié)構(gòu)定義
│  ├──── xc_shell.h   SHELL頭文件
│  └──── xconfig.h    硬件無關(guān)配置文件
│
├──┬──STM32F10x_StdPeriph_Lib_V3.5.0  STM32的標準外設庫
│  └──── ......
│
└──┬──USER        用戶文件    
   ├─ .....       
   └──── main.c        main文件
4.2 工程設置要點

1) 設置使用微庫:

圖片

2)配置包含和路徑,注意使用了“--c99”標準,如圖

圖片

3) 編譯工程,無錯誤警告后燒寫程序到開發(fā)板運行。

4.3 最終效果

按圖輸入一下指令,SHELL平臺會回復相關(guān)信息。其中輸入“l(fā)ed set 0=1”會將信號1分配到物理LED0上;輸入“l(fā)ed set 0=2”會將信號2分配到物理LED0上。這樣用戶編寫程序代碼時相當于擁有了超級多的LED信號可用,調(diào)試時序非常有用。 

圖片


5添加自己的腳本
5.1 源代碼示例

假設我要編寫一個自己的指令腳本, 來讀取MCU的關(guān)鍵信息,關(guān)鍵字為mcu, 文件命名為shell_mcu.c;當輸入“mcu rd 0”時顯示MCU的FLASH大小,輸入“mcu rd 1”時讀取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
*******************************************************************************/
 
/******************************************************************************
更新說明:
******************************************************************************/
 
/******************************************************************************
*********************************  編 譯 控 制 ********************************
******************************************************************************/
#define MCU_SHELL               //注釋掉時屏蔽iap shell功能
 
#include "xc_shell.h"       //Shell支持文件,含bool,uint8_t..以及串口數(shù)據(jù)收發(fā)操作
/******************************************************************************
********************************* 文件引用部分 ********************************
******************************************************************************/
/*---------------------* 
*     模塊驅(qū)動引用
*----------------------*/
//#include "net_w5500.h"
 
#ifdef MCU_SHELL
/******************************************************************************
********************************** Shell實例 **********************************
******************************************************************************/
/*---------------------* 
*      CLI指令服務
*----------------------*/
extern bool Shell_MCU_Service(void * pcBuff, uint16_t len );
 
/*---------------------* 
*       CLI 結(jié)構(gòu)
*----------------------*/
const Cmd_Typedef_t CLI_McuMsg=
{
    //識別關(guān)鍵字
    "mcu",
    
    //幫助內(nèi)容
 "[mcu contorls]rn"
 " mcu rd <d>tt- Read FLASH information.rn"
 "rn",
 
 //處理函數(shù)
 &Shell_MCU_Service,
 
 //附帶數(shù)據(jù)
 0,
  #ifdef SHELL_USE_YMODEM  
    //存儲介質(zhì)
    NULL,
  #endif
};
 
/*---------------------* 
*     CLI鏈表節(jié)(輸出)
*----------------------*/
Cmd_List_t  McuList  = {&CLI_McuMsg      ,NULL}; //IAP指令鏈表
 
/******************************************************************************
********************************* 函 數(shù) 聲 明 *********************************
******************************************************************************/
/******************************************************************************
/ 函數(shù)功能:STM32F103控制函數(shù)
/ 修改日期:2015/7/14 20:22:02
/ 輸入?yún)?shù):none
/ 輸出參數(shù):none
/ 使用說明:需要執(zhí)行約10s
******************************************************************************/
static bool FLASH_ioctl(uint8_t cmd,void * param)
{
    #define UID_ADDR            0x1FFFF7E0  //閃存容量寄存器,值對應KB單位
    #define MAC_ADDR            0x1FFFF7E8  //MCU的唯一ID號,共12個字節(jié)
    #define UID_SIZE            2           //UID的字節(jié)數(shù)
    #define MAC_SIZE            12          //MAC的字節(jié)數(shù)
 
    //step1: 檢查參數(shù)
    if(!param)return false;
        
    //step2: 處理數(shù)據(jù)
    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;
    }
}
 
/******************************************************************************
/ 函數(shù)功能:文件系統(tǒng)Shel指令處理
/ 修改日期:2013/9/10 19:04:15
/ 輸入?yún)?shù):輸入當前的程序版本
/ 輸出參數(shù):none
/ 使用說明: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  ************************************
******************************************************************************/
#endif
5.2 實現(xiàn)步驟

1) 將該文件添加到工程下。

2) 在main.c中用extern 引用McuList,源代碼為:

/*---------------------* 
*     Shell指令鏈表
*----------------------*/
extern Cmd_List_t  McuList;

3)在main.c初始化時添加:

//----------------------------------------------------------
//step1: shell初始化
shell_Init(115200,ledx_cfg);        //初始化shell接口
CLI_AddCmd(&McuList);     //添加模塊指令到鏈表

4)編譯工程文件。

5)下載到開發(fā)板運行即可在終端下看到新支持的CLI指令:

圖片

來源:https://dablelv.blog.csdn.net/article

*博客內(nèi)容為網(wǎng)友個人發(fā)布,僅代表博主個人觀點,如有侵權(quán)請聯(lián)系工作人員刪除。



關(guān)鍵詞: 單片機 Shell

相關(guān)推薦

技術(shù)專區(qū)

關(guān)閉