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

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

[复制链接]
Savy1314 发布时间:2017-7-26 13:24
STM32串口通信中使用printf发送数据配置方法(开发环境 Keil RVMDK)

, b1 [6 o8 m& A, p5 m- c. e
* m- v: X: N% }, t. X
在STM32串口通信程序中使用printf发送数据,非常的方便。可在刚开始使用的时候总是遇到问题,常见的是硬件访真时无法进入main主函数,其实只要简单的配置一下就可以了。" P6 G0 ^9 d# I, W$ ?- P
, O* {0 P0 W9 G! {! M
下面就说一下使用printf需要做哪些配置。
3 A3 d+ G; u4 m+ _3 B # \$ ^! _2 ?4 w" q6 A: a
有两种配置方法:2 r) _3 J0 Q0 O5 j

$ g, v& b# P) J; L  O9 |一、对工程属性进行配置,详细步骤如下
3 e: N; D% p8 D* l% P- i" p " ]# O2 l7 b$ g# l
1、首先要在你的main 文件中 包含“stdio.h” (标准输入输出头文件)。
% p4 ]( v9 l7 O7 l/ A 6 m- g' ]4 s' {" L+ ~6 H
2、在main文件中重定义函数   
+ U4 G! S5 j( D1 {3 B$ P如下:: h" L; ?" P! o. ]
  1. // 发送数据
    / \4 P+ e! `% @4 B# x# L
  2.    int fputc(int ch, FILE *f)
    ; I- w2 G6 F! `3 w2 d& M
  3.    {2 r' d+ ~" {7 B+ ]* Z% m* O" Y
  4.       USART_SendData(USART1, (unsigned char) ch);// USART1 可以换成 USART2 等
    ( {& G; f+ X0 g
  5.       while (!(USART1->SR & USART_FLAG_TXE));3 t" g* H* `) ]- x3 V4 F7 q
  6.       return (ch);
    9 V4 o% I) Z# c, u
  7.    }
    1 w# M6 Z( P. M0 T
  8.    // 接收数据$ H$ F0 j8 c6 A5 k
  9.    int GetKey (void)    C7 S/ D2 }8 i% N- K) _4 M6 q5 H

  10. ! p0 G0 X3 D& [; z. x3 b
  11.   {! {* ~4 N' t7 P
  12.       while (!(USART1->SR & USART_FLAG_RXNE));
    & N! _7 O) }9 ]
  13.       return ((int)(USART1->DR & 0x1FF));
    5 f8 ]( \0 m: A  b! o6 u/ C
  14.    }
复制代码
这样在使用printf时就会调用自定义的fputc函数,来发送字符。
) A# e% T. K0 x! O" o
. v) ~% q) Z+ w3 N4 _0 C3、在工程属性的 “Target" -> "Code Generation" 选项中勾选 "Use MicroLIB"
7 A( Z, j6 r# P4 n( {) I   MicroLIB 是缺省C的备份库,关于它可以到网上查找详细资料。3 g5 C# Z6 d  D6 P
% {! a& H/ ]+ A
二、第二种方法是在工程中添加“Regtarge.c”文件/ P% H& t3 V# c$ H1 M  r  B1 S
1、在main文件中包含 “stdio.h” 文件
' E9 c8 P- x: L4 e3 r& _( P' h5 X% X2 C4 r
2、在工程中创建一个文件保存为 Regtarge.c , 然后将其添加工程中在文件中输入如下内容(直接复制即可)5 {) y, Z6 t! c! T% {
  1. #include
    3 g/ B* q' U# L9 F+ I
  2. #include 6 M0 ^$ U$ ]- V' H
  3. #pragma import(__use_no_semihosting_swi)
    ; \6 K; @- J0 z$ t. s
  4. extern int  SendChar(int ch); // 声明外部函数,在main文件中定义
    : C; C# v/ C; V9 q
  5. extern int  GetKey(void);0 g; v' v3 U3 v  J' ^& {7 L$ |
  6. struct __FILE {+ C2 X" w  H# k  `9 ]
  7.   int handle;                 // Add whatever you need here
    5 ^2 e1 K$ V2 F/ f! g# `3 q) I3 s
  8. };* S1 N$ j' l* k' }  ?
  9. FILE __stdout;
    0 i4 K; L" G& u
  10. FILE __stdin;
    3 a6 V5 F4 n# i- m5 m
  11. int fputc(int ch, FILE *f) {* u" O% ^" t. T8 c6 p/ Q3 a
  12.   return (SendChar(ch));0 |) l8 e2 b( {6 x( R
  13. }% X4 q6 F1 e3 r3 s: r/ m3 I
  14. int fgetc(FILE *f) {
    8 G1 i9 t! a& N7 A( O: y
  15.   return (SendChar(GetKey()));% J' I! e# g1 K1 w' v$ J/ f
  16. }$ J$ o1 U# \) r; c6 e4 T
  17. void _ttywrch(int ch) {
    3 ]! Y4 i( v' h
  18. SendChar (ch);
    9 ?: n; W0 t3 l0 T+ f/ J
  19. }
    . a* _4 S( Y* [
  20. int ferror(FILE *f) {                            // Your implementation of ferror
    6 l5 ], R; ?5 `5 Y& S
  21.   return EOF;. h3 k! S' d5 G3 A
  22. }
    / T) S1 u  p) O5 n, F: z
  23. void _sys_exit(int return_code) {
    % Q5 D& D0 ]" _7 `
  24. label:  goto label;           // endless loop
    0 t. J! j( }# e
  25. }
复制代码
3、在main文件中添加定义以下两个函数$ m$ C3 Q+ X4 R4 B/ \. W# W
  1. int SendChar (int ch)  {1 x* Z6 S) E8 E( Q2 g
  2.   while (!(USART1->SR & USART_FLAG_TXE)); // USART1 可换成你程序中通信的串口
      ]! ?4 R! u6 K0 A3 P7 D
  3.   USART1->DR = (ch & 0x1FF);
    ) ]$ ^6 T3 O+ U6 b
  4.   return (ch);
    : G1 {) z, l( G' q2 J  q5 @8 y
  5. }
    7 g7 u3 m4 C' c3 X
  6. int GetKey (void)  {- Y1 K, z: h  M, B8 y3 D
  7.   while (!(USART1->SR & USART_FLAG_RXNE));4 n% x4 h) ?' @" I+ r8 K
  8.   return ((int)(USART1->DR & 0x1FF));
    , I! M4 {, {0 p+ a8 P$ t) D
  9. }
复制代码
至此完成配置,可以在main文件中随意使用 printf 。6 R$ C' M  O  Q# ?; O7 F- R
* l" _6 D# X( ]
! P7 v, b# O6 o+ W- L. @1 H
STM32程序添加printf函数后无法运行的解决方法(串口实验)
$ {' z! ?8 n6 I  [1 Z9 [; Q. C- q1 Q% F$ H0 ?2 d
标准库函数的默认输出设备是显示器,要实现在串口或LCD输出,必须重定义标准库函数里调用的与输出设备相关的函数.3 p8 u3 w( ^  E, Z9 Q

  N# L# w( s% i" G6 F& M; j例如:printf输出到串口,需要将fputc里面的输出指向串口(重定向),方法如下:
" G, E6 ~& u- k# W( R- s
) ~& N( B  M9 z, p只要自己添加一个int fputc(int ch, FILE *f)函数,能够输出字符就可以了
. m& G  @5 L  t1 c# H0 C! Q6 V# p
  1. #ifdef __GNUC__5 }1 Z3 b7 s! z
  2. " h: Z# a' \) w  b* t; r4 W5 E
  3. #define PUTCHAR_PROTOTYPE int __io_putchar(int ch)  y- o  g9 q! g2 l7 P
  4. #else2 D: f3 G) Q5 J) M3 c
  5. #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)  @1 A) |) `" w# k8 u: z  R
  6. #endif ; E+ Z$ T7 c% B- j

  7. 0 ~" K  i& y0 n2 D: a/ U
  8. PUTCHAR_PROTOTYPE
    : a9 G1 z7 m, N& I! S
  9. {, x% g2 u6 t4 V% U
  10.   6 q7 I/ `# J6 I2 G
  11.   
      ?- h" {6 n& J
  12.   USART_SendData(USART1, (uint8_t) ch);& \; d! r* g% n) i+ Z- _0 i- f. H6 h
  13.   7 L& K8 p5 t% T6 t; t
  14.   while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
    " |! _. g5 }8 H. u
  15.   return ch;  d1 p8 e5 d9 ]" D& x
  16. }
复制代码
因printf()之类的函数,使用了半主机模式。使用标准库会导致程序无法运行,以下是解决方法:/ y! D6 B0 s- [
( G1 S, J+ s( w' C
$ x+ x* o- t& q
方法1.使用微库,因为使用微库的话,不会使用半主机模式.9 I+ K1 R; C) m) B% ]( e9 \
7 s  l% H0 A7 [* E" h5 r' w; a
  |2 K: G" v  ?; @
方法2.仍然使用标准库,在主程序添加下面代码:$ l. w2 _4 N. z7 A) D( S/ L& ?
  1. #pragma import(__use_no_semihosting) 2 ^  C* Q# `9 l, [# e
  2.   u- E+ s3 c; E; D- f- B' q
  3. _sys_exit(int x)
    1 U0 Z& D( s% A) I
  4. { " k( U- `5 _1 j9 i- e
  5.   x = x;
    ; y& f% a( @/ a3 @5 c
  6. }% f) U: t+ g3 k* E: i! v. ]

  7. ' h$ o# \4 _, c, J  c! I  ^" N
  8. struct __FILE 8 w( U* |: s& D6 u, ]
  9. { 2 Y/ ?  l8 a5 C) M4 ~
  10.   int handle; ; g6 U$ L* y0 b, x+ T4 j1 U
  11. }; $ ?% Q+ ~6 N3 D5 j% N+ x2 U

  12. # F& N9 S3 C1 t
  13. FILE __stdout;
复制代码
IAR EWARM. @5 Z" I- e$ g. Z1 ~" A. D" V  x$ M
6 h8 ^7 n2 y1 ?6 E
General Options -- Library Configuration -- Library : Full < file descriptor support >
2 o" E1 \* }( r6 ?3 B- x8 ]
8 X. i) C6 E" t- Q/ w
  1. #include
      ~( C. w4 j! {* K# Q

  2. " p. u! M+ [1 D. V$ F: _
  3. #ifdef __GNUC__  ]5 W& Q* h# a1 T
  4. 5 [) J' p. J$ r
  5. #define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
    & c0 d) ]3 Z4 T8 Z
  6. #else
    4 r( q/ F: {1 T* F
  7. #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
    2 M; ~# z6 i$ l' f7 J( G/ Z8 S
  8. #endif 7 p$ h* y! N  D( C+ `
  9. PUTCHAR_PROTOTYPE" w& x2 ~- d) J, _; C
  10. {7 c* K1 I4 K" k+ r! r& T
  11.   . P' z3 c( [: |
  12.   
    % m% q* k( ?+ c" R* I6 @6 N7 O
  13.   USART_SendData(USART1, (uint8_t) ch);
    & k2 b% p/ z+ C5 i3 B8 }
  14.   
    ' Z3 j6 f5 v# ?8 _- m
  15.   while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);/ R0 V+ w* g! ?9 s) F3 Y
  16.   return ch;
    " c' M5 O2 {, d! [
  17. }
复制代码
8 U3 q' P) F$ J' k" ?
General Options -- Library Configuration -- Library : Normal < NO file descriptor support >1 Q% A4 Z4 m9 b  }; d# k
  1. #include + @4 U- H) o% w
  2. + L* ?7 \8 ]& A: c7 S
  3. size_t __write(int handle, const unsigned char * buffer, size_t size)! X5 x1 U% N. }, i4 ~- o2 y
  4. {8 s( V: G. U4 i7 ~3 a% ~2 m- R
  5.   // byte by byte write
    % N! G8 P# k- f6 L. x
  6. }
    - e& Y/ n4 G3 A) J  m
  7. 9 W, e( w+ h$ p% B0 x
  8. size_t __dwrite(int handle, const unsigned char * buffer, size_t size)
    / ]# h$ _- @4 r; ?4 d1 B
  9. {
    0 f. r* V( c4 K
  10.   // buffer[ 0x50 ]6 l2 t# f( e& f+ Q( r$ Z
  11. }
复制代码
) H3 N. _) w5 C, }- s% [6 V
Buffered Terminal Output : Enabled3 x4 h+ C0 I, ]5 Y

