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

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

[复制链接]
攻城狮Melo 发布时间:2023-8-10 14:48
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
微信图片_20230810144703_3.png
( 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
  • 发送数据寄存器TDR
    ! e% I- v  q8 U6 o% V
  • 发送移位寄存器,把一个字节的数据一位一位的移出去

      E' h, @$ n0 d9 U: g& I
  • 接收数据寄存器RDR

    5 d3 Z$ M' q" C; x2 D; t9 W4 H
  • 接收移位寄存器,把一个字节的数据

      s/ V: U# W- j" {
+ [. U6 p" O3 h4 ?- Z
下方为串口的发送和接收图解:
7 V3 t& }* Z2 S
微信图片_20230810144703_2.png 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
微信图片_20230810144703_1.png
: |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
微信图片_20230810144703.png # 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# ~: |
微信图片_20230810144704_3.png
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 ~
微信图片_20230810144704_2.png
! 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口
  1. RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);  //开启USART2的时钟
    4 d( V0 b' d2 O6 Q! y' M& W4 X2 c
  2. 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
微信图片_20230810144704_1.png
! l6 l& h% X/ U3 i9 ]; k
( O/ g: C4 D* i* G2 \
USART1对应的引脚

/ |8 L2 `' u/ p( t, U* |' y% y7 H
微信图片_20230810144704.png / F& ~9 y; ^6 x
8 B2 G, x+ A1 v% L0 R, M1 j+ R
这里根据手册来看,RX引脚模式配置成浮空输入或者上拉输入。TX引脚模式配置成复用推挽输出。
* i9 g& c& k8 a9 ]. {
**比如我这里只初始化TX发送端**
  1. //TX端
    + q$ z+ B3 f- Z- F9 i  h
  2.     GPIO_InitTypeDef GPIO_InitStructure;
    ! r9 l+ J. Y! v3 ]2 o
  3.     GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;  //复用推挽输出
    8 }7 ?, `/ g' h9 c7 w+ X
  4.     GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2;   //USART2对应的TX端为GPIOA2
    5 I( B) ?2 O) K6 [( \/ z
  5.     GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;  //50MhZ  Z6 _+ V; m& b* Z* q. A
  6.     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位可以选择
    1. //串口初始化
      - L  d! T9 C  N3 |! G" ~! M; m
    2.   USART_InitTypeDef USART_InitStruct;
      5 k" v9 K! Z" Y" |
    3.   USART_StructInit(&USART_InitStruct);  //初始默认值$ z, u; [: a5 ]+ d
    4.   USART_InitStruct.USART_BaudRate=9600;
        R8 H( o% V2 z' o
    5.   USART_InitStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None;   //不使用硬件流触发# L# R8 }9 E5 k9 k
    6.   USART_InitStruct.USART_Mode=USART_Mode_Tx;         //TX 发送模式
      : i  P7 @. v3 R- m* Z" x
    7.   USART_InitStruct.USART_Parity=USART_Parity_No;       //不选择校验
      3 |: _7 C' _7 B) p
    8.   USART_InitStruct.USART_StopBits=USART_StopBits_1;      //停止位1位
      : Q7 K+ x" b: i6 F& F0 j
    9.   USART_InitStruct.USART_WordLength=USART_WordLength_8b;  //数据位8位
      $ U' `- s- y3 _
    10.   USART_Init(USART2,&USART_InitStruct);
    复制代码
    8 i: U* p, n. S( `* W9 q

: N! V8 ?  o1 Z  U
4. 串口使能
  1. //串口使能$ z8 f0 ]* k+ z9 Y
  2.    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时,就说明可以发送下一个数据了。详细过程可看上面串口发送的解释。
  1. void Serial_SendByte(uint16_t Byte)
    ( c9 V  y1 h) [" t  X$ o
  2. {8 p$ i# P/ T, a3 T2 ?7 o0 G
  3.   USART_SendData(USART2,Byte);6 M" X& v( |0 ]
  4.   //0 表示数据还未转移到移位寄存器 循环等待 1 数据已经被转移到了移位寄存器可以发送数据
    + D' N+ E8 P  ^. U
  5.   while(USART_GetFlagStatus(USART2,USART_FLAG_TXE)==RESET);  //不需要手动清零 再次写入TDR时会自动清零  
    2 p; x3 b+ U! J* A* ]4 }
  6. }
复制代码
, U/ D8 H5 C% ^' H/ G
经过上述五步的配置,单片机就可以通过串口发送数据了。
4 u+ w- h) s3 D, ^' L& N
下方为发送数据图例,STM32向串口发送0x16数据
  }4 N7 [1 e. s" |7 o- \
微信图片_20230810144705_3.png
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引脚
    1. //RX端: h) t( t* l+ s  X- n( y
    2.   GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;  //上拉输入2 B. {" N1 |+ j0 ?) ^" w. C
    3.     GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3;2 N* d7 R8 r& j4 R' J* O
    4.     GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;  //50MhZ
      : x' Y8 j* n5 n* ]' H1 K- d
    5.     GPIO_Init(GPIOA,&GPIO_InitStructure);
    复制代码

    0 ]& n: H4 y, q1 r6 L$ P/ g
8 V5 u3 ~8 t8 V6 W
  • 添加串口模式
    1. 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
微信图片_20230810144705_2.png ) F' r; w. x: H5 ?

3 }+ W9 Q6 @8 a- F
上图描述,为0时数据没有收到,为1时收到了数据,数据可以从RDR里读出
( n0 ^, U7 L1 `; [
所以在主程序里不断读取RXNE标志位,如果为1,表示数据可以读出
  1. uint8_t RX_Data;% d; i$ `' y! C" \% N1 ]" ~2 y
  2. int main()* Y" b# [& Z; Q+ {! _$ l
  3. {
    6 k( u# H" y+ L. W
  4.     Serial_Init();
    / e8 n  q$ s' C
  5.     Serial_SendByte(0x16);6 C6 K5 G# [# D
  6.     while(1)
    / E; v" [* R0 `, K0 p! _
  7.     {
    ( y4 m2 n' @9 S6 x7 P9 E
  8.         if(USART_GetFlagStatus(USART2,USART_FLAG_RXNE)==SET)   //0 循环等待 1 可以接收数据  K( A& z0 i! y8 G! w2 d$ f$ P
  9.         {
    6 C  e4 O5 }8 @! f, `
  10.             RX_Data=USART_ReceiveData(USART2);           
    , W' T' G& l4 p9 s
  11.       Serial_SendByte(RX_Data);! I& J5 l+ O& Q
  12.         }& ?% [4 L3 m7 Y" x! Z6 U! Q& f
  13.     }
    2 u' s+ }2 A4 P/ V" b
  14. }
复制代码

  p7 X" {' h) X, Z; l. `
下图为程序现象:pc向单片机发送数据0x15,单片机接收数据0x15,并且把接收到的数据作为数据发送到pc,在pc上显示0x15。

( S1 E4 H5 `. j- H: s, q3 C
微信图片_20230810144705_1.png ! 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
  • 通过配置串口的接收作为中断源,开启中断输出控制,配置NVIC。开启中断通道。

    + L% L( _4 p- |. f8 t& b! p5 X
  1. //开启中断输出控制/ Q# q4 ]+ l/ _* Q
  2.   USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);
      {$ `- H( z& K* H
  3.   
    ( \  m8 ?, P+ n* C) N$ @
  4.   //配置NVIC
    / j6 v  i9 l7 n: B
  5.   NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 0 q2 ~+ `$ j# E4 Q* \3 k7 M. F
  6.   NVIC_InitTypeDef NVIC_InitStruct;
    1 x8 A  z$ J/ f; J8 S" u+ ]
  7.   NVIC_InitStruct.NVIC_IRQChannel=USART2_IRQn;   //选择USART2的中断通道
    , T: h! \, d* p% _
  8.   NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;    //中断使能- P% z) k; ~! ~- T$ g5 R  @& c, t
  9.   NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=1;
    ; n* G' v5 @, X4 `8 ?  X
  10.   NVIC_InitStruct.NVIC_IRQChannelSubPriority=1;
    ' J9 `# E4 J; N
  11.   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& @当接收到数据后,触发接收中断,主程序暂停执行。接收完数据后主程序回复执行。当接收到数据时,就触发中断。
  1. void USART2_IRQHandler(void)
    $ a, D4 k9 Q( z$ M/ v
  2. {
    5 M7 u8 s' x& H0 p& b; o6 v' G- d9 t
  3.   if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET)   //RXNE 标志位为1 表示可以接收数据
    7 N% F' k/ y  o* W4 W
  4.   {
    ( K% C0 ~+ s% U2 ?8 s- u& ~0 a
  5.     RX_Data=USART_ReceiveData(USART2);- l3 ?' V% _! a6 C7 C
  6.     Flag=1;9 h+ G6 A. i' ]* N
  7.     USART_ClearITPendingBit(USART2,USART_IT_RXNE);  //清除RXNE标志位
    5 k1 Y/ `  j8 q
  8.   }
    4 ?5 x% X  \8 x* W/ s& w' D
  9. }
复制代码

/ p- E0 p4 `' n
  • 主程序测试

    " |; m8 T: L; S0 B
  1. uint8_t RX_Data;
    . @+ g. w; v# Y8 ?* ]! _
  2. uint8_t Flag;  a- x* V+ P( X3 ~1 J$ R) G0 ?# P5 `: u

  3. 8 F$ R0 l" ?, X: b  ?
  4. int main()) V$ H$ m3 T. S0 S! u* x
  5. {
    ! J# l  K; y# Q1 R; R
  6.     Serial_Init();  d  g8 g* v  l6 n2 R. q
  7.     Serial_SendByte(0x16);
    3 y) p3 U) G) Z* z' \

  8. ' T; R. C, R3 m8 [9 S

  9. % `9 H+ f/ \& h* {/ h
  10.     while(1)) h" T! p; g2 i/ {% e7 R
  11.     {
    ' u5 N( `- i' l3 f: v- d
  12.         if(Flag==1)
    8 y; l5 ]0 _1 e3 Q& X4 O
  13.         {
    # Y" R6 w8 r3 l1 a5 R/ `/ i8 p
  14.             Serial_SendByte(RX_Data);
    * B# j3 J  d- B$ q
  15.         }9 o7 x; X5 U" u- v
  16.     }
    ! n; p# h4 Q2 ?5 ^% O; G
  17. }
    # V' \4 f2 U+ M) {) m

  18. % }& s' S5 C( ~" ~: A4 U2 _* w5 R% r
  19. void USART2_IRQHandler(void)
    * o6 v$ m% X1 B& \
  20. {: i2 `) d' M% u" _: U3 c3 S& h1 N
  21.     if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET)   //RXNE 标志位为1 表示可以接收数据2 q3 W/ c2 S, {% _* e
  22.     {
    ) Y) W$ L; d% j1 `) r
  23.         RX_Data=USART_ReceiveData(USART2);
    ) `' \( K% o8 p* T7 I
  24.         Flag=1;
    ! k% e4 ], ?; ^$ o' M1 Q
  25.         USART_ClearITPendingBit(USART2,USART_IT_RXNE);  //清除RXNE标志位
    / ~3 o2 D. ?& i* H, ]
  26.     }6 T) g1 b$ x3 y2 F. f3 o; q* C
  27. }
复制代码
- 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
微信图片_20230810144705.png
! 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
赞 收藏 评论0 发布时间:2023-8-10 14:48

举报

0个回答

所属标签

相似技术帖

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版