在单片机中使用最多的通信接口基本就是串口了,说起串口就不得不提串口中最常用的一个函数就是打印函数printf()函数,在上位机上中这个函数直接从库函数中调用就可以了,那么在单片机中这个函数要怎么使用呢?能不能将这个函数和串口1对应起来,当然是有方法的。
( o3 J# I1 m6 d. R5 P! i
) @+ T9 a2 B+ ~ 下面就通过代码来演示一下如何在串口1上使用printf()函数的功能。+ c/ s3 J6 c8 i" K
) Q6 q8 C9 F$ D- U0 Z/ B$ B- void uart_init(u32 bound)2 M7 U6 d/ u x2 L9 m
- {$ t( a) v; I3 l O; U W
- //GPIO端口设置
/ F4 c! q' l$ Z8 @0 Y - GPIO_InitTypeDef GPIO_InitStructure;
& _: u9 w# g9 X. A! q - USART_InitTypeDef USART_InitStructure;; a% p9 w% @4 G' w) i
- NVIC_InitTypeDef NVIC_InitStructure;
+ m/ x; `) U$ H: S8 T, O# K
1 i% N9 a' i8 ~1 K' b4 O, t' V% \" H- RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); //使能USART1,GPIOA时钟
6 ~, ]$ Y j; E0 |: Y2 t' p! p q - / M) e) ]+ S' t) L, j, V
- //USART1_TX GPIOA.93 J& |, s0 r9 r, F7 R! ]+ C' i. ^
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9- l0 I: l7 H9 I6 L0 A {8 j
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;8 Y8 f. O8 x4 y$ t0 L0 Q
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出5 j& q, N7 I; L
- GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.92 p8 p% M9 t& q/ o& }
- ) x- f: G* r6 A3 s$ h2 B
- //USART1_RX GPIOA.10初始化
/ c9 P* o# h0 g1 w4 u - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
6 D: G2 j1 ^0 Y7 p; S4 w) Y - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入# P4 |9 n9 T* _
- GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10
; `) s2 |& G0 c8 s - ; } N! G; @' g( S2 S2 L
- //Usart1 NVIC 配置
8 @0 h/ C1 o j4 a: R* F - NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
: j6 ?# f& `# \, M' @% N - NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3 ; //抢占优先级3" Y+ X, E8 L0 ?, I( S
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级3
( A, Q6 V4 R( ^6 W2 b0 @ { - NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能- Y) h! t. T. i, `9 y' z
- NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器8 H" x6 ]: a) Y. s# [6 A
# m3 s1 D$ i) H8 F* Y- //USART 初始化设置% p3 ?( ]5 L. K4 ~) J5 {
- 6 R3 q$ V8 {7 ~1 |4 J
- USART_InitStructure.USART_BaudRate = bound;//串口波特率! ~7 p6 @/ I8 d a& F) ~
- USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式8 g" ` T* W2 k+ P
- USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位+ }3 J( s: b/ d0 x% w
- USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位 v5 u6 X0 P, H, |5 T7 b/ Y
- USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制+ ]* h7 h9 A2 R V! q' _5 S
- USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式
# h5 v; B) P8 x) l i$ g
! b. @6 s/ q2 G S. @+ p- USART_Init(USART1, &USART_InitStructure); //初始化串口1
1 p% S3 I3 I; p0 D% H! q1 T! z - USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断
5 f3 f( g6 I3 d+ h3 y - USART_Cmd(USART1, ENABLE); //使能串口1
2 Q: M; _" k9 _ - }
复制代码 4 x9 Z% J, h9 _
首先初始化串口1所使用到的端口,将发送引脚设置为推挽输出模式,将接收引脚设置为浮空输入模式。这里串口使用中断来接收数据,所以还需要设置中断优先级,通过NVIC(嵌套向量中断控制器)来设置串口中断的优先级,接下来设置串口的波特率,字长为8位,1位停止位,无奇偶校验位,无硬件数据流控制端口,串口模式设置为收发模式。最后使能串口,并开启中断功能。
5 Z7 V1 d. z3 U1 h: U5 }8 u2 b$ a& H
4 \" L6 p7 I$ \& h4 Y 下面编写串口中断函数,用来接收数据
8 c! [& Q- t, k1 M$ o; _' D' y l3 z$ Q: Y$ {. [
- u8 USART_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.( `! ?" w) {8 u( o1 U
- //接收状态
! T/ u7 M6 ]" O+ C% t, @ - //bit15, 接收完成标志
0 `3 B8 G/ T) h4 q9 p - //bit14, 接收到0x0d9 ?& ^. A( O( ]
- //bit13~0, 接收到的有效字节数目
" m0 h$ D1 X1 I- S5 ] - u16 USART_RX_STA = 0; //接收状态标记! r8 c+ ?% y2 ~9 D) y9 A
7 A: N7 L: @5 E' |3 Q6 c- void USART1_IRQHandler(void) //串口1中断服务程序. |5 f- d- E) D3 a# R1 Y8 k+ i
- {1 y$ R4 }1 T" j+ D. f" a* v
- u8 Res;! _3 F( f; P k$ U; O
- if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾)2 l, k. d- |3 L7 U' a
- {
: E, N: D/ B, [) x2 O: G - Res = USART_ReceiveData(USART1); //读取接收到的数据
, H3 a, ]+ [. a6 F. G - 4 }5 G1 b6 A3 r* N
- if((USART_RX_STA & 0x8000) == 0) //接收未完成& A6 p! v- k( m; e" r8 \5 F( l. W
- {! K. d- m0 {9 X. ^
- if(USART_RX_STA & 0x4000) //接收到了0x0d5 V! I i. v& P k2 c6 H. A
- {* H& z3 k0 v" j @5 t% h4 D
- if(Res != 0x0a)USART_RX_STA = 0; //接收错误,重新开始4 g* t: v4 m' Y
- else USART_RX_STA |= 0x8000; //接收完成了# |: o4 g7 o" Y# i4 o1 I! w, o
- }
" f3 B+ o* M. J. m6 R/ G8 N! j) f - else //还没收到0X0D
; y8 O4 c* @6 K - {
: g- F& f X) Q- \ - if(Res == 0x0d)USART_RX_STA |= 0x4000;
' k/ w! M v! C1 W/ e/ }4 M - else9 a0 y) _: h) r/ }& z& y& m* K
- {
' ?8 k. r$ C" _. ^) F5 i0 w2 s q% K - USART_RX_BUF[USART_RX_STA & 0X3FFF] = Res ;2 z9 N9 s3 i* C
- USART_RX_STA++;
, U' c; J9 h U8 T& { - if(USART_RX_STA > (USART_REC_LEN - 1))USART_RX_STA = 0; //接收数据错误,重新开始接收' X: S5 u4 j C+ D1 J7 z5 t
- }1 c6 z) H. [- j' K- M' K! Y" D- @
- }% {0 F8 X: J& k- ~8 I; b' G
- }
9 {4 q3 G6 R$ v* E8 _, y - }
% ~, q4 \; r- Z; x. V - }
复制代码 # A7 w) R9 G& p: D- W" L
接收数据时通过USART_RX_STA变量的最高两位表示接收数据的状态,接收数据以回车换行作为结束位,回车换行对应的数据为0x0D、0x0A,只有这两个数据挨着接收到之后才表示一组数据成功的接收到了,这里使用USART_RX_STA最高两位表示是否接收到了这两个结束标志。4 T Z) x% F8 ^' F5 B$ [
! a# @4 @! y, r1 M) E/ d. {4 ]
串口设置好之后,接下来给串口1添加支持printf()函数的代码。
/ b/ b! x* S& M V6 \( q/ {$ j5 D6 G6 l3 d: b& H2 a
- #if 1
/ {* @2 o8 E) D+ R4 C, W - #pragma import(__use_no_semihosting)
3 _0 E4 G9 l& b5 s - //标准库需要的支持函数
3 x3 o$ y/ V# _6 R8 z# K) w/ ]* s - struct __FILE9 j7 M% |- u; ~
- {
' p* c! Q& W" c W/ D) ~ - int handle;
: d9 O9 E. [ ]6 u2 O4 @ - };
$ W' J# R. h1 W1 b9 {6 O - FILE __stdout;
+ h1 i; T Y2 V/ f3 _ - //定义_sys_exit()以避免使用半主机模式0 i/ ~2 `# ?9 @4 X) f, Z4 {$ `7 s
- _sys_exit(int x)! R/ r# L3 v: m. H8 a
- {
' J4 s* n! ]3 J7 y - x = x;4 A7 F) q; _: V* G' Q. v
- }
$ g" y- |% y. J, Q - //重定义fputc函数- p5 O# E5 d" V, W$ J+ S5 R
- int fputc(int ch, FILE *f)4 P9 O K2 j6 X# `5 w
- {
6 Z2 ]- y0 Q) u: V* c2 S) F! k: A2 W- A8 o - while((USART1->SR & 0X40) == 0); //循环发送,直到发送完毕
/ `8 w) ?3 w* @ f9 m - USART1->DR = (u8) ch;
/ k2 i( T# ~, n9 l: R2 ]3 B2 b - return ch;
( l( ^$ f7 ?0 S) B W6 w. ~. i/ b, W - }
- z, ~0 h% [+ z6 C6 E - #endif
复制代码 L( J5 \, _" M0 x/ g0 ?6 R
上面的两个函数是固定模式不需要修改,自己需要修改的是fputc()函数,在这个函数中将寄存器设置为串口1的寄存器,这样printf()函数在打印数据的时候,就会通过串口1将数据发送出去,如何这里的寄存器设置为了其他串口的寄存器,那么使用printf()函数打印的时候就会通过其他串口打印出去。重定义函数fputc()设置好之后,就可在程序中直接使用printf()函数打印了。( @' _! {: K2 \. |( j$ h9 T
; u o/ M' P, t8 N e- int main(void)
9 w! ?% t& V e) d; o8 j } - {
2 X6 {, h7 _. i w9 m - u8 t;# A+ @9 l4 V9 p$ D0 e
- u8 len;
: g( s; t( b% } - u16 times=0;7 A9 u, {2 X+ e x5 u
- delay_init(); //延时函数初始化
# x. d' V# k% o# u0 Y - NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
6 Z; M$ c6 _6 h; D! m - uart_init(115200);3 p3 R5 Q* k+ _0 ]
- LED_Init();
- \) s- \ p6 \4 f2 r - while(1)
$ m z7 A$ f- ]" Z( \, M - {( x% d1 ]5 K/ k! |
- if(USART_RX_STA&0x8000)
. s6 J7 G! W# y1 m1 e6 A ^ - {
7 k- k c8 X& Z; }, x- D. B5 T - LED1=!LED1;+ [' o. ]) q/ s( f* s9 Q! E& F
- len=USART_RX_STA&0x3fff; //获取本次接收数据长度& t+ r3 k! X* Y
- printf("\r\n您发送的消息为:\r\n");' X7 n+ P& o' v# V9 A1 h/ Z+ Y
- for(t=0;t<len;t++)% D2 [1 q/ N0 L4 n' z# d* [
- {
% P1 B4 V: z$ X/ B - USART_SendData(USART1,USART_RX_BUF[t]);//向串口1发送数据
- }9 F, {* R$ o$ |" D- { - while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待发送完成
) ?4 j1 A9 x, N7 x4 w - }
. O2 Y$ |, i$ J I' J, n - printf("\r\n");
+ U+ m, ~( L+ s - USART_RX_STA=0;7 Q9 _0 N# \- p3 m
- }; U. a" j, l0 J5 S: ] }$ ]! \
- else
3 e( C! d8 S9 Z3 O& A - {
: @3 X% ?/ ?7 |6 I" u - times++;
2 E8 n- a% C' j- A1 N - if(times%5000==0)
# s: E& J0 `' a# T! F - { d# m( ]: {- B9 R" `3 t* }
- printf("串口实验\r\n");0 `6 H! I& V6 J W: g% Q2 G
- }
# x7 B' v) X1 |8 W* { - if(times%300==0)% o J2 F; `; F
- printf("请输入数据,以回车键结束\r\n");1 {8 U0 L) _7 T w8 k
- if(times%30==0)
, u2 h. o% ?- @2 o: M; P - LED0=!LED0;
* n- Y9 S: ~1 E: J& ]: @/ U - delay_ms(10);8 O+ o8 ~; ^/ H: I) s7 F" D
-
; X+ `( d' t* y8 H5 Z; |. Y - }& \1 k+ ]- d8 Z+ a% O# c! e
- }# v% Q* X; `9 g4 n1 O3 ?
- }
复制代码 + n* H5 t, j% M2 Q% d% p7 Q9 E7 W* s* a9 Z
在主函数中通过printf()打印提示语句,提示用户输入数据。当用户输入数据并以回车换行结束后,串口接收到数据就会将接收到的数据直接打印出来。
' w7 H! @. b) L8 E) L# O
. E9 p* J' Q: j
9 D- t5 w0 f6 G
/ n7 Q a+ V4 S( [: a3 a) p1 ` 这样在单片机中,通过串口1就可以输出printf()函数打印的数据了。1 f `4 H6 F T, ?) Q
* e/ T4 C d) f9 a( L! U) h8 ~
, J3 n0 e! ]# e; f9 {
$ \9 b, c+ b8 _! {1 K; l |