|
STM32的串口通信
: C; T* y& | k7 h8 J! u本文在于记录自己的学习过程中遇到的问题和总结,各种情况下串口通信在STM32的实际使用方面占有很大的比重,本文主要对串口通信做一个简要的总结。
( T# \ g& k P9 d一、STM32里的串口通信 在STM32里,串口通信是USART,STM32可以通过串口和其他设备进行传输并行数据,是全双工,异步时钟控制,设备之间是点对点的传输。 4 I$ M0 Q; X% }2 C
对应的STM32引脚分别是RX和TX端。STM32的串口资源有USART1、USART2、USART3. " Z; {& N+ \3 r% r9 X2 z9 S
串口的几个重要的参数: 9 A/ a' ?! K( d6 W
波特率,串口通信的速率 ; @. h1 ?5 I- d3 ]
空闲,一般为高电平
$ W+ i H' A$ q$ v
起始位,标志一个数据帧的开始,固定为低电平。当数据开始发送时,产生一个下降沿。(空闲–>起始位) # S" y# M6 M* H' l9 Z
数据位,发送数据帧,1为高电平,0为低电平。低位先行。 1 u* L; g" @% C, {& H
比如 发送数据帧0x0F 在数据帧里就是低位线性 即 1111 0000
1 D p& r5 n4 k6 j
校验位,用于数据验证,根据数据位的计算得来。有奇校验,偶校验和无校验。
. V% e$ z6 @# g2 m3 Q( ^! l
停止位,用于数据的间隔,固定为高电平。数据帧发送完成后,产生一个上升沿。(数据传输–>停止位) : f: ^: U( s/ W
; ~4 ^% ~( L4 f; U( o
下方就是一个字节数据的传输过程,从图中可以看出,串口发送的数据一般都是以数据帧的形式进行传输,每个数据帧都由起始位,数据位,停止位组成, 且停止位可变。
7 h; D+ _2 m, K+ p0 A4 S" ~5 }
( z, f: t$ T( x K3 Z* ~- r1 A+ W
; |0 S8 U8 E: N. S% ~$ d
二、串口的发送和接收 USART是STM32内部集成的硬件外设,可以根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可以自动接收RX引脚的数据帧时序,拼接成一个字节数据,存放在数据寄存器里。 : e1 J: |( n& S, x. ^" X$ h
当配置好USART的电路之后,直接读取数据寄存器,就可以自动发送数据和接收数据了。在发送和接收的模块有4个重要的寄存器 - ~/ s+ d( q! @' \& O3 e7 `
5 P4 {; L* e# n1 O1 g
下方为串口的发送和接收图解:
4 l% H2 G( E# v, V
' s" e. g+ u0 z9 Q2 \
! o) h0 j) E; p. f* q
串口发送
% C4 l6 A2 D6 d3 X& |
在配置串口的各个参数时,可以选择发送数据帧的数据位的大小,可选8位或9位。 * O) G# z+ \3 [8 p
串口发送数据实际上就是对发送数据寄存器TDR进行写操作。 # ]+ {( m" F0 `
1. 当串口发送数据时,会检测发送移位寄存器是不是有数据正在移位,如果没有移位,那么这个数据就会立刻转移到发送移位寄存器里。准备发送。 / C; u4 B4 R7 W' Z V
2. 当数据移动到移位寄存器时,会产生一个TXE发送寄存器空标志位,该位描述如下。当TXE被置1,那么就可以在TDR写入下一个数据了。即发送下一个数据。
$ K$ v1 f+ e: L% H5 o8 x
* G3 X* O3 u, A2 e( W" W( g
) K+ Q3 d- N7 S2 B& o: v1 d5 ^. d
3. 发送移位寄存器在发送器控制的控制下,向右移位,一位一位的把数据传输到TX引脚。
: A; j4 g5 D' @0 t! H- @
6 k. ]# ]) L2 m; n2 z2 B' x% f* U' u2 ~
4. 数据移位完成后,新的数据就会再次从TDR转移到发送移位寄存器里来,依次重复1-3的过程。通过读取TXE标志位来判断是否发送下一个数据。
5 i- @. J+ Y( |6 `7 D1 q
串口接收
; M N' c: D1 D9 h8 `3 E8 q' y
数据从RX引脚通向接收移位寄存器,在接收控制的控制下,一位一位的读取RX的电平,把第一位放在最高位,然后右移,移位八次之后就可以接收一个字节了。
6 B- T! X c$ }4 d
当一个字节数据移位完成之后,这一个字节的数据就会整体的移到接收数据寄存器RDR里来。
7 j) `( Z- H9 U- w; Q
在转移时会置RXNE接收标志位,即RDR寄存器非空,下方为该位的描述。当被置1后,就说明数据可以被读出。 . U x8 Z) R) A- y8 U: |( n
0 s. n2 w. s: ?: G, l- s: ?; e# N. Y& o: g- ~' o3 I/ R
下图即为串口接收的工作流程
" l' K% `. f0 P) a+ m; P+ z$ g
4 h+ Y. l/ U: [% ~, E* r5 D& U4 N. q9 n7 c4 ^0 \
三、串口在STM32中的配置
( u. W6 d0 U* t4 X# G: d9 Y
首先要明确几点:使用STM32串口外设中的哪一个?串口发送或者接收数据?串口相关的参数配置?发送或接收是否使用到中断? # q4 ^4 F Y7 K
下方为串口发送的配置。 7 t& j- h0 G' U6 `0 K4 N; M
1. RCC开启USART、串口TX/RX所对应的GPIO口 ! r7 r- m2 [4 E+ h4 _) j0 i; }
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE); //开启USART2的时钟
+ g$ y m: y: a* h2 @% N - RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //开启GPIOA的时钟
复制代码" W$ ]$ Q- a" @
2. 初始化GPIO口 3 P! ?; q! X" ?( d
这里注意哈,根据自己的需求来配置GPIO口,发送和接收是都需要还是只需要其中一个。然后对应的根据引脚定义表来初始化对应的GPIO口。
, y( f" o$ e7 ], O" H
USART2对应的引脚 : d9 G4 N1 J" N, ]3 @
% @' @" u& i/ Q8 O+ e# F0 g1 N' p! m9 m! e
USART1对应的引脚
6 G( {* X8 f) _* G
( j2 Y3 {# _+ _; @6 z8 [
7 `( h1 c4 r- z7 N8 ]# N ~
这里根据手册来看,RX引脚模式配置成浮空输入或者上拉输入。TX引脚模式配置成复用推挽输出。 . ^! d9 i L( c( N7 i4 F
**比如我这里只初始化TX发送端**- //TX端/ S0 W0 D3 P! L4 B) W, F
- GPIO_InitTypeDef GPIO_InitStructure;' R& R" E Q/ s" R
- GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; //复用推挽输出0 R( ]3 i q& t
- GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2; //USART2对应的TX端为GPIOA2$ E* S+ S0 p* U k; J& U
- GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //50MhZ( ^* @% h$ _' N- b _+ j
- GPIO_Init(GPIOA,&GPIO_InitStructure);
复制代码( I/ U8 K5 G, u
3. 串口初始化 ! @ d5 f9 ?8 z; E4 x2 `0 a: E
注意哈,USART_Init()这个函数,是用来配置串口的相关参数的。 7 `, h! T/ K6 [# X' i
USART_BaudRate 串口通信使用的波特率 一般是9600或者是115200,这里我们给9600
% W; [/ X$ ?9 q2 G
USART_HardwareFlowControl 是否选择硬件流触发,一般这个我们也不选,所以选择无硬件流触发。 4 ?4 U1 x7 L+ E' R/ j' W2 u Q
USART_Mode 这个参数要注意了哈,串口的模式,发送模式还是接收模式,还是两者都有 : l/ ^& a1 A, b. J9 Y
USART_Parity 校验位,可以选择奇偶校验和不校验。没有需求就直接无校验
9 ]: o5 d# M* @
USART_StopBits 停止位 有1、0.5、2位,我们这里选1位停止位 - y6 S6 C3 C! s- R: Q/ N7 y
USART_WordLength 数据位 有8位和9位可以选择
r/ J' f M; `( \8 Z5 D
- //串口初始化
' Q; Q0 `2 l/ J1 d$ p7 @ - USART_InitTypeDef USART_InitStruct;" _1 r2 N. N% p# V
- USART_StructInit(&USART_InitStruct); //初始默认值% j/ f* Y2 T& P: c+ ]% I
- USART_InitStruct.USART_BaudRate=9600;
$ g2 r& J8 r0 Z5 X3 N. c+ w - USART_InitStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None; //不使用硬件流触发* o2 }: A7 k. B
- USART_InitStruct.USART_Mode=USART_Mode_Tx; //TX 发送模式3 L$ \3 l2 X& u2 |' R) p) g4 h
- USART_InitStruct.USART_Parity=USART_Parity_No; //不选择校验8 f, i0 x8 J- N, ^/ D' y% W
- USART_InitStruct.USART_StopBits=USART_StopBits_1; //停止位1位
* A' c9 p. ~/ ^1 a+ N - USART_InitStruct.USART_WordLength=USART_WordLength_8b; //数据位8位, ~& H7 u$ M' W" x( Z* \( `5 c2 p$ L
- USART_Init(USART2,&USART_InitStruct);
复制代码* D2 l! w# x' } T8 }, m
4. 串口使能 - //串口使能* V2 g6 L& ]& O% G# N4 L8 B. ?: @
- USART_Cmd(USART2,ENABLE);
复制代码
! y5 m3 J% s9 t
5. 串口发送数据 0 G8 |/ {3 f+ F7 g/ J; Y
注意哈,我们要判断TXE标志位的状态。0,数据还没有被转移到移位寄存器;1,数据已经被转移到移位寄存器。 ! d) H8 B# y" I) U
当TXE标志位为1时,就说明可以发送下一个数据了。详细过程可看上面串口发送的解释。 - void Serial_SendByte(uint16_t Byte)
1 j9 M2 t( q# S8 I; W ~9 W" r, j2 ?8 Z - {
1 C l% U2 h1 y! j& t - USART_SendData(USART2,Byte);
% w$ t1 b( g+ q - //0 表示数据还未转移到移位寄存器 循环等待 1 数据已经被转移到了移位寄存器可以发送数据& Q- Z( }& U5 B, t+ C2 m. r
- while(USART_GetFlagStatus(USART2,USART_FLAG_TXE)==RESET); //不需要手动清零 再次写入TDR时会自动清零 7 O$ ~3 c$ f/ @& C2 S+ I
- }
复制代码/ ^7 H1 V8 T0 g! R: N# O7 W
经过上述五步的配置,单片机就可以通过串口发送数据了。 E* k. a. a6 M
下方为发送数据图例,STM32向串口发送0x16数据
; f& K/ a2 f$ R& n' _9 B
7 M0 \% j8 s, X4 Y, y7 T/ ~! J+ h: x* c8 P6 O2 R1 D" f
2 {$ K6 I8 [ S- n& M% {
四、串口接收的两种实现方式 上方是发送数据的例子,那么串口接收又该如何配置,又要在串口发送的例子上做哪些更改呢? ) C. a b2 \8 Y5 Z' n8 x- J" o
这里我们可以通过查询或者中断的方式来进行接收数据的两种方式。
7 Z3 L4 w+ f$ A' s9 i, X
+ h0 V2 _' G; z$ v! G
1. 需要更改的地方
# J3 o# E) e \4 u$ c c. p- f3 \/ z+ ]0 b0 ]+ e9 E
既然我们要实现串口的接收,那么就要配置串口RX引脚,在串口模式中添加USART_Mode_RX模式。 + H$ Z5 _& }, U4 P3 c4 f* m- n
初始化RX引脚 ; d) C2 G. K& ^# X3 b
- //RX端
) `/ z1 s/ X5 ~4 m. U4 c. A' q - GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU; //上拉输入
7 G N5 T3 Q" L; k9 z - GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3; A6 \, D; m* [! V( y
- GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //50MhZ; Z" f' z1 S- I2 m) _+ D
- GPIO_Init(GPIOA,&GPIO_InitStructure);
复制代码
8 o3 g: t' X! g5 k- J L3 K9 f" W4 H
4 K3 x/ X) W% }# A
添加串口模式 5 Z+ C' _3 C- q. j& S# S4 c- K# t
- USART_InitStruct.USART_Mode=USART_Mode_Tx|USART_Mode_Rx; //TX 发送模式 RX 接收模式
复制代码
# C. o0 D9 h+ R% H* H
+ W1 K; t& k$ m& `0 W; P6 t
2. 查询RXNE标志位 & ~& m1 \0 M$ w: g _! E* M
这里我们还是来看一看RXNE标志位的描述
# `2 g9 p! q+ w& [
, J3 i0 [7 j% _( k b( w
" ~' v3 g0 w3 c
上图描述,为0时数据没有收到,为1时收到了数据,数据可以从RDR里读出
* x c# y8 N- `) ]
所以在主程序里不断读取RXNE标志位,如果为1,表示数据可以读出 - uint8_t RX_Data;. E/ r# M m+ f: N3 q% Q8 H7 U9 G
- int main()* m! `. h9 q! M: J4 I
- { ; W$ e. w8 U+ L1 y" z
- Serial_Init();8 A( L" z$ L6 ]4 x. q
- Serial_SendByte(0x16);
6 g& N Y+ M% b - while(1)
; R1 g/ Q4 y- [1 Y - {# B, b/ Y$ Z# E2 q$ t% ]' A
- if(USART_GetFlagStatus(USART2,USART_FLAG_RXNE)==SET) //0 循环等待 1 可以接收数据" D, K9 P+ O' P h& \0 @6 Z/ n8 K
- {
8 a+ H, @" u7 ^$ y - RX_Data=USART_ReceiveData(USART2); . c% y1 ~' ~+ O7 ?1 l* a; w
- Serial_SendByte(RX_Data);0 \0 o6 ^/ O3 e- t ^2 P" o0 n: o
- }
7 P8 P" I6 B( t7 M$ u. U - }+ }! A2 @3 K8 I7 R) U1 _8 x
- }
复制代码& u* _6 T1 t+ c! Y% } @2 U! i% R
下图为程序现象:pc向单片机发送数据0x15,单片机接收数据0x15,并且把接收到的数据作为数据发送到pc,在pc上显示0x15。
- L! h2 e6 u5 P$ [) V I. l& h
7 e) y2 p* O# H% A1 l7 p8 m O* _
+ d, v: n9 n x3 d
3. 使用中断 % y2 C# D6 i& L, [& N; n; z
- //开启中断输出控制! @& q+ R! i9 E! u6 J5 l
- USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);
! k' ?6 x7 k/ S" x" r0 V0 b! f - * K3 V; c2 S# u3 X' `, k& Z$ _
- //配置NVIC
. A- n$ _/ V c, U& e9 G4 T6 s - NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 0 t5 ]; [# `& y7 E* W. [ A
- NVIC_InitTypeDef NVIC_InitStruct;, _5 U: `* H6 |8 D+ H
- NVIC_InitStruct.NVIC_IRQChannel=USART2_IRQn; //选择USART2的中断通道
( b! X% ?7 F% f! s/ \+ X! _ - NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE; //中断使能
1 ^5 L3 B* ?1 [, }3 j' y - NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=1;
7 `4 ? e) g, m( ?+ p( ` - NVIC_InitStruct.NVIC_IRQChannelSubPriority=1;
+ C$ O1 F& J& L: E/ q/ x; q - NVIC_Init(&NVIC_InitStruct);
复制代码 5 u- ~1 a8 O, }; ^( j; @) W: b. A
! \/ e# m$ a0 k* p- v6 G, E
中断服务子函数 9 p5 j; _" c9 W. C+ P
5 q& w) z8 l4 }1 G
中断服务子函数写好后,就可以在中断里读取接收到的数据了。
: ]/ n* A9 w8 g! T) x3 s当接收到数据后,触发接收中断,主程序暂停执行。接收完数据后主程序回复执行。当接收到数据时,就触发中断。
- void USART2_IRQHandler(void)
3 |# X8 `3 O) a! m - {. N, O7 p( @6 v0 u0 ^/ j, z) }
- if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET) //RXNE 标志位为1 表示可以接收数据) p& r" H- W5 j. t5 T
- {
7 |0 S5 j8 f# p4 V6 K - RX_Data=USART_ReceiveData(USART2);
; R1 ]" @0 ]0 z5 R- ~1 R3 Q. U - Flag=1;
6 {" ?: [2 t7 \: U Z. ^ - USART_ClearITPendingBit(USART2,USART_IT_RXNE); //清除RXNE标志位: F; Q1 d, t& X* p( z0 ~4 d
- }
& m3 h! R0 A& w0 [ - }
复制代码 $ I* k0 t3 P9 T) `' X, A2 g
主程序测试
0 ^- n5 {$ t( W9 S# m+ ?7 Y
- uint8_t RX_Data;
2 g) G6 u5 D' W" X1 t% D - uint8_t Flag;
+ ^) w: P# @' b: U1 [- f - . `3 u5 k M- Z2 n4 @. a, E
- int main()
8 U) y) C8 d: }4 t2 K/ ?% o - {: Q' y4 n& [, o2 A3 O* }, O
- Serial_Init();3 v. \" `& Q: c3 f# i
- Serial_SendByte(0x16);
' [* N/ K! R8 J) _: w9 p
4 H$ }! e7 W& `$ _9 `, Q
- P# R! x2 U9 y5 n& |9 _, a- while(1)! f. V$ L7 u/ H5 `
- {7 ^0 H3 t% N7 S( U( ?# K& O% ]3 B
- if(Flag==1)
+ {) E5 q( l7 s0 y& V* p - {
, |8 f. N# {3 @' U J - Serial_SendByte(RX_Data);
3 C2 ~3 F/ j- N8 I8 c" |( @( | - }- C: m8 H* A1 t5 H- u& Q& `
- }5 F- `' f" M1 U4 b9 a z
- }9 J8 U& ] \7 J* R& E% Z
5 F( e7 ~( m [$ z( q- void USART2_IRQHandler(void)
2 J: c9 Q7 s" \' M. n - {
1 I8 v' x; R# U3 I - if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET) //RXNE 标志位为1 表示可以接收数据
% c+ V1 Z5 L( `0 P( ` - {
2 @, d6 r9 R& E, G0 L% F - RX_Data=USART_ReceiveData(USART2);& B6 H, I. L& i
- Flag=1;
6 {0 t7 b: }3 h& ]7 y - USART_ClearITPendingBit(USART2,USART_IT_RXNE); //清除RXNE标志位8 \2 D! h% r! T+ j, x" K; q" U* u
- }
" _1 U0 ^$ `4 Q3 N0 ?- [ - }
复制代码 7 Q; n# U4 i8 F: d% ~$ n* N* I( U! S
) A7 [& e* J5 Q( r9 O
下图为程序现象:可以看到,串口确实收到了数据,只是我把接收到的数据0xFE放在了while循环里,这说明数据接收是成功的,使用中断是可行的。
, L' H5 n5 ~) r
1 L( z: i8 R& l& M7 ]# w
' Y! h( {2 P4 ^! ^
+ X( L5 f; G/ c1 Q& x; P
总结 到这里,就大致总结了串口的发送和接收。 , w+ q% h6 M1 x8 o; x, k
串口的配置,使用查询或者中断来接收数据。 6 E! y2 W F4 |: d% u
串口的使用会很常用到,所以在这里对串口做一个总结,也算是对之前知识的一个回顾和总结,加强印象。 . r: ? b! N/ ?6 N$ \% e6 b
如果有什么写的不对的地方,欢迎指正! % {1 P1 w/ t: E4 Q
转载自: 古月居
% i( @7 n' a9 X
# P8 ?! N) _0 M! M/ m |