|
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- // 发送数据
, a, B4 x) Q/ c - int fputc(int ch, FILE *f)/ A$ O i3 m& [8 T( N- U7 C
- {' l" f7 D- b- X3 p3 n+ f
- USART_SendData(USART1, (unsigned char) ch);// USART1 可以换成 USART2 等
2 _- T/ p! h Q7 C9 [ - while (!(USART1->SR & USART_FLAG_TXE));
& Y/ M) N+ Q; i2 b8 @# l - return (ch);/ h( C& ?3 E/ U; m
- }
, G6 x% D. f: S+ I) a1 b( U$ X - // 接收数据
( G2 x3 p: L: ] - int GetKey (void) 8 G( }; y- E# k
- 1 | x- U$ r( u$ _9 z q. \7 e9 s
- {
1 N& e6 c( X2 \3 p) _7 r5 O; O, Y! ~ - while (!(USART1->SR & USART_FLAG_RXNE));; _3 u1 z/ ~& `. n7 a, I
- return ((int)(USART1->DR & 0x1FF));
9 a6 H" S5 o4 l% H - }
复制代码 这样在使用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
- #include - h, @! w+ q) t7 h0 R7 ~
- #include 4 J6 L- `. e9 V# j
- #pragma import(__use_no_semihosting_swi)
, T$ Q! z+ m6 g% u2 o/ U3 n9 u - extern int SendChar(int ch); // 声明外部函数,在main文件中定义5 r D) [4 H; B
- extern int GetKey(void);8 V2 e1 l% E: U u( q
- struct __FILE {
+ r) {+ w8 u# E; ~$ d( k: i1 d4 \ - int handle; // Add whatever you need here 1 M. |$ r, C+ P: L
- };
, m' ]3 }7 c0 j$ g - FILE __stdout;$ M% V6 D& s1 Q: f- H; ]- E
- FILE __stdin;
- ]8 L. g6 x! R& l+ g+ P6 y - int fputc(int ch, FILE *f) {! I+ \* _' u) z, i, A( ]4 s& l$ A' q
- return (SendChar(ch));
5 K2 m9 E+ _% _8 X: X, }7 a - }
/ y9 T: K% q! m - int fgetc(FILE *f) {
3 B1 t7 v' W4 W; ^# S6 f Q - return (SendChar(GetKey())); S" C& Y) ^4 X6 b' [5 B
- }9 \: {4 I, [" n8 S0 \4 t9 ^9 r1 u
- void _ttywrch(int ch) {
) L* Z7 B6 {* Z( ?3 r - SendChar (ch);( ]+ k1 r, l7 ^$ O
- }" L! Y) E" J6 I
- int ferror(FILE *f) { // Your implementation of ferror
/ f9 v; u1 A" j O$ S0 d. a1 M - return EOF;
/ P8 W8 t- `9 S) s2 K7 S% @ - }- `! F2 S) B. T- I' s# G
- void _sys_exit(int return_code) {/ x2 Q+ e+ K& ^1 N, F
- label: goto label; // endless loop
* y$ G2 G# ?+ N- A! `4 c - }
复制代码 3、在main文件中添加定义以下两个函数8 ?! v3 A# z" S3 G9 q. p8 \
- int SendChar (int ch) {
# I- p' R+ C& P6 @' C* U - while (!(USART1->SR & USART_FLAG_TXE)); // USART1 可换成你程序中通信的串口
$ Y. Y, l) N/ T3 g% k7 g9 h - USART1->DR = (ch & 0x1FF);0 Y& W8 N9 h- Z3 q! p
- return (ch);
5 n' X6 r- v% o' L0 p J - }
* o) T0 G! U5 _5 Z+ N+ f; ?; } - int GetKey (void) {! x5 B5 n) K1 J; B& u6 L W |
- while (!(USART1->SR & USART_FLAG_RXNE));
) n2 }. F1 H. x& G ^4 y7 X* C- b4 A - return ((int)(USART1->DR & 0x1FF));
2 B9 a9 J* Y$ P: O& R - }
复制代码 至此完成配置,可以在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- #ifdef __GNUC__$ f- L9 g; j M" a
: h' ^& z& o9 A. f* o- #define PUTCHAR_PROTOTYPE int __io_putchar(int ch)3 \8 E2 v7 z% l2 M/ v+ B" @
- #else, S) ?, P9 o# E
- #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)6 k, B' X! o" q$ K, d
- #endif : u/ l. ~ W0 e% ?! S* S. W# Z' s
- + i# t6 r8 ~; O3 s" k; w
- PUTCHAR_PROTOTYPE9 q( z" U4 o# L0 v9 |+ a% G9 {: P
- {2 B3 v! ~+ [5 C. W3 P4 F
-
2 S$ `2 W! U" g; h$ L3 Z -
# d& F. T. {9 u1 p6 T - USART_SendData(USART1, (uint8_t) ch);! _2 e. H1 @* w( o
-
9 H2 K/ R7 [4 \ - while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
* @1 H' M; m) ?1 t, c) X - return ch;
/ i9 F2 M# r! M# z, t - }
复制代码 因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- #pragma import(__use_no_semihosting) * E3 P. H$ E6 p2 V1 e7 L( M
- . O. x, O( v# A- ?3 C1 @
- _sys_exit(int x) 6 B4 l! V9 K' { n+ d
- {
- p# w" f3 J9 t9 o7 |& d) t - x = x; 7 ] W4 f. v' Z1 ~* e- M1 d. Y9 e
- }
$ R1 V5 U, o( d. o: x- A' T - 9 |" H Z4 L4 ]' w: j8 @; @: U
- struct __FILE ! ]$ v6 a* C* o% m, c
- { 8 P! N- ?7 \0 d% T' S
- int handle; 4 x6 y% V: Y- [; Z: Y
- };
. S$ D) l. E: x% ]! i b8 n* r
5 k- z0 Z6 t& n L+ j2 A- 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- #include 9 G* Q) K2 w8 u7 m p9 w0 J
. a8 ?) T5 i* r; ^9 C3 P( l) j% T- #ifdef __GNUC__/ P) U3 d! ] w1 C5 |& o2 U9 k* ~
- 4 C' V; p; q, L- t$ q
- #define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
, G @5 ?7 J3 z! W - #else
- c% X; t% e# M b8 w" Q - #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f) p2 v# Z2 \# `) h5 y
- #endif 7 d5 G0 b! r; T( @- m! |
- PUTCHAR_PROTOTYPE
( U7 s9 N8 }# l: o* t# ]/ _ - {4 N" c& e- t7 _4 x! q
-
. U: d0 _; {! u, a' v -
# a7 H( U+ }8 }4 D& P - USART_SendData(USART1, (uint8_t) ch);6 J0 R$ G( i: h& T# p' t) M+ w5 ]% Q
- ) w7 y n6 t7 N( Z- H) @9 n7 f( V
- while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET);
( Q& _7 c9 a0 S' X - return ch;
9 ]4 a! P3 H0 f - }
复制代码
- }# B4 L0 Q# TGeneral Options -- Library Configuration -- Library : Normal < NO file descriptor support >
4 W; Q9 G! m1 u1 L! E& `- #include , Z7 H- ^* d& h
- 5 R, I% Z- X! q: j2 ~
- size_t __write(int handle, const unsigned char * buffer, size_t size)
G8 k Y& `" @% A7 l9 f - {
/ n+ y9 ?4 }! m# H - // byte by byte write
% u1 W- N! R$ R# { - }
& Y7 D# q3 I" J. c- Y' W
' w; i9 u- H \1 Z9 I9 i* S! M- size_t __dwrite(int handle, const unsigned char * buffer, size_t size)& v' p) n4 R+ X& q3 @% W( ]
- {5 b5 E i2 w7 T0 o- {2 X% n& G
- // buffer[ 0x50 ]& Y$ Z2 l2 J0 B" v9 d- o6 K
- }
复制代码 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- #define STORE_SIZE 80
% W* F, [2 j, B m! Q" |9 _+ ]/ q
2 Y$ r6 v+ J3 X8 E; U6 _' ]+ O) |- static size_t storeLen = 0;$ ?; n4 W- _4 D; p ~6 P; K4 Q: s
- 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- #define LOG_MAX_STR_LEN 512
l- W# j% p# n( |8 F( _8 U ^ - void log_printf( const char * fmt, ... )9 }- G" d, F Z) y' i0 U7 f
- {
0 Q7 ]" P( V. a' `% r - char log_buf[ LOG_MAX_STR_LEN ];
5 W, j1 X6 N" F% I# H" s9 Q5 { - va_list args;
3 h! P* [4 K7 i8 z3 P3 ]+ F( O - ! `4 Y3 _& L1 o) C0 _
- va_start( args, fmt );
3 b& a4 }. s) m5 U* o2 ^ - int count = vsnprintf( log_buf, LOG_MAX_STR_LEN, fmt, args );
7 g+ f4 g7 R" r' {1 a6 {( _7 r - va_end( args );+ R+ Y% i* Q3 N: p5 q6 }
- # `5 x+ d' h5 N! C7 D" t/ L
- // If an output error is encountered, a negative value is returned.
/ m `$ `/ H0 Z- x* b; Z" X3 T - if ( count < 0 )1 B6 i$ w; @- b0 b8 V6 H
- return;
3 E% ]) {4 T- R" D! b
) j# J# C! W: g- // "123456" [123456][0X] : count = 6, n = 8
5 ~( [# k% Y, z+ S+ A2 k3 [2 g4 F - // "1234567" [1234567][0] : count = 7, n = 8
' N* L5 h9 m+ R4 g - // "12345678" [1234567][0] : count = 8, n = 83 h6 K% Y# x" K! s: N" l
- // "123456789" [1234567][0] : count = 9, n = 8
- n( n+ a# X/ ~3 P2 m - if ( count >= LOG_MAX_STR_LEN )
7 u6 u4 W- g& _! H9 J - count = LOG_MAX_STR_LEN - 1;, |% f- D+ P% F6 C' c
9 J* b4 P% a; N- f) ~( J- // now log_buf is C string with the terminating null character
, a' l5 `% z# y, G# N# K$ B0 X - __write(0, log_buf, count );1 b% o% H+ Z5 U8 P3 a- c( A5 `
- }
复制代码 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
- int fputc(int ch, FILE *f)3 ]! Y8 n- M; p2 q; Z2 C% J/ N y
- {
* l- `( |& t [8 B- r: T - USART_SendData(USART1, (uint8_t) ch);( R3 j$ I- I( [ o
-
9 K& Q+ f0 @( k2 g* s' w6 E8 F - while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET)3 ]* U+ a" e, j I, r( I4 s; I9 k
- {}
! _) @; I5 h9 T! r6 v - return ch;2 ~' F/ ~' x2 y) I0 u- _
- }
复制代码 通过JTAG的SW模式将printf重定向至SWO引脚输出
( D5 U; R1 E: O" [$ v; E4 l0 z! _* }3 c& p$ u
1.在源码中添加对ITM端口寄存器的定义( f+ a7 Q% U0 C
- #define ITM_Port8(n) (*((volatile unsigned char *)(0xE0000000+4*n)))
* N1 n, ]& m) ~( g. ^1 }7 q0 ] - #define ITM_Port16(n) (*((volatile unsigned short*)(0xE0000000+4*n)))
, R- f5 w1 x4 s7 Y! O0 n' L - #define ITM_Port32(n) (*((volatile unsigned long *)(0xE0000000+4*n)))9 x3 Y! O; S2 n
7 C$ m* H! A* ]6 X3 l$ o- #define DEMCR (*((volatile unsigned long *)(0xE000EDFC)))* z/ P" F' i0 Y
- #define TRCENA 0x01000000
复制代码 2.通过如下代码将printf的输出重定向至ITM的Port 0
7 s$ q. k: E; \2 l- int fputc(int ch, FILE *f)
* Y/ R7 ~+ W) O. X1 [ - {/ d8 v w, U& t+ W, h' Q
- if (DEMCR & TRCENA) {
) T2 m& z& t. U+ f( y! w- A - while (ITM_Port32(0) == 0);
8 S7 W( T6 Y5 \; K5 |$ V - ITM_Port8(0) = ch;
. l+ C6 D3 R( }/ l4 i - }* j9 s% k. i5 X, O6 {, E! e+ a3 y
- return(ch);7 z! J( {- C4 v1 C
- }
复制代码 3.通过printf输出调试信息! @, U2 m$ f: I4 ~
- 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. r9 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 K9 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- #include
% V |, l$ o+ _1 ?3 B. t# z - #include , s0 p; C: S: h; l
- 4 O& H4 B3 `/ |9 P2 c$ y
- #include "stm32f10x.h"5 g- o Q) m( z" X" J( X
+ V$ p! W( P$ b/ B3 f- #ifdef _DEBUG& s6 E) Z+ G8 L
0 Q; A. {, q% M% Z- int _write(int fd, char* ptr, int len)
& J. _! U3 M4 C/ x - {( m+ @! n* o) O( H& T- @4 }0 V
- if (fd == STDOUT_FILENO || fd == STDERR_FILENO)
& z- c. j: M0 X7 y - {
; G8 C |% Z7 m- l& n - int i = 0;7 [' |8 f% i5 |8 `$ r
- 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
- ITM_SendChar(ptr[i++]);
" z7 d" o- U% x J& i `; e* s - }
% U$ r5 D/ n; i' o8 i' s$ t7 n - return len;
* m8 R3 D: q1 d5 b1 i2 _ O" ~, Y - }
$ w5 p! b2 ~ o( I$ J6 R
7 B4 N, X8 X3 K: c) y- #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, w6 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 }
- void setupSWO(void)
; R& E# _6 M: S$ y - {; @) d2 H/ n! V( Y
- uint32_t *dwt_ctrl = (uint32_t *) 0xE0001000;5 w3 P3 |6 Y% _+ X4 a
- uint32_t *tpiu_prescaler = (uint32_t *) 0xE0040010;
( N( {7 O; ?. c Q, p( z& k3 M - uint32_t *tpiu_protocol = (uint32_t *) 0xE00400F0;2 J- C" G" B7 Y3 l; \; Y \6 e
-
. W7 `5 ?, r9 V5 D$ D. l - CMU->HFPERCLKEN0 |= CMU_HFPERCLKEN0_GPIO;
: r- E% A% E. _3 I7 U; } -
! n/ g7 S; Y: ~' U, K: g - GPIO->ROUTE |= GPIO_ROUTE_SWOPEN;0 G& \2 l- {+ [/ _$ G( f
- #if defined(_EFM32_GIANT_FAMILY)- _- \ n) @# V/ y
-
2 M7 K1 r1 ^0 x+ C: S4 N# O - GPIO->ROUTE = (GPIO->ROUTE & ~(_GPIO_ROUTE_SWLOCATION_MASK)) | GPIO_ROUTE_SWLOCATION_LOC0;
/ \5 f' u k7 h' b" Z+ d( f - : @ o! p- r5 R" \: @ w: e
- GPIO->P[5].MODEL &= ~(_GPIO_P_MODEL_MODE2_MASK);
% _- F8 s/ I2 q5 n! p - GPIO->P[5].MODEL |= GPIO_P_MODEL_MODE2_PUSHPULL;
2 J% p/ w m+ \+ |% \" t* D - #else
9 Q, }- v+ Q- O) [ K- H - * A5 @8 h; D. a7 H
- GPIO->ROUTE = (GPIO->ROUTE & ~(_GPIO_ROUTE_SWLOCATION_MASK)) | GPIO_ROUTE_SWLOCATION_LOC1;
& G) \+ s( r* X' C7 B - 6 `+ y, O. o" K* S( {6 k7 e# R8 T
- GPIO->P[2].MODEH &= ~(_GPIO_P_MODEH_MODE15_MASK);# b: ]% T" {. t$ M% K/ z$ r" B
- GPIO->P[2].MODEH |= GPIO_P_MODEH_MODE15_PUSHPULL;6 B0 j. |* W& t& Z6 o
- #endif4 ~) u$ e" U" c" ?) m f9 B( |
-
9 r# y3 y+ H! u6 s. Z& C - CMU->OSCENCMD = CMU_OSCENCMD_AUXHFRCOEN;
7 u: A5 A8 ?1 X4 ]* J$ Y -
5 u! R$ a" y0 m, F% C - while(!(CMU->STATUS & CMU_STATUS_AUXHFRCORDY));+ [% Y$ S! a* P1 n/ k) b
- : M; B& U0 ~( r7 k0 V" @
-
' }- n5 ]* P- o& |, f4 e - CoreDebug->DHCSR |= 1;
8 q1 }2 l! s+ v3 z) W2 ~ - CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
: U( b0 N2 K8 w! t8 ^5 N' m/ a, p -
: X( Z. L+ H C# @( e9 x - : w; a2 N/ {4 R+ F( i
- *dwt_ctrl = 0x400113FF;: d. R. H; }+ A8 u1 d) g- d
- 8 u" c4 l5 ?& Q) ^: t% h! M3 N
- *tpiu_prescaler = 0xf;/ B5 \3 k& j9 A1 y
- - Z4 c4 _' B p% b( _
- *tpiu_protocol = 2;
" l% W0 |: R, w- E o* h0 y -
- k& S$ {7 b9 X. \5 I4 U; @) A2 u - ITM->LAR = 0xC5ACCE55;* Q9 G2 N4 r) t4 `3 s' ~
- ITM->TCR = 0x10009;0 [8 `- j- a( E! @; |
- }
复制代码 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- struct __FILE
: D7 I% B( M& N7 Z
( J8 e5 Z8 v$ T- {
* v: z+ V$ W# a+ @0 h - : c% ~ B5 U9 F! R( i- s% n
- int handle; 8 B2 v' R( Y- F
- ( ~% s( }2 J1 d" j; }
- };+ a6 E$ c' a+ y/ H! S4 B
- ) K# d) I t" ]) T- X
- FILE __stdout;
9 R7 u5 y- V" ?/ o/ b* O* { - FILE __stdin;) F! ^ B; j# ]- {9 r( i
- int fputc(int ch, FILE *f) . b1 O; M5 g* S0 u
- 2 e; o, c7 K5 I" p' w/ G
- {2 W) N3 G9 a3 k H) f
- ITM_SendChar(ch);9 s9 L- s& P) m4 p) o
- return(ch);
+ j, m# i/ G& E, u' B3 i - }
复制代码 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
|
在群里看到朋友共享的GCC下printf函数实现的方法:
后来查询得知,printf()语句里面最后加上\r\n后才输出数据,我之前习惯在前边加\r\n。, p9 h' k- V! R2 ]& w
2 Y) W. T, h* v