|
STM32的串口通信
% B' W. M& U' R; C; ^
本文在于记录自己的学习过程中遇到的问题和总结,各种情况下串口通信在STM32的实际使用方面占有很大的比重,本文主要对串口通信做一个简要的总结。
9 z& J3 X3 @0 Z) q, n- I7 _
6 h8 ?& d# G! q
一、STM32里的串口通信
8 r. F5 L0 O, F' k# q
在STM32里,串口通信是USART,STM32可以通过串口和其他设备进行传输并行数据,是全双工,异步时钟控制,设备之间是点对点的传输。
" y, k- I% i1 c/ e& M0 k
对应的STM32引脚分别是RX和TX端。STM32的串口资源有USART1、USART2、USART3.
# a' f/ ?8 F0 M+ L
串口的几个重要的参数: 1 b0 \6 W- i7 L( _
波特率,串口通信的速率 * u( a+ _0 p) F7 ?7 {
空闲,一般为高电平 `0 d) V, r3 x4 \7 d: D0 X
起始位,标志一个数据帧的开始,固定为低电平。当数据开始发送时,产生一个下降沿。(空闲–>起始位) 6 J1 M6 y0 _% p, o8 |
数据位,发送数据帧,1为高电平,0为低电平。低位先行。
" q o7 D& @! w
比如 发送数据帧0x0F 在数据帧里就是低位线性 即 1111 0000 8 W5 { R4 c! r6 L
校验位,用于数据验证,根据数据位的计算得来。有奇校验,偶校验和无校验。
' X& ?. }& }0 V
停止位,用于数据的间隔,固定为高电平。数据帧发送完成后,产生一个上升沿。(数据传输–>停止位) 5 i, H& T. g9 M% r! }
* P( |. d, S$ D
下方就是一个字节数据的传输过程,从图中可以看出,串口发送的数据一般都是以数据帧的形式进行传输,每个数据帧都由起始位,数据位,停止位组成, 且停止位可变。
; M- e/ {; c, K3 D
. z8 `& r( ~6 ^6 ?& \0 a: u* D, G8 J8 ^4 T3 C+ ]; p
2 l- T7 \. }4 \; \$ c% \* l' G
二、串口的发送和接收 $ V$ q8 l0 [ N( G: L, H/ `
USART是STM32内部集成的硬件外设,可以根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可以自动接收RX引脚的数据帧时序,拼接成一个字节数据,存放在数据寄存器里。
0 B- P7 P4 n9 {7 R. {: }+ ?- E' b
当配置好USART的电路之后,直接读取数据寄存器,就可以自动发送数据和接收数据了。在发送和接收的模块有4个重要的寄存器
7 i5 e' K% ~7 f% `2 {) D: {) ]
& b- C) H: e. I/ ?7 f# U& O# S
下方为串口的发送和接收图解:
: c- T; |) {5 p, h" ~
0 F1 z, T* ^! {! v) d. c$ w* ^) h# f) K
串口发送 8 Y' b" |& c4 h
在配置串口的各个参数时,可以选择发送数据帧的数据位的大小,可选8位或9位。 : s% t5 @$ Q* M8 |5 }0 @
串口发送数据实际上就是对发送数据寄存器TDR进行写操作。 - k2 ^ V4 S3 ~8 ~. A
1. 当串口发送数据时,会检测发送移位寄存器是不是有数据正在移位,如果没有移位,那么这个数据就会立刻转移到发送移位寄存器里。准备发送。 0 O' Z3 l; n9 L: P/ B* r
2. 当数据移动到移位寄存器时,会产生一个TXE发送寄存器空标志位,该位描述如下。当TXE被置1,那么就可以在TDR写入下一个数据了。即发送下一个数据。
1 M0 N& U; R2 H1 P- }
' l; u4 t3 N+ ]! E- S" I9 t: q$ z J% ~, s
3. 发送移位寄存器在发送器控制的控制下,向右移位,一位一位的把数据传输到TX引脚。
0 F7 ?& K1 I, X* t3 Q# S3 Z
3 \/ [; n* ~" K3 Y, p% `9 g: `; E, z J! N# p+ G9 K# i
4. 数据移位完成后,新的数据就会再次从TDR转移到发送移位寄存器里来,依次重复1-3的过程。通过读取TXE标志位来判断是否发送下一个数据。
5 p# o( U+ g% g6 N
串口接收
6 d' z+ {0 N7 j( Q% q5 H
数据从RX引脚通向接收移位寄存器,在接收控制的控制下,一位一位的读取RX的电平,把第一位放在最高位,然后右移,移位八次之后就可以接收一个字节了。
# q i9 Q2 G' g9 B1 d# ]8 I8 R4 m; N
当一个字节数据移位完成之后,这一个字节的数据就会整体的移到接收数据寄存器RDR里来。 4 g/ I* ~6 x! p
在转移时会置RXNE接收标志位,即RDR寄存器非空,下方为该位的描述。当被置1后,就说明数据可以被读出。 # M( A) V8 S/ ?7 d9 \: ?
- a# U/ G, Q; l- [ a- H m& D" a8 @4 R- y
下图即为串口接收的工作流程 4 @) B, ^/ S6 a _: O
& z; d$ _; o. ^8 }# ]! l$ C8 }3 _0 I+ R) Q+ e( @
! Y: I, W* S/ X7 Y) k6 X三、串口在STM32中的配置
0 _/ }! t4 @" D8 @ d首先要明确几点:使用STM32串口外设中的哪一个?串口发送或者接收数据?串口相关的参数配置?发送或接收是否使用到中断?
. d- o2 v, F% M
下方为串口发送的配置。
' j7 B* x5 T8 {( m$ V* t! Z6 U
1. RCC开启USART、串口TX/RX所对应的GPIO口 - RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE); //开启USART2的时钟- i( t& n" x, t8 c/ }1 w
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //开启GPIOA的时钟
复制代码
& J. ]: R8 W: {% i% \' A
2. 初始化GPIO口 ( ]' p; e# J# l( ]3 g
这里注意哈,根据自己的需求来配置GPIO口,发送和接收是都需要还是只需要其中一个。然后对应的根据引脚定义表来初始化对应的GPIO口。 ( E. J. S7 W3 M
USART2对应的引脚 6 c8 k4 B6 N; G& W0 {5 \
% u9 ?! Z3 N" G0 S
* W2 U6 N1 \9 q/ f7 P0 F
USART1对应的引脚 - @& j! C' R' M" j* N3 d9 Q9 \
! W) g4 w) b$ {+ K6 ?$ ^# ~5 z6 L! H" R4 ?/ L
这里根据手册来看,RX引脚模式配置成浮空输入或者上拉输入。TX引脚模式配置成复用推挽输出。
, g& R% |9 E) G **比如我这里只初始化TX发送端**- //TX端
R6 P! k g1 s1 _3 r; R$ } - GPIO_InitTypeDef GPIO_InitStructure;
$ R6 V9 B' P( Q. [/ @9 u# p/ e4 z* ^ - GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; //复用推挽输出% z6 y: d' d) \9 K- m% I( @0 k l
- GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2; //USART2对应的TX端为GPIOA2% y4 \$ ^) {8 m* X# T# S
- GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //50MhZ0 U6 I3 B* Q* o5 u5 e6 O2 f& _
- GPIO_Init(GPIOA,&GPIO_InitStructure);
复制代码7 H. r4 \( ], |. L/ ^, o
3. 串口初始化
X% Q5 F, G4 {, o" [1 s
注意哈,USART_Init()这个函数,是用来配置串口的相关参数的。 + y% b: K) ^" \7 G( d
USART_BaudRate 串口通信使用的波特率 一般是9600或者是115200,这里我们给9600
& M5 I- I4 ^; K3 x: e# z1 e* J
USART_HardwareFlowControl 是否选择硬件流触发,一般这个我们也不选,所以选择无硬件流触发。
" U* x9 H [( o+ v
USART_Mode 这个参数要注意了哈,串口的模式,发送模式还是接收模式,还是两者都有
$ ^% b; J* z) T F4 H
USART_Parity 校验位,可以选择奇偶校验和不校验。没有需求就直接无校验
* N% Q/ L9 m5 g, p! Y$ N0 u
USART_StopBits 停止位 有1、0.5、2位,我们这里选1位停止位 6 n$ W/ A0 C" l5 Q& u
USART_WordLength 数据位 有8位和9位可以选择 - //串口初始化
$ K/ S8 D3 \6 ?0 f; e+ f3 m) Q+ A - USART_InitTypeDef USART_InitStruct;
, s; A; _# Z$ Z# ]1 N - USART_StructInit(&USART_InitStruct); //初始默认值
$ W5 m9 H" a* S# [ - USART_InitStruct.USART_BaudRate=9600;
5 b' K( i c. c0 t6 i - USART_InitStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None; //不使用硬件流触发& b+ W' v+ B- E6 s2 P* ?$ Z2 A3 P
- USART_InitStruct.USART_Mode=USART_Mode_Tx; //TX 发送模式5 a4 m! p: Z" k
- USART_InitStruct.USART_Parity=USART_Parity_No; //不选择校验7 C/ }/ a4 M: Y) f7 h5 s) q
- USART_InitStruct.USART_StopBits=USART_StopBits_1; //停止位1位% L8 H( n! R; P$ @0 `. {+ W
- USART_InitStruct.USART_WordLength=USART_WordLength_8b; //数据位8位
) |. n" `1 U- M/ Y/ D- t - USART_Init(USART2,&USART_InitStruct);
复制代码 6 X8 I- ?& P0 z/ U
7 C0 Q2 U" N2 S4 `- v6 r5 K3 [
4. 串口使能 - //串口使能
x" [- c3 z* g; Q% f - USART_Cmd(USART2,ENABLE);
复制代码
! u6 ]9 F1 L# v1 o ?7 ?
5. 串口发送数据
7 s; M) h5 Y' X1 Q( a4 }注意哈,我们要判断TXE标志位的状态。0,数据还没有被转移到移位寄存器;1,数据已经被转移到移位寄存器。
" }; l2 b' q' a
当TXE标志位为1时,就说明可以发送下一个数据了。详细过程可看上面串口发送的解释。 - void Serial_SendByte(uint16_t Byte), t5 k! M' N& y) ^( R2 y8 i, D
- {% U: _/ H" R3 k) C
- USART_SendData(USART2,Byte);
- I, u: S* K7 E0 ] - //0 表示数据还未转移到移位寄存器 循环等待 1 数据已经被转移到了移位寄存器可以发送数据! |. \/ K" J/ {6 i, H$ e: ?+ _
- while(USART_GetFlagStatus(USART2,USART_FLAG_TXE)==RESET); //不需要手动清零 再次写入TDR时会自动清零
. n4 f- J+ U8 n6 k3 i! t - }
复制代码& |' m4 h: L3 e
经过上述五步的配置,单片机就可以通过串口发送数据了。 ; l+ E# N- s/ _7 h' G5 q# x) Q9 _5 C
下方为发送数据图例,STM32向串口发送0x16数据 6 x; L' M1 O4 r6 H# U* J
* p+ Z. ^7 l1 a3 ], F `# h9 {, j( `
3 M# T- {$ u$ \2 D% p" m4 t; B2 ?
四、串口接收的两种实现方式
2 j9 p5 Q/ u. V0 ^) p v" F上方是发送数据的例子,那么串口接收又该如何配置,又要在串口发送的例子上做哪些更改呢? ( z( T+ F0 i& H X R3 u
这里我们可以通过查询或者中断的方式来进行接收数据的两种方式。 5 d9 I- c: K H! m* @ O
查询方式就是通过不断的查询RXNE标志位,通过判断RXNE位的状态来确定数据是否接收。
! P C9 g! t0 d& X' _$ i4 c# J
中断方式就是通过配置接收输出控制通道,配置NVIC,在中断服务子函数里进行数据的接收。
0 Q- [/ ^$ N& v$ \: Q# L1 y
. C, G/ U. Y4 B q* |1 k' x1 M4 c
1. 需要更改的地方% a& r2 M; T" T! v: x' f: H( E
4 q* E; ]2 z$ Z9 O& Z 既然我们要实现串口的接收,那么就要配置串口RX引脚,在串口模式中添加USART_Mode_RX模式。
, @2 Q+ [1 K/ E! F f$ ^! d
初始化RX引脚 - //RX端+ ~7 Y- z# s* F1 {
- GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU; //上拉输入
0 I4 q5 ^% r( G% X: ]3 F - GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3;" @2 `* H7 c9 w x' k/ k
- GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //50MhZ
0 e) }0 R! V6 H) H7 @ - GPIO_Init(GPIOA,&GPIO_InitStructure);
复制代码
7 x q5 d; `$ F' N) O
+ ]; h6 @6 I {, R. y# B; u/ k4 Q
添加串口模式 - USART_InitStruct.USART_Mode=USART_Mode_Tx|USART_Mode_Rx; //TX 发送模式 RX 接收模式
复制代码
/ V: \" _5 T8 ]# G# B
/ x/ J- `& L- c$ i6 ~
2. 查询RXNE标志位 6 W( y$ r$ Y& n, }. B' F! W5 o
这里我们还是来看一看RXNE标志位的描述 # o% i. g- S0 ^; Y% ]6 `5 E, y8 A
) l. \- }, \5 C+ J
' v% L, G: f7 w; \
上图描述,为0时数据没有收到,为1时收到了数据,数据可以从RDR里读出 + A! h7 T: I" J) P3 x {/ |& T
所以在主程序里不断读取RXNE标志位,如果为1,表示数据可以读出 - uint8_t RX_Data;
$ y6 K' P/ \) } - int main()5 s n! c0 h. k) }# d: Y
- { 3 i% p; Z4 t+ Q4 ~6 P2 i
- Serial_Init();2 o/ V1 S7 |% K5 v$ q8 I$ n
- Serial_SendByte(0x16);8 ?6 b' F* h. A2 w
- while(1)
" d. c, S e0 p& C A7 D+ S9 P/ W - {' E: u" m( K! n
- if(USART_GetFlagStatus(USART2,USART_FLAG_RXNE)==SET) //0 循环等待 1 可以接收数据
9 D" D, [* b( |- @; y, S S - {
9 P# J- p, s2 a4 s - RX_Data=USART_ReceiveData(USART2);
$ ~# \& J& J9 ]; W3 p5 W" ~+ W - Serial_SendByte(RX_Data);1 g+ N4 C8 P3 M+ R
- }
1 }+ W3 k6 S8 {8 { - }
c2 H* ]$ a O: t- K4 c m - }
复制代码
: G1 }& P! c; K, l
下图为程序现象:pc向单片机发送数据0x15,单片机接收数据0x15,并且把接收到的数据作为数据发送到pc,在pc上显示0x15。
! \* ^$ ^& ? k- x* Y% ]/ |
! D( y% V' ~3 F: e" U4 R) M6 U
K+ Q, R7 {+ U* Y
3. 使用中断 " g2 p6 [0 _0 ^ g6 n
- //开启中断输出控制) j: V @. R) L E' i5 k" _
- USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);$ b4 k+ ]4 M" i: i S. b0 ?4 f9 T
-
" Y( j- a8 v# M( P7 w& y2 o; L. ~% c" q - //配置NVIC
4 ^1 v0 A" {. S - NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 4 ~3 ^( c* _ t, ^6 f9 u" ]+ v
- NVIC_InitTypeDef NVIC_InitStruct;% e o( g+ G* Y, @. ?, R
- NVIC_InitStruct.NVIC_IRQChannel=USART2_IRQn; //选择USART2的中断通道
1 k e. t# F# z8 s& L - NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE; //中断使能
7 A* F1 i2 N# K) Z3 T' U( _' D - NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=1;& R& ]: v- I+ {
- NVIC_InitStruct.NVIC_IRQChannelSubPriority=1;
' w y7 n$ M; L, L, ~, f. s - NVIC_Init(&NVIC_InitStruct);
复制代码
* { t! W6 W0 }% ]9 J2 o- f! ]
中断服务子函数
& w1 v" [1 y* `4 |7 m
! y+ ~ w8 w8 u, f9 a中断服务子函数写好后,就可以在中断里读取接收到的数据了。
6 \8 o* x4 _4 s" k# x: h8 S; g
当接收到数据后,触发接收中断,主程序暂停执行。接收完数据后主程序回复执行。当接收到数据时,就触发中断。 - void USART2_IRQHandler(void)
6 Q. F, L9 M+ T- l6 y - {* U* H; d6 s0 E# ] K2 i7 |7 Y
- if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET) //RXNE 标志位为1 表示可以接收数据
% k3 z$ X4 b8 o/ W# l - {7 s/ C+ f" v4 A+ `% E
- RX_Data=USART_ReceiveData(USART2);
& O7 }6 w* q# ?8 t+ c! Q U - Flag=1;$ x k/ V0 L! T- U& l
- USART_ClearITPendingBit(USART2,USART_IT_RXNE); //清除RXNE标志位8 e- g' a! @' z# ~3 T% x
- }
5 n; E, ?3 y6 _( {) t1 H - }
复制代码
4 D+ d8 J& _1 V
- uint8_t RX_Data;
* z1 b W, s9 ^; ^1 P. C - uint8_t Flag;1 @( `7 r% `; ]. p# | S
$ P+ i6 b/ K1 s. z P o- int main()8 h5 S. M! r' p
- {" a* V6 p$ J7 x; K% ~9 q" `
- Serial_Init();, z* g* u3 }) f: M' F
- Serial_SendByte(0x16);
) J; k" G6 f9 M g
- z5 N6 n/ Z6 U, F# G6 `- e5 H% o
* @! l! [! v& `7 s) h. i; G! I. }- while(1), V! Z( C0 N4 D# k$ e# u/ [
- {7 t* x% y. t% F0 L
- if(Flag==1)% I" G- b% E" S. `2 W( M
- {8 \6 r& k2 F; w0 t% {
- Serial_SendByte(RX_Data);7 g3 t9 w4 F1 G& G: a
- }( ?+ ?" S d8 g- D* w. w* x
- }6 O# l7 z5 w% u0 |2 g9 `) U
- }$ A9 Y/ o/ o% C8 E2 L/ I# A$ n
# n3 [& B( a2 z9 F8 ^) z- void USART2_IRQHandler(void)
; Q' B2 Q. ?4 B( [1 ]7 Q - {
3 L! P% l" M: L M' v - if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET) //RXNE 标志位为1 表示可以接收数据( r0 _$ u( ]) D# N' {5 p/ x
- {
1 z. `5 m! x, W& S% N x% Z - RX_Data=USART_ReceiveData(USART2);
W, v! ?0 ]+ h% Q7 C* l - Flag=1;
1 _6 g5 [' G3 o+ P! |2 d. [9 o5 R - USART_ClearITPendingBit(USART2,USART_IT_RXNE); //清除RXNE标志位
" K+ _9 M* z1 k# f7 Q) v) b, Z* A - }
4 {, p* Z0 S# R) y. s$ J- K - }
复制代码
& S1 `) @3 w( A2 w) Z
5 W! D7 m5 Y0 b9 }( o
下图为程序现象:可以看到,串口确实收到了数据,只是我把接收到的数据0xFE放在了while循环里,这说明数据接收是成功的,使用中断是可行的。
# e# p' g" Z& j
2 h$ M( ~! R8 f2 R8 [& `
' U7 K$ x1 W& `
$ o H# Z# d4 @; }* D, \
总结
' g. I2 G9 w( B, y) ?
到这里,就大致总结了串口的发送和接收。
; s! b8 _: S% k @! |3 k
串口的配置,使用查询或者中断来接收数据。
" ?; A; o0 z* Z
串口的使用会很常用到,所以在这里对串口做一个总结,也算是对之前知识的一个回顾和总结,加强印象。
8 s7 P2 i+ b$ F$ J. t5 Y" v( s
转载自: 古月居 如有侵权请联系删除 1 m2 T6 Z( u* ^9 k$ C
* v5 ?' V: N7 C0 M0 k" \+ A8 ?+ O |