|
STM32的串口通信
- w7 a3 t4 u9 l% B本文在于记录自己的学习过程中遇到的问题和总结,各种情况下串口通信在STM32的实际使用方面占有很大的比重,本文主要对串口通信做一个简要的总结。 * Q. i+ z+ p& P( v+ t$ Z) X( E
一、STM32里的串口通信 在STM32里,串口通信是USART,STM32可以通过串口和其他设备进行传输并行数据,是全双工,异步时钟控制,设备之间是点对点的传输。
' x- a# z( f$ h* w9 }$ D
对应的STM32引脚分别是RX和TX端。STM32的串口资源有USART1、USART2、USART3. 6 w! M# N/ C+ z$ r
串口的几个重要的参数:
$ e) l. ~" a. h
波特率,串口通信的速率
' j. D# [; W4 ~% _5 U1 h
空闲,一般为高电平
8 U. v, f$ A4 e# ~! ^8 k8 G& c( s
起始位,标志一个数据帧的开始,固定为低电平。当数据开始发送时,产生一个下降沿。(空闲–>起始位) ) G- {2 C) n" \ }/ H) S
数据位,发送数据帧,1为高电平,0为低电平。低位先行。
/ X4 n% _7 R+ G1 r) Z2 k, t( O
比如 发送数据帧0x0F 在数据帧里就是低位线性 即 1111 0000 / H [/ H1 E; e" t
校验位,用于数据验证,根据数据位的计算得来。有奇校验,偶校验和无校验。
% j* J% Z2 N$ g' I/ P8 P! G
停止位,用于数据的间隔,固定为高电平。数据帧发送完成后,产生一个上升沿。(数据传输–>停止位) 0 u8 b9 F# l9 |. x
" t" e |5 w0 j8 j2 C3 G
下方就是一个字节数据的传输过程,从图中可以看出,串口发送的数据一般都是以数据帧的形式进行传输,每个数据帧都由起始位,数据位,停止位组成, 且停止位可变。
( ]1 q4 o. t- f& o) c
1 O" G! Q& _3 f8 J: m% i
4 |. O2 g- ^8 n& J
. O& \3 ]6 \8 k. @
二、串口的发送和接收 USART是STM32内部集成的硬件外设,可以根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可以自动接收RX引脚的数据帧时序,拼接成一个字节数据,存放在数据寄存器里。 6 G( V. m9 @7 [7 L% u
当配置好USART的电路之后,直接读取数据寄存器,就可以自动发送数据和接收数据了。在发送和接收的模块有4个重要的寄存器 $ _4 h- u" z' V. W& M
( r; z+ s9 a/ A: X- ?
下方为串口的发送和接收图解: " v2 J" Y9 k/ z8 `; r' z: J/ q3 k+ d. ]5 G
! o6 c- M* Y6 I; g: V' Q( Q0 [
- H+ B: @; L: T+ V; a1 y
串口发送
: X( @' p) y7 D' [/ T, R1 f- _, h& @
在配置串口的各个参数时,可以选择发送数据帧的数据位的大小,可选8位或9位。 5 U3 V5 l @0 K) T9 Z, |% r! `
串口发送数据实际上就是对发送数据寄存器TDR进行写操作。
( u" l; d5 s, {( l1 _; @, ~4 ^% v. l
1. 当串口发送数据时,会检测发送移位寄存器是不是有数据正在移位,如果没有移位,那么这个数据就会立刻转移到发送移位寄存器里。准备发送。
. I- V$ L2 M9 Z5 L
2. 当数据移动到移位寄存器时,会产生一个TXE发送寄存器空标志位,该位描述如下。当TXE被置1,那么就可以在TDR写入下一个数据了。即发送下一个数据。 % h1 N# _& [- q: D3 i
9 h% {' I8 O& I' i
: b7 _! e. O5 L7 H" Y, w0 ^
3. 发送移位寄存器在发送器控制的控制下,向右移位,一位一位的把数据传输到TX引脚。
1 o5 x B$ ?. |8 F8 `
( \' W1 J7 b4 h* D D: B& r8 h; U! T0 B& h
4. 数据移位完成后,新的数据就会再次从TDR转移到发送移位寄存器里来,依次重复1-3的过程。通过读取TXE标志位来判断是否发送下一个数据。
+ W5 A4 q3 Y4 }
串口接收
! F" n* l# ` S; n: y
数据从RX引脚通向接收移位寄存器,在接收控制的控制下,一位一位的读取RX的电平,把第一位放在最高位,然后右移,移位八次之后就可以接收一个字节了。 7 n/ {% P3 M" U3 _- [7 b' A
当一个字节数据移位完成之后,这一个字节的数据就会整体的移到接收数据寄存器RDR里来。 7 I$ O, A7 q8 s! _' y. J( V
在转移时会置RXNE接收标志位,即RDR寄存器非空,下方为该位的描述。当被置1后,就说明数据可以被读出。 9 \8 w) j( K4 W
; Q' z2 j+ ^) u1 m; L! F$ b0 [0 `4 H/ P$ K7 x2 n6 ~7 d, |
下图即为串口接收的工作流程 : N& l% y9 X# Y, ^7 |" P3 K% `
& [% W, E6 f0 K1 O( | K( k7 z( o: S, k' Z9 ?
三、串口在STM32中的配置
. I# S# `* B- I" q Q, R: D' J2 [8 t
首先要明确几点:使用STM32串口外设中的哪一个?串口发送或者接收数据?串口相关的参数配置?发送或接收是否使用到中断?
; ?! n% r) G% }' H4 M* c
下方为串口发送的配置。 0 ` s+ I0 M7 n) E0 A6 h! a: `
1. RCC开启USART、串口TX/RX所对应的GPIO口 4 g1 `+ y( d* a; R: ]
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE); //开启USART2的时钟
: m9 f' c) u3 e$ F' ^+ d- {: | - RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //开启GPIOA的时钟
复制代码+ ~0 E1 n; s9 G7 w$ i5 Y
2. 初始化GPIO口 + g1 P4 o' e0 N: ~/ i" k
这里注意哈,根据自己的需求来配置GPIO口,发送和接收是都需要还是只需要其中一个。然后对应的根据引脚定义表来初始化对应的GPIO口。 3 v. J* d% a1 ~ L( L
USART2对应的引脚
; x1 g$ n" |3 h8 b
* G9 F, f! b: Y+ A9 b
6 X3 m) ~7 ]2 `2 D& x
USART1对应的引脚 5 e* j6 A" ^- W/ D
: t- _3 E1 Q: u8 P+ m* y8 G+ s4 _; x0 b% e
这里根据手册来看,RX引脚模式配置成浮空输入或者上拉输入。TX引脚模式配置成复用推挽输出。
0 w0 N% Q9 P, ~- n7 h& t. o **比如我这里只初始化TX发送端**- //TX端' B# e' v, G2 p( g
- GPIO_InitTypeDef GPIO_InitStructure;
. B! e7 |) V/ `% B4 M - GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; //复用推挽输出* f( ?; J2 b! O E* a
- GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2; //USART2对应的TX端为GPIOA2
/ L) g6 x7 p& f( F' A" g. F6 [ - GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //50MhZ. u3 m; D* v( U0 ]. |
- GPIO_Init(GPIOA,&GPIO_InitStructure);
复制代码4 @8 f; L* H4 T& {! _! V
3. 串口初始化 % P& ^/ U) j% G4 I( v8 V
注意哈,USART_Init()这个函数,是用来配置串口的相关参数的。 5 J3 Q% Z/ T7 V; y! H! C! @. }
USART_BaudRate 串口通信使用的波特率 一般是9600或者是115200,这里我们给9600
% ^8 J. M6 e/ ^% [2 x: {
USART_HardwareFlowControl 是否选择硬件流触发,一般这个我们也不选,所以选择无硬件流触发。 4 Y8 j: F/ }# n0 f, B9 S }; V) Q
USART_Mode 这个参数要注意了哈,串口的模式,发送模式还是接收模式,还是两者都有
: Y0 h; U' y. w* V
USART_Parity 校验位,可以选择奇偶校验和不校验。没有需求就直接无校验
g5 M! p" W$ Z
USART_StopBits 停止位 有1、0.5、2位,我们这里选1位停止位 8 k6 W$ ?2 r; E
USART_WordLength 数据位 有8位和9位可以选择
" ^0 s6 a0 ?/ Y
- //串口初始化2 S$ }; ~9 {$ B
- USART_InitTypeDef USART_InitStruct;! r/ F* _: b a S" F, }6 X
- USART_StructInit(&USART_InitStruct); //初始默认值
- y! A9 |+ ~. g - USART_InitStruct.USART_BaudRate=9600;
& l1 Y' Z; w0 y: Q& G( B8 V - USART_InitStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None; //不使用硬件流触发/ E! A# n) \3 u( o N- ~8 J
- USART_InitStruct.USART_Mode=USART_Mode_Tx; //TX 发送模式: ?: [5 ?5 y& D) _: ?, ~
- USART_InitStruct.USART_Parity=USART_Parity_No; //不选择校验
& I M4 C, y+ Q4 {; I4 g& k: Y - USART_InitStruct.USART_StopBits=USART_StopBits_1; //停止位1位
; t( Y H ] }: S, f* y1 r$ Y - USART_InitStruct.USART_WordLength=USART_WordLength_8b; //数据位8位
* x+ v5 B( ^: |. F8 u. W2 U# X - USART_Init(USART2,&USART_InitStruct);
复制代码
: p' u8 ^2 T! y* @& T8 `! K
4. 串口使能 - //串口使能
) k) n( E5 H2 b - USART_Cmd(USART2,ENABLE);
复制代码
/ f* _, j" U( U- }
5. 串口发送数据
! S' w& [# \2 T, H4 o0 N, K- K注意哈,我们要判断TXE标志位的状态。0,数据还没有被转移到移位寄存器;1,数据已经被转移到移位寄存器。
5 X0 v6 E: R0 y/ B6 |, Z
当TXE标志位为1时,就说明可以发送下一个数据了。详细过程可看上面串口发送的解释。 - void Serial_SendByte(uint16_t Byte)& e9 r* M& _6 {4 |1 S& l! F4 k) x3 X
- {
& @2 K0 i% Q! Z: C; H/ M) E1 { - USART_SendData(USART2,Byte);# z4 v4 j) {) b/ { A5 T
- //0 表示数据还未转移到移位寄存器 循环等待 1 数据已经被转移到了移位寄存器可以发送数据
6 _7 B/ p6 J8 z2 F2 H - while(USART_GetFlagStatus(USART2,USART_FLAG_TXE)==RESET); //不需要手动清零 再次写入TDR时会自动清零
9 ^ y& P% t$ g2 F) Z: v' N o7 r - }
复制代码
3 a- U; C$ f/ Q% K/ K8 ?
经过上述五步的配置,单片机就可以通过串口发送数据了。 5 A* a( O' _; J* w9 q
下方为发送数据图例,STM32向串口发送0x16数据
6 c: u, ^) T% P/ d* ^! g, n0 h
$ e0 V, s3 k7 B
4 d s' L2 V1 j9 R8 F) d! q3 N/ c0 i5 w7 O. A+ H6 o$ m! E/ x
四、串口接收的两种实现方式 上方是发送数据的例子,那么串口接收又该如何配置,又要在串口发送的例子上做哪些更改呢? 9 n- Z- g9 d# u: k- s
这里我们可以通过查询或者中断的方式来进行接收数据的两种方式。 9 ^3 u- Q+ t. |4 @( `2 ^7 b$ l
查询方式就是通过不断的查询RXNE标志位,通过判断RXNE位的状态来确定数据是否接收。
- {) F/ |/ v! b6 O+ c& J* j
中断方式就是通过配置接收输出控制通道,配置NVIC,在中断服务子函数里进行数据的接收。 9 U9 ~' l6 D4 c2 Z- Z6 b: _0 _; _2 E
- M8 s' \2 |8 N( u
1. 需要更改的地方+ K9 t9 U& N6 N4 ~2 X5 T3 C* c9 x' T5 r
: K# N; K: E& X 既然我们要实现串口的接收,那么就要配置串口RX引脚,在串口模式中添加USART_Mode_RX模式。 4 P- R+ p' j9 c4 d5 K' d
初始化RX引脚 1 j% W1 F( ]& u* E& {0 G
- //RX端
( l. A: Z$ W3 }) B - GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU; //上拉输入
@$ |- C8 i# G; S - GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3;# C' i* `+ ~1 S3 I& N* n
- GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //50MhZ/ b! _- \- h0 X% w
- GPIO_Init(GPIOA,&GPIO_InitStructure);
复制代码
2 i+ Q1 k, B4 }2 o/ p- v4 j
; \; }. R1 C0 L7 ^% i+ k
添加串口模式
3 @* M# _, d9 [3 [
- USART_InitStruct.USART_Mode=USART_Mode_Tx|USART_Mode_Rx; //TX 发送模式 RX 接收模式
复制代码
' H0 y0 f4 a: Z0 o! g0 b% ]: e
/ I& n# _+ K8 K% P0 Q6 o" q
2. 查询RXNE标志位
, m+ j+ s+ W$ z& m1 X5 s
这里我们还是来看一看RXNE标志位的描述 3 N$ ^( _- n+ U# \$ I
; h) e( t8 Z" D
7 s. Y; J( R, j' U1 y7 r
上图描述,为0时数据没有收到,为1时收到了数据,数据可以从RDR里读出
0 B' V+ B. g) |3 I9 _" R9 y/ D$ {5 t
所以在主程序里不断读取RXNE标志位,如果为1,表示数据可以读出 - uint8_t RX_Data;
: k3 w4 {% `0 O- I/ C - int main()
/ x; m9 v4 O- \ - { % l9 {% }% e0 a, D) L. [
- Serial_Init();) i' X) t* ]: W) h3 `' `
- Serial_SendByte(0x16);. X6 K* X& o! U" }
- while(1)0 @ s1 D- f% ^- L
- {
. i: ]# [3 P/ n% ] - if(USART_GetFlagStatus(USART2,USART_FLAG_RXNE)==SET) //0 循环等待 1 可以接收数据! b9 y: n8 H5 n. ~# n; Q/ a
- {
0 S( {& f) B6 H/ n - RX_Data=USART_ReceiveData(USART2);
. Y, E2 X# x7 b6 ^9 F6 P$ \4 b - Serial_SendByte(RX_Data);
: ]" y. I1 e- H! ?1 E - }
4 t+ a4 i4 k d& L0 f/ s3 x - }
" s2 T* H4 e: R# [ - }
复制代码1 {! r" q- B" m {" r' ?6 ^
下图为程序现象:pc向单片机发送数据0x15,单片机接收数据0x15,并且把接收到的数据作为数据发送到pc,在pc上显示0x15。 S6 U1 v3 s- J) _0 O1 }6 \
8 y; U( G% m) N0 n n8 U: v% K
1 T, ~* v# L9 t* K5 T
3. 使用中断
6 Y; f; f& m& _2 ~7 e5 h# r
- //开启中断输出控制! C/ y$ q# x. U$ }! V
- USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);4 Z4 p* X- k5 r7 R2 _
- $ D" d( M# V# [8 v
- //配置NVIC# `3 {4 t' u* G+ J
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); ( S& _; u% p! k- J
- NVIC_InitTypeDef NVIC_InitStruct;
' l9 s }, U0 @' j& e' ^ - NVIC_InitStruct.NVIC_IRQChannel=USART2_IRQn; //选择USART2的中断通道
6 H$ r5 P9 J4 p8 Q4 q' c% L% { - NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE; //中断使能" E$ I7 I& ^: R2 w
- NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=1;
' l X, s6 o$ j# n - NVIC_InitStruct.NVIC_IRQChannelSubPriority=1;
9 g( k4 ~5 i9 I$ y - NVIC_Init(&NVIC_InitStruct);
复制代码
) q+ X( o, E. G8 o* w
5 O/ E% \( _) A# y
中断服务子函数
* p5 m( A; h( A; T S
[: K* d0 k6 P
中断服务子函数写好后,就可以在中断里读取接收到的数据了。
0 _! x7 e5 p2 d) y. }当接收到数据后,触发接收中断,主程序暂停执行。接收完数据后主程序回复执行。当接收到数据时,就触发中断。
- void USART2_IRQHandler(void)
* ~4 d+ R# j- [' p - {3 b: N- ~' F6 z# n. i% L# ~
- if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET) //RXNE 标志位为1 表示可以接收数据" {1 {( v# L5 V
- { C$ B- t& l9 [% j+ a
- RX_Data=USART_ReceiveData(USART2);
) `4 u8 m, w+ E s$ ], h* N - Flag=1;
( G) L0 \0 \+ o( T$ S - USART_ClearITPendingBit(USART2,USART_IT_RXNE); //清除RXNE标志位
$ A0 M- T( d/ I" `6 W: U; e* ~8 J - }: w1 F9 }$ z; c% [
- }
复制代码 ( C' W; ?! P0 r) f# M' g1 R' A
主程序测试
6 f5 R j$ E- ?. B9 J# e- f# w9 k
- uint8_t RX_Data;
2 J( R" M" q, ~ - uint8_t Flag;
4 m" R& K$ a: F+ \2 z/ L( T - : `0 l/ I' i8 p' S
- int main()
6 ]8 m% w9 T/ o9 x; S1 r - {
8 K' z* o& |' s' Q7 ~ - Serial_Init();
. f* R9 ^) R, Q1 m: V6 { - Serial_SendByte(0x16);; G( D" L) M, X, x
- L }3 D7 d- R
3 A. q2 u8 J( A6 \3 e) v) H- while(1)
7 t, s$ y) B+ M3 L" D W+ n - {1 {3 [3 K7 d7 f/ Q, Y
- if(Flag==1): H. y' a+ e+ C) M5 g
- {, A. l$ e. g* W1 Z3 y" t
- Serial_SendByte(RX_Data);2 \! Z( r$ c, O! \
- }6 O2 s4 y& x) f1 {* R5 z9 l
- }
+ r1 s, G$ p0 U h - }
: O( U+ R6 T3 w4 \0 F
" B$ n1 u' @7 ?/ E- void USART2_IRQHandler(void)
4 B7 D) }' k+ D7 l/ B# W - {
; E- ]' v" F d$ x - if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET) //RXNE 标志位为1 表示可以接收数据
8 W3 D I6 m# } - {' K! M- u0 e' X- d% o
- RX_Data=USART_ReceiveData(USART2);
* a! ?# _- C6 H* C, V' k p - Flag=1;( s) x: f: L/ ?8 i6 V" U9 `8 j4 z
- USART_ClearITPendingBit(USART2,USART_IT_RXNE); //清除RXNE标志位; I9 X4 n6 F* M( i2 u# g- q& S
- }
# v$ z9 S" o1 h7 n - }
复制代码 3 P! K$ {8 q* |2 J# H
. }% R( h2 p. e: {
下图为程序现象:可以看到,串口确实收到了数据,只是我把接收到的数据0xFE放在了while循环里,这说明数据接收是成功的,使用中断是可行的。 1 C2 ?3 }+ _; V! |/ @, x
5 v2 G3 [: v) T
1 }1 [; X. u) g! f6 o5 b! | ( |9 Z. ~8 m& W6 y& i
总结 到这里,就大致总结了串口的发送和接收。
4 K) G6 R: h1 k* B/ ^+ u! d! h( E
串口的配置,使用查询或者中断来接收数据。 , h" A9 W( y; p F( O& H% @
串口的使用会很常用到,所以在这里对串口做一个总结,也算是对之前知识的一个回顾和总结,加强印象。
% ` [3 l) N* \& ?# E
如果有什么写的不对的地方,欢迎指正!
( A# x5 g+ X, N: Z4 }/ F" m
转载自: 古月居 如有侵权请联系删除
/ c% w- s# r% v6 d; C 3 f) B( v5 F& T+ D7 y, @4 P
2 t; j# l. C- A/ o! N
|