单片机
返回首页

一个功能强大的嵌入式shell

2025-03-11 来源:elecfans

1. letter-shell

本期给大家带来的开源项目是 letter-shell,一个功能强大的嵌入式shell,作者NevermindZZT,目前收获 155 个star,遵循 MIT 开源许可协议。

letter shell 3.0是一个C语言编写的,可以嵌入在程序中的嵌入式shell,通俗一点说就是一个串口命令行,可以通过命令行调用、运行程序中的函数。

目前 letter-shell 3.0版本支持的功能有:

命令自动补全

快捷键功能定义

命令权限管理

用户管理

变量支持

项目地址:https://github.com/NevermindZZT/letter-shell

2. 移植letter-shell

2.1. 移植思路

① 看项目readme文件中的移植说明,一般都比较完善; ② 看项目中的demo,举一反三; ③ 看别人移植好的博客;

2.2. 移植过程

letter-shell的移植非常简单,自己实现串口读写一个字符的接口,自己编写一个初始化函数,完成。

本文中我使用的是小熊派IoT开发套件,主控芯片为STM32L431RCT6:

移植之前需要准备一份裸机工程,我使用STM32CubeMX生成,使用USART1的查询方式发送数据、使用USART1的中断方式接收数据,参考教程:

STM32CubeMX_06 | 使用USART发送和接收数据(查询模式)

STM32CubeMX_07 | 使用USART发送和接收数据(中断模式)

① 复制源码到工程中:

② 新建存放实现移植接口的文件:

③ 在keil中添加文件,添加头文件路径:

目录下还有头文件,添加到头文件路径中:

④ 编辑shell_port.c文件,实现向串口写入一个字符的接口,并编写shell的初始化函数:

/**

* @brief shell移植到STM32L431时的接口实现

* @author mculover666

* @date 2020/03/27

*/

#include 'shell.h'

#include

#include 'usart.h'

#include 'shell_port.h'

/* 1. 创建shell对象,开辟shell缓冲区 */

Shell shell;

char shell_buffer[512];

/* 2. 自己实现shell写函数 */

//shell写函数原型:typedef void (*shellWrite)(const char);

void User_Shell_Write(const char ch)

{

//调用STM32 HAL库 API 使用查询方式发送

HAL_UART_Transmit(&huart1, (uint8_t*)&ch, 1, 0xFFFF);

}

/* 3. 编写初始化函数 */

void User_Shell_Init(void)

{

//注册自己实现的写函数

    shell.write = User_Shell_Write;

//调用shell初始化函数

    shellInit(&shell, shell_buffer, 512);

}

⑤ 编辑shell_port.h文件,声明自己编写的初始化函数:

#ifndef _SHELL_PORT_H_

#define _SHELL_PORT_H_

#include 'shell.h'

/* 将shell定义为外部变量,在串口中断回调函数中还要使用 */

extern Shell shell;

/* 声明自己编写的初始化函数 */

void User_Shell_Init(void);

#endif /* _SHELL_PORT_H_ */

⑥ 在main.c文件的末尾编写串口中断回调函数,在接收到一个字符之后调用 shellHandler 函数进行处理,首先把头文件包含进来:

/* USER CODE BEGIN Includes */

#include 'shell_port.h'

/* USER CODE END Includes */

接着开辟一个串口接收缓冲区(一个字符):

/* USER CODE BEGIN PV */

uint8_t recv_buf = 0;

/* USER CODE END PV */

然后编写回调函数

/* USER CODE BEGIN 4 */

/* 中断回调函数 */

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)

{

    /* 判断是哪个串口触发的中断 */

    if(huart ->Instance == USART1)

    {

        //调用shell处理数据的接口

     shellHandler(&shell, recv_buf);

        //使能串口中断接收

     HAL_UART_Receive_IT(&huart1, (uint8_t*)&recv_buf, 1);

    }

}

/* USER CODE END 4 */

⑥ 在main函数中使能中断接收,调用自己编写的shell初始化函数:

/* USER CODE BEGIN 2 */

//使能串口中断接收

HAL_UART_Receive_IT(&huart1, (uint8_t*)&recv_buf, 1);

User_Shell_Init();

/* USER CODE END 2 */

至此,移植完成,编译、下载之后使用串口终端软件Mobaxterm即可看到效果:

2.3. 宏定义配置

letter-shell具备很多功能,可以通过宏定义来开启或者关闭,在shell_cfg.h文件中根据需要进行配置:

在本次移植过程中,我将shell默认用户改为了mculover666,其它宏保持默认:

#define     SHELL_DEFAULT_USER          'mculover666'

3. 使用letter-shell

3.1. 应用场景

在嵌入式项目中,做出一个项目之后还需要用户在串口终端中进行操作,这样的情况很少,串口的作用基本都是用来在调试阶段打印信息。

但是在调试的模组比较复杂时,比如SPI Flash、LCD屏幕这些,我希望可以在串口直接调用某几个功能函数开始执行,当移植了shell之后,在代码中只需要添加一行宏定义,就可以在串口中调用此函数开始执行,多爽!

当然也可以在main函数中调用,然后打断点进入调试,根据个人喜好选择就行。

3.2. 可执行命令定义宏

这个宏可以实现将函数添加到shell的可执行命令列表中,使用时需要确保shell_cfg.h中的宏定义要开启:

#define     SHELL_USING_CMD_EXPORT      1

该宏定义的定义如下:

/**

* @brief shell 命令定义

*

* @param _attr 命令属性

* @param _name 命令名

* @param _func 命令函数

* @param _desc 命令描述

*/

