[原创]ECShell - 基于ECLayer的ARM-CM轻量级Shell实现

eggcar
eggcar 05月10日 字数 3020
loading ...

https://github.com/eggcar/ECShell

最近需要在STM32上提供一个shell界面,实现基本的人机交互。本以为能随便找个库把这活干了,结果花了一些时间在网上搜索开源项目,没有找到符合我内心预期的现成项目(只有rt-thread的finsh完成度很高,但是跟rt-thread绑定的太深了)。于是趁51假期在家轮了一个。

基于我的ECLayer VFS层,在ECLayer中添加了基于lwip的socket封装后,ECShell可以同时支持串口、TCP/TLS及其他流式文件接口。不使用静态和全局变量,可同时例化多个对象。

通过ECLayer的封装,支持有RTOS和无RTOS的配置。

行编辑功能基于LineNoise库,但是LineNoise本身不太符合我的设计要求,因此做了比较大的删改。时间原因没有把全部功能都导入进来,将来再说。

命令行参数解析我导入了optparse库,可以直接用标准的getopt() getopt_long()式api进行解析。参数拆分是我自己实现的函数split_line_to_argv(),支持单引号、双引号对含空白符的参数做包装。

命令的存储查询基于avlmap。将来会添加配置选项选择链表存储,牺牲一部分查询性能来降低资源消耗。

shell命令的实现示例:

int ecshell_cmd_clear_screen(int argc, char *argv[], void *env)

{

int32_t ifd, ofd;

ifd = ((ecshell_env_t *)env)->stdin_fd;

ofd = ((ecshell_env_t *)env)->stdout_fd;

const char help_info[] =

CSI_SGR(SGR_COL_FRONT(COL_CYAN)) "clear" CSI_SGR(SGR_COL_FRONT(COL_DEFAULT)) "rn"

"Clear screen.rn";

const char err_info[] =

CSI_SGR(SGR_COL_FRONT(COL_RED)) "Invalid argument.rn" CSI_SGR(SGR_COL_FRONT(COL_DEFAULT)) "rn";

struct optparse_long longopts[] = {

{"help", 'h', OPTPARSE_NONE},

{0},

};

struct optparse options;

optparse_init(&options, argv);

int option;

while ((option = optparse_long(&options, longopts, NULL)) != -1) {

switch (option) {

case 'h':

write(ofd, help_info, strlen(help_info));

return 0;

default:

write(ofd, err_info, strlen(err_info));

return 0;

}

}

write(ofd, "x1b[Hx1b[2J", 7);

return 0;

}

然后对命令进行注册:

void ecshell_cmd_map_init(void)

{

avl_map_init(&cmd_map, BKDRHash, strcmp);

/** Begin to regist your own cmds */

REGIST_COMMAND(ecshell_cmd_clear_screen, "clear");

}

启动shell也很简单,开一个线程(有RTOS)或者直接初始化调用(无RTOS):

#define DEBUG_DEVICE_NAME "/drivers/stm32/uart5"

void StartDefaultTask(void *argument)

{

shell_fd = open(DEBUG_DEVICE_NAME, O_RDWR);

ecshell_cmd_map_init();

ecshell_t *shell = ecshell_new(shell_fd, shell_fd, e_SHELLTYPE_Default, 0);

for (;;) {

shell_run(shell);

}

}

需要注意的是,在进入shell_run()之前,需要将fd设置为阻塞模式(open时不使用O_NOBLOCK或调用fcntl清除O_NOBLOCK标志位)。命令程序里如果需要改为非阻塞模式,在退出命令函数之前需要改回阻塞模式。将来大概会添加选项允许shell_run()运行在非阻塞模式下。

然后就可以在shell中调用了:

Embedded 嵌入式系统
16 个回复
tom6bj
tom 05月10日

不错不错

rom和ram消耗情况如何

【 在 eggcar (eggcar) 的大作中提到: 】

https://github.com/eggcar/ECShell

: 最近需要在STM32上提供一个shell界面,实现基本的人机交互。本以为能随便找个库把这活干了,结果花了一些时间在网上搜索开源项目,没有找到符合我内心预期的现成项目(只有rt-thread的finsh完成度很高,但是跟rt-thread绑定的太深了)。于是趁51假期在家轮了一个。

