本帖最后由 xld0932 于 2019-12-25 10:13 编辑

1.概述

在计算机科学中,Shell俗称壳(用来区别于核),是指为使用者提供操作界面的软件(命令解析器)。它类似于DOS下的command和后来的cmd.exe。它接收用户命令,然后调用相应的应用程序。如果在MCU开发调试过程中有一个Shell命令解析器,通过串口调试软件输入一些命令然后运行相对应的调试函数,将会为开发调试提供极大的便利和帮助。

目前有很多开源的应用在MCU上的Shell代码,本文主要介绍了基于GD32VF103V-EVAL硬件平台上如何移植Letter-shell,以及实现基于Letter-shell来辅助开发调试。Letter-shell可以到下载中心下载:https://mbb.eet-china.com/download/36059.html,当然也可以到GitHub上下载:https://github.com/NevermindZZT/letter-shell(速度有时候相当慢...)

2.移植前的准备

2.1.基于MCU的Shell是通过串口的数据交互来实现的,我们基于上次新建的工程,使用USART0来作为Shell的通讯串口,所以我们需要对串口的初始化做一些修改:初始化串口、配置串口中断、使能串口接收中断,代码如下:

/* gd32vf103v_eval.c */
  • /*!
  •     \brief      configure COM port
  •     \param[in]  com: COM on the board
  •       \arg        EVAL_COM0: COM0 on the board
  •       \arg        EVAL_COM1: COM1 on the board
  •     \param[out] none
  •     \retval     none
  • */
  • void gd_eval_com_init(uint32_t com)
  • {
  •     uint32_t com_id = 0U;
  •     if(EVAL_COM0 == com){
  •         com_id = 0U;
  •     }else if(EVAL_COM1 == com){
  •         com_id = 1U;
  •     }
  •    
  •     /* USART interrupt configuration */
  •     eclic_global_interrupt_enable();
  •     eclic_priority_group_set(ECLIC_PRIGROUP_LEVEL3_PRIO1);
  •     eclic_irq_enable(USART0_IRQn, 1, 0);
  •     /* enable GPIO clock */
  •     rcu_periph_clock_enable(COM_GPIO_CLK[com_id]);
  •     /* enable USART clock */
  •     rcu_periph_clock_enable(COM_CLK[com_id]);
  •     /* connect port to USARTx_Tx */
  •     gpio_init(COM_GPIO_PORT[com_id], GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, COM_TX_PIN[com_id]);
  •     /* connect port to USARTx_Rx */
  •     gpio_init(COM_GPIO_PORT[com_id], GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, COM_RX_PIN[com_id]);
  •     /* USART configure */
  •     usart_deinit(com);
  •     usart_baudrate_set(com, 115200U);
  •     usart_word_length_set(com, USART_WL_8BIT);
  •     usart_stop_bit_set(com, USART_STB_1BIT);
  •     usart_parity_config(com, USART_PM_NONE);
  •     usart_hardware_flow_rts_config(com, USART_RTS_DISABLE);
  •     usart_hardware_flow_cts_config(com, USART_CTS_DISABLE);
  •     usart_receive_config(com, USART_RECEIVE_ENABLE);
  •     usart_transmit_config(com, USART_TRANSMIT_ENABLE);
  •     usart_enable(com);
  •     /* enable USART RBNE interrupt */
  •     usart_interrupt_enable(USART0, USART_INT_RBNE);
  • }
  • /*!
  •     \brief      com send data
  •     \param[in]  com: COM on the board
  •       \arg        EVAL_COM0: COM0 on the board
  •       \arg        EVAL_COM1: COM1 on the board
  •       \arg                data
  •     \param[out] none
  •     \retval     none
  • */
  • void gd_eval_com_send(uint32_t com, uint8_t data)
  • {
  •         switch(com)
  •         {
  •         case EVAL_COM0:
  •             usart_data_transmit(USART0, data);
  •             while(usart_flag_get(USART0, USART_FLAG_TBE) == RESET){
  •             }
  •             break;
  •         case EVAL_COM1:
  •             usart_data_transmit(USART1, data);
  •             while(usart_flag_get(USART1, USART_FLAG_TBE) == RESET){
  •             }
  •             break;
  •         default:
  •                 break;
  •         }
  • }
  • 复制代码

    2.2.在工程中我们添加了queue消息队列来处理从USART0中断接收到的数据,关于消息队列的实现可以参考queue.c和queue.h两个文件,USART0中断处理代码如下:

    /* gd32vf103v_it.c */
  • #include "gd32vf103_it.h"
  • #include "gd32vf103v_eval.h"
  • #include "queue.h"
  • /*!
  •     \brief      this function handles USART RBNE interrupt request and TBE interrupt request
  •     \param[in]  none
  •     \param[out] none
  •     \retval     none
  • */
  • void USART0_IRQHandler(void)
  • {
  •     if(RESET != usart_interrupt_flag_get(USART0, USART_INT_FLAG_RBNE)){
  •         /* receive data */
  •         QUEUE_WRITE(QUEUE_DBG_RX_IDX, usart_data_receive(USART0));
  •     }
  • }
  • 复制代码

    2.3.在main.c中调用串口初始化函数及消息初始化函数,在while(1)中去调用消息处理函数,代码如下所示:

    /* main.c */
  • #include "gd32vf103.h"
  • #include "gd32vf103v_eval.h"
  • #include "message.h"
  • #include "systick.h"
  • #include <stdio.h>
  • void xInitSystem(void)
  • {
  •     gd_eval_led_init(LED1); gd_eval_led_off(LED1);
  •     gd_eval_led_init(LED2); gd_eval_led_off(LED2);
  •     gd_eval_led_init(LED3); gd_eval_led_off(LED3);
  •     gd_eval_led_init(LED4); gd_eval_led_off(LED4);
  •     gd_eval_com_init(EVAL_COM0);
  •     printf("GD32VF103V-EVAL(RISC-V)\r\n");
  •     MSG_Init();
  • }
  • /*!
  •     \brief      main function
  •     \param[in]  none
  •     \param[out] none
  •     \retval     none
  • */
  • int main(void)
  • {
  •         xInitSystem();
  •     while(1){
  •             MSG_Handler();
  •     }
  • }
  • 复制代码

    2.4.在message.c中实现串口输出函数、消息初始化函数和消息处理函数,最终达到的功能是:程序运行后,MCU的USART0打印输出通过串口工具输入给MCU的数据,至此移植前的准备就完成了。代码如下所示:

    #include "message.h"
  • #include "queue.h"
  • /**
  •   * @brief
  •   * @param  None
  •   * @retval None
  •   */
  • void SerialPutByte(const char ch)
  • {
  •         gd_eval_com_send(EVAL_COM0, ch);
  • }
  • /**
  •   * @brief
  •   * @param  None
  •   * @retval None
  •   */
  • void MSG_Init(void)
  • {
  •     QUEUE_INIT(QUEUE_DBG_RX_IDX);
  • }
  • /**
  •   * @brief  
  •   * @param  None
  •   * @retval None
  •   */
  • void MSG_Handler(void)
  • {
  •     if(QUEUE_EMPTY(QUEUE_DBG_RX_IDX) == 0)
  •     {
  • SerialPutByte(QUEUE_READ(QUEUE_DBG_RX_IDX));
  •     }
  • }
  • 复制代码

    3.移植Letter-shell

    3.1.Letter-shell是一个体积极小的嵌入式shell,功能如下:


    • 命令自动补全,使用tab键补全命令
    • 命令长帮助,使用help [command]显示命令长帮助
    • 长帮助补全,输入命令后双击tab键补全命令长帮助指令
    • 快捷键,支持使用Ctrl + A~Z组合按键直接调用函数
    • shell变量,支持在shell中查看和修改变量值,支持变量作为命令参数
    • 登录密码,支持在shell中使用登录密码,支持超时自动锁定

    3.2.在message.c中定义shell对象

    SHELL_TypeDef shell;
    复制代码

    3.3.调用shellInit进行初始化

    void MSG_Init(void)
  • {
  •     QUEUE_INIT(QUEUE_DBG_RX_IDX);
  •     shell.write = SerialPutByte;
  •     shellInit(&shell);
  • }
  • 复制代码

    3.4.我们使用的是裸机开发环境,所以在接收到数据时,调用shellHandler就可以了

    void MSG_Handler(void)
  • {
  •     if(QUEUE_EMPTY(QUEUE_DBG_RX_IDX) == 0)
  •     {
  •             shellHandler(&shell, QUEUE_READ(QUEUE_DBG_RX_IDX));
  •     }
  • }
  • 复制代码

    3.5.接下来在shell_cfg.h中需要对shell做一些配置

    #ifndef __SHELL_CFG_H__
  • #define __SHELL_CFG_H__
  • #define     SHELL_USING_TASK            0
  • #define     SHELL_TASK_WHILE            1
  • #define     SHELL_USING_CMD_EXPORT      0
  • #define     SHELL_USING_VAR             0
  • #define     SHELL_DISPLAY_RETURN        0
  • #define     SHELL_AUTO_PRASE            1
  • #define     SHELL_LONG_HELP             1
  • #define     SHELL_COMMAND_MAX_LENGTH    50
  • #define     SHELL_PARAMETER_MAX_NUMBER  8
  • #define     SHELL_HISTORY_MAX_NUMBER    5
  • #define     SHELL_DOUBLE_CLICK_TIME     200
  • #define     SHELL_MAX_NUMBER            5
  • #define     SHELL_PRINT_BUFFER          128
  • #define     SHELL_GET_TICK()            0
  • #define     SHELL_DEFAULT_COMMAND       "\r\nMBB & GD32 RISC-V >>"
  • #define     SHELL_USING_AUTH             0
  • #define     SHELL_USER_PASSWORD         "letter"
  • #define     SHELL_LOCK_TIMEOUT          5 * 60 * 1000
  • #endif
  • 复制代码

    3.6.修改shell.c中的TEXT_INFO信息如下

    static const char *shellText[] =
  • {
  •     [TEXT_INFO]      = "\r\n\r\n"
  •                        "***********************************************************\r\n"
  •                        "*                   (C) COPYRIGHT 2019                    *\r\n"
  •                        "*                MBB & GD32 RISC-V  v"SHELL_VERSION"                *\r\n"
  •                        "*               Build: "__DATE__" "__TIME__"               *\r\n"
  •                        "***********************************************************\r\n",
  •     [TEXT_PWD_HINT]  = "\r\nPlease input password:",
  •     [TEXT_PWD_RIGHT] = "\r\npassword confirm success.\r\n",
  •     [TEXT_PWD_ERROR] = "\r\npassword confirm failed.\r\n",
  •     [TEXT_FUN_LIST]  = "\r\nCOMMAND LIST:\r\n\r\n",
  •     [TEXT_VAR_LIST]  = "\r\nVARIABLE LIST:\r\n\r\n",
  •     [TEXT_CMD_NONE]  = "Command not found\r\n",
  •     [TEXT_CMD_TOO_LONG] = "\r\nWarnig: Command is too long\r\n",
  •     [TEXT_READ_NOT_DEF] = "error: shell.read must be defined\r\n",
  • };
  • 复制代码

    3.7.在shell.h中将SHELL_TypeDef结构体当中的status修改为不使用位域的定义方式,在KEIL中支持位域操作,但在NucleiStudio IDE中不支持,也正是这个问题,挖了半天坑才搞出来^^,定义如下:

    typedef struct
  • {
  •     char                    *command;
  •     char                         buffer[SHELL_COMMAND_MAX_LENGTH];
  •     unsigned short  length;
  •     unsigned short  cursor;
  •     char               *param[SHELL_PARAMETER_MAX_NUMBER];
  •     char                     history[SHELL_HISTORY_MAX_NUMBER][SHELL_COMMAND_MAX_LENGTH];
  •     unsigned short         historyCount;
  •     short                         historyFlag;
  •     short                         historyOffset;
  •     SHELL_CommandTypeDef *commandBase;
  •     unsigned short         commandNumber;
  • #if SHELL_USING_VAR == 1
  •     SHELL_VaribaleTypeDef *variableBase;
  •     unsigned short variableNumber;
  • #endif
  •     int                         keyFuncBase;
  •     unsigned short         keyFuncNumber;
  •     struct
  •     {
  •         char inputMode;
  •         char tabFlag;
  •         char authFlag;
  •     } status;
  •     unsigned char         isActive;
  •     shellRead                 read;
  •     shellWrite                 write;
  • #if SHELL_LONG_HELP == 1 || (SHELL_USING_AUTH && SHELL_LOCK_TIMEOUT > 0)
  •     int                         activeTime;
  • #endif
  • }SHELL_TypeDef;
  • 复制代码

    3.8.至此Letter-shell就移植完成了,程序编译烧录后,就可以看到最小配置的shell输出了,可以通过输入help查看,当然直接按下TAB按键也可以看到支持的shell命令列表。

    4.Letter-shell应用

    4.1.在message.c中通过参数的传递实现对所有LED控制的函数

    void LED_TurnOnOFF(int idx, int en)
  • {
  •         switch(idx)
  •         {
  •         case 1:
  •                 if(en) gd_eval_led_on(LED1);
  •                 else   gd_eval_led_off(LED1);
  •                 break;
  •         case 2:
  •                 if(en) gd_eval_led_on(LED2);
  •                 else   gd_eval_led_off(LED2);
  •                 break;
  •         case 3:
  •                 if(en) gd_eval_led_on(LED3);
  •                 else   gd_eval_led_off(LED3);
  •                 break;
  •         case 4:
  •                 if(en) gd_eval_led_on(LED4);
  •                 else   gd_eval_led_off(LED4);
  •                 break;
  •         default:
  •                 break;
  •         }
  • }
  • 复制代码

    4.2.在shell.c中将LED_TurnOnOFF添加到shellDefaultCommandList命令列表当中去

    #if SHELL_USING_CMD_EXPORT != 1
  • extern void LED_TurnOnOFF(int idx, int en);
  • const SHELL_CommandTypeDef shellDefaultCommandList[] =
  • {
  •     SHELL_CMD_ITEM_EX(help, shellHelp, command help, help [command] --show help info of command),
  • #if SHELL_USING_VAR == 1
  •     SHELL_CMD_ITEM(vars, shellListVariables, show vars),
  •     SHELL_CMD_ITEM_EX(setVar, shellSetVariable, set var, setVar $[var] [value]),
  • #endif /** SHELL_USING_VAR == 1 */
  •     SHELL_CMD_ITEM(cls, shellClear, clear command line),
  •         SHELL_CMD_ITEM(led, LED_TurnOnOFF, turn LEDn on or off),
  • };
  • #if SHELL_USING_VAR == 1
  • const SHELL_VaribaleTypeDef shellDefaultVariableList[] =
  • {
  • };
  • #endif /** SHELL_USING_VAR == 1 */
  • #endif /** SHELL_USING_CMD_EXPORT != 1 */
  • 复制代码

    4.3.至此一个控制LED的shell应用就完成了,我们可以在串口调试助手的界面输入"led 1 1"来点亮LED1,又或者输入“led 1 0”来关闭LED1

    2.png

    5.工程代码

    GD32VF103V-EVAL_20191225.rar (698.35 KB, 下载次数: 6)

    全部回复 1
    • 81 主题
    • 204 帖子
    • 586 积分
    身份:LV3 中级技术员
    E币:836
    哇哦~感谢楼主的分享。github最近老卡我还以是我一个人这样
    回复楼主
    您需要登录后才可以评论 登录 立即注册