% ]7 ]8 G$ S; ?. ~% T) p" uxxwritebuffered.c
5 \( G0 ]: ?- a, v$ T: A- X& {0 h; h4 a* f) T! B- f
  1. #define STORE_SIZE 807 E$ K  ^- Z4 y6 l1 H
  2. 9 p' B! l5 @. u6 V
  3. static size_t storeLen = 0;
    * N/ A8 S3 w4 l8 E
  4. static unsigned char store[STORE_SIZE];
复制代码
5 z+ E1 q* _- Y/ g! V) W/ e* \
uint8_t store[ 0x50 ];
7 c$ Y5 p" M1 G4 ?* ~
/ ]+ Y# _- C' R" _) u) F+ z8 l& Xuint32_t storelen;% d& s( |5 o# `. N! K. z2 Y3 J( l, m

$ C$ l8 z% ]4 m# B, `  w; oprintf() --> __dwrite() : buffer[0x50]" K$ I9 r8 Y  x$ |
* Y" J* g% ^( N+ f: [  Z
Buffered Terminal Output : Disabled" v9 H  f1 x" v% H

$ ~0 \1 _' }! J1 @% n- Gprintf() --> __write(), byte by byte
1 a+ p; R% J# i- c- o7 j! R/ T1 k6 D- i( ?) l

