Nuoeriris · 2020年04月26日

基于灵动MM32 MCU的shell调试教程(三)

在前两节中,我们讲解了如何在MM32 MCU上使用shell来辅助开发,分别介绍的是通过串口方式和J-Link RTT方式的shell,本次课程我们分析源码来讲解shell实现原理。

软件资源如下:
以下为函数初始化配置及相关全局变量定义内容,代码如下:

typedef struct
{
char *command; // shell命令提示符
char buffer[SHELL_COMMAND_MAX_LENGTH]; // shell命令缓冲buffer
unsigned short length; // shell命令长度大小
unsigned short cursor; // shell光标位置偏移
char *param[SHELL_PARAMETER_MAX_NUMBER]; // shell参数变量
char history[SHELL_HISTORY_MAX_NUMBER][SHELL_COMMAND_MAX_LENGTH]; // 历史记录区域
unsigned short historyCount; // 历史记录数量
short historyFlag; // 当前记录偏移位置
short historyOffset; // 历史记录偏移大小
SHELL_CommandTypeDef *commandBase; // 命令表基地址
unsigned short commandNumber; // 命令数量
int keyFuncBase; // 按键响应表基地址
unsigned short keyFuncNumber; // 按键响应数量
SHELL_InputMode status; // shell输入状态
unsigned char isActive; //是不是当前激活的shell 
shellRead read; // shell读函数接口
shellWrite write; // shell写函数接口
}SHELL_TypeDef;

如上所示,为对象的定义接口,具体说明看注释,我们需要关注的是shell的读写接口。

void shellInit(SHELL_TypeDef *shell)
{
shellDisplay(shell, "\r\n\r\n");
shellDisplay(shell, "+=========================================================+\r\n");
shellDisplay(shell, "| (C) COPYRIGHT 2019 MindMotion            |\r\n");
shellDisplay(shell, "| shell v"SHELL_VERSION"                    |\r\n");
shellDisplay(shell, "| Build: "__DATE__" "__TIME__"               |\r\n");
shellDisplay(shell, "+=========================================================+\r\n");
shell->length = 0;
shell->cursor = 0;
shell->historyCount = 0;
shell->historyFlag = 0;
shell->historyOffset = 0;
shell->status = SHELL_IN_NORMAL;
shell->command = SHELL_DEFAULT_COMMAND;
shell->isActive = 0;
shellAdd(shell);
shellDisplay(shell, shell->command);
 
#if defined(__CC_ARM) || (defined(__ARMCC_VERSION) && __ARMCC_VERSION >= 6000000)
extern const unsigned int shellCommand$$Base;
extern const unsigned int shellCommand$$Limit;
extern const unsigned int shellVariable$$Base;
extern const unsigned int shellVariable$$Limit;
 
shell->commandBase = (SHELL_CommandTypeDef *)(&shellCommand$$Base);
shell->commandNumber = ((unsigned int)(&shellCommand$$Limit)
- (unsigned int)(&shellCommand$$Base))
/ sizeof(SHELL_CommandTypeDef);
#endif
}

上述代码void shellInit(SHELL_TypeDef * shell) 用来初始化shell对象,首先打印shell界面,然后对shell对象进行初始化为默认状态,然后给shell命令表指定区域和数量。

对于shell输入处理,需要分两种类型判断,一个是正常的字母按键,如A、B、C、D等,一个是功能按键,如方向键等。下面给出两种类型处理代码。

// shell ansi按键处理函数
void shellAnsi(SHELL_TypeDef *shell, char data)
{
switch ((unsigned char)(shell->status))
{
case SHELL_ANSI_CSI:
switch (data)
{
case 0x41: // 键盘方向键向上键
shellHistory(shell, 0);
break;  
        
case 0x42: // 键盘方向键向下键
shellHistory(shell, 1);
break;
 
case 0x43: // 键盘方向键向右键
if (shell->cursor < shell->length)
{
shellDisplayByte(shell, shell->buffer[shell->cursor]);
shell->cursor++;
}
break;
 
case 0x44: // 键盘方向键向左键
if (shell->cursor > 0)
{
shellDisplayByte(shell, '\b');
shell->cursor--;
}
break;
 
default:
break;
}
shell->status = SHELL_IN_NORMAL;
break;
 
case SHELL_ANSI_ESC:
if (data == 0x5B)
{
shell->status = SHELL_ANSI_CSI;
}
else
{
shell->status = SHELL_IN_NORMAL;
}
break;
default:
break;
}
}
 

