控制/MCU
在前两节中,我们讲解了如何在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);
}
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.keyCode == data) {
if (base.keyFunction) {
base.keyFunction(shell);
}
keyDefFind = 1;
}
}
if (keyDefFind == 0)
{
for (short i = 0;
i 《 sizeof(shellDefaultKeyFunctionList) / sizeof(SHELL_KeyFunctionDef);
i++)
{
if (shellDefaultKeyFunctionList.keyCode == data) {
if (shellDefaultKeyFunctionList.keyFunction) {
shellDefaultKeyFunctionList.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的方式。
推荐阅读:shell调试教程之MM32 MCU的J-Link RTT方式实现shell功能
推荐阅读:shell调试教程之如何在MM32 MCU上使用shell来辅助开发
全部0条评论
快来发表一下你的评论吧 !