本帖最后由 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的通讯串口,所以我们需要对串口的初始化做一些修改:初始化串口、配置串口中断、使能串口接收中断,代码如下:

  1. /* gd32vf103v_eval.c */
  2. /*!
  3.     \brief      configure COM port
  4.     \param[in]  com: COM on the board
  5.       \arg        EVAL_COM0: COM0 on the board
  6.       \arg        EVAL_COM1: COM1 on the board
  7.     \param[out] none
  8.     \retval     none
  9. */
  10. void gd_eval_com_init(uint32_t com)
  11. {
  12.     uint32_t com_id = 0U;
  13.     if(EVAL_COM0 == com){
  14.         com_id = 0U;
  15.     }else if(EVAL_COM1 == com){
  16.         com_id = 1U;
  17.     }
  18.    
  19.     /* USART interrupt configuration */
  20.     eclic_global_interrupt_enable();
  21.     eclic_priority_group_set(ECLIC_PRIGROUP_LEVEL3_PRIO1);
  22.     eclic_irq_enable(USART0_IRQn, 1, 0);
  23.     /* enable GPIO clock */
  24.     rcu_periph_clock_enable(COM_GPIO_CLK[com_id]);
  25.     /* enable USART clock */
  26.     rcu_periph_clock_enable(COM_CLK[com_id]);
  27.     /* connect port to USARTx_Tx */
  28.     gpio_init(COM_GPIO_PORT[com_id], GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, COM_TX_PIN[com_id]);
  29.     /* connect port to USARTx_Rx */
  30.     gpio_init(COM_GPIO_PORT[com_id], GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, COM_RX_PIN[com_id]);
  31.     /* USART configure */
  32.     usart_deinit(com);
  33.     usart_baudrate_set(com, 115200U);
  34.     usart_word_length_set(com, USART_WL_8BIT);
  35.     usart_stop_bit_set(com, USART_STB_1BIT);
  36.     usart_parity_config(com, USART_PM_NONE);
  37.     usart_hardware_flow_rts_config(com, USART_RTS_DISABLE);
  38.     usart_hardware_flow_cts_config(com, USART_CTS_DISABLE);
  39.     usart_receive_config(com, USART_RECEIVE_ENABLE);
  40.     usart_transmit_config(com, USART_TRANSMIT_ENABLE);
  41.     usart_enable(com);
  42.     /* enable USART RBNE interrupt */
  43.     usart_interrupt_enable(USART0, USART_INT_RBNE);
  44. }
  45. /*!
  46.     \brief      com send data
  47.     \param[in]  com: COM on the board
  48.       \arg        EVAL_COM0: COM0 on the board
  49.       \arg        EVAL_COM1: COM1 on the board
  50.       \arg                data
  51.     \param[out] none
  52.     \retval     none
  53. */
  54. void gd_eval_com_send(uint32_t com, uint8_t data)
  55. {
  56.         switch(com)
  57.         {
  58.         case EVAL_COM0:
  59.             usart_data_transmit(USART0, data);
  60.             while(usart_flag_get(USART0, USART_FLAG_TBE) == RESET){
  61.             }
  62.             break;
  63.         case EVAL_COM1:
  64.             usart_data_transmit(USART1, data);
  65.             while(usart_flag_get(USART1, USART_FLAG_TBE) == RESET){
  66.             }
  67.             break;
  68.         default:
  69.                 break;
  70.         }
  71. }

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

  1. /* gd32vf103v_it.c */
  2. #include "gd32vf103_it.h"
  3. #include "gd32vf103v_eval.h"
  4. #include "queue.h"
  5. /*!
  6.     \brief      this function handles USART RBNE interrupt request and TBE interrupt request
  7.     \param[in]  none
  8.     \param[out] none
  9.     \retval     none
  10. */
  11. void USART0_IRQHandler(void)
  12. {
  13.     if(RESET != usart_interrupt_flag_get(USART0, USART_INT_FLAG_RBNE)){
  14.         /* receive data */
  15.         QUEUE_WRITE(QUEUE_DBG_RX_IDX, usart_data_receive(USART0));
  16.     }
  17. }

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

  1. /* main.c */
  2. #include "gd32vf103.h"
  3. #include "gd32vf103v_eval.h"
  4. #include "message.h"
  5. #include "systick.h"
  6. #include <stdio.h>
  7. void xInitSystem(void)
  8. {
  9.     gd_eval_led_init(LED1); gd_eval_led_off(LED1);
  10.     gd_eval_led_init(LED2); gd_eval_led_off(LED2);
  11.     gd_eval_led_init(LED3); gd_eval_led_off(LED3);
  12.     gd_eval_led_init(LED4); gd_eval_led_off(LED4);
  13.     gd_eval_com_init(EVAL_COM0);
  14.     printf("GD32VF103V-EVAL(RISC-V)\r\n");
  15.     MSG_Init();
  16. }
  17. /*!
  18.     \brief      main function
  19.     \param[in]  none
  20.     \param[out] none
  21.     \retval     none
  22. */
  23. int main(void)
  24. {
  25.         xInitSystem();
  26.     while(1){
  27.             MSG_Handler();
  28.     }
  29. }

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

  1. #include "message.h"
  2. #include "queue.h"
  3. /**
  4.   * @brief
  5.   * @param  None
  6.   * @retval None
  7.   */
  8. void SerialPutByte(const char ch)
  9. {
  10.         gd_eval_com_send(EVAL_COM0, ch);
  11. }
  12. /**
  13.   * @brief
  14.   * @param  None
  15.   * @retval None
  16.   */
  17. void MSG_Init(void)
  18. {
  19.     QUEUE_INIT(QUEUE_DBG_RX_IDX);
  20. }
  21. /**
  22.   * @brief  
  23.   * @param  None
  24.   * @retval None
  25.   */
  26. void MSG_Handler(void)
  27. {
  28.     if(QUEUE_EMPTY(QUEUE_DBG_RX_IDX) == 0)
  29.     {
  30. SerialPutByte(QUEUE_READ(QUEUE_DBG_RX_IDX));
  31.     }
  32. }

