|
STM32的串口通信
+ A7 `5 f4 [$ t
本文在于记录自己的学习过程中遇到的问题和总结,各种情况下串口通信在STM32的实际使用方面占有很大的比重,本文主要对串口通信做一个简要的总结。
. c; {* O. E- B
, Y1 K# m/ a; [) q8 H* g
一、STM32里的串口通信 ; q- f- v6 c9 z$ A0 b7 n6 W2 n( ^
在STM32里,串口通信是USART,STM32可以通过串口和其他设备进行传输并行数据,是全双工,异步时钟控制,设备之间是点对点的传输。
. W: @3 j5 R9 v( q: X. Q4 x2 T
对应的STM32引脚分别是RX和TX端。STM32的串口资源有USART1、USART2、USART3. - K4 y- V9 ~: v' \5 x! T
串口的几个重要的参数:
8 @0 x9 J: U; ]) w- p
波特率,串口通信的速率
3 M4 M; X( T' h! ?- S7 l1 U e
空闲,一般为高电平
. T7 p5 _7 J3 w X: @' L# }+ w
起始位,标志一个数据帧的开始,固定为低电平。当数据开始发送时,产生一个下降沿。(空闲–>起始位) " ~" D* K( H: c/ t! ~* T* y' ]
数据位,发送数据帧,1为高电平,0为低电平。低位先行。 ! ~: _! S8 {) k( N, D+ d
比如 发送数据帧0x0F 在数据帧里就是低位线性 即 1111 0000 7 m$ B8 W5 G2 ]( q5 E! g0 f( e% A
校验位,用于数据验证,根据数据位的计算得来。有奇校验,偶校验和无校验。 2 f4 i6 P3 `8 l2 |, d9 T% g9 X1 U
停止位,用于数据的间隔,固定为高电平。数据帧发送完成后,产生一个上升沿。(数据传输–>停止位) 9 ~ x+ C% e' |. f$ ~
& |0 ~# \ [5 ^
下方就是一个字节数据的传输过程,从图中可以看出,串口发送的数据一般都是以数据帧的形式进行传输,每个数据帧都由起始位,数据位,停止位组成, 且停止位可变。
' M' {0 T/ x* N' f1 q3 h
4 b- S9 a% ~* I8 Y1 Y3 l) `. \( }4 s4 Z) R0 s9 c, F
* k0 N1 q2 H1 J% O0 n/ }; j* d8 ]
二、串口的发送和接收 1 ?! b" \( E o A/ B% o" n
USART是STM32内部集成的硬件外设,可以根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可以自动接收RX引脚的数据帧时序,拼接成一个字节数据,存放在数据寄存器里。
0 ?% p+ L' I( v* g( [
当配置好USART的电路之后,直接读取数据寄存器,就可以自动发送数据和接收数据了。在发送和接收的模块有4个重要的寄存器 # G- d$ T0 ^! |! U' J
5 O8 T1 `: A. h, ^' G8 m" Z5 A4 u" t
下方为串口的发送和接收图解:
2 C3 r+ k# D% e5 [2 P# E/ q
+ v5 I q7 G" E& d" B( N# V7 Z3 ^( H4 x7 @$ f
串口发送 2 m1 Z( F% D+ I
在配置串口的各个参数时,可以选择发送数据帧的数据位的大小,可选8位或9位。
4 C( w/ ~: o4 `5 R
串口发送数据实际上就是对发送数据寄存器TDR进行写操作。 6 o. U) r, A* T; J: g2 `8 l+ Q; R
1. 当串口发送数据时,会检测发送移位寄存器是不是有数据正在移位,如果没有移位,那么这个数据就会立刻转移到发送移位寄存器里。准备发送。 5 ^1 ^' W' F' ~, j% ~
2. 当数据移动到移位寄存器时,会产生一个TXE发送寄存器空标志位,该位描述如下。当TXE被置1,那么就可以在TDR写入下一个数据了。即发送下一个数据。 3 ^! v# i8 ]9 Y
, a' c5 X; b# d6 |4 i% G3 b% ]6 S: d* y4 G3 } C2 b
3. 发送移位寄存器在发送器控制的控制下,向右移位,一位一位的把数据传输到TX引脚。 . V8 Y0 z5 y2 y' b$ ~
9 N! d1 V, x& Y! `* y* Y7 l3 G$ ^
$ r" i) b$ [8 \; Z6 A4 }1 a7 g
4. 数据移位完成后,新的数据就会再次从TDR转移到发送移位寄存器里来,依次重复1-3的过程。通过读取TXE标志位来判断是否发送下一个数据。
: l& b f9 I2 h% x0 P6 @: R
串口接收
" Z" W1 z" J# V
数据从RX引脚通向接收移位寄存器,在接收控制的控制下,一位一位的读取RX的电平,把第一位放在最高位,然后右移,移位八次之后就可以接收一个字节了。
& ]: T$ l2 ~' z/ T# u" ?
当一个字节数据移位完成之后,这一个字节的数据就会整体的移到接收数据寄存器RDR里来。
8 B+ \4 H" l3 |# Z( d
在转移时会置RXNE接收标志位,即RDR寄存器非空,下方为该位的描述。当被置1后,就说明数据可以被读出。
9 q& U# I" _& J, X! k7 H
, r g8 \" s; _; x1 ?! d
( t) {0 r$ y# j6 A; q
下图即为串口接收的工作流程 s2 S; p; ?- j; f2 x7 s
: N" a0 \/ S$ E) Z2 |5 J @+ |
7 Z: Q6 X' {# d/ v7 k8 `
/ I5 R2 j& n; S三、串口在STM32中的配置 5 k. x$ ~ ? Z, K9 u0 i/ P
首先要明确几点:使用STM32串口外设中的哪一个?串口发送或者接收数据?串口相关的参数配置?发送或接收是否使用到中断?
+ l& r7 h6 D5 k/ G
下方为串口发送的配置。
: r, ~2 g% U+ @# |, P
1. RCC开启USART、串口TX/RX所对应的GPIO口 - RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE); //开启USART2的时钟
' C" |$ }/ c# B# }0 H - RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //开启GPIOA的时钟
复制代码
. |- j" T$ l1 l
2. 初始化GPIO口
: o8 e- W; Q8 i
这里注意哈,根据自己的需求来配置GPIO口,发送和接收是都需要还是只需要其中一个。然后对应的根据引脚定义表来初始化对应的GPIO口。
. M) _: I3 T- e% n/ c$ c# `
USART2对应的引脚 . e) l- z$ D: y: U4 w
$ G7 m6 c- L( \; |5 M4 e! N; x+ {( Y: x6 M7 @, I u
USART1对应的引脚
9 d# P4 s$ O7 V4 S+ K" f! q3 G
9 X9 x/ ~) s0 [, k R" W
" Z; M. u: {& z: w5 ^8 t. v
这里根据手册来看,RX引脚模式配置成浮空输入或者上拉输入。TX引脚模式配置成复用推挽输出。 9 }0 H7 P3 C: L# J
**比如我这里只初始化TX发送端**- //TX端
6 j$ ?# f8 o$ K. o$ _1 \! X - GPIO_InitTypeDef GPIO_InitStructure;
9 @, w S9 g9 d - GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; //复用推挽输出
8 [2 a- y2 r- {, \" c: ~. t - GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2; //USART2对应的TX端为GPIOA2 G) q9 u; R5 [, }( H8 x( i
- GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //50MhZ
$ t: k* P5 B% q* ~9 {: W& f* s/ k - GPIO_Init(GPIOA,&GPIO_InitStructure);
复制代码- @' o" [* G. p& [* D$ {# c! H
3. 串口初始化 ' |% G( {7 R5 t8 X
注意哈,USART_Init()这个函数,是用来配置串口的相关参数的。 : Z* X: X6 z A% P
USART_BaudRate 串口通信使用的波特率 一般是9600或者是115200,这里我们给9600 $ A2 ~8 T" f' y
USART_HardwareFlowControl 是否选择硬件流触发,一般这个我们也不选,所以选择无硬件流触发。 * P Q9 x% j4 V1 O+ }
USART_Mode 这个参数要注意了哈,串口的模式,发送模式还是接收模式,还是两者都有 3 p+ [% r6 _+ z
USART_Parity 校验位,可以选择奇偶校验和不校验。没有需求就直接无校验
! \" w4 a% J6 q7 X* J
USART_StopBits 停止位 有1、0.5、2位,我们这里选1位停止位 5 [* x) E' n1 J. {% O* f. T
USART_WordLength 数据位 有8位和9位可以选择 - //串口初始化
1 @2 D, D' H6 c; ?5 \ - USART_InitTypeDef USART_InitStruct;) x3 n, I9 s2 H" I( F) j# H
- USART_StructInit(&USART_InitStruct); //初始默认值* `- F2 q4 R& u5 e$ [# G
- USART_InitStruct.USART_BaudRate=9600;
# X' y4 M: {9 z9 y# h - USART_InitStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None; //不使用硬件流触发) g, d- z) o3 N4 k4 S& l" [4 U X
- USART_InitStruct.USART_Mode=USART_Mode_Tx; //TX 发送模式% X& t1 t4 m0 L4 ]8 F
- USART_InitStruct.USART_Parity=USART_Parity_No; //不选择校验. J6 o+ u* Y( u6 p6 E5 R- D" o! K
- USART_InitStruct.USART_StopBits=USART_StopBits_1; //停止位1位
7 |4 {9 t6 h$ Q9 P - USART_InitStruct.USART_WordLength=USART_WordLength_8b; //数据位8位9 ]) F4 J. ?: m2 v0 N$ K
- USART_Init(USART2,&USART_InitStruct);
复制代码 & c- H; K/ I' @* s" v# P2 }, W
! i1 x+ {( Z" t! ]4 Z+ ~; `
4. 串口使能 - //串口使能
7 u Y1 G* C/ v! t5 {5 V: o/ f - USART_Cmd(USART2,ENABLE);
复制代码! N* N3 _2 T+ O) O$ ~7 d
5. 串口发送数据
: a# K0 `5 h+ a: A) n2 p注意哈,我们要判断TXE标志位的状态。0,数据还没有被转移到移位寄存器;1,数据已经被转移到移位寄存器。
; b+ l" ]- X: _& t+ S6 W! V" |
当TXE标志位为1时,就说明可以发送下一个数据了。详细过程可看上面串口发送的解释。 - void Serial_SendByte(uint16_t Byte)3 U$ t" N2 s! G b5 S
- {. L# b5 A. X2 Y4 ~9 b \9 F5 f
- USART_SendData(USART2,Byte);
2 U( g; E: T( m9 D! R6 L) x - //0 表示数据还未转移到移位寄存器 循环等待 1 数据已经被转移到了移位寄存器可以发送数据
4 s" Q7 r2 G, N& ?. @8 s - while(USART_GetFlagStatus(USART2,USART_FLAG_TXE)==RESET); //不需要手动清零 再次写入TDR时会自动清零
) M/ J: A3 b U, A j1 B* U/ j6 _ - }
复制代码
6 Y" ]6 d7 p- m& I
经过上述五步的配置,单片机就可以通过串口发送数据了。 2 S1 ^* C6 `. p. k5 r+ H. |
下方为发送数据图例,STM32向串口发送0x16数据 0 E5 F8 n0 `7 O4 ~ R
3 k1 [$ V: ?: b) N% V& t- e4 U# l5 Q. x }6 p$ ^
四、串口接收的两种实现方式 7 ~& i$ \; P3 s3 k. r
上方是发送数据的例子,那么串口接收又该如何配置,又要在串口发送的例子上做哪些更改呢?
2 a- H8 t7 X1 \9 E3 ~+ z# ^
这里我们可以通过查询或者中断的方式来进行接收数据的两种方式。
' R4 j2 C- N) ?0 T5 U
查询方式就是通过不断的查询RXNE标志位,通过判断RXNE位的状态来确定数据是否接收。 4 W- l8 k, S- ]3 D. }$ R2 `
中断方式就是通过配置接收输出控制通道,配置NVIC,在中断服务子函数里进行数据的接收。 - h$ u% O; u! v- R8 R
6 W6 V" s8 h5 W& ] K
1. 需要更改的地方
7 K! H2 T1 L8 B9 s( k
6 U/ K+ n$ C2 }- q X 既然我们要实现串口的接收,那么就要配置串口RX引脚,在串口模式中添加USART_Mode_RX模式。
( d0 v8 X4 x4 K7 r# t* a
初始化RX引脚 - //RX端& ~& H h0 {4 t( j7 i
- GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU; //上拉输入
- j D. m9 o" Q - GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3;* {) b! ]& E# B W$ ~" m
- GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //50MhZ
: j' ]2 Q* \$ P* q3 M - GPIO_Init(GPIOA,&GPIO_InitStructure);
复制代码 ; `7 |, I7 w4 c/ c0 c/ Q" f3 i8 D: E
' X# ^6 q' C- r Y
添加串口模式 - USART_InitStruct.USART_Mode=USART_Mode_Tx|USART_Mode_Rx; //TX 发送模式 RX 接收模式
复制代码 5 R1 y1 g: u1 v8 i4 ?, ~
6 V; Y0 B; @% y5 w* |) X
2. 查询RXNE标志位
$ e/ S" C# L- O( {
这里我们还是来看一看RXNE标志位的描述 : b; h. j( \1 A7 N' Q, a
6 b& x; U0 \2 q( M; v- V) G) }$ O
上图描述,为0时数据没有收到,为1时收到了数据,数据可以从RDR里读出 , R! b* e5 ]6 s" n8 q( X
所以在主程序里不断读取RXNE标志位,如果为1,表示数据可以读出 - uint8_t RX_Data;1 I) C. s$ A' k9 K
- int main()
" v0 j3 j4 ^: E% \# S" H/ s - {
' @% W- B0 E1 z" y+ n - Serial_Init();2 H0 u: ~7 Z, Z* g0 S* K
- Serial_SendByte(0x16);, I1 ~1 i5 t6 R5 `- s
- while(1)* k' M9 M3 `; u+ A$ p6 F# l' s6 X& r
- {" ?: F' M! s0 X z& V( l' x
- if(USART_GetFlagStatus(USART2,USART_FLAG_RXNE)==SET) //0 循环等待 1 可以接收数据, o T! F4 e7 N- I1 _4 p
- {$ j: |( ?6 |! V0 R- S6 t7 P9 D
- RX_Data=USART_ReceiveData(USART2);
0 Y: h5 m6 f4 E! K - Serial_SendByte(RX_Data);
' V$ [' q B8 D( j% h - }/ z# @* }0 ~3 E$ i6 l& p, p
- }8 q4 k4 } h$ a0 P* q' Z W1 n
- }
复制代码7 ^0 O5 Z* k8 T, Z' Y
下图为程序现象:pc向单片机发送数据0x15,单片机接收数据0x15,并且把接收到的数据作为数据发送到pc,在pc上显示0x15。 4 f# _( p% Z; R
+ m+ b$ _: J, r6 x& j! l3 u
, N y7 ~- t) F' E" O: q' J
3. 使用中断 ' t8 S3 \# b' H8 U0 A
- //开启中断输出控制
" y- W3 _$ v2 n. m# F! N - USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);: ?& d5 ~& [6 Q2 B p, Z
-
0 U# Z' ]" [" a5 S+ t( F% @ - //配置NVIC3 n. c6 ~# B+ g, {, e5 x
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); / |( R1 W; b4 P' Y! N, Z# O
- NVIC_InitTypeDef NVIC_InitStruct;5 w' l- L x# X- V% A3 B: t- N! H
- NVIC_InitStruct.NVIC_IRQChannel=USART2_IRQn; //选择USART2的中断通道
# k3 B$ d5 l; W! R: |+ ? - NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE; //中断使能* h s$ O/ y9 F. @2 o- y
- NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=1;% @! {( D; v0 L$ M2 O9 m& h: a( a7 z' L( N
- NVIC_InitStruct.NVIC_IRQChannelSubPriority=1;% E3 V' M' T2 e
- NVIC_Init(&NVIC_InitStruct);
复制代码
1 Z/ [. ^# T2 ~' I
中断服务子函数
' d6 j8 x$ a& ^1 F
; d9 H& ]7 o. |# R- v7 R: T
中断服务子函数写好后,就可以在中断里读取接收到的数据了。 9 Y: b" S2 }+ _
当接收到数据后,触发接收中断,主程序暂停执行。接收完数据后主程序回复执行。当接收到数据时,就触发中断。 - void USART2_IRQHandler(void)
: j. D5 U' }' _ - {$ @) M' c1 F2 ~5 G
- if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET) //RXNE 标志位为1 表示可以接收数据' N7 `1 C J2 L& L3 `( C( K5 B5 V
- {
1 h( T9 e- f2 k% W - RX_Data=USART_ReceiveData(USART2);0 N9 J# F4 Q+ I/ [1 p
- Flag=1;# b1 a# n1 j% h5 W
- USART_ClearITPendingBit(USART2,USART_IT_RXNE); //清除RXNE标志位) L# u, V/ X. {0 |/ s/ Q; p& a
- }
$ d. M! o+ s/ V [9 ] H; b" t - }
复制代码& A: F. ^0 {2 E, x7 s; j
- uint8_t RX_Data;4 ^8 I* m6 |; V E" A% g
- uint8_t Flag;: x( A; R Z* d4 P f% N. `5 B
- % g1 g Y) @' U1 l
- int main()8 f" H6 ~9 U2 E$ h1 v
- {! i$ ]( h& Q! y* Z7 X9 s% d* z
- Serial_Init();* T5 ?! _* i0 C3 p4 H9 o4 V4 d
- Serial_SendByte(0x16);9 }% T" u% H4 V+ b1 z5 |
- " r3 F! y% B# \$ C4 q# K
- p: N+ o7 {; ~- N9 _* ~2 }1 k
- while(1)
; i3 B& q" u- w3 H) T$ S) q$ k - {
% Z& q5 S8 z; y: c D" ^$ R - if(Flag==1)
/ a9 I+ R W! B- A# L# |1 E% ] - {+ ~8 G% W: P, Z+ [: f% W
- Serial_SendByte(RX_Data);# u! x+ K+ U- o; z( g
- }1 l# G) Y8 ]/ A5 v+ ]" e+ B( G$ v
- }: y$ w T, F6 f) F
- }3 Q5 y+ N5 F, z
1 Q* @- q9 [. a- z- void USART2_IRQHandler(void)
* c: i/ [6 [0 j( L Q: Y- B - {
( B! Z, Z0 T9 i# r z! I- y6 F7 a e - if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET) //RXNE 标志位为1 表示可以接收数据
: ]8 u; u. C& H- S; E6 l1 [2 ] - {
5 x# [) @% ^7 m2 ^. k: U; [( o - RX_Data=USART_ReceiveData(USART2);2 S. C- A1 n! s/ w( _/ R, @
- Flag=1;
: ]$ d1 R, Q9 Q- y" b2 S - USART_ClearITPendingBit(USART2,USART_IT_RXNE); //清除RXNE标志位' a. F; O+ B& q
- }
5 m4 I* O! ^/ p1 f8 |- C& q3 i - }
复制代码
/ O* {8 Y; `; \6 c: r6 p
8 G" r" W1 E2 p. B% G! j" H
下图为程序现象:可以看到,串口确实收到了数据,只是我把接收到的数据0xFE放在了while循环里,这说明数据接收是成功的,使用中断是可行的。
" Y) T- n7 d# F5 E! c
' n) K0 X6 M- R* H t3 j& f
# c s& F8 K4 v1 F+ @9 X 8 ?6 A2 V; M# l# x& E9 a! \
总结 3 l, n& I! P0 X, @# K' B, ?0 Y
到这里,就大致总结了串口的发送和接收。 2 K6 P# n2 f7 S! r
串口的配置,使用查询或者中断来接收数据。
+ }) B# t! x3 }( q+ i& u
串口的使用会很常用到,所以在这里对串口做一个总结,也算是对之前知识的一个回顾和总结,加强印象。
+ }! v4 M& ^9 K3 N
转载自: 古月居 如有侵权请联系删除 . w4 z0 D- }* T% I
' E/ S) R$ `4 t# ~, t |