|
STM32的串口通信
* S2 L! Q# u$ J) i
本文在于记录自己的学习过程中遇到的问题和总结,各种情况下串口通信在STM32的实际使用方面占有很大的比重,本文主要对串口通信做一个简要的总结。 2 C6 s( ~. I6 G9 w0 h
6 g! `; l% k6 `. {: U
一、STM32里的串口通信
" d; O1 D5 C4 l9 i
在STM32里,串口通信是USART,STM32可以通过串口和其他设备进行传输并行数据,是全双工,异步时钟控制,设备之间是点对点的传输。
% l4 N# ]( N8 ~# B% M, q
对应的STM32引脚分别是RX和TX端。STM32的串口资源有USART1、USART2、USART3.
6 |& l/ Z2 y1 s+ g# p3 l0 g
串口的几个重要的参数: 2 J! {0 u8 j, I' Q' ], }: q& F _6 ~
波特率,串口通信的速率 4 d M; U, z) T9 C$ m: P
空闲,一般为高电平
l- J/ w, O+ S6 W9 u: g G
起始位,标志一个数据帧的开始,固定为低电平。当数据开始发送时,产生一个下降沿。(空闲–>起始位)
6 b5 K% r; [% I
数据位,发送数据帧,1为高电平,0为低电平。低位先行。 0 c2 a& f& x1 ^3 C* [
比如 发送数据帧0x0F 在数据帧里就是低位线性 即 1111 0000 ) s! Z) g4 k q0 y
校验位,用于数据验证,根据数据位的计算得来。有奇校验,偶校验和无校验。 ; e$ P1 `/ R1 |7 j1 ]' L
停止位,用于数据的间隔,固定为高电平。数据帧发送完成后,产生一个上升沿。(数据传输–>停止位)
. }( o) {* {4 \# K, \5 U" @' e
$ W! ?8 A8 Z$ C+ g4 Y; P* m5 x- z7 ~
下方就是一个字节数据的传输过程,从图中可以看出,串口发送的数据一般都是以数据帧的形式进行传输,每个数据帧都由起始位,数据位,停止位组成, 且停止位可变。 . j; v2 p. P; Z$ @6 c. C
' R0 \! P+ L( i8 h7 g
2 O2 f% S" T$ t
3 l6 p; n; Y% |/ n+ d& i7 T
二、串口的发送和接收
7 O+ S# p' |1 X; a. |# |USART是STM32内部集成的硬件外设,可以根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可以自动接收RX引脚的数据帧时序,拼接成一个字节数据,存放在数据寄存器里。
* A; }4 o6 F* w; }8 h7 \, S2 H
当配置好USART的电路之后,直接读取数据寄存器,就可以自动发送数据和接收数据了。在发送和接收的模块有4个重要的寄存器
2 \& Y; C2 u! X2 S' w8 w
发送数据寄存器TDR / o6 e* R/ M" Q9 @: d3 B* F, t
发送移位寄存器,把一个字节的数据一位一位的移出去
5 j6 j* I6 c& r0 C0 N* X q- h, ?
接收数据寄存器RDR 0 E& Y+ X1 x4 D1 g+ s, I
接收移位寄存器,把一个字节的数据 9 o; S& Q# `& r" d
+ |& P; @& j6 Q, |: s; p4 `
下方为串口的发送和接收图解:
* ?6 L, y, d+ Y' a2 j- P0 {
5 t0 `* E+ r3 {9 }$ m
' q! W0 }3 J; ]# d! ]) {
串口发送 4 F* v& H o* Y4 v3 |; b' L
在配置串口的各个参数时,可以选择发送数据帧的数据位的大小,可选8位或9位。 : @& {2 ]7 ^9 @. i
串口发送数据实际上就是对发送数据寄存器TDR进行写操作。 + l9 [- U4 }8 K F8 n& k
1. 当串口发送数据时,会检测发送移位寄存器是不是有数据正在移位,如果没有移位,那么这个数据就会立刻转移到发送移位寄存器里。准备发送。
% J' ^: w9 l( Y& |+ _9 r0 {3 }
2. 当数据移动到移位寄存器时,会产生一个TXE发送寄存器空标志位,该位描述如下。当TXE被置1,那么就可以在TDR写入下一个数据了。即发送下一个数据。 3 C8 U; X# O% D/ |, L
# l# e* E1 h( F3 O/ K- l. F3 g: i7 _$ b8 V* ~" P4 U' R
3. 发送移位寄存器在发送器控制的控制下,向右移位,一位一位的把数据传输到TX引脚。 2 O, \2 b& n( C: `
5 U+ H) K, u! P+ B& a
* l4 o i4 \' Q4 m6 u
4. 数据移位完成后,新的数据就会再次从TDR转移到发送移位寄存器里来,依次重复1-3的过程。通过读取TXE标志位来判断是否发送下一个数据。 6 Y, v# E$ i* g, y" L2 V
串口接收 ! \& _ _! p& v
数据从RX引脚通向接收移位寄存器,在接收控制的控制下,一位一位的读取RX的电平,把第一位放在最高位,然后右移,移位八次之后就可以接收一个字节了。 ; D0 q/ T: {, }- z' Q
当一个字节数据移位完成之后,这一个字节的数据就会整体的移到接收数据寄存器RDR里来。
q$ u+ v/ N% m- x) h9 v! w
在转移时会置RXNE接收标志位,即RDR寄存器非空,下方为该位的描述。当被置1后,就说明数据可以被读出。
; s1 `% H- ^; e5 ^! R; @8 R9 w& v0 _
3 G5 L3 K' Z+ a$ W3 e
6 _, [0 J( d, M! p1 F
下图即为串口接收的工作流程 4 w+ y. D1 c* g
0 w8 j' S$ I) c! _7 g7 a! K. h; t G- F4 R/ R
5 }3 R2 t- }# u. L三、串口在STM32中的配置 - i1 d9 \: K' Y z
首先要明确几点:使用STM32串口外设中的哪一个?串口发送或者接收数据?串口相关的参数配置?发送或接收是否使用到中断? 5 {2 o7 `# \# Y& q5 i; \
下方为串口发送的配置。
# b5 d. s2 _$ G) a' a% t% a
1. RCC开启USART、串口TX/RX所对应的GPIO口 - RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE); //开启USART2的时钟$ m% F. _2 b6 Y+ `8 W- R
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //开启GPIOA的时钟
复制代码* C3 u" {# k! H1 ]! k
2. 初始化GPIO口 2 E0 J$ h* x% T3 B' a5 I
这里注意哈,根据自己的需求来配置GPIO口,发送和接收是都需要还是只需要其中一个。然后对应的根据引脚定义表来初始化对应的GPIO口。 8 b5 o4 J j! W( r
USART2对应的引脚
; f% {: F) L9 |
2 [; v0 z- z$ z9 u# k( w
+ e+ L7 c3 M2 W3 ~- U# z
USART1对应的引脚 % `) H, R4 \% A' ]. U$ I) D) m/ ?
0 k& s; C* h8 t9 @0 d ]1 q
4 h) Q0 A% l6 y) x5 o( L3 {
这里根据手册来看,RX引脚模式配置成浮空输入或者上拉输入。TX引脚模式配置成复用推挽输出。 $ Y. w5 x+ n5 k- z4 z
**比如我这里只初始化TX发送端**- //TX端
3 i# R( }+ P' X - GPIO_InitTypeDef GPIO_InitStructure;
+ Q0 l2 P# m, g- H, B - GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; //复用推挽输出
( A+ O* q8 Q: N5 R4 W - GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2; //USART2对应的TX端为GPIOA2
: L4 e! v2 G7 m6 { - GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //50MhZ
, H( q+ O% D2 { - GPIO_Init(GPIOA,&GPIO_InitStructure);
复制代码+ @2 q `0 ~6 \( m: c
3. 串口初始化 . b0 x: j8 N* L
注意哈,USART_Init()这个函数,是用来配置串口的相关参数的。
0 x5 w# R$ S' ?0 L; i
USART_BaudRate 串口通信使用的波特率 一般是9600或者是115200,这里我们给9600
$ n$ B1 O6 C4 M }* U
USART_HardwareFlowControl 是否选择硬件流触发,一般这个我们也不选,所以选择无硬件流触发。 7 v$ Y1 ?# e# E: @) ]7 x5 x3 A
USART_Mode 这个参数要注意了哈,串口的模式,发送模式还是接收模式,还是两者都有 , B$ H" a8 E9 X4 t# F
USART_Parity 校验位,可以选择奇偶校验和不校验。没有需求就直接无校验
$ i# a) d. X3 j- I" ]
USART_StopBits 停止位 有1、0.5、2位,我们这里选1位停止位
! }/ p3 @& b6 H7 A, ]
USART_WordLength 数据位 有8位和9位可以选择 - //串口初始化
4 t8 `6 K& [* I& I4 U) Y8 e - USART_InitTypeDef USART_InitStruct;& U3 G7 v ]- C, K$ j) @
- USART_StructInit(&USART_InitStruct); //初始默认值
0 [) X( R* l9 c2 ]4 t9 v - USART_InitStruct.USART_BaudRate=9600;. G' ^% H* b% F4 `+ p
- USART_InitStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None; //不使用硬件流触发! ^/ g4 @; K; B9 H
- USART_InitStruct.USART_Mode=USART_Mode_Tx; //TX 发送模式0 E/ r& @2 K0 [$ M1 Q6 ^: f" T
- USART_InitStruct.USART_Parity=USART_Parity_No; //不选择校验
: p0 X" C& P/ |& ^- B* a; Y - USART_InitStruct.USART_StopBits=USART_StopBits_1; //停止位1位6 `8 ? j$ W7 e, Q; ]
- USART_InitStruct.USART_WordLength=USART_WordLength_8b; //数据位8位0 h) {% a, `& C O- }- Z, y
- USART_Init(USART2,&USART_InitStruct);
复制代码 5 W$ b8 K3 ]+ w9 B9 K% G' O
) _6 @* _* b4 z( K3 j8 ^% w8 h5 H
4. 串口使能 - //串口使能8 J" k4 y' i4 H; g2 k3 O8 _& T
- USART_Cmd(USART2,ENABLE);
复制代码
: }4 x8 j; i* L$ I
5. 串口发送数据
5 T, a7 N6 ~9 j5 y: O8 g% E注意哈,我们要判断TXE标志位的状态。0,数据还没有被转移到移位寄存器;1,数据已经被转移到移位寄存器。
A, d8 q7 F- w& P! G2 [7 `
当TXE标志位为1时,就说明可以发送下一个数据了。详细过程可看上面串口发送的解释。 - void Serial_SendByte(uint16_t Byte)# ]! [+ b D5 y0 m: x: V" N
- {
* y% `" h7 c j0 c6 A. t - USART_SendData(USART2,Byte);3 E) a4 j( A; G* S( H9 P) S
- //0 表示数据还未转移到移位寄存器 循环等待 1 数据已经被转移到了移位寄存器可以发送数据6 b4 ^2 R5 ^: B+ w
- while(USART_GetFlagStatus(USART2,USART_FLAG_TXE)==RESET); //不需要手动清零 再次写入TDR时会自动清零
% q7 b1 u- s; e( J - }
复制代码
6 o4 l: J# u5 `3 w/ @& p! B
经过上述五步的配置,单片机就可以通过串口发送数据了。 ; ^- o: J. R* a h8 L3 }! v
下方为发送数据图例,STM32向串口发送0x16数据
7 m0 q4 a! }$ ~, R
4 L# A! s2 v; t# }# u5 z0 ~
: A2 Y* a7 b: i4 ?
四、串口接收的两种实现方式 3 l0 d$ L6 y& D9 O+ u7 S
上方是发送数据的例子,那么串口接收又该如何配置,又要在串口发送的例子上做哪些更改呢?
; m, `6 X V E) s
这里我们可以通过查询或者中断的方式来进行接收数据的两种方式。 |5 B( K' U" Z3 F
+ \# M9 I3 s0 T
1. 需要更改的地方$ Y- G8 m9 [: W6 f6 ~
8 T! x m: d2 f% x: z 既然我们要实现串口的接收,那么就要配置串口RX引脚,在串口模式中添加USART_Mode_RX模式。 2 R7 i; j \& Q$ e7 a# }8 W9 ?) F4 Y
初始化RX引脚 - //RX端$ B5 m& \1 ^1 D j6 b
- GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU; //上拉输入
+ X8 T' h( F* s8 s8 T0 n x - GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3;: W2 t3 J( o+ E( q! Y
- GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //50MhZ
6 T3 G' h+ ^ G: K$ u7 m - GPIO_Init(GPIOA,&GPIO_InitStructure);
复制代码
' M+ w5 p U; K: q2 p1 u( s
2 p+ c: T+ x: {* z3 l
添加串口模式 - USART_InitStruct.USART_Mode=USART_Mode_Tx|USART_Mode_Rx; //TX 发送模式 RX 接收模式
复制代码 6 {; i6 g6 a. ?9 s7 V7 z
% Q3 }& ~1 e! K8 x3 W4 M( M* ^3 b
2. 查询RXNE标志位 : i: I1 c7 F1 o$ r9 ?$ e7 [' r
这里我们还是来看一看RXNE标志位的描述 $ {1 N- N8 y1 I# X5 b. i
4 ^* O' c! c( H5 [2 O! x7 _1 X, Q& r' T$ E0 K( z
上图描述,为0时数据没有收到,为1时收到了数据,数据可以从RDR里读出 4 {- U' V, k0 z0 L2 ?
所以在主程序里不断读取RXNE标志位,如果为1,表示数据可以读出 - uint8_t RX_Data;
& u2 y$ C x; z$ h% Q1 } - int main()
- k) h. A1 b \ - { ! y! s/ d) k; m2 M8 t7 m g
- Serial_Init();
4 C; k0 y/ P3 `/ ?4 i' T. J - Serial_SendByte(0x16);8 @* ~4 A8 ~, q) q* I# K9 X: _
- while(1). \" f. V; D8 h) w- u
- {
1 ?' a* v8 v. t7 |3 O8 i - if(USART_GetFlagStatus(USART2,USART_FLAG_RXNE)==SET) //0 循环等待 1 可以接收数据
, ?, _2 W3 q& x - {
3 y& l2 h: S) D6 {. w0 }1 V1 p: b - RX_Data=USART_ReceiveData(USART2);
" r# R( ` `2 E8 b' a - Serial_SendByte(RX_Data);
$ @# q2 U5 {! O - }5 e2 X* M D# M! V$ L; Y+ n
- }
4 q/ h0 f7 M: g& S - }
复制代码
4 E' ` x" C `2 \3 s
下图为程序现象:pc向单片机发送数据0x15,单片机接收数据0x15,并且把接收到的数据作为数据发送到pc,在pc上显示0x15。 ' D0 u3 }9 d" w
: i: \; @/ e, Q* t8 l% y0 }- t
; w1 w! l0 H. d% g" @. x
3. 使用中断 - M: c+ \# `2 D1 k& a! n3 C
- //开启中断输出控制5 \* j1 b1 d x4 R! g& m' I1 X
- USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);
" O6 K8 [& p" Q F9 W - : K O9 p+ {0 Z% u% I( X7 f, O. L
- //配置NVIC7 b0 j9 W$ G& e$ h5 X. W& Q
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
1 }6 U6 P2 f! q - NVIC_InitTypeDef NVIC_InitStruct;
, C2 B' S3 B6 Y; x8 g* _0 w - NVIC_InitStruct.NVIC_IRQChannel=USART2_IRQn; //选择USART2的中断通道
9 c" w [* \3 I( f$ X - NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE; //中断使能2 h t! Q' |0 u S) m; Q! ~
- NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=1;
F$ B+ w! o, ~- }6 e; ?4 A - NVIC_InitStruct.NVIC_IRQChannelSubPriority=1;% o3 e# P. n# T) T" O$ a9 d
- NVIC_Init(&NVIC_InitStruct);
复制代码! j: J* I$ L* D" L" j
中断服务子函数
3 T/ y0 {& l/ T Z7 d0 C
/ J4 R; O3 M. `! {中断服务子函数写好后,就可以在中断里读取接收到的数据了。
) l Z4 k1 l- ^/ L5 a# y0 j
当接收到数据后,触发接收中断,主程序暂停执行。接收完数据后主程序回复执行。当接收到数据时,就触发中断。 - void USART2_IRQHandler(void)
1 w& N2 F" P) E) ^ - {
" u' L: Q' \2 q4 ^ - if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET) //RXNE 标志位为1 表示可以接收数据6 S7 D3 c2 e" v5 Y2 }
- {
( w7 R3 d5 Q9 N" y$ u/ S0 R - RX_Data=USART_ReceiveData(USART2);/ N# ?" @) X7 S9 i" l+ ?
- Flag=1;
; C6 g" `! z: ~ J - USART_ClearITPendingBit(USART2,USART_IT_RXNE); //清除RXNE标志位
. O% p/ \2 d5 _, J6 p' w - }4 G! {: h9 J# A' H8 p, N; X/ s/ S
- }
复制代码
" k p P" A1 B/ E
主程序测试
$ T9 N% g5 w6 F5 c1 J
- uint8_t RX_Data;
$ H# ^, |* r# v - uint8_t Flag;) s0 A% G+ ]2 n0 t; T
/ P3 l" Z2 z7 B" q8 W5 |- int main()9 p7 [; o# ?+ e d4 u
- {
' k @* q+ R& o- s+ _ - Serial_Init();
4 L. \6 T1 B0 W - Serial_SendByte(0x16); |1 N$ T ^7 j# F5 u& e6 u
- ! v% W3 ?4 u' w
0 j7 m# _, L7 t" {& L9 i- while(1)
+ v) x9 i" H( g - {3 Y6 Y/ j( [" i! _+ [+ V. X
- if(Flag==1)
9 A: I" q' D T3 s- j0 _3 @ - {3 Y; D6 {5 E6 I3 B6 {
- Serial_SendByte(RX_Data);
7 r' P$ V3 I( @1 K4 B - }8 V: i0 I( ]7 P3 n1 A2 X6 s
- }
. [1 Z- N) H0 T) z+ s. u; u9 y - }
. m7 h. y0 q2 E) K" r
5 f) W p4 w9 y- void USART2_IRQHandler(void)
1 p2 @: L: P3 ?! T3 J - {5 F& f9 K% m8 N; m" H3 G% |3 H8 H+ m5 T
- if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET) //RXNE 标志位为1 表示可以接收数据* D( I; O7 }7 K
- {
. v% D. x$ Y5 Y; @ - RX_Data=USART_ReceiveData(USART2);; ~2 y' b% f" z# l+ F6 H ]
- Flag=1;" a! E2 g5 @4 ?! L
- USART_ClearITPendingBit(USART2,USART_IT_RXNE); //清除RXNE标志位
; U. J" _. |- [" | - }
# \ @2 X R: a' p: u - }
复制代码 . I U( w- g+ L( E9 S! ]7 ~
0 ? O @" `$ ]# f
下图为程序现象:可以看到,串口确实收到了数据,只是我把接收到的数据0xFE放在了while循环里,这说明数据接收是成功的,使用中断是可行的。
# l' a/ L# X1 t
/ n7 F* A& e. ?: D9 f! w# t5 E
! n% I" q, a( I4 ~2 P5 [
1 n1 a3 h. W; h* ], n) y" U# i总结
+ P; }' e6 `* {0 L" u5 r
到这里,就大致总结了串口的发送和接收。 ) t% |* P3 Q! b/ Q7 {4 n( `6 \% N
串口的配置,使用查询或者中断来接收数据。 & }& g% W4 p; G
串口的使用会很常用到,所以在这里对串口做一个总结,也算是对之前知识的一个回顾和总结,加强印象。 5 m6 Q, g5 V# C* C$ s; d3 u
转载自: 古月居 如有侵权请联系删除
- t: u& `1 T0 i% W
8 ~ `2 Z0 q/ h. b! [& r% H0 G |