|
STM32的串口通信 ' V9 S+ j% R8 S- g l! X
本文在于记录自己的学习过程中遇到的问题和总结,各种情况下串口通信在STM32的实际使用方面占有很大的比重,本文主要对串口通信做一个简要的总结。 + O, V# k" w- w/ f( Z* I4 E2 u
一、STM32里的串口通信 在STM32里,串口通信是USART,STM32可以通过串口和其他设备进行传输并行数据,是全双工,异步时钟控制,设备之间是点对点的传输。 6 }5 l5 X& ?7 V" K
对应的STM32引脚分别是RX和TX端。STM32的串口资源有USART1、USART2、USART3. ; G6 K* H, s: j' Z9 ?$ t
串口的几个重要的参数: 3 b9 R0 ]' x. L9 L3 q
波特率,串口通信的速率 # K5 i2 P: q+ \, D. [4 g% A: h# T
空闲,一般为高电平
1 Z8 k$ |& y: M) |
起始位,标志一个数据帧的开始,固定为低电平。当数据开始发送时,产生一个下降沿。(空闲–>起始位) & L3 f/ H& b0 O, o7 `0 C
数据位,发送数据帧,1为高电平,0为低电平。低位先行。
( {" ^% j/ s, I( S' q
比如 发送数据帧0x0F 在数据帧里就是低位线性 即 1111 0000
; c8 f7 D" H3 I9 G
校验位,用于数据验证,根据数据位的计算得来。有奇校验,偶校验和无校验。 $ I5 I2 v- K; D, J1 R5 d
停止位,用于数据的间隔,固定为高电平。数据帧发送完成后,产生一个上升沿。(数据传输–>停止位)
& |1 G L+ _6 F; e6 f2 {8 I
( p# h$ c( z; k( A& i0 a
下方就是一个字节数据的传输过程,从图中可以看出,串口发送的数据一般都是以数据帧的形式进行传输,每个数据帧都由起始位,数据位,停止位组成, 且停止位可变。 , G2 \3 U* O. O1 S/ s- U
% C* ~$ j' K2 z1 Y$ i+ K5 A7 a" e
o3 w! [- K/ A: Z
& Q/ B- U' U: B, t; @1 w- B2 H
二、串口的发送和接收 USART是STM32内部集成的硬件外设,可以根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可以自动接收RX引脚的数据帧时序,拼接成一个字节数据,存放在数据寄存器里。 3 _& n9 Z$ k8 A+ E* ], s) Y
当配置好USART的电路之后,直接读取数据寄存器,就可以自动发送数据和接收数据了。在发送和接收的模块有4个重要的寄存器 ; K. z6 ]; p% E. W2 x( A: @3 P$ A
% a% M3 Y4 @5 t/ a3 H& Y4 m# Y
下方为串口的发送和接收图解:
j4 S8 Z8 ?, J7 p/ v9 R- u
0 s3 h, W5 w3 P; o0 C+ Q
, H1 S9 R* T2 _6 o. I% e
串口发送 8 l8 B' u# J( l: g3 \
在配置串口的各个参数时,可以选择发送数据帧的数据位的大小,可选8位或9位。 ! K1 g" X6 R( ^2 p# R! C7 d3 q
串口发送数据实际上就是对发送数据寄存器TDR进行写操作。
! o. j5 Y8 ]% i& v( ]
1. 当串口发送数据时,会检测发送移位寄存器是不是有数据正在移位,如果没有移位,那么这个数据就会立刻转移到发送移位寄存器里。准备发送。 * g6 x6 [; d7 j2 B
2. 当数据移动到移位寄存器时,会产生一个TXE发送寄存器空标志位,该位描述如下。当TXE被置1,那么就可以在TDR写入下一个数据了。即发送下一个数据。
4 R2 t8 w& }% N z: U
^" @+ U% b1 G0 V/ V9 h4 e
7 O( v2 J+ o/ n" U
3. 发送移位寄存器在发送器控制的控制下,向右移位,一位一位的把数据传输到TX引脚。
2 p; l) @$ L8 }0 C, Y/ t/ c, l
, O1 {$ C9 I& K! J; q9 O* M
) \$ i, b& a2 ]: N0 e1 y
4. 数据移位完成后,新的数据就会再次从TDR转移到发送移位寄存器里来,依次重复1-3的过程。通过读取TXE标志位来判断是否发送下一个数据。
0 }: }& r/ l5 e; |; v/ K
串口接收 ! U& L/ E" l& j0 y
数据从RX引脚通向接收移位寄存器,在接收控制的控制下,一位一位的读取RX的电平,把第一位放在最高位,然后右移,移位八次之后就可以接收一个字节了。
2 E% o" } C" `2 \
当一个字节数据移位完成之后,这一个字节的数据就会整体的移到接收数据寄存器RDR里来。 3 o( }. D- K5 r4 G D. g
在转移时会置RXNE接收标志位,即RDR寄存器非空,下方为该位的描述。当被置1后,就说明数据可以被读出。
+ Y* e* n6 W( O/ X4 F- x" n: x* e. m9 y
8 ?. `+ w; e" ?: e0 f0 j! u
& G, y/ R2 l0 X7 g2 u. n. L
下图即为串口接收的工作流程
% R8 X- o! r9 _ w8 J) m8 A$ b
$ w5 }: a$ ]! z; R: d* f4 X2 V, B% ^8 R- X. O0 G* O2 | f! Q
三、串口在STM32中的配置
E' [# ~) T- x0 K( \, E
首先要明确几点:使用STM32串口外设中的哪一个?串口发送或者接收数据?串口相关的参数配置?发送或接收是否使用到中断?
# Y% Q4 ]4 @+ i
下方为串口发送的配置。 - z2 D8 _1 p7 j( C8 D1 U
1. RCC开启USART、串口TX/RX所对应的GPIO口 7 b3 j: Q' |9 D& _6 n- u" h
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE); //开启USART2的时钟* D+ X+ P% `# u) c
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //开启GPIOA的时钟
复制代码
* l( ^: @' ]/ b: L$ U
2. 初始化GPIO口 / e% X" L% P& J
这里注意哈,根据自己的需求来配置GPIO口,发送和接收是都需要还是只需要其中一个。然后对应的根据引脚定义表来初始化对应的GPIO口。 6 q" l9 T" Q2 w+ Z
USART2对应的引脚
; b4 w! A& s8 }. G; l2 q9 y
1 Y- q) ^. g/ z/ ~$ g) s) ?- o% q! X7 |( S
USART1对应的引脚 c6 x' Q6 Y% }
/ x0 @/ j6 U) q& [5 ^! I
0 L* e! z# V7 v4 {9 |
这里根据手册来看,RX引脚模式配置成浮空输入或者上拉输入。TX引脚模式配置成复用推挽输出。
. b, V+ ^4 G! g **比如我这里只初始化TX发送端**- //TX端% v; @( w9 O1 }0 O. R& t
- GPIO_InitTypeDef GPIO_InitStructure;
7 [" J+ L6 j( ]& _ H* N - GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; //复用推挽输出
, c; {- v" l: F0 W - GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2; //USART2对应的TX端为GPIOA2
5 j( u0 a( Z' J( V- Y: r+ P - GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //50MhZ# x+ z1 {# g" P! [# b
- GPIO_Init(GPIOA,&GPIO_InitStructure);
复制代码2 W# q! s" u; `& b( A
3. 串口初始化
: {- Y. p {$ K8 r0 I4 v# ]
注意哈,USART_Init()这个函数,是用来配置串口的相关参数的。 , w# F0 m3 \. o5 _! s9 E
USART_BaudRate 串口通信使用的波特率 一般是9600或者是115200,这里我们给9600
; g2 e6 I& i6 \
USART_HardwareFlowControl 是否选择硬件流触发,一般这个我们也不选,所以选择无硬件流触发。 5 t0 u# _- E- N( l/ l6 d
USART_Mode 这个参数要注意了哈,串口的模式,发送模式还是接收模式,还是两者都有 1 Y3 W8 W `, a5 s7 S
USART_Parity 校验位,可以选择奇偶校验和不校验。没有需求就直接无校验
9 J3 r: }# D. c4 i
USART_StopBits 停止位 有1、0.5、2位,我们这里选1位停止位 & A9 y# t: d8 U6 m. `8 l$ V
USART_WordLength 数据位 有8位和9位可以选择
' [! j/ r' q! k, j
- //串口初始化% l/ q6 Z% K, v7 D/ ` P) n
- USART_InitTypeDef USART_InitStruct;9 @0 |1 o6 M7 ?2 N% Y4 b1 _; m
- USART_StructInit(&USART_InitStruct); //初始默认值% k I y- P. y+ Q- s' l
- USART_InitStruct.USART_BaudRate=9600;6 J8 w1 h# B, y- L1 c+ h) f/ }
- USART_InitStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None; //不使用硬件流触发( A" _ n" C1 t) t$ w# F
- USART_InitStruct.USART_Mode=USART_Mode_Tx; //TX 发送模式
- k& o) X- v( N+ g - USART_InitStruct.USART_Parity=USART_Parity_No; //不选择校验
9 ~# G" a" S2 k% r# s/ O - USART_InitStruct.USART_StopBits=USART_StopBits_1; //停止位1位" S k& A( y- v" S8 q! @
- USART_InitStruct.USART_WordLength=USART_WordLength_8b; //数据位8位
, T @- `( y% V1 [ - USART_Init(USART2,&USART_InitStruct);
复制代码5 N: X( E4 a! V9 P3 ^0 m& ~
4. 串口使能 - //串口使能
( h) | n" C: i) r" h - USART_Cmd(USART2,ENABLE);
复制代码
3 \: ]/ N) x' w' O$ p# \/ z, {' c& z
5. 串口发送数据
* Q6 }$ S- o/ g, @6 R注意哈,我们要判断TXE标志位的状态。0,数据还没有被转移到移位寄存器;1,数据已经被转移到移位寄存器。
. w6 M! n5 C0 @% t: L2 O
当TXE标志位为1时,就说明可以发送下一个数据了。详细过程可看上面串口发送的解释。 - void Serial_SendByte(uint16_t Byte)- M2 ~9 P) ?9 ?' k
- {
# V* b' Z- t8 F' ] - USART_SendData(USART2,Byte);& c5 O8 y3 }+ s9 a" i% o
- //0 表示数据还未转移到移位寄存器 循环等待 1 数据已经被转移到了移位寄存器可以发送数据
$ m' Q) m" v% m/ A/ f - while(USART_GetFlagStatus(USART2,USART_FLAG_TXE)==RESET); //不需要手动清零 再次写入TDR时会自动清零
2 E( @1 v" w# R$ v) k% X+ A - }
复制代码
# B, Z* _- q2 g2 b7 H' L
经过上述五步的配置,单片机就可以通过串口发送数据了。 i [0 J1 _( a4 c' L7 m( ?, R
下方为发送数据图例,STM32向串口发送0x16数据
3 q1 C1 g$ F1 Y2 Z6 ^
! G% k: y# A" ~; n
F/ c) L2 F7 F( l! m4 E! z
3 s: ^# y: k m" o
四、串口接收的两种实现方式 上方是发送数据的例子,那么串口接收又该如何配置,又要在串口发送的例子上做哪些更改呢?
2 W8 |; Z2 `: D: F" t; S8 ?. G
这里我们可以通过查询或者中断的方式来进行接收数据的两种方式。
- z6 p3 {1 F- I( S& m
; t8 S6 S" G+ d* h% o+ T
1. 需要更改的地方
. @* A# j6 R& E5 {$ I: ^% `9 {0 v G. ^" r1 L6 u) v
既然我们要实现串口的接收,那么就要配置串口RX引脚,在串口模式中添加USART_Mode_RX模式。 8 [! `; H/ q r- x0 v
初始化RX引脚
/ ?4 t/ D$ H7 g4 Z; q) I
- //RX端( C" S( G" B) W( D) x6 z
- GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU; //上拉输入1 W8 n; N0 }3 D H
- GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3;- U/ ?- U" H7 Y+ Z# D" F' \4 c
- GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //50MhZ
3 h+ w' K% [& m% _ M - GPIO_Init(GPIOA,&GPIO_InitStructure);
复制代码
$ f- r( H4 F6 ^3 c6 u1 ~) H3 \" {3 Q( i% E0 b
添加串口模式 5 J" I$ m& W; o& Q' M# p1 a+ h
- USART_InitStruct.USART_Mode=USART_Mode_Tx|USART_Mode_Rx; //TX 发送模式 RX 接收模式
复制代码 ' i8 @) m3 V% Q" J) @' L4 x
# A0 u6 R6 A9 G* W8 ?. [8 D- q
2. 查询RXNE标志位 % |7 g7 o; _, g a1 j; M d
这里我们还是来看一看RXNE标志位的描述
4 D# k+ D% J1 s+ t
$ h9 H& u4 y( K% O5 a( R/ \$ m' q
上图描述,为0时数据没有收到,为1时收到了数据,数据可以从RDR里读出
3 }. I+ R2 O/ T6 c
所以在主程序里不断读取RXNE标志位,如果为1,表示数据可以读出 - uint8_t RX_Data;
. r0 x8 r% ~1 w5 @5 S' G - int main()
4 i2 H! q7 t0 b$ J - {
/ J( R# U8 `3 y/ ^7 \6 e - Serial_Init();
7 I/ y5 T, k: H - Serial_SendByte(0x16);* @0 W7 c& x3 q3 A5 ^& W0 ?
- while(1)7 N, F5 l7 v& r( g
- {
/ E" C$ R% ?" [, q2 e' r - if(USART_GetFlagStatus(USART2,USART_FLAG_RXNE)==SET) //0 循环等待 1 可以接收数据: Z0 e6 r4 t/ F
- {
/ x% J. A7 s4 G - RX_Data=USART_ReceiveData(USART2); 0 W; r% J9 [6 |, Q2 X0 o! L, Z2 {
- Serial_SendByte(RX_Data);) d( h ~" d4 T$ a
- }
; E5 s. o e4 V2 U) O6 l: V% k4 r2 r - }$ M* P* i+ I; f5 B, s" C8 }
- }
复制代码
; W% f8 H8 s$ y3 i
下图为程序现象:pc向单片机发送数据0x15,单片机接收数据0x15,并且把接收到的数据作为数据发送到pc,在pc上显示0x15。
1 x9 f7 _) S9 p8 D$ n! j7 G
& y L( O; M4 o+ I. |' g8 t; r: [
4 I. ~) l4 @# o3 a$ N) L/ t
3. 使用中断
, O* y( ^1 C8 L
- //开启中断输出控制
4 l. s* n+ A: f - USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);
: A, U( D9 b+ l6 A2 ? - ' S, E- m( J+ a* ]- w" W# Y
- //配置NVIC6 ]# M0 C' i/ V5 M( l; B; h0 s- R
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 5 X9 z8 p7 P+ l* M# `
- NVIC_InitTypeDef NVIC_InitStruct;
0 ?% ~( c/ n7 H D - NVIC_InitStruct.NVIC_IRQChannel=USART2_IRQn; //选择USART2的中断通道
' k& M( y1 ]" ~$ L - NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE; //中断使能
3 l* R$ w2 A3 n% \$ F - NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=1;
( }) @) E: `8 @! r q" ?- ^& f9 q - NVIC_InitStruct.NVIC_IRQChannelSubPriority=1;
5 q3 g2 g$ T( r, z$ }& Z. ] - NVIC_Init(&NVIC_InitStruct);
复制代码 8 u O' K) ?# A) r
4 P/ x) F% p0 H0 d
中断服务子函数
% G! H7 L6 R& ~. G, u. h
% R7 T& c5 @3 M! z5 _5 G4 T2 i中断服务子函数写好后,就可以在中断里读取接收到的数据了。
9 O1 e8 W7 G, T& r8 ?, c: x9 V# C
当接收到数据后,触发接收中断,主程序暂停执行。接收完数据后主程序回复执行。当接收到数据时,就触发中断。 - void USART2_IRQHandler(void)
1 Q7 U" {9 M3 n; t - {6 I: d$ \. `4 C6 s# Y* t; a
- if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET) //RXNE 标志位为1 表示可以接收数据9 `$ V. }8 T! a2 A# H4 B
- {8 ~/ s$ m: S: a( }0 q o- g
- RX_Data=USART_ReceiveData(USART2);
7 G7 K1 x4 s1 [& U7 i5 p - Flag=1;# ]9 h; H, ~. ^3 }. b. v
- USART_ClearITPendingBit(USART2,USART_IT_RXNE); //清除RXNE标志位
' D g7 ?/ d' s/ R; h - }
4 k8 u @, l8 c3 k6 } @& U - }
复制代码 3 z D. b t, r t
- uint8_t RX_Data;+ ^( @# a: W. |# y( G" ?8 U8 R) ]
- uint8_t Flag;% \: S/ T6 X. m2 i/ J, d- ]7 b
- I* ~- u5 K3 X$ U# R- int main()
4 B1 `* e+ o4 j* E - {
. t+ @+ @! Y: \7 h# O8 @" Z - Serial_Init();
- K* N! `& u, z" I+ E/ I# l - Serial_SendByte(0x16);
! I( R) i# `. F4 a - ( I: ^7 f( v4 T1 x2 y0 w% O
1 q. k! B! h; A0 V0 h- while(1)9 c2 |4 q/ G$ ~6 k" J5 p4 H) a" j
- {
3 z1 i F' Q. C9 E$ D - if(Flag==1)
/ O( l6 \8 X R( C9 ^* Y - {9 c1 T9 U5 G" X" d9 `
- Serial_SendByte(RX_Data);
2 p2 M$ Z2 z9 G4 q) N - }
) C- N8 f+ q7 T2 w7 [ - }
$ h3 ]7 T) O" X% T \1 ?5 P - }
" N4 b f$ B- \. A& L
. y" @$ [$ k. n- I* U8 l- void USART2_IRQHandler(void)
; E& x" q* n }1 S; H, T2 e v - {
: h k0 @4 s7 }$ I - if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET) //RXNE 标志位为1 表示可以接收数据0 P- N; D) W9 q# c
- {3 o+ r. g: B8 V1 W# w( @9 B# S: Z
- RX_Data=USART_ReceiveData(USART2);
. a! A8 [' B, ^4 J& Q: B - Flag=1;
; V8 Z) _8 v3 D# ?- z - USART_ClearITPendingBit(USART2,USART_IT_RXNE); //清除RXNE标志位
! V+ O% d& c# Q" w. ^ - }6 M8 l2 s4 e B% _- Y, v, A
- }
复制代码 6 ?! h4 [# Q4 \: d* q6 ]. b
* s! @: {$ s5 u& E/ @1 L N3 s6 d
下图为程序现象:可以看到,串口确实收到了数据,只是我把接收到的数据0xFE放在了while循环里,这说明数据接收是成功的,使用中断是可行的。 # }7 V4 x: ~- i& T3 g B
! K, X* ~% o' H: N; }
5 a- h8 G7 ?0 N8 @
* n( r l3 e9 v8 k# @! W3 h总结 到这里,就大致总结了串口的发送和接收。 + E$ T1 b7 J. g* w
串口的配置,使用查询或者中断来接收数据。
# Q, f5 d0 b, l6 @
串口的使用会很常用到,所以在这里对串口做一个总结,也算是对之前知识的一个回顾和总结,加强印象。
% B- G0 e5 S: f
如果有什么写的不对的地方,欢迎指正!
& v$ i9 d- P2 ]/ r
转载自: 古月居 如有侵权请联系删除 # X5 ^, [- l9 |5 m; Q3 `; f
1 |$ F0 M5 a: N; t% q4 @6 p6 r, G O; ]9 R" T& W
|