在单片机中实现类似shell的命令行工具
如果在单片机编程过程中有一个类似linux的shell命令工具可以通过串口调试助手输入命令然后运行一些调试函数,将会为编程提供极大的帮助。
本文旨在提供一个十分便于移植和十分简单有效的shell解决方法。
在移植时只需提供shellGetChar
函数和shellSendChar
函数,函数编写尽量简单、高效甚至可以简单的移植到类似51单片机这样的8位处理器上。
首先编写shell最基础的东西,从串口获取到一行字符串,其特点如下。
- 由’\r\n’ 或者’\n’结尾。
- 在超级终端上输入字符需要再返回给终端
- 需要对退格和删除键进行特殊处理
#include "shell.h"
char shellLine[100] = {SHELL_LINE_MAX_LEN}; /*用于存储从串口接收到的字符串*/
char *shellParam[SHELL_LINE_MAX_LEN] = {0}; /*用于存储接收到的参数(包括命令名字)*/
extern T_ShellCmd* sysCmd[];
uint8_t shellGetChar(char *recCh)
{
/*需要自己提供获取一个字符的函数,获取到字符返回1,反之返回0 */
return scanf("%c",recCh);
}
void shellSendChar(char ch)
{
printf("%c",ch);/*需要提供发送一个字符的函数*/
}
/*
*用于从串口获取一条以回车换行结尾的命令
*/
uint8_t shellGetOneLine(char *line, uint8_t maxLen)
{
char getChar;
static uint8_t count=0; /*用于记录除特殊字符外的其他有效字符的数量*/
if(shellGetChar(&getChar))
{
if(count>=maxLen) /*长度超限*/
{
count = 0; /*清零计数器以便后续使用*/
return 1; /*返回有效标志*/
}
line[count] = getChar; /*记录数据*/
switch(getChar)
{
case 0x08:
case 0x7F: /*退格键或者删除键*/
{
if(count>0)
{
count--; /*删除上一个接收到的字符*/
}
}break;
case '\r':
case '\n': /*接收到回车换行,证明已经收到一个完整的命令*/
{
line[count] = '\0'; /*添加字符串结束符,刚好可以去掉'\r'或者'\n'*/
count = 0; /*清零计数器以便后续使用*/
return 1; /*返回有效标志*/
}break;
default:
count++;
}
shellSendChar(getChar); /*把收到的字符输出到串口*/
}
return 0;
}
处理好接收一行命令的函数后,再编写函数将输入的一行字符串转换成命令名和参数,需要使用sting.h 中的strtok函数,使用此函数我们可以简便的提取到相关的参数字符。
/*从命令字符串中解析到命令和其参数
* 获取到的paramArry[0]为要允许的命令名
* 其他的为命令参数
* 返回值为获取到的参数的个数(包括一个命令名)
*/
uint8_t shellGetParam(char* line, char *paramArry[], uint8_t arryLen)
{
uint8_t i,ret;
char *ptr = NULL;
ptr = strtok(line, " ");
for(i=0; ptr!=NULL &&i<arryLen; i++)
{
paramArry[i] = ptr;
ptr = strtok(NULL, ",");
}
ret = i;
return ret;
}
现在我们可以从串口获取到一节命令字符串,并且可以解析得到命令的名字和参数,接下来就是要去通过命令名字去查找我们预先设置好的命令数组,找到相应的命令函数,然后运行即可。
在我们学习C语言的时候,我们有时候会发现main函数会是这个样子,是带参数的。百科上面的解释argc argv百科
int main(int argc, char* argv[])
{
}
因此我定义的命令函数的原型为
typedef int (*T_ShellFun)(int argc, char*argv[]);
还需要去设计一个结构体去将命令名和命令函数连接起来
typedef struct
{
char* name; /*命令的名字*/
char* help; /*帮助描述*/
T_ShellFun fun; /*命令函数*/
}T_ShellCmd;
这里并没有去定义命了的参数个数,在解析完用户输入,运行命令函数时也就不会去检查用户输入的参数的个数。以便实现以下类似功能
ls
不输入参数显示当前文件夹下的所有文件和文件夹
ls \user\
输入了参数,显示user目录下的文件和文件夹
定义好了命令描述结构体,和命令函数模板,就可以定义命令了
extern T_ShellCmd *sysCmd[];
int helpCmdFun(int argc, char*argv[]) /*命令函数*/
{
uint8_t i;
for(i=0; sysCmd[i]; i++)
{
printf("%-15s %s\r\n",sysCmd[i]->name, sysCmd[i]->help);
}
}
T_ShellCmd helpCmd= /*命令描述*/
{
.name = "help",
.help = "show all cmd list",
.fun = helpCmdFun
};
/*例程,此例程序可以接收两个参数,int a,float b*/
int paramTestCmdFun(int argc, char *argv[]) /*命令函数*/
{
uint8_t reti,retf;
int vali;
float valf;
printf("get param num %d\r\n", argc); /*如果用户输入的参数不够cmd函数使用,将传入一个默认的地址,其内容为 '\0' */
vali = shellStr2Int(argv[0], &reti); /*提供的字符串转int函数*/
valf = shellStr2Float(argv[1], &retf); /**/
printf("int[%d]:%d\r\n", reti,vali );
printf("float[%d]:%f\r\n",retf,valf );
return argc;
}
T_ShellCmd paramTestCmd = /*命令描述结构体*/
{
.name= "test",
.help = "(int a=0, float b=0) show tow param val", /*参数提示,使用函数参数书写方式,有等于号表明默认参数,无参数不提示参数项*/
.fun = paramTestCmdFun
};
定义好了一个命令之后我们可以定义一个数组,专门用来存放所有的命令描述结构体
T_ShellCmd *sysCmd[] =
{
&helpCmd, /*只存放命令结构体的指针减少对存储的占用*/
¶mTestCmd, /*按照help的方式建立的其他命令*/
NULL /*用于标记命令数组的结尾*/
};
最后编写shellMain以及上面用到的shellStr2Int和shellStr2Float函数,
uint8_t shellMain(void)
{
uint8_t paramNum = 0;
if(shellGetOneLine(shellLine, SHELL_LINE_MAX_LEN))
{
paramNum = shellGetParam(shellLine, shellParam, SHELL_PARAM_MAX_NUM);
if(paramNum)
{
uint8_t i=0;
for(i=0; sysCmd[i]; i++) /*查找命令名字*/
{
if(strcmp(sysCmd[i]->name, shellParam[0]) == 0)
{
int value = sysCmd[i]->fun(paramNum-1, &shellParam[1]); /*运行命令函数*/
printf("value %d = 0x%x\r\n", value, value); /*打印运行结果*/
return 1;
}
}
if(sysCmd[i-1] == NULL) /*没有找到命令*/
{
printf("C interp: unknown symbol name \'%s\' \r\n",shellLine); /*打印错误信息*/
}
}
printf("->");
}
return 0;
}
/*
* 提供int字符串转int
*/
int shellStr2Int(const char *str, uint8_t* ok)
{
int ret;
if(str == NULL)
{
ok = false;
return 0;
}
*ok=(uint8_t)sscanf(str,"%d", &ret);
if(*ok != 1)
{
*ok = false;
return 0;
}
return ret;
}
/*
* 提供flaot字符串转浮点数功能
*/
float shellStr2Float(const char *str, uint8_t* ok)
{
float ret;
if(str == NULL)
{
ok = false;
return 0;
}
*ok=(uint8_t)sscanf(str,"%f", &ret);
if(*ok != 1)
{
*ok = false;
return 0.0;
}
return ret;
}
/*
* 提供16进制字符串转数字的功能
*/
int shellStr2Hex(const char *str, uint8_t* ok)
{
int ret;
if(str == NULL)
{
ok = false;
return 0;
}
*ok=(uint8_t)sscanf(str,"%X", &ret);
if(*ok != 1)
{
*ok = false;
return 0;
}
return ret;
}