在单片机中使用最多的通信接口基本就是串口了,说起串口就不得不提串口中最常用的一个函数就是打印函数printf()函数,通常使用这个函数都是直接调用库函数来实现的,在单片机中如何要使用printf()函数一般都是在串口中进行重映射。如要在串口1中使用printf()函数,可以使用下面的代码进行重映射。
" w5 z9 v0 E6 q1 d, n F9 z& }/ T& e8 h$ q e* z, y+ V$ g v
- //加入以下代码,支持printf函数,而不需要选择use MicroLIB8 n4 e4 [0 w) v3 {9 A: T
- #if 1& G7 y. a0 M8 l8 N
- #pragma import(__use_no_semihosting)
4 s( o% o& ]( u2 T - //标准库需要的支持函数4 z$ j* O, u8 R1 z0 W
- struct __FILE+ l! z, |3 x% Q+ G5 y
- {
; Q% j+ h# [$ i2 d. O: } - int handle;* E8 M+ p9 y6 G6 l: G4 \4 _( b: ^
- };. b) D) Q/ O9 R# v
- FILE __stdout;
9 v: ^2 c9 l/ F w2 A - //定义_sys_exit()以避免使用半主机模式
& o' f9 { {6 a& P: Y+ t( G1 ] - _sys_exit(int x)
6 V4 U a$ o R( W6 q! h - {
3 b, Q: I4 f( W5 O$ ?+ @' O - x = x;4 ^+ G* R0 a/ D1 w& E: v- \5 j
- }
a4 E4 ^' j7 Q - //重定义fputc函数0 e0 B5 n0 Z& I: @& `
- int fputc(int ch, FILE *f)
A: F# I$ W. _: n2 c! C2 ` - {
: \3 N$ n7 x: K1 Q - while((USART1->SR & 0X40) == 0); //循环发送,直到发送完毕
3 }1 M# p# a. |3 r1 P - USART1->DR = (u8) ch;
6 z1 _+ ^( C) f! j Q9 W+ S - return ch;
( L {% \( g3 ` - }
+ S6 W. d. H0 x0 r* }1 g - #endif
复制代码 ' e; Y G0 \3 V6 z) [5 k+ u! C
在串口1的c文件中,添加上面的代码后,使用printf()函数时,就可以通过串口1来打印了。那么不通过库函数的话,自己能不能实现printf()函数的功能呢?当然是可以的,下面就通过串口2来演示,如何在串口2上直接实现printf()函数的功能。
2 `: I8 t. w& N2 t, f6 g: a0 Z; f
/ [) d1 A7 M3 r0 F6 y& i+ a- void UART2_Init(u32 bound)4 c8 p/ R4 a. {
- {
1 e3 O" y& |) R1 h, X* E! T, b - GPIO_InitTypeDef GPIO_InitStructure;
9 y7 b7 O$ V; N$ k' d& m5 J - USART_InitTypeDef USART_InitStructure; ` C+ j) L. I0 N8 h+ D
- NVIC_InitTypeDef NVIC_InitStructure;$ a6 b1 z. E* @; e! D6 \6 z$ T( D
& q% d! |' T( z& n$ ] s- // 1、串口时钟使能 GPIO时钟使能
4 X3 T1 R' L3 h - RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
3 H* C7 A- J# e% r, m) h2 p - RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
7 ~. `, l- B1 P% Z3 ]' ?( y* W
, n9 q q! W7 h) }9 W8 E- // 2、串口复位
, i9 s8 s8 @* W% |& A) t5 ? - USART_DeInit(USART2);8 V5 i# o: ^$ { @4 G4 ~5 S
. G3 F6 y) z' ^% ]" k- // 3、GPIO端口设置/ t1 _/ F# E% |# H' R
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //PA2 TX' u! R7 o7 s. M* Y( a( B$ _
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出& A- ]! f, \5 ?2 j) b
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
U* @/ j1 O( O% m8 r - GPIO_Init(GPIOA, &GPIO_InitStructure);! o# B0 R- Y6 Z. u- p
- " A" n c' M8 T$ W# |* m) }3 L/ t
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; //PA3 RX
9 p: |) C+ [; N$ ~' q, I - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
' ~: b) b" V& S B! J - GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;3 s+ O" X* _/ J# v2 J
- GPIO_Init(GPIOA, &GPIO_InitStructure);
) N; I% H T7 F6 u
2 }5 o, {7 d z s- // 4、串口参数初始化
+ d1 _1 T# N. ?2 a - USART_InitStructure.USART_BaudRate = bound;$ j* [5 u) q; d$ n6 Y4 V! N
- USART_InitStructure.USART_WordLength = USART_WordLength_8b;/ I( h) ?" N' y" c: n( s
- USART_InitStructure.USART_StopBits = USART_StopBits_1;
4 R0 `2 T2 R' o5 a - USART_InitStructure.USART_Parity = USART_Parity_No;: C" x) k5 W# T$ K6 H! M
- USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
* V# g3 c" ?: C) Q3 }$ J - USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;5 M% ], X# `, O# Y" G0 j1 |
- USART_Init(USART2, &USART_InitStructure);! k7 G0 h1 F" n1 F2 A
- - }7 ^6 x" f7 R6 g( f6 B
- // 5、初始化NVIC
! ~1 L3 x/ q7 m+ J/ t( R8 D. f - NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
; P: A! z+ W( w a( S- z - NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3; c" Q$ v m6 x
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
; ~2 X4 ]" U$ g. h - NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;5 I* s7 Z( \9 l i4 T4 |
- NVIC_Init(&NVIC_InitStructure);* y: a# q) D& @. F! h) E; ]
2 d9 A0 i2 n; X8 E4 H- // 6、开启中断
7 v# L7 R5 W8 H0 F - USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);- r( x* T5 y1 h. i D
- ( w& I0 ]5 k! O; v2 X) G3 K
- // 7、使能串口+ r2 Y. [- @6 n6 Q
- USART_Cmd(USART2, ENABLE);# j Q1 L5 z3 D+ I
- 2 ~: [- G6 T) z8 a
- }
, @+ D6 A- n5 c; Y' G& X - void USART2_IRQHandler(void)
. H: L3 N& U9 y! d. E( |" u/ |# k - {' X1 ^+ C2 M( }/ K. D; g! i
- u8 res;
2 q7 O4 I* C2 C1 g5 {/ f* Z - if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)
" K" l" L4 A+ t - {+ Q- I+ M+ k! D& M
- res = USART_ReceiveData(USART2);
* u+ E; d5 H5 ?# h - USART_SendData(USART2, res); //把接收到的数据发送出去
- {0 |- H. O: w6 A1 [, ]; t8 _" X9 N - }
* D" M# ^8 r2 y$ I. s7 M - }
复制代码
" s% L- a. u0 b" c 首先初始化串口2,初始化方式和正常情况下一样,初始化完成之后开始自定义一个函数来实现printf()函数的功能。
* [2 E2 w; _6 Y$ P3 _7 e9 k% E
5 m" n0 j9 {9 N& }- //自定义串口2 的printf 函数7 A6 Y6 w2 w% Q8 i* S
- char UART2_TX_BUF[200];
# d9 i1 T# t# R8 b+ M) P ~ - void u2_printf(char* fmt, ...) //无法列出传递函数的所有实参的类型和数目时,可以用省略号指定参数表
( ~4 A! Z% y5 _* { K9 P, b - {: H& M. o+ `: y4 ^; n4 n
- u16 i, j;3 E: n9 o7 b1 f: z0 ~( n( o
- va_list ap; //va_list 是一个字符指针,可以理解为指向当前参数的一个指针,取参必须通过这个指针进行。
+ [3 w9 a7 P* M5 w$ A - va_start(ap, fmt); //va_start函数来获取参数列表中的参数,使用完毕后调用va_end()结束' A/ R9 Q& v" P
- vsprintf((char*)UART2_TX_BUF, fmt, ap); // 把生成的格式化的字符串存放在这里& L; i. X/ A! E, z3 v
- va_end(ap);. k! P! u( Y# M: Y) r' Q. m- l3 S* e
- i = strlen((const char*)UART2_TX_BUF); //此次发送数据的长度
0 L! x) A& D q5 F, J2 W - for(j = 0; j < i; j++) //循环发送数据
6 t/ ]: \1 o2 M% [7 A - {0 A: N9 q- J C) g
- while((USART2->SR & 0X40) == 0); //循环发送,直到发送完毕
; P4 J/ t$ [4 s; O" j( M - USART2->DR = UART2_TX_BUF[j];) F. L6 V3 f. g; _+ I
- }2 F' ~/ J, P) E9 g6 O$ a, b1 I% x
- }
复制代码
$ t1 c/ ` g' c' C- z& A- L( w 这个函数名定义为 :u2_printf(char* fmt, …) 使用省略号表示当前参数为可变参数。接下里就可以直接使用这个函数来打印数据了。在主函数中通过一段代码来测试串口1和串口2 printf函数的功能。
" R; t, b% A/ L0 t8 q. d, i% a8 e
+ v( @7 t4 o+ y& x- int main(void)2 X- n2 e9 F$ ]$ Q U( }
- {
" }1 m. G) c0 h# y n2 _ - u8 t;: e2 g; x5 F- h. e6 K0 r
- u8 len;
7 Q0 O) O X! M. @+ R - u16 times = 0;/ a* ~7 T1 }: a/ u% r$ u P1 Z
- delay_init(); //延时函数初始化
+ G! `: d/ N5 k- Y+ n8 S - NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);- q- v* {' S( T% ^- H
- uart_init(115200);' ~8 o8 l$ l/ @
- LED_Init();
5 [7 {* J a! A% m {' f+ T5 C - UART2_Init(115200);
! j" I& u. h+ f! l1 m - printf("USART1 AND USART2 TEST!!!");
- [* l+ f" _; v( |- v: s - while(1)9 _' g/ z" m) M# g
- {! H6 w" t3 f1 g
- if(USART_RX_STA & 0x8000)
6 S- k* i5 R9 j. Z, Y$ j - {7 |7 Z4 E& h. T
- LED1 = !LED1;
: @. p6 e/ ]( B* E% V# e! u0 \5 | - len = USART_RX_STA & 0x3fff; //获取本次接收数据长度
3 R- l* I& Q2 }6 h9 l0 ]5 y# o3 J( x - printf("\r\n您发送的消息为:\r\n");
: w1 D0 j3 ?" }- } - for(t = 0; t < len; t++)& H, ]: u( K7 b$ M K. C8 U: R
- {* L+ K& p; O- {9 v# k; {9 z$ R; c$ O+ D
- USART_SendData(USART1, USART_RX_BUF[t]); //向串口1发送数据
$ T. W2 H6 {9 E$ n3 t( J" g9 }7 b1 d% r - while(USART_GetFlagStatus(USART1, USART_FLAG_TC) != SET); //等待发送完成" n& n0 v% W" I/ ^& J+ K: \5 Z$ N2 K7 f: W
- }
6 K: l9 A" l, a - printf("\r\n");
/ q2 y7 ~7 g9 `( _8 Q - USART_RX_STA = 0;
4 C0 m u1 A) [7 y0 l2 ^# n - }
# W/ `* i5 d' M* Q - else
9 {4 E; q& A8 q/ x- y - {
9 F' _( o9 b7 M1 D7 e5 B4 r - times++;0 c0 @7 B" h! p7 z5 `3 v! y0 h& K& b
- if(times % 5000 == 0)# c& {! h. I b( I* a) b" p+ i: e
- {
, O: W3 e3 i- ^: c4 }0 b' N - printf("串口实验\r\n");' Z' Y+ w- @2 g' r: q, _
- u2_printf("串口实验\r\n"); //调用串口2 printf 函数; ^$ G1 }* Q# A; T
- }5 R2 t' Y7 I+ y
- if(times % 300 == 0): I9 B; l! M# F, ?
- {' G$ e. q) e% C8 W* R
- printf("请输入数据,以回车键结束\r\n");( }( Q2 t* t5 E* C. G- O- I5 P: ]% Q
- u2_printf("请输入数据,以回车键结束\r\n"); //调用串口2 printf 函数
# K1 }. _4 X* ?- z) ?! \/ N - }
! E. e/ b+ s& O. X! ~: q - if(times % 30 == 0)/ T/ T K9 \! l$ [, [8 }
- LED0 = !LED0;- p4 u) g+ O! y! _2 X
- delay_ms(10);3 _: L/ q- |8 j* q- t' L5 e
- + ^5 e8 x+ j# d3 l2 D/ k4 D
- }
1 k8 h+ s4 \( I; R' G% a - 6 y5 {4 S1 c4 B; P6 r
- }
% t3 l8 G5 b# @; | - }
复制代码
# r( V/ N4 _ h) }1 g 通过串口1和串口2输出同样的提示信息,然后用串口助手分别给串口1和串口2发送数据,当串口1和串口2接收到数据后就会通过串口打印出来。串口1和串口2输出的提示信息都是用printf函数输出的。
9 O- X/ p6 `# q
a7 x1 Q9 L; K0 y8 u! @
; N* f* |* E" H) X
6 ~0 e; Z! ~4 [: D" f1 m 可以看出串口2的printf函数和功能和串口1的printf函数功能一样,可以正常输出字符串。也可以使用串口2的printf函数打印变量值,比如在代码中增加一句变量打印的功能。1 ^3 {0 L6 }- p9 T2 N
4 \# s- a: ^* n) E) j
: W9 I1 j' J& ^. e i7 @; ?0 \2 H) ?& l% i# S% `% E$ f
每次输出提示信息的时候,顺便打印一下变量times的值。
- S* l+ g$ O1 z. `4 q$ _. p! ]2 T7 l& Z, P
* H) Z9 u" P1 Z; g4 S1 e( T9 X1 U* C R) w1 t
可以看到变量times的值也可以正常打印。2 ^" ^) z8 K; t, ]
$ G& B P: r, L) k2 L! w) J" ? i
; s4 t5 N( k/ f! p* | |