3.移植Letter-shell

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


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

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

  1. SHELL_TypeDef shell;

3.3.调用shellInit进行初始化

  1. void MSG_Init(void)
  2. {
  3.     QUEUE_INIT(QUEUE_DBG_RX_IDX);
  4.     shell.write = SerialPutByte;
  5.     shellInit(&shell);
  6. }

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

  1. void MSG_Handler(void)
  2. {
  3.     if(QUEUE_EMPTY(QUEUE_DBG_RX_IDX) == 0)
  4.     {
  5.             shellHandler(&shell, QUEUE_READ(QUEUE_DBG_RX_IDX));
  6.     }
  7. }

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

  1. #ifndef __SHELL_CFG_H__
  2. #define __SHELL_CFG_H__
  3. #define     SHELL_USING_TASK            0
  4. #define     SHELL_TASK_WHILE            1
  5. #define     SHELL_USING_CMD_EXPORT      0
  6. #define     SHELL_USING_VAR             0
  7. #define     SHELL_DISPLAY_RETURN        0
  8. #define     SHELL_AUTO_PRASE            1
  9. #define     SHELL_LONG_HELP             1
  10. #define     SHELL_COMMAND_MAX_LENGTH    50
  11. #define     SHELL_PARAMETER_MAX_NUMBER  8
  12. #define     SHELL_HISTORY_MAX_NUMBER    5
  13. #define     SHELL_DOUBLE_CLICK_TIME     200
  14. #define     SHELL_MAX_NUMBER            5
  15. #define     SHELL_PRINT_BUFFER          128
  16. #define     SHELL_GET_TICK()            0
  17. #define     SHELL_DEFAULT_COMMAND       "\r\nMBB & GD32 RISC-V >>"
  18. #define     SHELL_USING_AUTH             0
  19. #define     SHELL_USER_PASSWORD         "letter"
  20. #define     SHELL_LOCK_TIMEOUT          5 * 60 * 1000
  21. #endif

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

  1. static const char *shellText[] =
  2. {
  3.     [TEXT_INFO]      = "\r\n\r\n"
  4.                        "***********************************************************\r\n"
  5.                        "*                   (C) COPYRIGHT 2019                    *\r\n"
  6.                        "*                MBB & GD32 RISC-V  v"SHELL_VERSION"                *\r\n"
  7.                        "*               Build: "__DATE__" "__TIME__"               *\r\n"
  8.                        "***********************************************************\r\n",
  9.     [TEXT_PWD_HINT]  = "\r\nPlease input password:",
  10.     [TEXT_PWD_RIGHT] = "\r\npassword confirm success.\r\n",
  11.     [TEXT_PWD_ERROR] = "\r\npassword confirm failed.\r\n",
  12.     [TEXT_FUN_LIST]  = "\r\nCOMMAND LIST:\r\n\r\n",
  13.     [TEXT_VAR_LIST]  = "\r\nVARIABLE LIST:\r\n\r\n",
  14.     [TEXT_CMD_NONE]  = "Command not found\r\n",
  15.     [TEXT_CMD_TOO_LONG] = "\r\nWarnig: Command is too long\r\n",
  16.     [TEXT_READ_NOT_DEF] = "error: shell.read must be defined\r\n",
  17. };

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

  1. typedef struct
  2. {
  3.     char                    *command;
  4.     char                         buffer[SHELL_COMMAND_MAX_LENGTH];
  5.     unsigned short  length;
  6.     unsigned short  cursor;
  7.     char               *param[SHELL_PARAMETER_MAX_NUMBER];
  8.     char                     history[SHELL_HISTORY_MAX_NUMBER][SHELL_COMMAND_MAX_LENGTH];
  9.     unsigned short         historyCount;
  10.     short                         historyFlag;
  11.     short                         historyOffset;
  12.     SHELL_CommandTypeDef *commandBase;
  13.     unsigned short         commandNumber;
  14. #if SHELL_USING_VAR == 1
  15.     SHELL_VaribaleTypeDef *variableBase;
  16.     unsigned short variableNumber;
  17. #endif
  18.     int                         keyFuncBase;
  19.     unsigned short         keyFuncNumber;
  20.     struct
  21.     {
  22.         char inputMode;
  23.         char tabFlag;
  24.         char authFlag;
  25.     } status;
  26.     unsigned char         isActive;
  27.     shellRead                 read;
  28.     shellWrite                 write;
  29. #if SHELL_LONG_HELP == 1 || (SHELL_USING_AUTH && SHELL_LOCK_TIMEOUT > 0)
  30.     int                         activeTime;
  31. #endif
  32. }SHELL_TypeDef;

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