: 基于我的ECLayer VFS层,在ECLayer中添加了基于lwip的socket封装后,ECShell可以同时支持串口、TCP/TLS及其他流式文件接口。不使用静态和全局变量,可同时例化多个对象。

: ...................

intron
内含子 05月10日

善哉!善哉!

【 在 eggcar (eggcar) 的大作中提到: 】

: 标  题: [原创]ECShell - 基于ECLayer的ARM-CM轻量级Shell实现

: 发信站: 水木社区 (Sun May 10 00:40:07 2020), 站内

https://github.com/eggcar/ECShell

: 最近需要在STM32上提供一个shell界面,实现基本的人机交互。本以为能随便找个库把这活干了,结果花了一些时间在网上搜索开源项目,没有找到符合我内心预期的现成项目(只有rt-thread的finsh完成度很高,但是跟rt-thread绑定的太深了)。于是趁51假期在家轮了一个。

: 基于我的ECLayer VFS层,在ECLayer中添加了基于lwip的socket封装后,ECShell可以同时支持串口、TCP/TLS及其他流式文件接口。不使用静态和全局变量,可同时例化多个对象。

: 通过ECLayer的封装,支持有RTOS和无RTOS的配置。

: 行编辑功能基于LineNoise库,但是LineNoise本身不太符合我的设计要求,因此做了比较大的删改。时间原因没有把全部功能都导入进来,将来再说。

: 命令行参数解析我导入了optparse库,可以直接用标准的getopt() getopt_long()式api进行解析。参数拆分是我自己实现的函数split_line_to_argv(),支持单引号、双引号对含空白符的参数做包装。

: 命令的存储查询基于avlmap。将来会添加配置选项选择链表存储,牺牲一部分查询性能来降低资源消耗。

: shell命令的实现示例:

: int ecshell_cmd_clear_screen(int argc, char *argv[], void *env)

: {

: int32_t ifd, ofd;

: ifd = ((ecshell_env_t *)env)->stdin_fd;

: ofd = ((ecshell_env_t *)env)->stdout_fd;

: const char help_info[] =

: CSI_SGR(SGR_COL_FRONT(COL_CYAN)) "clear" CSI_SGR(SGR_COL_FRONT(COL_DEFAULT)) "rn"

: "Clear screen.rn";

: const char err_info[] =

: CSI_SGR(SGR_COL_FRONT(COL_RED)) "Invalid argument.rn" CSI_SGR(SGR_COL_FRONT(COL_DEFAULT)) "rn";

: struct optparse_long longopts[] = {

: {"help", 'h', OPTPARSE_NONE},

: {0},

: };

: struct optparse options;

: optparse_init(&options, argv);

: int option;

: while ((option = optparse_long(&options, longopts, NULL)) != -1) {

: switch (option) {

: case 'h':

: write(ofd, help_info, strlen(help_info));

: return 0;

: default:

: write(ofd, err_info, strlen(err_info));

: return 0;

: }

: }

: write(ofd, "x1b[Hx1b[2J", 7);

: return 0;

: }

:

: 然后对命令进行注册:

: void ecshell_cmd_map_init(void)

: {

: avl_map_init(&cmd_map, BKDRHash, strcmp);

: /** Begin to regist your own cmds */

: REGIST_COMMAND(ecshell_cmd_clear_screen, "clear");

: }

:

: 启动shell也很简单,开一个线程(有RTOS)或者直接初始化调用(无RTOS):

: #define DEBUG_DEVICE_NAME "/drivers/stm32/uart5"

: void StartDefaultTask(void *argument)

: {

: shell_fd = open(DEBUG_DEVICE_NAME, O_RDWR);

: ecshell_cmd_map_init();

: ecshell_t *shell = ecshell_new(shell_fd, shell_fd, e_SHELLTYPE_Default, 0);

: for (;;) {

: shell_run(shell);

: }

: }

:

