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

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

[复制链接]
攻城狮Melo 发布时间:2023-8-10 14:48
STM32的串口通信
$ O1 D6 L. `& e" j* q% w
本文在于记录自己的学习过程中遇到的问题和总结,各种情况下串口通信在STM32的实际使用方面占有很大的比重,本文主要对串口通信做一个简要的总结。

+ n/ j3 j+ \* W) u! w
3 t3 Q1 F* O6 A, X. ~9 g9 X
一、STM32里的串口通信
$ A' h+ K' ?4 u: U$ l. ^2 D
在STM32里,串口通信是USART,STM32可以通过串口和其他设备进行传输并行数据,是全双工,异步时钟控制,设备之间是点对点的传输。

0 Y6 P5 l& C& k: s8 l- e3 d, n
对应的STM32引脚分别是RX和TX端。STM32的串口资源有USART1、USART2、USART3.
3 k  {: K, u$ u( `0 W1 e, D% y
串口的几个重要的参数:

* Z% M# {) V' y7 e, I
  • 波特率,串口通信的速率

    1 v) I& o" @" J- ^4 X
  • 空闲,一般为高电平
    5 B) V* L! X7 k0 Q
  • 起始位,标志一个数据帧的开始,固定为低电平。当数据开始发送时,产生一个下降沿。(空闲–>起始位)

    * `1 M9 v$ {( ]* G  m5 H. i
  • 数据位,发送数据帧,1为高电平,0为低电平。低位先行。

    7 [7 G5 k- a; J  {: |- \
  • 比如 发送数据帧0x0F 在数据帧里就是低位线性 即 1111 0000

    % Y- g/ L1 {& e. ?  F
  • 校验位,用于数据验证,根据数据位的计算得来。有奇校验,偶校验和无校验。
    & l8 W& F# k7 }! Y5 ^& x! N
  • 停止位,用于数据的间隔,固定为高电平。数据帧发送完成后,产生一个上升沿。(数据传输–>停止位)
    6 K5 P' S5 P  |) i
5 `/ t; X4 ^2 g* y3 y* }
下方就是一个字节数据的传输过程,从图中可以看出,串口发送的数据一般都是以数据帧的形式进行传输,每个数据帧都由起始位,数据位,停止位组成, 且停止位可变。

3 F; H8 F4 S+ k3 K4 j
微信图片_20230810144703_3.png ; |; y0 D$ ]5 Z4 d: r$ ?, b

4 @# w$ Z& U1 P2 _% Q7 R7 ]
3 W+ h) Z7 G* h1 _9 w- t
二、串口的发送和接收

8 h/ l0 A7 r  d
USART是STM32内部集成的硬件外设,可以根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可以自动接收RX引脚的数据帧时序,拼接成一个字节数据,存放在数据寄存器里。
6 G% Y; h! c, |( o4 i6 g$ `) ?9 ?
当配置好USART的电路之后,直接读取数据寄存器,就可以自动发送数据和接收数据了。在发送和接收的模块有4个重要的寄存器

# @# f6 y9 w6 `2 Z4 V, ]
  • 发送数据寄存器TDR
    1 m7 t" S2 K+ \" E; h& T# P( @
  • 发送移位寄存器,把一个字节的数据一位一位的移出去

    - I. }1 S  i9 E5 ~7 P$ f
  • 接收数据寄存器RDR

    2 v# L) H- [, z  j3 D. R
  • 接收移位寄存器,把一个字节的数据
    2 G$ T& o" {1 g  e' q+ q+ h$ J

( ~% a1 _7 L$ q8 ^. H) O/ P4 X
下方为串口的发送和接收图解:

