|
STM32的串口通信
" K- k, Z+ c( Z6 C本文在于记录自己的学习过程中遇到的问题和总结,各种情况下串口通信在STM32的实际使用方面占有很大的比重,本文主要对串口通信做一个简要的总结。
& }, g' b7 N7 ?一、STM32里的串口通信 在STM32里,串口通信是USART,STM32可以通过串口和其他设备进行传输并行数据,是全双工,异步时钟控制,设备之间是点对点的传输。
" k0 w0 ?& E1 D9 s' E A* v
对应的STM32引脚分别是RX和TX端。STM32的串口资源有USART1、USART2、USART3.
/ e+ u4 E) C6 J9 ]1 o
串口的几个重要的参数: 5 }# v% g3 y# D! z! f( j
波特率,串口通信的速率
1 b5 Y: J; R, m: V) @' j
空闲,一般为高电平 3 a' a0 S+ s T: Y$ k
起始位,标志一个数据帧的开始,固定为低电平。当数据开始发送时,产生一个下降沿。(空闲–>起始位) 9 [: L- P( w% C0 J# h, U' }0 H& z: k4 F
数据位,发送数据帧,1为高电平,0为低电平。低位先行。
0 J7 H% v3 i% Q& z7 S4 i9 G
比如 发送数据帧0x0F 在数据帧里就是低位线性 即 1111 0000
- D4 l2 t' ^( ?' i5 o: y9 K) N% O
校验位,用于数据验证,根据数据位的计算得来。有奇校验,偶校验和无校验。
) n/ Y! e$ D' x
停止位,用于数据的间隔,固定为高电平。数据帧发送完成后,产生一个上升沿。(数据传输–>停止位) 5 _; K F$ l- k2 G
8 F& T/ c8 c# C. n& Y+ i1 A; p
下方就是一个字节数据的传输过程,从图中可以看出,串口发送的数据一般都是以数据帧的形式进行传输,每个数据帧都由起始位,数据位,停止位组成, 且停止位可变。 * P7 j3 q* D$ C
- K" `3 b2 U2 ?1 J$ ^( R8 [: o7 Y" q1 T0 r: k
M- a/ X8 c3 b v% a
二、串口的发送和接收 USART是STM32内部集成的硬件外设,可以根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可以自动接收RX引脚的数据帧时序,拼接成一个字节数据,存放在数据寄存器里。 0 x" j3 {5 p+ d
当配置好USART的电路之后,直接读取数据寄存器,就可以自动发送数据和接收数据了。在发送和接收的模块有4个重要的寄存器 1 e, x+ ^, K. V+ e, _) p# b
: `, x( ~5 N; Y
下方为串口的发送和接收图解:
% _+ M" @8 h# {1 j9 [ ~3 J0 p
2 H" K* ]$ f7 o5 h2 B( P0 M3 D" U' i5 F& v* v2 E
串口发送 . b+ K/ J; Y5 @0 c$ }! W" p
在配置串口的各个参数时,可以选择发送数据帧的数据位的大小,可选8位或9位。
, i5 f2 Q/ R" p
串口发送数据实际上就是对发送数据寄存器TDR进行写操作。 4 x* W& V' }, w0 L Z, K& N
1. 当串口发送数据时,会检测发送移位寄存器是不是有数据正在移位,如果没有移位,那么这个数据就会立刻转移到发送移位寄存器里。准备发送。 ! c! U' F( g4 `* |
2. 当数据移动到移位寄存器时,会产生一个TXE发送寄存器空标志位,该位描述如下。当TXE被置1,那么就可以在TDR写入下一个数据了。即发送下一个数据。
" e$ Y: j& j& d; Q; {
" b* R& t9 a0 J* \" W; Y' V
* d- J/ |$ z7 v# {: }
3. 发送移位寄存器在发送器控制的控制下,向右移位,一位一位的把数据传输到TX引脚。
# U2 V! L9 H1 B$ T" a! O+ X/ l
. N3 k8 U* x) v; z4 V1 X
0 M. Y6 ^, g r
4. 数据移位完成后,新的数据就会再次从TDR转移到发送移位寄存器里来,依次重复1-3的过程。通过读取TXE标志位来判断是否发送下一个数据。
9 M& |9 |) E2 G6 m0 h! U C
串口接收
+ d6 m& s q$ Q! w8 W0 q
数据从RX引脚通向接收移位寄存器,在接收控制的控制下,一位一位的读取RX的电平,把第一位放在最高位,然后右移,移位八次之后就可以接收一个字节了。 * i5 b2 p: v9 t3 a# x% r5 X
当一个字节数据移位完成之后,这一个字节的数据就会整体的移到接收数据寄存器RDR里来。
H" _+ }1 v7 F' J, h7 I+ j
在转移时会置RXNE接收标志位,即RDR寄存器非空,下方为该位的描述。当被置1后,就说明数据可以被读出。 ; t5 Z, F. w- u# O
6 n% K7 T+ w! ?; y* M/ S
7 K9 N# v$ m( F- c! p
下图即为串口接收的工作流程 3 r7 A. r" u5 c: L- C& A, u z
2 g4 {4 \. g" C" o: d0 ^; J5 j c/ {, J
三、串口在STM32中的配置
# o ?6 X3 f$ W+ J- D) j
首先要明确几点:使用STM32串口外设中的哪一个?串口发送或者接收数据?串口相关的参数配置?发送或接收是否使用到中断?
7 P% g7 b2 Q9 T
下方为串口发送的配置。
2 F! [! o- n( [3 e
1. RCC开启USART、串口TX/RX所对应的GPIO口 4 F4 [+ ^9 r1 p, i
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE); //开启USART2的时钟
6 a) A# ~7 D/ | - RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //开启GPIOA的时钟
复制代码
! a! C9 N4 B* ]( k4 ]
2. 初始化GPIO口 ; s; `8 C- g, d% t6 b/ J; l
这里注意哈,根据自己的需求来配置GPIO口,发送和接收是都需要还是只需要其中一个。然后对应的根据引脚定义表来初始化对应的GPIO口。 + e+ U/ \/ [6 J$ b& ?4 T8 |
USART2对应的引脚 2 \, I2 |$ Q, R j) \$ w1 S4 m
3 ^1 Y( \3 K- t7 h3 k
/ x: n6 N1 q7 t$ a5 e1 w
USART1对应的引脚 : `2 c4 m' A3 @! _& f4 w) ^
( e0 Y) P' G3 M6 w
. B3 R+ u- _( U% G. A
这里根据手册来看,RX引脚模式配置成浮空输入或者上拉输入。TX引脚模式配置成复用推挽输出。
% x% l4 H7 j$ s! a **比如我这里只初始化TX发送端**- //TX端
8 O2 {9 L! `6 j1 Q1 Z7 ? - GPIO_InitTypeDef GPIO_InitStructure;
' a/ K" {7 @: ~ - GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; //复用推挽输出, k1 c$ G' u' c5 E0 h/ @
- GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2; //USART2对应的TX端为GPIOA2
% `$ J# v$ H% A. @% w, Z! h - GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //50MhZ4 B7 y" w$ P3 X: W5 e
- GPIO_Init(GPIOA,&GPIO_InitStructure);
复制代码" n4 T# Y! R) G/ t) I" R3 M
3. 串口初始化
, ^6 l# V; m0 ~9 T1 n8 r+ n
注意哈,USART_Init()这个函数,是用来配置串口的相关参数的。 8 Y4 O9 I) H0 }- U4 ^& c# I
USART_BaudRate 串口通信使用的波特率 一般是9600或者是115200,这里我们给9600
* Z& V% K# ? t' ^ l( c' N+ C, N
USART_HardwareFlowControl 是否选择硬件流触发,一般这个我们也不选,所以选择无硬件流触发。 2 z$ p" @% B- o/ i$ @/ i
USART_Mode 这个参数要注意了哈,串口的模式,发送模式还是接收模式,还是两者都有 ) V7 ^1 A& H) O, u
USART_Parity 校验位,可以选择奇偶校验和不校验。没有需求就直接无校验
J) O7 b4 T, C4 {3 o4 z) k8 S; c
USART_StopBits 停止位 有1、0.5、2位,我们这里选1位停止位
6 I4 N8 K( i2 G3 P
USART_WordLength 数据位 有8位和9位可以选择
" z( l2 W$ C9 m6 q$ _
- //串口初始化
$ ~, K. S+ Z, L! Z - USART_InitTypeDef USART_InitStruct;
1 P7 K* B0 k. w! y% I7 k7 B+ e8 [8 W - USART_StructInit(&USART_InitStruct); //初始默认值
3 {. w" p( I9 t - USART_InitStruct.USART_BaudRate=9600;
! O- L1 K( ?$ h) F, q' m5 p( q' J - USART_InitStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None; //不使用硬件流触发" c+ M" [/ C O/ A$ G
- USART_InitStruct.USART_Mode=USART_Mode_Tx; //TX 发送模式5 M9 y9 w( A& M0 Q G: a
- USART_InitStruct.USART_Parity=USART_Parity_No; //不选择校验
C0 Q- H* k( }: v: ] {% | - USART_InitStruct.USART_StopBits=USART_StopBits_1; //停止位1位+ k+ ~ ^9 T" ^# ?- F8 H
- USART_InitStruct.USART_WordLength=USART_WordLength_8b; //数据位8位' d0 Y% s, l8 I) `
- USART_Init(USART2,&USART_InitStruct);
复制代码- B3 {, j" P8 W+ L% f* B3 H7 y1 w
4. 串口使能 - //串口使能
% P: r( B6 r+ T, V- o+ d - USART_Cmd(USART2,ENABLE);
复制代码
2 A( M7 q0 m% W# J$ L9 R( p$ |
5. 串口发送数据
1 q1 y* O3 B) B! l) J- P注意哈,我们要判断TXE标志位的状态。0,数据还没有被转移到移位寄存器;1,数据已经被转移到移位寄存器。
+ e* {6 B* C- }1 R
当TXE标志位为1时,就说明可以发送下一个数据了。详细过程可看上面串口发送的解释。 - void Serial_SendByte(uint16_t Byte): x- u j$ F' _ H% U" W, Y" E/ r
- {5 |# x! X2 m2 n! A% _! W
- USART_SendData(USART2,Byte);
' G. y1 `4 d/ h! N4 {: f - //0 表示数据还未转移到移位寄存器 循环等待 1 数据已经被转移到了移位寄存器可以发送数据
( X7 O- g! C$ `0 B$ s1 F7 H - while(USART_GetFlagStatus(USART2,USART_FLAG_TXE)==RESET); //不需要手动清零 再次写入TDR时会自动清零 : U. e) G7 z1 o4 A0 S5 ?% W
- }
复制代码
8 P: v1 D2 ^! n. w2 i7 g6 r
经过上述五步的配置,单片机就可以通过串口发送数据了。 p" s# C/ e: o7 \4 v$ Y* _
下方为发送数据图例,STM32向串口发送0x16数据
+ T& Z' Z; z$ Y S5 w( P
' r- Q3 Q& ?. o& ~
. f5 y- E- _; X+ F% Z0 ]. }! ]+ {, ?9 _
% L) ~8 @/ R, b; j# ]* V- `
四、串口接收的两种实现方式 上方是发送数据的例子,那么串口接收又该如何配置,又要在串口发送的例子上做哪些更改呢? 3 E8 i. V5 D) W9 ~# _3 N" i! z, l
这里我们可以通过查询或者中断的方式来进行接收数据的两种方式。
9 K8 f6 j' {' R
: O* y k4 l0 `( D
1. 需要更改的地方( x! c) G* ` I. A3 u& U- o1 @
9 {+ P9 @2 \' b, a2 O2 B3 l
既然我们要实现串口的接收,那么就要配置串口RX引脚,在串口模式中添加USART_Mode_RX模式。
/ e% p: k# ?, S7 t
初始化RX引脚
8 q% D! \; k( {* C3 w& d+ Y
- //RX端/ X: a; x& c/ p7 \
- GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU; //上拉输入* ] A0 ~/ {, @4 {+ u1 H
- GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3;
3 \* c5 {! ]2 g1 c l" T3 g - GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //50MhZ* n1 ^* r6 V9 I O2 ]& J# {6 w
- GPIO_Init(GPIOA,&GPIO_InitStructure);
复制代码 1 ]$ K' r( ^2 N5 T" x
4 P' }& R, W2 s6 }
添加串口模式
, p- _, C8 t5 J+ u, O: [
- USART_InitStruct.USART_Mode=USART_Mode_Tx|USART_Mode_Rx; //TX 发送模式 RX 接收模式
复制代码 % ^. l# Q- C9 l) d# w6 R
! x2 \- M. ~9 J& F3 ^
2. 查询RXNE标志位
% h& w5 W H9 `/ t( M; o
这里我们还是来看一看RXNE标志位的描述
" \8 j0 \9 r& K# O
* Y' U7 V/ I' k5 {# g3 o% q8 n
, |8 O" n) C, Q! c9 K
上图描述,为0时数据没有收到,为1时收到了数据,数据可以从RDR里读出 ; l; U, b: ?9 D: K
所以在主程序里不断读取RXNE标志位,如果为1,表示数据可以读出 - uint8_t RX_Data;) {( `: T' G5 K& ]. K1 [
- int main()
% [: v/ r2 I8 x( C- u - { 6 N- n4 E9 _ P- h0 Z9 ^; [4 ?7 h m
- Serial_Init();; P6 L# h8 F% P: l5 d
- Serial_SendByte(0x16);
) M3 r, n0 L; l; b& ~ - while(1)4 E" ]2 x. O0 j8 X7 \. a: E* ~- S
- {
; D8 e! ]1 w4 B2 C" B - if(USART_GetFlagStatus(USART2,USART_FLAG_RXNE)==SET) //0 循环等待 1 可以接收数据
* m1 ]% F/ h5 g& y$ L7 ~9 Y. E - {* _4 E, X& Q5 M: e2 d; [9 z. `% i
- RX_Data=USART_ReceiveData(USART2);
$ `$ ?7 M- g+ d Z( w* O. K - Serial_SendByte(RX_Data);0 \3 O3 p" F) P+ b _9 e$ E% J G
- }: ^4 C: }. O: P2 B6 U- q4 D" C
- }
& r R4 \& o. T ~) r - }
复制代码
+ o5 I* @ u0 e7 @! Q& s
下图为程序现象:pc向单片机发送数据0x15,单片机接收数据0x15,并且把接收到的数据作为数据发送到pc,在pc上显示0x15。 ; Q! W/ c6 g- Z# G
, w' V% ]2 ~0 V3 ~7 S& L6 i
$ P# {% u9 r0 N# r6 J3 x( s) }
3. 使用中断 : [, A' \/ S/ m) V# ]2 t
- //开启中断输出控制5 |( s2 ]/ O5 h0 ]$ E4 I
- USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);
4 Q. d& d7 j5 a% k: i -
' N& Z2 {0 R. E8 Q6 a - //配置NVIC
% J# X& D. Z/ e2 N# U - NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
2 E b0 ?! C" ]# w- } - NVIC_InitTypeDef NVIC_InitStruct;! w: f) b5 Y5 \( V- p5 _* Y
- NVIC_InitStruct.NVIC_IRQChannel=USART2_IRQn; //选择USART2的中断通道
5 c1 r/ X2 B- P3 o - NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE; //中断使能; Y, `, ^3 g$ i, |
- NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=1;
6 V4 K$ Z5 V2 X; T) a - NVIC_InitStruct.NVIC_IRQChannelSubPriority=1;
4 M; C# x0 Z8 S/ l2 V0 K - NVIC_Init(&NVIC_InitStruct);
复制代码 * a/ j! z, d8 I( }* ^5 O/ n
) @$ M7 i, M+ w# r6 M) K" }" t
中断服务子函数
7 Q P. V: S4 p7 G2 e
& @2 T& n$ L" }% O+ R4 W6 T! ]/ B中断服务子函数写好后,就可以在中断里读取接收到的数据了。
8 g+ f1 o0 n- B, ^; o2 f当接收到数据后,触发接收中断,主程序暂停执行。接收完数据后主程序回复执行。当接收到数据时,就触发中断。
- void USART2_IRQHandler(void)
W/ n7 v0 V" e B* U, Q& y% ` - {) A L2 m4 r% f
- if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET) //RXNE 标志位为1 表示可以接收数据# G. q5 P" ?( V/ U2 K! W
- {
& j- V$ ^2 w2 K0 q0 N" h - RX_Data=USART_ReceiveData(USART2);
8 F& f1 a# }2 H - Flag=1;9 Z3 {8 ~3 I( C# `) Y
- USART_ClearITPendingBit(USART2,USART_IT_RXNE); //清除RXNE标志位/ M! a: N" X2 V, H; R+ ~+ U* @
- }
* R% D, s* w* b! X' T7 p9 ~! X8 c( J2 Y - }
复制代码 5 ?1 B/ P: W$ `% H; k' ^& F* }, t5 w X
主程序测试
5 ]: W2 O* M1 Q$ i, Z9 c: S' [
- uint8_t RX_Data;1 o, e. e" @! a( {
- uint8_t Flag;
n( b3 A2 C: V$ C) C& E- w
3 K( ]$ U9 D- D4 A0 T: q" Y3 Q- int main(), f* d$ d1 O% o% U9 ^% g
- {
: f' b) W6 K4 n, L3 F# ]; @ - Serial_Init();
4 V& G2 q& b; E7 A! Z) I - Serial_SendByte(0x16);
* W6 Q( p8 }6 a6 v - 9 g$ N5 X2 r! r1 A B7 k
- ! d# R# t0 z3 |0 m k+ V7 {
- while(1)6 O, M2 f; U1 u" ^/ K
- {* D( B+ ~. b6 H# S3 \+ ]
- if(Flag==1)
1 q# B+ K6 e, B: H% K3 m v2 a - {
2 f4 ^, Q6 }' G- T/ X) ? - Serial_SendByte(RX_Data);7 h) a) l8 t( N7 o4 n
- }4 b5 u+ I* W" h# T
- } g/ V7 X3 b0 z
- }
1 K% Q2 V5 S1 m. H: s' P* \0 I& [
5 A2 `, d0 m5 t- void USART2_IRQHandler(void)
. e% e# Q. r" ^ - {
, h& M3 J/ ~+ O4 @# W( r - if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET) //RXNE 标志位为1 表示可以接收数据- q6 q9 G) U2 ~; k) k
- {. R; T$ Q: y9 D7 C
- RX_Data=USART_ReceiveData(USART2);
1 R. v" D/ D" I, Q& n - Flag=1;8 v" O0 x3 F) w1 \6 m# p( N
- USART_ClearITPendingBit(USART2,USART_IT_RXNE); //清除RXNE标志位
+ e2 ?4 ~3 C( T/ s7 V3 ? - }
a9 E0 M3 q* z$ c- \ - }
复制代码 * I+ `' n# B( t% k
% \# y' l# m& r
下图为程序现象:可以看到,串口确实收到了数据,只是我把接收到的数据0xFE放在了while循环里,这说明数据接收是成功的,使用中断是可行的。 4 d5 s& m( d7 ]. {5 J4 E
' A1 L/ D- x' N3 |
7 b5 c) F, b/ P& G" N8 p: _1 i
7 R% m; m. a% M: h3 N- g& M总结 到这里,就大致总结了串口的发送和接收。 ; ]; n! O9 ] m, B
串口的配置,使用查询或者中断来接收数据。
% a" ^& Y8 B6 _( c+ d1 z; F5 u( o
串口的使用会很常用到,所以在这里对串口做一个总结,也算是对之前知识的一个回顾和总结,加强印象。
\% r8 S% v H4 l$ L
如果有什么写的不对的地方,欢迎指正! / T# `% O6 W7 `3 s9 t: O. F! q y
转载自: 古月居 如有侵权请联系删除 # O0 S3 X/ c* L& C" R' ^
) V5 s, D8 Y4 A9 p: Q2 q% ^" p
& @$ ^3 @8 t2 f/ {) E4 c+ H3 l# ^/ e |