: 需要注意的是,在进入shell_run()之前,需要将fd设置为阻塞模式(open时不使用O_NOBLOCK或调用fcntl清除O_NOBLOCK标志位)。命令程序里如果需要改为非阻塞模式,在退出命令函数之前需要改回阻塞模式。将来大概会添加选项允许shell_run()运行在非阻塞模式下。

: 然后就可以在shell中调用了:

: --

eggcar
eggcar 05月10日

新建的demo工程,用armclang 开-O0编译:

Program Size: Code=97476 RO-data=9468 RW-data=28 ZI-data=162784

-Oz编译:

Program Size: Code=50838 RO-data=3266 RW-data=36 ZI-data=162776

移植ECLayer后-O0编译:

Program Size: Code=100796 RO-data=9468 RW-data=2900 ZI-data=163268

-Oz编译:

Program Size: Code=52038 RO-data=3266 RW-data=2904 ZI-data=163260

移植ECShell后-O0编译:

Program Size: Code=132028 RO-data=12588 RW-data=2904 ZI-data=164248

-Oz编译:

Program Size: Code=68916 RO-data=4544 RW-data=2904 ZI-data=164220

【 在 tom6bj 的大作中提到: 】

: 不错不错

: rom和ram消耗情况如何

spadger
echo 05月10日

体积真大

【 在 eggcar (eggcar) 的大作中提到: 】

: 新建的demo工程,用armclang 开-O0编译:

: Program Size: Code=97476 RO-data=9468 RW-data=28 ZI-data=162784

: -Oz编译:

: ...................

eggcar
eggcar 05月10日

主要是三方库大,那个avl hash map代码量挺大的...其实没几条注册命令的话 用链表就好了

【 在 spadger 的大作中提到: 】

: 体积真大

eggcar
eggcar 05月10日

以及ram用量大是我开了个128k的内存池,实际就用了一丁点...

【 在 spadger 的大作中提到: 】

: 体积真大

eggcar
eggcar 05月10日

https://github.com/eggcar/ECShell-Demo

Demo工程在这里

spadger
echo 05月10日

我的bootloader,自带的命令解释器,固件总体积7kB

【 在 eggcar (eggcar) 的大作中提到: 】

: 主要是三方库大,那个avl hash map代码量挺大的...其实没几条注册命令的话 用链表就好了

tom6bj
tom 05月10日

嗯, 我学你那个, 把命令行界面去掉之后可以降到2-3k

我的bootloader,自带的命令解释器,固件总体积7kB

【 在 eggcar (eggcar) 的大作中提到: 】

【 在 spadger (imdx) 的大作中提到: 】

: 主要是三方库大,那个avl hash map代码量挺大的...其实没几条注册命令的话 用链表就好了

eggcar
eggcar 05月10日

那毕竟不能拿bootloader跟业务层用的东西比体积

光freertos加lwip就多大了……

【 在 spadger 的大作中提到: 】

: 我的bootloader,自带的命令解释器,固件总体积7kB

spadger
echo 05月10日

大部分代码都是那个命令行界面,无论.text还是.econst代码都很多。

Arduino的bootloader只有512字节。

【 在 tom6bj (tom) 的大作中提到: 】

: 嗯, 我学你那个, 把命令行界面去掉之后可以降到2-3k

: 我的bootloader,自带的命令解释器,固件总体积7kB

ffxz
非飞·奋发中 05月11日

楼主哪里高就?换工作否

rt-thread/finsh,把finsh本身砍掉,精简下只预msh,挺小巧的了

rt-thread上类似的几个小东西挺顺手的,msh、ulog。armink的sfud、cmbacktrace。现在armink也在rt-thread,所以在rt-thread上整合得也更好了

【 在 eggcar 的大作中提到: 】

https://github.com/eggcar/ECShell

: 最近需要在STM32上提供一个shell界面,实现基本的人机交互。本以为能随便找个库把这活干了,结果花了一些时间在网上搜索开源项目,没有找到符合我内心预期的现成项目(只有rt-thread的finsh完成度很高,但是跟rt-thread绑定的太深了)。于是趁51假期在家轮了一个。

