你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

基于STM32的串口通信经验分享

[复制链接]
攻城狮Melo 发布时间:2023-8-10 14:48
STM32的串口通信

* S2 L! Q# u$ J) i
本文在于记录自己的学习过程中遇到的问题和总结,各种情况下串口通信在STM32的实际使用方面占有很大的比重,本文主要对串口通信做一个简要的总结。
2 C6 s( ~. I6 G9 w0 h
6 g! `; l% k6 `. {: U
一、STM32里的串口通信

" d; O1 D5 C4 l9 i
在STM32里,串口通信是USART,STM32可以通过串口和其他设备进行传输并行数据,是全双工,异步时钟控制,设备之间是点对点的传输。

% l4 N# ]( N8 ~# B% M, q
对应的STM32引脚分别是RX和TX端。STM32的串口资源有USART1、USART2、USART3.

6 |& l/ Z2 y1 s+ g# p3 l0 g
串口的几个重要的参数:
2 J! {0 u8 j, I' Q' ], }: q& F  _6 ~
  • 波特率,串口通信的速率
    4 d  M; U, z) T9 C$ m: P
  • 空闲,一般为高电平

      l- J/ w, O+ S6 W9 u: g  G
  • 起始位,标志一个数据帧的开始,固定为低电平。当数据开始发送时,产生一个下降沿。(空闲–>起始位)

    6 b5 K% r; [% I
  • 数据位,发送数据帧,1为高电平,0为低电平。低位先行。
    0 c2 a& f& x1 ^3 C* [
  • 比如 发送数据帧0x0F 在数据帧里就是低位线性 即 1111 0000
    ) s! Z) g4 k  q0 y
  • 校验位,用于数据验证,根据数据位的计算得来。有奇校验,偶校验和无校验。
    ; e$ P1 `/ R1 |7 j1 ]' L
  • 停止位,用于数据的间隔,固定为高电平。数据帧发送完成后,产生一个上升沿。(数据传输–>停止位)

    . }( o) {* {4 \# K, \5 U" @' e

$ W! ?8 A8 Z$ C+ g4 Y; P* m5 x- z7 ~
下方就是一个字节数据的传输过程,从图中可以看出,串口发送的数据一般都是以数据帧的形式进行传输,每个数据帧都由起始位,数据位,停止位组成, 且停止位可变。
. j; v2 p. P; Z$ @6 c. C
微信图片_20230810144703_3.png
' R0 \! P+ L( i8 h7 g
2 O2 f% S" T$ t

3 l6 p; n; Y% |/ n+ d& i7 T
二、串口的发送和接收

7 O+ S# p' |1 X; a. |# |
USART是STM32内部集成的硬件外设,可以根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可以自动接收RX引脚的数据帧时序,拼接成一个字节数据,存放在数据寄存器里。

* A; }4 o6 F* w; }8 h7 \, S2 H
当配置好USART的电路之后,直接读取数据寄存器,就可以自动发送数据和接收数据了。在发送和接收的模块有4个重要的寄存器

2 \& Y; C2 u! X2 S' w8 w
  • 发送数据寄存器TDR
    / o6 e* R/ M" Q9 @: d3 B* F, t
  • 发送移位寄存器,把一个字节的数据一位一位的移出去

    5 j6 j* I6 c& r0 C0 N* X  q- h, ?
  • 接收数据寄存器RDR
    0 E& Y+ X1 x4 D1 g+ s, I
  • 接收移位寄存器,把一个字节的数据
    9 o; S& Q# `& r" d

+ |& P; @& j6 Q, |: s; p4 `
下方为串口的发送和接收图解:

* ?6 L, y, d+ Y' a2 j- P0 {
微信图片_20230810144703_2.png 5 t0 `* E+ r3 {9 }$ m

' q! W0 }3 J; ]# d! ]) {
串口发送
4 F* v& H  o* Y4 v3 |; b' L
在配置串口的各个参数时,可以选择发送数据帧的数据位的大小,可选8位或9位。
: @& {2 ]7 ^9 @. i
串口发送数据实际上就是对发送数据寄存器TDR进行写操作。
+ l9 [- U4 }8 K  F8 n& k
1. 当串口发送数据时,会检测发送移位寄存器是不是有数据正在移位,如果没有移位,那么这个数据就会立刻转移到发送移位寄存器里。准备发送。

