|
STM32的串口通信 $ O1 D6 L. `& e" j* q% w
本文在于记录自己的学习过程中遇到的问题和总结,各种情况下串口通信在STM32的实际使用方面占有很大的比重,本文主要对串口通信做一个简要的总结。
+ n/ j3 j+ \* W) u! w
3 t3 Q1 F* O6 A, X. ~9 g9 X
一、STM32里的串口通信 $ A' h+ K' ?4 u: U$ l. ^2 D
在STM32里,串口通信是USART,STM32可以通过串口和其他设备进行传输并行数据,是全双工,异步时钟控制,设备之间是点对点的传输。
0 Y6 P5 l& C& k: s8 l- e3 d, n
对应的STM32引脚分别是RX和TX端。STM32的串口资源有USART1、USART2、USART3. 3 k {: K, u$ u( `0 W1 e, D% y
串口的几个重要的参数:
* Z% M# {) V' y7 e, I
波特率,串口通信的速率
1 v) I& o" @" J- ^4 X
空闲,一般为高电平 5 B) V* L! X7 k0 Q
起始位,标志一个数据帧的开始,固定为低电平。当数据开始发送时,产生一个下降沿。(空闲–>起始位)
* `1 M9 v$ {( ]* G m5 H. i
数据位,发送数据帧,1为高电平,0为低电平。低位先行。
7 [7 G5 k- a; J {: |- \
比如 发送数据帧0x0F 在数据帧里就是低位线性 即 1111 0000
% Y- g/ L1 {& e. ? F
校验位,用于数据验证,根据数据位的计算得来。有奇校验,偶校验和无校验。 & l8 W& F# k7 }! Y5 ^& x! N
停止位,用于数据的间隔,固定为高电平。数据帧发送完成后,产生一个上升沿。(数据传输–>停止位) 6 K5 P' S5 P |) i
5 `/ t; X4 ^2 g* y3 y* }
下方就是一个字节数据的传输过程,从图中可以看出,串口发送的数据一般都是以数据帧的形式进行传输,每个数据帧都由起始位,数据位,停止位组成, 且停止位可变。
3 F; H8 F4 S+ k3 K4 j
; |; y0 D$ ]5 Z4 d: r$ ?, b
4 @# w$ Z& U1 P2 _% Q7 R7 ]3 W+ h) Z7 G* h1 _9 w- t
二、串口的发送和接收
8 h/ l0 A7 r dUSART是STM32内部集成的硬件外设,可以根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可以自动接收RX引脚的数据帧时序,拼接成一个字节数据,存放在数据寄存器里。 6 G% Y; h! c, |( o4 i6 g$ `) ?9 ?
当配置好USART的电路之后,直接读取数据寄存器,就可以自动发送数据和接收数据了。在发送和接收的模块有4个重要的寄存器
# @# f6 y9 w6 `2 Z4 V, ]
( ~% a1 _7 L$ q8 ^. H) O/ P4 X
下方为串口的发送和接收图解:
5 j1 X4 y* y; E! R: i3 L
- [' U, g5 b6 F! j3 \+ V4 i1 R% M! L7 V9 u+ f, h* f7 l3 P
串口发送 : c8 a+ ^. o3 H
在配置串口的各个参数时,可以选择发送数据帧的数据位的大小,可选8位或9位。
, M" S3 M; r- C v
串口发送数据实际上就是对发送数据寄存器TDR进行写操作。 , ]6 |7 X7 r) S! M6 J/ U
1. 当串口发送数据时,会检测发送移位寄存器是不是有数据正在移位,如果没有移位,那么这个数据就会立刻转移到发送移位寄存器里。准备发送。
+ L( o1 G u$ ?$ Q3 K
2. 当数据移动到移位寄存器时,会产生一个TXE发送寄存器空标志位,该位描述如下。当TXE被置1,那么就可以在TDR写入下一个数据了。即发送下一个数据。 ! O0 p! d5 b. _5 ^4 _
. b6 R7 R& K$ E6 s
1 D! ?, _+ \; A, z2 @8 S; W. Y: T
3. 发送移位寄存器在发送器控制的控制下,向右移位,一位一位的把数据传输到TX引脚。
5 t, n8 {! W. v1 \/ e2 _6 R
( |/ \' n. @. l. H r8 t! F6 @$ C4 x8 K7 C0 }0 E6 K
4. 数据移位完成后,新的数据就会再次从TDR转移到发送移位寄存器里来,依次重复1-3的过程。通过读取TXE标志位来判断是否发送下一个数据。
* k0 ]$ N) C: T" R
串口接收
4 H- w- k- q* S! t: l5 }* v# x! D
数据从RX引脚通向接收移位寄存器,在接收控制的控制下,一位一位的读取RX的电平,把第一位放在最高位,然后右移,移位八次之后就可以接收一个字节了。 ' `7 G6 R" F3 T; B. R
当一个字节数据移位完成之后,这一个字节的数据就会整体的移到接收数据寄存器RDR里来。 1 X. i- X+ C" `' U. j: L9 a/ s
在转移时会置RXNE接收标志位,即RDR寄存器非空,下方为该位的描述。当被置1后,就说明数据可以被读出。 , P* Y; t. @ ^9 |
8 C; ]* K* M. c5 w4 C1 ^' a5 I4 @' o
) c' Q( N# n# ]6 t; p/ p. [& j7 x
下图即为串口接收的工作流程
* N7 \# o1 m- Z' Q" x+ I6 L" o
; h: Q$ O; D P. z2 Z$ d( u' o
% y# n8 N4 D. j4 F6 g' h2 p
% }& }/ F* W5 [三、串口在STM32中的配置 [% i0 H1 U, n: @$ i2 x1 p
首先要明确几点:使用STM32串口外设中的哪一个?串口发送或者接收数据?串口相关的参数配置?发送或接收是否使用到中断?
4 y, n# f6 g) J% ~
下方为串口发送的配置。
: P+ K- Z9 _$ V; U
1. RCC开启USART、串口TX/RX所对应的GPIO口 - RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE); //开启USART2的时钟; Q' ^( C$ x: V, [* Y
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //开启GPIOA的时钟
复制代码% e/ U5 j0 W l. E3 t5 z6 n
2. 初始化GPIO口 $ A$ X5 m' y U, @( Y2 R
这里注意哈,根据自己的需求来配置GPIO口,发送和接收是都需要还是只需要其中一个。然后对应的根据引脚定义表来初始化对应的GPIO口。
- r. a- v# D7 X+ m4 O$ o" n7 ~; ?
USART2对应的引脚
7 A6 p/ |' Z/ B$ j/ f8 L
. {3 i1 L. D9 z! e/ |, T9 d0 W% m3 g+ V, W
USART1对应的引脚
0 @+ ~1 }( O9 q3 H; }/ S
2 b. m% }0 |0 ?9 N& `8 }) B8 Q
4 V$ ~0 W! |8 b1 C
这里根据手册来看,RX引脚模式配置成浮空输入或者上拉输入。TX引脚模式配置成复用推挽输出。
9 ]- V) R) L, C9 ^ **比如我这里只初始化TX发送端**- //TX端
" z; }, K- G( U. o2 @ - GPIO_InitTypeDef GPIO_InitStructure;
' @4 l$ W' U% W$ Q4 o+ C - GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; //复用推挽输出' e8 q& p, P1 K% n2 Y
- GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2; //USART2对应的TX端为GPIOA2
! R/ N. m" h/ c. A* c, t% H - GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //50MhZ- m6 a$ z2 w' O6 d1 _5 _; \' c
- GPIO_Init(GPIOA,&GPIO_InitStructure);
复制代码
4 @/ D2 E4 O- B# @* j
3. 串口初始化 0 d) ~0 N+ T2 q7 A) e
注意哈,USART_Init()这个函数,是用来配置串口的相关参数的。 . N) n" H! A' J* g
USART_BaudRate 串口通信使用的波特率 一般是9600或者是115200,这里我们给9600 % ?- ^7 ]: _) ~3 @1 C/ g& x
USART_HardwareFlowControl 是否选择硬件流触发,一般这个我们也不选,所以选择无硬件流触发。
) K, d5 K8 Z6 l* P/ l& d, K' [% a( C
USART_Mode 这个参数要注意了哈,串口的模式,发送模式还是接收模式,还是两者都有
`. t* ?0 d0 T
USART_Parity 校验位,可以选择奇偶校验和不校验。没有需求就直接无校验 1 {3 w6 w' u" Q; b; Q
USART_StopBits 停止位 有1、0.5、2位,我们这里选1位停止位
2 O* C: h3 U; Z- K
USART_WordLength 数据位 有8位和9位可以选择 - //串口初始化
' V5 n3 c( V1 E. s3 X - USART_InitTypeDef USART_InitStruct;( F# E/ l7 V; {' H& M; u* j; h4 @
- USART_StructInit(&USART_InitStruct); //初始默认值2 S, N+ Y+ L$ r4 i# u5 G
- USART_InitStruct.USART_BaudRate=9600;
) x& _/ d7 U4 H, ]/ @8 Q( u% @% Y, t - USART_InitStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None; //不使用硬件流触发; l3 t1 |5 \1 D" @6 [$ h: [
- USART_InitStruct.USART_Mode=USART_Mode_Tx; //TX 发送模式
% Z' Z+ I- }9 j5 D5 F4 Q - USART_InitStruct.USART_Parity=USART_Parity_No; //不选择校验* U3 Z, S8 N2 B* J* P5 P
- USART_InitStruct.USART_StopBits=USART_StopBits_1; //停止位1位
9 B) y4 D2 v5 s) o4 q7 \ - USART_InitStruct.USART_WordLength=USART_WordLength_8b; //数据位8位; Y) v7 [) m6 b5 N! a1 }+ ~
- USART_Init(USART2,&USART_InitStruct);
复制代码
- `1 V$ Q4 t; T& j5 \/ V8 f2 f# F
6 h% i# x/ H$ M$ R1 I0 L: O
4. 串口使能 - //串口使能- V1 _6 u# x0 V5 S+ E2 L$ W- c
- USART_Cmd(USART2,ENABLE);
复制代码( b; m7 P! Q) s2 d$ x6 l
5. 串口发送数据 + L: a$ Y9 o& y* Z: r4 e7 e
注意哈,我们要判断TXE标志位的状态。0,数据还没有被转移到移位寄存器;1,数据已经被转移到移位寄存器。 ; d5 p$ X1 X" N
当TXE标志位为1时,就说明可以发送下一个数据了。详细过程可看上面串口发送的解释。 - void Serial_SendByte(uint16_t Byte)4 B0 [9 M4 F& s5 k/ d5 v
- {. Y; F) q% y0 j) M; Z
- USART_SendData(USART2,Byte);, a. D* n( |* D1 `
- //0 表示数据还未转移到移位寄存器 循环等待 1 数据已经被转移到了移位寄存器可以发送数据
6 I% Z w: D" R; g+ D( B W - while(USART_GetFlagStatus(USART2,USART_FLAG_TXE)==RESET); //不需要手动清零 再次写入TDR时会自动清零
6 o# R/ x; [. ]5 L J0 H - }
复制代码3 ]8 u7 P8 L0 {! E
经过上述五步的配置,单片机就可以通过串口发送数据了。 2 J! Z1 m; s ]2 O5 Z& u
下方为发送数据图例,STM32向串口发送0x16数据 ; n. @: K2 }6 H( i$ U3 h7 d0 Q
5 J( r5 {& J7 y8 I* n
/ t! m4 h( K- E) S$ F
四、串口接收的两种实现方式 7 e' ~: i( h: a3 H. c V% g) @
上方是发送数据的例子,那么串口接收又该如何配置,又要在串口发送的例子上做哪些更改呢?
% ~! I0 t0 m; J6 w8 D; B" H! r o7 p
这里我们可以通过查询或者中断的方式来进行接收数据的两种方式。
4 d& h1 g! i! \9 v4 B _& x
查询方式就是通过不断的查询RXNE标志位,通过判断RXNE位的状态来确定数据是否接收。
8 w8 Q& z/ R+ x% N: i& e1 v3 [% @
中断方式就是通过配置接收输出控制通道,配置NVIC,在中断服务子函数里进行数据的接收。 1 C0 D8 X' N8 Q0 G8 o- u; E
. N; \" @% {+ h' i. Z! o
1. 需要更改的地方
- v0 f: F5 _/ T3 J: ~' M# o
0 u& x! R) P( N# J8 U K: L 既然我们要实现串口的接收,那么就要配置串口RX引脚,在串口模式中添加USART_Mode_RX模式。
: z7 t- m5 R8 x5 v, J
初始化RX引脚 - //RX端; B9 i' I# v3 W, e, R: `
- GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU; //上拉输入
! I! D- ~: z, d s$ s' R8 r. U/ M' Z - GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3;1 L' D- m+ m/ x" c2 d5 g! r
- GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //50MhZ3 j7 }0 l6 a9 G( F# z: A/ f
- GPIO_Init(GPIOA,&GPIO_InitStructure);
复制代码 / \2 p l- g! S. h9 E' ^: n# ]
9 T8 M% B) d& g* G5 g( \2 j K
添加串口模式 - USART_InitStruct.USART_Mode=USART_Mode_Tx|USART_Mode_Rx; //TX 发送模式 RX 接收模式
复制代码 ! t$ _! r( { p5 p0 r Y
$ f' f9 n4 B# L3 {9 \+ ]
2. 查询RXNE标志位
, F) d; r% q ~) B$ j5 R
这里我们还是来看一看RXNE标志位的描述 $ N( o3 E: g1 I: s" Y
: i8 N5 P4 R1 Z& n
% z+ z! E1 A5 {5 p0 T# ]" r6 V# i
上图描述,为0时数据没有收到,为1时收到了数据,数据可以从RDR里读出 # I1 H8 n. s% L* |
所以在主程序里不断读取RXNE标志位,如果为1,表示数据可以读出 - uint8_t RX_Data;* B+ J/ R: P) d0 f# a5 `, J
- int main()
, A9 |4 P3 p* q) d4 r Y; z - { 1 g( e6 T; c# h- |' I
- Serial_Init();
: w. _5 x9 g$ l; Z a$ n* U - Serial_SendByte(0x16);
8 j) ]1 l1 }" u: y9 G5 x; ?% s - while(1)
, [. K6 [3 b$ n3 U2 @0 x - {
$ s, Z3 e! C% u$ ~ - if(USART_GetFlagStatus(USART2,USART_FLAG_RXNE)==SET) //0 循环等待 1 可以接收数据7 g1 o3 a" ^3 [
- {; Q/ i' q; z0 W8 S/ `# f( X
- RX_Data=USART_ReceiveData(USART2); 3 u" U" W2 } e1 u+ v* o/ r" f
- Serial_SendByte(RX_Data);
6 U" U9 J4 U8 z9 T: |4 f% @ - }
1 l; n0 K: e3 y - }
/ r+ E& w, P. B: W, B) q - }
复制代码& c/ J/ e0 ~' u. p% I7 R
下图为程序现象:pc向单片机发送数据0x15,单片机接收数据0x15,并且把接收到的数据作为数据发送到pc,在pc上显示0x15。
0 O) R& L) y, i/ v: }# ?" A
?; I# h: i% z1 t
* C/ ~( }$ o6 A# h2 Y+ L
3. 使用中断 . _3 ?5 s, |( L$ R/ d
- //开启中断输出控制
3 }" j0 s2 X# Q4 a( ]3 A - USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);
8 ^7 {$ y; {6 J# I - $ h% }# ?$ D, M2 y* b; T4 ?
- //配置NVIC
* H% F. k; i/ @, Z# b: e - NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
5 T$ V1 P8 n2 } H - NVIC_InitTypeDef NVIC_InitStruct;3 q0 ]% \5 X* V8 R
- NVIC_InitStruct.NVIC_IRQChannel=USART2_IRQn; //选择USART2的中断通道" H0 t" p9 t7 J, F) L! V/ W4 Y7 [
- NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE; //中断使能
, \6 r7 ?# }6 \. {3 N* \& y! {9 e - NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=1;$ z- K% L R! ^0 j
- NVIC_InitStruct.NVIC_IRQChannelSubPriority=1;! ^, ?6 P/ m6 x' B( ]2 b+ v
- NVIC_Init(&NVIC_InitStruct);
复制代码
# g( ?1 u; B4 l& Q! l" q; `( ^, P) @
, U2 V* E8 h# T, T8 o中断服务子函数写好后,就可以在中断里读取接收到的数据了。
4 S& e" I/ P( z8 O a/ r当接收到数据后,触发接收中断,主程序暂停执行。接收完数据后主程序回复执行。当接收到数据时,就触发中断。
- void USART2_IRQHandler(void)
. j6 a! \' a2 B. H! ~1 n2 j, P) l$ | - {, e4 _. {: [2 `2 O" t1 @
- if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET) //RXNE 标志位为1 表示可以接收数据
: h8 i7 S" c# U2 j - {
( q! t1 F2 D6 F$ u9 ]/ d - RX_Data=USART_ReceiveData(USART2);! O3 m# O' W( p2 x3 U, W
- Flag=1;5 ~0 u% l1 l# T2 x" G. e* F
- USART_ClearITPendingBit(USART2,USART_IT_RXNE); //清除RXNE标志位% R; T y8 L! j% z/ D( o
- }$ s# Y7 }2 b; c2 [8 ~9 n* V1 T
- }
复制代码3 \' h) t, w! L9 K6 Y _* A! Z& s* s8 s
主程序测试
) g4 T& \) F5 H7 @4 Y3 @7 p3 L
- uint8_t RX_Data;3 {: M$ f8 h3 v# J' v2 W
- uint8_t Flag;" X$ G0 E# a4 w- I5 a* B! \" z& M
, l$ p; E2 p; |# j6 G- int main()% O$ r5 f* y% g3 [
- {
1 D. h( Q. Y! J4 }. H7 J - Serial_Init();9 ^1 o, x" m9 L2 \& W6 d# a
- Serial_SendByte(0x16);* M" y6 A$ |4 \2 d- U! @& {# H
- % B) S( w. h# J" R J
$ y n/ y: W7 Q: G- while(1)
; k) k& A9 F! q" ~# H - {
; M+ q7 b# [% r4 O4 S - if(Flag==1)0 ^) `7 Z& M7 A7 y1 n% s9 r
- {
7 L$ \& ^6 o3 s9 q9 r8 U. C+ @ - Serial_SendByte(RX_Data);
+ ?5 h+ @) ]. P( a2 [ - }' X8 B3 n' A: Z$ |- r2 D6 {
- }' C0 H7 w: t1 A9 T+ \( I
- }
8 ^, f" t) I- O
0 s g% [/ J) g, ]: U- void USART2_IRQHandler(void)
* Z1 b3 q! ^: d' i6 d' ? - {
X( V( c" O7 `, G) Q/ J - if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET) //RXNE 标志位为1 表示可以接收数据0 F5 a6 m, f* e/ W% Z
- {
3 G& m4 f$ E3 x t - RX_Data=USART_ReceiveData(USART2);
5 Z2 t/ V+ s+ E% l" V) T6 y - Flag=1;
$ y3 S8 E7 Z/ Z/ c- J( N2 J - USART_ClearITPendingBit(USART2,USART_IT_RXNE); //清除RXNE标志位
9 ^8 h$ R: y2 D! R6 P4 R9 j$ Q - }! `6 s$ {' }+ X% f' ~1 D$ L/ L
- }
复制代码
4 I J: S f4 q- x7 \" P9 p5 g+ g, ] T( {
下图为程序现象:可以看到,串口确实收到了数据,只是我把接收到的数据0xFE放在了while循环里,这说明数据接收是成功的,使用中断是可行的。 & r4 @5 t2 C5 f
+ U' E- K7 l9 s( o% b- ^& t C5 R* q; M/ Y9 F' g. C
9 t2 Y3 i3 P) t* b7 A总结
- y8 c$ d) E1 U5 o
到这里,就大致总结了串口的发送和接收。 - v; h$ M6 f* r8 Z4 u$ r2 K
串口的配置,使用查询或者中断来接收数据。 , t* @: H7 }& u8 P( N0 L
串口的使用会很常用到,所以在这里对串口做一个总结,也算是对之前知识的一个回顾和总结,加强印象。
5 c% j0 F) M; J- i5 S& R
转载自: 古月居 如有侵权请联系删除
4 q" ]" b9 Y9 m$ Q# `5 c
0 U% P* C7 X9 z( a R! a |