在单片机中使用最多的通信接口基本就是串口了,说起串口就不得不提串口中最常用的一个函数就是打印函数printf()函数,在上位机上中这个函数直接从库函数中调用就可以了,那么在单片机中这个函数要怎么使用呢?能不能将这个函数和串口1对应起来,当然是有方法的。" b; [) B, g. k6 W4 P4 \7 M' ?
* k/ q! q& Q# H) m% A p
下面就通过代码来演示一下如何在串口1上使用printf()函数的功能。
, X$ h4 J% B9 f4 X! ~* i. |6 t7 {9 @
- void uart_init(u32 bound)
5 j0 ~4 k; M' a. t - {
, G0 `- z, A D9 }4 l2 E - //GPIO端口设置" l8 P& h$ o# W8 c; } g; W" ?3 Y* A
- GPIO_InitTypeDef GPIO_InitStructure;8 w. l1 T# v S: S
- USART_InitTypeDef USART_InitStructure;
* a2 h( ^5 N- x7 r* I - NVIC_InitTypeDef NVIC_InitStructure;
7 P5 X# u. L7 Y! \
5 T; ^3 \7 e9 Q- RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); //使能USART1,GPIOA时钟 M# V$ {# d0 {, N: _' W
- + g* V3 Y7 ]; A" a. A6 Y
- //USART1_TX GPIOA.9
4 K* N, b* {, B$ |3 w" A# f0 j - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
& c, S. Q$ ]" ]9 l/ J8 k) K - GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
# u: d+ n$ ~" r: v. w! |4 x - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出+ V" h0 d: A5 k+ Z* G
- GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.94 s9 Q; z9 k0 v! |& X' \) U; E) L9 r
. a7 M; v2 y5 ]7 K( J9 d- //USART1_RX GPIOA.10初始化
* n' }! w6 h# s8 ]* @5 W - GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
# k* d% u2 ?- A: c, l7 m3 C2 L - GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
3 p/ ]# Q q& u! O1 S z - GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10
; Z# z' c1 b k, I% R4 u8 w - 5 C* W5 e- ? @3 R6 d) j E
- //Usart1 NVIC 配置
0 p' y+ \# _& t! q: I. J* h - NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
$ n1 o3 m0 {5 D1 } - NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 3 ; //抢占优先级35 ?6 s7 E0 a& a9 N% e
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //子优先级3
9 L4 ?9 n7 R! z/ n& O* p9 I( o% [ - NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能+ I: {# w$ D: I3 w
- NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器0 W6 k5 |! }8 K1 t# k8 x3 N) I7 Q
- ( }5 ~$ }4 }: C) r, ^7 m6 a
- //USART 初始化设置
3 Z* n: }$ r3 Q0 C8 \& m - " k; O4 W3 p. c. t; @* R6 Y
- USART_InitStructure.USART_BaudRate = bound;//串口波特率
% Z5 ~+ i5 r3 n6 z7 G( z) H y- F6 T - USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式0 h+ L2 [8 t# x6 Q, l }9 o
- USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
/ _" P2 j! R, Z/ [6 d4 d$ n y - USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位8 V& F8 ^' P" A" M. a
- USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//无硬件数据流控制
" f9 x: }" r" ~- s0 H! F" x - USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式$ \8 @: l3 h& X
- F- E; Q" G6 g* V ~
- USART_Init(USART1, &USART_InitStructure); //初始化串口13 t' p" F( n& J2 M$ Z* V& K4 {, i U
- USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断5 \* m/ C X' c/ \
- USART_Cmd(USART1, ENABLE); //使能串口1
7 a' o* ~' B8 _8 Q* N2 [ X( } - }
复制代码
' |+ G/ `8 s* [% x4 T% l% z! I 首先初始化串口1所使用到的端口,将发送引脚设置为推挽输出模式,将接收引脚设置为浮空输入模式。这里串口使用中断来接收数据,所以还需要设置中断优先级,通过NVIC(嵌套向量中断控制器)来设置串口中断的优先级,接下来设置串口的波特率,字长为8位,1位停止位,无奇偶校验位,无硬件数据流控制端口,串口模式设置为收发模式。最后使能串口,并开启中断功能。! g" B4 {% b- _; r7 V/ A# i2 G
, I, k8 J3 A4 {$ A% G3 y7 d9 ? 下面编写串口中断函数,用来接收数据2 X2 Z3 x& t) ? [! V6 {, X
9 a. M! S( C6 i1 T) x8 `# d
- u8 USART_RX_BUF[USART_REC_LEN]; //接收缓冲,最大USART_REC_LEN个字节.; x2 I) t2 Z) L2 F2 X* l- N* E$ c
- //接收状态- F8 f3 R) N" k' K
- //bit15, 接收完成标志
; Z5 P( K( P5 q; j - //bit14, 接收到0x0d& X. b* Q7 p7 G! u0 G7 p" ?
- //bit13~0, 接收到的有效字节数目
4 }' I, V, I& V# X' p - u16 USART_RX_STA = 0; //接收状态标记
; X% F( x9 B, d
1 K) I7 l8 L, k2 i. T, X- void USART1_IRQHandler(void) //串口1中断服务程序
3 I+ [; a2 \ u% c - {# u) A* {; N; Q1 ~( h
- u8 Res;* p+ s2 s7 ^0 Q. f4 H! A+ s
- if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾)
& I8 y* Y. f) a1 x g" Q - {% ^7 c4 Z0 E* g: Q" a
- Res = USART_ReceiveData(USART1); //读取接收到的数据9 q+ M |2 S1 a6 P$ ?9 D$ T. n
% F8 u8 k( H: l& z5 U% h- if((USART_RX_STA & 0x8000) == 0) //接收未完成, v" o$ l# t$ u9 o5 \
- {1 W! R$ W; _( e$ k: N/ m) y* j
- if(USART_RX_STA & 0x4000) //接收到了0x0d$ G9 I7 C! W% z0 P& O
- {8 m" N0 e1 h. V+ L
- if(Res != 0x0a)USART_RX_STA = 0; //接收错误,重新开始- j; S' S F+ V& F. k5 r
- else USART_RX_STA |= 0x8000; //接收完成了4 W; m9 s3 o. l; Q; _7 [
- }4 a+ X! O3 m( Z3 r& y7 ?
- else //还没收到0X0D
7 H# b, Q# i0 O - {
3 x9 D3 D _# o6 s; ~2 Q/ Z3 A - if(Res == 0x0d)USART_RX_STA |= 0x4000;3 ?$ _2 ~, v: J0 u
- else% X3 s ]2 |. e; D
- {
4 h) [2 r: @5 b- F2 ~ - USART_RX_BUF[USART_RX_STA & 0X3FFF] = Res ;7 A; M" i4 ]2 C/ V$ B: h) Z; e
- USART_RX_STA++;2 F8 P8 a. k6 p+ K
- if(USART_RX_STA > (USART_REC_LEN - 1))USART_RX_STA = 0; //接收数据错误,重新开始接收
( i! j. \4 ~1 I0 g4 l# _( P - }
! T5 H/ f2 H4 J2 z; h - }2 m. O1 q* b7 G. E& c$ d
- }2 B5 ?' w4 Q- ^1 z
- }
: P: D3 M' F# k1 g- C% |% j# ^& z - }
复制代码
* ^+ \4 W7 U, W, N0 J 接收数据时通过USART_RX_STA变量的最高两位表示接收数据的状态,接收数据以回车换行作为结束位,回车换行对应的数据为0x0D、0x0A,只有这两个数据挨着接收到之后才表示一组数据成功的接收到了,这里使用USART_RX_STA最高两位表示是否接收到了这两个结束标志。8 s, b/ S$ U1 V% C
4 d s$ p& Y) y5 N" X2 c4 v: F+ y
串口设置好之后,接下来给串口1添加支持printf()函数的代码。/ m/ g9 D: E, J% q7 k
& m" h" Y4 a' A/ |6 ?( g/ R- #if 1
6 S. Q9 J9 q6 d - #pragma import(__use_no_semihosting)
% b, S6 T i9 X5 K; ? C; C - //标准库需要的支持函数1 V8 r5 @# K# _ F4 `# I
- struct __FILE, U% e- S* L6 f, C1 L- j: B
- {. ~$ ]: }* j' m' T$ y9 C
- int handle;/ s* s3 B6 `3 R9 q9 q a
- };
% u3 v& \' X. y' A, v& K$ r - FILE __stdout;5 i/ f/ |8 N% `3 h/ {
- //定义_sys_exit()以避免使用半主机模式
$ ?2 X( Y: F/ f - _sys_exit(int x)
) W, {9 l; D- S# C+ U2 j5 A! B* n0 f - {
6 \+ g. x& S: D, k' o8 v; U6 B - x = x;
/ l1 b! K- W4 S' A4 a - }
" y# E! y2 H" {+ N - //重定义fputc函数 x* s8 Z+ Z" R2 m& x4 R" _2 j- t9 C
- int fputc(int ch, FILE *f)
- _8 o/ ^& {- T - {
2 I4 R/ `" b; Z0 N9 j8 _ - while((USART1->SR & 0X40) == 0); //循环发送,直到发送完毕
8 d2 p/ i( A7 f: l4 T - USART1->DR = (u8) ch;9 d' B9 {5 ?1 K! T" R) I
- return ch;
+ V U9 M4 g! ?: P7 [; n5 y - }
& `- o- c4 h/ v - #endif
复制代码 1 u; i0 k5 M% P& r) I
上面的两个函数是固定模式不需要修改,自己需要修改的是fputc()函数,在这个函数中将寄存器设置为串口1的寄存器,这样printf()函数在打印数据的时候,就会通过串口1将数据发送出去,如何这里的寄存器设置为了其他串口的寄存器,那么使用printf()函数打印的时候就会通过其他串口打印出去。重定义函数fputc()设置好之后,就可在程序中直接使用printf()函数打印了。$ J, ?9 b R* E
+ U) j% l ~. b r0 K r- int main(void)" s% F5 p6 R$ A
- {( w1 z% ^, [3 L/ p7 n0 n. o, `
- u8 t;
4 W/ K j. y; r' A8 n - u8 len;& G4 ?5 q4 A' x# A; j3 B9 K# R, I
- u16 times=0;' l1 i, x; m4 T3 r
- delay_init(); //延时函数初始化) U2 _9 L; ?8 L
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);3 `' j/ ~: S: i- `6 x
- uart_init(115200);& _/ n4 I- g* U% Q4 T4 d7 B
- LED_Init();, i/ D: ]6 {, ?1 q: `! ?0 d- c, R
- while(1)
, s' r7 H! B8 c - {
1 x" Q8 F" b# `" ~, `; I - if(USART_RX_STA&0x8000)
8 T/ q+ I) R" C - {- |& k. X ?3 E( I6 C# x( k1 i
- LED1=!LED1;
2 P- H1 d5 p. g: z( y. L% r; U3 l - len=USART_RX_STA&0x3fff; //获取本次接收数据长度
+ K6 N" J% d% A- F8 k - printf("\r\n您发送的消息为:\r\n");
; y) w9 F" q4 Y0 m+ i/ `3 V8 i, E - for(t=0;t<len;t++)0 a# X! z# W. E/ x5 ^: N' J
- {
" b3 y0 ^6 h; E+ s3 a* _, S - USART_SendData(USART1,USART_RX_BUF[t]);//向串口1发送数据' ]' R7 B2 ?0 E, ]1 O
- while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待发送完成 & C/ _7 u: n* r+ b
- }
8 x9 s& d% L) \- P" P8 c- I- y - printf("\r\n");7 ?; D9 O7 ?1 S) g& a O
- USART_RX_STA=0;$ y8 j" i- ]) f; e0 ^6 g
- }
: ~ j# x& p, x' ] - else
# M3 q" O5 n* o. Q - {
, u0 p1 F) ? S: t! F7 c - times++;
$ k! t. {9 d0 e" `; O - if(times%5000==0)9 L1 K; C0 @ d* B5 ~
- {- Z" ~ @5 |6 C+ l" _% E
- printf("串口实验\r\n");
; x" b6 A% y+ F# S5 p - }, e' H" M# O( ]: B) a4 o$ n
- if(times%300==0)
; O0 {9 n7 l, g/ G! l1 ~1 M# ] - printf("请输入数据,以回车键结束\r\n");9 E# r' j4 I1 Q$ W# D0 ?) P
- if(times%30==0)0 \8 C! {7 y" G* d" Y
- LED0=!LED0;
% b2 \% Z% A* i - delay_ms(10);
5 C8 G! J2 {; j6 w5 B6 v0 K9 j) J -
2 Z P2 I& \" `! w, ^7 H - }1 D! _0 n! R' ]- r, K( L) E$ u
- }
4 @" J/ i. o2 `, A$ b - }
复制代码
, `- v+ T/ @: h @ 在主函数中通过printf()打印提示语句,提示用户输入数据。当用户输入数据并以回车换行结束后,串口接收到数据就会将接收到的数据直接打印出来。: y1 k6 M( g0 Z( e0 R
7 ?$ i0 H( u3 g- j0 T1 `! {
0 j6 \3 b6 _4 L& w2 y+ G4 o1 `
这样在单片机中,通过串口1就可以输出printf()函数打印的数据了。
4 j" j @9 Y) p( k0 h: u- o+ H3 v1 q# R2 O6 F! O
8 d: t4 Y* D* `" k6 o( t: w' X7 ?6 _2 G5 t2 s$ c0 R
|