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

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

[复制链接]
STMCU小助手 发布时间:2022-8-23 16:13
前言5 R& t, m3 D: e" f
一般使用串口来打印调试信息之类的,正点原子的USMART也不错,这边引入了一个类似于Linux 命令行的调试功能,Letter Shell,功能很强大,github的链接在下面。( q- g. w; X4 h( }7 t! V" {% X
我们要移植一下这个功能,方便后面的调试。这个功能使用到了串口,接收到串口数据后回传到上位机,直到接收到命令行回车触发,也就是0x0A(LF 换行)以及0x0D(CR 回车),不同的软件,结束符可能也有点区别。确认收到完整的命令,就可以解析命令,根据不同函数的地址就可以进行调用了,这样子调试程序就很方便了。
9 s& Y) M& v1 U% l$ U4 H4 G效果图如下:
2 s$ a2 C  e  c2 ]4 i1 O2 w4 E* N) C3 |& j7 u
20210220214542675.gif
% j* M4 _* y" O( _" h
$ W* s2 b) n- R
一、FreeRTOS配置
4 X; q' j, \0 X% F- Y' O! Y1.FreeRTOS

4 r' s2 ^" P% Z8 [; r( _- e) yLetter Shell 仅需要用到串口,然后使用shell 的回调命令,就可以使用了,但是后续我们需要使用到FreeRTOS,这就涉及到中断优先级的问题了,STM32CubeMX有16个优先级,0为最高优先级,15为最低优先级,FreeRTOS可以管理管理包括优先级5-16的中断,我们这里先配置一下FreeRTOS,暂时不需要其他功能,默认配置有一个defaultTask的线程,我们用这个来控制LED以指示系统的正常运行,所以这个按照默认配置就可以了。& {6 m) Y9 D: \2 d/ h2 r7 [# a

: T3 a# g! d( [$ C% E 20210124211939416.png : D2 K8 _2 b4 G9 Z
; N6 B" T3 \7 M* P9 [( `, x+ V
FreeRTOS需要使用时钟基,默认是SysTick,滴答定时器,默认配置是1ms中断一次,然后计时器+1,但是SysTick并不是很准确,我们可以在SYS 选项卡中改一下Timebase Source,这里我们改为定时器3当时钟基。; O' O  A# T/ ^5 h
20210124222836342.png 4 D6 Z" Y4 p7 B! K

9 t+ l( v! ~- K; f: g: e然后配置一下串口,因为Nucleo板载了ST-LINK V2.1,有一个虚拟串口连接到USART3,所以使能USART3,然后使能中断就可以了。点击GENERATE CODE生成代码。6 T6 ]+ P: t' {. F) L1 S

3 |9 V" C+ l9 @0 v 2021012422385383.png " {8 b$ T0 i& U8 B, ]3 p3 D5 e8 s

