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

基于STM32串口通信经验分享

[复制链接]
攻城狮Melo 发布时间:2023-7-26 14:43
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 }
微信图片_20230726144152_2.png
( 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 `
  • 发送数据寄存器TDR

    - Y( d, o+ z2 B* Q! ~) l) V
  • 发送移位寄存器,把一个字节的数据一位一位的移出去

    ! k& f# }, E+ R! C1 _
  • 接收数据寄存器RDR
    + B; x3 ^8 N& s( v
  • 接收移位寄存器,把一个字节的数据

    ' F5 r8 J' [/ n

5 P4 {; L* e# n1 O1 g
下方为串口的发送和接收图解:

4 l% H2 G( E# v, V
微信图片_20230726144152_1.png ' 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
微信图片_20230726144152.png * 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- @
微信图片_20230726144153_3.png
6 k. ]# ]) L2 m; n2 z
2 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
微信图片_20230726144153_2.png
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
微信图片_20230726144153_1.png
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; }
  1. RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);  //开启USART2的时钟
    + g$ y  m: y: a* h2 @% N
  2. 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 @
微信图片_20230726144153.png
% @' @" u& i/ Q8 O+ e
# F0 g1 N' p! m9 m! e
USART1对应的引脚

6 G( {* X8 f) _* G
微信图片_20230726144154_4.png
( j2 Y3 {# _+ _; @6 z8 [

7 `( h1 c4 r- z7 N8 ]# N  ~
这里根据手册来看,RX引脚模式配置成浮空输入或者上拉输入。TX引脚模式配置成复用推挽输出。
. ^! d9 i  L( c( N7 i4 F
**比如我这里只初始化TX发送端**
  1. //TX端/ S0 W0 D3 P! L4 B) W, F
  2.     GPIO_InitTypeDef GPIO_InitStructure;' R& R" E  Q/ s" R
  3.     GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;  //复用推挽输出0 R( ]3 i  q& t
  4.     GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2;   //USART2对应的TX端为GPIOA2$ E* S+ S0 p* U  k; J& U
  5.     GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;  //50MhZ( ^* @% h$ _' N- b  _+ j
  6.     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
  1. //串口初始化
    ' Q; Q0 `2 l/ J1 d$ p7 @
  2.   USART_InitTypeDef USART_InitStruct;" _1 r2 N. N% p# V
  3.   USART_StructInit(&USART_InitStruct);  //初始默认值% j/ f* Y2 T& P: c+ ]% I
  4.   USART_InitStruct.USART_BaudRate=9600;
    $ g2 r& J8 r0 Z5 X3 N. c+ w
  5.   USART_InitStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None;   //不使用硬件流触发* o2 }: A7 k. B
  6.   USART_InitStruct.USART_Mode=USART_Mode_Tx;         //TX 发送模式3 L$ \3 l2 X& u2 |' R) p) g4 h
  7.   USART_InitStruct.USART_Parity=USART_Parity_No;       //不选择校验8 f, i0 x8 J- N, ^/ D' y% W
  8.   USART_InitStruct.USART_StopBits=USART_StopBits_1;      //停止位1位
    * A' c9 p. ~/ ^1 a+ N
  9.   USART_InitStruct.USART_WordLength=USART_WordLength_8b;  //数据位8位, ~& H7 u$ M' W" x( Z* \( `5 c2 p$ L
  10.   USART_Init(USART2,&USART_InitStruct);
复制代码
* D2 l! w# x' }  T8 }, m
4. 串口使能
  1. //串口使能* V2 g6 L& ]& O% G# N4 L8 B. ?: @
  2.    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时,就说明可以发送下一个数据了。详细过程可看上面串口发送的解释。
  1. void Serial_SendByte(uint16_t Byte)
    1 j9 M2 t( q# S8 I; W  ~9 W" r, j2 ?8 Z
  2. {
    1 C  l% U2 h1 y! j& t
  3.   USART_SendData(USART2,Byte);
    % w$ t1 b( g+ q
  4.   //0 表示数据还未转移到移位寄存器 循环等待 1 数据已经被转移到了移位寄存器可以发送数据& Q- Z( }& U5 B, t+ C2 m. r
  5.   while(USART_GetFlagStatus(USART2,USART_FLAG_TXE)==RESET);  //不需要手动清零 再次写入TDR时会自动清零  7 O$ ~3 c$ f/ @& C2 S+ I
  6. }
复制代码
/ ^7 H1 V8 T0 g! R: N# O7 W
经过上述五步的配置,单片机就可以通过串口发送数据了。
  E* k. a. a6 M
下方为发送数据图例,STM32向串口发送0x16数据

; f& K/ a2 f$ R& n' _9 B
微信图片_20230726144154_3.png
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
  • 查询方式就是通过不断的查询RXNE标志位,通过判断RXNE位的状态来确定数据是否接收。
    & ~6 V8 B  m3 B
  • 中断方式就是通过配置接收输出控制通道,配置NVIC,在中断服务子函数里进行数据的接收。

    1 |- ^8 {7 k. Y, X5 @
+ 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
  1. //RX端
    ) `/ z1 s/ X5 ~4 m. U4 c. A' q
  2.   GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;  //上拉输入
    7 G  N5 T3 Q" L; k9 z
  3.     GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3;  A6 \, D; m* [! V( y
  4.     GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;  //50MhZ; Z" f' z1 S- I2 m) _+ D
  5.     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
  1. 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& [
微信图片_20230726144154_2.png , J3 i0 [7 j% _( k  b( w
" ~' v3 g0 w3 c
上图描述,为0时数据没有收到,为1时收到了数据,数据可以从RDR里读出

* x  c# y8 N- `) ]
所以在主程序里不断读取RXNE标志位,如果为1,表示数据可以读出
  1. uint8_t RX_Data;. E/ r# M  m+ f: N3 q% Q8 H7 U9 G
  2. int main()* m! `. h9 q! M: J4 I
  3. { ; W$ e. w8 U+ L1 y" z
  4.     Serial_Init();8 A( L" z$ L6 ]4 x. q
  5.     Serial_SendByte(0x16);
    6 g& N  Y+ M% b
  6.     while(1)
    ; R1 g/ Q4 y- [1 Y
  7.     {# B, b/ Y$ Z# E2 q$ t% ]' A
  8.         if(USART_GetFlagStatus(USART2,USART_FLAG_RXNE)==SET)   //0 循环等待 1 可以接收数据" D, K9 P+ O' P  h& \0 @6 Z/ n8 K
  9.         {
    8 a+ H, @" u7 ^$ y
  10.             RX_Data=USART_ReceiveData(USART2);           . c% y1 ~' ~+ O7 ?1 l* a; w
  11.       Serial_SendByte(RX_Data);0 \0 o6 ^/ O3 e- t  ^2 P" o0 n: o
  12.         }
    7 P8 P" I6 B( t7 M$ u. U
  13.     }+ }! A2 @3 K8 I7 R) U1 _8 x
  14. }
复制代码
& 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
微信图片_20230726144154_1.png 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
  • 通过配置串口的接收作为中断源,开启中断输出控制,配置NVIC。开启中断通道。
    0 f) \; T. R. J
  1. //开启中断输出控制! @& q+ R! i9 E! u6 J5 l
  2.   USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);
    ! k' ?6 x7 k/ S" x" r0 V0 b! f
  3.   * K3 V; c2 S# u3 X' `, k& Z$ _
  4.   //配置NVIC
    . A- n$ _/ V  c, U& e9 G4 T6 s
  5.   NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 0 t5 ]; [# `& y7 E* W. [  A
  6.   NVIC_InitTypeDef NVIC_InitStruct;, _5 U: `* H6 |8 D+ H
  7.   NVIC_InitStruct.NVIC_IRQChannel=USART2_IRQn;   //选择USART2的中断通道
    ( b! X% ?7 F% f! s/ \+ X! _
  8.   NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;    //中断使能
    1 ^5 L3 B* ?1 [, }3 j' y
  9.   NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=1;
    7 `4 ?  e) g, m( ?+ p( `
  10.   NVIC_InitStruct.NVIC_IRQChannelSubPriority=1;
    + C$ O1 F& J& L: E/ q/ x; q
  11.   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当接收到数据后,触发接收中断,主程序暂停执行。接收完数据后主程序回复执行。当接收到数据时,就触发中断。
  1. void USART2_IRQHandler(void)
    3 |# X8 `3 O) a! m
  2. {. N, O7 p( @6 v0 u0 ^/ j, z) }
  3.   if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET)   //RXNE 标志位为1 表示可以接收数据) p& r" H- W5 j. t5 T
  4.   {
    7 |0 S5 j8 f# p4 V6 K
  5.     RX_Data=USART_ReceiveData(USART2);
    ; R1 ]" @0 ]0 z5 R- ~1 R3 Q. U
  6.     Flag=1;
    6 {" ?: [2 t7 \: U  Z. ^
  7.     USART_ClearITPendingBit(USART2,USART_IT_RXNE);  //清除RXNE标志位: F; Q1 d, t& X* p( z0 ~4 d
  8.   }
    & m3 h! R0 A& w0 [
  9. }
复制代码
$ I* k0 t3 P9 T) `' X, A2 g
  • 主程序测试

    0 ^- n5 {$ t( W9 S# m+ ?7 Y
  1. uint8_t RX_Data;
    2 g) G6 u5 D' W" X1 t% D
  2. uint8_t Flag;
    + ^) w: P# @' b: U1 [- f
  3. . `3 u5 k  M- Z2 n4 @. a, E
  4. int main()
    8 U) y) C8 d: }4 t2 K/ ?% o
  5. {: Q' y4 n& [, o2 A3 O* }, O
  6.     Serial_Init();3 v. \" `& Q: c3 f# i
  7.     Serial_SendByte(0x16);
    ' [* N/ K! R8 J) _: w9 p

  8. 4 H$ }! e7 W& `$ _9 `, Q

  9. - P# R! x2 U9 y5 n& |9 _, a
  10.     while(1)! f. V$ L7 u/ H5 `
  11.     {7 ^0 H3 t% N7 S( U( ?# K& O% ]3 B
  12.         if(Flag==1)
    + {) E5 q( l7 s0 y& V* p
  13.         {
    , |8 f. N# {3 @' U  J
  14.             Serial_SendByte(RX_Data);
    3 C2 ~3 F/ j- N8 I8 c" |( @( |
  15.         }- C: m8 H* A1 t5 H- u& Q& `
  16.     }5 F- `' f" M1 U4 b9 a  z
  17. }9 J8 U& ]  \7 J* R& E% Z

  18. 5 F( e7 ~( m  [$ z( q
  19. void USART2_IRQHandler(void)
    2 J: c9 Q7 s" \' M. n
  20. {
    1 I8 v' x; R# U3 I
  21.     if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET)   //RXNE 标志位为1 表示可以接收数据
    % c+ V1 Z5 L( `0 P( `
  22.     {
    2 @, d6 r9 R& E, G0 L% F
  23.         RX_Data=USART_ReceiveData(USART2);& B6 H, I. L& i
  24.         Flag=1;
    6 {0 t7 b: }3 h& ]7 y
  25.         USART_ClearITPendingBit(USART2,USART_IT_RXNE);  //清除RXNE标志位8 \2 D! h% r! T+ j, x" K; q" U* u
  26.     }
    " _1 U0 ^$ `4 Q3 N0 ?- [
  27. }
复制代码
7 Q; n# U4 i8 F: d% ~$ n* N* I( U! S
) A7 [& e* J5 Q( r9 O
下图为程序现象:可以看到,串口确实收到了数据,只是我把接收到的数据0xFE放在了while循环里,这说明数据接收是成功的,使用中断是可行的。

, L' H5 n5 ~) r
微信图片_20230726144154.png 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
转载自: 古月居
如有侵权请联系删除
0 l; B4 i9 m- t

% i( @7 n' a9 X
# P8 ?! N) _0 M! M/ m
赞 收藏 评论0 发布时间:2023-7-26 14:43

举报

0个回答

所属标签

相似技术帖

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