% J' ^: w9 l( Y& |+ _9 r0 {3 }
2. 当数据移动到移位寄存器时,会产生一个TXE发送寄存器空标志位,该位描述如下。当TXE被置1,那么就可以在TDR写入下一个数据了。即发送下一个数据。
3 C8 U; X# O% D/ |, L
微信图片_20230810144703_1.png
# l# e* E1 h( F3 O/ K- l. F
3 g: i7 _$ b8 V* ~" P4 U' R
3. 发送移位寄存器在发送器控制的控制下,向右移位,一位一位的把数据传输到TX引脚。
2 O, \2 b& n( C: `
微信图片_20230810144703.png 5 U+ H) K, u! P+ B& a
* l4 o  i4 \' Q4 m6 u
4. 数据移位完成后,新的数据就会再次从TDR转移到发送移位寄存器里来,依次重复1-3的过程。通过读取TXE标志位来判断是否发送下一个数据。
6 Y, v# E$ i* g, y" L2 V
串口接收
! \& _  _! p& v
数据从RX引脚通向接收移位寄存器,在接收控制的控制下,一位一位的读取RX的电平,把第一位放在最高位,然后右移,移位八次之后就可以接收一个字节了。
; D0 q/ T: {, }- z' Q
当一个字节数据移位完成之后,这一个字节的数据就会整体的移到接收数据寄存器RDR里来。

  q$ u+ v/ N% m- x) h9 v! w
在转移时会置RXNE接收标志位,即RDR寄存器非空,下方为该位的描述。当被置1后,就说明数据可以被读出。

; s1 `% H- ^; e5 ^! R; @8 R9 w& v0 _
微信图片_20230810144704_3.png 3 G5 L3 K' Z+ a$ W3 e
6 _, [0 J( d, M! p1 F
下图即为串口接收的工作流程
4 w+ y. D1 c* g
微信图片_20230810144704_2.png
0 w8 j' S$ I) c! _7 g7 a! K. h; t  G- F4 R/ R

5 }3 R2 t- }# u. L
三、串口在STM32中的配置
- i1 d9 \: K' Y  z
首先要明确几点:使用STM32串口外设中的哪一个?串口发送或者接收数据?串口相关的参数配置?发送或接收是否使用到中断?
5 {2 o7 `# \# Y& q5 i; \
下方为串口发送的配置。

# b5 d. s2 _$ G) a' a% t% a
1. RCC开启USART、串口TX/RX所对应的GPIO口
  1. RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);  //开启USART2的时钟$ m% F. _2 b6 Y+ `8 W- R
  2. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);   //开启GPIOA的时钟
复制代码
* C3 u" {# k! H1 ]! k
2. 初始化GPIO口
2 E0 J$ h* x% T3 B' a5 I
这里注意哈,根据自己的需求来配置GPIO口,发送和接收是都需要还是只需要其中一个。然后对应的根据引脚定义表来初始化对应的GPIO口。
8 b5 o4 J  j! W( r
USART2对应的引脚

; f% {: F) L9 |
微信图片_20230810144704_1.png 2 [; v0 z- z$ z9 u# k( w
+ e+ L7 c3 M2 W3 ~- U# z
USART1对应的引脚
% `) H, R4 \% A' ]. U$ I) D) m/ ?
微信图片_20230810144704.png 0 k& s; C* h8 t9 @0 d  ]1 q
4 h) Q0 A% l6 y) x5 o( L3 {
这里根据手册来看,RX引脚模式配置成浮空输入或者上拉输入。TX引脚模式配置成复用推挽输出。
$ Y. w5 x+ n5 k- z4 z
**比如我这里只初始化TX发送端**
  1. //TX端
    3 i# R( }+ P' X
  2.     GPIO_InitTypeDef GPIO_InitStructure;
    + Q0 l2 P# m, g- H, B
  3.     GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;  //复用推挽输出
    ( A+ O* q8 Q: N5 R4 W
  4.     GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2;   //USART2对应的TX端为GPIOA2
    : L4 e! v2 G7 m6 {
  5.     GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;  //50MhZ
    , H( q+ O% D2 {
  6.     GPIO_Init(GPIOA,&GPIO_InitStructure);
复制代码
+ @2 q  `0 ~6 \( m: c
3. 串口初始化
. b0 x: j8 N* L
注意哈,USART_Init()这个函数,是用来配置串口的相关参数的。

0 x5 w# R$ S' ?0 L; i
  • USART_BaudRate 串口通信使用的波特率 一般是9600或者是115200,这里我们给9600

    $ n$ B1 O6 C4 M  }* U
  • USART_HardwareFlowControl 是否选择硬件流触发,一般这个我们也不选,所以选择无硬件流触发。
    7 v$ Y1 ?# e# E: @) ]7 x5 x3 A
  • USART_Mode 这个参数要注意了哈,串口的模式,发送模式还是接收模式,还是两者都有
    , B$ H" a8 E9 X4 t# F
  • USART_Parity 校验位,可以选择奇偶校验和不校验。没有需求就直接无校验

    $ i# a) d. X3 j- I" ]
  • USART_StopBits 停止位 有1、0.5、2位,我们这里选1位停止位

    ! }/ p3 @& b6 H7 A, ]
  • USART_WordLength 数据位 有8位和9位可以选择
    1. //串口初始化
      4 t8 `6 K& [* I& I4 U) Y8 e
    2.   USART_InitTypeDef USART_InitStruct;& U3 G7 v  ]- C, K$ j) @
    3.   USART_StructInit(&USART_InitStruct);  //初始默认值
      0 [) X( R* l9 c2 ]4 t9 v
    4.   USART_InitStruct.USART_BaudRate=9600;. G' ^% H* b% F4 `+ p
    5.   USART_InitStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None;   //不使用硬件流触发! ^/ g4 @; K; B9 H
    6.   USART_InitStruct.USART_Mode=USART_Mode_Tx;         //TX 发送模式0 E/ r& @2 K0 [$ M1 Q6 ^: f" T
    7.   USART_InitStruct.USART_Parity=USART_Parity_No;       //不选择校验
      : p0 X" C& P/ |& ^- B* a; Y
    8.   USART_InitStruct.USART_StopBits=USART_StopBits_1;      //停止位1位6 `8 ?  j$ W7 e, Q; ]
    9.   USART_InitStruct.USART_WordLength=USART_WordLength_8b;  //数据位8位0 h) {% a, `& C  O- }- Z, y
    10.   USART_Init(USART2,&USART_InitStruct);
    复制代码
    5 W$ b8 K3 ]+ w9 B9 K% G' O

) _6 @* _* b4 z( K3 j8 ^% w8 h5 H
4. 串口使能
  1. //串口使能8 J" k4 y' i4 H; g2 k3 O8 _& T
  2.    USART_Cmd(USART2,ENABLE);
复制代码

: }4 x8 j; i* L$ I
5. 串口发送数据