! P- b3 z: h) J% i先打开 “freertos.c”,里面有一个默认的线程,StartDefaultTask,我们在这里加一个LED闪烁的功能,先测试一下生成的工程没问题。) a& p3 e% ?3 X! _5 }
0 e* @+ n, r- y. @; V. n$ c* j
  1. /* USER CODE BEGIN Header_StartDefaultTask *// V' o( N& G6 M- n
  2. /**/ n+ r4 P0 Z: z2 H/ u
  3.   * @brief  Function implementing the defaultTask thread.
    $ U; W8 s; e( o: u
  4.   * @param  argument: Not used
    - b! k$ b  d6 K
  5.   * @retval None( f, u0 Q6 ^4 a& D0 S
  6.   */0 x- R/ v  r  q+ [+ j# C
  7. /* USER CODE END Header_StartDefaultTask */
      P/ W! I" w' ]- z( u" g4 g- d
  8. void StartDefaultTask(void *argument)
    5 x& U% U; @7 ?5 q! I; x# G" f$ J
  9. {
    % ~. D( C2 z- h% H2 o: q* v, b# X, v
  10.   /* USER CODE BEGIN StartDefaultTask */
    6 z/ f. {6 S7 l8 O# M' I
  11.   /* Infinite loop */2 M: x* V; z; s& g: ^5 S+ p0 |0 D% ^
  12.   for(;;)
    ' \  ]3 A5 n0 \/ ?0 ]% I3 |
  13.   {
    ! J  ]6 t& a# ~2 K9 A
  14.     HAL_GPIO_TogglePin(LD3_GPIO_Port,LD3_Pin);
    ( Z2 h6 G9 [" k9 B- B3 C6 y
  15.     osDelay(100);( f( ~( F% i# f4 Z$ B
  16.   }
    1 D" w6 ]4 Z& ~
  17.   /* USER CODE END StartDefaultTask */
    ) n( P( z) b! j; |
  18. }
    # t$ E. _9 }, ]3 w& W' W$ v
复制代码

2 b# p6 J$ a/ F& C$ `& f& O& ^0 L1 h注意,配置的Keil工程,默认是没有设置下载完自动复位,我们需要按一下Nucleo 板子上面黑色的复位按钮,另外默认的配置下,有一个以太网的初始化 “MX_ETH_Init()”,需要接入网线到路由器,不然初始化不通过,会跳转到Error_Handler();
  1. if (HAL_ETH_Init(&heth) != HAL_OK); C- _' R7 W% @, F9 v* S
  2. {
    $ R# ^: E+ q# T
  3.   Error_Handler();
    2 }7 S+ g6 ?4 g# y/ n7 \
  4. }
复制代码
; @. N4 z: i8 X" G# c( n
二、移植Letter Shell
# U4 |" T$ W" U7 |下载github上面的源代码,解压出来。7 R2 v8 q/ M2 ~* p( K" M
* g$ @5 F/ U& D( b1 `. W2 r
20210124230430874.png
  h: T# _: ?: f7 o: m
: O* o% }* L, S1 i7 jdemo 有一些平台上面的例程& o1 Q2 ~2 c1 F9 B8 P
doc 是功能演示
$ r# b0 b8 ^& Q. Dextensions 有一些扩展功能
1 D9 Q& \7 T( q- [src 是源代码2 u2 l9 A: G+ ?  H
tools 是一个用于遍历工程中命令导出的工具,位于tools/shellTools.py,需要python3环境运行,可以列出工程中,所有使用SHELL_EXPORT_XXX导出的命令名,以及位置,结合VS Code可以直接进行跳转
# b. {" v8 j4 J1 p) w% z, o2 F6 [0 F, s7 k, }
我们在STM32Cube 的工程项目下新建一个文件夹 Shell,然后把 src文件夹下的源代码都放进去,遵守开源公约,LICENSE也要记得放进去。放进去之后,demo\stm32-freertos 目录下还有三个文件,我们也拷贝过去覆盖即可。/ T) {/ S; S) o, g
  L- R1 O& j- G" a. H3 w
20210124231354443.png 2 k8 s1 I' T9 U  K/ `  W; k3 L1 D5 i

6 m( m4 U/ p: s& C然后在keil工程下添加这些文件。
4 @1 k& ^3 `& j% Q( m' q0 V+ Z" f# i6 }+ L1 u+ B5 S
20210124231834258.png / R! m8 o1 l" A

& c3 [  e, ]2 n编译一下,此时会报错5 p) v$ W$ `8 _4 \3 A, g! o
; q0 q& h$ E* J* d; ^  y
  1. ..\Shell\shell_cfg.h(15): error:  #5: cannot open source input file "stm32f4xx_hal.h": No such file or directory
复制代码

: K" E" G' w3 F" F: E原因是letter shell的demo使用的是F4的板子, “shell_cfg.h”,里面包含了f4的头文件,#include “stm32f4xx_hal.h” 。我们这是 F7的板子,并且是STM32CubeMX生成的工程文件,所以我们只要替换成 #include “main.h”,这样改了一次以后,以后换芯片系列也能很方便的移植了。再次编译,仍然会报错。
, k8 \* k6 L/ m3 B* v, E
$ T5 C' o6 \. j1 C
  1. ..\Shell\shell_port.c(13): error:  #5: cannot open source input file "serial.h": No such file or directory
复制代码

; \4 L5 e' \3 W, X这是因为 “shell_port.c” 中有 serial.h,看名字,这是与串口相关的文件,我们把这种不需要的头文件删掉,只留下两个头文件就可以了。( p( k% P2 P: ^. r
7 V, F9 d$ N/ X( o( T1 k
  1. #include "shell.h") D) @" ~4 T! H
  2. #include "usart.h"
复制代码
. A# A3 `& x) E
下面两个函数是shell读写的实现,里面使用的是固件库的函数。, X( \4 m3 S: u7 p
2 E1 n# }( _2 ]4 l9 h) F7 E% \' j
  1. /**
    ) @4 v! i0 ^, g' B2 a5 m8 @  X
  2. * @brief 用户shell写
    $ R! t- e& Y. F3 J0 I' ~: ]1 p
  3. * ; @, L# O% G7 ?2 H' i$ n
  4. * @param data 数据
    % n) {0 H- ~3 h2 B, F
  5. *// g5 J5 i* s  c+ A! O7 \
  6. void userShellWrite(char data)
      G1 f5 T, i' [6 T/ a
  7. {6 S0 I" X3 O( T3 a, l- `- c
  8.     serialTransmit(&debugSerial, (uint8_t *)&data, 1, 0xFF);, q7 B6 L6 }# t5 A8 w
  9. }+ x. k# q! g5 m8 x7 F6 V; n
  10. $ D7 G+ f$ L2 Y5 s+ X) a
  11. /**
    - }* b: R; v$ ^4 C+ I
  12. * @brief 用户shell读: y- f! s* c6 W+ t% m9 F
  13. *
    9 I4 K- e0 ]4 S' k; x
  14. * @param data 数据6 C; u6 o* M' l7 R, q2 R
  15. * @return char 状态4 S2 H$ H. c! t( `
  16. */
    / ~7 h, U1 r5 T8 w$ ~6 G
  17. signed char userShellRead(char *data)
    - h! o' ~( B( N" `
  18. {5 B4 c9 s/ ~8 y7 j( {0 c7 m
  19.     if (serialReceive(&debugSerial, (uint8_t *)data, 1, 0) == 1)
    ; x# _4 v/ E* U! K, J1 i; A- X
  20.     {+ w  X+ t( [9 W5 v, |
  21.         return 0;
    . q! l) _6 v) I# `; |4 o: G
  22.     }4 }  X5 H1 C' B, K+ E; y
  23.     else
    & P: f! t" x3 H" f8 l- A/ r
  24.     {2 W: s8 a- f6 V
  25.         return -1;# [5 R! ?' W5 c) N7 N
  26.     }
    9 F; v% x9 z5 \5 ]. N

  27. " V# r* j( J/ Z( s0 M. S+ _
  28. }
复制代码

" w/ z: e. Z8 \6 r3 p既然使用了STM32CubeMX来生成工程,这里我们改成HAL库的形式。  V% G+ I; n" i5 H

5 g# E) l$ R, J9 p% t% t
  1. /**
    ; ~- h' h- |; v; d4 m. X5 X
  2. * @brief 用户shell写3 t" b% b$ c; \! ?1 Q. x
  3. *
      v  C4 W( t# C) s
  4. * @param data 数据. S+ y! V( j6 g7 v* A) L
  5. */- k; B* N, {- X, z; |
  6. void userShellWrite(char data)
    9 v) x4 l! }5 ~
  7. {- y% y* }! T- K8 P" v1 i$ r
  8.   HAL_UART_Transmit(&huart3,(uint8_t *)&data, 1,1000);
    $ n0 I; T* i6 c6 N" B6 D7 c8 _
  9. }
    , j: y5 C7 A6 m& ?  t% s$ \. Z
  10. * S! i# h' t5 @- q
  11. ) e) P% h8 V; @
  12. /**- s' d/ }) ]2 _" O" ~! @9 c
  13. * @brief 用户shell读( I; v# F7 U1 L' Y! ?
  14. * ! O9 l6 U* T0 \0 }9 d
  15. * @param data 数据
    8 s0 h; C: v5 x
  16. * @return char 状态+ H, i2 h) ^3 \) ^
  17. */
      H5 O$ n0 J- j
  18. signed char userShellRead(char *data)
    4 c+ Y6 f1 [2 Z% f1 b/ z
  19. {7 U2 N5 U' H# a2 R) |
  20.   if(HAL_UART_Receive(&huart3,(uint8_t *)data, 1, 0) == HAL_OK). v9 I3 M, R4 [1 Z( l/ _
  21.   {! i9 t% n0 D( d  H) S  `+ e, V
  22.       return 0;; K  W. ]6 ^& S1 h# Q  S
  23.   }8 T% [' p8 d! D6 q0 Q
  24.   else
    ! n7 l/ J7 i- p; o
  25.   {
    ! ]4 V( }6 q+ t4 j; C0 f6 o
  26.       return -1;4 M: c7 {/ x! E3 x0 v' M
  27.   }
    5 e1 N2 l/ s: _( n* f
  28. }0 r, H9 N. p  C3 a0 L, _
复制代码
4 I$ W- s+ X) J
再次编译就不会出错了。: A% H6 y' t+ P  l7 P

) ]- N  Y3 M2 x1 Y% b三、Letter Shell的使用$ o, y2 q1 x/ C% X; e2 k/ n
github 上有具体的使用说明。我这里是下载的最新版本,3.0.6,这里也是根据这个版本进行一些解释性的说明。
2 K' l: L5 B7 I, _8 e' [9 O
! f/ U6 _* `; a( Z5 B3.1 定义shell对象
# a; C( c8 [( I% N. \. o  w( y, b: \& P
在 “shell_port.c” 中定义了 shell 的对象。这个Shell结构体定义了历史的输入,读写对象的函数指针等等。9 }# m2 L" i3 o# c) G

& K) @# [5 P8 g" o
  1. Shell shell;
复制代码
7 [6 R* i: C3 K% E9 _: z( ^
3.2 定义shell读写函数
) ^' p8 V0 K" n7 q/ y5 ?定义shell读,写函数,函数原型如下:
4 R$ k  L( _9 q' R9 [6 T0 x8 b7 U) ^3 q7 R: w% j8 H3 ^
  1. /**- E/ |  l" L3 Z' u
  2. * @brief shell读取数据函数原型: @- E" D  M( f! n% m
  3. *
    , n8 P5 P% d* i' _
  4. * @param char shell读取的字符
      r: f1 h# h0 l) s. r* }1 U
  5. *( u0 L* y: i; M( V" }7 c2 P
  6. * @return char 0 读取数据成功4 L* f/ J1 @: c5 E- G8 q4 T% q/ N) F
  7. * @return char -1 读取数据失败
    : G) {+ d9 t- U  ^
  8. */
    & }# x, A9 n4 Q' O' J+ ?
  9. typedef signed char (*shellRead)(char *);: z8 `$ v" L; J% b8 W& G

  10. # \0 E" z8 Y& ^
  11. /**, a  j' ?7 A& t* s) G
  12. * @brief shell写数据函数原型' \: F' |5 x) w2 Y$ X
  13. *; v9 t3 K8 Q2 t" \' q
  14. * @param const char 需写的字符. Q# s' O" M# `; r: B" r" t; K5 x# S+ C* l
  15. */! \5 p; L6 i9 D
  16. typedef void (*shellWrite)(const char);% W: v3 C$ N1 z3 X2 s5 F
复制代码

/ B- U! ~* }4 l) d我们在上面已经进行了修改。6 C; k# k( |# ]8 b' O
* t' _! _" O+ ?- A
3.3 申请缓存区
3 v/ [& b( R" u申请一片缓冲区- D# p3 {( Z- w1 Y( \; p( Q" Q
, r8 A6 |6 m; [6 ]  g# [) L
  1. char shellBuffer[512];
复制代码

. F% T, F- c' T% c& l5 @3.4 调用shellInit进行初始化& U8 O' p" h' g! |
调用shellInit进行初始化,函数实现如下:
' Q4 e* s/ }  Z4 r7 |
: ^& U0 \8 p; A$ h3 c3 L0 D
  1. /**
    & C: c/ x( z, {$ B
  2. * @brief 用户shell初始化0 m1 F2 o3 m8 z, T% J
  3. * 9 s7 _3 ]9 |9 N
  4. */6 T& \. l3 \0 l6 `6 i& n# ]1 n
  5. void userShellInit(void)% t" {9 m) \5 `0 Y+ T9 \6 D
  6. {! S5 T; y2 l1 f" f' x$ l0 D
  7.     shell.write = userShellWrite;) a& |4 p* P  F3 Q8 \
  8.     shell.read = userShellRead;
    5 o/ V8 C1 }! Y
  9.     shellInit(&shell, shellBuffer, 512);
    % [  Y& V2 o; M. P5 J
  10. }
复制代码

2 x) t9 G& }* k! M0 C# l% Y" |3.5 初步完成shell的初始化4 E  u4 Y' r. n) c
以上步骤,除了要修改shell的读写函数,其他的在shell_port.c中都已经实现了,所以我们在main.c 添加头文件
4 U! Q( t. B1 q- P* u4 k% A! H8 s9 M' A
  1. /* USER CODE BEGIN Includes */8 l# U; o  A' X) c6 }
  2. #include "shell_port.h"" K3 ^2 m4 ~6 k4 Y8 \# O
  3. /* USER CODE END Includes */
复制代码
3 p& ]% [( ?) C5 C8 J5 D  S
然后在 main 函数中初始化shell,基本上就移植完毕了。* G/ P6 ~: R! V6 v
  o. b8 X# F- a: g/ n4 ]! w
  1. /* USER CODE BEGIN 2 */5 X2 o/ a" I6 ]/ `6 q5 r) |' e
  2.   userShellInit();    //letter shell 的初始化
    - Q) B4 R0 r7 [+ Q  a" H" s
  3.   /* USER CODE END 2 */
复制代码
$ k" D) o. `2 ^; ^, N4 f! y
此时编译一下,还是报错,老样子,是因为没有 “serial.h” 文件,我们双击报错点,跳到异常点,把这个注释掉即可。然后再次编译,就没有报错了。, h# L  y1 R& l5 W& F
  W+ \1 [- i5 D4 E' A; c. y% l' h3 d
  1. ..\Shell\shell_port.h(15): error:  #5: cannot open source input file "serial.h": No such file or directory
复制代码

0 l3 V' i6 t* M7 L. h我们把程序下载到Nucleo。
" s! ?" m8 E! [8 @然后打开SecureCRT 或者 MobaXterm 或者 “putty”,建议是使用MobaXterm,因为这个功能强大,且有免费试用版。
7 x1 G# d6 q* |  b. e, z0 }: m8 A9 r2 L# J" ]$ I' F2 @
我们需要建立一个串口连接, SecureCRT 点击快速连接, MobaXterm 点击 Session ,然后找到串口的选项,串口的端口就是 STLINK-V2.1的虚拟串口, 波特率为115200,其他的默认不变,点击 OK
- Z  p# ]. i2 m3 z( A  `+ T
" Y8 J/ }& e& T# ~1 q) e( Z 20210124235734855.png 4 q) n9 c8 x  b# ?) x9 c4 @

0 u) c. z" d+ W! u3 w" M% j& {由于我们还没编写串口接收方面的函数,按下字符是不会进行回传的,这里我们先按一下复位按钮,接上网线等到初始化完成后,就可以看到 letter shell 初始化完成后的串口信息。1 L. J) |) Z# v. ^/ ~; [* x; ^

8 o' v+ h4 R0 U  G  Y6 d 20210125000101173.png
9 }1 e/ o; Q+ N9 M/ r/ m
+ h7 X5 y% E1 B) Q: ^- h) d/ r! A四、Letter Shell的进一步了解
* R4 V" `( L/ e" |# Y第三节已经完成了Letter Shell的初始化,在这一步,已经能够shell初始化已经打印的功能,但是要想使用好letter shell,我们还需要对letter shell有进一步的了解。
! l  h+ h" s. Y* Q- [: X5 G& Y# o( _9 Z
4.1 宏定义7 g4 f2 V2 j. N+ v, Y
在文件 “shell_cfg.h” 中定义了各种各样的宏,用户可以根据不同的需求来配置宏。$ `* d/ p0 a1 I6 X& `
下面的表格列出了各个宏的意义。
* p, _# _# p3 Q# t. Y3 W4 g7 E3 [. j
Z9[D2ZUY1[R~NU1CT(4F8BN.png
7 D( Z5 t3 I0 S. F0 X, u6 o
7 `) L# W8 I. X7 N: r- o在文件 “shell_cfg.h” 也有具体的说明。3 t! F' r$ X% z2 v: }

3 r6 }  r4 t: D
  1. /**6 Q" w1 _4 u; a
  2. * @brief 是否使用默认shell任务while循环,使能宏`SHELL_USING_TASK`后此宏有意义
    1 W* U% j& V$ k& n* |4 o; S
  3. *        使能此宏,则`shellTask()`函数会一直循环读取输入,一般使用操作系统建立shell2 c* W1 S9 y5 [+ _0 t0 r
  4. *        任务时使能此宏,关闭此宏的情况下,一般适用于无操作系统,在主循环中调用`shellTask()`% r% W4 Y7 z/ `) {; a, N7 b7 {
  5. */
      B* `5 Y) @+ w0 B; e& t9 N
  6. #define     SHELL_TASK_WHILE            1
    9 X9 o9 M$ O* C! a( X2 T- ~- F1 U
  7. ; O4 ^0 s0 t# Y- E, {6 h/ r- V
  8. /**$ f) x& k1 @$ V( k& l# ^
  9. * @brief 是否使用命令导出方式; e& p2 L4 k. O' s/ J- H5 H
  10. *        使能此宏后,可以使用`SHELL_EXPORT_CMD()`等导出命令( O6 U$ A1 w( b7 t
  11. *        定义shell命令,关闭此宏的情况下,需要使用命令表的方式
    + u1 b" y) t% ^, [1 e
  12. */
    . P1 `" q4 p+ s' P
  13. #define     SHELL_USING_CMD_EXPORT      1
    ! ~  n  j9 i% P

  14. + o( v6 m" P0 p/ D5 ]
  15. /**
    ) S- k- X0 q) G$ f: V$ n
  16. * @brief 是否使用shell伴生对象$ M' h* r: j8 [: b' }. [# s
  17. *        一些扩展的组件(文件系统支持,日志工具等)需要使用伴生对象
    ; M' h, o% ~5 ~# |1 H0 f
  18. */8 s! ^# Z- \4 p  h5 |: v& ^
  19. #define     SHELL_USING_COMPANION       0
    . B$ d) ]- H& \0 i& [' f

  20. 4 |4 J9 l) L& q1 ~! V9 Z/ S* G
  21. /**
    ) D  f- f$ [1 B1 ?
  22. * @brief 支持shell尾行模式& i  x4 D# i! x
  23. */
    * d3 J" S; q6 a5 y! x. `3 t$ r9 e
  24. #define     SHELL_SUPPORT_END_LINE      0
    * r+ W1 o+ @7 I5 V1 J* l1 K

  25. " b" y( Q% t7 _$ q# E
  26. /**/ E9 k" i4 k1 p+ N% ?: o9 t# x
  27. * @brief 是否在输出命令列表中列出用户
    . U9 @$ o9 c& _6 v0 B
  28. */
    3 n  U- O% S2 u- K
  29. #define     SHELL_HELP_LIST_USER        0! U$ ~% v) a: M. B$ M/ `

  30. - a' r2 r$ j; U5 h0 X& o5 l
  31. /**
    2 w6 t5 Y# k6 w3 d
  32. * @brief 是否在输出命令列表中列出变量& l* D) f) Q4 ]. T8 m/ G, ?
  33. */
    " _/ W5 _6 w. W2 V( u
  34. #define     SHELL_HELP_LIST_VAR         02 e# ?) p. x+ n% t# x' ~8 V

  35. # A3 L7 ^  f  R; ^+ C
  36. /**. L. P5 L( N$ S. v# c3 V+ u4 u6 g
  37. * @brief 是否在输出命令列表中列出按键1 p; x5 h$ ]4 d, [; T: p& W% l
  38. */
    " t  G, [9 o$ y
  39. #define     SHELL_HELP_LIST_KEY         0! Y9 k6 b4 s: N

  40. ' X0 ]: M% R. b
  41. /**- `5 Q% @" n0 P. S4 s
  42. * @brief 是否在输出命令列表中展示命令权限1 i  D6 i, }$ u. Y( z; I! W
  43. */
    0 S) X8 e) f! k1 T8 W
  44. #define     SHELL_HELP_SHOW_PERMISSION  1
    5 J) s* k  ~; y, |+ ?" d

  45. ! r6 E; U5 L2 j/ c
  46. /**8 P" z  n; z& F% M
  47. * @brief 使用LF作为命令行回车触发
    # g! y1 z# ]- K
  48. *        可以和SHELL_ENTER_CR同时开启$ _: _( [- A( C. X+ O! |
  49. */
    4 m1 b8 r* g  T
  50. #define     SHELL_ENTER_LF              0& d3 m# R9 z, ]* Y# n9 V

  51. & b, Q- B0 D" ^4 E1 f, b
  52. /**0 A* |, F7 R- t
  53. * @brief 使用CR作为命令行回车触发
    6 z% q1 Z$ F+ ?9 l# D. ?/ I
  54. *        可以和SHELL_ENTER_LF同时开启; @1 R& h' A5 `1 q- P4 \: ^8 {
  55. */
    7 N( V( G2 A: ]5 }* P
  56. #define     SHELL_ENTER_CR              0
    - }( ^# O7 R" D& w  o

  57. 4 @- W% D% h; p0 ~5 E& U# P! m  O
  58. /**) y2 m! U  u& v% p9 T
  59. * @brief 使用CRLF作为命令行回车触发+ f5 y0 [! I2 I% i+ S: |
  60. *        不可以和SHELL_ENTER_LF或SHELL_ENTER_CR同时开启
    # a. y3 j3 S, \/ z
  61. */1 |2 N6 Q) F2 [
  62. #define     SHELL_ENTER_CRLF            1
    ) }$ b- H" m' ^0 q' D+ q* ^
  63. * N) h) n6 b* Q& Q* n, H# \3 q- M* C7 \: [
  64. /**' \. A: P3 N2 B8 i5 \
  65. * @brief 使用执行未导出函数的功能; L# e4 H1 e: n4 G. U4 s
  66. *        启用后,可以通过`exec [addr] [args]`直接执行对应地址的函数
    3 l- f" u  e# B; |7 `
  67. * @attention 如果地址错误,可能会直接引起程序崩溃
    # M) Q6 p! q" _% {% ^
  68. */5 c# s5 Z" R) x) v
  69. #define     SHELL_EXEC_UNDEF_FUNC       0
    7 Q; w* b$ t8 l" s% B! c4 b/ T
  70. 9 |0 V; W+ S8 Q
  71. /**
    & b$ n+ ~( G$ F; V, w. S# C
  72. * @brief shell命令参数最大数量
    4 f# t6 ~% q! T2 M& v: z' t
  73. *        包含命令名在内,超过8个参数并且使用了参数自动转换的情况下,需要修改源码
    # k( k) y$ W) G: o# O% Q
  74. */
    3 c3 o! W( |$ t0 n; p# |
  75. #define     SHELL_PARAMETER_MAX_NUMBER  8$ o0 ]4 p8 k3 I0 J8 G
  76. 9 @$ X9 K9 i3 ]: B% r
  77. /**1 }5 I: ~6 x3 @. v
  78. * @brief 历史命令记录数量+ a1 I8 T+ w7 W, w% B' N
  79. */
    + d9 T1 q8 a* ?4 ^. H* D
  80. #define     SHELL_HISTORY_MAX_NUMBER    5
    7 @1 T) z2 K, p. q) _& q

  81. % n- u7 _+ \! y  R
  82. /**
    9 b0 F/ u' j( k' p2 |' p# @
  83. * @brief 双击间隔(ms)
      ?; v- ?& r! F+ H1 S9 Y  t
  84. *        使能宏`SHELL_LONG_HELP`后此宏生效,定义双击tab补全help的时间间隔, F; E- B! e3 l/ w  h6 O
  85. */
    # r; {7 e8 d+ e' G) |, u
  86. #define     SHELL_DOUBLE_CLICK_TIME     200% b( }3 C" a! H6 }/ i1 T* ~6 B6 ~
  87.   C; D% Z2 G4 z0 x* z1 m
  88. /**) h. ^* Z9 X( e6 Z4 \5 R. M0 V
  89. * @brief 管理的最大shell数量0 C5 G0 R3 V- q* k! u: D
  90. */
    2 H5 r  k! N( K4 ?) p; O/ H
  91. #define     SHELL_MAX_NUMBER            59 {2 E6 O0 W4 A1 W; O0 [
  92. ! [+ u0 M3 c, M# x( Y; X
  93. /**0 R' b4 g. ]- D4 ?* s+ \
  94. * @brief shell格式化输出的缓冲大小9 L7 O5 Q  z& i& @  E/ h) p
  95. *        为0时不使用shell格式化输出9 ?4 `! m1 g# G. w# ~) S- w. F. ~) Y
  96. */
    ' n, z8 d* c2 R0 @8 z5 S- R
  97. #define     SHELL_PRINT_BUFFER          128, \8 a  M- J/ s3 k1 q

  98. # |8 Y# N0 p% C3 \- [1 k& J
  99. /**
    7 b/ s% [2 \- O( x
  100. * @brief 获取系统时间(ms)
    4 d  A0 @& D& W6 k) C9 H  z
  101. *        定义此宏为获取系统Tick,如`HAL_GetTick()`
    + k* F- {* s- L0 F& H0 X8 [. |+ M- I
  102. * @note 此宏不定义时无法使用双击tab补全命令help,无法使用shell超时锁定
    8 A7 Y. m( z- V* |' Y0 C
  103. */  ?# @/ h* V& ?2 v. K* C
  104. #define     SHELL_GET_TICK()            HAL_GetTick()9 F3 o; n( G; d# y" J: a

  105. & R$ l# o/ h7 C9 d" U
  106. /**" C; Y( ^+ J) H% d, b/ C0 M
  107. * @brief shell内存分配
    / w+ v; E3 X2 P$ N: \& L
  108. *        shell本身不需要此接口,若使用shell伴生对象,需要进行定义6 ~/ y9 g4 Y6 c
  109. */: f# Q) C3 y/ D4 S
  110. #define     SHELL_MALLOC(size)          0
    $ j' G: c8 Y% }; |# T1 _
  111. ; _9 l9 x& N' f
  112. /**) c/ f: Y) c1 _% v- D! w3 r. R
  113. * @brief shell内存释放
    " W4 p1 h. f; V1 Z1 e* a
  114. *        shell本身不需要此接口,若使用shell伴生对象,需要进行定义
    - Q) n% B( z$ i5 a. p$ M- ?  O5 t1 D
  115. */! O4 u  I. f# F4 c0 R
  116. #define     SHELL_FREE(obj)             0" s1 d+ m( q/ M: V
  117. ) i3 `7 V) b" `" n! c
  118. /**; G. ]4 P9 h, @+ h; F
  119. * @brief 是否显示shell信息! W) V/ _" |) q8 e  q; O/ f
  120. */' ?, H7 _' F$ L$ d, Z
  121. #define     SHELL_SHOW_INFO             1) A: V) n3 x% g. l8 P2 t

  122. . I5 H& x* J% E# j5 k
  123. /**
    ' f7 y  A" t+ b( |8 m3 Z
  124. * @brief 是否在登录后清除命令行
    % w* X* b5 \- l9 t7 ?
  125. */
    $ v2 O$ X% m3 e5 m
  126. #define     SHELL_CLS_WHEN_LOGIN        1
    1 x( @  S4 Y" j5 j( ^

  127. 6 Q, j) Z1 ^2 T! ^; l/ @0 L
  128. /**
    ( Y  N/ c% ]1 A1 l3 e/ U6 I- n
  129. * @brief shell默认用户& c; A* m1 e) n) X( ?
  130. */
    3 b: C; r" M! U5 z4 I- |
  131. #define     SHELL_DEFAULT_USER          "letter"
    . c4 m9 N5 Y0 w" X5 \8 `
  132. 1 p7 g8 G( }& ?8 D7 p
  133. /**; a+ }5 A. n2 U/ D7 R; B3 R# W3 k
  134. * @brief shell默认用户密码
    ' H1 d% @1 d3 A2 h
  135. *        若默认用户不需要密码,设为""7 B9 `  d8 J# V2 |3 z, X( k
  136. */
    % {7 B" C7 h  v7 z7 N* T, K
  137. #define     SHELL_DEFAULT_USER_PASSWORD ""% Y) z) r# H: Q6 w

  138. ' w( u% e: @0 w- S( Z1 |: u9 b% I+ y7 L
  139. /**9 j2 v5 \$ }3 T, K6 e. q; K! s
  140. * @brief shell自动锁定超时" `2 e* N' A) m' U2 K, M3 L5 g0 @
  141. *        shell当前用户密码有效的时候生效,超时后会自动重新锁定shell
    1 I& E' q- T% I4 L% ]
  142. *        设置为0时关闭自动锁定功能,时间单位为`SHELL_GET_TICK()`单位
    ; o5 A) E" O+ C0 m
  143. * @note 使用超时锁定必须保证`SHELL_GET_TICK()`有效) p0 W1 i6 Z, i( a! @5 v5 E
  144. */9 q# W3 n) o( ~( Z. ^; Q
  145. #define     SHELL_LOCK_TIMEOUT          0 * 60 * 1000
    $ u: K' E( K2 ?8 F, ]7 A
复制代码

& O8 [$ V% U, Q6 @' T% n) {) {) r下面介绍一下比较重要的几个宏:- I1 P8 j8 q4 `' E, h! C

# E- l% u, x9 H- ~6 F) D4.2 SHELL_TASK_WHILE
! C. s) _9 e& X7 N/ u$ z这个宏仅在这里面生效,这是在使用操作系统的时候使用到的,它会一直轮询地读取串口数据,然后调用 shellHandler 解析串口数据。这仅仅是个范例,因为这里没有进行任务切换,所以会一直堵塞在这里,而我们如果要使用FreeRTOS创建一个线程来处理这个任务,需要使用osDelay来进行切换。因为这里一次只读取一个字节,所以如果任务切换间隔过大,可能会丢失某些数据,另外,因为接收串口的时候要进行回显,所以任务间隔过大,也会影响操作体验,所以我们在串口接收中断调用 shellHandler 进行处理。我们不需要在线程中一直读取,所以这个宏 SHELL_TASK_WHILE 设为0,但是设为1也不影响就是了。
% K2 n  Z" p3 m! d' e
1 O  y2 ]$ S3 t* |$ f
  1. /**
    2 L' C! w; q8 o" h7 K
  2. * @brief shell 任务
    ) K. i3 U  a" {& Y
  3. *
    / b9 ?9 G3 \0 ^& c; Z! h$ v2 p
  4. * @param param 参数(shell对象)2 n0 c; e- m7 W# Q
  5. *
    % Z2 I2 A% A, V" u0 h
  6. */
    7 s) p" s# m# K7 F/ X$ G
  7. void shellTask(void *param)
    * Q' E6 j+ ]7 H3 e. D6 h
  8. {+ @6 C' ?3 E) h+ r
  9.     Shell *shell = (Shell *)param;2 C/ J( ~' }8 {' b
  10.     char data;: h4 l+ w  C# E. C
  11. #if SHELL_TASK_WHILE == 11 \; z: ?% p) B  ~! J' d' Q, U
  12.     while(1). h" e; o) b  R6 I, j# c: }
  13.     {% G* c% ?# m- M& V
  14. #endif
    ; c5 ?+ H' }/ @. C) h
  15.         if (shell->read && shell->read(&data) == 0)
    - T7 G3 ]3 o2 i+ N
  16.         {) b4 b7 Q) \" W9 k, @6 E+ b
  17.             shellHandler(shell, data);
    / E9 h. R6 B9 U- ^, {- v. z
  18.         }8 w! w: |5 J. p+ \
  19. #if SHELL_TASK_WHILE == 1
    " _7 p: w2 |# P4 p3 X( V! j, r' h
  20.     }
    : Y' V) B7 w4 |# N# |
  21. #endif6 H9 a$ w6 u6 X, h, f4 |0 A
  22. }
    - i- J2 r0 t. D
复制代码

# u- e" s% L7 a$ I4.3 SHELL_USING_CMD_EXPORT
1 g/ |$ t# G* h+ ^. Y命令导出功能,通过这个宏,我们就可以在命令行中输入指定函数的名称以及传递函数,就可以直接调用该函数,用于调试,十分方便,所以这个宏不变,默认为1.
5 D+ o( M8 G" _& @6 f8 |; j6 f) a+ u" D
4.4 SHELL_USING_COMPANION
& N- \; e+ z: _5 j6 E3 Q' r( c1 n作者定义的伴生对象,用于一些扩展的组件(文件系统支持,日志工具等),但是目前这个功能还不是很完善,所以这个宏默认设为0,不启用。9 v/ }/ A9 H: r; ?9 q* e" S4 S
7 ~% h/ E/ L# [0 I4 ]7 t' ~, e
4.5 SHELL_ENTER_CRLF

/ j& `, S+ u4 @8 _7 P
$ y: x, j& c/ G* |% v这个与 SHELL_ENTER_LF 以及 SHELL_ENTER_CR 互斥,作为命令行回车触发,使用的SecureCRT 以及 MobaXterm 是使用 CR 作为命令行回车触犯,所以这三个的配置为
/ I) N, @1 O) ^' B( E' Z: j4 M; j0 V! h
3 x, z% A2 H/ u6 Z
  1. #define     SHELL_ENTER_LF              00 F/ w  p8 \" \$ m* p) I& e5 c, }
  2. #define     SHELL_ENTER_CR              1
    : g3 T4 o% e7 E" N, M4 h& K/ g
  3. #define     SHELL_ENTER_CRLF            0
复制代码

0 A# m! t7 n( W9 d# ]4 z4.6 SHELL_PRINT_BUFFER! C# t& T. I0 G9 M7 S: ]
这个宏定义了格式化输出的大小,在 shellPrint 中使用到,使用方法和 printf 类似,只是注意不要超过缓存大小。+ j" A0 e; {8 @! \+ @
这个宏也是按照默认值就可以了,也可以根据自己的需求进行修改。
! E( g: K4 o$ C' Q% c/ S4 R/ z( c. V  T% l: }7 `4 A7 N3 q
4.7 其他的宏" J; }: S8 U" T8 l" k
其他的宏理解起来很容易,这里也就不进行细说了。( `% V) ~% n, E3 k) X  i
最终宏的定义如下:5 B2 [( a1 C8 l5 u

! ?; o1 P) [4 m/ K! _
  1. #define     SHELL_TASK_WHILE            0
    : [- ]) t' C5 ]
  2. #define     SHELL_USING_CMD_EXPORT      1
    & f  y9 c' Q+ f/ b$ ^4 ]
  3. #define     SHELL_USING_COMPANION       0, t4 f0 B. `. m- Y
  4. #define     SHELL_USING_COMPANION       0
    4 c" n2 S: e; a' R" i
  5. #define     SHELL_SUPPORT_END_LINE      0
    9 c; L% X0 `# i* l/ W
  6. #define     SHELL_HELP_LIST_USER        0
    , v7 O/ P, [6 T) I2 P' E! E
  7. #define     SHELL_HELP_LIST_VAR         08 l- C6 {( K6 |) h
  8. #define     SHELL_HELP_LIST_KEY         0) v" t) Z. m& l
  9. #define     SHELL_HELP_SHOW_PERMISSION  1/ e5 j9 N$ B0 ?+ s4 V
  10. #define     SHELL_ENTER_LF              0" X# C8 V2 w( L  G( c" D
  11. #define     SHELL_ENTER_CR              1; _- B. g$ K0 i* U
  12. #define     SHELL_ENTER_CRLF            0. n9 |$ D; k; k
  13. #define     SHELL_EXEC_UNDEF_FUNC       0
    - k: P2 Z1 Q0 Q5 [$ J& H) W
  14. #define     SHELL_PARAMETER_MAX_NUMBER  89 r# M) Q% W" G6 A$ M4 \
  15. #define     SHELL_HISTORY_MAX_NUMBER    57 m4 u* M" C5 h$ o) I% P, f! I; O3 C
  16. #define     SHELL_DOUBLE_CLICK_TIME     200
    , `2 K5 B. i0 c- H1 Y* ?; \
  17. #define     SHELL_MAX_NUMBER            5: g! f# m7 o# m( M8 y9 w
  18. #define     SHELL_PRINT_BUFFER          128$ g) b9 n* K8 u' I& ^5 v
  19. #define     SHELL_GET_TICK()            HAL_GetTick()
    , \+ V' x$ E3 Q$ I- |: s6 j
  20. #define     SHELL_MALLOC(size)          06 I) ^+ z/ M& J3 ]) L2 s3 e' S$ w9 Q
  21. #define     SHELL_FREE(obj)             09 e1 `- G$ E  ^  t5 p
  22. #define     SHELL_SHOW_INFO             1
    % J* f. f, }1 O
  23. #define     SHELL_CLS_WHEN_LOGIN        18 r% M6 n. A# g: U3 Z2 q, e% o0 e
  24. #define     SHELL_DEFAULT_USER          "letter"6 Y  M; T+ O3 ^8 f. j
  25. #define     SHELL_DEFAULT_USER_PASSWORD ""5 A' N, p& I* K  A# ]; P
  26. #define     SHELL_LOCK_TIMEOUT          0 * 60 * 1000
复制代码

* e: G( K+ v) a9 V4.8 shell的接收处理
  s  b: E* t! ~( H  m要正确的将串口接收到的数据当参数传递给 shellHandler,先要确保打开串口的中断,HAL_UART_Receive_IT ,使用这个串口中断,需要定义接收到的buffer数组,以及接收buffer的大小。
1 z+ [% k$ i$ A* m7 r$ _
5 Z9 r( U" g1 g+ X1 ~2 U! k
  1. #define HAL_USART3_RXBUFFERSIZE         1
    ' b6 _# U9 ?- o- K2 q
  2. uint8_t HAL_USART3_RxBuffer[HAL_USART3_RXBUFFERSIZE]; //HAL库使用的串口接收缓冲
复制代码
* ^2 ?/ v$ N7 f7 _" _: Z$ @; V' p) g
在main函数中打开串口中断
9 C8 e: Q! N/ [+ P% t
& C0 _3 j4 b$ ~# _, ?
  1. HAL_UART_Receive_IT(&huart3, (uint8_t *)HAL_USART3_RxBuffer, HAL_USART3_RXBUFFERSIZE);//使能串口中断:标志位UART_IT_RXNE,并且设置接收缓冲以及接收缓冲接收最大数据量
复制代码

6 i8 S8 q( L3 L2 K3 ^$ k打开串口中断后,在串口中断中就能获取到串口数据了,在HAL库中,串口中断最终会回调 HAL_UART_RxCpltCallback,所以我们就在这个接收串口数据,然后调用 shellHandler 进行解析。2 p9 _9 [  m/ n( K+ i0 X

6 ]- E3 w% E: l; e5 K
  1. void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)# D& o, t1 p- K1 t1 L
  2. {; M8 K2 v8 M# `. B+ v
  3.   uint8_t udata;
    ; j0 S8 {, e$ j) w9 c
  4.   if(huart->Instance == USART3)      //串口3的接收部分. M6 q3 ]! }2 ]! }# K- Z
  5.   {
    9 W+ }* w# ]. d  u: T, h0 R
  6.     HAL_UART_Receive_IT(&huart3,(uint8_t *)HAL_USART3_RxBuffer, HAL_USART3_RXBUFFERSIZE);     //接收串口的缓存
    : ?: l& L) E. x" w9 x
  7.     udata = HAL_USART3_RxBuffer[0];
    6 h5 y# b, W* o" R* V- a
  8.     shellHandler(&shell, udata);5 i( g8 C! ^' H0 L5 w1 [( b. R
  9.   }
    ( l3 a  Y& e8 A& @, |+ b2 E
  10. }
复制代码
6 J( w' N' U* e
编译后下载程序,复位,这时候输入cmd 命令,按下回车,就会列出可以支持的命令了。后续如果自定义了导出函数,也会在这里显示。到这里,就说明了串口的收发是正常的,shell 也移植成功了。. L2 j2 V, l$ C6 Q8 Y: S
, N$ w6 n( a( p& U: j* o! F: V. x$ [
2021012523522544.png ' b) u7 M, a- E# m
" C: j0 \& n% ?3 D4 q3 m' B
五、使用Letter Shell 进行调试程序3 ^. [( }! P5 A' c/ Z8 v
如同上面的 cmds 命令,我们用户也可以自定义命令,在编写完一个函数之后,我们可以通过SHELL_EXPORT_CMD 导出该函数,这样我们就能通过命令行来调用这个命令执行,以达到调试的目的。
+ @* a; \* h4 R- Y0 D3 yletter shell 3.0同时支持两种形式的函数定义方式,形如main函数定义的func(int argc, char *agrv[])以及形如普通C函数的定义func(int i, char *str, …),两种函数定义方式适用于不同的场景。
6 l1 L0 s+ R- U0 p/ m) M  ]. P& f) o- d3 g& L/ s
下面使用LED来举例。8 L' ~- h- `/ `5 ~

  Z! `- x4 N0 k: R' U5.1 普通c函数的命令导出' {! C4 _6 l3 \# Y. k3 V+ L
  1. void LD1_ON(void)
    4 L3 }& b$ l& p7 K5 w- a+ i0 n3 m$ {1 r
  2. {
    " t/ \% T/ [7 m; ^
  3.   HAL_GPIO_WritePin(LD1_GPIO_Port,LD1_Pin,GPIO_PIN_SET);0 i" o8 [0 k% S4 ?# ~1 T4 y0 ?3 ~
  4. }
    2 w/ j' b& i! d4 |! c3 R& o$ M

  5. $ s5 _9 G; j! J6 O$ M
  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);$ Z* e6 D& l1 [. F3 ~

  7. ! s) f! i7 k; l# Z
  8. void LD1_OFF(void)& K( X& d% M( i2 \8 [* r
  9. {  B, [2 u* a2 \) x  S
  10.   HAL_GPIO_WritePin(LD1_GPIO_Port,LD1_Pin,GPIO_PIN_RESET);
    8 H; e1 |% X7 c. s
  11. }
    ; p: H7 f6 l' {* A" X

  12. % ]4 X6 [/ F' I/ e0 |0 f; H
  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);( c4 Z3 w1 k4 e
复制代码

4 }  |( z3 K1 K; d5 P" `: k  O上面写了两个函数,顾名思义,LD1_ON是点亮LD1,LD1_OFF是熄灭LD1,SHELL_EXPORT_CMD 就是导出命令,各个参数分别是 命令属性,命令名,命令函数,命令描述。
# j4 f2 L) H' |% N4 M- B3 O" ]# W( }+ k7 k
  1. /**2 ~' t; k) C! m% S& e$ ^! Z
  2.      * @brief shell 命令定义$ `% _9 d" x0 m, i- s
  3.      * + D- ~- w  H7 u
  4.      * @param _attr 命令属性
    3 {# X! X$ d! L5 ?& Q* e
  5.      * @param _name 命令名: l$ a& s- G1 l
  6.      * @param _func 命令函数' E4 R3 @; H# ?  O9 T% R7 U7 A
  7.      * @param _desc 命令描述; A4 \2 R6 d1 s# u4 E
  8.      */1 d5 C, u3 _" {4 {9 M
  9.     #define SHELL_EXPORT_CMD(_attr, _name, _func, _desc) \
    4 Q- u3 z; b1 N- o
  10.             const char shellCmd##_name[] = #_name; \
    * p1 Y8 n7 e& w  ]) r
  11.             const char shellDesc##_name[] = #_desc; \% H& J6 _6 D+ q
  12.             const ShellCommand \0 K+ c: \8 s6 k& ^! Z
  13.             shellCommand##_name SECTION("shellCommand") =  \+ O  g& [0 S7 _3 o
  14.             { \8 o% G# D; S' x6 e. r/ G( V
  15.                 .attr.value = _attr, \
    2 z! k  j7 ]6 T6 ?8 r
  16.                 .data.cmd.name = shellCmd##_name, \$ g' C2 q5 e/ g" @5 j; c5 T! ]
  17.                 .data.cmd.function = (int (*)())_func, \
    % k; `  p- F5 n2 ]+ P, s8 }, ?: Z
  18.                 .data.cmd.desc = shellDesc##_name \
    - O/ ]) Z9 L; j: z0 P) U" _/ L
  19.             }
复制代码

; t# N2 x4 I! Q" W9 [) Q5.1.1 命令属性1 u* j0 p0 \8 r2 e' I, X( Q8 L% \8 Q
命令属性 这部分在 shell.h 中进行定义,几个属性可以通过 " | " 来进行连接。属性有以下几种:$ i9 C3 ~! G0 j- S& \% y

2 m" o9 m7 r! p- v
  1. /**
    4 l- Q6 F! U# v7 g+ \0 x: ~0 h
  2. * @brief shell 命令权限
    ! d6 Y- G; j( l+ V; K8 O9 A& Q
  3. * 5 I# r2 h4 f! g; v3 U6 z' |/ N1 D
  4. * @param permission 权限级别
    6 d5 e/ p3 A, x
  5. *// |1 ^9 a; }/ r! ]0 n
  6. #define     SHELL_CMD_PERMISSION(permission) \5 q* w5 y0 C1 W
  7.             (permission & 0x000000FF)
    ) ~% o( g. {# m; d) S4 {% Q/ [

  8. ; u6 @1 d( \7 F. f1 l- j
  9. /**7 ~; ^" e6 D$ D8 W, i
  10. * @brief shell 命令类型
    4 h3 E; F# S2 V! {. `1 Y
  11. *   ^6 u- A3 ~( G) T
  12. * @param type 类型% J6 z. T* r6 O# e1 r$ a
  13. */
    % p& e% r1 D5 c8 @, I. \' V7 b2 }
  14. #define     SHELL_CMD_TYPE(type) \( ]% v( J8 J- u5 z. B2 d; J
  15.             ((type & 0x0000000F) << 8)( m; Q8 k+ D8 f! @7 T4 B5 \

  16. . Y9 O: @% F- Z9 a% M8 _
  17. /**
    / u. I# P' W6 D' |4 }1 j( `6 ^( U! \7 H
  18. * @brief 使能命令在未校验密码的情况下使用1 \. `9 a/ c* ^/ l: ^1 A, f& H9 Y
  19. */
    % n# |2 e& a( d" ~& [
  20. #define     SHELL_CMD_ENABLE_UNCHECKED \$ a! q; j% _# x9 x% r
  21.             (1 << 12)4 S& L) L& W9 m
  22. 3 e) N) E2 Z! w: }4 y- z
  23. /**- q: U$ l! d5 c! i4 T3 M
  24. * @brief 禁用返回值打印9 E( @# u* M. }, q& G
  25. */3 R9 l9 m) R. q  ?5 z
  26. #define     SHELL_CMD_DISABLE_RETURN \
    6 k' A* K! p/ x; P- y% m( d
  27.             (1 << 13)3 ?3 {1 V& g/ L. |" F4 v- s9 h# ?
  28. # A1 q* A( y6 j- [% `& h
  29. /**
    ; D* d* L+ E; n$ i' n$ N
  30. * @brief 只读属性(仅对变量生效)
    ; i6 x1 e* }; X8 W  ~2 A
  31. */7 R: X3 T: W; E/ F
  32. #define     SHELL_CMD_READ_ONLY \
    6 Z8 h) D- `6 P: f" A8 W
  33.             (1 << 14)
    7 ]8 A9 O7 I3 u0 Q5 ?( R, l& |+ I
复制代码

0 K' f3 N5 O4 y7 |在上面的例子中,使用到的命令属性是
$ L  Z& r3 _& K4 B$ w  N  X& k1 ?* i  ^& p6 k
  1. SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_FUNC)|SHELL_CMD_DISABLE_RETURN
复制代码
/ J/ y6 Q6 j2 Q% }% }
这个意思是这个命令任何用户均可使用、命令的类型是普通的C函数、执行完函数后,不在命令行上显示返回的结果4 `5 y9 }8 s5 S1 l" W; v
1 D2 R: d% g- z
5.1.2 命令名
$ ?2 [9 J' w8 F# v命令名 在命令行中,输入命令名就能执行对应的C函数,可以通过命令行的 cmds 命令进行查看
: @0 |7 l9 }: Q( E, z6 p在上面的例子中,实现了两个命令名,分别是4 d% g! K0 z% w6 _4 \" }1 J

% _6 ?" B3 a5 I* ]: l: O4 ]
  1. led1_on- A1 B2 M! z, H' X
  2. led1_off
复制代码
/ l: w( j) o0 w: C& n. x- z9 X  u
5.1.3 命令函数; Y! A% @$ m# N7 h# |4 S
命令函数 也就是我们在keil工程中,所编写的函数的函数名。+ t6 X: T3 U7 ~& ~$ V) Y8 n" n
比如在命令行中,我们输入 led1_on ,那么实际调用的函数是 LD1_ON,输入 led1_off,那么实际调用的函数是 LD1_OFF5 Z( ^3 g  r1 v7 D* o8 R0 c
+ H% C) B/ Y7 e) I: H' S" ]
5.1.4 命令描述
' A$ ]* X* w9 K. d7 z/ N% p7 c" C命令描述 命令描述出现在 cmds 命令中,它主要是简明扼要的说明这个函数的功能。
, D- W) X# e. y8 U8 Q' l5 K5 |/ v+ x! f& W3 O! r" b0 z
5.1.5 实际效果$ C+ S. Z. X1 T" |
如图所示,在输入cmds命令之后,会打印出我们可执行的命令,这里是多出了两个命令,led1_on 以及 led1_off,我们输入对应的命令,就可以控制LED1的亮灭了。
" p# Y; C& C' r% w
# |; k, L3 H2 F0 m 20210126222149487.png
+ Z8 d5 Z! Z& {4 b: T/ }. k2 v0 F1 W( K4 L" V' l' L# N7 S7 R' s
5.2 main函数的命令导出
0 Z' R# L/ o$ F" x3 a一般我们写的程序,用普通的c函数的命令导出即可,main函数也可以,使用上也比较类似。使用此方式,一个函数定义的例子如下:
# \9 |3 H1 {/ r; Q3 R& n8 E' D7 A9 g" z
  1. int func(int argc, char *agrv[])( s3 C" u, _3 j% L! W+ b
  2. {. V% F7 d" {: ^+ |! i0 }
  3.     printf("%dparameter(s)\r\n", argc);$ e8 @/ u4 @5 d
  4.     for (char i = 1; i < argc; i++)
    ! z  b$ h! a% ^$ b( X8 ^
  5.     {: |  P) D( ~! J; f
  6.         printf("%s\r\n", argv);
    . a3 a. Q+ G6 @6 H2 `
  7.     }: a% C0 T4 T% X4 b) Q
  8. }8 K& u% o; d9 }( l. f5 ~0 `9 Z; D0 U
  9. SHELL_EXPORT_CMD(SHELL_CMD_PERMISSION(0)|SHELL_CMD_TYPE(SHELL_TYPE_CMD_MAIN), func, func, test);
复制代码
$ F3 N; {. c: G) l
终端调用
" f/ w$ F+ |/ Y2 `  h. K+ z" P9 F+ N/ t0 u6 q. s- g
  1. letter:/$ func "hello world"
    " n! t$ f' k0 {3 b
  2. 2 parameter(s)2 j. c, J5 G$ i( E* y
  3. hello world
复制代码

. W( p) ~5 G5 f' d# w9 R5.3 带参的命令导出/ }1 s% G" V* B  Y4 \6 B& z8 `6 M
我们编写一个带参的函数,命令行是以空格划分传递的参数。
& V3 A% r' y( P: _" i, n. y" Y+ e; d" O4 w& |8 Z6 X
  1. void LD1_ON_OFF_ms(uint16_t on_ms,uint16_t off_ms)3 Z- I5 Z1 T/ N% k8 r4 |
  2. {' j+ S6 z2 {# C+ M# P+ U
  3.   HAL_GPIO_WritePin(LD1_GPIO_Port,LD1_Pin,GPIO_PIN_SET);    //点亮LD1' C! H* N( F6 z5 j
  4.   HAL_Delay(on_ms);                                         //延时' X1 M2 b+ v4 x' E+ A: ^. L
  5.   HAL_GPIO_WritePin(LD1_GPIO_Port,LD1_Pin,GPIO_PIN_RESET);  //熄灭LD1/ ?1 Y; r" j% C- D) S0 J
  6.   HAL_Delay(off_ms);                                        //延时
    8 O: O, h% k! ]8 ]2 \! Y& V1 W
  7.   HAL_GPIO_WritePin(LD1_GPIO_Port,LD1_Pin,GPIO_PIN_SET);    //点亮LD1* ^6 h) n' m  V5 O, a
  8. }# s% e2 n: u3 z. u. c6 y. v
  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);
    . I% B, L4 B! w$ d1 w* |$ }1 s$ j
复制代码

6 y# l4 k5 e. Q+ \% x这个例子是控制LD1亮灭的时间,LD1首先点亮 on_ms 个ms,再熄灭 "off_ms"个ms,再重新点亮。! J5 c) T' Y; u
输入以下命令,就能看到 LD1 先亮 1000ms,然后熄灭 100ms,最后又点亮的状态。2 M" Y3 T0 g# P6 i( `+ H! y3 G

5 n% L* n" g1 D1 l" u& g% J3 R6 c
  1. letter:/$ led1_on_off_ms 1000 100
复制代码
这是输入一个数字的例子,也可以传递字符串,比如说我们要使用文件管理系统 FatFs 来管理文件,很多地方就要使用到字符串了,letter shell 支持伴生对象,里面可以扩展文件管理系统的功能。
" ~; H% V4 o8 V$ e, n7 \: Y1 M( \7 {; Q, G% g
fs_support作为letter shell的插件,用于实现letter shell对常见文件系统操作的支持,比如说cd,ls等命令,fs_support依赖于letter shell的伴生对象功能,并且使用到了内存分配和内存释放,所以请确认已经配置好了letter shell2 T( _& j' |5 x" y0 ]

9 l2 W0 x2 f6 n. ?6 R, m: F3 Yfs_support并非一个完全实现的letter shell插件,由于文件系统的接口和操作系统以及具体使用的文件系统相关,所以fs_support仅仅通过接入几个基本的接口以实现cd,ls命令,具体使用时,可能需要根据使用的文件系统接口修改fs_support,letter shell的demo/x86-gcc下有针对linux平台的移植,可以及进行参考% T% e: T+ |5 m, ^! V  Y( p7 z
! [- H) c- m- _, l) l% k  {
因为这个功能作者还未完善,所以这部分的命令需要我们自己来实现,下面是我在 FatFs中对 mv 命令的实现,功能是在将 old_path 的文件移动到 new_path,或者是将 old_path的文件移动到 new_path,并且重命名该文件。
& S/ X4 L3 l' h* }3 h! Z. b
8 y* r& L' h. ?4 {8 z  }2 L( B
  1. /**6 n2 |5 Q: K' x- s9 u
  2. * @brief 重命名/移动文件或子目录  U! j1 W% g+ G. [2 c4 S
  3. */  S; B  @; @: z/ {
  4. void shell_mv(char *old_path,char *new_path); K3 [8 v$ t1 a/ ~/ N$ |
  5. {
    + v% D4 c4 h8 M! t  S# |
  6.   FRESULT res;
    2 o& c* y# ~3 B; o$ A* t

  7. 6 `# o, A! L9 ?
  8.   shell_active = shellGetCurrent();   //Get the currently active shelll
    5 i- n* l" e9 H( G+ w5 y/ [1 B
  9.   res = f_rename(old_path, new_path);
    " J2 W' t# t8 |+ E/ ~
  10.   if(res != FR_OK)
    1 e6 M& n# ]! ^* O8 y# F3 k
  11.   {
    5 P# S4 f! |: Q8 b# m
  12.     shellWriteString(shell_active,"Operation failed! Error Code:");
    3 n9 c" s( g6 i, @; T
  13.     shellPrint(shell_active,"%d\r\n",res);
    , n" j' [5 b$ t: L! Q& a
  14.   }
    0 |- U: q$ Y; Q& D) Q/ W
  15. }
    9 \. S& f5 o2 V4 n( _5 h3 ]
  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);
复制代码
/ P8 S& u# c$ w+ H  W. s: ?
输入以下命令,就能将 dir1目录下的 file1.txt 移动到 dir2 目录下,并且这个文件名将修改为 file2.txt。" U' }* ]& x$ L- l- n5 P; u( U
' B2 U/ J+ n1 f) a3 x7 q5 G
  1. mv dir1/file1.txt dir2/file2.txt
复制代码

4 ?% c  C7 G, B: o$ K不过要注意一点,FatFs 的磁盘是以数字打头的,不像window,各个硬盘是以字符打头的,比如 "C:\file.txt"表示 C盘目录下有个 file.txt,在FatFs中,对应的目录是 “0:\file.txt” ,假如我们以 “0:\file.txt” 当成参数传递进去,那么接收是会存在问题的,这是因为第一个输入的是 0,在解析参数的时候,它会把这一整串字符串当成数字来解析。
4 r8 \% N- Y7 `& S我们先找到解析参数的函数,可以看到 if (*string == ‘-’ || (*string >= ‘0’ && *string <= ‘9’)) 那么就是要解析数字,我们在这个判断中,再次判断,从字符开始到后面,如果出现了任何一个不属于数字的字符,我们就判定这个是字符串。而如果在空格或者回车命令前,全数字的字符,那么我们就判断这个是数字,通过这个方式,就能规避 FatFs 的硬盘号会导致参数解析异常的问题了。  P  P; S0 m! R. b! h

2 X6 j, u/ z6 y( z8 z8 }+ ~) J. j
  1. /**
    / ~/ ?& |, F' g& W
  2. * @brief 解析参数
    1 p: v- U# z7 _& J2 s, F) M/ e# J
  3. * # H; n- f+ |5 ]5 {# l  X, ]
  4. * @param shell shell对象/ ^( O* }2 c8 ]0 D  l% U: G& I
  5. * @param string 参数
    4 }9 g6 B1 g8 Q2 T2 A1 j
  6. * @return unsigned int 解析结果" u4 G- J7 N: j9 w' J2 r* h) l
  7. */
    : ^" h# o1 s# ?
  8. unsigned int shellExtParsePara(Shell *shell, char *string)
    ) H) l" w  P2 \' p; D
  9. {4 S, `9 }  U+ `% z
  10.         uint16_t i = 1;
    # W' {% I% t* b6 u1 o, O
  11.     if (*string == '\'' && *(string + 1))
    1 t& L5 t6 K% u7 r
  12.     {
    & ]6 @/ }2 L% K; [/ d
  13.         return (unsigned int)shellExtParseChar(string);# p/ P; W  L( d- u) O# \. f. u7 @
  14.     }
    * f+ H* z0 N- V$ [+ K' V
  15.     else if (*string == '-' || (*string >= '0' && *string <= '9'))
    0 b3 p: }- n7 k2 C
  16.     {0 E8 _7 B" S6 m6 S% }
  17.                 while(1)
    + r3 ?. E6 i8 A' _0 w6 q( t
  18.                 {0 M- ^  z5 q( t2 w6 @2 H
  19.                         //寻找下一个表示该参数已经结束的字符2 E2 F* j0 S8 Q' O0 i) H6 L5 u
  20.                         if((*(string + i)  == ' ')                 //下一个为空格表示该参数已经结束' F. o: [1 C$ e
  21.                                 #if SHELL_ENTER_LF == 1                        // LF 表示命令结束
    , _: a7 E) l2 o' Y# c6 B% |
  22.                                 || (*(string + i)  == 0x0A))        + g& ^$ I# h6 C5 p2 _
  23.                                 #endif. ^! y6 G% p0 T" e0 a) X  R0 L
  24.                                 #if SHELL_ENTER_CR == 1                        // CR 表示命令结束6 Q; ^: `$ x2 K# z  Q7 @
  25.                                 || (*(string + i)  == 0x0D))
    8 e( T  p) r. [8 d# {: m" g
  26.                                 #endif6 ?$ n% o0 Z6 L- U0 i; f
  27.                                 #if SHELL_ENTER_CRLF == 1                // CR LF 表示命令结束
    ) }6 d  L, D9 w/ i2 G
  28.                                 || ((*(string + i)  == 0x0A) && (*(string + i + 1)  == 0x0D))
    5 y3 X+ @: A5 J4 v* p; E
  29.                                 #endif; R5 |) ]  K& Z3 Q" K0 f& K
  30.                         break;
    ; A0 {" j8 {! O

  31. 1 [6 b5 E; A3 x2 ^, ]. [! m* N# @
  32.                         //出现不为小数点或者非数字的字符,表明该参数为字符串
    " k% }6 @& q6 T/ g- Q
  33.                         if((*(string + i) != '.') || (*(string + i) < '0') || (*(string + i) > '9'))
    0 z- Y: C+ P' Y' |7 b! ~8 r
  34.                                 return (unsigned int)shellExtParseString(string);
    ; u: p7 A& B" E

  35. - [/ B0 d( B- P. W0 x; I
  36.                         i++;) F2 J# V9 p  s8 Y- ^
  37.                 }
    + Z! t2 N, v6 f, R
  38.         return (unsigned int)shellExtParseNumber(string);
    # a. g8 `! ?) M3 Y
  39.     }
    ! t( h2 ~9 G6 B# F: Y. z
  40.     else if (*string == '
    . h$ k* i) I+ Q: p3 A) p' l
  41. [b][size=3]六、其他常用的 Letter Shell 函数[/size][/b]
    7 [3 n! e& @  s  _) P$ [
  42. [b]6.1 shellWriteString[/b]
    / H! m4 Z; V1 Q! B
  43. 函数原型如下,这个功能就是 printf 。这个应该很好理解,就不细说了。: ]2 \/ M2 j- k' G8 Q1 G% s5 B

  44. , ]6 y$ v/ l* T; W: Y) m. f
  45. [code]/**
    ! B* d5 J& B& z
  46. * @brief shell 写字符串  {- I: m+ ]# ~- X- |( \5 H
  47. *
    ) R& v. j# Y' ?* y/ o. b
  48. * @param shell shell对象3 M! S# V. K8 O' I
  49. * @param string 字符串数据
    ( @2 Z* d- Z8 N3 Z6 Z
  50. * $ S/ }, g/ ~, ~8 V9 k
  51. * @return unsigned short 写入字符的数量5 p7 U$ k4 L1 Y& R5 I
  52. */
    , e2 Q! z, @0 ^  h2 n: b4 T0 a
  53. unsigned short shellWriteString(Shell *shell, const char *string)
复制代码

3 B+ ~- I0 \7 W2 O! }+ S) G3 ~, X6.2 shellGetCurrent5 J2 y$ \" P! F) C4 y& z
letter shell采取一个静态数组对定义的多个shell进行管理,shell数量可以修改宏SHELL_MAX_NUMBER定义(为了不使用动态内存分配,此处通过数据进行管理),从而,在shell执行的函数中,可以调用shellGetCurrent()获得当前活动的shell对象,从而可以实现某一个函数在不同的shell对象中发生不同的行为,也可以通过这种方式获得shell对象后,调用shellWriteString(shell, string)进行shell的输出。! v% }# A/ ~2 G0 X( \7 b

