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

STM32串口通信中使用printf发送数据配置方法

[复制链接]
Savy1314 发布时间:2017-7-26 13:24
STM32串口通信中使用printf发送数据配置方法(开发环境 Keil RVMDK)
( ^: C" l8 O# [! i* t! N4 x

+ o4 b0 s3 i  K2 R3 N9 L6 D* _在STM32串口通信程序中使用printf发送数据,非常的方便。可在刚开始使用的时候总是遇到问题,常见的是硬件访真时无法进入main主函数,其实只要简单的配置一下就可以了。
+ a. h" i  W, w& L' c, H
& P& r5 i3 J! E3 f  B; c$ F# R: e) f下面就说一下使用printf需要做哪些配置。
/ k  }7 a7 \: M6 o+ G/ J& V / b3 Q& l8 d; Y# F1 h
有两种配置方法:& \3 @2 M+ R) x/ P+ m6 U

: b, D4 y+ x8 v' B一、对工程属性进行配置,详细步骤如下& m* X) i& P4 j, H6 ^
& o2 i8 p/ Q/ y  R2 x
1、首先要在你的main 文件中 包含“stdio.h” (标准输入输出头文件)。& Z' D, J0 n% L+ h* P

3 c- S' u$ _, y1 h2、在main文件中重定义函数   
: F, N" L. g. `$ A, B+ j& G如下:
! F. W2 w3 g7 U/ A5 S2 E
  1. // 发送数据
    , a, B4 x) Q/ c
  2.    int fputc(int ch, FILE *f)/ A$ O  i3 m& [8 T( N- U7 C
  3.    {' l" f7 D- b- X3 p3 n+ f
  4.       USART_SendData(USART1, (unsigned char) ch);// USART1 可以换成 USART2 等
    2 _- T/ p! h  Q7 C9 [
  5.       while (!(USART1->SR & USART_FLAG_TXE));
    & Y/ M) N+ Q; i2 b8 @# l
  6.       return (ch);/ h( C& ?3 E/ U; m
  7.    }
    , G6 x% D. f: S+ I) a1 b( U$ X
  8.    // 接收数据
    ( G2 x3 p: L: ]
  9.    int GetKey (void)  8 G( }; y- E# k
  10. 1 |  x- U$ r( u$ _9 z  q. \7 e9 s
  11.   {
    1 N& e6 c( X2 \3 p) _7 r5 O; O, Y! ~
  12.       while (!(USART1->SR & USART_FLAG_RXNE));; _3 u1 z/ ~& `. n7 a, I
  13.       return ((int)(USART1->DR & 0x1FF));
    9 a6 H" S5 o4 l% H
  14.    }
复制代码
这样在使用printf时就会调用自定义的fputc函数,来发送字符。- Z1 l' }, P& E$ f2 I$ I% r

* w& A1 b# b9 v0 s3、在工程属性的 “Target" -> "Code Generation" 选项中勾选 "Use MicroLIB"/ Z/ T5 v, {: M
   MicroLIB 是缺省C的备份库,关于它可以到网上查找详细资料。# n, C$ E1 ^- @$ g

4 b' g0 B, x8 I  K  m; O  O二、第二种方法是在工程中添加“Regtarge.c”文件4 ?- x$ O5 o5 @+ M  }
1、在main文件中包含 “stdio.h” 文件; ]% d  u$ z, Z0 O% ?
' _# n4 ?0 z& v. R" {% W
2、在工程中创建一个文件保存为 Regtarge.c , 然后将其添加工程中在文件中输入如下内容(直接复制即可)% o6 {! e6 Q7 L6 i  ]) X
  1. #include - h, @! w+ q) t7 h0 R7 ~
  2. #include 4 J6 L- `. e9 V# j
  3. #pragma import(__use_no_semihosting_swi)
    , T$ Q! z+ m6 g% u2 o/ U3 n9 u
  4. extern int  SendChar(int ch); // 声明外部函数,在main文件中定义5 r  D) [4 H; B
  5. extern int  GetKey(void);8 V2 e1 l% E: U  u( q
  6. struct __FILE {
    + r) {+ w8 u# E; ~$ d( k: i1 d4 \
  7.   int handle;                 // Add whatever you need here 1 M. |$ r, C+ P: L
  8. };
    , m' ]3 }7 c0 j$ g
  9. FILE __stdout;$ M% V6 D& s1 Q: f- H; ]- E
  10. FILE __stdin;
    - ]8 L. g6 x! R& l+ g+ P6 y
  11. int fputc(int ch, FILE *f) {! I+ \* _' u) z, i, A( ]4 s& l$ A' q
  12.   return (SendChar(ch));
    5 K2 m9 E+ _% _8 X: X, }7 a
  13. }
    / y9 T: K% q! m
  14. int fgetc(FILE *f) {
    3 B1 t7 v' W4 W; ^# S6 f  Q
  15.   return (SendChar(GetKey()));  S" C& Y) ^4 X6 b' [5 B
  16. }9 \: {4 I, [" n8 S0 \4 t9 ^9 r1 u
  17. void _ttywrch(int ch) {
    ) L* Z7 B6 {* Z( ?3 r
  18. SendChar (ch);( ]+ k1 r, l7 ^$ O
  19. }" L! Y) E" J6 I
  20. int ferror(FILE *f) {                            // Your implementation of ferror
    / f9 v; u1 A" j  O$ S0 d. a1 M
  21.   return EOF;
    / P8 W8 t- `9 S) s2 K7 S% @
  22. }- `! F2 S) B. T- I' s# G
  23. void _sys_exit(int return_code) {/ x2 Q+ e+ K& ^1 N, F
  24. label:  goto label;           // endless loop
    * y$ G2 G# ?+ N- A! `4 c
  25. }
复制代码
3、在main文件中添加定义以下两个函数8 ?! v3 A# z" S3 G9 q. p8 \
  1. int SendChar (int ch)  {
    # I- p' R+ C& P6 @' C* U
  2.   while (!(USART1->SR & USART_FLAG_TXE)); // USART1 可换成你程序中通信的串口
    $ Y. Y, l) N/ T3 g% k7 g9 h
  3.   USART1->DR = (ch & 0x1FF);0 Y& W8 N9 h- Z3 q! p
  4.   return (ch);
    5 n' X6 r- v% o' L0 p  J
  5. }
    * o) T0 G! U5 _5 Z+ N+ f; ?; }
  6. int GetKey (void)  {! x5 B5 n) K1 J; B& u6 L  W  |
  7.   while (!(USART1->SR & USART_FLAG_RXNE));
    ) n2 }. F1 H. x& G  ^4 y7 X* C- b4 A
  8.   return ((int)(USART1->DR & 0x1FF));
    2 B9 a9 J* Y$ P: O& R
  9. }