5 T, a7 N6 ~9 j5 y: O8 g% E注意哈,我们要判断TXE标志位的状态。0,数据还没有被转移到移位寄存器;1,数据已经被转移到移位寄存器。
  A, d8 q7 F- w& P! G2 [7 `
当TXE标志位为1时,就说明可以发送下一个数据了。详细过程可看上面串口发送的解释。
  1. void Serial_SendByte(uint16_t Byte)# ]! [+ b  D5 y0 m: x: V" N
  2. {
    * y% `" h7 c  j0 c6 A. t
  3.   USART_SendData(USART2,Byte);3 E) a4 j( A; G* S( H9 P) S
  4.   //0 表示数据还未转移到移位寄存器 循环等待 1 数据已经被转移到了移位寄存器可以发送数据6 b4 ^2 R5 ^: B+ w
  5.   while(USART_GetFlagStatus(USART2,USART_FLAG_TXE)==RESET);  //不需要手动清零 再次写入TDR时会自动清零  
    % q7 b1 u- s; e( J
  6. }
复制代码

6 o4 l: J# u5 `3 w/ @& p! B
经过上述五步的配置,单片机就可以通过串口发送数据了。
; ^- o: J. R* a  h8 L3 }! v
下方为发送数据图例,STM32向串口发送0x16数据

7 m0 q4 a! }$ ~, R
微信图片_20230810144705_3.png
4 L# A! s2 v; t# }# u5 z0 ~

: A2 Y* a7 b: i4 ?
四、串口接收的两种实现方式
3 l0 d$ L6 y& D9 O+ u7 S
上方是发送数据的例子,那么串口接收又该如何配置,又要在串口发送的例子上做哪些更改呢?

