STM32的串口通信
! h) N+ \9 R0 G, s1 x) b本文在于记录自己的学习过程中遇到的问题和总结,各种情况下串口通信在STM32的实际使用方面占有很大的比重,本文主要对串口通信做一个简要的总结。 . \% w2 ~; a' \
一、STM32里的串口通信 在STM32里,串口通信是USART,STM32可以通过串口和其他设备进行传输并行数据,是全双工,异步时钟控制,设备之间是点对点的传输。
a( F `" |9 K+ Y
对应的STM32引脚分别是RX和TX端。STM32的串口资源有USART1、USART2、USART3. ! C1 f3 e* z, s, u% G5 k
串口的几个重要的参数: ; D% u+ _! K% R$ E5 a* G5 p
波特率,串口通信的速率
% z- z7 [7 G3 f% x
空闲,一般为高电平 4 j- L1 K4 ^) u; `0 Y; v8 F% Z- ~
起始位,标志一个数据帧的开始,固定为低电平。当数据开始发送时,产生一个下降沿。(空闲–>起始位)
1 \* k9 p1 T |; C' S% Y3 V
数据位,发送数据帧,1为高电平,0为低电平。低位先行。
& h- H; r0 Y* \
比如 发送数据帧0x0F 在数据帧里就是低位线性 即 1111 0000 % ^, B( ^9 N/ d1 \
校验位,用于数据验证,根据数据位的计算得来。有奇校验,偶校验和无校验。 . J4 n1 D! y0 Y( Y
停止位,用于数据的间隔,固定为高电平。数据帧发送完成后,产生一个上升沿。(数据传输–>停止位)
! A; g4 Y0 _2 k. z! X
% M9 `8 h8 h' o1 p( Z+ e
下方就是一个字节数据的传输过程,从图中可以看出,串口发送的数据一般都是以数据帧的形式进行传输,每个数据帧都由起始位,数据位,停止位组成, 且停止位可变。 7 l& C% |& p) H( g9 M) m {
3 Z; p8 c6 P* z; v: V1 H$ H+ A1 z# C) {
9 [, {1 u2 a; y; m9 H( |
/ U4 a0 H" d; C8 i9 U
二、串口的发送和接收 USART是STM32内部集成的硬件外设,可以根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可以自动接收RX引脚的数据帧时序,拼接成一个字节数据,存放在数据寄存器里。 / S5 m3 l% x w' B P" s5 F4 @
当配置好USART的电路之后,直接读取数据寄存器,就可以自动发送数据和接收数据了。在发送和接收的模块有4个重要的寄存器
* P- v7 B; }( ?) ] V! w
发送数据寄存器TDR % T) L! j9 f" }
发送移位寄存器,把一个字节的数据一位一位的移出去
) N, k- @. X$ x
接收数据寄存器RDR
( {6 t6 U3 Z% b' M
接收移位寄存器,把一个字节的数据
! s6 Q; z, M4 P
x+ r( L/ A' f( O- ]
下方为串口的发送和接收图解: - F% X, g) i x$ @" N, x
+ s. L9 U: {8 e/ q2 x
; R, L4 k! j' @9 X
串口发送
( R* o1 u6 H$ N3 J- F. m' |' a; F R" X
在配置串口的各个参数时,可以选择发送数据帧的数据位的大小,可选8位或9位。 ( f( _" Z) _, Z- K- D( E) y
串口发送数据实际上就是对发送数据寄存器TDR进行写操作。
) o: N% U, A: k+ q
1. 当串口发送数据时,会检测发送移位寄存器是不是有数据正在移位,如果没有移位,那么这个数据就会立刻转移到发送移位寄存器里。准备发送。 , k- j/ j& S; y
2. 当数据移动到移位寄存器时,会产生一个TXE发送寄存器空标志位,该位描述如下。当TXE被置1,那么就可以在TDR写入下一个数据了。即发送下一个数据。
$ b' ^+ a) T/ z* X& [3 w0 F
& d, t6 f( e- a% | w2 W
0 y+ F0 \8 r9 e( J1 R9 d
3. 发送移位寄存器在发送器控制的控制下,向右移位,一位一位的把数据传输到TX引脚。 / J6 R4 J. w( ]
; y+ W% _1 y7 p6 U& E& j5 ~9 I& r) |" |9 a0 q- @ Q
4. 数据移位完成后,新的数据就会再次从TDR转移到发送移位寄存器里来,依次重复1-3的过程。通过读取TXE标志位来判断是否发送下一个数据。
! j+ Z6 ^0 W% q+ Q
串口接收
5 L. E( s+ `. {) D9 C% A* @
数据从RX引脚通向接收移位寄存器,在接收控制的控制下,一位一位的读取RX的电平,把第一位放在最高位,然后右移,移位八次之后就可以接收一个字节了。
) S* C% m* z( D R
当一个字节数据移位完成之后,这一个字节的数据就会整体的移到接收数据寄存器RDR里来。 5 T( {4 A4 L* {+ i* W" O9 D! J
在转移时会置RXNE接收标志位,即RDR寄存器非空,下方为该位的描述。当被置1后,就说明数据可以被读出。
" _9 Z8 \; P% r
* i0 T& @6 Z% q6 g D1 k
8 y; M% T; Z+ Z7 ~' l- e
下图即为串口接收的工作流程
) Q7 F! d/ Q9 k+ N, \; A( t6 O
( b! n% C# c; l8 q% P( U
; h# @* z6 t& P$ ]/ D6 U0 R
三、串口在STM32中的配置 8 [( G5 ?$ y* z, l3 l% Q
首先要明确几点:使用STM32串口外设中的哪一个?串口发送或者接收数据?串口相关的参数配置?发送或接收是否使用到中断?
$ O3 a( o9 |* I5 S5 w* B& r
下方为串口发送的配置。 : u- g! c2 O B- ]/ e
1. RCC开启USART、串口TX/RX所对应的GPIO口 - @5 p# ^8 F p7 s3 t' U: V* Z
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE); //开启USART2的时钟% s$ w" Y6 Q: X8 c ?$ i
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //开启GPIOA的时钟
复制代码
$ K( Q( h) s5 `5 }( Q
2. 初始化GPIO口
3 J9 F, J; O/ o5 a8 h* }/ O" G
这里注意哈,根据自己的需求来配置GPIO口,发送和接收是都需要还是只需要其中一个。然后对应的根据引脚定义表来初始化对应的GPIO口。
, F, y A) ?; R: E, ?# h y
USART2对应的引脚 " ^' G$ E% \/ g1 r4 s2 ?; x' c4 {
F5 m, D! B# [' u T2 L
. E0 k' p/ r8 G+ m+ m1 k
USART1对应的引脚
( O8 w' X3 k, K- h
) d) {+ z! P- L5 B% q$ \$ m8 t0 i5 R U2 L9 k K
这里根据手册来看,RX引脚模式配置成浮空输入或者上拉输入。TX引脚模式配置成复用推挽输出。 ; ]: t9 q* ^3 m. {% M/ r
**比如我这里只初始化TX发送端**- //TX端/ w, S- t. m7 ]' Y' Z
- GPIO_InitTypeDef GPIO_InitStructure;$ v; i8 c, j5 T2 Y, R* V
- GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; //复用推挽输出
# z( H' n( Y3 s7 l1 \5 ^% F% a5 ~ - GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2; //USART2对应的TX端为GPIOA2
0 l) D/ r% L, Z, q: z- t& n7 E9 Z- F - GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //50MhZ( i# l2 H. I: |$ R2 D
- GPIO_Init(GPIOA,&GPIO_InitStructure);
复制代码
) v- q7 @) z% j7 h3 N
3. 串口初始化 1 r1 E" \4 v9 f7 q4 K7 u: |
注意哈,USART_Init()这个函数,是用来配置串口的相关参数的。
* `0 P: @; r: n- b( R
USART_BaudRate 串口通信使用的波特率 一般是9600或者是115200,这里我们给9600 ' v% j( O v2 d# q* {
USART_HardwareFlowControl 是否选择硬件流触发,一般这个我们也不选,所以选择无硬件流触发。 5 ^) X! _( H2 L/ W- p
USART_Mode 这个参数要注意了哈,串口的模式,发送模式还是接收模式,还是两者都有 9 u: E& _; U3 f1 g3 Q1 I0 n1 L
USART_Parity 校验位,可以选择奇偶校验和不校验。没有需求就直接无校验
, d" ~6 e$ n* J" K( {
USART_StopBits 停止位 有1、0.5、2位,我们这里选1位停止位 8 ~! O4 d5 Q, p5 \) U3 m
USART_WordLength 数据位 有8位和9位可以选择 , J4 q* P6 n. C6 x
- //串口初始化
7 I9 D- ^. x/ X) R2 O - USART_InitTypeDef USART_InitStruct; ]8 i- |. u1 ]5 `/ ]4 R! l V
- USART_StructInit(&USART_InitStruct); //初始默认值. Q, m1 X8 b* o, W/ ^! N
- USART_InitStruct.USART_BaudRate=9600;
5 _' S% N) J! r/ D. o3 x9 n- Q - USART_InitStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None; //不使用硬件流触发& c; I; l/ r( o+ ?" J# G4 d" M) J
- USART_InitStruct.USART_Mode=USART_Mode_Tx; //TX 发送模式* K( w" Q3 u5 u: L+ w* k+ m
- USART_InitStruct.USART_Parity=USART_Parity_No; //不选择校验8 g) O/ z! s* l* v& V
- USART_InitStruct.USART_StopBits=USART_StopBits_1; //停止位1位' j3 G9 N6 Y; e1 \; e
- USART_InitStruct.USART_WordLength=USART_WordLength_8b; //数据位8位4 Y& M- K; |# K0 L% H
- USART_Init(USART2,&USART_InitStruct);
复制代码
, E/ D' [- h4 ]& b( q
4. 串口使能 - //串口使能- P) d9 e* q! a
- USART_Cmd(USART2,ENABLE);
复制代码# l/ ?+ U9 @5 G9 t
5. 串口发送数据 / W( O0 u6 G( g. n0 A0 U3 x
注意哈,我们要判断TXE标志位的状态。0,数据还没有被转移到移位寄存器;1,数据已经被转移到移位寄存器。
, u; F" a* W$ I4 ~: _+ p; M
当TXE标志位为1时,就说明可以发送下一个数据了。详细过程可看上面串口发送的解释。 - void Serial_SendByte(uint16_t Byte)' q7 M% |: y1 Y% u* A G2 O
- {. X: n+ b- _% m
- USART_SendData(USART2,Byte);
3 a, X' f8 L4 T0 C) G1 c$ v - //0 表示数据还未转移到移位寄存器 循环等待 1 数据已经被转移到了移位寄存器可以发送数据2 |! H0 N$ n6 o( C; P' g
- while(USART_GetFlagStatus(USART2,USART_FLAG_TXE)==RESET); //不需要手动清零 再次写入TDR时会自动清零
0 R- I. @. r8 Y, G9 U5 P( E9 |2 Q - }
复制代码6 Z- M/ K8 H- [1 P1 a+ C
经过上述五步的配置,单片机就可以通过串口发送数据了。 & S4 P9 I* h, g
下方为发送数据图例,STM32向串口发送0x16数据
0 |% H! r: j+ Y0 N8 o( t) p1 R
% Q4 }$ r9 p+ O8 x8 m% l+ v: J/ c* D' n9 p
5 q# \! u& H. m" ^
四、串口接收的两种实现方式 上方是发送数据的例子,那么串口接收又该如何配置,又要在串口发送的例子上做哪些更改呢? * Y1 I: N8 d7 w0 v2 h/ ?5 s# Y: e
这里我们可以通过查询或者中断的方式来进行接收数据的两种方式。
1 [ l+ d5 v0 m3 c# p
4 ?" R6 {0 u5 j8 h$ ?
1. 需要更改的地方* w* j/ f, {2 b
w _. m- }; s: K1 g% ?1 F 既然我们要实现串口的接收,那么就要配置串口RX引脚,在串口模式中添加USART_Mode_RX模式。
+ t& V$ m4 j, {& F, l8 a' I
初始化RX引脚
# e7 U1 b. O+ z7 \6 h
- //RX端
* f. S4 O, ^4 p - GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU; //上拉输入 M& ^9 g& O* Q3 f( i# M
- GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3;( U: W# s- a$ r7 Y! l# }) p* ]
- GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //50MhZ( K! l5 q: n: W/ t j3 L2 |& y
- GPIO_Init(GPIOA,&GPIO_InitStructure);
复制代码
}2 e3 M( I, f6 \( W+ r- |: M& L' o6 F$ {9 D0 l: r# S# Y/ x9 W
添加串口模式
8 w- R: {# u; N- K7 M4 o6 W/ }
- USART_InitStruct.USART_Mode=USART_Mode_Tx|USART_Mode_Rx; //TX 发送模式 RX 接收模式
复制代码 + o1 k: y1 d5 Y/ v; o
: q0 |+ p8 v' f: x
2. 查询RXNE标志位 ; i9 Z. v+ q3 `6 f% f. U5 f! W" J
这里我们还是来看一看RXNE标志位的描述
% `! ~ b# X% ^ Z) e, B9 {. D# {
. {# _7 ] L8 o: a0 h/ P2 x
4 y5 W: R! {! f A
上图描述,为0时数据没有收到,为1时收到了数据,数据可以从RDR里读出
. N6 O) I& e! K) U9 j. g/ T( e
所以在主程序里不断读取RXNE标志位,如果为1,表示数据可以读出 - uint8_t RX_Data;7 B; Z" N) V5 g: i6 I) ]2 e" |
- int main()
! y' D) {) B" a2 j, x$ Z1 G - { ; \1 P# M5 \9 I! `
- Serial_Init();
- e5 v1 `" h9 K. D6 e* o0 K3 g - Serial_SendByte(0x16);: f. a4 M. J. F% o# D" Q
- while(1)
9 I' Q% @% G1 Y - {
( t& Q% S3 R! w8 N; b7 f - if(USART_GetFlagStatus(USART2,USART_FLAG_RXNE)==SET) //0 循环等待 1 可以接收数据
. w6 H" G0 C4 g! W! Y - {
, S( a0 V2 T3 ?* \" N- M - RX_Data=USART_ReceiveData(USART2);
+ W8 @! ?. v& _" p5 Z0 a - Serial_SendByte(RX_Data);* y( c' c, A6 N& C# a! c
- }* `* ]2 e3 {7 x
- }# A! R$ s) p/ @' X& y0 }3 x
- }
复制代码+ j- k" p N; S
下图为程序现象:pc向单片机发送数据0x15,单片机接收数据0x15,并且把接收到的数据作为数据发送到pc,在pc上显示0x15。
- N2 v& `4 j6 z- {
3 a0 U/ D- v$ O9 F
2 M8 Z# l( E' ^9 v& ^" f( |; i4 e9 g7 d
3. 使用中断 ( A4 S# o7 {0 e+ b8 Q
- //开启中断输出控制$ q, ]4 R0 ]+ d' p3 D9 h9 J
- USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);; u4 ?1 ]+ B( \: u7 V3 ?3 o
- 6 ^' m- @2 d" ~/ `; g* |3 q2 D
- //配置NVIC
. X9 x5 ]+ u X* Z2 z7 | Y - NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); q8 i, ]" _: E% O- Y1 s
- NVIC_InitTypeDef NVIC_InitStruct;
0 }2 s; v$ O' o2 |8 w3 W, @ - NVIC_InitStruct.NVIC_IRQChannel=USART2_IRQn; //选择USART2的中断通道
0 U2 I0 y5 f+ D" [1 d - NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE; //中断使能
0 J5 b/ ~" `" J# \ - NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=1;
0 G- ?3 `- v" j% ?! I9 \4 E/ C# j0 D - NVIC_InitStruct.NVIC_IRQChannelSubPriority=1;2 {% P4 o' P* W* C
- NVIC_Init(&NVIC_InitStruct);
复制代码 % B7 _* }8 X3 \- n7 w
8 L+ x. p9 w* e, u- R% A
中断服务子函数
4 x) ]$ k1 D5 `$ Y: F& c: X
! O* N2 Q" v) L
中断服务子函数写好后,就可以在中断里读取接收到的数据了。
! t, y4 t# Y: X( L+ l6 [; Y当接收到数据后,触发接收中断,主程序暂停执行。接收完数据后主程序回复执行。当接收到数据时,就触发中断。
- void USART2_IRQHandler(void)* T" e. ^" p2 O I
- {
0 t! A, j' k) \+ j" T1 M) F( \ - if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET) //RXNE 标志位为1 表示可以接收数据9 [$ O5 Q3 u# ^2 d! E" K o
- {; L, T7 @& e- q2 \' K1 z. h3 W
- RX_Data=USART_ReceiveData(USART2);
3 X" y9 |. X; s - Flag=1;* p- z# p5 P, ~# P' }% l
- USART_ClearITPendingBit(USART2,USART_IT_RXNE); //清除RXNE标志位# N: ^) S8 B8 w5 S- C. V, i
- }* z+ F8 g0 Z2 v5 T6 ^% r% @& t
- }
复制代码
& w4 g6 s% U( d, H主程序测试 ! X. L! A) k) e: E! \% w$ j
- uint8_t RX_Data;' M( X3 a3 y) G
- uint8_t Flag;
" o, s1 X; S7 o" Y, x1 t
0 m9 S4 q0 k6 D+ | N- int main()) ~- w" w( I9 z" I
- {9 y( }: N7 i/ H+ C5 M) G5 Q- t
- Serial_Init();7 V- P3 l" }# k8 v7 H
- Serial_SendByte(0x16); C" J* Q: ]6 L* o3 h& a
! E; M' E) y3 n7 Y7 O
# {3 K% u' ~6 R, A- while(1)
/ q+ r) u# Z( T5 S3 m$ K - {7 h% P( ~: k7 P
- if(Flag==1)
3 C r% y& Y# d1 h8 X" `! E& n% b - {
# G+ E6 I, X8 c - Serial_SendByte(RX_Data);
' o0 |/ `' P3 C( z' H k - }) m8 b, K3 [% k3 l5 o
- }% K: Z# R) {7 h
- }
" i C/ P5 b# p R# q
d' ~& ?" I* X! u9 ^- void USART2_IRQHandler(void)
. d( |; E; \8 X; h - {
* r9 P# r/ D3 V. Z2 L0 ?4 L - if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET) //RXNE 标志位为1 表示可以接收数据. ~/ m! M- A( N
- {" A; h; O% n# q7 }% ]3 F
- RX_Data=USART_ReceiveData(USART2);
, m& r5 U9 A" j% k# k - Flag=1;: \/ g7 K% C( b
- USART_ClearITPendingBit(USART2,USART_IT_RXNE); //清除RXNE标志位
: @" F5 | q3 z- \) D2 b" o" u - }
# U3 {; o3 E% f - }
复制代码
7 F3 \; f8 H4 R" W2 A' N$ M! ?( B$ T j
下图为程序现象:可以看到,串口确实收到了数据,只是我把接收到的数据0xFE放在了while循环里,这说明数据接收是成功的,使用中断是可行的。 5 I) K. I# q+ S! w8 w
, S1 N! d. R, ]2 q
8 p8 y2 U5 y9 O& m
2 a% x s7 Q" M, Z8 T总结 到这里,就大致总结了串口的发送和接收。
( n6 Z/ }5 y3 _: z5 k+ R2 P
串口的配置,使用查询或者中断来接收数据。 4 o& V5 D( y5 g6 N6 _/ @
串口的使用会很常用到,所以在这里对串口做一个总结,也算是对之前知识的一个回顾和总结,加强印象。
" y! {( j8 T3 y5 O8 q
如果有什么写的不对的地方,欢迎指正! : j+ h5 y/ j; G: E
转载自: 古月居 如有侵权请联系删除
/ _8 m9 W2 N) d- s- J! r c; i ; ? j- B2 D2 M7 X' u
6 ]9 E0 N! p0 ]5 a5 P( t2 ` |