4.Letter-shell应用

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

  1. void LED_TurnOnOFF(int idx, int en)
  2. {
  3.         switch(idx)
  4.         {
  5.         case 1:
  6.                 if(en) gd_eval_led_on(LED1);
  7.                 else   gd_eval_led_off(LED1);
  8.                 break;
  9.         case 2:
  10.                 if(en) gd_eval_led_on(LED2);
  11.                 else   gd_eval_led_off(LED2);
  12.                 break;
  13.         case 3:
  14.                 if(en) gd_eval_led_on(LED3);
  15.                 else   gd_eval_led_off(LED3);
  16.                 break;
  17.         case 4:
  18.                 if(en) gd_eval_led_on(LED4);
  19.                 else   gd_eval_led_off(LED4);
  20.                 break;
  21.         default:
  22.                 break;
  23.         }
  24. }

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

  1. #if SHELL_USING_CMD_EXPORT != 1
  2. extern void LED_TurnOnOFF(int idx, int en);
  3. const SHELL_CommandTypeDef shellDefaultCommandList[] =
  4. {
  5.     SHELL_CMD_ITEM_EX(help, shellHelp, command help, help [command] --show help info of command),
  6. #if SHELL_USING_VAR == 1
  7.     SHELL_CMD_ITEM(vars, shellListVariables, show vars),
  8.     SHELL_CMD_ITEM_EX(setVar, shellSetVariable, set var, setVar $[var] [value]),
  9. #endif /** SHELL_USING_VAR == 1 */
  10.     SHELL_CMD_ITEM(cls, shellClear, clear command line),
  11.         SHELL_CMD_ITEM(led, LED_TurnOnOFF, turn LEDn on or off),
  12. };
  13. #if SHELL_USING_VAR == 1
  14. const SHELL_VaribaleTypeDef shellDefaultVariableList[] =
  15. {
  16. };
  17. #endif /** SHELL_USING_VAR == 1 */
  18. #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最近老卡我还以是我一个人这样
回复楼主
您需要登录后才可以评论 登录 立即注册