; m, `6 X  V  E) s
这里我们可以通过查询或者中断的方式来进行接收数据的两种方式。
  |5 B( K' U" Z3 F
  • 查询方式就是通过不断的查询RXNE标志位,通过判断RXNE位的状态来确定数据是否接收。

    0 Y- J' O" x+ K- F
  • 中断方式就是通过配置接收输出控制通道,配置NVIC,在中断服务子函数里进行数据的接收。

    1 K% ]. Z/ f% u0 f% V

+ \# M9 I3 s0 T
1. 需要更改的地方$ Y- G8 m9 [: W6 f6 ~

8 T! x  m: d2 f% x: z
既然我们要实现串口的接收,那么就要配置串口RX引脚,在串口模式中添加USART_Mode_RX模式。
2 R7 i; j  \& Q$ e7 a# }8 W9 ?) F4 Y
  • 初始化RX引脚
    1. //RX端$ B5 m& \1 ^1 D  j6 b
    2.   GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;  //上拉输入
      + X8 T' h( F* s8 s8 T0 n  x
    3.     GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3;: W2 t3 J( o+ E( q! Y
    4.     GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;  //50MhZ
      6 T3 G' h+ ^  G: K$ u7 m
    5.     GPIO_Init(GPIOA,&GPIO_InitStructure);
    复制代码

    ' M+ w5 p  U; K: q2 p1 u( s

2 p+ c: T+ x: {* z3 l
  • 添加串口模式
    1. USART_InitStruct.USART_Mode=USART_Mode_Tx|USART_Mode_Rx;         //TX 发送模式  RX 接收模式
    复制代码
    6 {; i6 g6 a. ?9 s7 V7 z
% Q3 }& ~1 e! K8 x3 W4 M( M* ^3 b
2. 查询RXNE标志位
: i: I1 c7 F1 o$ r9 ?$ e7 [' r
这里我们还是来看一看RXNE标志位的描述
$ {1 N- N8 y1 I# X5 b. i
微信图片_20230810144705_2.png
4 ^* O' c! c( H5 [2 O! x7 _
1 X, Q& r' T$ E0 K( z
上图描述,为0时数据没有收到,为1时收到了数据,数据可以从RDR里读出
4 {- U' V, k0 z0 L2 ?
所以在主程序里不断读取RXNE标志位,如果为1,表示数据可以读出
  1. uint8_t RX_Data;
    & u2 y$ C  x; z$ h% Q1 }
  2. int main()
    - k) h. A1 b  \
  3. { ! y! s/ d) k; m2 M8 t7 m  g
  4.     Serial_Init();
    4 C; k0 y/ P3 `/ ?4 i' T. J
  5.     Serial_SendByte(0x16);8 @* ~4 A8 ~, q) q* I# K9 X: _
  6.     while(1). \" f. V; D8 h) w- u
  7.     {
    1 ?' a* v8 v. t7 |3 O8 i
  8.         if(USART_GetFlagStatus(USART2,USART_FLAG_RXNE)==SET)   //0 循环等待 1 可以接收数据
    , ?, _2 W3 q& x
  9.         {
    3 y& l2 h: S) D6 {. w0 }1 V1 p: b
  10.             RX_Data=USART_ReceiveData(USART2);           
    " r# R( `  `2 E8 b' a
  11.       Serial_SendByte(RX_Data);
    $ @# q2 U5 {! O
  12.         }5 e2 X* M  D# M! V$ L; Y+ n
  13.     }
    4 q/ h0 f7 M: g& S
  14. }
复制代码

4 E' `  x" C  `2 \3 s
下图为程序现象:pc向单片机发送数据0x15,单片机接收数据0x15,并且把接收到的数据作为数据发送到pc,在pc上显示0x15。
' D0 u3 }9 d" w
微信图片_20230810144705_1.png : i: \; @/ e, Q* t8 l% y0 }- t

; w1 w! l0 H. d% g" @. x
3. 使用中断
- M: c+ \# `2 D1 k& a! n3 C
  • 通过配置串口的接收作为中断源,开启中断输出控制,配置NVIC。开启中断通道。

    $ E$ T  n3 e$ ^) k" s+ @' W9 S
  1. //开启中断输出控制5 \* j1 b1 d  x4 R! g& m' I1 X
  2.   USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);
    " O6 K8 [& p" Q  F9 W
  3.   : K  O9 p+ {0 Z% u% I( X7 f, O. L
  4.   //配置NVIC7 b0 j9 W$ G& e$ h5 X. W& Q
  5.   NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    1 }6 U6 P2 f! q
  6.   NVIC_InitTypeDef NVIC_InitStruct;
    , C2 B' S3 B6 Y; x8 g* _0 w
  7.   NVIC_InitStruct.NVIC_IRQChannel=USART2_IRQn;   //选择USART2的中断通道
    9 c" w  [* \3 I( f$ X
  8.   NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;    //中断使能2 h  t! Q' |0 u  S) m; Q! ~
  9.   NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=1;
      F$ B+ w! o, ~- }6 e; ?4 A
  10.   NVIC_InitStruct.NVIC_IRQChannelSubPriority=1;% o3 e# P. n# T) T" O$ a9 d
  11.   NVIC_Init(&NVIC_InitStruct);