复制代码
至此完成配置,可以在main文件中随意使用 printf 。
6 ^; j+ D9 X8 \+ A& Z+ `- J& W/ A9 u- {$ P9 G* u: N

& |. u( ^% q# h5 W# a! S) MSTM32程序添加printf函数后无法运行的解决方法(串口实验) 4 p! j- u5 Z4 B. @0 g
( U  m, L; W2 x% T& V7 }* U& ^
标准库函数的默认输出设备是显示器,要实现在串口或LCD输出,必须重定义标准库函数里调用的与输出设备相关的函数.
  R: O# p4 p" Q2 U

7 _$ O2 u" H- V1 {% o- J2 A/ |例如:printf输出到串口,需要将fputc里面的输出指向串口(重定向),方法如下:$ N! N) x* _6 i6 G8 a/ U+ t

7 o( T2 Y+ i' r' [" q- d只要自己添加一个int fputc(int ch, FILE *f)函数,能够输出字符就可以了
( r% s3 R2 B. ?8 d8 n) b' F3 P
  1. #ifdef __GNUC__$ f- L9 g; j  M" a

  2. : h' ^& z& o9 A. f* o
  3. #define PUTCHAR_PROTOTYPE int __io_putchar(int ch)3 \8 E2 v7 z% l2 M/ v+ B" @
  4. #else, S) ?, P9 o# E
  5. #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)6 k, B' X! o" q$ K, d
  6. #endif : u/ l. ~  W0 e% ?! S* S. W# Z' s
  7. + i# t6 r8 ~; O3 s" k; w
  8. PUTCHAR_PROTOTYPE9 q( z" U4 o# L0 v9 |+ a% G9 {: P
  9. {2 B3 v! ~+ [5 C. W3 P4 F
  10.   
    2 S$ `2 W! U" g; h$ L3 Z
  11.   
    # d& F. T. {9 u1 p6 T
  12.   USART_SendData(USART1, (uint8_t) ch);! _2 e. H1 @* w( o
  13.   
    9 H2 K/ R7 [4 \
  14.   while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
    * @1 H' M; m) ?1 t, c) X
  15.   return ch;
    / i9 F2 M# r! M# z, t
  16. }
复制代码
因printf()之类的函数,使用了半主机模式。使用标准库会导致程序无法运行,以下是解决方法:
$ I" K, s# f" {) R# G7 c
  @& w' O! ?4 d! q5 H

$ H" |* b6 @9 [+ ]% Z, C) R方法1.使用微库,因为使用微库的话,不会使用半主机模式.
! M0 ?/ Z! _, n/ _. ^' R/ a" z
( h2 H0 M2 M; O" |" B0 O
4 V" A9 e7 b! M$ |方法2.仍然使用标准库,在主程序添加下面代码:
' E) {( y9 @" Z+ D$ x( j; K5 i# C8 f
  1. #pragma import(__use_no_semihosting) * E3 P. H$ E6 p2 V1 e7 L( M
  2. . O. x, O( v# A- ?3 C1 @
  3. _sys_exit(int x) 6 B4 l! V9 K' {  n+ d
  4. {
    - p# w" f3 J9 t9 o7 |& d) t
  5.   x = x; 7 ]  W4 f. v' Z1 ~* e- M1 d. Y9 e
  6. }
    $ R1 V5 U, o( d. o: x- A' T
  7. 9 |" H  Z4 L4 ]' w: j8 @; @: U
  8. struct __FILE ! ]$ v6 a* C* o% m, c
  9. { 8 P! N- ?7 \0 d% T' S
  10.   int handle; 4 x6 y% V: Y- [; Z: Y
  11. };
    . S$ D) l. E: x% ]! i  b8 n* r

  12. 5 k- z0 Z6 t& n  L+ j2 A
  13. FILE __stdout;
复制代码
IAR EWARM2 L" U% @7 n& m/ m$ h+ T
+ K, x" |8 i( |3 \. `, C
General Options -- Library Configuration -- Library : Full < file descriptor support >
: ]' ~* H4 G/ S5 `
9 P9 ]% O, w; u+ x: \0 ^$ T
  1. #include 9 G* Q) K2 w8 u7 m  p9 w0 J

  2. . a8 ?) T5 i* r; ^9 C3 P( l) j% T
  3. #ifdef __GNUC__/ P) U3 d! ]  w1 C5 |& o2 U9 k* ~
  4. 4 C' V; p; q, L- t$ q
  5. #define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
    , G  @5 ?7 J3 z! W
  6. #else
    - c% X; t% e# M  b8 w" Q
  7. #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)  p2 v# Z2 \# `) h5 y
  8. #endif 7 d5 G0 b! r; T( @- m! |
  9. PUTCHAR_PROTOTYPE
    ( U7 s9 N8 }# l: o* t# ]/ _
  10. {4 N" c& e- t7 _4 x! q
  11.   
    . U: d0 _; {! u, a' v
  12.   
    # a7 H( U+ }8 }4 D& P
  13.   USART_SendData(USART1, (uint8_t) ch);6 J0 R$ G( i: h& T# p' t) M+ w5 ]% Q
  14.   ) w7 y  n6 t7 N( Z- H) @9 n7 f( V
  15.   while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
    ( Q& _7 c9 a0 S' X
  16.   return ch;
    9 ]4 a! P3 H0 f
  17. }
复制代码

- }# B4 L0 Q# TGeneral Options -- Library Configuration -- Library : Normal < NO file descriptor support >
4 W; Q9 G! m1 u1 L! E& `
  1. #include , Z7 H- ^* d& h
  2. 5 R, I% Z- X! q: j2 ~
  3. size_t __write(int handle, const unsigned char * buffer, size_t size)
      G8 k  Y& `" @% A7 l9 f
  4. {
    / n+ y9 ?4 }! m# H
  5.   // byte by byte write
    % u1 W- N! R$ R# {
  6. }
    & Y7 D# q3 I" J. c- Y' W

  7. ' w; i9 u- H  \1 Z9 I9 i* S! M
  8. size_t __dwrite(int handle, const unsigned char * buffer, size_t size)& v' p) n4 R+ X& q3 @% W( ]
  9. {5 b5 E  i2 w7 T0 o- {2 X% n& G
  10.   // buffer[ 0x50 ]& Y$ Z2 l2 J0 B" v9 d- o6 K
  11. }
复制代码
  U( d" A. |9 R/ w1 n1 I
Buffered Terminal Output : Enabled
  O( {2 r6 B7 P
' o: n# O/ i5 M) u/ ?8 Z% vxxwritebuffered.c: A0 B# Y/ m7 M, @. B! b3 X

/ a# v% d! I7 p# w* T
  1. #define STORE_SIZE 80
    % W* F, [2 j, B  m! Q" |9 _+ ]/ q

  2. 2 Y$ r6 v+ J3 X8 E; U6 _' ]+ O) |
  3. static size_t storeLen = 0;$ ?; n4 W- _4 D; p  ~6 P; K4 Q: s
  4. static unsigned char store[STORE_SIZE];
复制代码
5 m! t$ i% s# m: {( ^0 o
uint8_t store[ 0x50 ];
$ Y6 @- R1 a7 [5 L( A1 S, T2 C) T$ s  L
uint32_t storelen;% L( a9 o3 {) ?! @0 D# O. y$ S

# \) B; G2 _) D, J" bprintf() --> __dwrite() : buffer[0x50]
1 r/ o- y0 e5 h  x* w
1 r6 K2 {" I- lBuffered Terminal Output : Disabled! E/ b# A1 z; K
! P) o. e# k( y1 a" d+ P5 E3 t/ _
printf() --> __write(), byte by byte
2 a3 J  J7 ?  Y/ }4 B. Z
) n& Y. W* X+ W  C- n: d0 }* W" F* A) y  {: U# |5 N. J( n" q3 a
自定义输出缓冲区
3 G1 L$ o+ h. |+ c$ j
  1. #define LOG_MAX_STR_LEN  512
      l- W# j% p# n( |8 F( _8 U  ^
  2. void log_printf( const char * fmt, ... )9 }- G" d, F  Z) y' i0 U7 f
  3. {
    0 Q7 ]" P( V. a' `% r
  4.   char log_buf[ LOG_MAX_STR_LEN ];
    5 W, j1 X6 N" F% I# H" s9 Q5 {
  5.   va_list args;
    3 h! P* [4 K7 i8 z3 P3 ]+ F( O
  6. ! `4 Y3 _& L1 o) C0 _
  7.   va_start( args, fmt );
    3 b& a4 }. s) m5 U* o2 ^
  8.   int count = vsnprintf( log_buf, LOG_MAX_STR_LEN, fmt, args );
    7 g+ f4 g7 R" r' {1 a6 {( _7 r
  9.   va_end( args );+ R+ Y% i* Q3 N: p5 q6 }
  10. # `5 x+ d' h5 N! C7 D" t/ L
  11.   // If an output error is encountered, a negative value is returned.
    / m  `$ `/ H0 Z- x* b; Z" X3 T
  12.   if ( count < 0 )1 B6 i$ w; @- b0 b8 V6 H
  13.     return;
    3 E% ]) {4 T- R" D! b

  14. ) j# J# C! W: g
  15.   // "123456"    [123456][0X] : count = 6, n = 8
    5 ~( [# k% Y, z+ S+ A2 k3 [2 g4 F
  16.   // "1234567"   [1234567][0] : count = 7, n = 8
    ' N* L5 h9 m+ R4 g
  17.   // "12345678"  [1234567][0] : count = 8, n = 83 h6 K% Y# x" K! s: N" l
  18.   // "123456789" [1234567][0] : count = 9, n = 8
    - n( n+ a# X/ ~3 P2 m
  19.   if ( count >= LOG_MAX_STR_LEN )
    7 u6 u4 W- g& _! H9 J
  20.     count = LOG_MAX_STR_LEN - 1;, |% f- D+ P% F6 C' c

  21. 9 J* b4 P% a; N- f) ~( J
  22.   // now log_buf is C string with the terminating null character
    , a' l5 `% z# y, G# N# K$ B0 X
  23.   __write(0, log_buf, count );1 b% o% H+ Z5 U8 P3 a- c( A5 `
  24. }
复制代码
log_printf --> __write(), bufferred
0 o+ u! b5 h1 g  }7 O
( |3 t+ [$ N1 H  W
stm32系列单片机之printf重定向! r' V& h4 l! k: D% a; s

, s# Z4 c& A  z0 Z# b6 x9 |在程序的调试过程中,除了那些高大上的调试手段外,printf无疑是我们最熟悉最顺手的调试方法。
. V9 Q) }6 ~: K9 E% M# z8 @# b

$ k  h3 m9 ^8 c3 U6 Y通过使用printf,我们可以很方便很直观的获取当前程序的运行状态。# Z& A% Z. G4 w5 S5 _6 x2 e
/ K6 z/ |' i/ i" U( Y$ I
printf()函数是格式化输出函数, 一般用于向标准输出设备按规定格式输出信息。
  V1 a  o. p2 h0 C& `
% m, E5 [! @! ^6 n+ m7 \但是在单片机开发中,一般情况下并不存在标准输出设备,因此我们需要将printf的输出信息重定向,也就是输出到其他输出设备中去。' F1 ]& _  @- F

  ]2 w' \. H4 D" a8 H在stm32平台上实现重定向的方式有两种,重定向至UART,或者通过JTAG的SW模式将printf重定向至SWO引脚输出。" A+ D% m+ K; ]- q% T& W" }. @) r
! S3 b3 b9 Q3 m2 V+ {. C; R+ O. `
首先介绍第一种,重定向至UART,这种方式我们比较熟悉,ST官方提供的固件库中也是使用的这种方法。
0 v0 d2 C% e; i. e+ l( }- a0 n4 r) _& ^  B3 ]1 Q; q' M+ C  c
代码如下:在对UART进行初始化后,通过如下代码对printf进行重定向; c" Y7 [$ X7 i4 M3 P2 G0 A( R. N5 A
  1. int fputc(int ch, FILE *f)3 ]! Y8 n- M; p2 q; Z2 C% J/ N  y
  2. {
    * l- `( |& t  [8 B- r: T
  3.   USART_SendData(USART1, (uint8_t) ch);( R3 j$ I- I( [  o
  4.   
    9 K& Q+ f0 @( k2 g* s' w6 E8 F
  5.   while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET)3 ]* U+ a" e, j  I, r( I4 s; I9 k
  6.   {}
    ! _) @; I5 h9 T! r6 v
  7.   return ch;2 ~' F/ ~' x2 y) I0 u- _
  8. }
复制代码
通过JTAG的SW模式将printf重定向至SWO引脚输出
( D5 U; R1 E: O" [$ v; E4 l0 z! _* }3 c& p$ u
1.在源码中添加对ITM端口寄存器的定义( f+ a7 Q% U0 C
  1. #define ITM_Port8(n)    (*((volatile unsigned char *)(0xE0000000+4*n)))
    * N1 n, ]& m) ~( g. ^1 }7 q0 ]
  2. #define ITM_Port16(n)   (*((volatile unsigned short*)(0xE0000000+4*n)))
    , R- f5 w1 x4 s7 Y! O0 n' L
  3. #define ITM_Port32(n)   (*((volatile unsigned long *)(0xE0000000+4*n)))9 x3 Y! O; S2 n

  4. 7 C$ m* H! A* ]6 X3 l$ o
  5. #define DEMCR           (*((volatile unsigned long *)(0xE000EDFC)))* z/ P" F' i0 Y
  6. #define TRCENA          0x01000000
复制代码
2.通过如下代码将printf的输出重定向至ITM的Port 0
7 s$ q. k: E; \2 l
  1. int fputc(int ch, FILE *f)
    * Y/ R7 ~+ W) O. X1 [
  2. {/ d8 v  w, U& t+ W, h' Q
  3.   if (DEMCR & TRCENA) {
    ) T2 m& z& t. U+ f( y! w- A
  4.     while (ITM_Port32(0) == 0);
    8 S7 W( T6 Y5 \; K5 |$ V
  5.     ITM_Port8(0) = ch;
    . l+ C6 D3 R( }/ l4 i
  6.   }* j9 s% k. i5 X, O6 {, E! e+ a3 y
  7.   return(ch);7 z! J( {- C4 v1 C
  8. }
复制代码
3.通过printf输出调试信息! @, U2 m$ f: I4 ~
  1. printf("AD value = 0xX\r\n", AD_value);
复制代码
4.将Jtag设置为SW模式,并设置ITM的Port 0 获取信息。
  M0 @- q* S7 z) p- ^

; Z9 K& F  k. W4 }, }8 O1 QSTM32中重定向printf到SWO口$ u- D, D/ z& v4 M9 s- f7 |
& N: ~' T& p" \/ _
printf在命令行编程的时候是非常常用的,虽然是个老函数,但是功能强大,经久不衰5 B, E6 B& ]( d% d
- e+ n) ?% M& ]1 A; z5 m. n
51等8位单片机由于RAM比较小,栈就比较小,跑printf比较吃力,
7 P* B1 Z, ]5 a* a* p! O+ Z* A
! ]. ~) `# b! \0 @8 z2 w+ H但是STM32这种32位单片机跑printf就很容易了,而作为一种调试手段,printf十分方便、直观。
  @) X6 ~8 }" a# m. r
9 U9 I0 U/ b* a- ~% D
比较常见的方法是把printf重定向到串口,不过这需要外接一个串口线,比较麻烦。: O; z6 B2 X6 E" T
' P: X$ _7 @# Y" e, j9 _" i
其实STM32自带的SWO口是能够异步输出数据的,而且不需要外接什么设备,
, }4 X& l- p! x1 w; a/ S3 O6 v3 H1 B5 Z2 Q# C
ST-LINK/J-Link等带SWO口的调试器都支持。
/ o# O- S$ E( v: M# d( \4 Z8 Z; x
7 E/ K6 W" k3 H2 u5 h8 Y. i+ B7 T; B下面以STM32F4Discovery开发板+GCC为例说明。
4 `' K9 {9 w' P

" v- b0 O  p# Z2 A根据这里的方法,也可以把printf定位到其他外设。6 x) Q( v% ^- e# Z! Y

# X5 M) a) @6 q% OPS:IAR在编译选项里自带了printf via SWO的功能,就不需要外加设置了。
& p8 c# I+ t; O  e  ~$ u2 _: R; K' b. Q& L% a4 q
首先来说说怎么把信息输出到SWO口,一句话搞定。; [( C/ s3 h; x* s4 [$ c+ b

9 X% F# ^9 C+ d- CITM_SendChar(ch);
  ^, m# S: C. E6 Y' q6 @8 K

4 o6 |0 H/ t1 U; e* Q这是在core_cm4.h(如果是F1系列的那就是core_cm3.h)中定义的内联函数。* O7 |) d+ p' W! ~6 n* H
" _9 p( J* L# }
不过不需要特意去include这个头文件,通过#include "stm32f4xx.h"就间接地将core_cm4.h包含进来。
0 ^8 f6 ?; Y4 }2 h3 a
4 B9 M! R. j) c' ?) L5 [不过说起来,ITM这个东西其实严格来说是Cortex-M提供的一个特性,而不是STM32。# r' r# Q  B2 M8 c5 b
5 u, u4 V/ i, v" u5 f1 R# M( w7 h7 L
利用这个函数把信息输出到SWO口之后再打开St-Link Utility,8 J( ]% n8 @& w1 Y/ l/ i9 L: r

9 |8 Y1 ^- i; R9 L4 |/ v7 ^/ f在菜单里找到ST-LINK→Printf via SWO Viewer就会弹出一个窗口,/ L6 f! O) m% _& e# l

; R# z( V7 R# b5 B2 R; t/ j6 K设置System Clock为单片机内核频率,点Start就能看到输出的信息了。
- f+ |  Q) ]9 ]$ o- x
- F7 [$ O) z* q3 M1 y接下来就是把printf函数输出的字符串重定向过去了。
% @3 F) S, q& L. E, N' N
5 z. k5 w* I7 k8 K/ B: }% @$ u5 Z$ x由于单片机的外设功能是根据需求变化的,编译器不可能确定printf需要用到的外设资源,4 j8 ~7 ]' D) {6 W2 P- W
) L3 h# _6 H  O
于是乎它就干脆留了个接口,也就是_write函数,7 d, F* x5 A% K

