源码的地址:
https://gitee.com/somebug/atomlib
shell 层与硬件无关,有无操作系统都可以使用,适用于stm32等32位小端的单片机,支持历史纪录功能,tab 键补全命令,提供命令行参数解释函数,可以响应tab、backspace、上下左右编辑等功能,并提供一个简易版的vi 文本编辑功能。支持多交互接口,各个接口间数据流互不影响。
其中此文件夹中的文件都与硬件平台无关,但可能与编译平台有关。此库编译版本要在C99以上,或用GCC编译。根据不同的硬件平台分别写了不同的控制台demo, F1 文件夹的是STM32F1 相关的控制台, F4 则是 F4xx 系列的控制台,但是L1平台的我并没有调通,手上也没有这个板子,所以先放着。
文件说明:
avltree.c :平衡二叉树相关实现代码,在注册命令较多的时候可以启用平衡二叉树进行索引匹配。
getopt.c :有些编译环境,如 KEIL5 中,没有函数getopt() ,这是其源码,是我在网上找的。要是用 gcc 相关的编译平台可以 #include
heaplib.c : 内存管理,其实这个是我原封不动从freertos 的 heap_4.c 拷过来的,在heaplib.h 提供了部分宏支持,可以脱离操作系统使用。
shell.c:命令行相关,支持 table 键补全,支持上下左右箭头响应,提供参数解析,历史纪录。支持多个交互,如串口,telnet,或者usb,可各自建立交互。
tasklib.c:协程控制器。有需要的话使用协程可以简化代码的编写。我把它模拟成一个操作系统。
ustdio.c:提供 printk 函数,重定义 printf 函数
vim.c:这是我仿照 linux 的 vi 写的一个建议的文本编辑器,依赖 shell ,可以实现简易的文本编辑。
F1/F4/L1:不同硬件平台的相关串口控制台实现,提供串口在线升级功能。
LittleFS: LittleFS 是一个用于 spi flash 的文件系统,先放这。
使用这个库的基本功能只需要把 shell.c 、shell.h 、ustdio.c 、ustdio.h 和 kernel.h 这几个文件包含进文件工程里面即可。系统的使用可以大致分为以下几个步骤:
0.初始化硬件部分。
1.编写硬件对应的void puts(char * buf , uint16_t len) 发送函数。
2.shell_init(sign,puts)初始化输入标志和默认输出。
3.新建一个交互shellx 并初始化shell_input_init(&shellx,puts);
4.接收到一包数据后,调用shell_input(&shellx,buf,len)
以下用stm32cubemx新建串口工程实际说明。Mcu选择为STM32F103VET6;
配置 SYS -> DEBUG 为 Serial Wire :
配置 RCC -> HSE 为 Crystal ,
配置 USART1 -> Mode 为 asynchronization ,并打开USART1 的中断:
调整时钟为外部晶振,其余配置保留原始值即可:
最后生成MDK-KEIL5 工程。把 shell.c 、ustdio.c 复制到工程文件夹 src 中,并加入文件, containerof.h、shell.h、ustdio.h复制到 inc 文件夹中。
接下来需要编写串口的发送函数,类型如下:
void puts(char *data,uint16_t len) ,
为了方便此处在main.c下编写,内容如下:
编写串口对应的接收回调函数,如下:
在 main.c 中include “shell.h”,新建一个交互usart1_shell;如下:
在 main() 中添加如下代码,初始化整个系统,其中,shell_init()初始化默认的输入标志和printf/printk的默认输出,默认输出可以在线重定义流向,shell_input_init()初始化交互。
编译并下载代码。然后用 SecureCRT 或者 putty 连接串口,连续敲回车出现输入标志,则说明系统正常运行:
调用 shell_init() 后,系统默认内置三条命令,输入命令“cmd-list”回车,获取所有命令列表:
至此shell 系统已经基本成功跑起,可以尝试 tab 键命令补全,但上下箭头回溯历史,左右箭头编辑命令行等功能还不能用,因为箭头按键是3个字节数据,目前是接收一个字节就输入shell_input()一次,数据包被分割输入,所以需要使用这部分功能,需要接收完整一包数据后才调用shell_input()输入,比如利用空闲中断,此处修改接收回调
开启空闲中断:
修改usart1的中断函数,添加空闲中断处理代码:
修改main()函数while(1)处理
至此,shell系统基本可以完整跑起来了,已可以使用上下左右箭头按键的功能。代码链接:
https://pan.baidu.com/s/1ij9EXbxYwMCqrZkuDcLvOw
下面介绍 shell用户命令的注册和使用。
目前这个库提供了两个宏定义来实现用户命令的注册,分别是
shell_register_command(name,func);
shell_register_confirm(name,func,info);
shell_register_command用于一般的命令注册,name为命令字符,如“reboot”,需要注意的是,同一串字符串不能注册多次,即不能注册同一条命令多次,以先到先得的原则会只注册第一条。func为命令对应的执行代码,类型为
void cmd_fn(void * arg);
其中输入形参void * arg 为命令行的输入内存指针。以hello-world命令为例,编写对应的执行代码:
void helloworld(void * arg)
{
printk("this is hello world command\r\n");//或者printf
}
然后在main()函数调用注册:
shell_register_command(“hello-world”,helloworld);
便可以在cmd-list里面找到对应的命令,在终端输入hello-world,会反馈打印内容。此外还有另外一种带确认信息的注册方式:
shell_register_confirm (“hello-world2”,helloworld,“sure to test this command?”);
这种方式在输入命令以后需要输入[y/Y/n/N]确认命令执行。如下
上文提到,命令对应的执行函数类型为
void cmd_fn(void * arg);
其中的void * arg是把命令行内存首地址传入,如在终端输入"hello-world 1234" 回车,shell会先匹配到 helloworld 对应的执行代码,并把整串输入字符串的首地址作为arg输入给函数,用户可对此进行参数解析。当前库提供两个函数用于解析命令行参数
int cmdline_param (char * str,int * argv,uint32_t maxread);
int cmdline_strtok(char * str ,char ** argv,uint32_t maxread);
cmdline_param()可以把命令行后面所跟参数转为整形数据,不过仅支持整形的转换,包括正负数和十六进制,转换结果存储于 argv 内,字符串转换正常返回参数个数,有非数字字符会返回<0。大致如下:
void demo_cmd(void * arg)
{
int argv[4];
int argc = cmdline_param((char*)arg,argv,4);
printk("get %d parameters\r\n",argc);
for(int i = 0 ; i < argc ; ++i)
printk("argv[%d]:%d\r\n",i,argv[i]);
}
cmdline_strtok()是对命令行的输入字符串进行分割,把输入分割为一串指针数组输出到argv[]中。需要注意的是这个函数会改变命令行的输入内存内容(把空格替换了字符串结束符)。如下
void demo2_cmd(void * arg)
{
char * argv[4];
int argc =cmdline_strtok((char*)arg,argv,4);
printk("get %d parameters\r\n",argc);
for(int i = 0 ; i < argc ; ++i)
printk("argv[%d]:\"%s\"\r\n",i,argv[i]);
}
然后注册命令测试
shell_register_command(“param2int”,demo_cmd);
shell_register_command(“param-get”,demo2_cmd);
运行结果如下:
利用 cmdline_strtok() ,可以实现linux 的 getopt() 选项输入支持。由于getopt() 是gnuc的库,所以上述库还收录了 getopt() 的源码,有需要的可以把getopt.c和getopt.h加入工程便可使用getopt()函数进行输入选项分类,代码如下:
void demo3_cmd(void * arg)
{
int opt;
int argc;
char* argv[8];
argc = cmdline_strtok((char*)arg,argv,8);
optind = 1;//getopt()之前,这个值要为 1
while ((opt = getopt(argc, argv, "ab:c:de::")) != -1)
{
printk("optind: %d\r\n", optind);
switch (opt)
{
case 'a':
printk("HAVE option: -a\r\n");
break;
case 'b':
printk("HAVE option: -b\r\n");
printk("The argument of -b is %s\r\n", optarg);
break;
case 'c':
printk("HAVE option: -c\r\n");
printk("The argument of -c is %s\r\n", optarg);
break;
case 'd':
printk("HAVE option: -d\r\n");
break;
case 'e':
printk("HAVE option: -e\r\n");
printk("The argument of -e is %s\r\n", optarg);
break;
case '?':
printk("Unknown option: %c\r\n",(char)optopt);
break;
}
}
}
注册shell_register_command(“getopt-demo”,demo3_cmd);结果如下:
另外,getopt.c不是作者所写,编译可能会有些许警告,而且其源码编译下来也不小,还是按需使用吧。
源码链接:https://gitee.com/somebug/atomlib/blob/master/appdemo/f103-shell-demo-2.0.zip