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

demi的头像
demi 发布于:周四, 11/28/2019 - 13:47 ,关键词:

在前两节中,我们讲解了如何在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的方式。

来源: 灵动MM32MCU

围观 48