2025-04-29
电子
0

目录

下载Letter Shell
下载SEGGER RTT
移植SEGGER RTT
移植Letter Shell
新建shell_port.h和.c文件
修改shell_cfg.h
调用shell
编译报错
将RTT对接到终端

未标题-1.png 最近了解到了Letter Shell这个轻量级的命令行,看了一下官方给出的例程,可以移植到SEGGER RTT上,于是尝试了一下,记录一下过程。

下载Letter Shell

首先从GitHub上拉取Letter Shell的源码:https://github.com/NevermindZZT/letter-shell
建议将Github上的说明文档仔细看一遍再参考我下面的教程。

下载SEGGER RTT

然后从JLink的安装目录下将SEGGER RTT的库文件拷贝出来,库文件一般在Jlink安装目录/Samples/RTT下面

移植SEGGER RTT

将RTT的库文件拷贝到你的工程里面,设置好文件路径,在文件中包含RTT的头文件SEGGER_RTT.h ,然后在main函数中调用SEGGER_RTT_printf(),随意打印一些东西,编译下载运行。插上Jlink,用J-Link RTT Viewer连接单片机,如果能够打印出东西,说明RTT已经移植成功。

运行结果如下:

移植Letter Shell

同样将Letter Shell的库文件拷贝到你的工程中,并设置好文件路径。

新建shell_port.h和.c文件

shell_port.h和.c文件用于shell和外部接口的对接,包括读写函数和初始化shell的函数。这里是将shell对接到了RTT的通道0上面。我的这两个文件如下:

shell_port.h:

c
/** * @file shell_port.h * @author Letter (NevermindZZT@gmail.com) * @brief * @version 0.1 * @date 2019-02-22 * * @copyright (c) 2019 Letter * */ /* 加在.rodata : _shell_command_start = .; KEEP (*(shellCommand)) _shell_command_end = .; */ #ifndef __SHELL_PORT_H__ #define __SHELL_PORT_H__ #include "shell.h" #include <stdbool.h> extern Shell rttShell; void rttShellInit(void); short rttShellRead(char *data, unsigned short len); short rttShellWrite(char *data, unsigned short len); #endif

shell_port.c:

c
/** * @file shell_port.c * @author Letter (NevermindZZT@gmail.com) * @brief * @version 0.1 * @date 2019-02-22 * * @copyright (c) 2019 Letter * */ #include "shell.h" #include "shell_port.h" #include "SEGGER_RTT.h" Shell rttShell; char rttShellBuffer[512]; /** * @brief rtt shell写 * * @param data 数据 * @param len 数据长度 * * @return short 实际写入的数据长度 */ short rttShellWrite(char *data, unsigned short len) { static int blocked = 0; unsigned int time = HAL_GetTick(); short avail = 0; short write = 0; short wrote = 0; do { avail = SEGGER_RTT_GetAvailWriteSpace(0); if (avail <= 0) { if (blocked > 10) { return 0; } HAL_Delay(1); blocked++; } else { blocked = 0; write = avail >= len ? len : avail; SEGGER_RTT_Write(0, data, write); data += write; len -= write; wrote += write; } } while (len > 0 && HAL_GetTick() - time < 10); return wrote; } /** * @brief rtt shell读 * * @param data 数据 * @param len 数据长度 * * @return short 实际读取的数据长度 */ short rttShellRead(char *data, unsigned short len) { return SEGGER_RTT_Read(0, data, len); } /** * @brief 用户shell初始化 * */ void rttShellInit(void) { rttShell.write = rttShellWrite; rttShell.read = rttShellRead; shellInit(&rttShell, rttShellBuffer, 512); }

修改shell_cfg.h

然后根据需要修改shell_cfg.h中的配置,我的配置如下:

c
/** * @file shell_cfg.h * @author Letter (nevermindzzt@gmail.com) * @brief shell config * @version 3.0.0 * @date 2019-12-31 * * @copyright (c) 2019 Letter * */ #include "main.h" #ifndef __SHELL_CFG_H__ #define __SHELL_CFG_H__ #ifdef SHELL_CFG_USER #include SHELL_CFG_USER #endif #ifndef SHELL_TASK_WHILE /** * @brief 是否使用默认shell任务while循环 * 使能此宏,则`shellTask()`函数会一直循环读取输入,一般使用操作系统建立shell * 任务时使能此宏,关闭此宏的情况下,一般适用于无操作系统,在主循环中调用`shellTask()` */ #define SHELL_TASK_WHILE 0 #endif /** SHELL_TASK_WHILE */ #ifndef SHELL_USING_CMD_EXPORT /** * @brief 是否使用命令导出方式 * 使能此宏后,可以使用`SHELL_EXPORT_CMD()`等导出命令 * 定义shell命令,关闭此宏的情况下,需要使用命令表的方式 */ #define SHELL_USING_CMD_EXPORT 1 #endif /** SHELL_USING_CMD_EXPORT */ #ifndef SHELL_USING_COMPANION /** * @brief 是否使用shell伴生对象 * 一些扩展的组件(文件系统支持,日志工具等)需要使用伴生对象 */ #define SHELL_USING_COMPANION 0 #endif /** SHELL_USING_COMPANION */ #ifndef SHELL_SUPPORT_END_LINE /** * @brief 支持shell尾行模式 */ #define SHELL_SUPPORT_END_LINE 0 #endif /** SHELL_SUPPORT_END_LINE */ #ifndef SHELL_HELP_LIST_USER /** * @brief 是否在输出命令列表中列出用户 */ #define SHELL_HELP_LIST_USER 0 #endif /** SHELL_HELP_LIST_USER */ #ifndef SHELL_HELP_LIST_VAR /** * @brief 是否在输出命令列表中列出变量 */ #define SHELL_HELP_LIST_VAR 0 #endif /** SHELL_HELP_LIST_VAR */ #ifndef SHELL_HELP_LIST_KEY /** * @brief 是否在输出命令列表中列出按键 */ #define SHELL_HELP_LIST_KEY 0 #endif /** SHELL_HELP_LIST_KEY */ #ifndef SHELL_HELP_SHOW_PERMISSION /** * @brief 是否在输出命令列表中展示命令权限 */ #define SHELL_HELP_SHOW_PERMISSION 1 #endif /** SHELL_HELP_SHOW_PERMISSION */ #ifndef SHELL_ENTER_LF /** * @brief 使用LF作为命令行回车触发 * 可以和SHELL_ENTER_CR同时开启 */ #define SHELL_ENTER_LF 1 #endif /** SHELL_ENTER_LF */ #ifndef SHELL_ENTER_CR /** * @brief 使用CR作为命令行回车触发 * 可以和SHELL_ENTER_LF同时开启 */ #define SHELL_ENTER_CR 1 #endif /** SHELL_ENTER_CR */ #ifndef SHELL_ENTER_CRLF /** * @brief 使用CRLF作为命令行回车触发 * 不可以和SHELL_ENTER_LF或SHELL_ENTER_CR同时开启 */ #define SHELL_ENTER_CRLF 0 #endif /** SHELL_ENTER_CRLF */ #ifndef SHELL_EXEC_UNDEF_FUNC /** * @brief 使用执行未导出函数的功能 * 启用后,可以通过`exec [addr] [args]`直接执行对应地址的函数 * @attention 如果地址错误,可能会直接引起程序崩溃 */ #define SHELL_EXEC_UNDEF_FUNC 0 #endif /** SHELL_EXEC_UNDEF_FUNC */ #ifndef SHELL_PARAMETER_MAX_NUMBER /** * @brief shell命令参数最大数量 * 包含命令名在内,超过16个参数并且使用了参数自动转换的情况下,需要修改源码 */ #define SHELL_PARAMETER_MAX_NUMBER 8 #endif /** SHELL_PARAMETER_MAX_NUMBER */ #ifndef SHELL_HISTORY_MAX_NUMBER /** * @brief 历史命令记录数量 */ #define SHELL_HISTORY_MAX_NUMBER 5 #endif /** SHELL_HISTORY_MAX_NUMBER */ #ifndef SHELL_DOUBLE_CLICK_TIME /** * @brief 双击间隔(ms) * 使能宏`SHELL_LONG_HELP`后此宏生效,定义双击tab补全help的时间间隔 */ #define SHELL_DOUBLE_CLICK_TIME 200 #endif /** SHELL_DOUBLE_CLICK_TIME */ #ifndef SHELL_QUICK_HELP /** * @brief 快速帮助 * 作用于双击tab的场景,当使能此宏时,双击tab不会对命令进行help补全,而是直接显示对应命令的帮助信息 */ #define SHELL_QUICK_HELP 1 #endif /** SHELL_QUICK_HELP */ #ifndef SHELL_KEEP_RETURN_VALUE /** * @brief 保存命令返回值 * 开启后会默认定义一个`RETVAL`变量,会保存上一次命令执行的返回值,可以在随后的命令中进行调用 * 如果命令的`SHELL_CMD_DISABLE_RETURN`标志被设置,则该命令不会更新`RETVAL` */ #define SHELL_KEEP_RETURN_VALUE 0 #endif /** SHELL_KEEP_RETURN_VALUE */ #ifndef SHELL_MAX_NUMBER /** * @brief 管理的最大shell数量 */ #define SHELL_MAX_NUMBER 5 #endif /** SHELL_MAX_NUMBER */ #ifndef SHELL_PRINT_BUFFER /** * @brief shell格式化输出的缓冲大小 * 为0时不使用shell格式化输出 */ #define SHELL_PRINT_BUFFER 128 #endif /** SHELL_PRINT_BUFFER */ #ifndef SHELL_SCAN_BUFFER /** * @brief shell格式化输入的缓冲大小 * 为0时不使用shell格式化输入 * @note shell格式化输入会阻塞shellTask, 仅适用于在有操作系统的情况下使用 */ #define SHELL_SCAN_BUFFER 0 #endif /** SHELL_SCAN_BUFFER */ #ifndef SHELL_GET_TICK /** * @brief 获取系统时间(ms) * 定义此宏为获取系统Tick,如`HAL_GetTick()` * @note 此宏不定义时无法使用双击tab补全命令help,无法使用shell超时锁定 */ #define SHELL_GET_TICK() HAL_GetTick() #endif /** SHELL_GET_TICK */ #ifndef SHELL_USING_LOCK /** * @brief 使用锁 * @note 使用shell锁时,需要对加锁和解锁进行实现 */ #define SHELL_USING_LOCK 0 #endif /** SHELL_USING_LOCK */ #ifndef SHELL_MALLOC /** * @brief shell内存分配 * shell本身不需要此接口,若使用shell伴生对象,需要进行定义 */ #define SHELL_MALLOC(size) 0 #endif /** SHELL_MALLOC */ #ifndef SHELL_FREE /** * @brief shell内存释放 * shell本身不需要此接口,若使用shell伴生对象,需要进行定义 */ #define SHELL_FREE(obj) 0 #endif /** SHELL_FREE */ #ifndef SHELL_SHOW_INFO /** * @brief 是否显示shell信息 */ #define SHELL_SHOW_INFO 1 #endif /** SHELL_SHOW_INFO */ #ifndef SHELL_CLS_WHEN_LOGIN /** * @brief 是否在登录后清除命令行 */ #define SHELL_CLS_WHEN_LOGIN 1 #endif /** SHELL_CLS_WHEN_LOGIN */ #ifndef SHELL_DEFAULT_USER /** * @brief shell默认用户 */ #define SHELL_DEFAULT_USER "letter-shell" #endif /** SHELL_DEFAULT_USER */ #ifndef SHELL_DEFAULT_USER_PASSWORD /** * @brief shell默认用户密码 * 若默认用户不需要密码,设为"" */ #define SHELL_DEFAULT_USER_PASSWORD "" #endif /** SHELL_DEFAULT_USER_PASSWORD */ #ifndef SHELL_LOCK_TIMEOUT /** * @brief shell自动锁定超时 * shell当前用户密码有效的时候生效,超时后会自动重新锁定shell * 设置为0时关闭自动锁定功能,时间单位为`SHELL_GET_TICK()`单位 * @note 使用超时锁定必须保证`SHELL_GET_TICK()`有效 */ #define SHELL_LOCK_TIMEOUT 0 * 60 * 1000 #endif /** SHELL_LOCK_TIMEOUT */ #ifndef SHELL_USING_FUNC_SIGNATURE /** * @brief 使用函数签名 * 使能后,可以在声明命令时,指定函数的签名,shell 会根据函数签名进行参数转换, * 而不是自动判断参数的类型,如果参数和函数签名不匹配,会停止执行命令 */ #define SHELL_USING_FUNC_SIGNATURE 0 #endif /** SHELL_USING_FUNC_SIGNATURE */ #ifndef SHELL_SUPPORT_ARRAY_PARAM /** * @brief 支持数组参数 * 使能后,可以在命令中使用数组参数,如`cmd [1,2,3]` * 需要使能 `SHELL_USING_FUNC_SIGNATURE` 宏,并且配置 `SHELL_MALLOC`, `SHELL_FREE` */ #define SHELL_SUPPORT_ARRAY_PARAM 0 #endif /** SHELL_SUPPORT_ARRAY_PARAM */ #endif

