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. ]
- // 发送数据
/ \4 P+ e! `% @4 B# x# L - int fputc(int ch, FILE *f)
; I- w2 G6 F! `3 w2 d& M - {2 r' d+ ~" {7 B+ ]* Z% m* O" Y
- USART_SendData(USART1, (unsigned char) ch);// USART1 可以换成 USART2 等
( {& G; f+ X0 g - while (!(USART1->SR & USART_FLAG_TXE));3 t" g* H* `) ]- x3 V4 F7 q
- return (ch);
9 V4 o% I) Z# c, u - }
1 w# M6 Z( P. M0 T - // 接收数据$ H$ F0 j8 c6 A5 k
- int GetKey (void) C7 S/ D2 }8 i% N- K) _4 M6 q5 H
! p0 G0 X3 D& [; z. x3 b- {! {* ~4 N' t7 P
- while (!(USART1->SR & USART_FLAG_RXNE));
& N! _7 O) }9 ] - return ((int)(USART1->DR & 0x1FF));
5 f8 ]( \0 m: A b! o6 u/ C - }
复制代码 这样在使用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% {
- #include
3 g/ B* q' U# L9 F+ I - #include 6 M0 ^$ U$ ]- V' H
- #pragma import(__use_no_semihosting_swi)
; \6 K; @- J0 z$ t. s - extern int SendChar(int ch); // 声明外部函数,在main文件中定义
: C; C# v/ C; V9 q - extern int GetKey(void);0 g; v' v3 U3 v J' ^& {7 L$ |
- struct __FILE {+ C2 X" w H# k `9 ]
- int handle; // Add whatever you need here
5 ^2 e1 K$ V2 F/ f! g# `3 q) I3 s - };* S1 N$ j' l* k' } ?
- FILE __stdout;
0 i4 K; L" G& u - FILE __stdin;
3 a6 V5 F4 n# i- m5 m - int fputc(int ch, FILE *f) {* u" O% ^" t. T8 c6 p/ Q3 a
- return (SendChar(ch));0 |) l8 e2 b( {6 x( R
- }% X4 q6 F1 e3 r3 s: r/ m3 I
- int fgetc(FILE *f) {
8 G1 i9 t! a& N7 A( O: y - return (SendChar(GetKey()));% J' I! e# g1 K1 w' v$ J/ f
- }$ J$ o1 U# \) r; c6 e4 T
- void _ttywrch(int ch) {
3 ]! Y4 i( v' h - SendChar (ch);
9 ?: n; W0 t3 l0 T+ f/ J - }
. a* _4 S( Y* [ - int ferror(FILE *f) { // Your implementation of ferror
6 l5 ], R; ?5 `5 Y& S - return EOF;. h3 k! S' d5 G3 A
- }
/ T) S1 u p) O5 n, F: z - void _sys_exit(int return_code) {
% Q5 D& D0 ]" _7 ` - label: goto label; // endless loop
0 t. J! j( }# e - }
复制代码 3、在main文件中添加定义以下两个函数$ m$ C3 Q+ X4 R4 B/ \. W# W
- int SendChar (int ch) {1 x* Z6 S) E8 E( Q2 g
- while (!(USART1->SR & USART_FLAG_TXE)); // USART1 可换成你程序中通信的串口
]! ?4 R! u6 K0 A3 P7 D - USART1->DR = (ch & 0x1FF);
) ]$ ^6 T3 O+ U6 b - return (ch);
: G1 {) z, l( G' q2 J q5 @8 y - }
7 g7 u3 m4 C' c3 X - int GetKey (void) {- Y1 K, z: h M, B8 y3 D
- while (!(USART1->SR & USART_FLAG_RXNE));4 n% x4 h) ?' @" I+ r8 K
- return ((int)(USART1->DR & 0x1FF));
, I! M4 {, {0 p+ a8 P$ t) D - }
复制代码 至此完成配置,可以在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- #ifdef __GNUC__5 }1 Z3 b7 s! z
- " h: Z# a' \) w b* t; r4 W5 E
- #define PUTCHAR_PROTOTYPE int __io_putchar(int ch) y- o g9 q! g2 l7 P
- #else2 D: f3 G) Q5 J) M3 c
- #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f) @1 A) |) `" w# k8 u: z R
- #endif ; E+ Z$ T7 c% B- j
0 ~" K i& y0 n2 D: a/ U- PUTCHAR_PROTOTYPE
: a9 G1 z7 m, N& I! S - {, x% g2 u6 t4 V% U
- 6 q7 I/ `# J6 I2 G
-
?- h" {6 n& J - USART_SendData(USART1, (uint8_t) ch);& \; d! r* g% n) i+ Z- _0 i- f. H6 h
- 7 L& K8 p5 t% T6 t; t
- while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
" |! _. g5 }8 H. u - return ch; d1 p8 e5 d9 ]" D& x
- }
复制代码 因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& ?
- #pragma import(__use_no_semihosting) 2 ^ C* Q# `9 l, [# e
- u- E+ s3 c; E; D- f- B' q
- _sys_exit(int x)
1 U0 Z& D( s% A) I - { " k( U- `5 _1 j9 i- e
- x = x;
; y& f% a( @/ a3 @5 c - }% f) U: t+ g3 k* E: i! v. ]
-
' h$ o# \4 _, c, J c! I ^" N - struct __FILE 8 w( U* |: s& D6 u, ]
- { 2 Y/ ? l8 a5 C) M4 ~
- int handle; ; g6 U$ L* y0 b, x+ T4 j1 U
- }; $ ?% Q+ ~6 N3 D5 j% N+ x2 U
# F& N9 S3 C1 t- 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- #include
~( C. w4 j! {* K# Q
" p. u! M+ [1 D. V$ F: _- #ifdef __GNUC__ ]5 W& Q* h# a1 T
- 5 [) J' p. J$ r
- #define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
& c0 d) ]3 Z4 T8 Z - #else
4 r( q/ F: {1 T* F - #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
2 M; ~# z6 i$ l' f7 J( G/ Z8 S - #endif 7 p$ h* y! N D( C+ `
- PUTCHAR_PROTOTYPE" w& x2 ~- d) J, _; C
- {7 c* K1 I4 K" k+ r! r& T
- . P' z3 c( [: |
-
% m% q* k( ?+ c" R* I6 @6 N7 O - USART_SendData(USART1, (uint8_t) ch);
& k2 b% p/ z+ C5 i3 B8 } -
' Z3 j6 f5 v# ?8 _- m - while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);/ R0 V+ w* g! ?9 s) F3 Y
- return ch;
" c' M5 O2 {, d! [ - }
复制代码 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
- #include + @4 U- H) o% w
- + L* ?7 \8 ]& A: c7 S
- size_t __write(int handle, const unsigned char * buffer, size_t size)! X5 x1 U% N. }, i4 ~- o2 y
- {8 s( V: G. U4 i7 ~3 a% ~2 m- R
- // byte by byte write
% N! G8 P# k- f6 L. x - }
- e& Y/ n4 G3 A) J m - 9 W, e( w+ h$ p% B0 x
- size_t __dwrite(int handle, const unsigned char * buffer, size_t size)
/ ]# h$ _- @4 r; ?4 d1 B - {
0 f. r* V( c4 K - // buffer[ 0x50 ]6 l2 t# f( e& f+ Q( r$ Z
- }
复制代码 ) 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
- #define STORE_SIZE 807 E$ K ^- Z4 y6 l1 H
- 9 p' B! l5 @. u6 V
- static size_t storeLen = 0;
* N/ A8 S3 w4 l8 E - 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- #define LOG_MAX_STR_LEN 512! U# j$ l8 B* I* n3 _9 {
- void log_printf( const char * fmt, ... )$ \6 V$ r8 ^/ Z8 E/ c; x
- {) |( L0 Y9 m7 N+ t8 Y
- char log_buf[ LOG_MAX_STR_LEN ];! _2 Q; k$ ~$ c7 C& Y5 I+ a
- va_list args;
$ w f0 B% e, I1 a. K" `
; G* {7 F% _' G% J- va_start( args, fmt );5 A2 w" v0 j. m6 z/ |
- int count = vsnprintf( log_buf, LOG_MAX_STR_LEN, fmt, args );; p$ [& Q7 h3 e# G `4 k
- va_end( args );
6 Z1 {) }4 z4 @) f4 s - + i" G. B: p9 v# [# K
- // If an output error is encountered, a negative value is returned.
! U2 B6 z# T9 \& C | - if ( count < 0 )" B+ ?! w/ b8 G( s4 S9 U
- return;
: P( _" w' \: x- N- h' R# o8 e8 J' K - & w7 A _- E- h
- // "123456" [123456][0X] : count = 6, n = 8* y0 O/ Z5 B' x( [
- // "1234567" [1234567][0] : count = 7, n = 8! Q" Z# O$ T9 Z: I7 d& b# d, N
- // "12345678" [1234567][0] : count = 8, n = 8
. ^# s/ r, y& | q% \3 e/ S - // "123456789" [1234567][0] : count = 9, n = 8
& Z$ B% C9 s3 j2 J7 | - if ( count >= LOG_MAX_STR_LEN )
" \) l* d; _: |+ X3 p6 h, a3 W - count = LOG_MAX_STR_LEN - 1;
- T2 H3 b8 F, C( k1 F* m5 D) n - 2 \( t1 G0 u0 n) D; T9 F
- // now log_buf is C string with the terminating null character
3 b% d9 G$ m2 ~1 c - __write(0, log_buf, count );1 o ^: C6 |! Q, H' t- r4 n2 N
- }
复制代码 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
- int fputc(int ch, FILE *f)
: S' `8 B( q4 R - {
$ b3 R3 N& W$ _( E \ - USART_SendData(USART1, (uint8_t) ch);
/ B: ?5 J3 c- Q -
1 D: X) H# ~4 I$ o( s0 ^ - while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET)1 R# e9 e8 v" W. @
- {}1 C& |4 N* c$ C X) I
- return ch;; T! V1 J; P" Q! h6 h- G: i
- }
复制代码 通过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
- #define ITM_Port8(n) (*((volatile unsigned char *)(0xE0000000+4*n)))
+ r5 g% [9 e |! } - #define ITM_Port16(n) (*((volatile unsigned short*)(0xE0000000+4*n)))5 }) f/ Z7 r d
- #define ITM_Port32(n) (*((volatile unsigned long *)(0xE0000000+4*n)))% M! K1 v8 m( n1 S& `! b, p2 a9 @
- 4 C3 y6 B* c1 W
- #define DEMCR (*((volatile unsigned long *)(0xE000EDFC)))
4 W* l; B9 X8 F7 e( L2 s6 M - #define TRCENA 0x01000000
复制代码 2.通过如下代码将printf的输出重定向至ITM的Port 0
( D$ n& n; ~* G! f- int fputc(int ch, FILE *f) ; j. L4 k( X# S) F, N/ J
- {
2 u) [, J" h; J8 o2 w' l" t0 H - if (DEMCR & TRCENA) {7 t) W# e/ M- [% E* Y3 g+ ]1 h
- while (ITM_Port32(0) == 0);
4 n( Q, n* q7 C# I - ITM_Port8(0) = ch;
: L9 w( L2 D( b - }
~. o, ?! K7 C- J# A# C - return(ch);. t- ~2 ~# e1 y
- }
复制代码 3.通过printf输出调试信息
3 n: ?% L9 [" L W& k& M- 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 u0 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; N6 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 h4 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 c0 }. }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- #include
- C w" {" f/ \9 X: D6 \0 L( x. H% I - #include 6 J: o" `# {* G% Q% L5 p# e
- ; q4 G& S1 c; b+ j4 \+ W8 q: i" {
- #include "stm32f10x.h") i$ a y, d* p$ _9 L. J" v& A
& _/ o, `( `0 W$ B- #ifdef _DEBUG
8 m1 ~' p; V5 O% | x( d, S" y% H - ; T( ?: j e" E6 }
- int _write(int fd, char* ptr, int len)0 ^0 N4 D2 P9 `
- {
3 W8 N) ]4 O0 i4 C& i - if (fd == STDOUT_FILENO || fd == STDERR_FILENO)0 x( Y; C% [/ j4 N H8 [4 t
- {
0 W4 i& |; s. Q& e' F6 t - int i = 0; k1 z7 k5 J+ Z8 N9 `5 _
- 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 - ITM_SendChar(ptr[i++]);9 A/ z% n F9 j1 D: z2 K
- }, L1 S- ~, Z1 ]4 H
- return len;
: V4 o# V+ j. }) G* k* T6 O3 J - }0 U- A2 o* G* T" [
- - l9 s* Y; ?* Y: b- \% f) k
- #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 ^$ u8 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- void setupSWO(void)
4 g" K: e* p9 m0 h0 U. p - {# C( X+ d8 v) C* g
- uint32_t *dwt_ctrl = (uint32_t *) 0xE0001000;+ u* K1 d7 ^! q: H- A
- uint32_t *tpiu_prescaler = (uint32_t *) 0xE0040010;9 x& j$ u" G! \" _
- uint32_t *tpiu_protocol = (uint32_t *) 0xE00400F0;
, K, N6 z* j! Y& x" P: a/ S; | - 2 B3 f) ]5 _' G! W- e J, g8 H2 i
- CMU->HFPERCLKEN0 |= CMU_HFPERCLKEN0_GPIO;# Q8 N& l$ E2 N9 k8 g3 U3 P
- / y0 _7 ?0 H2 b l$ |
- GPIO->ROUTE |= GPIO_ROUTE_SWOPEN;0 E2 B$ V+ Y6 r* q
- #if defined(_EFM32_GIANT_FAMILY)+ o5 E' o+ C) l2 n- F7 I8 F# J
- ! M" W/ N; b8 s
- GPIO->ROUTE = (GPIO->ROUTE & ~(_GPIO_ROUTE_SWLOCATION_MASK)) | GPIO_ROUTE_SWLOCATION_LOC0;# N& Y4 d- R) d4 m# }
-
- g5 Q/ v/ Y. l. i i) z- V/ ^; R% V - GPIO->P[5].MODEL &= ~(_GPIO_P_MODEL_MODE2_MASK);& q6 w8 c/ A1 C- o5 Y
- GPIO->P[5].MODEL |= GPIO_P_MODEL_MODE2_PUSHPULL;
4 d. K* u; J- o - #else. ^8 ]( p6 H7 `' `6 G
-
: G" ^# c' F- ^6 j - GPIO->ROUTE = (GPIO->ROUTE & ~(_GPIO_ROUTE_SWLOCATION_MASK)) | GPIO_ROUTE_SWLOCATION_LOC1;
1 b3 t# ]% d1 i8 l# U9 V - , C: X ]2 |/ V& e* b& W
- GPIO->P[2].MODEH &= ~(_GPIO_P_MODEH_MODE15_MASK);
$ M- L" `5 I0 @ m# m1 i - GPIO->P[2].MODEH |= GPIO_P_MODEH_MODE15_PUSHPULL;
. z# o4 f1 k& ~9 w' g6 k - #endif
- n; ~+ N0 H! y6 \( i( @ - 3 Y( V8 `+ C7 E7 f6 U
- CMU->OSCENCMD = CMU_OSCENCMD_AUXHFRCOEN;
; i- N% i8 h6 H2 Z/ s$ i. U$ _ -
5 n6 ^9 _1 q6 y7 l - while(!(CMU->STATUS & CMU_STATUS_AUXHFRCORDY));$ u, z5 Y9 D$ L' b
-
" b- I3 u) P( f) ]5 I9 [9 q$ ~ -
0 v8 ?7 W, ^, ^8 Q4 e& |2 A - CoreDebug->DHCSR |= 1;
& Z. P/ n/ D# T8 Z" Y1 Q - CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
" G, h- D* s5 r. ~% [. ^. S - ; p( E w$ w! v- M
- / M# x! s( |2 O# K- K
- *dwt_ctrl = 0x400113FF;: Q; V6 @; W; q D
-
, g# w" P! `7 |$ H f, E - *tpiu_prescaler = 0xf;/ m3 A' t( V* @
-
4 ^3 b1 d/ N( } - *tpiu_protocol = 2;# u8 s b1 F) B* K4 v; k: t
-
7 b. P a8 [- n* `' a, t' Z8 o - ITM->LAR = 0xC5ACCE55;9 b) y* x- A) B E4 O
- ITM->TCR = 0x10009;
! n$ L! [, J9 u8 \" e. f# M5 j - }
复制代码 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
- struct __FILE
9 |- [; O5 j7 h; F, n - 3 j/ D `9 d! _: Q7 F' Z
- { 6 d d( m# c9 ]
4 Q: H% r: l4 y( z b" o3 K) N- int handle; ; v0 L' x0 @* S# R. q$ n
- & ~( d- k0 P! Q: w U3 _
- };4 Y9 O: k% `7 D* c0 x. D- }9 C
- V6 Z9 s2 T. ~- FILE __stdout;
2 Y) R, h6 @/ |/ Q - FILE __stdin;" r$ B3 F- m8 Z ~! X! q
- int fputc(int ch, FILE *f) ; ~! t @5 m7 Z" Q! W o
- 2 _; v/ V5 I8 f- O, R2 d
- {
6 L* J( v7 [7 u2 \! O - ITM_SendChar(ch);
& h, b5 f, h# @: o# x - return(ch);
, Q* I& V( r& y1 c. @0 b4 o0 i# K - }
复制代码 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
|
! [: q! k |3 |3 ^* i+ v
在群里看到朋友共享的GCC下printf函数实现的方法:5 F/ a0 K+ b- u# m h# r
后来查询得知,printf()语句里面最后加上\r\n后才输出数据,我之前习惯在前边加\r\n。
* _, J' x# q6 _* Z" h+ [) T3 A