你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

STM32CubeMX Nucleo F767ZI 教程(3) 串口调试工具 Letter Shell

[复制链接]
STMCU小助手 发布时间:2022-8-23 16:13
前言  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; |
20210220214542675.gif
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 _ 20210124211939416.png 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. ~ 20210124222836342.png
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 }
2021012422385383.png
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
  1. /* USER CODE BEGIN Header_StartDefaultTask */0 {& Z& B3 k, U
  2. /**
    3 d9 m" @& ~7 |3 j0 u7 {$ z; O2 G  l
  3.   * @brief  Function implementing the defaultTask thread.+ x, k  \+ Z* Z
  4.   * @param  argument: Not used
    ) ?3 U8 o# w( _& X. g& w
  5.   * @retval None
    - O$ ], }! r, U$ j2 S1 A
  6.   */
    9 t3 l$ x- c5 `% I; {
  7. /* USER CODE END Header_StartDefaultTask *// G1 K; i7 k+ }* l
  8. void StartDefaultTask(void *argument)" U& c$ L( L6 _9 w2 f
  9. {& U  b" g7 ]& ]' V
  10.   /* USER CODE BEGIN StartDefaultTask */2 S! ?* s4 I# ?+ d5 G; I6 |) }  N. L
  11.   /* Infinite loop */
    / C( v0 K: c/ n" D
  12.   for(;;)
    0 s' T3 B2 W6 ?- O+ E1 c
  13.   {
    " j+ f# N5 m/ j
  14.     HAL_GPIO_TogglePin(LD3_GPIO_Port,LD3_Pin);
    * X6 I) V9 \% U7 N  G% E
  15.     osDelay(100);
    # Q9 g2 s# J& P: }, M, J. m3 E
  16.   }0 K, H- Z, R, d$ |( ]0 E5 @
  17.   /* USER CODE END StartDefaultTask */+ X" z, M8 Z5 z  }6 V& }
  18. }$ 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();
  1. if (HAL_ETH_Init(&heth) != HAL_OK)
    + d: a: n7 o) k6 A6 M
  2. {
    $ {" X5 y7 ~  t% ]' V8 |1 G& h
  3.   Error_Handler();
    0 A% X$ c8 @& x; d
  4. }
复制代码

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 20210124230430874.png
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
20210124231354443.png
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
20210124231834258.png ; 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
  1. ..\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
  1. ..\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
  1. #include "shell.h"
    1 |1 B- G. n* }/ `9 l
  2. #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
  1. /**8 M; H5 t6 p! J8 k2 t0 H# G& ]
  2. * @brief 用户shell写
    $ E' h" ]' g, y" @1 f
  3. *
      W% W" Z; x( M
  4. * @param data 数据
    4 `2 S* O6 S" Q/ z6 w$ m! L* \
  5. */
    ! L# }. k" m0 F( M& y- D1 B
  6. void userShellWrite(char data)
    : C6 c  U) T& K: E
  7. {
    ) U2 @+ l- k' s% b
  8.     serialTransmit(&debugSerial, (uint8_t *)&data, 1, 0xFF);
    9 x2 F# [& Y% T/ E! I1 p( n
  9. }
    , G4 H8 P5 D/ q! ?) G9 J( L5 A& f

  10. ' C+ a4 q9 H4 r- F4 a( C
  11. /**  X: \- \7 f: Y# ?7 Z
  12. * @brief 用户shell读8 k: M7 A' A6 [2 \5 Z( Y
  13. * 8 m9 W+ t; u4 ^8 A" g
  14. * @param data 数据
    " Q& s1 V7 x' g& v# i
  15. * @return char 状态
    + {" Y' k1 O/ S8 Q
  16. */  X. f) r& o) I% m% K
  17. signed char userShellRead(char *data)
    6 |9 T7 ?, M4 y' p
  18. {% |. |0 C7 m; ?; Y
  19.     if (serialReceive(&debugSerial, (uint8_t *)data, 1, 0) == 1)
    " j. q# h8 p  l& T
  20.     {4 t$ E& T1 @& u& r& ^9 a$ {& p
  21.         return 0;
    1 l$ g# x" b& E4 `' g
  22.     }
    7 j, D( H& x7 Y5 L$ D
  23.     else
    6 o1 i+ g4 C: K: |3 ?! W
  24.     {  z# k1 z/ X2 y1 d7 o* J
  25.         return -1;
    6 ~) q  ~  i/ K; D6 s* y
  26.     }, a: e! g) S/ t$ W) @* F
  27. 8 v2 }. x: S( M6 o
  28. }
复制代码
; [! k! B* T3 H2 h
既然使用了STM32CubeMX来生成工程,这里我们改成HAL库的形式。
# z$ _4 k5 y0 M! @. Z9 J9 x2 Y9 Q& `" |. J# D/ E9 g
  1. /**
    . o0 C1 ]" @; z7 M
  2. * @brief 用户shell写1 P% b& U& K" ~  m5 |9 l& j  u
  3. *
    & b) n: ?9 {# }6 q; n/ X
  4. * @param data 数据
    & z. V5 ^5 a, m: D
  5. */
    & M) o2 @2 Z2 p, ~0 \: [
  6. void userShellWrite(char data), g9 L2 e4 s$ D7 }$ f
  7. {
    % X% B' ~' t3 ~5 T5 @: x4 c4 ?0 ?; |7 V
  8.   HAL_UART_Transmit(&huart3,(uint8_t *)&data, 1,1000);% b) \1 |# V/ g
  9. }2 C0 B  n4 l# Z; ~& I8 ^% }
  10. # s# A# }9 ^' b3 d
  11. : w2 s, ^- R3 n; v
  12. /**, l+ h& m2 Z  X( l* k% R2 b' ?+ R
  13. * @brief 用户shell读' C: E* b' s: Z) C( V2 {' `
  14. *
    ' S! F! |& h6 M6 P' C
  15. * @param data 数据
    / j0 o- Y& s/ o* \
  16. * @return char 状态
    $ k# T3 G- L' z
  17. */
    # Y' b' D  @& B& O: U0 t* k& ?
  18. signed char userShellRead(char *data)
    ' q+ C" B6 ~, ?  i; a5 _
  19. {# i  V$ V6 ?4 H/ U$ q
  20.   if(HAL_UART_Receive(&huart3,(uint8_t *)data, 1, 0) == HAL_OK)/ \# O8 q9 e2 c0 n6 o
  21.   {
    $ F+ b" v$ V& A+ N; f
  22.       return 0;
    " u9 Y& |( Z7 {$ M% B  M
  23.   }
    6 Z' _) c5 ^% l2 b- Q
  24.   else
    + t4 H- S) c% e' B2 s
  25.   {
    3 w4 E1 J0 J6 e3 d0 e* t/ k  t
  26.       return -1;
    ) a& I; R- Q  m1 F# \  q
  27.   }7 y- d0 {8 b0 E, i! U% b$ F7 X$ X
  28. }! 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
  1. Shell shell;