调用shell

首先初始化shell,直接调用刚才在shell_port.c中实现的函数:

c
rttShellInit(); // 初始化RTT Shell

接下来就是调用shell任务。官方说明文档给出了调用shell任务的几种方式:

因为我这边是裸机开发,所以我选择了调用shellHandler的方式,然后在whlie(1)中简单实现一个定时调度的状态机,给shellHandler分配了一个任务:

编译报错

如果在编译的时候,出现类似的错误:

参考官方文档的说明,添加对应的参数:
因为我使用的是GCC编译器,所以在链接文件中添加如下内容:
解决完上面的问题之后,编译下载,就可以在RTT Viewer中看到shell的界面了:

将RTT对接到终端

为了方便使用,我们可以选择将RTT对接到终端,这样就可以直接在终端中使用shell了。因为J-Link RTT Viewer中只能输入单个字符,不便于我们输入一长串指令,也不方便我们修改,所以对接到正经的终端中使用还是挺有必要的。
这里我使用了Letter Shell官方推荐的rtt转发telnet工具Rtt2Telnethttps://github.com/mcujackson/Rtt2Telnet 设置好Rtt2Telnet的目标芯片和连接速度后,点击开启,就会在本地的6198端口上开启RTT转发,使用支持telnet连接的终端即可连接到RTT。我这里使用的是MobaXterm,配置成功的结果如下:

原文链接:https://zxcli.fun/2025/04/26/将你的Letter-Shell对接到SEGGER-RTT上/

本文由原作者授权于本站联合发布

本文作者:自行车等等我

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 Polari_S_tation 版权所有 许可协议。转载请注明出处!