5 j1 X4 y* y; E! R: i3 L
微信图片_20230810144703_2.png
- [' U, g5 b6 F! j3 \+ V4 i
1 R% M! L7 V9 u+ f, h* f7 l3 P
串口发送
: c8 a+ ^. o3 H
在配置串口的各个参数时,可以选择发送数据帧的数据位的大小,可选8位或9位。

, M" S3 M; r- C  v
串口发送数据实际上就是对发送数据寄存器TDR进行写操作。
, ]6 |7 X7 r) S! M6 J/ U
1. 当串口发送数据时,会检测发送移位寄存器是不是有数据正在移位,如果没有移位,那么这个数据就会立刻转移到发送移位寄存器里。准备发送。

+ L( o1 G  u$ ?$ Q3 K
2. 当数据移动到移位寄存器时,会产生一个TXE发送寄存器空标志位,该位描述如下。当TXE被置1,那么就可以在TDR写入下一个数据了。即发送下一个数据。
! O0 p! d5 b. _5 ^4 _
微信图片_20230810144703_1.png
. b6 R7 R& K$ E6 s

1 D! ?, _+ \; A, z2 @8 S; W. Y: T
3. 发送移位寄存器在发送器控制的控制下,向右移位,一位一位的把数据传输到TX引脚。

5 t, n8 {! W. v1 \/ e2 _6 R
微信图片_20230810144703.png
( |/ \' n. @. l. H  r
8 t! F6 @$ C4 x8 K7 C0 }0 E6 K
4. 数据移位完成后,新的数据就会再次从TDR转移到发送移位寄存器里来,依次重复1-3的过程。通过读取TXE标志位来判断是否发送下一个数据。

* k0 ]$ N) C: T" R
串口接收

4 H- w- k- q* S! t: l5 }* v# x! D
数据从RX引脚通向接收移位寄存器,在接收控制的控制下,一位一位的读取RX的电平,把第一位放在最高位,然后右移,移位八次之后就可以接收一个字节了。
' `7 G6 R" F3 T; B. R
当一个字节数据移位完成之后,这一个字节的数据就会整体的移到接收数据寄存器RDR里来。
1 X. i- X+ C" `' U. j: L9 a/ s
在转移时会置RXNE接收标志位,即RDR寄存器非空,下方为该位的描述。当被置1后,就说明数据可以被读出。
, P* Y; t. @  ^9 |
微信图片_20230810144704_3.png 8 C; ]* K* M. c5 w4 C1 ^' a5 I4 @' o
) c' Q( N# n# ]6 t; p/ p. [& j7 x
下图即为串口接收的工作流程

* N7 \# o1 m- Z' Q" x+ I6 L" o
微信图片_20230810144704_2.png ; h: Q$ O; D  P. z2 Z$ d( u' o
% y# n8 N4 D. j4 F6 g' h2 p

% }& }/ F* W5 [
三、串口在STM32中的配置
  [% i0 H1 U, n: @$ i2 x1 p
首先要明确几点:使用STM32串口外设中的哪一个?串口发送或者接收数据?串口相关的参数配置?发送或接收是否使用到中断?

4 y, n# f6 g) J% ~
下方为串口发送的配置。

: P+ K- Z9 _$ V; U
1. RCC开启USART、串口TX/RX所对应的GPIO口
  1. RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);  //开启USART2的时钟; Q' ^( C$ x: V, [* Y
  2. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);   //开启GPIOA的时钟
复制代码
% e/ U5 j0 W  l. E3 t5 z6 n
2. 初始化GPIO口
$ A$ X5 m' y  U, @( Y2 R
这里注意哈,根据自己的需求来配置GPIO口,发送和接收是都需要还是只需要其中一个。然后对应的根据引脚定义表来初始化对应的GPIO口。

- r. a- v# D7 X+ m4 O$ o" n7 ~; ?
USART2对应的引脚

