在单片机中使用最多的通信接口基本就是串口了,说起串口就不得不提串口中最常用的一个函数就是打印函数printf()函数,通常使用这个函数都是直接调用库函数来实现的,在单片机中如何要使用printf()函数一般都是在串口中进行重映射。如要在串口1中使用printf()函数,可以使用下面的代码进行重映射。
/ A$ \! ?3 V# d* p6 E* _" Z" }7 i' S+ D/ v
- //加入以下代码,支持printf函数,而不需要选择use MicroLIB
. i3 j5 \+ w' j - #if 1
* u9 g) Z9 z, w F+ n2 r - #pragma import(__use_no_semihosting)
( @# l! }7 Y- w4 t4 Z- N% r - //标准库需要的支持函数; B1 x( u( \! t1 V% c# a8 F5 j" I+ a$ j
- struct __FILE
. `5 `0 t+ Z+ s# [# _ - {
' T4 t! l' t4 I" Y* [, o - int handle;
0 q7 S! `( Z% _; |) A# o, Q" w0 N - };) @( w$ y+ A/ x$ O& j
- FILE __stdout;
- ?" k( l5 e+ H. K4 }" Z; E - //定义_sys_exit()以避免使用半主机模式* A+ C9 D; [& q y- t% k
- _sys_exit(int x)
t" c5 q8 d( G& c - {0 K7 W2 D) |& [5 d: c
- x = x;
6 @2 D- H+ k/ L' q6 `1 P - }9 p' Y7 W9 }6 P
- //重定义fputc函数+ a/ v. c- j3 v
- int fputc(int ch, FILE *f)
5 y) W+ q* f; h2 N) X - {7 u' L* B( z* g. Z8 t" k- y
- while((USART1->SR & 0X40) == 0); //循环发送,直到发送完毕
5 M9 N3 [* F o' j - USART1->DR = (u8) ch;6 W# L+ U/ K5 ]* j& u% O R: e4 X
- return ch;( B- k7 h5 \/ l! ]- N- y
- }2 ^9 m% H; v& q( ~$ y
- #endif
复制代码 * y4 S2 Z% D7 N/ F, M$ u
在串口1的c文件中,添加上面的代码后,使用printf()函数时,就可以通过串口1来打印了。那么不通过库函数的话,自己能不能实现printf()函数的功能呢?当然是可以的,下面就通过串口2来演示,如何在串口2上直接实现printf()函数的功能。
/ E5 C1 D+ B- h- v/ V8 K) O" q6 M* ]3 F9 A, Z! y9 H
- void UART2_Init(u32 bound)- a: w6 `) r7 h6 N, r& _: {
- {
w/ [) Q/ o+ N, L - GPIO_InitTypeDef GPIO_InitStructure;
+ Y# p3 Z5 z8 t - USART_InitTypeDef USART_InitStructure;6 A, E3 z1 Z" G- k
- NVIC_InitTypeDef NVIC_InitStructure;
1 Q, j5 z: A; B5 _0 m& w. `) P
7 W9 e0 `! `7 V: {- // 1、串口时钟使能 GPIO时钟使能/ M3 R/ B5 ~6 q# q0 }
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
" ?9 Z* b, V7 r! M @ - RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);) A7 P0 B N) U% H
- 3 Y! v. |' u! W3 O8 V2 a
- // 2、串口复位! @+ B$ i. _4 K
- USART_DeInit(USART2);
, o9 h! B7 O. y$ ^+ C& l3 {
" j$ v0 h" h: ~5 {4 ^- // 3、GPIO端口设置, t' u* r" O1 X' ~
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //PA2 TX. D1 s5 u! ^3 [5 S' S7 O
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
/ l, _+ [$ M( {8 h+ B' x9 b1 J - GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;; ~* V* i1 H$ S# o0 @
- GPIO_Init(GPIOA, &GPIO_InitStructure);
6 S' }8 x* ?: X( c( t7 y1 R
; a# r/ A4 C" w1 |. \2 e; f' p- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; //PA3 RX
8 Z7 S, ]+ V" } - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入2 t5 T) Y% {* k( O
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;$ W1 C1 W. J# C: m# g E5 C
- GPIO_Init(GPIOA, &GPIO_InitStructure);
( K# D7 j. r8 P+ G" [ - $ z- o' F) m1 x, v$ T
- // 4、串口参数初始化5 w: z; y7 R, Y: P
- USART_InitStructure.USART_BaudRate = bound;
% w, r% G2 n1 z7 Y3 B7 F% x% @ - USART_InitStructure.USART_WordLength = USART_WordLength_8b;" U! C: b* U. h8 y3 U2 U
- USART_InitStructure.USART_StopBits = USART_StopBits_1;" g3 }+ C% E/ O
- USART_InitStructure.USART_Parity = USART_Parity_No;
% ^: @1 P4 m* }5 @1 \" _. I - USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
; [1 j, ?9 X ~ - USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
7 p, Y6 k4 n! M" V+ g( Q - USART_Init(USART2, &USART_InitStructure);
+ q: Y* q/ f, l( a - 9 i; b/ I) u! _
- // 5、初始化NVIC# G" B& u& g/ H) T
- NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
z3 p6 d0 x: H$ M3 H; t4 j7 \ - NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3;
2 H" M" H+ o. j1 c+ m - NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
# M4 Q1 j% C" }- H$ I9 A3 m - NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;9 v+ }; u* ~7 Z1 m8 L1 c% I( e
- NVIC_Init(&NVIC_InitStructure);
: x `" s7 i2 C" F9 ` - 9 W5 ~1 h' |! g k
- // 6、开启中断
4 h5 |- W4 f4 l, X* P3 N - USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
: C' B+ W" K9 s9 g - $ J; ]$ m5 B/ _7 G; \9 h- l
- // 7、使能串口
; t7 R# b, m4 c8 d( }8 { - USART_Cmd(USART2, ENABLE);# x3 H: E. _8 R' t6 n
- ; ]4 J: P( ?' a! N
- }
' I) z. W! M" F% q" o - void USART2_IRQHandler(void)
+ v! m# D" _% H/ r7 F$ L - {
, m& [( p4 n/ A. s - u8 res;7 ^3 o8 v* G3 x1 F- ~
- if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)
- w) t8 i2 _4 _1 P - {* V* {$ z! f/ j, {4 f5 {
- res = USART_ReceiveData(USART2);
+ v$ J5 q/ E o& L% p- q+ E - USART_SendData(USART2, res); //把接收到的数据发送出去
% m6 X; f* F6 C# E4 Z" _! d4 d0 p. i9 o - }
; X$ B W3 D3 Q8 a; Q4 x9 c - }
复制代码
4 }0 u! p9 U) F. d& s2 o. o9 _" F 首先初始化串口2,初始化方式和正常情况下一样,初始化完成之后开始自定义一个函数来实现printf()函数的功能。9 T1 Z2 k5 |: a7 j( K4 v9 m: m
" l$ k& Z6 f$ J" Z0 ?- p* {
- //自定义串口2 的printf 函数) q" q& A6 P, p4 w9 y3 V8 z" ]
- char UART2_TX_BUF[200];1 |: @1 z! ~6 |% _: d& V% K* ?/ S
- void u2_printf(char* fmt, ...) //无法列出传递函数的所有实参的类型和数目时,可以用省略号指定参数表
0 v/ Z0 c; ~/ ~# D C6 c - {; N& x5 F8 n/ h$ `+ Q
- u16 i, j; a" B9 N+ M, s5 p7 r
- va_list ap; //va_list 是一个字符指针,可以理解为指向当前参数的一个指针,取参必须通过这个指针进行。
( ^, g0 Y7 T4 h9 i+ \% T' S - va_start(ap, fmt); //va_start函数来获取参数列表中的参数,使用完毕后调用va_end()结束& j" L) ^7 A5 r! e6 O
- vsprintf((char*)UART2_TX_BUF, fmt, ap); // 把生成的格式化的字符串存放在这里9 t$ B+ ] j# F& w
- va_end(ap);4 L+ L, O: D7 w. `2 I
- i = strlen((const char*)UART2_TX_BUF); //此次发送数据的长度% d2 n( a) @: z; R/ f5 @3 D
- for(j = 0; j < i; j++) //循环发送数据: Q# i# b& G$ P+ L$ z+ c
- {
5 ~" |" I# }0 Y - while((USART2->SR & 0X40) == 0); //循环发送,直到发送完毕
5 x* e) d+ j/ B; L( B7 m) H - USART2->DR = UART2_TX_BUF[j];/ ]8 |. c* n9 A, j0 C9 T. R
- }3 A$ K# {- [" u j
- }
复制代码 & Z$ G. E z2 S7 i) z7 b$ \% ~
这个函数名定义为 :u2_printf(char* fmt, …) 使用省略号表示当前参数为可变参数。接下里就可以直接使用这个函数来打印数据了。在主函数中通过一段代码来测试串口1和串口2 printf函数的功能。4 `$ F) a# t+ m) O0 f' D( G6 x8 @' c% d
. j! c/ ~- e) g. C
- int main(void)8 P: p, L3 ]4 l- k5 @/ f* t% X
- {
8 z6 A# d# |6 c2 E - u8 t;8 \/ h& W! R0 |4 T ]4 ~- ^; ~
- u8 len;
6 p" [3 h/ Q) _* |0 g( z, v - u16 times = 0;( i. L9 \$ T% _* [4 I! n
- delay_init(); //延时函数初始化* U& Y5 U0 A% l+ H& ^1 Y$ t' P7 ?) T
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);- M7 |4 a* }4 j. a* n
- uart_init(115200);
6 b# T0 u/ F7 X( E/ n6 W - LED_Init();# u9 j# b6 o9 T6 G! L/ e7 r
- UART2_Init(115200);4 I7 Y. P$ @/ i: D1 V- _
- printf("USART1 AND USART2 TEST!!!");
7 X8 e( b' x: X5 N - while(1)
6 p* ~) j# ~3 T) F2 n0 @/ q - {) z$ I8 I9 U& f# q; A3 j, ~
- if(USART_RX_STA & 0x8000)7 p/ T1 O e7 w' K5 j
- {2 e; l0 x9 E4 Z- v: E4 ^
- LED1 = !LED1;1 [; ]; y- V; q% L+ O
- len = USART_RX_STA & 0x3fff; //获取本次接收数据长度
$ p7 G3 D2 |( i9 H: E' ^: M - printf("\r\n您发送的消息为:\r\n");
9 k$ }4 r* Z' _1 g, s - for(t = 0; t < len; t++)
" r% U7 r5 A/ ` - {3 p# N5 g! @" [! D/ s3 B0 @6 M- n
- USART_SendData(USART1, USART_RX_BUF[t]); //向串口1发送数据- V: Y' h* L8 q/ M. ^ `
- while(USART_GetFlagStatus(USART1, USART_FLAG_TC) != SET); //等待发送完成( K) Q$ o$ W6 d$ @" E; v7 x
- }) g' W- P/ E) a5 P3 p$ [# f
- printf("\r\n");! _9 Z. _' d" S8 E
- USART_RX_STA = 0;. E7 e- ?. [( N9 f- }# ]
- }. T0 L( U$ e+ D9 D+ d
- else
3 g) o2 E! a( a% G7 C - {
' M' e6 n- H' R, I2 W, k! w - times++;
# z" @0 h% M; n! {: F& X) o - if(times % 5000 == 0)
) [% A2 D6 z/ M% G - {
! @' E. u5 q5 n/ _7 `% V* t) a - printf("串口实验\r\n");
, d/ l" H: E* e; X$ ?0 B+ c - u2_printf("串口实验\r\n"); //调用串口2 printf 函数0 O+ V2 o8 x: b: H' _& V h: c
- }+ U) q1 E# S: d$ ~, {4 h' O
- if(times % 300 == 0)
) c- ?( [7 p, L- y9 I - {7 A! g1 A6 a2 f& V
- printf("请输入数据,以回车键结束\r\n");# \( o3 C& P; d0 D1 f. m- E
- u2_printf("请输入数据,以回车键结束\r\n"); //调用串口2 printf 函数
8 z3 t) ?' g7 b4 r5 I- X - }( ~: m1 J" \, z$ M$ n
- if(times % 30 == 0)$ M: [; }) O* }9 W( R# Q
- LED0 = !LED0;5 {3 N; C# k7 H" @! m6 b& k1 \
- delay_ms(10);& [7 s' U* n: h8 c6 a# r
- / Q: C" x& O4 `4 q) I, T
- }! P$ h2 e" f. S
$ N, S' C/ A, R8 K% z! k, z- }$ [! x' E V" S
- }
复制代码 $ ^' ?/ D# r$ b9 H T6 I) D
通过串口1和串口2输出同样的提示信息,然后用串口助手分别给串口1和串口2发送数据,当串口1和串口2接收到数据后就会通过串口打印出来。串口1和串口2输出的提示信息都是用printf函数输出的。7 ] \3 I3 f4 v7 t! X
5 ?- Z! X+ ~+ g3 s
2 Z% O7 N) R" ]5 f8 }5 |& j& B
! g: {* T5 v5 T. }# S9 ~
可以看出串口2的printf函数和功能和串口1的printf函数功能一样,可以正常输出字符串。也可以使用串口2的printf函数打印变量值,比如在代码中增加一句变量打印的功能。
, T! {1 [" ?7 K3 d5 h5 n+ f, s' v# G6 @
6 @' P2 I" r8 _) B/ H/ I0 X1 v1 t l# i+ D7 Y/ V- v
每次输出提示信息的时候,顺便打印一下变量times的值。8 r5 R+ q( h/ a0 s# F
e; |# ]* N( g
, O# {$ ~7 D8 i& O
( `! w7 `1 j) l3 W8 k$ ~2 w0 B
可以看到变量times的值也可以正常打印。( H. K" }0 _4 h7 e, w. e* @- t- {+ [
# r! B, h' _9 v! K/ o- d. L# e1 c" n0 O3 j) k f
|