复制代码
! j: J* I$ L* D" L" j
  • 中断服务子函数

    3 T/ y0 {& l/ T  Z7 d0 C

/ J4 R; O3 M. `! {中断服务子函数写好后,就可以在中断里读取接收到的数据了。
) l  Z4 k1 l- ^/ L5 a# y0 j
当接收到数据后,触发接收中断,主程序暂停执行。接收完数据后主程序回复执行。当接收到数据时,就触发中断。
  1. void USART2_IRQHandler(void)
    1 w& N2 F" P) E) ^
  2. {
    " u' L: Q' \2 q4 ^
  3.   if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET)   //RXNE 标志位为1 表示可以接收数据6 S7 D3 c2 e" v5 Y2 }
  4.   {
    ( w7 R3 d5 Q9 N" y$ u/ S0 R
  5.     RX_Data=USART_ReceiveData(USART2);/ N# ?" @) X7 S9 i" l+ ?
  6.     Flag=1;
    ; C6 g" `! z: ~  J
  7.     USART_ClearITPendingBit(USART2,USART_IT_RXNE);  //清除RXNE标志位
    . O% p/ \2 d5 _, J6 p' w
  8.   }4 G! {: h9 J# A' H8 p, N; X/ s/ S
  9. }
复制代码

" k  p  P" A1 B/ E
  • 主程序测试

    $ T9 N% g5 w6 F5 c1 J
  1. uint8_t RX_Data;
    $ H# ^, |* r# v
  2. uint8_t Flag;) s0 A% G+ ]2 n0 t; T

  3. / P3 l" Z2 z7 B" q8 W5 |
  4. int main()9 p7 [; o# ?+ e  d4 u
  5. {
    ' k  @* q+ R& o- s+ _
  6.     Serial_Init();
    4 L. \6 T1 B0 W
  7.     Serial_SendByte(0x16);  |1 N$ T  ^7 j# F5 u& e6 u
  8. ! v% W3 ?4 u' w

  9. 0 j7 m# _, L7 t" {& L9 i
  10.     while(1)
    + v) x9 i" H( g
  11.     {3 Y6 Y/ j( [" i! _+ [+ V. X
  12.         if(Flag==1)
    9 A: I" q' D  T3 s- j0 _3 @
  13.         {3 Y; D6 {5 E6 I3 B6 {
  14.             Serial_SendByte(RX_Data);
    7 r' P$ V3 I( @1 K4 B
  15.         }8 V: i0 I( ]7 P3 n1 A2 X6 s
  16.     }
    . [1 Z- N) H0 T) z+ s. u; u9 y
  17. }
    . m7 h. y0 q2 E) K" r

  18. 5 f) W  p4 w9 y
  19. void USART2_IRQHandler(void)
    1 p2 @: L: P3 ?! T3 J
  20. {5 F& f9 K% m8 N; m" H3 G% |3 H8 H+ m5 T
  21.     if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET)   //RXNE 标志位为1 表示可以接收数据* D( I; O7 }7 K
  22.     {
    . v% D. x$ Y5 Y; @
  23.         RX_Data=USART_ReceiveData(USART2);; ~2 y' b% f" z# l+ F6 H  ]
  24.         Flag=1;" a! E2 g5 @4 ?! L
  25.         USART_ClearITPendingBit(USART2,USART_IT_RXNE);  //清除RXNE标志位
    ; U. J" _. |- [" |
  26.     }
    # \  @2 X  R: a' p: u
  27. }
复制代码
. I  U( w- g+ L( E9 S! ]7 ~

0 ?  O  @" `$ ]# f
下图为程序现象:可以看到,串口确实收到了数据,只是我把接收到的数据0xFE放在了while循环里,这说明数据接收是成功的,使用中断是可行的。

# l' a/ L# X1 t
微信图片_20230810144705.png / n7 F* A& e. ?: D9 f! w# t5 E
! n% I" q, a( I4 ~2 P5 [

1 n1 a3 h. W; h* ], n) y" U# i
总结

+ P; }' e6 `* {0 L" u5 r
到这里,就大致总结了串口的发送和接收。
) t% |* P3 Q! b/ Q7 {4 n( `6 \% N
串口的配置,使用查询或者中断来接收数据。
& }& g% W4 p; G
串口的使用会很常用到,所以在这里对串口做一个总结,也算是对之前知识的一个回顾和总结,加强印象。
5 m6 Q, g5 V# C* C$ s; d3 u
转载自: 古月居
如有侵权请联系删除

- t: u& `1 T0 i% W

8 ~  `2 Z0 q/ h. b! [& r% H0 G
收藏 评论0 发布时间:2023-8-10 14:48

举报

0个回答

所属标签

相似技术帖

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版