7 A6 p/ |' Z/ B$ j/ f8 L
微信图片_20230810144704_1.png
. {3 i1 L. D9 z! e
/ |, T9 d0 W% m3 g+ V, W
USART1对应的引脚

0 @+ ~1 }( O9 q3 H; }/ S
微信图片_20230810144704.png
2 b. m% }0 |0 ?9 N& `8 }) B8 Q

4 V$ ~0 W! |8 b1 C
这里根据手册来看,RX引脚模式配置成浮空输入或者上拉输入。TX引脚模式配置成复用推挽输出。

9 ]- V) R) L, C9 ^
**比如我这里只初始化TX发送端**
  1. //TX端
    " z; }, K- G( U. o2 @
  2.     GPIO_InitTypeDef GPIO_InitStructure;
    ' @4 l$ W' U% W$ Q4 o+ C
  3.     GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;  //复用推挽输出' e8 q& p, P1 K% n2 Y
  4.     GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2;   //USART2对应的TX端为GPIOA2
    ! R/ N. m" h/ c. A* c, t% H
  5.     GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;  //50MhZ- m6 a$ z2 w' O6 d1 _5 _; \' c
  6.     GPIO_Init(GPIOA,&GPIO_InitStructure);
复制代码

4 @/ D2 E4 O- B# @* j
3. 串口初始化
0 d) ~0 N+ T2 q7 A) e
注意哈,USART_Init()这个函数,是用来配置串口的相关参数的。
. N) n" H! A' J* g
  • USART_BaudRate 串口通信使用的波特率 一般是9600或者是115200,这里我们给9600
    % ?- ^7 ]: _) ~3 @1 C/ g& x
  • USART_HardwareFlowControl 是否选择硬件流触发,一般这个我们也不选,所以选择无硬件流触发。

    ) K, d5 K8 Z6 l* P/ l& d, K' [% a( C
  • USART_Mode 这个参数要注意了哈,串口的模式,发送模式还是接收模式,还是两者都有

      `. t* ?0 d0 T
  • USART_Parity 校验位,可以选择奇偶校验和不校验。没有需求就直接无校验
    1 {3 w6 w' u" Q; b; Q
  • USART_StopBits 停止位 有1、0.5、2位,我们这里选1位停止位

    2 O* C: h3 U; Z- K
  • USART_WordLength 数据位 有8位和9位可以选择
    1. //串口初始化
      ' V5 n3 c( V1 E. s3 X
    2.   USART_InitTypeDef USART_InitStruct;( F# E/ l7 V; {' H& M; u* j; h4 @
    3.   USART_StructInit(&USART_InitStruct);  //初始默认值2 S, N+ Y+ L$ r4 i# u5 G
    4.   USART_InitStruct.USART_BaudRate=9600;
      ) x& _/ d7 U4 H, ]/ @8 Q( u% @% Y, t
    5.   USART_InitStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None;   //不使用硬件流触发; l3 t1 |5 \1 D" @6 [$ h: [
    6.   USART_InitStruct.USART_Mode=USART_Mode_Tx;         //TX 发送模式
      % Z' Z+ I- }9 j5 D5 F4 Q
    7.   USART_InitStruct.USART_Parity=USART_Parity_No;       //不选择校验* U3 Z, S8 N2 B* J* P5 P
    8.   USART_InitStruct.USART_StopBits=USART_StopBits_1;      //停止位1位
      9 B) y4 D2 v5 s) o4 q7 \
    9.   USART_InitStruct.USART_WordLength=USART_WordLength_8b;  //数据位8位; Y) v7 [) m6 b5 N! a1 }+ ~
    10.   USART_Init(USART2,&USART_InitStruct);
    复制代码

    - `1 V$ Q4 t; T& j5 \/ V8 f2 f# F
6 h% i# x/ H$ M$ R1 I0 L: O
4. 串口使能
  1. //串口使能- V1 _6 u# x0 V5 S+ E2 L$ W- c
  2.    USART_Cmd(USART2,ENABLE);
复制代码
( b; m7 P! Q) s2 d$ x6 l
5. 串口发送数据
+ L: a$ Y9 o& y* Z: r4 e7 e
注意哈,我们要判断TXE标志位的状态。0,数据还没有被转移到移位寄存器;1,数据已经被转移到移位寄存器。
; d5 p$ X1 X" N
当TXE标志位为1时,就说明可以发送下一个数据了。详细过程可看上面串口发送的解释。
  1. void Serial_SendByte(uint16_t Byte)4 B0 [9 M4 F& s5 k/ d5 v
  2. {. Y; F) q% y0 j) M; Z
  3.   USART_SendData(USART2,Byte);, a. D* n( |* D1 `
  4.   //0 表示数据还未转移到移位寄存器 循环等待 1 数据已经被转移到了移位寄存器可以发送数据
    6 I% Z  w: D" R; g+ D( B  W
  5.   while(USART_GetFlagStatus(USART2,USART_FLAG_TXE)==RESET);  //不需要手动清零 再次写入TDR时会自动清零  
    6 o# R/ x; [. ]5 L  J0 H
  6. }
