前言 T1 p/ A4 r* g
一般使用串口来打印调试信息之类的,正点原子的USMART也不错,这边引入了一个类似于Linux 命令行的调试功能,Letter Shell,功能很强大,github的链接在下面。. U# m- s) v9 n* z" w8 e
我们要移植一下这个功能,方便后面的调试。这个功能使用到了串口,接收到串口数据后回传到上位机,直到接收到命令行回车触发,也就是0x0A(LF 换行)以及0x0D(CR 回车),不同的软件,结束符可能也有点区别。确认收到完整的命令,就可以解析命令,根据不同函数的地址就可以进行调用了,这样子调试程序就很方便了。6 P4 |, L/ W9 b" V8 c! H
效果图如下:
7 L# s5 }8 f6 a2 r4 ^7 U* ?* g% `8 k; O1 B; |
7 l+ F! D5 k7 `! X: q. R5 |# c8 x8 E7 G0 u7 `& E8 j/ h2 V
一、FreeRTOS配置
5 |- f; @# s" k% D% `6 Z8 h; x1.FreeRTOS
; d% @6 G* N ~" oLetter Shell 仅需要用到串口,然后使用shell 的回调命令,就可以使用了,但是后续我们需要使用到FreeRTOS,这就涉及到中断优先级的问题了,STM32CubeMX有16个优先级,0为最高优先级,15为最低优先级,FreeRTOS可以管理管理包括优先级5-16的中断,我们这里先配置一下FreeRTOS,暂时不需要其他功能,默认配置有一个defaultTask的线程,我们用这个来控制LED以指示系统的正常运行,所以这个按照默认配置就可以了。
; g5 L2 k" Q0 h! I3 q/ N
$ C0 o% q; ~4 f/ p# a/ D5 _
5 `7 q `7 X, L' C
7 V4 j& F( P) d
FreeRTOS需要使用时钟基,默认是SysTick,滴答定时器,默认配置是1ms中断一次,然后计时器+1,但是SysTick并不是很准确,我们可以在SYS 选项卡中改一下Timebase Source,这里我们改为定时器3当时钟基。
. P/ p$ I4 T1 b. ~
0 u2 P9 F4 A9 q, V8 i6 [% V8 H5 v9 g( ?. e5 z
然后配置一下串口,因为Nucleo板载了ST-LINK V2.1,有一个虚拟串口连接到USART3,所以使能USART3,然后使能中断就可以了。点击GENERATE CODE生成代码。) F5 @3 q1 D0 l: A
& ?+ Z% p! B* e& d2 }
5 l/ {2 T& m* O G3 o- N0 u5 C9 m% p: b0 L7 r( T. l: b: |
先打开 “freertos.c”,里面有一个默认的线程,StartDefaultTask,我们在这里加一个LED闪烁的功能,先测试一下生成的工程没问题。
" L' O3 r/ d7 L' u: i5 q
0 L- z8 ~) r0 C) x- /* USER CODE BEGIN Header_StartDefaultTask */0 {& Z& B3 k, U
- /**
3 d9 m" @& ~7 |3 j0 u7 {$ z; O2 G l - * @brief Function implementing the defaultTask thread.+ x, k \+ Z* Z
- * @param argument: Not used
) ?3 U8 o# w( _& X. g& w - * @retval None
- O$ ], }! r, U$ j2 S1 A - */
9 t3 l$ x- c5 `% I; { - /* USER CODE END Header_StartDefaultTask *// G1 K; i7 k+ }* l
- void StartDefaultTask(void *argument)" U& c$ L( L6 _9 w2 f
- {& U b" g7 ]& ]' V
- /* USER CODE BEGIN StartDefaultTask */2 S! ?* s4 I# ?+ d5 G; I6 |) } N. L
- /* Infinite loop */
/ C( v0 K: c/ n" D - for(;;)
0 s' T3 B2 W6 ?- O+ E1 c - {
" j+ f# N5 m/ j - HAL_GPIO_TogglePin(LD3_GPIO_Port,LD3_Pin);
* X6 I) V9 \% U7 N G% E - osDelay(100);
# Q9 g2 s# J& P: }, M, J. m3 E - }0 K, H- Z, R, d$ |( ]0 E5 @
- /* USER CODE END StartDefaultTask */+ X" z, M8 Z5 z }6 V& }
- }$ z( }8 o8 p; z: D5 C7 D1 f' r
复制代码
1 e; K* H7 ?5 d# m) ~. C$ r注意,配置的Keil工程,默认是没有设置下载完自动复位,我们需要按一下Nucleo 板子上面黑色的复位按钮,另外默认的配置下,有一个以太网的初始化 “MX_ETH_Init()”,需要接入网线到路由器,不然初始化不通过,会跳转到Error_Handler();- if (HAL_ETH_Init(&heth) != HAL_OK)
+ d: a: n7 o) k6 A6 M - {
$ {" X5 y7 ~ t% ]' V8 |1 G& h - Error_Handler();
0 A% X$ c8 @& x; d - }
复制代码
6 _5 n% K3 `, Y3 w$ m" ]) t# V6 j4 k二、移植Letter Shell& x2 ?' K0 ~+ k: t+ u$ @
下载github上面的源代码,解压出来。
3 w( i1 B/ U# M# ?
3 I6 f5 [- ] u6 }' t# |4 B/ N
3 |$ S# `+ M3 ~- F ], @# C( Q* q* k6 [
3 s7 i; y1 [# V5 \( R, w- jdemo 有一些平台上面的例程
$ |2 G% G# G3 zdoc 是功能演示& j0 c- a' Q, e4 T. y
extensions 有一些扩展功能0 b2 S h$ k. d2 T$ h/ Q
src 是源代码
) K7 j4 v# g2 q# B( p' _tools 是一个用于遍历工程中命令导出的工具,位于tools/shellTools.py,需要python3环境运行,可以列出工程中,所有使用SHELL_EXPORT_XXX导出的命令名,以及位置,结合VS Code可以直接进行跳转5 x" O# J3 w- |, |" S
( [( ]; q, i, }% q9 W我们在STM32Cube 的工程项目下新建一个文件夹 Shell,然后把 src文件夹下的源代码都放进去,遵守开源公约,LICENSE也要记得放进去。放进去之后,demo\stm32-freertos 目录下还有三个文件,我们也拷贝过去覆盖即可。2 l# n0 n3 X- f- I6 h
1 P k! G P+ g4 p. ]4 C
0 h% q" K' O) o
( F1 }; o* V7 f, \/ y然后在keil工程下添加这些文件。
7 p& w$ [* K, N2 H3 f3 j/ G* j1 |$ F- F# j! K
; d5 ~ U) B' k4 y
3 U7 |6 Q% k$ q$ c' v/ p7 Y) g- o
编译一下,此时会报错
1 g( ]+ f' t0 O: k# g7 |% n$ U
; m/ y& X8 R* ~. o8 |* e- ..\Shell\shell_cfg.h(15): error: #5: cannot open source input file "stm32f4xx_hal.h": No such file or directory
复制代码 y' G; u% H+ ~6 T1 r* k
原因是letter shell的demo使用的是F4的板子, “shell_cfg.h”,里面包含了f4的头文件,#include “stm32f4xx_hal.h” 。我们这是 F7的板子,并且是STM32CubeMX生成的工程文件,所以我们只要替换成 #include “main.h”,这样改了一次以后,以后换芯片系列也能很方便的移植了。再次编译,仍然会报错。
. X% y* {3 K5 a' F
, o) G* r* L4 s- ..\Shell\shell_port.c(13): error: #5: cannot open source input file "serial.h": No such file or directory
复制代码 2 v% T! G& Q: a# `- h$ x6 m
这是因为 “shell_port.c” 中有 serial.h,看名字,这是与串口相关的文件,我们把这种不需要的头文件删掉,只留下两个头文件就可以了。
" w' q! [9 ?4 P8 z9 @1 T6 I# Y: i' f: m' k5 O4 J0 Z2 m
- #include "shell.h"
1 |1 B- G. n* }/ `9 l - #include "usart.h"
复制代码
+ `; L5 H/ ^" u# M8 Y下面两个函数是shell读写的实现,里面使用的是固件库的函数。
2 ]. F+ V& u [- A+ N( {0 W% x, k: X0 G1 a, D7 g% ]% K0 d* P m
- /**8 M; H5 t6 p! J8 k2 t0 H# G& ]
- * @brief 用户shell写
$ E' h" ]' g, y" @1 f - *
W% W" Z; x( M - * @param data 数据
4 `2 S* O6 S" Q/ z6 w$ m! L* \ - */
! L# }. k" m0 F( M& y- D1 B - void userShellWrite(char data)
: C6 c U) T& K: E - {
) U2 @+ l- k' s% b - serialTransmit(&debugSerial, (uint8_t *)&data, 1, 0xFF);
9 x2 F# [& Y% T/ E! I1 p( n - }
, G4 H8 P5 D/ q! ?) G9 J( L5 A& f
' C+ a4 q9 H4 r- F4 a( C- /** X: \- \7 f: Y# ?7 Z
- * @brief 用户shell读8 k: M7 A' A6 [2 \5 Z( Y
- * 8 m9 W+ t; u4 ^8 A" g
- * @param data 数据
" Q& s1 V7 x' g& v# i - * @return char 状态
+ {" Y' k1 O/ S8 Q - */ X. f) r& o) I% m% K
- signed char userShellRead(char *data)
6 |9 T7 ?, M4 y' p - {% |. |0 C7 m; ?; Y
- if (serialReceive(&debugSerial, (uint8_t *)data, 1, 0) == 1)
" j. q# h8 p l& T - {4 t$ E& T1 @& u& r& ^9 a$ {& p
- return 0;
1 l$ g# x" b& E4 `' g - }
7 j, D( H& x7 Y5 L$ D - else
6 o1 i+ g4 C: K: |3 ?! W - { z# k1 z/ X2 y1 d7 o* J
- return -1;
6 ~) q ~ i/ K; D6 s* y - }, a: e! g) S/ t$ W) @* F
- 8 v2 }. x: S( M6 o
- }
复制代码 ; [! k! B* T3 H2 h
既然使用了STM32CubeMX来生成工程,这里我们改成HAL库的形式。
# z$ _4 k5 y0 M! @. Z9 J9 x2 Y9 Q& `" |. J# D/ E9 g
- /**
. o0 C1 ]" @; z7 M - * @brief 用户shell写1 P% b& U& K" ~ m5 |9 l& j u
- *
& b) n: ?9 {# }6 q; n/ X - * @param data 数据
& z. V5 ^5 a, m: D - */
& M) o2 @2 Z2 p, ~0 \: [ - void userShellWrite(char data), g9 L2 e4 s$ D7 }$ f
- {
% X% B' ~' t3 ~5 T5 @: x4 c4 ?0 ?; |7 V - HAL_UART_Transmit(&huart3,(uint8_t *)&data, 1,1000);% b) \1 |# V/ g
- }2 C0 B n4 l# Z; ~& I8 ^% }
- # s# A# }9 ^' b3 d
- : w2 s, ^- R3 n; v
- /**, l+ h& m2 Z X( l* k% R2 b' ?+ R
- * @brief 用户shell读' C: E* b' s: Z) C( V2 {' `
- *
' S! F! |& h6 M6 P' C - * @param data 数据
/ j0 o- Y& s/ o* \ - * @return char 状态
$ k# T3 G- L' z - */
# Y' b' D @& B& O: U0 t* k& ? - signed char userShellRead(char *data)
' q+ C" B6 ~, ? i; a5 _ - {# i V$ V6 ?4 H/ U$ q
- if(HAL_UART_Receive(&huart3,(uint8_t *)data, 1, 0) == HAL_OK)/ \# O8 q9 e2 c0 n6 o
- {
$ F+ b" v$ V& A+ N; f - return 0;
" u9 Y& |( Z7 {$ M% B M - }
6 Z' _) c5 ^% l2 b- Q - else
+ t4 H- S) c% e' B2 s - {
3 w4 E1 J0 J6 e3 d0 e* t/ k t - return -1;
) a& I; R- Q m1 F# \ q - }7 y- d0 {8 b0 E, i! U% b$ F7 X$ X
- }! x% Y- z& n/ m8 F
复制代码
+ \2 X3 T( Z" w8 @! p. r再次编译就不会出错了。
h/ h5 ^% o1 ~2 j: i# B W0 c
三、Letter Shell的使用- P. `6 k' q, V5 \2 e+ c _ ?
github 上有具体的使用说明。我这里是下载的最新版本,3.0.6,这里也是根据这个版本进行一些解释性的说明。
4 U2 z0 D; |) {8 O7 r$ U& s8 ]. p2 g1 t$ k, C, H
3.1 定义shell对象
. ?5 w8 ^/ x8 a$ g在 “shell_port.c” 中定义了 shell 的对象。这个Shell结构体定义了历史的输入,读写对象的函数指针等等。: f. h% t% j9 D0 T0 @
2 U' U9 X! p2 y( }: V5 Q* x
, F; G4 c4 C5 a. n$ e6 e: S
3.2 定义shell读写函数- ^3 o( r- i' ^8 j" e2 X; R
定义shell读,写函数,函数原型如下:6 } ?1 y9 M- g* j6 ~
7 a! Q9 A! C4 q6 M7 U* o( S- /**5 i( Q* J( ?' W0 R+ h
- * @brief shell读取数据函数原型
+ b' n3 Z' s D% A7 p/ w - *
+ S# C4 s _6 }5 s - * @param char shell读取的字符5 a1 m8 r9 B9 o7 N9 F5 B$ l
- *
9 e! j, A+ {, b: M6 H - * @return char 0 读取数据成功% M6 v/ A' V, o5 e' X' L
- * @return char -1 读取数据失败, p/ u! b: N% l5 [
- */
: r: g5 H$ a/ O- i2 L5 d6 q - typedef signed char (*shellRead)(char *);/ m5 o& E' K2 y* g# O
- , v+ T4 n; i. I' B2 m; n& J
- /**
! D: W0 b$ u% T( C - * @brief shell写数据函数原型
; G5 w+ |+ n! z, | - *
+ Q/ y; B# a$ d( N$ O. o. y - * @param const char 需写的字符
/ n* F( O$ c; U1 e( } - */
0 H1 R& n+ x" U/ @ - typedef void (*shellWrite)(const char);
& Y$ \; \5 K( t7 }) k
复制代码
$ d3 A3 k& [- ?我们在上面已经进行了修改。+ j) w* J+ a) f5 y" `6 P" f
# a! g9 ^: U; y3.3 申请缓存区& U# P4 [8 ?+ i9 b; s- _
申请一片缓冲区
6 H0 A! r$ T0 ^( n. m5 ?! \0 ?; E5 x' W; N, [8 q- v
5 H7 E8 T! W+ u) q) R
3.4 调用shellInit进行初始化
3 A4 J4 b) B* t0 `) b: L: \调用shellInit进行初始化,函数实现如下:3 A% t! q1 F% ^1 [+ `4 S
* n5 Z8 ~! {' ]
- /**. N- I [* p( o' G) p* ?
- * @brief 用户shell初始化" D8 j1 ^" A4 ]. X2 E
- * 9 w- U6 n. O7 b0 E. a; K J
- */
1 e3 N4 C- U/ R6 D" h8 ]0 r - void userShellInit(void)
4 T3 \$ E h5 T5 o- M* U& h# N4 e - {
: Y1 K) @% `0 l: i# C* N - shell.write = userShellWrite;
7 k$ q! Q8 D t) }& I - shell.read = userShellRead;
" J3 u% Q! `/ o; y8 K* b2 V - shellInit(&shell, shellBuffer, 512);
6 |6 y( g2 n$ K& E- {# _" v( g, D - }
复制代码
2 g5 ?3 e6 p- E1 h7 a. `3.5 初步完成shell的初始化$ O5 b4 C2 o9 S& k
以上步骤,除了要修改shell的读写函数,其他的在shell_port.c中都已经实现了,所以我们在main.c 添加头文件7 ^' W6 b# z9 R: \
: [( y+ E$ e" X U- _5 J% W) @
- /* USER CODE BEGIN Includes */! k/ Q# y7 q4 G0 o B
- #include "shell_port.h"
0 \+ Z* ~" [! A0 ?7 q( R# ? - /* USER CODE END Includes */
复制代码 0 G1 A/ y' S8 ^+ `3 h* }) g/ q
然后在 main 函数中初始化shell,基本上就移植完毕了。
5 E0 ]- V) A( D) `; a G3 R; L: N N9 X- m$ F5 H
- /* USER CODE BEGIN 2 */
* i# v- [- {5 q# \2 e$ u - userShellInit(); //letter shell 的初始化9 Y; D3 m% h- G. c5 ?
- /* USER CODE END 2 */
复制代码
# [2 H# y+ {4 V+ n9 }此时编译一下,还是报错,老样子,是因为没有 “serial.h” 文件,我们双击报错点,跳到异常点,把这个注释掉即可。然后再次编译,就没有报错了。9 N* [9 `8 N" x/ }
, w% m* X2 f& {- ..\Shell\shell_port.h(15): error: #5: cannot open source input file "serial.h": No such file or directory
复制代码 6 D) q. m5 |' b+ K1 ~( f
我们把程序下载到Nucleo。
6 L% }. L) T+ [7 Q然后打开SecureCRT 或者 MobaXterm 或者 “putty”,建议是使用MobaXterm,因为这个功能强大,且有免费试用版。
4 G& w% y" Y1 C" f3 O, [7 O
, c" d% `$ m3 O* o7 g! ~我们需要建立一个串口连接, SecureCRT 点击快速连接, MobaXterm 点击 Session ,然后找到串口的选项,串口的端口就是 STLINK-V2.1的虚拟串口, 波特率为115200,其他的默认不变,点击 OK9 F& Z1 @$ d% E$ g" P6 D
& p! a$ \2 o/ r" r* W
; ^$ l, u: c( k) |6 _: j
* G5 s0 x. e, l: e4 S由于我们还没编写串口接收方面的函数,按下字符是不会进行回传的,这里我们先按一下复位按钮,接上网线等到初始化完成后,就可以看到 letter shell 初始化完成后的串口信息。
9 c) \, ~/ V( d! |2 H
& ~: i$ J' o& _; t
& }' w2 N' ^1 F
3 F7 H' a; \. |: |四、Letter Shell的进一步了解% h' V4 g: C! R& f
第三节已经完成了Letter Shell的初始化,在这一步,已经能够shell初始化已经打印的功能,但是要想使用好letter shell,我们还需要对letter shell有进一步的了解。
# T: }; f. l T; Q0 @2 E3 m
3 v* s! f: ?* E+ M4.1 宏定义 ^. [+ I8 t4 Z/ O# X
在文件 “shell_cfg.h” 中定义了各种各样的宏,用户可以根据不同的需求来配置宏。
/ d. l M. V& r- y+ T5 c下面的表格列出了各个宏的意义。+ x0 j( p# c4 F+ g. v$ E
( c9 L& y O& V5 }& }: j) V P0 K
, x3 s8 N0 ~! F& N2 `( K. u6 M* }2 _9 s6 C4 e% h" T
在文件 “shell_cfg.h” 也有具体的说明。
4 h& N* m# z6 f! c* A* Y) u% R- I8 s" h: @9 t, M7 P
- /**
1 ?- U: E2 y e9 f7 p6 U- @ - * @brief 是否使用默认shell任务while循环,使能宏`SHELL_USING_TASK`后此宏有意义/ ? \3 ?) l! u9 Y" X6 e
- * 使能此宏,则`shellTask()`函数会一直循环读取输入,一般使用操作系统建立shell7 w. j! ]/ y3 `: f4 a) T
- * 任务时使能此宏,关闭此宏的情况下,一般适用于无操作系统,在主循环中调用`shellTask()`
9 L% k) x: j4 ?: z/ v4 ^ - */: A* g- x1 p+ U0 A3 }2 _
- #define SHELL_TASK_WHILE 1/ X/ n$ b9 W8 c3 t
7 K+ g3 ]( e0 K! P; r+ x: @- /**) z* ^+ E) o, V" [, L5 u% H# _1 `
- * @brief 是否使用命令导出方式' X! @) \5 o4 k% ?/ q, p8 d
- * 使能此宏后,可以使用`SHELL_EXPORT_CMD()`等导出命令( o2 A& r' A) F$ R* ], s/ {
- * 定义shell命令,关闭此宏的情况下,需要使用命令表的方式" v2 A1 n( }- `! n/ \
- */
) p5 _& M+ J1 |" O - #define SHELL_USING_CMD_EXPORT 1& r. A1 |7 H5 `$ n
" K- s0 e* S O$ t; A+ f6 e- /**
' @2 u; [( s- M+ u! d- E9 v - * @brief 是否使用shell伴生对象
. k8 r0 Y, `! x+ h+ Z - * 一些扩展的组件(文件系统支持,日志工具等)需要使用伴生对象
4 l" I; c/ z9 B' R - */( ?; A8 Z4 A2 c: U
- #define SHELL_USING_COMPANION 0
# a( D9 [* {- T6 B
% f3 X' ~' @4 {- /**1 h# H# k3 J/ [( _8 e
- * @brief 支持shell尾行模式
5 S# R5 Y. s: K6 F% {% S, l - */
5 d. a; f) w" r - #define SHELL_SUPPORT_END_LINE 0
$ [$ S3 |# h+ v4 r
+ u& h0 G* m0 j3 |, `: Q' K6 j+ y% Y- /**
1 Y& ?/ Y, b7 r% P( ?) i: K6 m0 l - * @brief 是否在输出命令列表中列出用户
! z5 Y7 ^# o! e8 Z/ g - */
" E- P) K. e5 {1 L- d5 _1 d$ @ - #define SHELL_HELP_LIST_USER 0
2 y% t! H: W# [0 u
) p3 b+ p; ~$ z, K& v- /**3 z. K; p, X! \8 U# q
- * @brief 是否在输出命令列表中列出变量2 I$ ]0 F p0 w* ^0 j
- */6 E: M3 Z# M/ {7 d5 T1 }
- #define SHELL_HELP_LIST_VAR 07 |( c7 T2 t9 q5 ~3 j1 }+ F& E/ D
* g5 L+ D) h0 ]1 V, T6 R- /**
. c5 L) f3 {& g% O - * @brief 是否在输出命令列表中列出按键
# w7 U; u" {5 B4 r( d - */
+ w1 o; M' K8 v' ` ?. g2 S - #define SHELL_HELP_LIST_KEY 0
! o/ X5 V4 x1 x7 |
6 J9 m- I4 g; r& B- /**
+ |3 }! G$ M ^2 @& e" S/ J - * @brief 是否在输出命令列表中展示命令权限( Y- k) C5 l; b2 J8 v# T" o
- */
$ I4 n9 b0 n; k2 y - #define SHELL_HELP_SHOW_PERMISSION 1 r% _0 w% s9 h! g
: U4 v; c( w+ ?4 k- /**( t0 G F# L, R8 _3 c. Y
- * @brief 使用LF作为命令行回车触发- }5 J# K* _' F
- * 可以和SHELL_ENTER_CR同时开启
* M' t1 n6 H, o( A* l& h; r. P - */: c& |+ X8 ]- f0 K/ ]
- #define SHELL_ENTER_LF 01 m" L6 P! N* Z0 }1 T7 q
- $ S% y8 q c, ]7 H3 S
- /**
. j/ R( @& g7 M/ u# _3 G+ {5 Q2 W! Z - * @brief 使用CR作为命令行回车触发; @/ |1 n- V9 K
- * 可以和SHELL_ENTER_LF同时开启2 E+ v6 Q; z) k `* v4 {
- */9 x( k) v0 o1 f7 Z# B/ Y. {( }7 x8 R
- #define SHELL_ENTER_CR 0
) y6 r" v0 O7 D8 C3 R5 \
6 D0 V. |+ w* t3 H! m9 Y- /**
! Q# a. e* U, v0 D) y% y0 g1 \ - * @brief 使用CRLF作为命令行回车触发7 L' g$ t$ I* ~5 D, ^
- * 不可以和SHELL_ENTER_LF或SHELL_ENTER_CR同时开启' H6 J C t0 @9 E
- */& q1 o+ H- g% z. F- w
- #define SHELL_ENTER_CRLF 1' Z1 _8 _% m; J- [
- 8 g+ ?4 O0 n6 C' x* A3 @( e
- /**+ x- Y$ {& V3 w$ j- o
- * @brief 使用执行未导出函数的功能. J4 f7 M U! {: e; Z9 s
- * 启用后,可以通过`exec [addr] [args]`直接执行对应地址的函数
# U9 ~# a% g) V$ [4 ]9 D" C* \, f" Z - * @attention 如果地址错误,可能会直接引起程序崩溃
! o+ b& v' L* B( v$ q! @ - */
1 [# ^) @: f8 c: ?! N8 \' a - #define SHELL_EXEC_UNDEF_FUNC 05 s o5 P+ I! T2 `2 i3 H" E4 e
- & }8 K8 C. x% S" N- L6 F! a2 e
- /**! U; f% Z3 e2 D3 S1 U- ?* Q
- * @brief shell命令参数最大数量. D* E7 x; X( `2 l) W) y
- * 包含命令名在内,超过8个参数并且使用了参数自动转换的情况下,需要修改源码
& I! l( ?8 Y8 O8 F/ \5 C! D - */: w" X# O s0 b3 B, @9 g
- #define SHELL_PARAMETER_MAX_NUMBER 8" \4 x& k( ~1 M# k: j3 D
% l+ I4 P6 X0 j- /**
) \0 X( q$ K8 s* q) s2 q - * @brief 历史命令记录数量
' ~. i( T) H1 |* E( ?" f% W - */
9 B4 P4 N9 Z' q - #define SHELL_HISTORY_MAX_NUMBER 5
3 \+ d" l# n( e. J% q
0 z. y$ s5 k3 g6 `- /**
/ J/ h7 Y4 s5 ^% y, s. R" z - * @brief 双击间隔(ms)
4 c6 Q7 U% {7 p9 ] - * 使能宏`SHELL_LONG_HELP`后此宏生效,定义双击tab补全help的时间间隔
) ]/ I: T- i* s+ ^" Y - */
& ?+ E- q! W6 Y1 @/ C2 K# Y - #define SHELL_DOUBLE_CLICK_TIME 200
) Z, c* S6 `* g1 K$ j - * Y1 A' P! k% `2 W+ x5 [" ~! H
- /**
; R1 l5 [' h1 K) n0 } - * @brief 管理的最大shell数量
l* }; }9 A0 W% E' x( r - */
, O1 ?% ? L7 H+ b: l - #define SHELL_MAX_NUMBER 5" U. x% f0 O- m4 ^1 V; L5 _. C
% G; p) |6 a5 U6 |1 I- /**
n6 { d" D; z# C - * @brief shell格式化输出的缓冲大小) d n, ]- h( y+ d
- * 为0时不使用shell格式化输出
$ K% T: q! h2 E# k9 b. I+ M9 `$ B - */
6 m; [9 w" X; X1 l - #define SHELL_PRINT_BUFFER 128
. {) d6 s. n w
8 R- @1 y! m9 Q- /**2 L3 a! p' r$ b( r& p
- * @brief 获取系统时间(ms)
- q9 ^( A5 O% W% e( y. e& \$ g - * 定义此宏为获取系统Tick,如`HAL_GetTick()`( O6 M6 t, N/ R- k9 z. n
- * @note 此宏不定义时无法使用双击tab补全命令help,无法使用shell超时锁定5 g: n" `' c) N* b
- */
$ @4 D% `; T6 y - #define SHELL_GET_TICK() HAL_GetTick()# g) Q9 u# T' V; a" f: d, m# @
* l7 V! F# K' ~) _- o! O' t. _; b- /**
% H! E* @! Q' |& H' s - * @brief shell内存分配
6 U& v' L2 ]2 n - * shell本身不需要此接口,若使用shell伴生对象,需要进行定义
" k7 ~- {8 [6 d+ Z2 ^ - */# S$ [* J9 i' j! \" C0 z: s& B& g/ V
- #define SHELL_MALLOC(size) 0
# d* x% j, E i' ~+ R. ^4 L
& X; z, k. A* Y% M5 Z$ H- /**
* ]; `7 Q+ Z. e' e. e - * @brief shell内存释放# a! E( r6 [+ W3 d3 E" Z3 c
- * shell本身不需要此接口,若使用shell伴生对象,需要进行定义
: U) U, |" y- b0 |: a - */
& p5 J' a7 f b2 P' O! I, c3 `& I - #define SHELL_FREE(obj) 0
2 O# h( A/ k8 \5 X6 o - . _" O1 \! {1 f: W; E9 k* _5 x
- /**& O, ?, S3 o# J( I4 s
- * @brief 是否显示shell信息
/ b8 B! Y" z1 x" o& R% @7 T2 V - *// _2 a! I% W3 z
- #define SHELL_SHOW_INFO 1
' C* o @: v. G( I! K
* ?! Q" y0 t7 U% a( l A- /**1 x. \4 v% u1 A4 ~" a ~+ j% N
- * @brief 是否在登录后清除命令行
8 s4 B4 E7 k& v2 l7 t+ Z - */
- I+ ?5 B3 b1 I- [ l, N" Z - #define SHELL_CLS_WHEN_LOGIN 1
3 h% `. K6 b* y: G - 9 B5 ]: c: f9 L1 }/ c1 V
- /**
& d5 h; q! m& s, l- x3 C" n# n - * @brief shell默认用户6 m0 r0 N( U- `, A0 h6 `, t: u
- */
e( h4 r* G: q: n4 p6 N - #define SHELL_DEFAULT_USER "letter"8 Y0 k6 l; b- ^
- & Y6 V6 t7 k* v9 Q
- /**$ O! J& X2 t2 {' S D1 A) ^- {' J( u
- * @brief shell默认用户密码
4 Y/ R" D0 t9 e, P, Y - * 若默认用户不需要密码,设为""
% N3 p1 A/ ~$ P( r6 o ?; _ - */: x4 Y8 a; Z1 Y- f" m; W' P
- #define SHELL_DEFAULT_USER_PASSWORD ""# a1 g& O/ \ Y& v$ G0 m& |/ j' `* B
K' r2 Z2 S9 F% ]: z- /**
6 E$ t, J: Z/ _' E: e9 `' _. S - * @brief shell自动锁定超时
0 V9 b: K: W# J) d1 F8 P - * shell当前用户密码有效的时候生效,超时后会自动重新锁定shell
4 \! P* S4 J; Z" [ - * 设置为0时关闭自动锁定功能,时间单位为`SHELL_GET_TICK()`单位
; B* f" S# D& `( z5 Y - * @note 使用超时锁定必须保证`SHELL_GET_TICK()`有效. Q# w3 s9 _0 H- Z# P; s
- */" \8 {! g4 ^: A8 ?. a3 K
- #define SHELL_LOCK_TIMEOUT 0 * 60 * 1000
. F8 m3 ?- _8 |
复制代码 ( A. b9 t: F+ E% j% [. X* @
下面介绍一下比较重要的几个宏:1 Q& T+ S/ `5 B3 W
* h& t7 ?$ l1 X, X( q
4.2 SHELL_TASK_WHILE; ]8 n2 X# P2 {6 u$ ~- A9 Z) `6 s
这个宏仅在这里面生效,这是在使用操作系统的时候使用到的,它会一直轮询地读取串口数据,然后调用 shellHandler 解析串口数据。这仅仅是个范例,因为这里没有进行任务切换,所以会一直堵塞在这里,而我们如果要使用FreeRTOS创建一个线程来处理这个任务,需要使用osDelay来进行切换。因为这里一次只读取一个字节,所以如果任务切换间隔过大,可能会丢失某些数据,另外,因为接收串口的时候要进行回显,所以任务间隔过大,也会影响操作体验,所以我们在串口接收中断调用 shellHandler 进行处理。我们不需要在线程中一直读取,所以这个宏 SHELL_TASK_WHILE 设为0,但是设为1也不影响就是了。! C' J; c/ ~5 @) c V5 v2 e# G
$ ?/ b# _0 j7 f1 n& z- /**5 z; T; q' B8 O+ E+ s
- * @brief shell 任务, Z, y0 C. m" u4 K/ }3 x
- *
" ^* A6 a+ f9 o9 D$ t - * @param param 参数(shell对象)" H# @ [; P3 A$ F' K" z9 Y
- * ; a8 u3 k. p+ h6 g6 V
- */4 Y/ R$ s$ p# x
- void shellTask(void *param)
7 ?0 X& K, l; k - {4 G- C7 \& ?+ ~$ @
- Shell *shell = (Shell *)param;
6 u& ]' U+ s$ B, W! n8 G - char data;
9 m- {: m6 H- f: f& \ C - #if SHELL_TASK_WHILE == 1
* b, ?3 q R( G3 K9 W- i' N - while(1)
7 v5 s) {3 ]/ _ - {! o4 W: v/ p( N, N1 y/ y' ]6 ~
- #endif5 |. y9 H1 \+ D- v; T
- if (shell->read && shell->read(&data) == 0)
! t8 V8 q8 E4 j - {
- S9 Q) W4 J+ j( |" e7 X; r - shellHandler(shell, data);- i ?- G9 G+ g6 D' Q& O
- }* f7 a% O2 A' l- `5 T, P$ ^% o+ b6 ^
- #if SHELL_TASK_WHILE == 1
8 d7 k8 U. J5 V7 R8 L& r - }
: e, L( _) |2 Q E - #endif- T- n2 N! B; C
- }1 v) ?( I1 O8 S; J) c
复制代码
5 _# I9 l- j$ D' m+ V8 _2 e! W4.3 SHELL_USING_CMD_EXPORT6 ]% H# d( d8 a' q% y0 ^
命令导出功能,通过这个宏,我们就可以在命令行中输入指定函数的名称以及传递函数,就可以直接调用该函数,用于调试,十分方便,所以这个宏不变,默认为1.& S! \" y* R% \* A
0 t* A! @3 w: m5 T" x3 n0 ~. G4.4 SHELL_USING_COMPANION
0 y( a$ a( V1 ^. e$ [! v' F作者定义的伴生对象,用于一些扩展的组件(文件系统支持,日志工具等),但是目前这个功能还不是很完善,所以这个宏默认设为0,不启用。& L8 B$ \0 p3 X1 m% m
0 F: @" o( K4 t: J# N" R, {! W& ?
4.5 SHELL_ENTER_CRLF- ~9 ?2 L* x+ y, R* t( N
2 z) V6 Y, R3 Z
这个与 SHELL_ENTER_LF 以及 SHELL_ENTER_CR 互斥,作为命令行回车触发,使用的SecureCRT 以及 MobaXterm 是使用 CR 作为命令行回车触犯,所以这三个的配置为9 j1 c) @1 d! \' ~
0 P- u6 ?$ Z# T4 B# i5 C7 ?9 {
- #define SHELL_ENTER_LF 0
/ o1 N4 [5 y* ?, K - #define SHELL_ENTER_CR 1+ @; G3 U7 ~1 t6 o5 u( H
- #define SHELL_ENTER_CRLF 0
复制代码
1 [, e& |9 @, L& p" y, I8 ~2 F4.6 SHELL_PRINT_BUFFER
J7 H' @: w b% @ W# F8 o这个宏定义了格式化输出的大小,在 shellPrint 中使用到,使用方法和 printf 类似,只是注意不要超过缓存大小。
' ~) K) V$ M1 \4 h; Y这个宏也是按照默认值就可以了,也可以根据自己的需求进行修改。
$ h" U9 Y' B0 a5 e9 V
5 x" Q4 @, B, S! W9 a: v6 s$ ~4.7 其他的宏: O& ]4 L# s" t/ w- \# y
其他的宏理解起来很容易,这里也就不进行细说了。8 U! x1 @! ?4 e" z5 U
最终宏的定义如下:. F' U6 e9 H3 b- _
% N4 Z7 P+ |$ u1 r U& {- #define SHELL_TASK_WHILE 0( p/ L1 u3 \0 L) r% o( r# {
- #define SHELL_USING_CMD_EXPORT 14 T# F8 _2 \- Y; R
- #define SHELL_USING_COMPANION 09 i9 I/ j9 V$ r( }2 @& W
- #define SHELL_USING_COMPANION 0
2 V5 |- Z% d" R - #define SHELL_SUPPORT_END_LINE 0
, I! C9 v/ g" c% o0 v - #define SHELL_HELP_LIST_USER 0' ]# Q8 n4 t: ~( E
- #define SHELL_HELP_LIST_VAR 0
+ N& h" i- C% i6 j - #define SHELL_HELP_LIST_KEY 0$ G, a4 x. B) s9 x2 {
- #define SHELL_HELP_SHOW_PERMISSION 1
; G, m4 f) ^+ k; U1 S- ~ - #define SHELL_ENTER_LF 0
$ A# m/ z, Y+ Z5 w, @7 y - #define SHELL_ENTER_CR 1
: T* W& V% ` T& g - #define SHELL_ENTER_CRLF 0
6 A+ f+ e# ?* U; O1 ]4 t - #define SHELL_EXEC_UNDEF_FUNC 0
7 J) |( V! r6 ~9 w8 z U" x& `5 K - #define SHELL_PARAMETER_MAX_NUMBER 8
3 n. D0 l. g5 }' n& U8 M: g - #define SHELL_HISTORY_MAX_NUMBER 5
5 }% {0 [% I% W( N% B6 {7 ?" s# w! w; Z - #define SHELL_DOUBLE_CLICK_TIME 200# U1 o( f& G1 u2 G; G
- #define SHELL_MAX_NUMBER 5
9 K2 h Z$ [( z# c- M+ b4 \) b9 L - #define SHELL_PRINT_BUFFER 128
/ ]1 `, u L! B! C" g7 v - #define SHELL_GET_TICK() HAL_GetTick()
5 m$ z7 u9 w' B4 J- ?: ?0 x& u - #define SHELL_MALLOC(size) 0
/ }3 p [9 j% D4 E1 V+ K - #define SHELL_FREE(obj) 0: |' P% `9 D0 b3 m. g0 S
- #define SHELL_SHOW_INFO 1
1 n b" Y% Z" ^9 A, c' Q6 x( z% ` - #define SHELL_CLS_WHEN_LOGIN 1
/ o# R) M: Y5 l; i - #define SHELL_DEFAULT_USER "letter"$ e W3 x [ H$ v0 h, f
- #define SHELL_DEFAULT_USER_PASSWORD ""# k0 D3 R7 e& ^1 q4 X7 M6 a4 @
- #define SHELL_LOCK_TIMEOUT 0 * 60 * 1000
复制代码
3 Q* S6 w7 z, i/ o4.8 shell的接收处理# F& ~/ U8 f0 y
要正确的将串口接收到的数据当参数传递给 shellHandler,先要确保打开串口的中断,HAL_UART_Receive_IT ,使用这个串口中断,需要定义接收到的buffer数组,以及接收buffer的大小。) g( F! f1 a; k7 a0 q1 a* q
" e/ o% e- H8 I$ D$ t) y0 Z
- #define HAL_USART3_RXBUFFERSIZE 14 B- `# I3 }' b, z- W
- uint8_t HAL_USART3_RxBuffer[HAL_USART3_RXBUFFERSIZE]; //HAL库使用的串口接收缓冲
复制代码
) u) z, h* G, ]4 Y/ ]' O1 y在main函数中打开串口中断( d! U" U) h2 E* m3 |
! l) G7 }4 O: Y' G x6 P" R: b- HAL_UART_Receive_IT(&huart3, (uint8_t *)HAL_USART3_RxBuffer, HAL_USART3_RXBUFFERSIZE);//使能串口中断:标志位UART_IT_RXNE,并且设置接收缓冲以及接收缓冲接收最大数据量
复制代码 ' y" V3 M' J; h( z
打开串口中断后,在串口中断中就能获取到串口数据了,在HAL库中,串口中断最终会回调 HAL_UART_RxCpltCallback,所以我们就在这个接收串口数据,然后调用 shellHandler 进行解析。/ ], c$ t. m% f( v
5 U3 i5 L7 [' D% U- void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)+ }' _2 [3 I2 N6 ~- x! _
- {
5 Y3 J! Q" K* g - uint8_t udata;
# r, B. s X# a G3 O( h" |( N - if(huart->Instance == USART3) //串口3的接收部分
- _, u' C' g' x6 E$ e - {" `6 x; S' E& p* ]4 V
- HAL_UART_Receive_IT(&huart3,(uint8_t *)HAL_USART3_RxBuffer, HAL_USART3_RXBUFFERSIZE); //接收串口的缓存
/ b6 u4 T8 v' s+ n, C6 _ - udata = HAL_USART3_RxBuffer[0];% X, s3 K. D$ K; T: I; M
- shellHandler(&shell, udata);
- {$ z6 b- l- ]) P3 ~. }/ V - }/ T* `# E! X4 a- p8 V
- }
复制代码
" D- G2 Z; V9 Y- J编译后下载程序,复位,这时候输入cmd 命令,按下回车,就会列出可以支持的命令了。后续如果自定义了导出函数,也会在这里显示。到这里,就说明了串口的收发是正常的,shell 也移植成功了。! j6 ]# \- x4 R1 ~
7 i( m2 \! b( f
" E" R. S2 g" c6 v+ b7 g$ L* z! N& |/ Y/ t' l. G
五、使用Letter Shell 进行调试程序
0 a0 z* Y) R% z1 R如同上面的 cmds 命令,我们用户也可以自定义命令,在编写完一个函数之后,我们可以通过SHELL_EXPORT_CMD 导出该函数,这样我们就能通过命令行来调用这个命令执行,以达到调试的目的。
8 w; x/ i' X9 u! c, K( M: D2 p4 m- qletter shell 3.0同时支持两种形式的函数定义方式,形如main函数定义的func(int argc, char *agrv[])以及形如普通C函数的定义func(int i, char *str, …),两种函数定义方式适用于不同的场景。1 `8 j) A" P( g8 U7 f
- o% b( y5 S- {8 b1 h下面使用LED来举例。
F2 ]% B% H) Z3 \7 w8 p( ~* M8 S; k6 |* Y
5.1 普通c函数的命令导出1 ]4 B% t" l+ u- o. p1 A
- void LD1_ON(void)$ E% |8 U/ ^2 w5 I# O U+ n) I& v
- {* f7 i& k! c: \8 O2 D4 }- q. p) M
- HAL_GPIO_WritePin(LD1_GPIO_Port,LD1_Pin,GPIO_PIN_SET);
' \3 t4 H6 ^) f- I$ ^( i - }) H2 Q t/ |& {
2 H3 |" T0 f! @1 H- SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC)|SHELL_CMD_DISABLE_RETURN, led1_on, LD1_ON, LD1 ON);# z9 [, b" y9 n$ p# T/ U3 i4 l
- + D$ b* m0 ]; H+ E
- void LD1_OFF(void)
2 a3 J. x4 {4 @5 o. b. \ - {
/ i+ g# H! m s - HAL_GPIO_WritePin(LD1_GPIO_Port,LD1_Pin,GPIO_PIN_RESET);
y$ d* w2 Y7 |! P) M - }
( u+ d& }; F1 z - ! L2 ^8 o. q4 l* ]+ Z; v
- SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC)|SHELL_CMD_DISABLE_RETURN, led1_off, LD1_OFF, LD1 OFF);
- `4 p4 \6 B4 K- N
复制代码
5 K& {5 [6 P1 \; ~" B上面写了两个函数,顾名思义,LD1_ON是点亮LD1,LD1_OFF是熄灭LD1,SHELL_EXPORT_CMD 就是导出命令,各个参数分别是 命令属性,命令名,命令函数,命令描述。* O( t" V, Z2 W# N, R$ ?# p
# d0 b9 s3 `$ o( _. w
- /**8 I9 o0 b, W: A" {8 ~( [/ ]
- * @brief shell 命令定义
4 H3 r; s( K2 A+ t' d \ - * ) `# d7 X$ ?7 s4 U
- * @param _attr 命令属性$ k* K( W9 H y/ b! a
- * @param _name 命令名
: I5 F- {& K" R6 V2 y( D$ B - * @param _func 命令函数" T( I1 k4 U2 V5 p
- * @param _desc 命令描述7 }! o$ ]8 Q& n9 O4 o
- */
: i3 w6 q( j" T- H$ v+ L# k/ Y - #define SHELL_EXPORT_CMD(_attr, _name, _func, _desc) \
7 B9 q+ M7 m0 `$ I$ ^9 T/ ^ A$ } - const char shellCmd##_name[] = #_name; \
8 |( I# _. c: w6 [: \' u% G - const char shellDesc##_name[] = #_desc; \
$ d, ?( `0 f$ p3 ^& d5 p. E - const ShellCommand \
4 o; g; H, ~: U4 ^ - shellCommand##_name SECTION("shellCommand") = \2 r* U: ]8 v7 b
- { \
) ~2 x8 [0 |& }- \& l# n/ i$ U: z - .attr.value = _attr, \
b: z8 b& d4 d0 C3 E" k C' ^/ p3 S1 b - .data.cmd.name = shellCmd##_name, \* ^2 X% u" L; b4 \2 q0 R
- .data.cmd.function = (int (*)())_func, \- B2 l1 m5 z5 v7 o
- .data.cmd.desc = shellDesc##_name \/ v# w/ E# N( b" {6 ]+ `
- }
复制代码
0 P' }9 A8 W5 I; o5.1.1 命令属性
4 W& [) ~- t" H' H命令属性 这部分在 shell.h 中进行定义,几个属性可以通过 " | " 来进行连接。属性有以下几种:
9 }' v( q- S/ c$ W! a6 T* ` l
$ @; s* j2 s( p3 n) x/ ^- /**
0 y, s [( X1 h7 ]- m - * @brief shell 命令权限! p* ]. U0 G) I4 ]
- *
1 T5 M( k. v" J$ w; `. r6 S - * @param permission 权限级别
! _, T: c4 ]# f0 ] - */2 L$ [' N& Y |; L* p
- #define SHELL_CMD_PERMISSION(permission) \
( S# R# p1 E) ` - (permission & 0x000000FF)% ~% K( \0 V& w- U) C. u; S
- + I' N/ |1 e5 U9 Y% B
- /**6 R i6 L, f' ^0 d) f( p; \8 }
- * @brief shell 命令类型! E1 |- ~! ~% [3 ]: R3 w
- * + H9 u- Q: X) y; x
- * @param type 类型
2 F$ N0 M7 m @ - */
' g7 Z/ V# [3 ?+ J8 y$ h - #define SHELL_CMD_TYPE(type) \* {7 l' D+ Y; P2 f$ ^) n" O0 @8 M& r t
- ((type & 0x0000000F) << 8)3 @. v! v- N3 d" e/ [9 x" T
- - C p8 L: ^ e) N3 [; t) n
- /**7 P% I. ]3 z# r9 r: U9 c9 e
- * @brief 使能命令在未校验密码的情况下使用
+ K! r7 N6 X: j6 C# @ - */
* E: l4 }# e( b, Q7 X - #define SHELL_CMD_ENABLE_UNCHECKED \' H7 ~* c, L2 b S* ]. P+ O
- (1 << 12); p+ T/ w# R6 r$ N# f+ J' m( h$ D
% r0 b3 ^; I: Q/ a- /**) U! F8 y& u M+ y0 v; Y
- * @brief 禁用返回值打印) m! m- s+ ]- l! a
- */3 [- K( O' {( e# u% [
- #define SHELL_CMD_DISABLE_RETURN \$ f( N. T8 y7 Q/ E- u' U6 V& g) B
- (1 << 13)* ]( I' v" I% b% E0 N7 R1 _2 m0 u
; h; G. A1 F3 v6 H- ~- H/ l- /**) l# k& r) p8 d
- * @brief 只读属性(仅对变量生效)
k+ k3 g, @; a) h - */
! }$ a6 n4 k: K D( j6 I9 F - #define SHELL_CMD_READ_ONLY \
3 c9 [; A5 W: g) W" i+ f - (1 << 14)
4 I) O$ j$ U1 {9 l
复制代码
" e3 t: j9 C( g0 c在上面的例子中,使用到的命令属性是, M9 O2 [4 a) `6 O* v+ l
# o/ B, [- j" l+ U% r
- SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC)|SHELL_CMD_DISABLE_RETURN
复制代码 d( l' H7 L, P5 k! J
这个意思是这个命令任何用户均可使用、命令的类型是普通的C函数、执行完函数后,不在命令行上显示返回的结果
3 e/ g( S4 H# P* k: g% m) J# P6 t
5.1.2 命令名, e& C5 s, D: Z/ f1 `
命令名 在命令行中,输入命令名就能执行对应的C函数,可以通过命令行的 cmds 命令进行查看* \- f8 A3 e6 x9 D! r. ^
在上面的例子中,实现了两个命令名,分别是: T J% D) Y9 F" h
3 `. G: Z% z; I
- led1_on8 j, l1 R p$ l- [5 g8 ^
- led1_off
复制代码
) y* H% R8 ~8 O! n6 {5.1.3 命令函数
$ \) G; S" l* o) \: Z+ A命令函数 也就是我们在keil工程中,所编写的函数的函数名。
5 f/ n$ b+ p2 {! M比如在命令行中,我们输入 led1_on ,那么实际调用的函数是 LD1_ON,输入 led1_off,那么实际调用的函数是 LD1_OFF. x# p# S) B/ `
: A5 X$ U2 Z6 D0 k, N( E
5.1.4 命令描述& B* J/ h( e9 M
命令描述 命令描述出现在 cmds 命令中,它主要是简明扼要的说明这个函数的功能。
9 p) ^# r! d' N9 B2 `
9 R% f, `; x4 S5.1.5 实际效果
' G( k' u* h- |' U0 a. z如图所示,在输入cmds命令之后,会打印出我们可执行的命令,这里是多出了两个命令,led1_on 以及 led1_off,我们输入对应的命令,就可以控制LED1的亮灭了。
0 `+ _+ V; U# { q9 ~5 o8 ~
1 n; w( K! n& N1 U7 w8 T5 U9 ^
& C1 Y- z) ?& i7 A$ g, y
0 o9 h: J( g, C5 ]8 G5.2 main函数的命令导出
8 h! f' W) m0 h% }6 n一般我们写的程序,用普通的c函数的命令导出即可,main函数也可以,使用上也比较类似。使用此方式,一个函数定义的例子如下:# L4 Q2 A. \" z6 P: G
: c7 v$ W& L; r# Y" r- T0 H5 A, w3 P- int func(int argc, char *agrv[])
" @$ k1 m8 v2 E3 E0 R - {' H* O8 d! E' ?, [$ N. n n# {: O
- printf("%dparameter(s)\r\n", argc);0 D, h; k5 \: ?( i" L' ^
- for (char i = 1; i < argc; i++)
2 P/ [# @% H. Z - { A+ K- p9 i, X3 C
- printf("%s\r\n", argv);
8 Q& |3 `: Q: k* H2 C - }
! _4 F8 P5 t! A( B4 u% o& A1 ` - }
- @# k4 | [0 P/ I- o+ U! f" o - SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_MAIN), func, func, test);
复制代码 & K' A. C6 A/ P! q. u
终端调用
7 X$ y; L2 D* ~4 d8 |7 f5 Z/ J
1 N4 S7 X/ Y- H- letter:/$ func "hello world"8 T4 f! I- D: M d8 n7 D
- 2 parameter(s)
* w% N& n1 @; c- a" b+ L: f c" H1 ~+ X5 e - hello world
复制代码
2 d. T8 A+ I5 ]+ y- b0 E# I5.3 带参的命令导出; T/ q3 H; `. q, V& U( a+ a$ L6 j4 S
我们编写一个带参的函数,命令行是以空格划分传递的参数。0 `* U/ r& C+ y" Z3 a' T+ y
4 f1 U1 ?0 A: b1 v1 Z* o- void LD1_ON_OFF_ms(uint16_t on_ms,uint16_t off_ms)( e: Z* y, F$ N$ B
- {
7 Q$ Y# b, n+ P - HAL_GPIO_WritePin(LD1_GPIO_Port,LD1_Pin,GPIO_PIN_SET); //点亮LD1! u' ?( J: y) f% s8 \' ^4 H
- HAL_Delay(on_ms); //延时: B+ J% _/ V( o% l/ V- x- M" X
- HAL_GPIO_WritePin(LD1_GPIO_Port,LD1_Pin,GPIO_PIN_RESET); //熄灭LD1
0 q7 @) [: {$ T - HAL_Delay(off_ms); //延时5 F& _' V0 ?7 Y/ s0 y0 Z
- HAL_GPIO_WritePin(LD1_GPIO_Port,LD1_Pin,GPIO_PIN_SET); //点亮LD1
# h- R7 g8 i0 _6 ~& o# U& Q - }+ R( N* q7 o# @6 A$ u" V; H
- SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC)|SHELL_CMD_DISABLE_RETURN, led1_on_off_ms, LD1_ON_OFF_ms, LD1 on ms);& e1 g: C: V* P$ E0 t4 O2 z
复制代码 1 v$ ^# c: E' `9 x
这个例子是控制LD1亮灭的时间,LD1首先点亮 on_ms 个ms,再熄灭 "off_ms"个ms,再重新点亮。 d' v/ i9 l: c3 g- r" ?9 j" D+ {
输入以下命令,就能看到 LD1 先亮 1000ms,然后熄灭 100ms,最后又点亮的状态。4 h8 y- Y4 ]1 ^; v
3 C' O6 g2 e; J
- letter:/$ led1_on_off_ms 1000 100
复制代码 这是输入一个数字的例子,也可以传递字符串,比如说我们要使用文件管理系统 FatFs 来管理文件,很多地方就要使用到字符串了,letter shell 支持伴生对象,里面可以扩展文件管理系统的功能。
$ L1 @0 O. c; x' k5 i+ @" g' ^- e, L5 w2 D, W6 H7 J
fs_support作为letter shell的插件,用于实现letter shell对常见文件系统操作的支持,比如说cd,ls等命令,fs_support依赖于letter shell的伴生对象功能,并且使用到了内存分配和内存释放,所以请确认已经配置好了letter shell
# V z" n+ [' V4 G6 J8 i. r, N( M z+ E7 ~
fs_support并非一个完全实现的letter shell插件,由于文件系统的接口和操作系统以及具体使用的文件系统相关,所以fs_support仅仅通过接入几个基本的接口以实现cd,ls命令,具体使用时,可能需要根据使用的文件系统接口修改fs_support,letter shell的demo/x86-gcc下有针对linux平台的移植,可以及进行参考" f) H* r& ~4 `: n
) M4 f2 D% Y- v6 x
因为这个功能作者还未完善,所以这部分的命令需要我们自己来实现,下面是我在 FatFs中对 mv 命令的实现,功能是在将 old_path 的文件移动到 new_path,或者是将 old_path的文件移动到 new_path,并且重命名该文件。
" o4 ~+ L, a+ U$ O0 S5 S* E, H+ g
9 ^' E/ k- U- x6 m3 z7 P, Y$ u- /**
$ x( w) h3 V g+ A" [ - * @brief 重命名/移动文件或子目录
, N% ^, p) S1 I( |" _0 q - */3 s- O( \: O r( t% E
- void shell_mv(char *old_path,char *new_path)
4 c; o' T& [* E, W5 ?3 s6 _8 M7 M - {
: A/ j2 {/ \% W' h1 m* O - FRESULT res;
8 O2 m2 z7 t+ v, a: w ~ @
# d4 A1 l+ Z g# ~- shell_active = shellGetCurrent(); //Get the currently active shelll1 F) r, Y' O. o- M0 p2 @
- res = f_rename(old_path, new_path);% ]- i [0 r' A4 y" L# p) u
- if(res != FR_OK)7 [# K+ s5 C1 r$ n
- {+ U1 Z: H5 y% D: Y# ]! q
- shellWriteString(shell_active,"Operation failed! Error Code:");
) p4 e6 u( ~! \6 _ - shellPrint(shell_active,"%d\r\n",res);8 C* x# B$ Y( X g2 z9 b9 r4 |
- }
$ f1 Q- z5 ?0 c" O, [ - }
4 u0 o f; c3 l, j6 G; B - SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC)|SHELL_CMD_DISABLE_RETURN, mv, shell_mv, rename or moves a file or directory);
复制代码 2 V6 m; X- I. S; B) j$ v
输入以下命令,就能将 dir1目录下的 file1.txt 移动到 dir2 目录下,并且这个文件名将修改为 file2.txt。, Z! A* j5 v. v( a7 N2 d4 [
5 G, M5 [ p v2 ~6 Q2 v- mv dir1/file1.txt dir2/file2.txt
复制代码 5 f$ v6 B6 _1 C/ l
不过要注意一点,FatFs 的磁盘是以数字打头的,不像window,各个硬盘是以字符打头的,比如 "C:\file.txt"表示 C盘目录下有个 file.txt,在FatFs中,对应的目录是 “0:\file.txt” ,假如我们以 “0:\file.txt” 当成参数传递进去,那么接收是会存在问题的,这是因为第一个输入的是 0,在解析参数的时候,它会把这一整串字符串当成数字来解析。
% D, {1 O l6 R我们先找到解析参数的函数,可以看到 if (*string == ‘-’ || (*string >= ‘0’ && *string <= ‘9’)) 那么就是要解析数字,我们在这个判断中,再次判断,从字符开始到后面,如果出现了任何一个不属于数字的字符,我们就判定这个是字符串。而如果在空格或者回车命令前,全数字的字符,那么我们就判断这个是数字,通过这个方式,就能规避 FatFs 的硬盘号会导致参数解析异常的问题了。9 y, c A* M5 e% \6 a8 o" g2 | `
4 b/ v% ^. [/ q6 W& \9 e: h2 ]% V
- /**
7 \. z) M/ n H0 g - * @brief 解析参数* {- G) ]- Z; e* o
- *
4 D* v# t! z8 O" w U - * @param shell shell对象4 L) b/ U. J- F
- * @param string 参数" i! C7 r8 m1 i# u- ~* B4 s
- * @return unsigned int 解析结果
# F5 w1 Q# i$ Z - */ p7 z2 O. [3 c' S
- unsigned int shellExtParsePara(Shell *shell, char *string)
7 T! {) z, ^; `! W% [5 E - {9 m* [1 D0 T( C, p
- uint16_t i = 1;
9 P x' [! O- [" Q; S$ u& r1 L0 f - if (*string == '\'' && *(string + 1))
; r* [2 A6 p; }1 z2 Y9 Z2 i. G - {3 U6 A4 ^6 i( F4 [. ~
- return (unsigned int)shellExtParseChar(string);
5 E% n% M8 t' f+ z - }
) @, H* U# g2 L2 b, Y2 f - else if (*string == '-' || (*string >= '0' && *string <= '9'))3 P) R( ~% X& M/ x* K1 E6 F
- {: _5 U+ L* `. z& Y9 b2 j
- while(1)' s, O6 @$ e1 H6 _0 J( x
- {8 b, k8 ?4 M0 C& @$ k
- //寻找下一个表示该参数已经结束的字符$ L9 U3 N7 v$ L" W5 Q
- if((*(string + i) == ' ') //下一个为空格表示该参数已经结束/ C. n& B" J3 `( r6 \9 @
- #if SHELL_ENTER_LF == 1 // LF 表示命令结束4 j" T: k0 \0 y) l! y- l
- || (*(string + i) == 0x0A))
- E7 E3 ]- ]. `+ b' Z$ m9 N - #endif
/ V0 B: m8 N& m v, t - #if SHELL_ENTER_CR == 1 // CR 表示命令结束% L* }6 \7 _5 r7 q3 S" c
- || (*(string + i) == 0x0D))8 }) W- ]4 f% Z. C" n4 H$ y' I# Q
- #endif( I4 \+ l$ ^, w" q
- #if SHELL_ENTER_CRLF == 1 // CR LF 表示命令结束6 `% i6 z5 H0 z. p: y9 H2 Q! q/ V) S
- || ((*(string + i) == 0x0A) && (*(string + i + 1) == 0x0D))1 T/ U9 F3 d+ z+ m( ?
- #endif
; l8 |/ U4 o- t: n1 H8 j - break;
" X4 O6 h* q d - 2 w2 y+ c1 c' d% F: X" P, y) G# i
- //出现不为小数点或者非数字的字符,表明该参数为字符串
5 C2 w( b. |( A! ]7 @ - if((*(string + i) != '.') || (*(string + i) < '0') || (*(string + i) > '9'))0 q9 L: Y. y4 j/ e- C, v
- return (unsigned int)shellExtParseString(string);
# t6 g% h- ~+ ~; ^- ? - $ b; }8 R/ g3 R, Z$ E2 [: \- q4 j
- i++;
3 h! h) f- `2 S; z3 f - }
) b6 ~, b! j! m: q+ r/ L - return (unsigned int)shellExtParseNumber(string);3 z2 k/ w% O, r/ u. z6 E1 \0 m8 T2 B
- }
( A/ ] e i0 l8 z - else if (*string == ', [- O0 Z1 s. x1 u5 j2 F
- [b][size=3]六、其他常用的 Letter Shell 函数[/size][/b]
& n: y8 X: k0 D( o) e - [b]6.1 shellWriteString[/b]8 q: i# ?8 `9 C! |* d% x# I7 ]
- 函数原型如下,这个功能就是 printf 。这个应该很好理解,就不细说了。' e# P# E/ e% k. N g9 [) @
- 0 ]5 E* j: L* v }6 C
- [code]/**1 v. e- C) `8 y. p7 X. K
- * @brief shell 写字符串
) n( N6 S! c4 e* Q8 _( [ - * $ q% e* t5 p' ~
- * @param shell shell对象
0 N0 J8 J+ M) y0 L9 E - * @param string 字符串数据
+ d/ n6 d4 w& f' R- O, {( t - *
! l7 }) ^4 I$ y - * @return unsigned short 写入字符的数量5 B* T0 n, s9 @
- */
* @0 m$ P6 y& m2 m/ z - unsigned short shellWriteString(Shell *shell, const char *string)
复制代码
2 x& E3 r7 |* j% f0 I6.2 shellGetCurrent
9 i5 D w" I$ a$ C8 f. d7 sletter shell采取一个静态数组对定义的多个shell进行管理,shell数量可以修改宏SHELL_MAX_NUMBER定义(为了不使用动态内存分配,此处通过数据进行管理),从而,在shell执行的函数中,可以调用shellGetCurrent()获得当前活动的shell对象,从而可以实现某一个函数在不同的shell对象中发生不同的行为,也可以通过这种方式获得shell对象后,调用shellWriteString(shell, string)进行shell的输出。* J( i! X6 w* h
; ?2 N9 u" L1 g/ G- /**+ r7 p u# f( U* v7 z8 L
- * @brief 获取当前活动shell
) {2 F5 Q. n8 g5 {. r5 c' |" g - * / ^4 W+ N/ z$ A- H/ S6 i7 j1 v
- * @return Shell* 当前活动shell对象% @7 f3 b4 I. \6 s* z" ]7 u
- */
5 d0 Y( N! ]( z/ P4 j1 q3 @; A9 m - Shell* shellGetCurrent(void)
6 w3 S* {- U- c' }: @. H; h0 @2 l8 {
复制代码
3 t0 g# y' ^! c+ Q4 N- O9 j可以定义一个全局变量, j) }9 K! M5 R* A! V5 |6 K; T R
9 a: |$ `% G1 }8 Y+ e. h- Shell *shell_active; //当前活动的shell
复制代码
$ t& k/ I: l3 f5 `4 k! r- l7 b如果我们使用多个串口创建shell 对象,如果通过不同的串口访问文件,那么就不容易知道应该在哪个界面进行输出,正如这个 mv 的命令,首先获取当前活动的shell,再通过 shellWriteString(shell_active,“Operation failed! Error Code:”); 进行输出,这样就可以避免冲突了。/ y) L7 H+ @( U. [8 g# M
0 C) ^6 g9 D7 t; w K
- /**
: F) S% g# U' \* M - * @brief 重命名/移动文件或子目录
, u8 _7 a0 a: N b - */, R+ j I& X+ i" [- @/ A8 D6 l0 U
- void shell_mv(char *old_path,char *new_path)
5 d4 C( O- L" A% ]% Y, Y" r% s - {
: i! s3 ~. @ X8 p4 ?2 ~/ Q - FRESULT res;$ Q% J( K6 \; Z# h6 r2 g3 v
% S6 t6 r) a" W3 x* b$ P8 x- shell_active = shellGetCurrent(); //Get the currently active shelll
; |1 K! h A; w& a5 Z - res = f_rename(old_path, new_path);
5 s# [% c# v# | Q- T* o - if(res != FR_OK)
1 l6 h4 e0 j+ h8 j! l, s9 k - {
4 a% F0 K$ V$ X$ \ - shellWriteString(shell_active,"Operation failed! Error Code:");
: D- m4 t' H8 P* N3 ] - shellPrint(shell_active,"%d\r\n",res);
9 n! ?4 o3 ?. v! F - }3 b, j! }. d( Z; j# \0 o& G# W$ @
- }; M" ?6 _2 W4 ?: o A) Z
- SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC)|SHELL_CMD_DISABLE_RETURN, mv, shell_mv, rename or moves a file or directory);
复制代码
3 N$ X {* g+ d; x! q0 ]6.3 shellPrint" o1 S- B* p; P) n" ^5 h7 w
这个功能就是 vsnprintf 的简单封装了,用于向字符串中打印数据、数据格式用户自定义,但是要注意,打印的大小不要超过 SHELL_PRINT_BUFFER。" A. ~ d( l$ z5 J, T8 T1 F; [
3 b. ?+ g- K, D. ]
- #if SHELL_PRINT_BUFFER > 0' p) R' M8 X. h6 h
- /**# m% a; S) J! [- N- B! I0 K
- * @brief shell格式化输出! d9 S# H) O. f' O3 e
- * ! U0 a4 U) f6 v" H ~+ G* x
- * @param shell shell对象1 d# h: w8 u! a# }
- * @param fmt 格式化字符串
1 q2 a' H! ^6 _7 p2 L - * @param ... 参数
, j1 B% U. k, R$ N. w - */$ [( ]) P2 ?0 q
- void shellPrint(Shell *shell, char *fmt, ...)/ x4 W T# ~& M+ x, z7 M8 T
- {
/ V, J0 w4 S, Z% L( H8 K# B+ u4 C - char buffer[SHELL_PRINT_BUFFER];
- @6 g$ Q1 L# b" r% r* ~ - va_list vargs;0 |+ |3 k" }. \" E% i
+ ^3 v( l% p$ N" Q$ ~- SHELL_ASSERT(shell, return);
}- _3 c; x: Q" d* n4 o2 R
4 o' F5 O, d" n; l; q- va_start(vargs, fmt);0 L4 }6 {( s! A s0 W- ]% E" B
- vsnprintf(buffer, SHELL_PRINT_BUFFER - 1, fmt, vargs);$ @0 Y, q' x3 G# f5 O4 S6 Z* y" v% m( S
- va_end(vargs);
. f, o/ O- D' q9 F - ! u$ e3 E* y; W& s7 w- T2 u& U3 U" }! q
- shellWriteString(shell, buffer);
" y& @& X& E; h) F# r2 a+ S - }
( g" t' `/ Z! {7 K! K3 L$ b - #endif
复制代码
2 [1 G" M) J3 |+ f0 S到这里,介绍就完了,而这也足够应付绝大部分的调试内容了。
5 F3 h |" ~; j, ]/ m9 v
+ p3 {% }! v7 ~! i+ A
# i2 m3 b6 h7 P% V/ |: y
# Z8 q2 o7 }( L7 G4 L {3 T9 L && *(string + 1))
2 N' T# ~. ~# C# [ j, T$ W {1 f" x0 z" ]3 ^# K$ h
return shellExtParseVar(shell, string);
# a, n3 }! q8 { O" G4 }1 s8 e }9 ^! y$ @ c% f5 j$ r
else if (*string)7 W$ x7 Z6 H z$ T
{& {8 _8 ]; @- ~9 ^6 s8 ^
return (unsigned int)shellExtParseString(string);2 h8 w0 s2 ]4 B( C; }2 i: T5 E- t% ?8 |
}: L4 Z8 k* a/ O6 W
return 0;1 h5 Y4 E$ h/ \1 w2 Y" s
}[/code]
2 K5 V- C: Y s: h" a- ]/ i: c; z六、其他常用的 Letter Shell 函数& V* ?/ G" m7 ]: F- _$ J
6.1 shellWriteString
$ b3 T! s* A+ s4 R- F9 p函数原型如下,这个功能就是 printf 。这个应该很好理解,就不细说了。* J" ]+ a2 F1 a) V- r4 k! ]
. }7 k2 \9 N$ K. l- /**
' s9 Z x, {# o - * @brief shell 写字符串
* E6 c3 t$ X) f9 z6 h' [2 Q$ d - *
Q7 d! B) @9 ]/ j' m2 y3 c+ x - * @param shell shell对象
0 @% R0 c, g- a6 d2 W0 l - * @param string 字符串数据5 d7 T( G! L2 w8 T) |
- * + n6 y5 a5 g* A2 |6 q0 K
- * @return unsigned short 写入字符的数量
, \+ I" V. b/ h7 i6 g - */
( f8 l$ [$ n1 c+ g) d# U9 E4 y - unsigned short shellWriteString(Shell *shell, const char *string)
复制代码
. w% Z7 M' Z3 W9 A* q4 v7 E8 R- J) E6.2 shellGetCurrent; o- x7 n+ [( q4 y
letter shell采取一个静态数组对定义的多个shell进行管理,shell数量可以修改宏SHELL_MAX_NUMBER定义(为了不使用动态内存分配,此处通过数据进行管理),从而,在shell执行的函数中,可以调用shellGetCurrent()获得当前活动的shell对象,从而可以实现某一个函数在不同的shell对象中发生不同的行为,也可以通过这种方式获得shell对象后,调用shellWriteString(shell, string)进行shell的输出。, V4 }+ g* r0 I/ l
% h# v, |6 h( ?# }0 }- /**5 ]0 L% _" g3 w. n2 R
- * @brief 获取当前活动shell
( |4 _+ p& n9 U - *
. i' f9 V2 M# \! Y4 G2 n; o - * @return Shell* 当前活动shell对象
0 ~- w8 G3 M, b5 T# `5 a/ P - */" X! ?, q' G4 L4 J9 y, x
- Shell* shellGetCurrent(void)
( N+ _! ]# y6 Y% y/ s& b- Y
复制代码 ; u: M- d" ]8 v0 t
可以定义一个全局变量: e6 V# g) ?; J( W
% w I: R) P6 w! y
- Shell *shell_active; //当前活动的shell
复制代码 8 y/ w* ]3 a4 Y/ b3 L
如果我们使用多个串口创建shell 对象,如果通过不同的串口访问文件,那么就不容易知道应该在哪个界面进行输出,正如这个 mv 的命令,首先获取当前活动的shell,再通过 shellWriteString(shell_active,“Operation failed! Error Code:”); 进行输出,这样就可以避免冲突了。
: r9 q3 f9 o# v% C
! j) L/ a: y6 H- /**. [; ? f/ X1 d& a
- * @brief 重命名/移动文件或子目录
( W& E% R5 ~. O: E5 e - */
! X( E- E/ h* h# S: u( B+ q - void shell_mv(char *old_path,char *new_path)
; Q% V Y- P. G8 ~1 F! k - {
9 J" {/ N D5 Q# @! @# n - FRESULT res;
- O3 p0 n; Q- c; }
# k* c; G1 P9 q- shell_active = shellGetCurrent(); //Get the currently active shelll
% }( `$ i- D: z$ Q+ c3 R9 J# I2 v - res = f_rename(old_path, new_path);
0 o. ?+ G l ^9 q - if(res != FR_OK)0 I* L6 Y4 Z" u+ j) ^
- {6 D$ v# o: @; D1 E
- shellWriteString(shell_active,"Operation failed! Error Code:");6 t6 W5 n0 l" Y6 ~, V1 D& }
- shellPrint(shell_active,"%d\r\n",res);
% I7 A0 v) @# d - }
, }- I: `$ v3 W: w: Y& H9 |& h+ {$ f1 y - }
( e4 `: a% Q0 ?! I% Z: A - SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC)|SHELL_CMD_DISABLE_RETURN, mv, shell_mv, rename or moves a file or directory);
复制代码
4 [( C, u1 B* h4 O5 N% F" P6.3 shellPrint3 S0 h! O3 u9 l3 _7 ^5 o" H
这个功能就是 vsnprintf 的简单封装了,用于向字符串中打印数据、数据格式用户自定义,但是要注意,打印的大小不要超过 SHELL_PRINT_BUFFER。9 a) C, p$ f& Z' d( i
6 O' `6 L0 i; z' {1 r9 ~2 v$ N0 f2 n
- #if SHELL_PRINT_BUFFER > 0
* S- q' H/ ^: Y7 D/ p - /**. z- n$ i4 g0 x: V: q
- * @brief shell格式化输出) ]# P2 e; V- K- f9 d! G7 Y
- *
* y' |+ ?1 {, F- E. v6 k3 b! M! N - * @param shell shell对象/ [, t' X, B z7 o
- * @param fmt 格式化字符串
/ a0 b5 T% K/ W4 L S: B" e5 ] - * @param ... 参数
" E2 _+ e* B( u - */
" i/ `! w. H, M- N1 U - void shellPrint(Shell *shell, char *fmt, ...)
. J k8 X, s/ h, r% L6 c - {
$ e/ B, u4 u7 r; T - char buffer[SHELL_PRINT_BUFFER];2 l& s2 X* _: E+ P4 i2 t
- va_list vargs;, A' q/ h w3 ]
- ; _& X+ [, _' ? F) l# v' i" z
- SHELL_ASSERT(shell, return);
# |; n: m6 _- }" e3 S% A; _9 ]# z - $ ?( w5 d5 }4 f3 b
- va_start(vargs, fmt);) b6 h% d4 K. \: v! o/ _5 t9 q
- vsnprintf(buffer, SHELL_PRINT_BUFFER - 1, fmt, vargs);
- s/ A; m; W/ T1 W0 w! r - va_end(vargs);
- h% U3 ]2 ]9 k! Q2 N - : p" r# r% s) F( N' K5 x% F! n1 D$ X
- shellWriteString(shell, buffer);3 {! j; q" b% K1 r1 v4 p& o
- }
" x$ R+ w9 |0 P1 R$ ?/ M - #endif
复制代码
7 p; [" z( z0 h6 p- i7 B$ F: }$ ^$ a到这里,介绍就完了,而这也足够应付绝大部分的调试内容了。' {( Z# O {; Z p- O
3 L3 D# j' A- T6 p————————————————# g$ @* n, ] h
转载:fafuwxm
% K* G1 [+ A- z% ^( ^& | z/ L7 u1 s( f" B/ [0 G
|