; Y2 D& m) D/ C  M自定义输出缓冲区
, @5 B0 P' E; r1 T6 e/ e4 d
  1. #define LOG_MAX_STR_LEN  512! U# j$ l8 B* I* n3 _9 {
  2. void log_printf( const char * fmt, ... )$ \6 V$ r8 ^/ Z8 E/ c; x
  3. {) |( L0 Y9 m7 N+ t8 Y
  4.   char log_buf[ LOG_MAX_STR_LEN ];! _2 Q; k$ ~$ c7 C& Y5 I+ a
  5.   va_list args;
    $ w  f0 B% e, I1 a. K" `

  6. ; G* {7 F% _' G% J
  7.   va_start( args, fmt );5 A2 w" v0 j. m6 z/ |
  8.   int count = vsnprintf( log_buf, LOG_MAX_STR_LEN, fmt, args );; p$ [& Q7 h3 e# G  `4 k
  9.   va_end( args );
    6 Z1 {) }4 z4 @) f4 s
  10. + i" G. B: p9 v# [# K
  11.   // If an output error is encountered, a negative value is returned.
    ! U2 B6 z# T9 \& C  |
  12.   if ( count < 0 )" B+ ?! w/ b8 G( s4 S9 U
  13.     return;
    : P( _" w' \: x- N- h' R# o8 e8 J' K
  14. & w7 A  _- E- h
  15.   // "123456"    [123456][0X] : count = 6, n = 8* y0 O/ Z5 B' x( [
  16.   // "1234567"   [1234567][0] : count = 7, n = 8! Q" Z# O$ T9 Z: I7 d& b# d, N
  17.   // "12345678"  [1234567][0] : count = 8, n = 8
    . ^# s/ r, y& |  q% \3 e/ S
  18.   // "123456789" [1234567][0] : count = 9, n = 8
    & Z$ B% C9 s3 j2 J7 |
  19.   if ( count >= LOG_MAX_STR_LEN )
    " \) l* d; _: |+ X3 p6 h, a3 W
  20.     count = LOG_MAX_STR_LEN - 1;
    - T2 H3 b8 F, C( k1 F* m5 D) n
  21. 2 \( t1 G0 u0 n) D; T9 F
  22.   // now log_buf is C string with the terminating null character
    3 b% d9 G$ m2 ~1 c
  23.   __write(0, log_buf, count );1 o  ^: C6 |! Q, H' t- r4 n2 N
  24. }
复制代码
log_printf --> __write(), bufferred
/ p, k# t" Y) |1 |, D

( Y& r) j( D3 N' {8 H' Gstm32系列单片机之printf重定向* v% M4 ^# Y, Z, x
! A4 _) B% v. A' W$ S
在程序的调试过程中,除了那些高大上的调试手段外,printf无疑是我们最熟悉最顺手的调试方法。
* ~* x% A( s; R) m/ J. M

0 c' G  C& [; Z3 q& e) M+ A3 N  d: l通过使用printf,我们可以很方便很直观的获取当前程序的运行状态。! b$ F  {+ V) r; f& u& R$ X$ ^

6 k2 B4 B# w/ }  [) wprintf()函数是格式化输出函数, 一般用于向标准输出设备按规定格式输出信息。. ^! K" v, _9 o( m) N+ N
) L  O/ z) U: J
但是在单片机开发中,一般情况下并不存在标准输出设备,因此我们需要将printf的输出信息重定向,也就是输出到其他输出设备中去。1 Z- g/ m7 A5 }/ Q! b  G# E, Z
- _7 D% ]' |/ Z! F6 }& W
在stm32平台上实现重定向的方式有两种,重定向至UART,或者通过JTAG的SW模式将printf重定向至SWO引脚输出。
4 o6 k! S7 I, P9 F5 h3 d2 C