8 `2 C( @/ `0 x" K0 {0 c! z5 t
  1. /**5 i* P! N' [0 i# W
  2. * @brief 获取当前活动shell# ^& d, A3 }2 r, N! t
  3. *
    " S  C: b' D. W% I
  4. * @return Shell* 当前活动shell对象
    : `. Y7 T1 u; ?. x/ A
  5. */% ?2 m9 i2 f: ^5 B
  6. Shell* shellGetCurrent(void)6 a/ N7 Q- T8 H7 U; ~
复制代码
" S+ M* ~$ m5 A& b) `( x+ Z& j
可以定义一个全局变量. M* {/ m+ m( q: j/ Y# U

. _1 T+ i( o# S& R9 k) t
  1. Shell *shell_active;      //当前活动的shell
复制代码

% a2 E0 D1 \( d5 e7 j如果我们使用多个串口创建shell 对象,如果通过不同的串口访问文件,那么就不容易知道应该在哪个界面进行输出,正如这个 mv 的命令,首先获取当前活动的shell,再通过 shellWriteString(shell_active,“Operation failed! Error Code:”); 进行输出,这样就可以避免冲突了。
0 K9 B9 |  j% k: ?
" _* ]* ~3 `" E- N
  1. /**5 A- r. V1 e4 a, `  @
  2. * @brief 重命名/移动文件或子目录
    : @$ b% O& V8 R: f# I3 A6 N' r
  3. */- B* p1 s5 _% i8 E* G* u' S/ J- ]
  4. void shell_mv(char *old_path,char *new_path)
    " X1 u+ Z( h  F3 ^& n
  5. {& o( k# Z- y' l% ]  h
  6.   FRESULT res;
    5 ]) l2 ?9 u0 e/ [7 z6 V  k9 K( y
  7. 3 N7 m, c3 D. Q& m0 Z* P2 p
  8.   shell_active = shellGetCurrent();   //Get the currently active shelll
    $ |+ W4 [% A" R4 T' t9 @( Y3 @
  9.   res = f_rename(old_path, new_path);
    * ~- q/ \3 M  h1 y
  10.   if(res != FR_OK)5 r0 i- n: y* e3 l- K
  11.   {6 {0 C/ r$ v3 Q9 F; M2 s0 A$ P
  12.     shellWriteString(shell_active,"Operation failed! Error Code:");
    : A; S& M: o* a1 ?
  13.     shellPrint(shell_active,"%d\r\n",res);- X5 ]+ U9 u' @7 r5 H. W, M: q; ]- R& P
  14.   }3 v& x: U% d+ E9 R7 G0 ]
  15. }2 w/ n. R) O; Z+ y- j; G& S9 T' V
  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);
复制代码
; W/ R9 }( s( q
6.3 shellPrint
$ i. \: e2 ~  c这个功能就是 vsnprintf 的简单封装了,用于向字符串中打印数据、数据格式用户自定义,但是要注意,打印的大小不要超过 SHELL_PRINT_BUFFER。4 a/ u) Z" G5 R" B
$ F* W) p1 B4 ^3 L( G
  1. #if SHELL_PRINT_BUFFER > 07 F+ l8 y, y  a
  2. /**
    4 s3 m1 [+ V& ?- y8 B
  3. * @brief shell格式化输出& n; S' E8 v; q: M
  4. * 0 y' c4 `. _  c, N
  5. * @param shell shell对象3 b: X8 k/ ]/ B4 |0 R) d( P& L  J
  6. * @param fmt 格式化字符串2 B! G4 U2 _. e9 h' r: }
  7. * @param ... 参数% d+ P# y, {1 c) d9 R% h) C2 q
  8. */
    * b9 ]8 {& ?# ]  c# W  O' f
  9. void shellPrint(Shell *shell, char *fmt, ...)" A) c2 q  ~/ ~; h7 L
  10. {
    0 b/ ]8 U1 Z$ L9 O5 o$ e. O% @
  11.     char buffer[SHELL_PRINT_BUFFER];
    2 |" p4 P% L' f2 f5 [3 t/ t
  12.     va_list vargs;
    , ?; f4 J6 L4 ^6 q$ s/ h$ I
  13. - `, U! E' j  ~! [( D% u
  14.     SHELL_ASSERT(shell, return);
    0 ?# t: p6 F, u5 J! {7 Q
  15. " S" F! M( U: o4 x# ~
  16.     va_start(vargs, fmt);+ `" H1 |* h& e& E6 r4 L8 j
  17.     vsnprintf(buffer, SHELL_PRINT_BUFFER - 1, fmt, vargs);
    5 G" L  ^' w" M: P$ w: V$ g3 ?9 O
  18.     va_end(vargs);. I# K8 M# S1 _1 w* Y, M
  19. 7 m! l3 A4 M' I1 H, b* E
  20.     shellWriteString(shell, buffer);3 [  l* h( j. _
  21. }
    1 q2 N* G' b, }. ^2 l% k$ c1 c
  22. #endif
复制代码

8 S7 E( S( e, q+ f- a- `- C8 |到这里,介绍就完了,而这也足够应付绝大部分的调试内容了。
5 i0 Q# f2 B- y/ O1 T/ |3 r7 Z$ W! A7 y
/ n" w6 K& z$ L' v1 ]* Y0 D3 `
5 u! C0 n" d  g, u4 N9 w4 {! c. O
&& *(string + 1))- ~, J7 i- _& ^) ?4 Z
    {# E+ p$ \5 l2 @3 I9 \. u
        return shellExtParseVar(shell, string);& a' V/ J3 w, X& P8 M
    }
/ `) z# }( @) y- s+ a7 z    else if (*string)- j6 f1 _8 I) Z& v/ W, ~
    {) a' l% C7 y4 {1 ]' d% o$ H+ d" Z
        return (unsigned int)shellExtParseString(string);
3 w, j" U7 J1 F- f4 ]5 R    }% T4 Z6 y0 k1 V3 I2 b8 x. \1 v) _& j  f
    return 0;
6 k# S! H/ M, j6 _}[/code]
7 `4 L" X! \( W8 \六、其他常用的 Letter Shell 函数$ e6 c; ~/ O2 y9 D; K  q" S: Q
6.1 shellWriteString
/ r* _& A$ g. {! x! Z6 Q5 G函数原型如下,这个功能就是 printf 。这个应该很好理解,就不细说了。/ O, ~: r' f# P1 m8 e
3 Q* c8 q7 |0 ]
  1. /**' L/ z1 |2 t% g  P% U9 N
  2. * @brief shell 写字符串' \1 j' a5 f# E+ H; i5 l
  3. *
    7 i& t7 Z3 y; Z  [8 h
  4. * @param shell shell对象7 K5 Y; q  _- ~9 T' o
  5. * @param string 字符串数据7 d- S# V; m7 V! R4 R
  6. * ' A9 `, r1 O: a/ `8 u6 A  V4 t9 ^  A" C3 O
  7. * @return unsigned short 写入字符的数量
    4 x5 K+ Q+ a& l. J# Y* M* w
  8. */; f6 [+ _( M( q) `! g! R% _
  9. unsigned short shellWriteString(Shell *shell, const char *string)
复制代码

' E; g4 |' k( w# S) @( W6.2 shellGetCurrent# B( q. |; i$ ~) ^
letter shell采取一个静态数组对定义的多个shell进行管理,shell数量可以修改宏SHELL_MAX_NUMBER定义(为了不使用动态内存分配,此处通过数据进行管理),从而,在shell执行的函数中,可以调用shellGetCurrent()获得当前活动的shell对象,从而可以实现某一个函数在不同的shell对象中发生不同的行为,也可以通过这种方式获得shell对象后,调用shellWriteString(shell, string)进行shell的输出。; u* E* {. `3 E! P& r
2 g/ ]& j) [. S/ F% b; b* ^
  1. /**6 Z$ ^: E0 G- g; q
  2. * @brief 获取当前活动shell  c$ m, y; r4 Q2 a* G( M
  3. *
      U6 ^& }" C0 V8 N" Q
  4. * @return Shell* 当前活动shell对象
    ' i& C- _, t% H# `/ z
  5. */% t+ k+ A+ s* i( Z! G. W
  6. Shell* shellGetCurrent(void)
    ) R2 Y* N/ p  J" f' t" m
复制代码

  ?- _" m7 \( C3 n4 P可以定义一个全局变量
" C, m3 a, C, D' T, K9 t; D8 Y0 t& p3 q# q& A. f9 m" R9 h: n2 V
  1. Shell *shell_active;&nbsp; &nbsp;&nbsp; &nbsp;//当前活动的shell
复制代码
/ G2 q' ?: o. K; s4 A' x2 K1 L7 A
如果我们使用多个串口创建shell 对象,如果通过不同的串口访问文件,那么就不容易知道应该在哪个界面进行输出,正如这个 mv 的命令,首先获取当前活动的shell,再通过 shellWriteString(shell_active,“Operation failed! Error Code:”); 进行输出,这样就可以避免冲突了。
7 n# c) J/ ?) ^0 L
8 F, a1 j! M1 O$ m! f
  1. /**
    ! }; Q7 ]6 U- w8 Y
  2. * @brief 重命名/移动文件或子目录
    6 Z3 O( e7 @1 |
  3. */0 Q& G" O3 A! s1 a( z9 K( }
  4. void shell_mv(char *old_path,char *new_path)# {3 L1 g5 m$ {" a$ C* \4 [1 G/ ~
  5. {% a7 k) w; e  E
  6. &nbsp;&nbsp;FRESULT res;
    % ?1 k5 ?% e. D4 W* F/ h) ]% w

  7. ) O' m, Q1 _8 d) Q( w
  8. &nbsp;&nbsp;shell_active = shellGetCurrent();&nbsp; &nbsp;//Get the currently active shelll
    ; i" J1 b' L9 y, p5 G
  9. &nbsp;&nbsp;res = f_rename(old_path, new_path);7 |0 a. I9 Y. @
  10. &nbsp;&nbsp;if(res != FR_OK)
    . @1 b1 P0 W6 Y/ j* ?3 A9 b
  11. &nbsp;&nbsp;{8 Q' ~% ?; T0 ^: B; Z
  12. &nbsp; &nbsp; shellWriteString(shell_active,"Operation failed! Error Code:");
    4 P/ ?4 M1 w; M( K9 ]) V
  13. &nbsp; &nbsp; shellPrint(shell_active,"%d\r\n",res);
    , q7 G! k$ {$ O7 p/ _8 n9 }3 C
  14. &nbsp;&nbsp;}
    ( ~2 D  w5 T& m' f, [1 U; `: U
  15. }
    ; r1 o; O4 a3 v3 j' v3 T6 \3 I
  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);