#define SHELL_EXPORT_CMD(_attr, _name, _func, _desc)

        const char shellCmd##_name[] = #_name;

        const char shellDesc##_name[] = #_desc;

        const ShellCommand

        shellCommand##_name SECTION('shellCommand') = 

        {

            .attr.value = _attr,

            .data.cmd.name = shellCmd##_name,

            .data.cmd.function = (int (*)())_func,

            .data.cmd.desc = shellDesc##_name

        }

第一个参数attr表示该命令的属性,包括命令权限和命令类型等,但是对于目前这种应用场合下多用户没什么用,所以设置为下面的值就ok:

SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC)

最后需要注意一点,这个宏目前测试在MDK中可以正常使用,在IAR和GCC中请移步项目地址阅读文档。

3.3. 调用函数示例

在main.c文件中随便定义一个函数,然后使用宏定义导出到命令列表进行测试:

/* USER CODE BEGIN 0 */

int test(int i, char ch, char *str)

{

    printf('input int: %d, char: %c, string: %srn', i, ch, str);

return 0;

}

//导出到命令列表里

SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC), test, test, test);

/* USER CODE END 0 */

有两点需要注意:

① 目前支持的参数类型为整型、字符型、字符串型,不支持浮点型参数。

② 函数最大传入参数个数由shell_cfg.h中的宏定义配置:

#define     SHELL_PARAMETER_MAX_NUMBER  8

所以在使用时导出到命令列表中的参数最大是 8 - 1 = 7个,测试如下:

int test2(int i1, int i2, int i3, int i4, int i5, int i6, int i7, int i8)

{

printf('input int: %d, %d, %d, %d, %d, %d, %d, %drn', i1, i2, i3, i4, i5, i6, i7, i8);

return 0;

}

//导出到命令列表里

SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC), test2, test2, test2);

执行之后失败:

4. letter-shell设计思想解读

4.1. 终端软件交互原理

当我们使用诸如 mobaxterm 这类软件作为串口终端交互时,首先需要注意三点:

① 当在终端软件中输入一个字符时,这个字符会被直接通过串口发送,屏幕上不会显示任何东西; ② 如果单片机做了回显,终端软件中收到后才会显示出来; ③ 当在终端软件中按下回车键、退格键、删除键、四个方向键时,会直接发送其键值,单片机中靠这个键值来解析是否按下了按键。

在lettershell中输入keys命令即可查看当前支持的按键键值解析:

解析这些键值的源码在shell.c中,分别对应以下的函数:

void shellUp(Shell *shell);

……

void shellTab(Shell *shell);

void shellBackspace(Shell *shell);

void shellEnter(Shell *shell);

……

除此之外,lettershell还支持设置快捷键来快速调用函数执行,比如这里我设置在终端中按下Ctrl+A之后调用hello函数,过程如下:

① 首先在串口中断中添加一行代码,查看按下Ctrl+A快捷键之后串口发送的键值:

编译下载,在终端软件中按下Ctrl+A,得到的键值如下:

此处需要注意,终端发送的字符串序列以大端模式表示,所以单片机解析的键值应该为0x01000000。

② 一行代码搞定键值解析,在main文件中添加测试函数hello,并使用宏定义向lettershell中添加按键解析值:

int hello()

{

printf('Hello Worldrn');

return 0;

}

SHELL_EXPORT_KEY(SHELL_CMD_PERMISSION(0), 0x01000000, hello, hello);

将之前串口接收中断函数中添加的printt注释,编译,下载:

4.2. 如何解析命令

在letter shell中,当它接收到一个字符后,首先判断是不是特殊的按键,否则直接扔进命令解析缓冲区,因为它对左右方向键、退格键处理的比较好,相当于不停的在维护命令解析缓冲区的内容,非常整齐,解析时就变得相对容易了。

当用户按下回车键时,首先去命令列表中匹配:

/**

* @brief shell匹配命令

*

* @param shell shell对象

* @param cmd 命令

* @param base 匹配命令表基址

* @param compareLength 匹配字符串长度

* @return ShellCommand* 匹配到的命令

*/

ShellCommand* shellSeekCommand(Shell *shell,

                               const char *cmd,

                               ShellCommand *base,

                               unsigned short compareLength)

如果匹配到之后则开始执行这条命令:

/**

* @brief shell运行命令

*

* @param shell shell对象

* @param command 命令

*/

static void shellRunCommand(Shell *shell, ShellCommand *command)

限于文章篇幅,具体解析的过程和方法不再深入讲述,如有兴趣可以自行研究学习,另外,=除了本文所讲述的自定义函数调用,自定义快捷键设置,letter shell还有非常多的功能,比如自定义变量查看,用户登录,用户权限,自动锁定shell等高级内容,也欢迎有兴趣的读者探索,研究!


进入单片机查看更多内容>>
相关视频
  • 【TI MSPM0 应用实战】智能小车+工业角度编码器+血氧仪+烟雾探测器!硬核参考设计详解!

  • 2022 Digi-Key KOL 系列: 你见过1GHz主频的单片机吗?Teensy 4.1开发板介绍

  • TI 新一代 C2000™ 微控制器:全方位助力伺服及马达驱动应用

  • MSP430电容触摸技术 - 防水Demo演示

  • 直播回放: Microchip Timberwolf™ 音频处理器在线研讨会

  • 基于灵动MM32W0系列MCU的指夹血氧仪控制及OTA升级应用方案分享

精选电路图
  • 1瓦线性调频增强器

  • 家用电器遥控器

  • 12V 转 28V DC-DC 变换器(基于 LM2585)

  • 红外开关

  • DS1669数字电位器

  • HA1377 桥式放大器 BCL 电容 17W(汽车音频)

    相关电子头条文章