1 L* k. g9 Y. C" O当然除了_write函数之外还有_read等其他函数,不过这里我们用不到。& y) ~, v% E" P- \2 n
" T( h- A$ r; Z4 y
其声明为 int _write(int fd, char* ptr, int len);/ v4 S! ]1 M! B6 C$ ?! A
+ D" H; V5 C9 \. T- `( S( ~
关于_write函数,说简单点,就是所有涉及到输出字符串的函数,& H# V' K! a* Z, k) s

! H) W5 X1 \. O. A比如printf和putchar(),最终都会跑到_write函数,这里fd是文件标识符,说开来就比较复杂了,
* W9 n% @5 I; c$ q1 K
9 S) V  A+ y. n6 @! e
这里我们用得到的就只有STDOUT_FILENO跟STDERR_FILENO,' L1 g7 Z8 l) A4 }% v2 s, s
- ?9 M: d  v7 B5 ^4 ?, e
其中前一个是标准输出的文件标识符的预定义变量,后一个是错误输出的文件标识符预定义变量。! h8 `3 I9 B+ L/ F
$ I, b1 b! P- p) m
第二个变量ptr是需要输出的字符串首地址,len就是输出长度。
$ [) }$ K% W. E  Y1 F
$ n5 M: S, y8 S当我们调用printf函数后,C运行库会把输入变量转换为最终需要输出的字符串,2 ^1 b( c4 c5 z" g, _) z; n
  X% A1 R3 W7 q2 M8 ]* m- ^6 y5 k9 Q
然后调用_write函数,将结果输出。我们的工作就是实现一个_write函数。
, }) u! n- A- N, f: P. z1 f- Y% ]$ }/ ]' T' Z
新建一个_write.c文件,内容如下:
) e) b% Z0 g4 e8 g
  1. #include
    % V  |, l$ o+ _1 ?3 B. t# z
  2. #include , s0 p; C: S: h; l
  3. 4 O& H4 B3 `/ |9 P2 c$ y
  4. #include "stm32f10x.h"5 g- o  Q) m( z" X" J( X

  5. + V$ p! W( P$ b/ B3 f
  6. #ifdef _DEBUG& s6 E) Z+ G8 L

  7. 0 Q; A. {, q% M% Z
  8. int _write(int fd, char* ptr, int len)
    & J. _! U3 M4 C/ x
  9. {( m+ @! n* o) O( H& T- @4 }0 V
  10. if (fd == STDOUT_FILENO || fd == STDERR_FILENO)
    & z- c. j: M0 X7 y
  11. {
    ; G8 C  |% Z7 m- l& n
  12. int i = 0;7 [' |8 f% i5 |8 `$ r
  13. while(i<<span style="font-family: 'Courier New' !important; line-height: 1.8; margin: 0px; padding: 0px;">len)- h" F6 b/ b* p1 ~6 Z
  14. ITM_SendChar(ptr[i++]);
    " z7 d" o- U% x  J& i  `; e* s
  15. }
    % U$ r5 D/ n; i' o8 i' s$ t7 n
  16. return len;
    * m8 R3 D: q1 d5 b1 i2 _  O" ~, Y
  17. }
    $ w5 p! b2 ~  o( I$ J6 R

  18. 7 B4 N, X8 X3 K: c) y
  19. #endif
复制代码
加了个#ifdef _DEBUG 的效果是未加 _DEBUG 定义的时候就忽略下面的东西,
% `1 }! b" V$ n
7 K  Y% r: J6 c3 p* s$ E: Y. p$ N因为这东西主要是用在调试阶段,RELEASE版本里面都用不到了,而且多少还是会影响速度。
; R5 A2 j# Z. n( p6 G

- {5 g* d. X4 D) Z3 n其他东西就很简单了- -不需要多说明了吧。- W3 k7 t8 j7 p" Z

* |. G( @" N+ g/ ^+ t6 C直接编译能通过,但是链接会报错,提示无法找到_read之类的一堆函数。
% V, Y% g0 H' O: ?, N" }! I
+ U8 i( p" I# G3 |在链接脚本的下面libgcc.a ( * )后面加上libnosys.a ( * ),就不会报错了。
: v# l) {  S1 W2 {% b, Z  e/ B; ~( k1 K% q+ O
具体原因涉及到Cortex-M3使用的newlib库的实现,就不具体展开了。
0 P# v" H: x9 h
8 N2 `1 d$ _5 }" P5 z* ?; Y0 P好吧好吧,其实我也不知道。8 w6 b5 J6 u- ?) o

/ Q3 y* J3 r6 {. s/ z2 r% z+ \如果想把信息定位到串口,可以直接把ITM_SendChar改成相应的串口函数,
5 `7 b7 c; R* U9 C9 r# @$ w8 s# R1 o) U$ a
也可以利用DMA,先把数据拷贝到DMA缓冲区,让DMA自动传数据,提高响应速度。1 g- d, _1 x+ _' @0 D2 h+ Y

# c8 X4 D7 |5 }2 W$ d$ f, w
6 m& e! N) F/ O. g
STM32片内外设--DBG之Keil SWO输出8 n  L! L  ~& M7 t3 c4 a

' v5 J4 }( ~0 }7 K2 r1) 加入stdio.h,这样你就可以调用printf函数了2 }( G1 s7 B( a1 j; n4 `
2) 使能SWO输出
7 A1 k" n" T: c4 t3 E8 Q7 A6 q- [* H/ G  M% A
使能SWO输出。最简单的办法就是将如下的函数拷贝到你的工程里面,并且在mian函数初始化之后调用该函数。! E, g, L" i3 W5 }
  1. void setupSWO(void)
    ; R& E# _6 M: S$ y
  2. {; @) d2 H/ n! V( Y
  3. uint32_t *dwt_ctrl = (uint32_t *) 0xE0001000;5 w3 P3 |6 Y% _+ X4 a
  4. uint32_t *tpiu_prescaler = (uint32_t *) 0xE0040010;
    ( N( {7 O; ?. c  Q, p( z& k3 M
  5. uint32_t *tpiu_protocol = (uint32_t *) 0xE00400F0;2 J- C" G" B7 Y3 l; \; Y  \6 e

  6. . W7 `5 ?, r9 V5 D$ D. l
  7. CMU->HFPERCLKEN0 |= CMU_HFPERCLKEN0_GPIO;
    : r- E% A% E. _3 I7 U; }

  8. ! n/ g7 S; Y: ~' U, K: g
  9. GPIO->ROUTE |= GPIO_ROUTE_SWOPEN;0 G& \2 l- {+ [/ _$ G( f
  10. #if defined(_EFM32_GIANT_FAMILY)- _- \  n) @# V/ y

  11. 2 M7 K1 r1 ^0 x+ C: S4 N# O
  12. GPIO->ROUTE = (GPIO->ROUTE & ~(_GPIO_ROUTE_SWLOCATION_MASK)) | GPIO_ROUTE_SWLOCATION_LOC0;
    / \5 f' u  k7 h' b" Z+ d( f
  13. : @  o! p- r5 R" \: @  w: e
  14. GPIO->P[5].MODEL &= ~(_GPIO_P_MODEL_MODE2_MASK);
    % _- F8 s/ I2 q5 n! p
  15. GPIO->P[5].MODEL |= GPIO_P_MODEL_MODE2_PUSHPULL;
    2 J% p/ w  m+ \+ |% \" t* D
  16. #else
    9 Q, }- v+ Q- O) [  K- H
  17. * A5 @8 h; D. a7 H
  18. GPIO->ROUTE = (GPIO->ROUTE & ~(_GPIO_ROUTE_SWLOCATION_MASK)) | GPIO_ROUTE_SWLOCATION_LOC1;
    & G) \+ s( r* X' C7 B
  19. 6 `+ y, O. o" K* S( {6 k7 e# R8 T
  20. GPIO->P[2].MODEH &= ~(_GPIO_P_MODEH_MODE15_MASK);# b: ]% T" {. t$ M% K/ z$ r" B
  21. GPIO->P[2].MODEH |= GPIO_P_MODEH_MODE15_PUSHPULL;6 B0 j. |* W& t& Z6 o
  22. #endif4 ~) u$ e" U" c" ?) m  f9 B( |

  23. 9 r# y3 y+ H! u6 s. Z& C
  24. CMU->OSCENCMD = CMU_OSCENCMD_AUXHFRCOEN;
    7 u: A5 A8 ?1 X4 ]* J$ Y

  25. 5 u! R$ a" y0 m, F% C
  26. while(!(CMU->STATUS & CMU_STATUS_AUXHFRCORDY));+ [% Y$ S! a* P1 n/ k) b
  27. : M; B& U0 ~( r7 k0 V" @

  28. ' }- n5 ]* P- o& |, f4 e
  29. CoreDebug->DHCSR |= 1;
    8 q1 }2 l! s+ v3 z) W2 ~
  30. CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
    : U( b0 N2 K8 w! t8 ^5 N' m/ a, p

  31. : X( Z. L+ H  C# @( e9 x
  32. : w; a2 N/ {4 R+ F( i
  33. *dwt_ctrl = 0x400113FF;: d. R. H; }+ A8 u1 d) g- d
  34. 8 u" c4 l5 ?& Q) ^: t% h! M3 N
  35. *tpiu_prescaler = 0xf;/ B5 \3 k& j9 A1 y
  36. - Z4 c4 _' B  p% b( _
  37. *tpiu_protocol = 2;
    " l% W0 |: R, w- E  o* h0 y

  38. - k& S$ {7 b9 X. \5 I4 U; @) A2 u
  39. ITM->LAR = 0xC5ACCE55;* Q9 G2 N4 r) t4 `3 s' ~
  40. ITM->TCR = 0x10009;0 [8 `- j- a( E! @; |
  41. }
复制代码
3) 配置Keil的工程选项
2 ~$ F" Y- n* E6 X* @7 ?( `
, y  s5 y8 x3 m; L9 t& s. [打开Keil的工程配置,选择Debug页面,选择仿真器为Cortex-M/R J-Link/J-Trace, 并点击仿真器选项边上的setting选项,打开具体的设置窗口。
5 h* j5 ?3 V. \* w2 n) o% g

: @- @" {+ i5 o  G在打开的窗口中,切换到Trace页面,选中Enable,并且设置Core Clock为14MHz,分频选项为Core Clock/16。详情如下:: @9 O) n) b' S4 ]" B
4) 在初始化SWO函数之后的地方,使用printf函数进行输出。例如printf("Hello world")。) ~$ f- A# p8 b$ W) ?  ?5 K. D0 [
: |+ t+ K- J+ R6 M- O; ?- x  ]
5) 在你的工程里面,需要添加如下的函数:
9 {$ p4 H" @, T; x1 B; g9 Z5 y
  1. struct __FILE
    : D7 I% B( M& N7 Z

  2. ( J8 e5 Z8 v$ T
  3. {
    * v: z+ V$ W# a+ @0 h
  4. : c% ~  B5 U9 F! R( i- s% n
  5.   int handle; 8 B2 v' R( Y- F
  6. ( ~% s( }2 J1 d" j; }
  7. };+ a6 E$ c' a+ y/ H! S4 B
  8. ) K# d) I  t" ]) T- X
  9. FILE __stdout;
    9 R7 u5 y- V" ?/ o/ b* O* {
  10. FILE __stdin;) F! ^  B; j# ]- {9 r( i
  11. int fputc(int ch, FILE *f) . b1 O; M5 g* S0 u
  12. 2 e; o, c7 K5 I" p' w/ G
  13. {2 W) N3 G9 a3 k  H) f
  14.   ITM_SendChar(ch);9 s9 L- s& P) m4 p) o
  15.   return(ch);
    + j, m# i/ G& E, u' B3 i
  16. }
复制代码
6) 编译你的代码,并且进入Debug状态7 K* {9 D6 {* u0 q! w. r3 j
- B" I- }: ?: }
7) 打开Keil的printf-view窗口, 通过 View -> Serial Windows -> Debug(printf) View- l5 t9 J% U$ m' ^

2 q' w. ?% B' F7 d7 ~  J! m2 C8) 点击运行之后,在Debug (printf) View里即可查看0 O# `( U" Y/ v5 W
1 c2 v9 A0 J( t6 [7 d
赞 收藏 4 评论4 发布时间:2017-7-26 13:24

举报

4个回答
张亚飞 回答时间:2017-7-26 14:05:24
先收藏,需要的时候拿出来弄一下printf
MrJiu 回答时间:2017-7-27 09:20:20
支持一个!!
any012 回答时间:2017-7-27 14:53:07
本帖最后由 any012 于 2017-7-27 15:04 编辑
0 E9 i% \+ W- ~  T9 f; T
0 `  e1 m+ ]) k- h6 s% x在群里看到朋友共享的GCC下printf函数实现的方法:
1 z6 O" e* p9 A3 t) K
  1. int _write(int fd, char *ptr, int len)1 j% [- r! E' }# z2 G
  2. {- q6 A2 U' \: J: F" B
  3.     HAL_UART_Transmit(&huart1, (uint8_t*) ptr, len, 0xFFFF);- d* f* D4 Q( y4 Z
  4.     return len;, W! z) j$ u& R3 e* f2 W5 _9 y
  5. }* F' A1 Y% I: ?' E# Y) n$ g

  6. & U6 @+ [& i% f: P
  7. int _read(int fd, char *ptr, int len)
    ' I: u, |' ~0 n" Z
  8. {0 K, _% x: I- z' u1 S0 u
  9.     *ptr = 0x00;    //Flush the character buffer
      V0 }% |) g8 r% A+ `8 C! h7 |
  10.     HAL_UART_Receive(&huart1, (uint8_t*) ptr, 1, 0xFFFF);6 s' G& c* k+ B9 U; E6 ]2 v7 f7 G
  11.     return 1;
    % x  m. O- |$ {8 Q3 }  r& z
  12. }7 q) e9 {! _' @* M) d
复制代码
一开始使用时总觉得有问题,有些数没被发出来。" S# t) E/ {- V4 K0 [+ w
后来查询得知,printf()语句里面最后加上\r\n后才输出数据,我之前习惯在前边加\r\n。, p9 h' k- V! R2 ]& w
2 Y) W. T, h* v
淡定的H羊 回答时间:2017-7-29 00:10:28
这个太厉害了!谢谢分享。以前看正点原子的串口代码看得云里雾里

所属标签

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