1 E/ Q( h  s' P+ [) e7 i: L5 G  ]首先介绍第一种,重定向至UART,这种方式我们比较熟悉,ST官方提供的固件库中也是使用的这种方法。
& T$ x8 W- z3 P6 O* \. y$ H% C: o
" j9 r3 E: F- f. G9 @- K- t代码如下:在对UART进行初始化后,通过如下代码对printf进行重定向6 V8 N2 A# P" d% \! k
  1. int fputc(int ch, FILE *f)
    : S' `8 B( q4 R
  2. {
    $ b3 R3 N& W$ _( E  \
  3.   USART_SendData(USART1, (uint8_t) ch);
    / B: ?5 J3 c- Q
  4.   
    1 D: X) H# ~4 I$ o( s0 ^
  5.   while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET)1 R# e9 e8 v" W. @
  6.   {}1 C& |4 N* c$ C  X) I
  7.   return ch;; T! V1 J; P" Q! h6 h- G: i
  8. }
复制代码
通过JTAG的SW模式将printf重定向至SWO引脚输出  M- P4 k# o* w1 l; z9 e2 r, q5 @
/ K+ R! c6 a) w2 y2 H$ O. I
1.在源码中添加对ITM端口寄存器的定义- s$ K) y# _. s; R! I
  1. #define ITM_Port8(n)    (*((volatile unsigned char *)(0xE0000000+4*n)))
    + r5 g% [9 e  |! }
  2. #define ITM_Port16(n)   (*((volatile unsigned short*)(0xE0000000+4*n)))5 }) f/ Z7 r  d
  3. #define ITM_Port32(n)   (*((volatile unsigned long *)(0xE0000000+4*n)))% M! K1 v8 m( n1 S& `! b, p2 a9 @
  4. 4 C3 y6 B* c1 W
  5. #define DEMCR           (*((volatile unsigned long *)(0xE000EDFC)))
    4 W* l; B9 X8 F7 e( L2 s6 M
  6. #define TRCENA          0x01000000
复制代码
2.通过如下代码将printf的输出重定向至ITM的Port 0
( D$ n& n; ~* G! f
  1. int fputc(int ch, FILE *f) ; j. L4 k( X# S) F, N/ J
  2. {
    2 u) [, J" h; J8 o2 w' l" t0 H
  3.   if (DEMCR & TRCENA) {7 t) W# e/ M- [% E* Y3 g+ ]1 h
  4.     while (ITM_Port32(0) == 0);
    4 n( Q, n* q7 C# I
  5.     ITM_Port8(0) = ch;
    : L9 w( L2 D( b
  6.   }
      ~. o, ?! K7 C- J# A# C
  7.   return(ch);. t- ~2 ~# e1 y
  8. }
复制代码
3.通过printf输出调试信息
3 n: ?% L9 [" L  W& k& M
  1. printf("AD value = 0xX\r\n", AD_value);
复制代码
4.将Jtag设置为SW模式,并设置ITM的Port 0 获取信息。$ h8 X  }1 W9 M- b4 r6 O0 C* L
4 @' m; {% p$ x) [( y" U" x
STM32中重定向printf到SWO口
  g. Y: m$ i7 `) G( a) b

4 @9 s, d% e* F3 k% I3 t( A# oprintf在命令行编程的时候是非常常用的,虽然是个老函数,但是功能强大,经久不衰9 e% S& u9 D1 C5 g. V  A4 C  n$ n

4 u7 D1 V$ {% s2 [5 ^9 ?  A3 Y! q51等8位单片机由于RAM比较小,栈就比较小,跑printf比较吃力,2 U, C$ |  m; l5 e8 D: ~
, A. `; F; m  R6 m; K" F+ h
但是STM32这种32位单片机跑printf就很容易了,而作为一种调试手段,printf十分方便、直观。
3 `9 M$ k& O2 \1 o( a; m5 |1 u
0 h$ s) k/ T; J# q# B1 F4 L3 Y
比较常见的方法是把printf重定向到串口,不过这需要外接一个串口线,比较麻烦。
/ l% g7 q# l/ W" z- ~& P3 Z$ \0 G8 I# V
其实STM32自带的SWO口是能够异步输出数据的,而且不需要外接什么设备,3 E$ U! Z$ G. v& O# S8 e6 V" R

! X2 r$ i- A. ~4 a$ p+ cST-LINK/J-Link等带SWO口的调试器都支持。
8 K) U3 a, `9 [$ N. I3 @* T0 @9 P  C; |# F# R
下面以STM32F4Discovery开发板+GCC为例说明。4 i0 v  N' m) E4 v3 G! L
7 i' a# J9 I6 Q0 ^. \# H* e7 ?& ]
根据这里的方法,也可以把printf定位到其他外设。
; b# X% m  [' D4 t- t: c9 Y9 K& ~! p
PS:IAR在编译选项里自带了printf via SWO的功能,就不需要外加设置了。
+ o5 ^7 ^4 t! }1 g4 P' g/ _
( p! e, B" I" @9 y" y) |( y# F9 {首先来说说怎么把信息输出到SWO口,一句话搞定。
" I1 [# c1 f) s9 j4 P. l; N
6 G+ i3 V" ~0 G2 g% N
ITM_SendChar(ch);
' r' p$ z/ i: l6 q# y

, A7 ?4 C* ]7 l/ r这是在core_cm4.h(如果是F1系列的那就是core_cm3.h)中定义的内联函数。
) B7 v. z- E' @9 u+ ~5 {' H( o, Y2 U( ]4 ]4 W0 o. H0 v
不过不需要特意去include这个头文件,通过#include "stm32f4xx.h"就间接地将core_cm4.h包含进来。
, T1 Q6 N5 \  q( v  U8 c. [; h
' t- K/ c2 w+ @: A" [不过说起来,ITM这个东西其实严格来说是Cortex-M提供的一个特性,而不是STM32。
9 O' }7 @9 e- |4 o7 R5 A/ i* S0 i4 r4 R- {+ M8 y  `' N9 D; }
利用这个函数把信息输出到SWO口之后再打开St-Link Utility,% z7 L& D' V1 Y: r/ y* D5 ?; y& I
+ L$ k8 ?8 X  a. m/ d! p2 U
在菜单里找到ST-LINK→Printf via SWO Viewer就会弹出一个窗口,  [  e, }" s; x7 u" p3 B

( u3 j, B% A) w" S. y设置System Clock为单片机内核频率,点Start就能看到输出的信息了。
7 o! I1 L( ^1 E9 S$ g0 G1 g( b; l, X8 R2 t) Q: ^" I
接下来就是把printf函数输出的字符串重定向过去了。
4 }" b: i3 ?; P0 U: `- i" j' r% _! u1 ]. k6 N" F2 S0 p
由于单片机的外设功能是根据需求变化的,编译器不可能确定printf需要用到的外设资源,
; c& l7 [3 |5 t0 ~! r5 \6 `" j* n5 y& H3 V0 p1 [
于是乎它就干脆留了个接口,也就是_write函数,
- _6 ]  v! Y  k: Q1 g7 {; P5 w9 M7 X; g; C. c  C
当然除了_write函数之外还有_read等其他函数,不过这里我们用不到。
) I  s; S1 A/ s0 X) U, ^; f

) i1 Q, f: p% _9 d2 o其声明为 int _write(int fd, char* ptr, int len);! S; [  |) H5 p) Q* u6 P
/ }) K) O: ?. d* s3 A! U3 P
关于_write函数,说简单点,就是所有涉及到输出字符串的函数," O( @* _6 @5 I

: n7 H8 G1 k# v) p4 M$ m# v比如printf和putchar(),最终都会跑到_write函数,这里fd是文件标识符,说开来就比较复杂了,+ q& H9 b0 V1 N: y" p1 l  G; v
( ~' E  w; V" B, g: Q  u
这里我们用得到的就只有STDOUT_FILENO跟STDERR_FILENO,9 o, f. v- ^3 M# f
; _* O5 q; Y$ c3 _
其中前一个是标准输出的文件标识符的预定义变量,后一个是错误输出的文件标识符预定义变量。
1 T  [9 n; k- W8 h
4 s  ^2 [# [. n9 V0 K4 b6 T. C$ B- T
第二个变量ptr是需要输出的字符串首地址,len就是输出长度。
4 ^7 z, g" Q( X! U! s6 Q
8 v7 a# U% M9 j& E2 g7 q9 f' g当我们调用printf函数后,C运行库会把输入变量转换为最终需要输出的字符串,
% e& Z5 t( w9 c
0 }. }7 M' U0 w1 [. b
然后调用_write函数,将结果输出。我们的工作就是实现一个_write函数。
* M; u6 ~  y% Q2 f
# A0 W% M; y9 ~, m' _新建一个_write.c文件,内容如下:
' }* d$ S2 H6 O+ {$ T0 S
  1. #include
    - C  w" {" f/ \9 X: D6 \0 L( x. H% I
  2. #include 6 J: o" `# {* G% Q% L5 p# e
  3. ; q4 G& S1 c; b+ j4 \+ W8 q: i" {
  4. #include "stm32f10x.h") i$ a  y, d* p$ _9 L. J" v& A

  5. & _/ o, `( `0 W$ B
  6. #ifdef _DEBUG
    8 m1 ~' p; V5 O% |  x( d, S" y% H
  7. ; T( ?: j  e" E6 }
  8. int _write(int fd, char* ptr, int len)0 ^0 N4 D2 P9 `
  9. {
    3 W8 N) ]4 O0 i4 C& i
  10. if (fd == STDOUT_FILENO || fd == STDERR_FILENO)0 x( Y; C% [/ j4 N  H8 [4 t
  11. {
    0 W4 i& |; s. Q& e' F6 t
  12. int i = 0;  k1 z7 k5 J+ Z8 N9 `5 _
  13. while(i<<span style="font-family: 'Courier New' !important; line-height: 1.8; margin: 0px; padding: 0px;">len)
    * B; A$ Y" W: n* E0 I
  14. ITM_SendChar(ptr[i++]);9 A/ z% n  F9 j1 D: z2 K
  15. }, L1 S- ~, Z1 ]4 H
  16. return len;
    : V4 o# V+ j. }) G* k* T6 O3 J
  17. }0 U- A2 o* G* T" [
  18. - l9 s* Y; ?* Y: b- \% f) k
  19. #endif
复制代码
加了个#ifdef _DEBUG 的效果是未加 _DEBUG 定义的时候就忽略下面的东西,
9 O! @9 }: b4 ~8 [6 H/ g
& H6 i6 G, e7 R4 b6 D# U因为这东西主要是用在调试阶段,RELEASE版本里面都用不到了,而且多少还是会影响速度。( b8 t! P! _4 H3 }. [" v) R2 p

" f; C) ^& f6 J, }其他东西就很简单了- -不需要多说明了吧。* h. A# i2 J% E3 W. p

# S0 f6 G. m* z# [. ~直接编译能通过,但是链接会报错,提示无法找到_read之类的一堆函数。- l  ^- H$ m9 Y3 q6 ]& G
" u! s6 w0 l3 m) t& J' Y! R
在链接脚本的下面libgcc.a ( * )后面加上libnosys.a ( * ),就不会报错了。& l# K" t" l( i( c

9 ?0 T$ L# Y! C, Q1 b; C$ o具体原因涉及到Cortex-M3使用的newlib库的实现,就不具体展开了。
: ?9 \) u; z- r( w1 l5 b2 H* @
; k3 D; I! m" }9 F好吧好吧,其实我也不知道。  B* _, E* |0 X2 O6 l" N

$ ]! I9 K* N& b7 v如果想把信息定位到串口,可以直接把ITM_SendChar改成相应的串口函数,
5 S2 q: v- L& r2 }! z+ U) R1 {; \
1 |6 Z: j( [5 H. g+ b+ X0 G, J也可以利用DMA,先把数据拷贝到DMA缓冲区,让DMA自动传数据,提高响应速度。; }+ `. I3 p# W" C2 x8 N

) i2 S2 [5 x9 e7 ^$ u
8 a2 D# L1 K% s
STM32片内外设--DBG之Keil SWO输出
( o1 R6 D# G) |# g. N( c
( z7 X- c; W% ?% d1) 加入stdio.h,这样你就可以调用printf函数了/ @3 E4 n/ L1 D  R: C' ?7 e
2) 使能SWO输出  E2 r) [4 n6 O

! s2 q! h6 S) ]" t; f* ?. ~使能SWO输出。最简单的办法就是将如下的函数拷贝到你的工程里面,并且在mian函数初始化之后调用该函数。
4 }$ a5 W% R6 M. i4 X
  1. void setupSWO(void)
    4 g" K: e* p9 m0 h0 U. p
  2. {# C( X+ d8 v) C* g
  3. uint32_t *dwt_ctrl = (uint32_t *) 0xE0001000;+ u* K1 d7 ^! q: H- A
  4. uint32_t *tpiu_prescaler = (uint32_t *) 0xE0040010;9 x& j$ u" G! \" _
  5. uint32_t *tpiu_protocol = (uint32_t *) 0xE00400F0;
    , K, N6 z* j! Y& x" P: a/ S; |
  6. 2 B3 f) ]5 _' G! W- e  J, g8 H2 i
  7. CMU->HFPERCLKEN0 |= CMU_HFPERCLKEN0_GPIO;# Q8 N& l$ E2 N9 k8 g3 U3 P
  8. / y0 _7 ?0 H2 b  l$ |
  9. GPIO->ROUTE |= GPIO_ROUTE_SWOPEN;0 E2 B$ V+ Y6 r* q
  10. #if defined(_EFM32_GIANT_FAMILY)+ o5 E' o+ C) l2 n- F7 I8 F# J
  11. ! M" W/ N; b8 s
  12. GPIO->ROUTE = (GPIO->ROUTE & ~(_GPIO_ROUTE_SWLOCATION_MASK)) | GPIO_ROUTE_SWLOCATION_LOC0;# N& Y4 d- R) d4 m# }

  13. - g5 Q/ v/ Y. l. i  i) z- V/ ^; R% V
  14. GPIO->P[5].MODEL &= ~(_GPIO_P_MODEL_MODE2_MASK);& q6 w8 c/ A1 C- o5 Y
  15. GPIO->P[5].MODEL |= GPIO_P_MODEL_MODE2_PUSHPULL;
    4 d. K* u; J- o
  16. #else. ^8 ]( p6 H7 `' `6 G

  17. : G" ^# c' F- ^6 j
  18. GPIO->ROUTE = (GPIO->ROUTE & ~(_GPIO_ROUTE_SWLOCATION_MASK)) | GPIO_ROUTE_SWLOCATION_LOC1;
    1 b3 t# ]% d1 i8 l# U9 V
  19. , C: X  ]2 |/ V& e* b& W
  20. GPIO->P[2].MODEH &= ~(_GPIO_P_MODEH_MODE15_MASK);
    $ M- L" `5 I0 @  m# m1 i
  21. GPIO->P[2].MODEH |= GPIO_P_MODEH_MODE15_PUSHPULL;
    . z# o4 f1 k& ~9 w' g6 k
  22. #endif
    - n; ~+ N0 H! y6 \( i( @
  23. 3 Y( V8 `+ C7 E7 f6 U
  24. CMU->OSCENCMD = CMU_OSCENCMD_AUXHFRCOEN;
    ; i- N% i8 h6 H2 Z/ s$ i. U$ _

  25. 5 n6 ^9 _1 q6 y7 l
  26. while(!(CMU->STATUS & CMU_STATUS_AUXHFRCORDY));$ u, z5 Y9 D$ L' b

  27. " b- I3 u) P( f) ]5 I9 [9 q$ ~

  28. 0 v8 ?7 W, ^, ^8 Q4 e& |2 A
  29. CoreDebug->DHCSR |= 1;
    & Z. P/ n/ D# T8 Z" Y1 Q
  30. CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
    " G, h- D* s5 r. ~% [. ^. S
  31. ; p( E  w$ w! v- M
  32. / M# x! s( |2 O# K- K
  33. *dwt_ctrl = 0x400113FF;: Q; V6 @; W; q  D

  34. , g# w" P! `7 |$ H  f, E
  35. *tpiu_prescaler = 0xf;/ m3 A' t( V* @

  36. 4 ^3 b1 d/ N( }
  37. *tpiu_protocol = 2;# u8 s  b1 F) B* K4 v; k: t

  38. 7 b. P  a8 [- n* `' a, t' Z8 o
  39. ITM->LAR = 0xC5ACCE55;9 b) y* x- A) B  E4 O
  40. ITM->TCR = 0x10009;
    ! n$ L! [, J9 u8 \" e. f# M5 j
  41. }
复制代码
3) 配置Keil的工程选项
- t9 P( g$ y+ w# B2 o, `, Z& B, i! o  M; O$ t
打开Keil的工程配置,选择Debug页面,选择仿真器为Cortex-M/R J-Link/J-Trace, 并点击仿真器选项边上的setting选项,打开具体的设置窗口。
7 l" v& c$ V9 H$ e. ~( ?
/ }2 z4 r$ ?) |
在打开的窗口中,切换到Trace页面,选中Enable,并且设置Core Clock为14MHz,分频选项为Core Clock/16。详情如下:: `# [& i; @  i9 H1 C; d8 X
4) 在初始化SWO函数之后的地方,使用printf函数进行输出。例如printf("Hello world")。
( I  \, j8 H$ i  q% _! i5 S8 G0 a# N3 f8 W( E
5) 在你的工程里面,需要添加如下的函数:) h" }4 N7 O0 t, z. k) l$ v; f
  1. struct __FILE
    9 |- [; O5 j7 h; F, n
  2. 3 j/ D  `9 d! _: Q7 F' Z
  3. { 6 d  d( m# c9 ]

  4. 4 Q: H% r: l4 y( z  b" o3 K) N
  5.   int handle; ; v0 L' x0 @* S# R. q$ n
  6. & ~( d- k0 P! Q: w  U3 _
  7. };4 Y9 O: k% `7 D* c0 x. D- }9 C

  8. - V6 Z9 s2 T. ~
  9. FILE __stdout;
    2 Y) R, h6 @/ |/ Q
  10. FILE __stdin;" r$ B3 F- m8 Z  ~! X! q
  11. int fputc(int ch, FILE *f) ; ~! t  @5 m7 Z" Q! W  o
  12. 2 _; v/ V5 I8 f- O, R2 d
  13. {
    6 L* J( v7 [7 u2 \! O
  14.   ITM_SendChar(ch);
    & h, b5 f, h# @: o# x
  15.   return(ch);
    , Q* I& V( r& y1 c. @0 b4 o0 i# K
  16. }
复制代码
6) 编译你的代码,并且进入Debug状态
5 D9 @1 o4 Q4 ]' E- V
$ E0 \, ~' h7 k7) 打开Keil的printf-view窗口, 通过 View -> Serial Windows -> Debug(printf) View
. k/ Z4 [) E& P! G- A! O' {/ ~$ M8 m" O6 Y! s
8) 点击运行之后,在Debug (printf) View里即可查看
- z0 _2 ~: r  m1 }* q: S9 f( k3 K1 n  ~$ |* s2 f, l
收藏 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 编辑 ' x+ Z) L) O$ G: l: N
! [: q! k  |3 |3 ^* i+ v
在群里看到朋友共享的GCC下printf函数实现的方法:5 F/ a0 K+ b- u# m  h# r
  1. int _write(int fd, char *ptr, int len)
    7 x5 m" {2 E6 U4 @  u
  2. {# A: H1 T/ C+ w' U: c( l
  3.     HAL_UART_Transmit(&huart1, (uint8_t*) ptr, len, 0xFFFF);- m9 \. }& n! ]
  4.     return len;
    2 R" I8 g3 J. P
  5. }% A0 |# N3 E! E1 h% {& V( C& g
  6. * U( w2 _" O7 r0 N* F8 _+ y
  7. int _read(int fd, char *ptr, int len)/ K1 W, A# A- s& M
  8. {
    5 r8 Z6 [/ J. ^4 w. d% K
  9.     *ptr = 0x00;    //Flush the character buffer
    # U  {' ~* R( D) l
  10.     HAL_UART_Receive(&huart1, (uint8_t*) ptr, 1, 0xFFFF);
    8 I% |: K. W- G. u  r, H, _# z0 o
  11.     return 1;
    $ ^1 V$ Z3 R' J5 U7 o
  12. }
    & e% v' u  e/ i* M7 U7 I
复制代码
一开始使用时总觉得有问题,有些数没被发出来。3 H4 r' h( p" |
后来查询得知,printf()语句里面最后加上\r\n后才输出数据,我之前习惯在前边加\r\n。
+ x5 m; Y% I  _/ t! e/ W2 u" P* _, J' x# q6 _* Z" h+ [) T3 A
淡定的H羊 回答时间:2017-7-29 00:10:28
这个太厉害了!谢谢分享。以前看正点原子的串口代码看得云里雾里

所属标签

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