复制代码
3 ]8 u7 P8 L0 {! E
经过上述五步的配置,单片机就可以通过串口发送数据了。
2 J! Z1 m; s  ]2 O5 Z& u
下方为发送数据图例,STM32向串口发送0x16数据
; n. @: K2 }6 H( i$ U3 h7 d0 Q
微信图片_20230810144705_3.png
5 J( r5 {& J7 y8 I* n

/ t! m4 h( K- E) S$ F
四、串口接收的两种实现方式
7 e' ~: i( h: a3 H. c  V% g) @
上方是发送数据的例子,那么串口接收又该如何配置,又要在串口发送的例子上做哪些更改呢?

% ~! I0 t0 m; J6 w8 D; B" H! r  o7 p
这里我们可以通过查询或者中断的方式来进行接收数据的两种方式。

4 d& h1 g! i! \9 v4 B  _& x
  • 查询方式就是通过不断的查询RXNE标志位,通过判断RXNE位的状态来确定数据是否接收。

    8 w8 Q& z/ R+ x% N: i& e1 v3 [% @
  • 中断方式就是通过配置接收输出控制通道,配置NVIC,在中断服务子函数里进行数据的接收。
    1 C0 D8 X' N8 Q0 G8 o- u; E
. N; \" @% {+ h' i. Z! o
1. 需要更改的地方
- v0 f: F5 _/ T3 J: ~' M# o
0 u& x! R) P( N# J8 U  K: L
既然我们要实现串口的接收,那么就要配置串口RX引脚,在串口模式中添加USART_Mode_RX模式。

: z7 t- m5 R8 x5 v, J
  • 初始化RX引脚
    1. //RX端; B9 i' I# v3 W, e, R: `
    2.   GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;  //上拉输入
      ! I! D- ~: z, d  s$ s' R8 r. U/ M' Z
    3.     GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3;1 L' D- m+ m/ x" c2 d5 g! r
    4.     GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;  //50MhZ3 j7 }0 l6 a9 G( F# z: A/ f
    5.     GPIO_Init(GPIOA,&GPIO_InitStructure);
    复制代码
    / \2 p  l- g! S. h9 E' ^: n# ]
9 T8 M% B) d& g* G5 g( \2 j  K
  • 添加串口模式
    1. USART_InitStruct.USART_Mode=USART_Mode_Tx|USART_Mode_Rx;         //TX 发送模式  RX 接收模式
    复制代码
    ! t$ _! r( {  p5 p0 r  Y
$ f' f9 n4 B# L3 {9 \+ ]
2. 查询RXNE标志位

, F) d; r% q  ~) B$ j5 R
这里我们还是来看一看RXNE标志位的描述
$ N( o3 E: g1 I: s" Y
微信图片_20230810144705_2.png : i8 N5 P4 R1 Z& n

% z+ z! E1 A5 {5 p0 T# ]" r6 V# i
上图描述,为0时数据没有收到,为1时收到了数据,数据可以从RDR里读出
# I1 H8 n. s% L* |
所以在主程序里不断读取RXNE标志位,如果为1,表示数据可以读出
  1. uint8_t RX_Data;* B+ J/ R: P) d0 f# a5 `, J
  2. int main()
    , A9 |4 P3 p* q) d4 r  Y; z
  3. { 1 g( e6 T; c# h- |' I
  4.     Serial_Init();
    : w. _5 x9 g$ l; Z  a$ n* U
  5.     Serial_SendByte(0x16);
    8 j) ]1 l1 }" u: y9 G5 x; ?% s
  6.     while(1)
    , [. K6 [3 b$ n3 U2 @0 x
  7.     {
    $ s, Z3 e! C% u$ ~
  8.         if(USART_GetFlagStatus(USART2,USART_FLAG_RXNE)==SET)   //0 循环等待 1 可以接收数据7 g1 o3 a" ^3 [
  9.         {; Q/ i' q; z0 W8 S/ `# f( X
  10.             RX_Data=USART_ReceiveData(USART2);           3 u" U" W2 }  e1 u+ v* o/ r" f
  11.       Serial_SendByte(RX_Data);
    6 U" U9 J4 U8 z9 T: |4 f% @
  12.         }
    1 l; n0 K: e3 y
  13.     }
    / r+ E& w, P. B: W, B) q
  14. }
复制代码
& c/ J/ e0 ~' u. p% I7 R
下图为程序现象:pc向单片机发送数据0x15,单片机接收数据0x15,并且把接收到的数据作为数据发送到pc,在pc上显示0x15。

0 O) R& L) y, i/ v: }# ?" A
微信图片_20230810144705_1.png   ?; I# h: i% z1 t