复制代码
, 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
  1. /**5 i( Q* J( ?' W0 R+ h
  2. * @brief shell读取数据函数原型
    + b' n3 Z' s  D% A7 p/ w
  3. *
    + S# C4 s  _6 }5 s
  4. * @param char shell读取的字符5 a1 m8 r9 B9 o7 N9 F5 B$ l
  5. *
    9 e! j, A+ {, b: M6 H
  6. * @return char 0 读取数据成功% M6 v/ A' V, o5 e' X' L
  7. * @return char -1 读取数据失败, p/ u! b: N% l5 [
  8. */
    : r: g5 H$ a/ O- i2 L5 d6 q
  9. typedef signed char (*shellRead)(char *);/ m5 o& E' K2 y* g# O
  10. , v+ T4 n; i. I' B2 m; n& J
  11. /**
    ! D: W0 b$ u% T( C
  12. * @brief shell写数据函数原型
    ; G5 w+ |+ n! z, |
  13. *
    + Q/ y; B# a$ d( N$ O. o. y
  14. * @param const char 需写的字符
    / n* F( O$ c; U1 e( }
  15. */
    0 H1 R& n+ x" U/ @
  16. 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
  1. char shellBuffer[512];
复制代码
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 ~! {' ]
  1. /**. N- I  [* p( o' G) p* ?
  2. * @brief 用户shell初始化" D8 j1 ^" A4 ]. X2 E
  3. * 9 w- U6 n. O7 b0 E. a; K  J
  4. */
    1 e3 N4 C- U/ R6 D" h8 ]0 r
  5. void userShellInit(void)
    4 T3 \$ E  h5 T5 o- M* U& h# N4 e
  6. {
    : Y1 K) @% `0 l: i# C* N
  7.     shell.write = userShellWrite;
    7 k$ q! Q8 D  t) }& I
  8.     shell.read = userShellRead;
    " J3 u% Q! `/ o; y8 K* b2 V
  9.     shellInit(&shell, shellBuffer, 512);
    6 |6 y( g2 n$ K& E- {# _" v( g, D
  10. }
复制代码

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) @
  1. /* USER CODE BEGIN Includes */! k/ Q# y7 q4 G0 o  B
  2. #include "shell_port.h"
    0 \+ Z* ~" [! A0 ?7 q( R# ?
  3. /* 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
  1. /* USER CODE BEGIN 2 */
    * i# v- [- {5 q# \2 e$ u
  2.   userShellInit();    //letter shell 的初始化9 Y; D3 m% h- G. c5 ?
  3.   /* USER CODE END 2 */
复制代码

# [2 H# y+ {4 V+ n9 }此时编译一下,还是报错,老样子,是因为没有 “serial.h” 文件,我们双击报错点,跳到异常点,把这个注释掉即可。然后再次编译,就没有报错了。9 N* [9 `8 N" x/ }

, w% m* X2 f& {
  1. ..\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
20210124235734855.png
; ^$ 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 20210125000101173.png
& }' 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 Z9[D2ZUY1[R~NU1CT(4F8BN.png
, 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. /**
    1 ?- U: E2 y  e9 f7 p6 U- @
  2. * @brief 是否使用默认shell任务while循环,使能宏`SHELL_USING_TASK`后此宏有意义/ ?  \3 ?) l! u9 Y" X6 e
  3. *        使能此宏,则`shellTask()`函数会一直循环读取输入,一般使用操作系统建立shell7 w. j! ]/ y3 `: f4 a) T
  4. *        任务时使能此宏,关闭此宏的情况下,一般适用于无操作系统,在主循环中调用`shellTask()`
    9 L% k) x: j4 ?: z/ v4 ^
  5. */: A* g- x1 p+ U0 A3 }2 _
  6. #define     SHELL_TASK_WHILE            1/ X/ n$ b9 W8 c3 t

  7. 7 K+ g3 ]( e0 K! P; r+ x: @
  8. /**) z* ^+ E) o, V" [, L5 u% H# _1 `
  9. * @brief 是否使用命令导出方式' X! @) \5 o4 k% ?/ q, p8 d
  10. *        使能此宏后,可以使用`SHELL_EXPORT_CMD()`等导出命令( o2 A& r' A) F$ R* ], s/ {
  11. *        定义shell命令,关闭此宏的情况下,需要使用命令表的方式" v2 A1 n( }- `! n/ \
  12. */
    ) p5 _& M+ J1 |" O
  13. #define     SHELL_USING_CMD_EXPORT      1& r. A1 |7 H5 `$ n

  14. " K- s0 e* S  O$ t; A+ f6 e
  15. /**
    ' @2 u; [( s- M+ u! d- E9 v
  16. * @brief 是否使用shell伴生对象
    . k8 r0 Y, `! x+ h+ Z
  17. *        一些扩展的组件(文件系统支持,日志工具等)需要使用伴生对象
    4 l" I; c/ z9 B' R
  18. */( ?; A8 Z4 A2 c: U
  19. #define     SHELL_USING_COMPANION       0
    # a( D9 [* {- T6 B

  20. % f3 X' ~' @4 {
  21. /**1 h# H# k3 J/ [( _8 e
  22. * @brief 支持shell尾行模式
    5 S# R5 Y. s: K6 F% {% S, l
  23. */
    5 d. a; f) w" r
  24. #define     SHELL_SUPPORT_END_LINE      0
    $ [$ S3 |# h+ v4 r

  25. + u& h0 G* m0 j3 |, `: Q' K6 j+ y% Y
  26. /**
    1 Y& ?/ Y, b7 r% P( ?) i: K6 m0 l
  27. * @brief 是否在输出命令列表中列出用户
    ! z5 Y7 ^# o! e8 Z/ g
  28. */
    " E- P) K. e5 {1 L- d5 _1 d$ @
  29. #define     SHELL_HELP_LIST_USER        0
    2 y% t! H: W# [0 u

  30. ) p3 b+ p; ~$ z, K& v
  31. /**3 z. K; p, X! \8 U# q
  32. * @brief 是否在输出命令列表中列出变量2 I$ ]0 F  p0 w* ^0 j
  33. */6 E: M3 Z# M/ {7 d5 T1 }
  34. #define     SHELL_HELP_LIST_VAR         07 |( c7 T2 t9 q5 ~3 j1 }+ F& E/ D

  35. * g5 L+ D) h0 ]1 V, T6 R
  36. /**
    . c5 L) f3 {& g% O
  37. * @brief 是否在输出命令列表中列出按键
    # w7 U; u" {5 B4 r( d
  38. */
    + w1 o; M' K8 v' `  ?. g2 S
  39. #define     SHELL_HELP_LIST_KEY         0
    ! o/ X5 V4 x1 x7 |

  40. 6 J9 m- I4 g; r& B
  41. /**
    + |3 }! G$ M  ^2 @& e" S/ J
  42. * @brief 是否在输出命令列表中展示命令权限( Y- k) C5 l; b2 J8 v# T" o
  43. */
    $ I4 n9 b0 n; k2 y
  44. #define     SHELL_HELP_SHOW_PERMISSION  1  r% _0 w% s9 h! g

  45. : U4 v; c( w+ ?4 k
  46. /**( t0 G  F# L, R8 _3 c. Y
  47. * @brief 使用LF作为命令行回车触发- }5 J# K* _' F
  48. *        可以和SHELL_ENTER_CR同时开启
    * M' t1 n6 H, o( A* l& h; r. P
  49. */: c& |+ X8 ]- f0 K/ ]
  50. #define     SHELL_ENTER_LF              01 m" L6 P! N* Z0 }1 T7 q
  51. $ S% y8 q  c, ]7 H3 S
  52. /**
    . j/ R( @& g7 M/ u# _3 G+ {5 Q2 W! Z
  53. * @brief 使用CR作为命令行回车触发; @/ |1 n- V9 K
  54. *        可以和SHELL_ENTER_LF同时开启2 E+ v6 Q; z) k  `* v4 {
  55. */9 x( k) v0 o1 f7 Z# B/ Y. {( }7 x8 R
  56. #define     SHELL_ENTER_CR              0
    ) y6 r" v0 O7 D8 C3 R5 \

  57. 6 D0 V. |+ w* t3 H! m9 Y
  58. /**
    ! Q# a. e* U, v0 D) y% y0 g1 \
  59. * @brief 使用CRLF作为命令行回车触发7 L' g$ t$ I* ~5 D, ^
  60. *        不可以和SHELL_ENTER_LF或SHELL_ENTER_CR同时开启' H6 J  C  t0 @9 E
  61. */& q1 o+ H- g% z. F- w
  62. #define     SHELL_ENTER_CRLF            1' Z1 _8 _% m; J- [
  63. 8 g+ ?4 O0 n6 C' x* A3 @( e
  64. /**+ x- Y$ {& V3 w$ j- o
  65. * @brief 使用执行未导出函数的功能. J4 f7 M  U! {: e; Z9 s
  66. *        启用后,可以通过`exec [addr] [args]`直接执行对应地址的函数
    # U9 ~# a% g) V$ [4 ]9 D" C* \, f" Z
  67. * @attention 如果地址错误,可能会直接引起程序崩溃
    ! o+ b& v' L* B( v$ q! @
  68. */
    1 [# ^) @: f8 c: ?! N8 \' a
  69. #define     SHELL_EXEC_UNDEF_FUNC       05 s  o5 P+ I! T2 `2 i3 H" E4 e
  70. & }8 K8 C. x% S" N- L6 F! a2 e
  71. /**! U; f% Z3 e2 D3 S1 U- ?* Q
  72. * @brief shell命令参数最大数量. D* E7 x; X( `2 l) W) y
  73. *        包含命令名在内,超过8个参数并且使用了参数自动转换的情况下,需要修改源码
    & I! l( ?8 Y8 O8 F/ \5 C! D
  74. */: w" X# O  s0 b3 B, @9 g
  75. #define     SHELL_PARAMETER_MAX_NUMBER  8" \4 x& k( ~1 M# k: j3 D

  76. % l+ I4 P6 X0 j
  77. /**
    ) \0 X( q$ K8 s* q) s2 q
  78. * @brief 历史命令记录数量
    ' ~. i( T) H1 |* E( ?" f% W
  79. */
    9 B4 P4 N9 Z' q
  80. #define     SHELL_HISTORY_MAX_NUMBER    5
    3 \+ d" l# n( e. J% q

  81. 0 z. y$ s5 k3 g6 `
  82. /**
    / J/ h7 Y4 s5 ^% y, s. R" z
  83. * @brief 双击间隔(ms)
    4 c6 Q7 U% {7 p9 ]
  84. *        使能宏`SHELL_LONG_HELP`后此宏生效,定义双击tab补全help的时间间隔
    ) ]/ I: T- i* s+ ^" Y
  85. */
    & ?+ E- q! W6 Y1 @/ C2 K# Y
  86. #define     SHELL_DOUBLE_CLICK_TIME     200
    ) Z, c* S6 `* g1 K$ j
  87. * Y1 A' P! k% `2 W+ x5 [" ~! H
  88. /**
    ; R1 l5 [' h1 K) n0 }
  89. * @brief 管理的最大shell数量
      l* }; }9 A0 W% E' x( r
  90. */
    , O1 ?% ?  L7 H+ b: l
  91. #define     SHELL_MAX_NUMBER            5" U. x% f0 O- m4 ^1 V; L5 _. C

  92. % G; p) |6 a5 U6 |1 I
  93. /**
      n6 {  d" D; z# C
  94. * @brief shell格式化输出的缓冲大小) d  n, ]- h( y+ d
  95. *        为0时不使用shell格式化输出
    $ K% T: q! h2 E# k9 b. I+ M9 `$ B
  96. */
    6 m; [9 w" X; X1 l
  97. #define     SHELL_PRINT_BUFFER          128
    . {) d6 s. n  w

  98. 8 R- @1 y! m9 Q
  99. /**2 L3 a! p' r$ b( r& p
  100. * @brief 获取系统时间(ms)
    - q9 ^( A5 O% W% e( y. e& \$ g
  101. *        定义此宏为获取系统Tick,如`HAL_GetTick()`( O6 M6 t, N/ R- k9 z. n
  102. * @note 此宏不定义时无法使用双击tab补全命令help,无法使用shell超时锁定5 g: n" `' c) N* b
  103. */
    $ @4 D% `; T6 y
  104. #define     SHELL_GET_TICK()            HAL_GetTick()# g) Q9 u# T' V; a" f: d, m# @

  105. * l7 V! F# K' ~) _- o! O' t. _; b
  106. /**
    % H! E* @! Q' |& H' s
  107. * @brief shell内存分配
    6 U& v' L2 ]2 n
  108. *        shell本身不需要此接口,若使用shell伴生对象,需要进行定义
    " k7 ~- {8 [6 d+ Z2 ^
  109. */# S$ [* J9 i' j! \" C0 z: s& B& g/ V
  110. #define     SHELL_MALLOC(size)          0
    # d* x% j, E  i' ~+ R. ^4 L

  111. & X; z, k. A* Y% M5 Z$ H
  112. /**
    * ]; `7 Q+ Z. e' e. e
  113. * @brief shell内存释放# a! E( r6 [+ W3 d3 E" Z3 c
  114. *        shell本身不需要此接口,若使用shell伴生对象,需要进行定义
    : U) U, |" y- b0 |: a
  115. */
    & p5 J' a7 f  b2 P' O! I, c3 `& I
  116. #define     SHELL_FREE(obj)             0
    2 O# h( A/ k8 \5 X6 o
  117. . _" O1 \! {1 f: W; E9 k* _5 x
  118. /**& O, ?, S3 o# J( I4 s
  119. * @brief 是否显示shell信息
    / b8 B! Y" z1 x" o& R% @7 T2 V
  120. *// _2 a! I% W3 z
  121. #define     SHELL_SHOW_INFO             1
    ' C* o  @: v. G( I! K

  122. * ?! Q" y0 t7 U% a( l  A
  123. /**1 x. \4 v% u1 A4 ~" a  ~+ j% N
  124. * @brief 是否在登录后清除命令行
    8 s4 B4 E7 k& v2 l7 t+ Z
  125. */
    - I+ ?5 B3 b1 I- [  l, N" Z
  126. #define     SHELL_CLS_WHEN_LOGIN        1
    3 h% `. K6 b* y: G
  127. 9 B5 ]: c: f9 L1 }/ c1 V
  128. /**
    & d5 h; q! m& s, l- x3 C" n# n
  129. * @brief shell默认用户6 m0 r0 N( U- `, A0 h6 `, t: u
  130. */
      e( h4 r* G: q: n4 p6 N
  131. #define     SHELL_DEFAULT_USER          "letter"8 Y0 k6 l; b- ^
  132. & Y6 V6 t7 k* v9 Q
  133. /**$ O! J& X2 t2 {' S  D1 A) ^- {' J( u
  134. * @brief shell默认用户密码
    4 Y/ R" D0 t9 e, P, Y
  135. *        若默认用户不需要密码,设为""
    % N3 p1 A/ ~$ P( r6 o  ?; _
  136. */: x4 Y8 a; Z1 Y- f" m; W' P
  137. #define     SHELL_DEFAULT_USER_PASSWORD ""# a1 g& O/ \  Y& v$ G0 m& |/ j' `* B

  138.   K' r2 Z2 S9 F% ]: z
  139. /**
    6 E$ t, J: Z/ _' E: e9 `' _. S
  140. * @brief shell自动锁定超时
    0 V9 b: K: W# J) d1 F8 P
  141. *        shell当前用户密码有效的时候生效,超时后会自动重新锁定shell
    4 \! P* S4 J; Z" [
  142. *        设置为0时关闭自动锁定功能,时间单位为`SHELL_GET_TICK()`单位
    ; B* f" S# D& `( z5 Y
  143. * @note 使用超时锁定必须保证`SHELL_GET_TICK()`有效. Q# w3 s9 _0 H- Z# P; s
  144. */" \8 {! g4 ^: A8 ?. a3 K
  145. #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
  1. /**5 z; T; q' B8 O+ E+ s
  2. * @brief shell 任务, Z, y0 C. m" u4 K/ }3 x
  3. *
    " ^* A6 a+ f9 o9 D$ t
  4. * @param param 参数(shell对象)" H# @  [; P3 A$ F' K" z9 Y
  5. * ; a8 u3 k. p+ h6 g6 V
  6. */4 Y/ R$ s$ p# x
  7. void shellTask(void *param)
    7 ?0 X& K, l; k
  8. {4 G- C7 \& ?+ ~$ @
  9.     Shell *shell = (Shell *)param;
    6 u& ]' U+ s$ B, W! n8 G
  10.     char data;
    9 m- {: m6 H- f: f& \  C
  11. #if SHELL_TASK_WHILE == 1
    * b, ?3 q  R( G3 K9 W- i' N
  12.     while(1)
    7 v5 s) {3 ]/ _
  13.     {! o4 W: v/ p( N, N1 y/ y' ]6 ~
  14. #endif5 |. y9 H1 \+ D- v; T
  15.         if (shell->read && shell->read(&data) == 0)
    ! t8 V8 q8 E4 j
  16.         {
    - S9 Q) W4 J+ j( |" e7 X; r
  17.             shellHandler(shell, data);- i  ?- G9 G+ g6 D' Q& O
  18.         }* f7 a% O2 A' l- `5 T, P$ ^% o+ b6 ^
  19. #if SHELL_TASK_WHILE == 1
    8 d7 k8 U. J5 V7 R8 L& r
  20.     }
    : e, L( _) |2 Q  E
  21. #endif- T- n2 N! B; C
  22. }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 {
  1. #define     SHELL_ENTER_LF              0
    / o1 N4 [5 y* ?, K
  2. #define     SHELL_ENTER_CR              1+ @; G3 U7 ~1 t6 o5 u( H
  3. #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& {
  1. #define     SHELL_TASK_WHILE            0( p/ L1 u3 \0 L) r% o( r# {
  2. #define     SHELL_USING_CMD_EXPORT      14 T# F8 _2 \- Y; R
  3. #define     SHELL_USING_COMPANION       09 i9 I/ j9 V$ r( }2 @& W
  4. #define     SHELL_USING_COMPANION       0
    2 V5 |- Z% d" R
  5. #define     SHELL_SUPPORT_END_LINE      0
    , I! C9 v/ g" c% o0 v
  6. #define     SHELL_HELP_LIST_USER        0' ]# Q8 n4 t: ~( E
  7. #define     SHELL_HELP_LIST_VAR         0
    + N& h" i- C% i6 j
  8. #define     SHELL_HELP_LIST_KEY         0$ G, a4 x. B) s9 x2 {
  9. #define     SHELL_HELP_SHOW_PERMISSION  1
    ; G, m4 f) ^+ k; U1 S- ~
  10. #define     SHELL_ENTER_LF              0
    $ A# m/ z, Y+ Z5 w, @7 y
  11. #define     SHELL_ENTER_CR              1
    : T* W& V% `  T& g
  12. #define     SHELL_ENTER_CRLF            0
    6 A+ f+ e# ?* U; O1 ]4 t
  13. #define     SHELL_EXEC_UNDEF_FUNC       0
    7 J) |( V! r6 ~9 w8 z  U" x& `5 K
  14. #define     SHELL_PARAMETER_MAX_NUMBER  8
    3 n. D0 l. g5 }' n& U8 M: g
  15. #define     SHELL_HISTORY_MAX_NUMBER    5
    5 }% {0 [% I% W( N% B6 {7 ?" s# w! w; Z
  16. #define     SHELL_DOUBLE_CLICK_TIME     200# U1 o( f& G1 u2 G; G
  17. #define     SHELL_MAX_NUMBER            5
    9 K2 h  Z$ [( z# c- M+ b4 \) b9 L
  18. #define     SHELL_PRINT_BUFFER          128
    / ]1 `, u  L! B! C" g7 v
  19. #define     SHELL_GET_TICK()            HAL_GetTick()
    5 m$ z7 u9 w' B4 J- ?: ?0 x& u
  20. #define     SHELL_MALLOC(size)          0
    / }3 p  [9 j% D4 E1 V+ K
  21. #define     SHELL_FREE(obj)             0: |' P% `9 D0 b3 m. g0 S
  22. #define     SHELL_SHOW_INFO             1
    1 n  b" Y% Z" ^9 A, c' Q6 x( z% `
  23. #define     SHELL_CLS_WHEN_LOGIN        1
    / o# R) M: Y5 l; i
  24. #define     SHELL_DEFAULT_USER          "letter"$ e  W3 x  [  H$ v0 h, f
  25. #define     SHELL_DEFAULT_USER_PASSWORD ""# k0 D3 R7 e& ^1 q4 X7 M6 a4 @
  26. #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
  1. #define HAL_USART3_RXBUFFERSIZE         14 B- `# I3 }' b, z- W
  2. 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
  1. 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
  1. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)+ }' _2 [3 I2 N6 ~- x! _
  2. {
    5 Y3 J! Q" K* g
  3.   uint8_t udata;
    # r, B. s  X# a  G3 O( h" |( N
  4.   if(huart->Instance == USART3)      //串口3的接收部分
    - _, u' C' g' x6 E$ e
  5.   {" `6 x; S' E& p* ]4 V
  6.     HAL_UART_Receive_IT(&huart3,(uint8_t *)HAL_USART3_RxBuffer, HAL_USART3_RXBUFFERSIZE);     //接收串口的缓存
    / b6 u4 T8 v' s+ n, C6 _
  7.     udata = HAL_USART3_RxBuffer[0];% X, s3 K. D$ K; T: I; M
  8.     shellHandler(&shell, udata);
    - {$ z6 b- l- ]) P3 ~. }/ V
  9.   }/ T* `# E! X4 a- p8 V
  10. }
复制代码

" D- G2 Z; V9 Y- J编译后下载程序,复位,这时候输入cmd 命令,按下回车,就会列出可以支持的命令了。后续如果自定义了导出函数,也会在这里显示。到这里,就说明了串口的收发是正常的,shell 也移植成功了。! j6 ]# \- x4 R1 ~

7 i( m2 \! b( f 2021012523522544.png
" 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
  1. void LD1_ON(void)$ E% |8 U/ ^2 w5 I# O  U+ n) I& v
  2. {* f7 i& k! c: \8 O2 D4 }- q. p) M
  3.   HAL_GPIO_WritePin(LD1_GPIO_Port,LD1_Pin,GPIO_PIN_SET);
    ' \3 t4 H6 ^) f- I$ ^( i
  4. }) H2 Q  t/ |& {

  5. 2 H3 |" T0 f! @1 H
  6. 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
  7. + D$ b* m0 ]; H+ E
  8. void LD1_OFF(void)
    2 a3 J. x4 {4 @5 o. b. \
  9. {
    / i+ g# H! m  s
  10.   HAL_GPIO_WritePin(LD1_GPIO_Port,LD1_Pin,GPIO_PIN_RESET);
      y$ d* w2 Y7 |! P) M
  11. }
    ( u+ d& }; F1 z
  12. ! L2 ^8 o. q4 l* ]+ Z; v
  13. 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
  1. /**8 I9 o0 b, W: A" {8 ~( [/ ]
  2.      * @brief shell 命令定义
    4 H3 r; s( K2 A+ t' d  \
  3.      * ) `# d7 X$ ?7 s4 U
  4.      * @param _attr 命令属性$ k* K( W9 H  y/ b! a
  5.      * @param _name 命令名
    : I5 F- {& K" R6 V2 y( D$ B
  6.      * @param _func 命令函数" T( I1 k4 U2 V5 p
  7.      * @param _desc 命令描述7 }! o$ ]8 Q& n9 O4 o
  8.      */
    : i3 w6 q( j" T- H$ v+ L# k/ Y
  9.     #define SHELL_EXPORT_CMD(_attr, _name, _func, _desc) \
    7 B9 q+ M7 m0 `$ I$ ^9 T/ ^  A$ }
  10.             const char shellCmd##_name[] = #_name; \
    8 |( I# _. c: w6 [: \' u% G
  11.             const char shellDesc##_name[] = #_desc; \
    $ d, ?( `0 f$ p3 ^& d5 p. E
  12.             const ShellCommand \
    4 o; g; H, ~: U4 ^
  13.             shellCommand##_name SECTION("shellCommand") =  \2 r* U: ]8 v7 b
  14.             { \
    ) ~2 x8 [0 |& }- \& l# n/ i$ U: z
  15.                 .attr.value = _attr, \
      b: z8 b& d4 d0 C3 E" k  C' ^/ p3 S1 b
  16.                 .data.cmd.name = shellCmd##_name, \* ^2 X% u" L; b4 \2 q0 R
  17.                 .data.cmd.function = (int (*)())_func, \- B2 l1 m5 z5 v7 o
  18.                 .data.cmd.desc = shellDesc##_name \/ v# w/ E# N( b" {6 ]+ `
  19.             }
复制代码

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/ ^
  1. /**
    0 y, s  [( X1 h7 ]- m
  2. * @brief shell 命令权限! p* ]. U0 G) I4 ]
  3. *
    1 T5 M( k. v" J$ w; `. r6 S
  4. * @param permission 权限级别
    ! _, T: c4 ]# f0 ]
  5. */2 L$ [' N& Y  |; L* p
  6. #define     SHELL_CMD_PERMISSION(permission) \
    ( S# R# p1 E) `
  7.             (permission & 0x000000FF)% ~% K( \0 V& w- U) C. u; S
  8. + I' N/ |1 e5 U9 Y% B
  9. /**6 R  i6 L, f' ^0 d) f( p; \8 }
  10. * @brief shell 命令类型! E1 |- ~! ~% [3 ]: R3 w
  11. * + H9 u- Q: X) y; x
  12. * @param type 类型
    2 F$ N0 M7 m  @
  13. */
    ' g7 Z/ V# [3 ?+ J8 y$ h
  14. #define     SHELL_CMD_TYPE(type) \* {7 l' D+ Y; P2 f$ ^) n" O0 @8 M& r  t
  15.             ((type & 0x0000000F) << 8)3 @. v! v- N3 d" e/ [9 x" T
  16. - C  p8 L: ^  e) N3 [; t) n
  17. /**7 P% I. ]3 z# r9 r: U9 c9 e
  18. * @brief 使能命令在未校验密码的情况下使用
    + K! r7 N6 X: j6 C# @
  19. */
    * E: l4 }# e( b, Q7 X
  20. #define     SHELL_CMD_ENABLE_UNCHECKED \' H7 ~* c, L2 b  S* ]. P+ O
  21.             (1 << 12); p+ T/ w# R6 r$ N# f+ J' m( h$ D

  22. % r0 b3 ^; I: Q/ a
  23. /**) U! F8 y& u  M+ y0 v; Y
  24. * @brief 禁用返回值打印) m! m- s+ ]- l! a
  25. */3 [- K( O' {( e# u% [
  26. #define     SHELL_CMD_DISABLE_RETURN \$ f( N. T8 y7 Q/ E- u' U6 V& g) B
  27.             (1 << 13)* ]( I' v" I% b% E0 N7 R1 _2 m0 u

  28. ; h; G. A1 F3 v6 H- ~- H/ l
  29. /**) l# k& r) p8 d
  30. * @brief 只读属性(仅对变量生效)
      k+ k3 g, @; a) h
  31. */
    ! }$ a6 n4 k: K  D( j6 I9 F
  32. #define     SHELL_CMD_READ_ONLY \
    3 c9 [; A5 W: g) W" i+ f
  33.             (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
  1. 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
  1. led1_on8 j, l1 R  p$ l- [5 g8 ^
  2. 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 ^ 20210126222149487.png
& 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
  1. int func(int argc, char *agrv[])
    " @$ k1 m8 v2 E3 E0 R
  2. {' H* O8 d! E' ?, [$ N. n  n# {: O
  3.     printf("%dparameter(s)\r\n", argc);0 D, h; k5 \: ?( i" L' ^
  4.     for (char i = 1; i < argc; i++)
    2 P/ [# @% H. Z
  5.     {  A+ K- p9 i, X3 C
  6.         printf("%s\r\n", argv);
    8 Q& |3 `: Q: k* H2 C
  7.     }
    ! _4 F8 P5 t! A( B4 u% o& A1 `
  8. }
    - @# k4 |  [0 P/ I- o+ U! f" o
  9. 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
  1. letter:/$ func "hello world"8 T4 f! I- D: M  d8 n7 D
  2. 2 parameter(s)
    * w% N& n1 @; c- a" b+ L: f  c" H1 ~+ X5 e
  3. 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
  1. void LD1_ON_OFF_ms(uint16_t on_ms,uint16_t off_ms)( e: Z* y, F$ N$ B
  2. {
    7 Q$ Y# b, n+ P
  3.   HAL_GPIO_WritePin(LD1_GPIO_Port,LD1_Pin,GPIO_PIN_SET);    //点亮LD1! u' ?( J: y) f% s8 \' ^4 H
  4.   HAL_Delay(on_ms);                                         //延时: B+ J% _/ V( o% l/ V- x- M" X
  5.   HAL_GPIO_WritePin(LD1_GPIO_Port,LD1_Pin,GPIO_PIN_RESET);  //熄灭LD1
    0 q7 @) [: {$ T
  6.   HAL_Delay(off_ms);                                        //延时5 F& _' V0 ?7 Y/ s0 y0 Z
  7.   HAL_GPIO_WritePin(LD1_GPIO_Port,LD1_Pin,GPIO_PIN_SET);    //点亮LD1
    # h- R7 g8 i0 _6 ~& o# U& Q
  8. }+ R( N* q7 o# @6 A$ u" V; H
  9. 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
  1. 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
  1. /**
    $ x( w) h3 V  g+ A" [
  2. * @brief 重命名/移动文件或子目录
    , N% ^, p) S1 I( |" _0 q
  3. */3 s- O( \: O  r( t% E
  4. void shell_mv(char *old_path,char *new_path)
    4 c; o' T& [* E, W5 ?3 s6 _8 M7 M
  5. {
    : A/ j2 {/ \% W' h1 m* O
  6.   FRESULT res;
    8 O2 m2 z7 t+ v, a: w  ~  @

  7. # d4 A1 l+ Z  g# ~
  8.   shell_active = shellGetCurrent();   //Get the currently active shelll1 F) r, Y' O. o- M0 p2 @
  9.   res = f_rename(old_path, new_path);% ]- i  [0 r' A4 y" L# p) u
  10.   if(res != FR_OK)7 [# K+ s5 C1 r$ n
  11.   {+ U1 Z: H5 y% D: Y# ]! q
  12.     shellWriteString(shell_active,"Operation failed! Error Code:");
    ) p4 e6 u( ~! \6 _
  13.     shellPrint(shell_active,"%d\r\n",res);8 C* x# B$ Y( X  g2 z9 b9 r4 |
  14.   }
    $ f1 Q- z5 ?0 c" O, [
  15. }
    4 u0 o  f; c3 l, j6 G; B
  16. 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
  1. 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
  1. /**
    7 \. z) M/ n  H0 g
  2. * @brief 解析参数* {- G) ]- Z; e* o
  3. *
    4 D* v# t! z8 O" w  U
  4. * @param shell shell对象4 L) b/ U. J- F
  5. * @param string 参数" i! C7 r8 m1 i# u- ~* B4 s
  6. * @return unsigned int 解析结果
    # F5 w1 Q# i$ Z
  7. */  p7 z2 O. [3 c' S
  8. unsigned int shellExtParsePara(Shell *shell, char *string)
    7 T! {) z, ^; `! W% [5 E
  9. {9 m* [1 D0 T( C, p
  10.         uint16_t i = 1;
    9 P  x' [! O- [" Q; S$ u& r1 L0 f
  11.     if (*string == '\'' && *(string + 1))
    ; r* [2 A6 p; }1 z2 Y9 Z2 i. G
  12.     {3 U6 A4 ^6 i( F4 [. ~
  13.         return (unsigned int)shellExtParseChar(string);
    5 E% n% M8 t' f+ z
  14.     }
    ) @, H* U# g2 L2 b, Y2 f
  15.     else if (*string == '-' || (*string >= '0' && *string <= '9'))3 P) R( ~% X& M/ x* K1 E6 F
  16.     {: _5 U+ L* `. z& Y9 b2 j
  17.                 while(1)' s, O6 @$ e1 H6 _0 J( x
  18.                 {8 b, k8 ?4 M0 C& @$ k
  19.                         //寻找下一个表示该参数已经结束的字符$ L9 U3 N7 v$ L" W5 Q
  20.                         if((*(string + i)  == ' ')                 //下一个为空格表示该参数已经结束/ C. n& B" J3 `( r6 \9 @
  21.                                 #if SHELL_ENTER_LF == 1                        // LF 表示命令结束4 j" T: k0 \0 y) l! y- l
  22.                                 || (*(string + i)  == 0x0A))        
    - E7 E3 ]- ]. `+ b' Z$ m9 N
  23.                                 #endif
    / V0 B: m8 N& m  v, t
  24.                                 #if SHELL_ENTER_CR == 1                        // CR 表示命令结束% L* }6 \7 _5 r7 q3 S" c
  25.                                 || (*(string + i)  == 0x0D))8 }) W- ]4 f% Z. C" n4 H$ y' I# Q
  26.                                 #endif( I4 \+ l$ ^, w" q
  27.                                 #if SHELL_ENTER_CRLF == 1                // CR LF 表示命令结束6 `% i6 z5 H0 z. p: y9 H2 Q! q/ V) S
  28.                                 || ((*(string + i)  == 0x0A) && (*(string + i + 1)  == 0x0D))1 T/ U9 F3 d+ z+ m( ?
  29.                                 #endif
    ; l8 |/ U4 o- t: n1 H8 j
  30.                         break;
    " X4 O6 h* q  d
  31. 2 w2 y+ c1 c' d% F: X" P, y) G# i
  32.                         //出现不为小数点或者非数字的字符,表明该参数为字符串
    5 C2 w( b. |( A! ]7 @
  33.                         if((*(string + i) != '.') || (*(string + i) < '0') || (*(string + i) > '9'))0 q9 L: Y. y4 j/ e- C, v
  34.                                 return (unsigned int)shellExtParseString(string);
    # t6 g% h- ~+ ~; ^- ?
  35. $ b; }8 R/ g3 R, Z$ E2 [: \- q4 j
  36.                         i++;
    3 h! h) f- `2 S; z3 f
  37.                 }
    ) b6 ~, b! j! m: q+ r/ L
  38.         return (unsigned int)shellExtParseNumber(string);3 z2 k/ w% O, r/ u. z6 E1 \0 m8 T2 B
  39.     }
    ( A/ ]  e  i0 l8 z
  40.     else if (*string == ', [- O0 Z1 s. x1 u5 j2 F
  41. [b][size=3]六、其他常用的 Letter Shell 函数[/size][/b]
    & n: y8 X: k0 D( o) e
  42. [b]6.1 shellWriteString[/b]8 q: i# ?8 `9 C! |* d% x# I7 ]
  43. 函数原型如下,这个功能就是 printf 。这个应该很好理解,就不细说了。' e# P# E/ e% k. N  g9 [) @
  44. 0 ]5 E* j: L* v  }6 C
  45. [code]/**1 v. e- C) `8 y. p7 X. K
  46. * @brief shell 写字符串
    ) n( N6 S! c4 e* Q8 _( [
  47. * $ q% e* t5 p' ~
  48. * @param shell shell对象
    0 N0 J8 J+ M) y0 L9 E
  49. * @param string 字符串数据
    + d/ n6 d4 w& f' R- O, {( t
  50. *
    ! l7 }) ^4 I$ y
  51. * @return unsigned short 写入字符的数量5 B* T0 n, s9 @
  52. */
    * @0 m$ P6 y& m2 m/ z
  53. 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
  1. /**+ r7 p  u# f( U* v7 z8 L
  2. * @brief 获取当前活动shell
    ) {2 F5 Q. n8 g5 {. r5 c' |" g
  3. * / ^4 W+ N/ z$ A- H/ S6 i7 j1 v
  4. * @return Shell* 当前活动shell对象% @7 f3 b4 I. \6 s* z" ]7 u
  5. */
    5 d0 Y( N! ]( z/ P4 j1 q3 @; A9 m
  6. 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
  1. 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
  1. /**
    : F) S% g# U' \* M
  2. * @brief 重命名/移动文件或子目录
    , u8 _7 a0 a: N  b
  3. */, R+ j  I& X+ i" [- @/ A8 D6 l0 U
  4. void shell_mv(char *old_path,char *new_path)
    5 d4 C( O- L" A% ]% Y, Y" r% s
  5. {
    : i! s3 ~. @  X8 p4 ?2 ~/ Q
  6.   FRESULT res;$ Q% J( K6 \; Z# h6 r2 g3 v

  7. % S6 t6 r) a" W3 x* b$ P8 x
  8.   shell_active = shellGetCurrent();   //Get the currently active shelll
    ; |1 K! h  A; w& a5 Z
  9.   res = f_rename(old_path, new_path);
    5 s# [% c# v# |  Q- T* o
  10.   if(res != FR_OK)
    1 l6 h4 e0 j+ h8 j! l, s9 k
  11.   {
    4 a% F0 K$ V$ X$ \
  12.     shellWriteString(shell_active,"Operation failed! Error Code:");
    : D- m4 t' H8 P* N3 ]
  13.     shellPrint(shell_active,"%d\r\n",res);
    9 n! ?4 o3 ?. v! F
  14.   }3 b, j! }. d( Z; j# \0 o& G# W$ @
  15. }; M" ?6 _2 W4 ?: o  A) Z
  16. 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. ]
  1. #if SHELL_PRINT_BUFFER > 0' p) R' M8 X. h6 h
  2. /**# m% a; S) J! [- N- B! I0 K
  3. * @brief shell格式化输出! d9 S# H) O. f' O3 e
  4. * ! U0 a4 U) f6 v" H  ~+ G* x
  5. * @param shell shell对象1 d# h: w8 u! a# }
  6. * @param fmt 格式化字符串
    1 q2 a' H! ^6 _7 p2 L
  7. * @param ... 参数
    , j1 B% U. k, R$ N. w
  8. */$ [( ]) P2 ?0 q
  9. void shellPrint(Shell *shell, char *fmt, ...)/ x4 W  T# ~& M+ x, z7 M8 T
  10. {
    / V, J0 w4 S, Z% L( H8 K# B+ u4 C
  11.     char buffer[SHELL_PRINT_BUFFER];
    - @6 g$ Q1 L# b" r% r* ~
  12.     va_list vargs;0 |+ |3 k" }. \" E% i

  13. + ^3 v( l% p$ N" Q$ ~
  14.     SHELL_ASSERT(shell, return);
      }- _3 c; x: Q" d* n4 o2 R

  15. 4 o' F5 O, d" n; l; q
  16.     va_start(vargs, fmt);0 L4 }6 {( s! A  s0 W- ]% E" B
  17.     vsnprintf(buffer, SHELL_PRINT_BUFFER - 1, fmt, vargs);$ @0 Y, q' x3 G# f5 O4 S6 Z* y" v% m( S
  18.     va_end(vargs);
    . f, o/ O- D' q9 F
  19. ! u$ e3 E* y; W& s7 w- T2 u& U3 U" }! q
  20.     shellWriteString(shell, buffer);
    " y& @& X& E; h) F# r2 a+ S
  21. }
    ( g" t' `/ Z! {7 K! K3 L$ b
  22. #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
  1. /**
    ' s9 Z  x, {# o
  2. * @brief shell 写字符串
    * E6 c3 t$ X) f9 z6 h' [2 Q$ d
  3. *
      Q7 d! B) @9 ]/ j' m2 y3 c+ x
  4. * @param shell shell对象
    0 @% R0 c, g- a6 d2 W0 l
  5. * @param string 字符串数据5 d7 T( G! L2 w8 T) |
  6. * + n6 y5 a5 g* A2 |6 q0 K
  7. * @return unsigned short 写入字符的数量
    , \+ I" V. b/ h7 i6 g
  8. */
    ( f8 l$ [$ n1 c+ g) d# U9 E4 y
  9. 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 }
  1. /**5 ]0 L% _" g3 w. n2 R
  2. * @brief 获取当前活动shell
    ( |4 _+ p& n9 U
  3. *
    . i' f9 V2 M# \! Y4 G2 n; o
  4. * @return Shell* 当前活动shell对象
    0 ~- w8 G3 M, b5 T# `5 a/ P
  5. */" X! ?, q' G4 L4 J9 y, x
  6. 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
  1. Shell *shell_active;&nbsp; &nbsp;&nbsp; &nbsp;//当前活动的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
  1. /**. [; ?  f/ X1 d& a
  2. * @brief 重命名/移动文件或子目录
    ( W& E% R5 ~. O: E5 e
  3. */
    ! X( E- E/ h* h# S: u( B+ q
  4. void shell_mv(char *old_path,char *new_path)
    ; Q% V  Y- P. G8 ~1 F! k
  5. {
    9 J" {/ N  D5 Q# @! @# n
  6. &nbsp;&nbsp;FRESULT res;
    - O3 p0 n; Q- c; }

  7. # k* c; G1 P9 q
  8. &nbsp;&nbsp;shell_active = shellGetCurrent();&nbsp; &nbsp;//Get the currently active shelll
    % }( `$ i- D: z$ Q+ c3 R9 J# I2 v
  9. &nbsp;&nbsp;res = f_rename(old_path, new_path);
    0 o. ?+ G  l  ^9 q
  10. &nbsp;&nbsp;if(res != FR_OK)0 I* L6 Y4 Z" u+ j) ^
  11. &nbsp;&nbsp;{6 D$ v# o: @; D1 E
  12. &nbsp; &nbsp; shellWriteString(shell_active,"Operation failed! Error Code:");6 t6 W5 n0 l" Y6 ~, V1 D& }
  13. &nbsp; &nbsp; shellPrint(shell_active,"%d\r\n",res);
    % I7 A0 v) @# d
  14. &nbsp;&nbsp;}
    , }- I: `$ v3 W: w: Y& H9 |& h+ {$ f1 y
  15. }
    ( e4 `: a% Q0 ?! I% Z: A
  16. 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
  1. #if SHELL_PRINT_BUFFER &gt; 0
    * S- q' H/ ^: Y7 D/ p
  2. /**. z- n$ i4 g0 x: V: q
  3. * @brief shell格式化输出) ]# P2 e; V- K- f9 d! G7 Y
  4. *
    * y' |+ ?1 {, F- E. v6 k3 b! M! N
  5. * @param shell shell对象/ [, t' X, B  z7 o
  6. * @param fmt 格式化字符串
    / a0 b5 T% K/ W4 L  S: B" e5 ]
  7. * @param ... 参数
    " E2 _+ e* B( u
  8. */
    " i/ `! w. H, M- N1 U
  9. void shellPrint(Shell *shell, char *fmt, ...)
    . J  k8 X, s/ h, r% L6 c
  10. {
    $ e/ B, u4 u7 r; T
  11. &nbsp; &nbsp; char buffer[SHELL_PRINT_BUFFER];2 l& s2 X* _: E+ P4 i2 t
  12. &nbsp; &nbsp; va_list vargs;, A' q/ h  w3 ]
  13. ; _& X+ [, _' ?  F) l# v' i" z
  14. &nbsp; &nbsp; SHELL_ASSERT(shell, return);
    # |; n: m6 _- }" e3 S% A; _9 ]# z
  15. $ ?( w5 d5 }4 f3 b
  16. &nbsp; &nbsp; va_start(vargs, fmt);) b6 h% d4 K. \: v! o/ _5 t9 q
  17. &nbsp; &nbsp; vsnprintf(buffer, SHELL_PRINT_BUFFER - 1, fmt, vargs);
    - s/ A; m; W/ T1 W0 w! r
  18. &nbsp; &nbsp; va_end(vargs);
    - h% U3 ]2 ]9 k! Q2 N
  19. : p" r# r% s) F( N' K5 x% F! n1 D$ X
  20. &nbsp; &nbsp; shellWriteString(shell, buffer);3 {! j; q" b% K1 r1 v4 p& o
  21. }
    " x$ R+ w9 |0 P1 R$ ?/ M
  22. #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
R`)~GN97DP3}]5R[IQUN}XE.png
收藏 评论0 发布时间:2022-8-23 16:13

举报

0个回答

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版