清风徐来——Zephyr实战篇(5)之Shell
来源:恩智浦MCU加油站 发布时间:2021-11-04 分享至微信

这次小编为大家带来的是ZephyrOS系列文章的第六篇,将为大家介绍Shell。




清风徐来前期回顾:




什么是Shell呢?熟悉Linux的朋友们可能并不陌生,当然,在Windows上也是可以看到他的身影的。
先摘一段来自于某库的介绍:Unix shell,通常被称作“命令行”,为Unix和类Unix操作系统提供了传统的用户界面。用户通过输入shell所执行的命令,引导计算机的操作。在微软Windows操作系统平台,类似程序是command.com,或者基于Windows NT内核操作系统的cmd.exe。

下面,让我们看看Zephyr中的Shell有哪些不同:

  1. Unix-like shell

  2. 支持创建用户自定义指令集

  3. 能够和ZephyrOS中的Logging系统相互配合

  4. 支持Tab键的命令补齐

  5. 多种内建指令:clear, shell, colors, echo, history, resize

  6. 可通过Kconfig进行配置以优化内存占用

  7. 支持通配符*和?

  8. 支持显示历史

当然,作为Shell,需要能够将其传输到我们的终端上进行显示,那么在Zephyr的2.6.0版本中,所支持的传输通信方式包括:Segger RTT, SMP, Telnet,UART, USB, DUMMY。


了解了Zephyr中Shell的基本概念,我们来看看怎么手动创建一个自定义Shell指令,准备工作可以说非常简单,我们只需要在需要创建shell指令的文件中包含shell.h,即可通过其中提供的宏创建指令。

1. 静态指令 实现方法非常简单,首先我们定义一个叫做cmd_toggle的函数,这就是我们在控制台调用指令时,所执行的函数,随后通过宏SHELL_CMD_REGISTER进行指令的创建和注册,例如:
static int cmd_toggle(const struct shell *shell, size_t argc, char **argv)
{
ARG_UNUSED(argc);
ARG_UNUSED(argv);
led_on = !led_on;
gpio_pin_set(dev, PIN, (int)led_on);
return 0;
}
* Creating root (level 0) command "toggle" */
SHELL_CMD_REGISTER(toggle, NULL, "Toggle LED command", cmd_toggle);

这样一来我们就定义了一个叫做toggle的指令,可以直接在控制台中执行toggle调用。

这个宏的原型是SHELL_CMD_REGISTER(syntax, subcmd, help, handler),我们来详细介绍下这个宏中的参数:

  1. syntax:指令名称

  2. subcmd:是否支持多级指令,即子指令

  3. help:指令帮助

  4. handler:指向实际的处理函数

既然提到了子指令,我们来看看如何创建一个子指令:
* Creating subcommands (level 1 command) array for command "demo". */
SHELL_STATIC_SUBCMD_SET_CREATE(sub_demo,
SHELL_CMD(params, NULL, "Print params command.",
cmd_demo_params),
SHELL_CMD(ping, NULL, "Ping command.", cmd_demo_ping),
SHELL_SUBCMD_SET_END
);
* Creating root (level 0) command "demo" */
SHELL_CMD_REGISTER(demo, &sub_demo, "Demo commands", NULL);

对比刚才的定义方式,在SHELL_CMD_REGISTER中我们将handler置为了NULL,然后将&sub_demo作为第二个参数,subcmd传入。

我们再来详细看看这个sub_cmd是怎么生成的。

同样我们还是依赖于一个shell.h中的宏SHELL_STATIC_SUBCMD_SET_CREATE,其原型是,SHELL_STATIC_SUBCMD_SET_CREATE(name, ...),这里要注意了,第二个参数代表他是一个可变参数,代表了一个指令列表,通过传入SHELL_SUBCMD_SET_END来表示指令集定义完毕,name是所定义的子指令的名字。

而我们的子指令是通过宏SHELL_CMD进行创建的,其原型是SHELL_CMD(_syntax, _subcmd, _help, _handler),可以发现其与SHELL_CMD_REGISTER是类似的,但是功能就不一样了,这个宏仅仅是代表一个子指令的初始化,不会被注册进Shell的指令列表中。

这样一来,我们就定义了一个叫做demo的根指令,其中包含了一个sub_demo指向的子指令集,其中包含了两条指令,分别是params和ping,调用方式略有不同,需要通过在控制台指令demo params或是demo ping进行调用。


2. 字典指令顾名思义,就是我们可以定义个能够使用字符串+数值的组合形式的指令:

static int gain_cmd_handler(const struct shell *shell,
size_t argc, char **argv, void *data)
{
int gain;

* data is a value corresponding to called command syntax */
gain = (int)data;
adc_set_gain(gain);

shell_print(shell, "ADC gain set to: %s\n"
Value send to ADC driver: %d",
argv[0],
gain);

return 0;
}

SHELL_SUBCMD_DICT_SET_CREATE(sub_gain, gain_cmd_handler,
(gain_1, 1), (gain_2, 2), (gain_1_2, 3), (gain_1_4, 4)
);
SHELL_CMD_REGISTER(gain, &sub_gain, "Set ADC gain", NULL);

其展示效果如下:

即当我们将某个定义好的字典键(字符串)作为指令执行时,Shell系统在指令解析时,会将其对应的数指作为参数传入。

我们来分析下它的实现,命令注册部分依旧是通过调用SHELL_CMD_REGISTER实现,不同的是,我们调用了一个新的宏SHELL_SUBCMD_DICT_SET_CREATE,来进行字典指令集的注册,其原型是:SHELL_SUBCMD_DICT_SET_CREATE(_name, _handler, ...),前两个参数我们已经见过多次了,分别代表指令名和处理函数,而第三个参数依旧是个可变参数,代表字典对,其格式为(指令名,值)。


3. 动态指令 和静态指令不同的是,我们可以在程序运行期间,动态的添加指令。

例如,当我们使用scan指令搜索可用的蓝牙设备以通过connect指令以进行连接。

关于动态指令的添加,小编在这里就不扩展讲了,感兴趣的朋友门可以参考


4. 参数获取 讲完如何创建一条Shell指令,我们来看看如何获取指令参数。

Shell支持通过argv的下标来访问相应参数,正的索引表示其指令参数,0表示指令本身,同时我们还可以通过负数索引来获取其父指令名称,例如刚才我们定义的子指令,demo params 10,那么argv[1] = 10, argv[0] = params,argv[-1] = demo:
static int cmd_handler(const struct shell *shell, size_t argc, char **argv)
{
ARG_UNUSED(argc);

* If it is a subcommand handler parent command syntax
* can be found using argv[-1].
*/
shell_print(shell, "This command has a parent command: %s",
argv[-1]);

* Print this command syntax */
shell_print(shell, "This command syntax is: %s", argv[0]);

* Print first argument */
shell_print(shell, "%s", argv[1]);

return 0;
}


至此,关于Zephyr中的Shell系统小编就介绍完了,相信大家已经跃跃欲试想要去创建一个属于自己的专属指令了。


[ 新闻来源:恩智浦MCU加油站,更多精彩资讯请下载icspec App。如对本稿件有异议,请联系微信客服specltkj]
存入云盘 收藏
举报
全部评论

暂无评论哦,快来评论一下吧!