上述void shellAnsi(SHELL_TypeDef * shell, char data)函数为shellAnsi处理。

//shell正常按键处理函数
static void shellNormal(SHELL_TypeDef *shell, char data)
{
if (data == 0)
{
return;
}
if (shell->length < SHELL_COMMAND_MAX_LENGTH - 1)
{
if (shell->length == shell->cursor)
{
shell->buffer[shell->length++] = data;
shell->cursor++;
shellDisplayByte(shell, data);
}
else
{
for (short i = shell->length - shell->cursor; i > 0; i--)
{
shell->buffer[shell->cursor + i] = shell->buffer[shell->cursor + i - 1];
}
shell->buffer[shell->cursor++] = data;
shell->buffer[++shell->length] = 0;
for (short i = shell->cursor - 1; i < shell->length; i++)
{
shellDisplayByte(shell, shell->buffer[i]);
}
for (short i = shell->length - shell->cursor; i > 0; i--)
{
shellDisplayByte(shell, '\b');
}
}
}
else
{
shellDisplay(shell, "\r\nWarnig: Command is too long\r\n");
shellDisplay(shell, shell->command);
shellDisplay(shell, shell->buffer);
shell->cursor = shell->length;
}
}

基于上述的两个类型代码,即可封装得到shell的处理代码,如下所示:

//shell处理
void shellHandler(SHELL_TypeDef *shell, char data) //shell处理函数
{
if (shell->status == SHELL_IN_NORMAL) //shell工作在正常模式
{
char keyDefFind = 0;
SHELL_KeyFunctionDef *base = (SHELL_KeyFunctionDef *)shell->keyFuncBase;
for (short i = 0; i < shell->keyFuncNumber; i++)
{
if (base[i].keyCode == data) {
if (base[i].keyFunction) {
base[i].keyFunction(shell);
}
keyDefFind = 1;
}
}
if (keyDefFind == 0)
{
for (short i = 0; 
i < sizeof(shellDefaultKeyFunctionList) / sizeof(SHELL_KeyFunctionDef);
i++)
{
if (shellDefaultKeyFunctionList[i].keyCode == data) {
if (shellDefaultKeyFunctionList[i].keyFunction) {
shellDefaultKeyFunctionList[i].keyFunction(shell);
}
keyDefFind = 1;
}
}
}
if (keyDefFind == 0)
{
shellNormal(shell, data);
}
}
else
{
shellAnsi(shell, data);//shell ansi处理
}
}

以上就是shell的全部介绍,融合两节的代码,如下:

int main(void)
{
int GetKey;
delay_init();
LED_Init();
uart_nvic_init(115200);  //串口初始化为115200
 
//uart_shell.read = shellRead;
uart_shell.write = Uart_PutChar;
shellInit(&uart_shell);
 
/* 配置通道 0,上行配置*/
SEGGER_RTT_ConfigUpBuffer(0,"RTTUP",NULL,0,SEGGER_RTT_MODE_NO_BLOCK_SKIP);
/* 配置通道 0,下行配置*/
SEGGER_RTT_ConfigDownBuffer(0,"RTTDOWN",NULL,0,SEGGER_RTT_MODE_NO_BLOCK_SKIP);
 
//rtt_shell.read = shellRead;
rtt_shell.write = RTT_PutChar;
shellInit(&rtt_shell);
   
while (1)
{
if (SEGGER_RTT_HasKey())
{
GetKey = SEGGER_RTT_GetKey();
shellHandler(&rtt_shell, GetKey);
}
}
}

通过上述代码,可以同时支持串口方式和J-Link RTT模式的shell,方便用户根据自己实际条件来辅助调试代码。

以上实现方式可能会影响MCU的运行效率,我们在本教程中优先考虑提供实现shell的方式。

更多信息请访问:www.mm32mcu.com,微信公众号请搜索“灵动MM32MCU”,QQ技术讨论群:294016370。

推荐阅读
关注数
6143
内容数
276
灵动MM32 MCU相关技术知识,欢迎关注~
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息