适用stm32的命令行解释器shell

适用于stm32的命令行解释器shell

  • 记录一下这个shell的应用说明
    • 基本使用
    • 用户命令的注册和使用
    • 命令行的参数解析

记录一下这个shell的应用说明

源码的地址:
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 找到 getopt() 函数支持。
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 :
适用stm32的命令行解释器shell_第1张图片
配置 RCC -> HSE 为 Crystal ,
适用stm32的命令行解释器shell_第2张图片
配置 USART1 -> Mode 为 asynchronization ,并打开USART1 的中断:
适用stm32的命令行解释器shell_第3张图片
调整时钟为外部晶振,其余配置保留原始值即可:
适用stm32的命令行解释器shell_第4张图片
最后生成MDK-KEIL5 工程。把 shell.c 、ustdio.c 复制到工程文件夹 src 中,并加入文件, containerof.h、shell.h、ustdio.h复制到 inc 文件夹中。
适用stm32的命令行解释器shell_第5张图片
接下来需要编写串口的发送函数,类型如下:
void puts(char *data,uint16_t len) ,
为了方便此处在main.c下编写,内容如下:

适用stm32的命令行解释器shell_第6张图片
编写串口对应的接收回调函数,如下:
适用stm32的命令行解释器shell_第7张图片
在 main.c 中include “shell.h”,新建一个交互usart1_shell;如下:
在这里插入图片描述
在 main() 中添加如下代码,初始化整个系统,其中,shell_init()初始化默认的输入标志和printf/printk的默认输出,默认输出可以在线重定义流向,shell_input_init()初始化交互。

适用stm32的命令行解释器shell_第8张图片
编译并下载代码。然后用 SecureCRT 或者 putty 连接串口,连续敲回车出现输入标志,则说明系统正常运行:

适用stm32的命令行解释器shell_第9张图片
适用stm32的命令行解释器shell_第10张图片
调用 shell_init() 后,系统默认内置三条命令,输入命令“cmd-list”回车,获取所有命令列表:
适用stm32的命令行解释器shell_第11张图片
至此shell 系统已经基本成功跑起,可以尝试 tab 键命令补全,但上下箭头回溯历史,左右箭头编辑命令行等功能还不能用,因为箭头按键是3个字节数据,目前是接收一个字节就输入shell_input()一次,数据包被分割输入,所以需要使用这部分功能,需要接收完整一包数据后才调用shell_input()输入,比如利用空闲中断,此处修改接收回调
适用stm32的命令行解释器shell_第12张图片
开启空闲中断:
在这里插入图片描述
修改usart1的中断函数,添加空闲中断处理代码:
适用stm32的命令行解释器shell_第13张图片
修改main()函数while(1)处理
适用stm32的命令行解释器shell_第14张图片
至此,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]确认命令执行。如下
适用stm32的命令行解释器shell_第15张图片

命令行的参数解析

上文提到,命令对应的执行函数类型为

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);
运行结果如下:
适用stm32的命令行解释器shell_第16张图片
利用 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);结果如下:
适用stm32的命令行解释器shell_第17张图片
另外,getopt.c不是作者所写,编译可能会有些许警告,而且其源码编译下来也不小,还是按需使用吧。
源码链接:https://gitee.com/somebug/atomlib/blob/master/appdemo/f103-shell-demo-2.0.zip

你可能感兴趣的:(C)