: 基于我的ECLayer VFS层,在ECLayer中添加了基于lwip的socket封装后,ECShell可以同时支持串口、TCP/TLS及其他流式文件接口。不使用静态和全局变量,可同时例化多个对象。

: ...................

eggcar
eggcar 05月11日

久仰久仰...我工作里也经常用armink的库,节约了不少时间精力和人力...

暂时没在正式项目里用rt-thread,主要是考虑rt-thread的生态定位更偏向IoT领域,我们目前的产品面向工业控制和数据中心,对于很多rt-thread自带的功能,用kconfig直接配置启用确实非常方便,但是为了保证产品的可维护性,我们得把代码都通读过 了解原理 保证有问题自己接得住 才敢上生产环境。现在暂时没有精力去做这项工作,所以生产环境里还在用我们团队自己更熟悉的freertos和一些自己写的库。

最近也在考虑先在不太重要的场合下试试切换到rt-thread上去,磨合一段时间看看效果,比较麻烦的就是我们一些既有的库挪过去怕跟rtt的组件有冲突

【 在 ffxz 的大作中提到: 】

: 楼主哪里高就?换工作否

: rt-thread/finsh,把finsh本身砍掉,精简下只预msh,挺小巧的了

: rt-thread上类似的几个小东西挺顺手的,msh、ulog。armink的sfud、cmbacktrace。现在armink也在rt-thread,所以在rt-thread上整合得也更好了

: ...................

spadger
echo 05月11日

这个确实是使用rtos的最大障碍,不出问题还好,出了问题,定位起来太麻烦。

另外小容量器件确实用不起,flash根本存不下那么多通用代码。

SFUD我试过最后还是放弃了,一般产品兼容三四种芯片就够了,代码可以很简化,不需要兼容那么多型号。

【 在 eggcar (eggcar) 的大作中提到: 】

: 久仰久仰...我工作里也经常用armink的库,节约了不少时间精力和人力...

: 暂时没在正式项目里用rt-thread,主要是考虑rt-thread的生态定位更偏向IoT领域,我们目前的产品面向工业控制和数据中心,对于很多rt-thread自带的功能,用kconfig直接配置启用确实非常方便,但是为了保证产品的可维护性,我们得把代码都通读过 了解原理 保证有问题自己

eggcar
eggcar 05月11日

这个问题,作为总工,我的办法是,多花点钱换大容量的芯片,或者外置nor,留足够的空间余量让代码尽量通用化,代码功能上也尽量多做冗余,这样不光方便移植,方便后期维护,还能一处暴露问题 多处同步升级...

当然我们设备不是消费类电子跑量的,多十几块钱成本毛毛雨...≥kk量级的产品压缩成本到小数点后2位这个也完全理解,只能说还好我现在不用考虑这个

( o ω o )?

【 在 spadger 的大作中提到: 】

: 这个确实是使用rtos的最大障碍,不出问题还好,出了问题,定位起来太麻烦。

: 另外小容量器件确实用不起,flash根本存不下那么多通用代码。

: SFUD我试过最后还是放弃了,一般产品兼容三四种芯片就够了,代码可以很简化,不需要兼容那么多型号。

: ...................

ffxz
非飞·奋发中 06月04日

RT-Thread对于工控环境同样不错,欢迎多试试,遇到问题也可以多多沟通,反馈。

从RT-Thread来说,还需要做精,做得更小而美……

【 在 eggcar 的大作中提到: 】

: 久仰久仰...我工作里也经常用armink的库,节约了不少时间精力和人力...

: 暂时没在正式项目里用rt-thread,主要是考虑rt-thread的生态定位更偏向IoT领域,我们目前的产品面向工业控制和数据中心,对于很多rt-thread自带的功能,用kconfig直接配置启用确实非常方便,但是为了保证产品的可维护性,我们得把代码都通读过 了解原理 保证有问题自己接得住 才敢上生产环境。现在暂时没有精力去做这项工作,所以生产环境里还在用我们团队自己更熟悉的freertos和一些自己写的库。

: 最近也在考虑先在不太重要的场合下试试切换到rt-thread上去,磨合一段时间看看效果,比较麻烦的就是我们一些既有的库挪过去怕跟rtt的组件有冲突

: ...................