复制代码

& m# ~  d7 B' N; I6.3 shellPrint6 n; Y6 z& W; i1 V
这个功能就是 vsnprintf 的简单封装了,用于向字符串中打印数据、数据格式用户自定义,但是要注意,打印的大小不要超过 SHELL_PRINT_BUFFER。/ O$ I" x+ ]" d
- d5 [+ U( i2 H
  1. #if SHELL_PRINT_BUFFER &gt; 05 _# D0 k* ^: |4 P/ q
  2. /**
    % Z8 M* }6 Q1 O! W/ H
  3. * @brief shell格式化输出
    % ~$ G8 J. K* x+ i3 W; {7 @
  4. * 0 [' x3 r! {  ^5 o8 S
  5. * @param shell shell对象
    4 V8 I4 q' ?9 W
  6. * @param fmt 格式化字符串: ]' P- W& ?) [+ [: G% W6 Z
  7. * @param ... 参数2 I: t& p4 O8 H5 I2 v
  8. */
    9 m/ o% _( G8 I4 J
  9. void shellPrint(Shell *shell, char *fmt, ...)
    # j( v2 r! k9 C4 B6 C
  10. {; Q! o1 _* F7 A! f1 \8 L
  11. &nbsp; &nbsp; char buffer[SHELL_PRINT_BUFFER];' H1 b1 `. O: v% g( m; T0 C
  12. &nbsp; &nbsp; va_list vargs;
    0 g# g% a6 n4 z, ~  M% j

  13. # a" n& Y3 Y# I9 g" v0 j& D% p
  14. &nbsp; &nbsp; SHELL_ASSERT(shell, return);, k9 m! U& ~2 j
  15. , _. S8 u2 j  f# f( q- {
  16. &nbsp; &nbsp; va_start(vargs, fmt);, M$ b  Z9 f! h; b0 q
  17. &nbsp; &nbsp; vsnprintf(buffer, SHELL_PRINT_BUFFER - 1, fmt, vargs);
    8 S" C3 D2 @* S# d8 }' I9 L
  18. &nbsp; &nbsp; va_end(vargs);4 h5 S0 m% l& C& R, K/ L% J# U, ^

  19. 4 C/ ?. p) q3 f5 W6 Q
  20. &nbsp; &nbsp; shellWriteString(shell, buffer);, I$ ^1 Q) Z& {5 ^/ A) D
  21. }
      \: S' H( X7 g4 c# z
  22. #endif
复制代码

. `! W& D( d# F3 W到这里,介绍就完了,而这也足够应付绝大部分的调试内容了。+ _0 y) s! x& z- A/ B' n

0 S$ K+ q6 V, ^7 ]5 h: m9 B  W  f6 k————————————————
0 G6 w: ~. J; I/ {0 |6 G转载:fafuwxm6 ~3 S; a) S  s/ Z! L0 i3 G
* D4 v7 Q* B, Z) \: s
R`)~GN97DP3}]5R[IQUN}XE.png
赞 收藏 评论0 发布时间:2022-8-23 16:13

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版