* C/ ~( }$ o6 A# h2 Y+ L
3. 使用中断
. _3 ?5 s, |( L$ R/ d
  • 通过配置串口的接收作为中断源,开启中断输出控制,配置NVIC。开启中断通道。

    7 C# n. y7 ?+ e) N, T* E9 O
  1. //开启中断输出控制
    3 }" j0 s2 X# Q4 a( ]3 A
  2.   USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);
    8 ^7 {$ y; {6 J# I
  3.   $ h% }# ?$ D, M2 y* b; T4 ?
  4.   //配置NVIC
    * H% F. k; i/ @, Z# b: e
  5.   NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    5 T$ V1 P8 n2 }  H
  6.   NVIC_InitTypeDef NVIC_InitStruct;3 q0 ]% \5 X* V8 R
  7.   NVIC_InitStruct.NVIC_IRQChannel=USART2_IRQn;   //选择USART2的中断通道" H0 t" p9 t7 J, F) L! V/ W4 Y7 [
  8.   NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;    //中断使能
    , \6 r7 ?# }6 \. {3 N* \& y! {9 e
  9.   NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=1;$ z- K% L  R! ^0 j
  10.   NVIC_InitStruct.NVIC_IRQChannelSubPriority=1;! ^, ?6 P/ m6 x' B( ]2 b+ v
  11.   NVIC_Init(&NVIC_InitStruct);
复制代码

# g( ?1 u; B4 l& Q! l" q; `( ^, P) @
  • 中断服务子函数
    4 B" U% A1 `, _

, U2 V* E8 h# T, T8 o中断服务子函数写好后,就可以在中断里读取接收到的数据了。

