|
STM32的串口通信 * S8 a, R6 N2 c1 f7 W
本文在于记录自己的学习过程中遇到的问题和总结,各种情况下串口通信在STM32的实际使用方面占有很大的比重,本文主要对串口通信做一个简要的总结。 3 r( ?$ h: D" G) r) P
一、STM32里的串口通信 在STM32里,串口通信是USART,STM32可以通过串口和其他设备进行传输并行数据,是全双工,异步时钟控制,设备之间是点对点的传输。
, G! y' @, d S8 Y x' J
对应的STM32引脚分别是RX和TX端。STM32的串口资源有USART1、USART2、USART3. , v, R6 t# x# P$ `
串口的几个重要的参数:
; K$ y. y6 N2 n& x3 a% Q
波特率,串口通信的速率 1 T) w9 |9 o/ C# r% p
空闲,一般为高电平 0 }/ \7 D. A+ `/ s! h
起始位,标志一个数据帧的开始,固定为低电平。当数据开始发送时,产生一个下降沿。(空闲–>起始位)
( H: ^. c# r1 _. J
数据位,发送数据帧,1为高电平,0为低电平。低位先行。
7 P8 V) G( M+ t' L3 U" _
比如 发送数据帧0x0F 在数据帧里就是低位线性 即 1111 0000 # T0 V, m& y1 o; H7 A. s+ o
校验位,用于数据验证,根据数据位的计算得来。有奇校验,偶校验和无校验。
8 d3 T& c6 b) ~! @2 R, Y+ [
停止位,用于数据的间隔,固定为高电平。数据帧发送完成后,产生一个上升沿。(数据传输–>停止位)
6 P5 d* l+ X' T% X4 e% s
/ A3 y1 r# g& B; ?2 O6 X P
下方就是一个字节数据的传输过程,从图中可以看出,串口发送的数据一般都是以数据帧的形式进行传输,每个数据帧都由起始位,数据位,停止位组成, 且停止位可变。 0 P: g+ d; f! J
& E- {$ h" E. t- j& C
2 ]/ N; I# U0 s& T0 H* H, d/ W8 U I% z4 Q3 q9 X
二、串口的发送和接收 USART是STM32内部集成的硬件外设,可以根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可以自动接收RX引脚的数据帧时序,拼接成一个字节数据,存放在数据寄存器里。 ) t5 i7 R% q* U1 t8 k# w
当配置好USART的电路之后,直接读取数据寄存器,就可以自动发送数据和接收数据了。在发送和接收的模块有4个重要的寄存器
5 G1 ]6 ]; f( J8 H- D
发送数据寄存器TDR 8 G; i5 ?, M/ P: i
发送移位寄存器,把一个字节的数据一位一位的移出去 6 p# f, r6 Z5 N) N* ?% L
接收数据寄存器RDR
- u4 m+ [' }% \% E
接收移位寄存器,把一个字节的数据
% ]6 c. Z* q' R h8 g; m( d
8 g" u6 N7 Z- \0 j) X5 \7 I' Y- n
下方为串口的发送和接收图解:
' g$ Y( y4 t: F) M) O! ]( `0 l
- t+ u" v+ \( }# D% s
- {: D# J* j2 K9 ~8 V5 Z
串口发送 ! W% j3 `3 M/ u% i
在配置串口的各个参数时,可以选择发送数据帧的数据位的大小,可选8位或9位。
- }- B$ v( l9 v3 T
串口发送数据实际上就是对发送数据寄存器TDR进行写操作。
$ q6 U4 d# j9 Q' D6 ~
1. 当串口发送数据时,会检测发送移位寄存器是不是有数据正在移位,如果没有移位,那么这个数据就会立刻转移到发送移位寄存器里。准备发送。
' s/ s: h S/ J! d6 j F" R
2. 当数据移动到移位寄存器时,会产生一个TXE发送寄存器空标志位,该位描述如下。当TXE被置1,那么就可以在TDR写入下一个数据了。即发送下一个数据。
L2 P2 i1 r! Z0 @9 s. `
( f* Q$ B" w# K
/ J% f8 ?7 f* w: [ M2 L
3. 发送移位寄存器在发送器控制的控制下,向右移位,一位一位的把数据传输到TX引脚。
; k6 x/ u# o# \) b
# [/ |' n. }$ c/ Q- O# W( g
' n6 o8 q; k, }/ A6 J: N! K% @
4. 数据移位完成后,新的数据就会再次从TDR转移到发送移位寄存器里来,依次重复1-3的过程。通过读取TXE标志位来判断是否发送下一个数据。
) g: ^* X8 x7 I1 E7 Y
串口接收 " C ~. P3 P# [3 @ m8 F5 j& L
数据从RX引脚通向接收移位寄存器,在接收控制的控制下,一位一位的读取RX的电平,把第一位放在最高位,然后右移,移位八次之后就可以接收一个字节了。
, k) t% N/ b4 q; G4 L6 c
当一个字节数据移位完成之后,这一个字节的数据就会整体的移到接收数据寄存器RDR里来。
& ]$ @( I/ }5 p" j7 q' |
在转移时会置RXNE接收标志位,即RDR寄存器非空,下方为该位的描述。当被置1后,就说明数据可以被读出。
0 Q. S* F# X7 Z2 E, r
8 e; x' F0 H! n( C8 Y
6 g8 r5 Y+ I- \6 z3 n5 z( d
下图即为串口接收的工作流程 3 N1 a$ P- |& u; l
5 l1 K H8 A' N3 W5 T
- n, f' E+ \$ p8 M4 v
三、串口在STM32中的配置
6 h% P6 ?/ U5 K: G7 ^* U
首先要明确几点:使用STM32串口外设中的哪一个?串口发送或者接收数据?串口相关的参数配置?发送或接收是否使用到中断?
3 `1 g5 x' m1 y( x* `% Y& Q. x$ c
下方为串口发送的配置。
" E+ ` b! l7 L8 f+ ?9 _( A
1. RCC开启USART、串口TX/RX所对应的GPIO口
6 }, F- t- d- R/ ~2 K
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE); //开启USART2的时钟
1 J: v( \$ ?% g) M0 K$ q/ a' \ - RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //开启GPIOA的时钟
复制代码
2 ]: M3 M% d, p' v3 C
2. 初始化GPIO口
6 V/ i, Q/ C* T% c9 U( Q5 Z' {
这里注意哈,根据自己的需求来配置GPIO口,发送和接收是都需要还是只需要其中一个。然后对应的根据引脚定义表来初始化对应的GPIO口。 ' L% O6 e0 I) e5 x9 }% Z
USART2对应的引脚 4 Q: R& Z; d& S( a0 [
4 q. q. b$ s" m8 ]- y8 t' q
5 T) S, `. v, B, O. a. K
USART1对应的引脚
! v# I! A% A; g1 E4 l% b
$ y {+ |3 b P3 `# j
4 ~4 e/ l, ?* T; ~. b# W) v& k7 d) n
这里根据手册来看,RX引脚模式配置成浮空输入或者上拉输入。TX引脚模式配置成复用推挽输出。
' a2 T: m0 @0 u, v$ |% \! O( _! C **比如我这里只初始化TX发送端**- //TX端9 q) A! |! |2 v) h" U. ]1 H }7 D1 k
- GPIO_InitTypeDef GPIO_InitStructure;
: H& Z8 G9 \! ~ - GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; //复用推挽输出5 F# }- p" s' V% u: \
- GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2; //USART2对应的TX端为GPIOA2
4 r9 ?$ F8 U7 P! S/ x6 W - GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //50MhZ
2 x, d3 [0 A N" I. U U! h - GPIO_Init(GPIOA,&GPIO_InitStructure);
复制代码, r2 e2 k) Y |
3. 串口初始化
4 `# e; g1 G& z# v e
注意哈,USART_Init()这个函数,是用来配置串口的相关参数的。
1 x* C( |2 g0 a+ z Q
USART_BaudRate 串口通信使用的波特率 一般是9600或者是115200,这里我们给9600 : `4 P4 X9 F1 b2 T3 ?0 x" N+ H
USART_HardwareFlowControl 是否选择硬件流触发,一般这个我们也不选,所以选择无硬件流触发。 ( V9 h! E, h5 u6 u- O! j4 O/ H( `
USART_Mode 这个参数要注意了哈,串口的模式,发送模式还是接收模式,还是两者都有
. w6 t1 T K+ Z3 Q7 ]) J; V. v
USART_Parity 校验位,可以选择奇偶校验和不校验。没有需求就直接无校验 $ r$ S; l) f* c1 {
USART_StopBits 停止位 有1、0.5、2位,我们这里选1位停止位 M/ ]4 J. V9 i6 f3 S: k0 a
USART_WordLength 数据位 有8位和9位可以选择
- h. t/ h+ H1 X8 L( f+ H( ^
- //串口初始化5 z; t8 D8 P" V4 ~" d) X/ B# H5 R. ?
- USART_InitTypeDef USART_InitStruct;
Z5 j2 ?# z2 } Z - USART_StructInit(&USART_InitStruct); //初始默认值) [/ a" X2 \5 L
- USART_InitStruct.USART_BaudRate=9600;4 \/ b- u8 x6 \# I6 Z' S
- USART_InitStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None; //不使用硬件流触发3 G$ j% P0 R1 k0 w) h0 G2 P" c
- USART_InitStruct.USART_Mode=USART_Mode_Tx; //TX 发送模式- y* l! X. J7 e2 `7 c1 j2 E
- USART_InitStruct.USART_Parity=USART_Parity_No; //不选择校验
; Z8 ~9 k( S- G% c/ C2 s' | - USART_InitStruct.USART_StopBits=USART_StopBits_1; //停止位1位! `) \) _0 _$ y0 h# O' Q
- USART_InitStruct.USART_WordLength=USART_WordLength_8b; //数据位8位
/ e2 H+ Y8 b9 d - USART_Init(USART2,&USART_InitStruct);
复制代码
4 o5 P$ U) c5 M5 l- i, v
4. 串口使能 - //串口使能5 I& d# }+ }! Y
- USART_Cmd(USART2,ENABLE);
复制代码$ n/ m/ E7 U" I( z+ h; v+ n
5. 串口发送数据
8 l1 Y4 ]" p6 l+ F3 k) C' }- S注意哈,我们要判断TXE标志位的状态。0,数据还没有被转移到移位寄存器;1,数据已经被转移到移位寄存器。
" z' q8 U; h* v3 K1 u
当TXE标志位为1时,就说明可以发送下一个数据了。详细过程可看上面串口发送的解释。 - void Serial_SendByte(uint16_t Byte)9 [; ]! G4 u4 k3 ^
- {
; q' T. k* V3 y9 @( N - USART_SendData(USART2,Byte);" ]2 Y) u4 L; J5 w+ T
- //0 表示数据还未转移到移位寄存器 循环等待 1 数据已经被转移到了移位寄存器可以发送数据
e2 S! u4 _3 G( D5 _9 I0 o2 m. Y - while(USART_GetFlagStatus(USART2,USART_FLAG_TXE)==RESET); //不需要手动清零 再次写入TDR时会自动清零 . z3 R3 e$ V. T9 s3 X; F
- }
复制代码
U# a! P ?2 ^3 @7 H# `
经过上述五步的配置,单片机就可以通过串口发送数据了。 0 L! Z e; _7 r# h( h
下方为发送数据图例,STM32向串口发送0x16数据 ; }, k( Y: V0 G
4 {0 c0 r9 P# L+ s! A
+ l7 e- Q5 A5 e0 O# H! W; t/ r4 h
/ n7 w8 ]& Z/ m' X
四、串口接收的两种实现方式 上方是发送数据的例子,那么串口接收又该如何配置,又要在串口发送的例子上做哪些更改呢? ; d" I, v: z- _8 D* K$ k' P9 f& h
这里我们可以通过查询或者中断的方式来进行接收数据的两种方式。
; @/ E4 I% ?4 \1 e# a8 c
" h) z% `5 Y9 s4 t
1. 需要更改的地方
# B4 m, V& l9 U/ h7 a
; \3 r ]7 g* [ q" Z5 E 既然我们要实现串口的接收,那么就要配置串口RX引脚,在串口模式中添加USART_Mode_RX模式。
" ~, r# R2 u4 T/ W
初始化RX引脚 . b" ]3 ]- @4 {( V
- //RX端; Y1 E" t" L C% ?+ P$ h
- GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU; //上拉输入0 d7 i* E( g5 {1 @
- GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3;: L0 h# E( x B p2 D3 e* d
- GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //50MhZ
6 b0 q" `' O( _' ^% U - GPIO_Init(GPIOA,&GPIO_InitStructure);
复制代码
9 c* b) x, S( \) p m. `
, b# }5 |3 z. m N
添加串口模式
! q% T3 L! ?, H5 o; s1 _7 n
- USART_InitStruct.USART_Mode=USART_Mode_Tx|USART_Mode_Rx; //TX 发送模式 RX 接收模式
复制代码 ' ^. f. l3 \9 s0 }1 n
' [6 ~) J: k# j) V
2. 查询RXNE标志位 ) E! ]! `$ p5 v2 D" Q- l
这里我们还是来看一看RXNE标志位的描述 9 ~* L4 I5 k# a5 K& H
+ m& G9 f+ |( [1 h* |( N# u, h
% j3 s' x7 J; d; i5 C# T T: C
上图描述,为0时数据没有收到,为1时收到了数据,数据可以从RDR里读出
( g/ l; K o0 s( b8 |, R
所以在主程序里不断读取RXNE标志位,如果为1,表示数据可以读出 - uint8_t RX_Data;* T4 X7 N- \, |/ U1 y
- int main()
?% N. F) _$ O7 V - { , r$ s$ V6 K- P, V% V
- Serial_Init();
: K, X' c" W+ u, Q. [ - Serial_SendByte(0x16);
, x2 r- m4 o, F( v5 k/ n - while(1)
) q$ x o& O/ v" I/ O - {
- S% i6 B) a% V3 Z4 B - if(USART_GetFlagStatus(USART2,USART_FLAG_RXNE)==SET) //0 循环等待 1 可以接收数据
' n. @3 s* L, N3 x: @4 T# A - {9 W+ k( _; t) O o" |, g
- RX_Data=USART_ReceiveData(USART2); * G- n3 P3 E, R) B# U$ P
- Serial_SendByte(RX_Data);
! `' x3 |9 C* K - } v) m- y+ E8 N+ ]1 Q" H( y. C% w
- }/ H% T& i/ S' R6 E- P( u% {3 P( f
- }
复制代码+ E( u4 y- O- \1 U! W
下图为程序现象:pc向单片机发送数据0x15,单片机接收数据0x15,并且把接收到的数据作为数据发送到pc,在pc上显示0x15。 6 P8 b$ Y5 N& ?- ~
% Q( K3 h" Z: Y
) P% e% {$ e( h$ X8 @1 k O+ G1 I
3. 使用中断
3 A# i0 P9 }# U; b% w" r1 Z
- //开启中断输出控制- y; l, h7 \1 F: r( J0 H, k0 x
- USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);0 A4 U& B* n2 K A/ @! q
-
. e% ~) |) q' I; G - //配置NVIC6 Y1 M2 f% i8 E4 O7 _; l$ R
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 6 a* H0 {4 X6 Q$ \
- NVIC_InitTypeDef NVIC_InitStruct;
5 C. G: F/ K# J, D; ]( w - NVIC_InitStruct.NVIC_IRQChannel=USART2_IRQn; //选择USART2的中断通道
! U( O* o4 h( U% N( D - NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE; //中断使能) K% \/ U5 L# C
- NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=1;
6 k7 }. g, @2 m4 q9 R4 R0 q - NVIC_InitStruct.NVIC_IRQChannelSubPriority=1;) c2 e; F1 F5 r1 h- u q1 }# J1 Z
- NVIC_Init(&NVIC_InitStruct);
复制代码
8 p/ z2 i" P0 ^ H
: B& n$ P$ i% R
中断服务子函数 0 M4 ^ ]+ e& W' G3 u# X
( q0 h& D2 x. ]/ O5 v: R
中断服务子函数写好后,就可以在中断里读取接收到的数据了。 + m4 U8 `& o$ e8 |. q
当接收到数据后,触发接收中断,主程序暂停执行。接收完数据后主程序回复执行。当接收到数据时,就触发中断。 - void USART2_IRQHandler(void). ]5 X% i! b6 Z6 f; R u
- {
# z! V! V7 C* a- C N - if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET) //RXNE 标志位为1 表示可以接收数据
- n+ Q0 W8 y- j9 t% _" r/ E$ O - {
h) q" M2 z: U, {; i9 J1 z8 \ - RX_Data=USART_ReceiveData(USART2);
, i+ ?7 T, B9 [7 j! P% d1 _/ N - Flag=1;
8 U7 L4 E6 [7 S! B3 D1 e - USART_ClearITPendingBit(USART2,USART_IT_RXNE); //清除RXNE标志位
( |# t! S' M/ ^ - }/ Q& X+ d4 k: s' }! t
- }
复制代码 ! s8 H) k& ^3 c k
主程序测试 , o* T7 e( W E) @) ^" O4 W0 i+ i
- uint8_t RX_Data;
P g9 D: P3 V6 G1 `& W, E& v& C6 w2 A - uint8_t Flag;
& A+ t) Q3 G: s+ L3 }
! s0 y; F+ K$ U4 E& p4 }* D- int main()
7 C! Q6 {, u& L - {/ o4 {% d9 } B1 s% v
- Serial_Init();# J( y. u0 u" K0 m; @
- Serial_SendByte(0x16);
9 }1 U8 i3 H8 k5 L# e
- N( _& [/ H8 R' \4 Z" y
8 D$ K; N! f8 Y- Y0 \0 @: n- while(1)
8 d9 \' F* ?0 F - {
+ c2 l& L' `! k1 a/ w0 j - if(Flag==1)
) K' }( m( C/ c4 D9 V# C* { - {& l0 k$ z9 d8 O4 m
- Serial_SendByte(RX_Data);% E s3 u) ^$ F' D5 v. T
- }
2 w# u" T0 l" O - }# m0 O. G$ z: e
- }$ {; o* ]9 `3 ^3 S/ Z8 ^& O
- " `* k& Z8 }* u, H
- void USART2_IRQHandler(void)
; @5 |: [% X. G/ T2 U! g! l' t - {) R7 g! T. h: }% _! }% L( W, Y
- if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET) //RXNE 标志位为1 表示可以接收数据
8 }, W7 t7 C) D; k7 P# G* F - {5 u% G# {5 R4 U$ I! u$ n2 v/ f% |
- RX_Data=USART_ReceiveData(USART2);0 ?" x: w) Z" e, Y2 l
- Flag=1;
* a% E0 X# P3 G! m9 Q/ }5 U, m - USART_ClearITPendingBit(USART2,USART_IT_RXNE); //清除RXNE标志位
8 O, W: [" G) ~7 ~ - }
4 D$ v& }* X: f5 D/ c @! |" f - }
复制代码
: a8 e9 P) H5 A1 f. y- m, g
* U, B8 ?( U1 m* s
下图为程序现象:可以看到,串口确实收到了数据,只是我把接收到的数据0xFE放在了while循环里,这说明数据接收是成功的,使用中断是可行的。
$ S$ A/ `. R( h5 N3 g& a
5 X L K9 a, ?% m
c& C9 C% t6 b" l) n
' {& b3 Y7 B- V3 Z1 s. B总结 到这里,就大致总结了串口的发送和接收。 8 ^+ T7 g- c( M+ T+ r" ^8 `
串口的配置,使用查询或者中断来接收数据。
- A* z3 c" E' \8 |! T) A2 W
串口的使用会很常用到,所以在这里对串口做一个总结,也算是对之前知识的一个回顾和总结,加强印象。
/ n' H0 T: r0 ?) t) ^
如果有什么写的不对的地方,欢迎指正!
+ v% z9 D0 f! w$ z, h, C
转载自: 古月居 如有侵权请联系删除
6 k5 S2 T* Q2 I) E : ^3 x4 ]5 v2 k! |8 V, q @* H9 N( `
. g- ]- r0 U7 _* H |