本帖最后由 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对象
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 == 1const 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
5.工程代码
GD32VF103V-EVAL_20191225.rar
(698.35 KB, 下载次数: 6)