4 S& e" I/ P( z8 O  a/ r当接收到数据后,触发接收中断,主程序暂停执行。接收完数据后主程序回复执行。当接收到数据时,就触发中断。
  1. void USART2_IRQHandler(void)
    . j6 a! \' a2 B. H! ~1 n2 j, P) l$ |
  2. {, e4 _. {: [2 `2 O" t1 @
  3.   if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET)   //RXNE 标志位为1 表示可以接收数据
    : h8 i7 S" c# U2 j
  4.   {
    ( q! t1 F2 D6 F$ u9 ]/ d
  5.     RX_Data=USART_ReceiveData(USART2);! O3 m# O' W( p2 x3 U, W
  6.     Flag=1;5 ~0 u% l1 l# T2 x" G. e* F
  7.     USART_ClearITPendingBit(USART2,USART_IT_RXNE);  //清除RXNE标志位% R; T  y8 L! j% z/ D( o
  8.   }$ s# Y7 }2 b; c2 [8 ~9 n* V1 T
  9. }
复制代码
3 \' h) t, w! L9 K6 Y  _* A! Z& s* s8 s
  • 主程序测试

    ) g4 T& \) F5 H7 @4 Y3 @7 p3 L
  1. uint8_t RX_Data;3 {: M$ f8 h3 v# J' v2 W
  2. uint8_t Flag;" X$ G0 E# a4 w- I5 a* B! \" z& M

  3. , l$ p; E2 p; |# j6 G
  4. int main()% O$ r5 f* y% g3 [
  5. {
    1 D. h( Q. Y! J4 }. H7 J
  6.     Serial_Init();9 ^1 o, x" m9 L2 \& W6 d# a
  7.     Serial_SendByte(0x16);* M" y6 A$ |4 \2 d- U! @& {# H
  8. % B) S( w. h# J" R  J

  9. $ y  n/ y: W7 Q: G
  10.     while(1)
    ; k) k& A9 F! q" ~# H
  11.     {
    ; M+ q7 b# [% r4 O4 S
  12.         if(Flag==1)0 ^) `7 Z& M7 A7 y1 n% s9 r
  13.         {
    7 L$ \& ^6 o3 s9 q9 r8 U. C+ @
  14.             Serial_SendByte(RX_Data);
    + ?5 h+ @) ]. P( a2 [
  15.         }' X8 B3 n' A: Z$ |- r2 D6 {
  16.     }' C0 H7 w: t1 A9 T+ \( I
  17. }
    8 ^, f" t) I- O

  18. 0 s  g% [/ J) g, ]: U
  19. void USART2_IRQHandler(void)
    * Z1 b3 q! ^: d' i6 d' ?
  20. {
      X( V( c" O7 `, G) Q/ J
  21.     if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET)   //RXNE 标志位为1 表示可以接收数据0 F5 a6 m, f* e/ W% Z
  22.     {
    3 G& m4 f$ E3 x  t
  23.         RX_Data=USART_ReceiveData(USART2);
    5 Z2 t/ V+ s+ E% l" V) T6 y
  24.         Flag=1;
    $ y3 S8 E7 Z/ Z/ c- J( N2 J
  25.         USART_ClearITPendingBit(USART2,USART_IT_RXNE);  //清除RXNE标志位
    9 ^8 h$ R: y2 D! R6 P4 R9 j$ Q
  26.     }! `6 s$ {' }+ X% f' ~1 D$ L/ L
  27. }
复制代码

4 I  J: S  f4 q
- x7 \" P9 p5 g+ g, ]  T( {
下图为程序现象:可以看到,串口确实收到了数据,只是我把接收到的数据0xFE放在了while循环里,这说明数据接收是成功的,使用中断是可行的。
& r4 @5 t2 C5 f
微信图片_20230810144705.png
+ U' E- K7 l9 s( o% b- ^
& t  C5 R* q; M/ Y9 F' g. C

9 t2 Y3 i3 P) t* b7 A
总结

- y8 c$ d) E1 U5 o
到这里,就大致总结了串口的发送和接收。
- v; h$ M6 f* r8 Z4 u$ r2 K
串口的配置,使用查询或者中断来接收数据。
, t* @: H7 }& u8 P( N0 L
串口的使用会很常用到,所以在这里对串口做一个总结,也算是对之前知识的一个回顾和总结,加强印象。

5 c% j0 F) M; J- i5 S& R
转载自: 古月居
如有侵权请联系删除

4 q" ]" b9 Y9 m$ Q# `5 c

0 U% P* C7 X9 z( a  R! a
收藏 评论0 发布时间:2023-8-10 14:48

举报

0个回答

所属标签

相似技术帖

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