将你的 Letter Shell 对接到 SEGGER RTT 上
最近了解到了 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 上面。我的这两个文件如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32/**
* @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 = .;
*/
extern Shell rttShell;
void rttShellInit(void);
short rttShellRead(char *data, unsigned short len);
short rttShellWrite(char *data, unsigned short len);1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77/**
* @file shell_port.c
* @author Letter (NevermindZZT@gmail.com)
* @brief
* @version 0.1
* @date 2019-02-22
*
* @copyright (c) 2019 Letter
*
*/
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 中的配置,我的配置如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270/**
* @file shell_cfg.h
* @author Letter (nevermindzzt@gmail.com)
* @brief shell config
* @version 3.0.0
* @date 2019-12-31
*
* @copyright (c) 2019 Letter
*
*/
/**
* @brief 是否使用默认shell任务while循环
* 使能此宏,则`shellTask()`函数会一直循环读取输入,一般使用操作系统建立shell
* 任务时使能此宏,关闭此宏的情况下,一般适用于无操作系统,在主循环中调用`shellTask()`
*/
/**
* @brief 是否使用命令导出方式
* 使能此宏后,可以使用`SHELL_EXPORT_CMD()`等导出命令
* 定义shell命令,关闭此宏的情况下,需要使用命令表的方式
*/
/**
* @brief 是否使用shell伴生对象
* 一些扩展的组件(文件系统支持,日志工具等)需要使用伴生对象
*/
/**
* @brief 支持shell尾行模式
*/
/**
* @brief 是否在输出命令列表中列出用户
*/
/**
* @brief 是否在输出命令列表中列出变量
*/
/**
* @brief 是否在输出命令列表中列出按键
*/
/**
* @brief 是否在输出命令列表中展示命令权限
*/
/**
* @brief 使用LF作为命令行回车触发
* 可以和SHELL_ENTER_CR同时开启
*/
/**
* @brief 使用CR作为命令行回车触发
* 可以和SHELL_ENTER_LF同时开启
*/
/**
* @brief 使用CRLF作为命令行回车触发
* 不可以和SHELL_ENTER_LF或SHELL_ENTER_CR同时开启
*/
/**
* @brief 使用执行未导出函数的功能
* 启用后,可以通过`exec [addr] [args]`直接执行对应地址的函数
* @attention 如果地址错误,可能会直接引起程序崩溃
*/
/**
* @brief shell命令参数最大数量
* 包含命令名在内,超过16个参数并且使用了参数自动转换的情况下,需要修改源码
*/
/**
* @brief 历史命令记录数量
*/
/**
* @brief 双击间隔(ms)
* 使能宏`SHELL_LONG_HELP`后此宏生效,定义双击tab补全help的时间间隔
*/
/**
* @brief 快速帮助
* 作用于双击tab的场景,当使能此宏时,双击tab不会对命令进行help补全,而是直接显示对应命令的帮助信息
*/
/**
* @brief 保存命令返回值
* 开启后会默认定义一个`RETVAL`变量,会保存上一次命令执行的返回值,可以在随后的命令中进行调用
* 如果命令的`SHELL_CMD_DISABLE_RETURN`标志被设置,则该命令不会更新`RETVAL`
*/
/**
* @brief 管理的最大shell数量
*/
/**
* @brief shell格式化输出的缓冲大小
* 为0时不使用shell格式化输出
*/
/**
* @brief shell格式化输入的缓冲大小
* 为0时不使用shell格式化输入
* @note shell格式化输入会阻塞shellTask, 仅适用于在有操作系统的情况下使用
*/
/**
* @brief 获取系统时间(ms)
* 定义此宏为获取系统Tick,如`HAL_GetTick()`
* @note 此宏不定义时无法使用双击tab补全命令help,无法使用shell超时锁定
*/
/**
* @brief 使用锁
* @note 使用shell锁时,需要对加锁和解锁进行实现
*/
/**
* @brief shell内存分配
* shell本身不需要此接口,若使用shell伴生对象,需要进行定义
*/
/**
* @brief shell内存释放
* shell本身不需要此接口,若使用shell伴生对象,需要进行定义
*/
/**
* @brief 是否显示shell信息
*/
/**
* @brief 是否在登录后清除命令行
*/
/**
* @brief shell默认用户
*/
/**
* @brief shell默认用户密码
* 若默认用户不需要密码,设为""
*/
/**
* @brief shell自动锁定超时
* shell当前用户密码有效的时候生效,超时后会自动重新锁定shell
* 设置为0时关闭自动锁定功能,时间单位为`SHELL_GET_TICK()`单位
* @note 使用超时锁定必须保证`SHELL_GET_TICK()`有效
*/
/**
* @brief 使用函数签名
* 使能后,可以在声明命令时,指定函数的签名,shell 会根据函数签名进行参数转换,
* 而不是自动判断参数的类型,如果参数和函数签名不匹配,会停止执行命令
*/
/**
* @brief 支持数组参数
* 使能后,可以在命令中使用数组参数,如`cmd [1,2,3]`
* 需要使能 `SHELL_USING_FUNC_SIGNATURE` 宏,并且配置 `SHELL_MALLOC`, `SHELL_FREE`
*/
调用 shell
首先初始化 shell, 直接调用刚才在 shell_port.c 中实现的函数:1
rttShellInit(); // 初始化RTT Shell
接下来就是调用 shell 任务。官方说明文档给出了调用 shell 任务的几种方式:

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

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

参考官方文档的说明,添加对应的参数:

因为我使用的是 GCC 编译器,所以在链接文件中添加如下内容:

解决完上面的问题之后,编译下载,就可以在 RTT Viewer 中看到 shell 的界面了:

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