|
STM32的串口通信 + y& `4 N9 x Q* y
本文在于记录自己的学习过程中遇到的问题和总结,各种情况下串口通信在STM32的实际使用方面占有很大的比重,本文主要对串口通信做一个简要的总结。 % d M+ @; c7 `. Z3 I
( Q& g! i6 q0 o1 Q
一、STM32里的串口通信
: V7 u. b% b6 j( W) f& A9 j5 E
在STM32里,串口通信是USART,STM32可以通过串口和其他设备进行传输并行数据,是全双工,异步时钟控制,设备之间是点对点的传输。 . N' ~9 Y; Z, `1 z) a' E) e4 u
对应的STM32引脚分别是RX和TX端。STM32的串口资源有USART1、USART2、USART3. $ `( J' r _& Z" [! \
串口的几个重要的参数:
, D! q* n8 v2 z n0 S# Z7 o
波特率,串口通信的速率
- w' x+ B( H- e
空闲,一般为高电平 3 f5 L7 w! l6 @' f- L9 j% W
起始位,标志一个数据帧的开始,固定为低电平。当数据开始发送时,产生一个下降沿。(空闲–>起始位)
& @! @" x' @! ]7 E! ^* b8 f- @
数据位,发送数据帧,1为高电平,0为低电平。低位先行。
- L# h8 f2 J& V/ @" Z& t
比如 发送数据帧0x0F 在数据帧里就是低位线性 即 1111 0000 0 M5 j K$ R8 V
校验位,用于数据验证,根据数据位的计算得来。有奇校验,偶校验和无校验。
2 K& h" h: S! a0 S! I
停止位,用于数据的间隔,固定为高电平。数据帧发送完成后,产生一个上升沿。(数据传输–>停止位)
: Y$ U7 ~9 ]5 a, @; o' B
( A3 P! v/ D+ S9 c$ X( N; L
下方就是一个字节数据的传输过程,从图中可以看出,串口发送的数据一般都是以数据帧的形式进行传输,每个数据帧都由起始位,数据位,停止位组成, 且停止位可变。
1 ~( n* }1 g) y) }+ Z
( H3 f9 C# w, j6 B
- ?7 g! P7 {1 J$ G- W. A6 r
! G/ {0 y, g2 w0 h
二、串口的发送和接收 9 {- a B* O! D4 q$ n
USART是STM32内部集成的硬件外设,可以根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可以自动接收RX引脚的数据帧时序,拼接成一个字节数据,存放在数据寄存器里。 5 @5 T9 R L, J
当配置好USART的电路之后,直接读取数据寄存器,就可以自动发送数据和接收数据了。在发送和接收的模块有4个重要的寄存器 7 C$ _' K4 b2 M) Y
+ [. U6 p" O3 h4 ?- Z
下方为串口的发送和接收图解: 7 V3 t& }* Z2 S
5 @! o% \; I4 X& S
2 Y' u$ A2 B5 e+ p8 O9 }& q' j6 `* v
串口发送
% _$ k* j+ }2 E$ \
在配置串口的各个参数时,可以选择发送数据帧的数据位的大小,可选8位或9位。 ' s3 k3 k6 p$ R' w& N4 P
串口发送数据实际上就是对发送数据寄存器TDR进行写操作。
$ D' \/ s9 R' r% E9 K9 g
1. 当串口发送数据时,会检测发送移位寄存器是不是有数据正在移位,如果没有移位,那么这个数据就会立刻转移到发送移位寄存器里。准备发送。
: U. U; e0 _; b! ~8 _8 z- t1 @
2. 当数据移动到移位寄存器时,会产生一个TXE发送寄存器空标志位,该位描述如下。当TXE被置1,那么就可以在TDR写入下一个数据了。即发送下一个数据。
. \! i5 a- w7 }0 X# f
: |4 P _# L2 l* |& x8 t! V
" c7 b/ g9 t1 P! m3 T& d
3. 发送移位寄存器在发送器控制的控制下,向右移位,一位一位的把数据传输到TX引脚。 ( H0 v- n3 m# g6 Y. I1 c5 J
# Z9 U. N6 |- N7 Z) ^
1 g8 U4 W0 t- a5 y- i' Z$ `
4. 数据移位完成后,新的数据就会再次从TDR转移到发送移位寄存器里来,依次重复1-3的过程。通过读取TXE标志位来判断是否发送下一个数据。
3 f9 s+ M4 B. ?. M
串口接收 : ]( C2 ]- Y0 `, u: ]; k& K
数据从RX引脚通向接收移位寄存器,在接收控制的控制下,一位一位的读取RX的电平,把第一位放在最高位,然后右移,移位八次之后就可以接收一个字节了。 ' F. Q) ?' c& ] t1 c3 E
当一个字节数据移位完成之后,这一个字节的数据就会整体的移到接收数据寄存器RDR里来。 # n. h+ I9 K& o
在转移时会置RXNE接收标志位,即RDR寄存器非空,下方为该位的描述。当被置1后,就说明数据可以被读出。 9 V9 e9 o' Q# ~: |
1 M/ E# x% I* u0 ]
3 R1 g( p0 E' G( o5 O! h a3 Z1 K3 X
下图即为串口接收的工作流程
' ~7 S8 q1 c- n( U/ ]# ^& ^2 ~
! M5 B2 g- \. [# l% z+ d5 v2 v! X6 t9 @ Q5 }- H4 C! A/ q
) z9 h7 a4 ~2 b, i7 s2 r三、串口在STM32中的配置 : q/ B) x( t D3 [* K0 P
首先要明确几点:使用STM32串口外设中的哪一个?串口发送或者接收数据?串口相关的参数配置?发送或接收是否使用到中断?
. i& i. }' _- k) A% Z
下方为串口发送的配置。
/ Q; P1 q7 `5 E: u% W1 [5 {& {' o
1. RCC开启USART、串口TX/RX所对应的GPIO口 - RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE); //开启USART2的时钟
4 d( V0 b' d2 O6 Q! y' M& W4 X2 c - RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //开启GPIOA的时钟
复制代码5 R8 T) ^9 o3 f% X1 J
2. 初始化GPIO口
2 c0 C% K% q) ?% l: e
这里注意哈,根据自己的需求来配置GPIO口,发送和接收是都需要还是只需要其中一个。然后对应的根据引脚定义表来初始化对应的GPIO口。 , h) \% d& o7 @+ T; \7 u
USART2对应的引脚 + D9 a! |/ c- F" Y, V
! l6 l& h% X/ U3 i9 ]; k( O/ g: C4 D* i* G2 \
USART1对应的引脚
/ |8 L2 `' u/ p( t, U* |' y% y7 H
/ F& ~9 y; ^6 x
8 B2 G, x+ A1 v% L0 R, M1 j+ R
这里根据手册来看,RX引脚模式配置成浮空输入或者上拉输入。TX引脚模式配置成复用推挽输出。 * i9 g& c& k8 a9 ]. {
**比如我这里只初始化TX发送端**- //TX端
+ q$ z+ B3 f- Z- F9 i h - GPIO_InitTypeDef GPIO_InitStructure;
! r9 l+ J. Y! v3 ]2 o - GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP; //复用推挽输出
8 }7 ?, `/ g' h9 c7 w+ X - GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2; //USART2对应的TX端为GPIOA2
5 I( B) ?2 O) K6 [( \/ z - GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //50MhZ Z6 _+ V; m& b* Z* q. A
- GPIO_Init(GPIOA,&GPIO_InitStructure);
复制代码
f& d5 a( l' `/ c8 C
3. 串口初始化 4 q" J( t2 W" h, J9 E" R6 n- N* N
注意哈,USART_Init()这个函数,是用来配置串口的相关参数的。
- {# Q$ `! ]+ J" P2 L+ B
USART_BaudRate 串口通信使用的波特率 一般是9600或者是115200,这里我们给9600 : U# H. P/ F0 {- Z2 Z! [% P' ?
USART_HardwareFlowControl 是否选择硬件流触发,一般这个我们也不选,所以选择无硬件流触发。 3 i4 U0 b% h- `, q' z, x
USART_Mode 这个参数要注意了哈,串口的模式,发送模式还是接收模式,还是两者都有 r; U( X+ f) d/ C$ d0 v/ c# [
USART_Parity 校验位,可以选择奇偶校验和不校验。没有需求就直接无校验 V) ]" n% x( n6 K3 R- N: q9 g
USART_StopBits 停止位 有1、0.5、2位,我们这里选1位停止位
7 S$ Y) s4 f5 W; [" F$ L0 u
USART_WordLength 数据位 有8位和9位可以选择 - //串口初始化
- L d! T9 C N3 |! G" ~! M; m - USART_InitTypeDef USART_InitStruct;
5 k" v9 K! Z" Y" | - USART_StructInit(&USART_InitStruct); //初始默认值$ z, u; [: a5 ]+ d
- USART_InitStruct.USART_BaudRate=9600;
R8 H( o% V2 z' o - USART_InitStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None; //不使用硬件流触发# L# R8 }9 E5 k9 k
- USART_InitStruct.USART_Mode=USART_Mode_Tx; //TX 发送模式
: i P7 @. v3 R- m* Z" x - USART_InitStruct.USART_Parity=USART_Parity_No; //不选择校验
3 |: _7 C' _7 B) p - USART_InitStruct.USART_StopBits=USART_StopBits_1; //停止位1位
: Q7 K+ x" b: i6 F& F0 j - USART_InitStruct.USART_WordLength=USART_WordLength_8b; //数据位8位
$ U' `- s- y3 _ - USART_Init(USART2,&USART_InitStruct);
复制代码 8 i: U* p, n. S( `* W9 q
: N! V8 ? o1 Z U
4. 串口使能 - //串口使能$ z8 f0 ]* k+ z9 Y
- USART_Cmd(USART2,ENABLE);
复制代码
, @6 @7 {6 G1 Z ?- ?
5. 串口发送数据 1 b' J/ o( ^/ z! Q* @) J
注意哈,我们要判断TXE标志位的状态。0,数据还没有被转移到移位寄存器;1,数据已经被转移到移位寄存器。 , l/ y h0 y- ^6 a) s% g
当TXE标志位为1时,就说明可以发送下一个数据了。详细过程可看上面串口发送的解释。 - void Serial_SendByte(uint16_t Byte)
( c9 V y1 h) [" t X$ o - {8 p$ i# P/ T, a3 T2 ?7 o0 G
- USART_SendData(USART2,Byte);6 M" X& v( |0 ]
- //0 表示数据还未转移到移位寄存器 循环等待 1 数据已经被转移到了移位寄存器可以发送数据
+ D' N+ E8 P ^. U - while(USART_GetFlagStatus(USART2,USART_FLAG_TXE)==RESET); //不需要手动清零 再次写入TDR时会自动清零
2 p; x3 b+ U! J* A* ]4 } - }
复制代码, U/ D8 H5 C% ^' H/ G
经过上述五步的配置,单片机就可以通过串口发送数据了。 4 u+ w- h) s3 D, ^' L& N
下方为发送数据图例,STM32向串口发送0x16数据 }4 N7 [1 e. s" |7 o- \
5 s; b o7 m* `# i4 `
& }7 u7 B2 Y( F+ M4 @
四、串口接收的两种实现方式 2 X) ^, @- r# P& z; ]# e
上方是发送数据的例子,那么串口接收又该如何配置,又要在串口发送的例子上做哪些更改呢?
* a+ c: e) \" X/ J
这里我们可以通过查询或者中断的方式来进行接收数据的两种方式。
* u" C% H' |: Y. E' p
查询方式就是通过不断的查询RXNE标志位,通过判断RXNE位的状态来确定数据是否接收。 ' B1 A% U, e' I+ f- N6 g) r
中断方式就是通过配置接收输出控制通道,配置NVIC,在中断服务子函数里进行数据的接收。 & @. H. F8 `& q. D: m
% n, ^0 \: z' y6 x4 @) d: z
1. 需要更改的地方
# A0 I3 m; w+ ~, b9 n6 }% y2 I( s5 y" {0 z$ T5 f
既然我们要实现串口的接收,那么就要配置串口RX引脚,在串口模式中添加USART_Mode_RX模式。
9 E2 T0 ]$ u3 u, Q3 b5 A0 J3 Z6 Z4 Q
初始化RX引脚 - //RX端: h) t( t* l+ s X- n( y
- GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU; //上拉输入2 B. {" N1 |+ j0 ?) ^" w. C
- GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3;2 N* d7 R8 r& j4 R' J* O
- GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; //50MhZ
: x' Y8 j* n5 n* ]' H1 K- d - GPIO_Init(GPIOA,&GPIO_InitStructure);
复制代码
0 ]& n: H4 y, q1 r6 L$ P/ g
8 V5 u3 ~8 t8 V6 W
添加串口模式 - USART_InitStruct.USART_Mode=USART_Mode_Tx|USART_Mode_Rx; //TX 发送模式 RX 接收模式
复制代码
* w" c% D3 ?& Y1 O! A: U% d4 ^
$ P+ T# ~) K2 _' C
2. 查询RXNE标志位
, G- t9 l+ t- I0 x/ P; D# L- N
这里我们还是来看一看RXNE标志位的描述
' g6 Q; f, K/ ^1 b1 a
) F' r; w. x: H5 ?
3 }+ W9 Q6 @8 a- F
上图描述,为0时数据没有收到,为1时收到了数据,数据可以从RDR里读出 ( n0 ^, U7 L1 `; [
所以在主程序里不断读取RXNE标志位,如果为1,表示数据可以读出 - uint8_t RX_Data;% d; i$ `' y! C" \% N1 ]" ~2 y
- int main()* Y" b# [& Z; Q+ {! _$ l
- {
6 k( u# H" y+ L. W - Serial_Init();
/ e8 n q$ s' C - Serial_SendByte(0x16);6 C6 K5 G# [# D
- while(1)
/ E; v" [* R0 `, K0 p! _ - {
( y4 m2 n' @9 S6 x7 P9 E - if(USART_GetFlagStatus(USART2,USART_FLAG_RXNE)==SET) //0 循环等待 1 可以接收数据 K( A& z0 i! y8 G! w2 d$ f$ P
- {
6 C e4 O5 }8 @! f, ` - RX_Data=USART_ReceiveData(USART2);
, W' T' G& l4 p9 s - Serial_SendByte(RX_Data);! I& J5 l+ O& Q
- }& ?% [4 L3 m7 Y" x! Z6 U! Q& f
- }
2 u' s+ }2 A4 P/ V" b - }
复制代码
p7 X" {' h) X, Z; l. `
下图为程序现象:pc向单片机发送数据0x15,单片机接收数据0x15,并且把接收到的数据作为数据发送到pc,在pc上显示0x15。
( S1 E4 H5 `. j- H: s, q3 C
! p! {. |. l$ H- _; ?: l/ L) Y
" l: j0 _0 w$ z C& K3 I1 S2 G
3. 使用中断
+ J- c, Q! ^! |1 W4 G' h5 z
- //开启中断输出控制/ Q# q4 ]+ l/ _* Q
- USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);
{$ `- H( z& K* H -
( \ m8 ?, P+ n* C) N$ @ - //配置NVIC
/ j6 v i9 l7 n: B - NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 0 q2 ~+ `$ j# E4 Q* \3 k7 M. F
- NVIC_InitTypeDef NVIC_InitStruct;
1 x8 A z$ J/ f; J8 S" u+ ] - NVIC_InitStruct.NVIC_IRQChannel=USART2_IRQn; //选择USART2的中断通道
, T: h! \, d* p% _ - NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE; //中断使能- P% z) k; ~! ~- T$ g5 R @& c, t
- NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=1;
; n* G' v5 @, X4 `8 ? X - NVIC_InitStruct.NVIC_IRQChannelSubPriority=1;
' J9 `# E4 J; N - NVIC_Init(&NVIC_InitStruct);
复制代码
1 x5 P- c; B" y& {1 i2 Q k
中断服务子函数
' T$ g' v4 t; R0 }, a' |! T! D7 E
. t$ k; r/ c Y7 y9 {
中断服务子函数写好后,就可以在中断里读取接收到的数据了。
+ Q6 R. U& ]/ M1 e6 C& @当接收到数据后,触发接收中断,主程序暂停执行。接收完数据后主程序回复执行。当接收到数据时,就触发中断。
- void USART2_IRQHandler(void)
$ a, D4 k9 Q( z$ M/ v - {
5 M7 u8 s' x& H0 p& b; o6 v' G- d9 t - if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET) //RXNE 标志位为1 表示可以接收数据
7 N% F' k/ y o* W4 W - {
( K% C0 ~+ s% U2 ?8 s- u& ~0 a - RX_Data=USART_ReceiveData(USART2);- l3 ?' V% _! a6 C7 C
- Flag=1;9 h+ G6 A. i' ]* N
- USART_ClearITPendingBit(USART2,USART_IT_RXNE); //清除RXNE标志位
5 k1 Y/ ` j8 q - }
4 ?5 x% X \8 x* W/ s& w' D - }
复制代码
/ p- E0 p4 `' n
- uint8_t RX_Data;
. @+ g. w; v# Y8 ?* ]! _ - uint8_t Flag; a- x* V+ P( X3 ~1 J$ R) G0 ?# P5 `: u
8 F$ R0 l" ?, X: b ?- int main()) V$ H$ m3 T. S0 S! u* x
- {
! J# l K; y# Q1 R; R - Serial_Init(); d g8 g* v l6 n2 R. q
- Serial_SendByte(0x16);
3 y) p3 U) G) Z* z' \
' T; R. C, R3 m8 [9 S
% `9 H+ f/ \& h* {/ h- while(1)) h" T! p; g2 i/ {% e7 R
- {
' u5 N( `- i' l3 f: v- d - if(Flag==1)
8 y; l5 ]0 _1 e3 Q& X4 O - {
# Y" R6 w8 r3 l1 a5 R/ `/ i8 p - Serial_SendByte(RX_Data);
* B# j3 J d- B$ q - }9 o7 x; X5 U" u- v
- }
! n; p# h4 Q2 ?5 ^% O; G - }
# V' \4 f2 U+ M) {) m
% }& s' S5 C( ~" ~: A4 U2 _* w5 R% r- void USART2_IRQHandler(void)
* o6 v$ m% X1 B& \ - {: i2 `) d' M% u" _: U3 c3 S& h1 N
- if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET) //RXNE 标志位为1 表示可以接收数据2 q3 W/ c2 S, {% _* e
- {
) Y) W$ L; d% j1 `) r - RX_Data=USART_ReceiveData(USART2);
) `' \( K% o8 p* T7 I - Flag=1;
! k% e4 ], ?; ^$ o' M1 Q - USART_ClearITPendingBit(USART2,USART_IT_RXNE); //清除RXNE标志位
/ ~3 o2 D. ?& i* H, ] - }6 T) g1 b$ x3 y2 F. f3 o; q* C
- }
复制代码 - J- o" C$ x# q* c" l2 K0 V
3 @) {7 A- o% F& L
下图为程序现象:可以看到,串口确实收到了数据,只是我把接收到的数据0xFE放在了while循环里,这说明数据接收是成功的,使用中断是可行的。
|5 V; r( \+ ~! Z- t# P% x8 x
! k! w8 l* H: {/ R' H+ p
) f. w! P4 L, s/ g& \
1 Z' j3 g0 n8 P7 l' x" F. x' l( I总结
* ]% b. U. V+ g3 E+ C1 n0 R& s
到这里,就大致总结了串口的发送和接收。
9 D/ j9 U! \! O5 l* c* c7 v
串口的配置,使用查询或者中断来接收数据。 2 `6 @& C! V0 c. R' {, i9 p
串口的使用会很常用到,所以在这里对串口做一个总结,也算是对之前知识的一个回顾和总结,加强印象。 * j2 N# e( J1 {1 [
转载自: 古月居 如有侵权请联系删除
- G0 a% \; d. f; H0 s @ 9 x9 l h; P& c4 X( c# X! G
|