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

基于STM32串口通信经验分享

[复制链接]
攻城狮Melo 发布时间:2023-7-26 14:43
STM32的串口通信
& Y! y- s3 D) S' Z  E4 b# V* O% N
本文在于记录自己的学习过程中遇到的问题和总结,各种情况下串口通信在STM32的实际使用方面占有很大的比重,本文主要对串口通信做一个简要的总结。

2 W& C: [) [7 B3 W' W7 C
一、STM32里的串口通信
在STM32里,串口通信是USART,STM32可以通过串口和其他设备进行传输并行数据,是全双工,异步时钟控制,设备之间是点对点的传输。

6 w0 ~$ F" H1 q" y0 h/ P3 e. e
对应的STM32引脚分别是RX和TX端。STM32的串口资源有USART1、USART2、USART3.

$ @: Z: v9 C* U" {
串口的几个重要的参数:

. W. H  r1 K! x
  • 波特率,串口通信的速率
    . |& H. o& Y& [0 P1 C- v
  • 空闲,一般为高电平

    + v7 l  V' ^! T* W3 `/ s
  • 起始位,标志一个数据帧的开始,固定为低电平。当数据开始发送时,产生一个下降沿。(空闲–>起始位)
    ( Q6 Q+ i" s! Z- F2 E& z6 N, U% l
  • 数据位,发送数据帧,1为高电平,0为低电平。低位先行。
    " ^0 B  i  K" B9 B  w5 t
  • 比如 发送数据帧0x0F 在数据帧里就是低位线性 即 1111 0000
    2 [) Z( s- z) ]* O3 u6 f
  • 校验位,用于数据验证,根据数据位的计算得来。有奇校验,偶校验和无校验。
    / \1 x) t3 y( F! W( L6 ]" O1 `: I" U
  • 停止位,用于数据的间隔,固定为高电平。数据帧发送完成后,产生一个上升沿。(数据传输–>停止位)

    6 @0 T; G2 Q9 d8 q2 _: M

7 n7 F4 `4 Y4 _4 O: @( O
下方就是一个字节数据的传输过程,从图中可以看出,串口发送的数据一般都是以数据帧的形式进行传输,每个数据帧都由起始位,数据位,停止位组成, 且停止位可变。

1 s- f5 m( e/ ~* z+ c% s1 f
微信图片_20230726144152_2.png % O# K  T4 J2 S) _5 ]& i3 R

' E6 @& T. V5 p, y) b

* M3 v8 ^+ u# b# {0 d
二、串口的发送和接收
USART是STM32内部集成的硬件外设,可以根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可以自动接收RX引脚的数据帧时序,拼接成一个字节数据,存放在数据寄存器里。
& E7 n% Y8 S+ G0 y; A2 z
当配置好USART的电路之后,直接读取数据寄存器,就可以自动发送数据和接收数据了。在发送和接收的模块有4个重要的寄存器

/ U- q/ s. h% e0 ^0 @7 ]. I
  • 发送数据寄存器TDR

    1 o4 k, I8 ~4 ]1 z
  • 发送移位寄存器,把一个字节的数据一位一位的移出去
    # A3 v& W1 ~) P& A( G7 j2 U
  • 接收数据寄存器RDR
    ) a+ M+ d* J! _* P2 s
  • 接收移位寄存器,把一个字节的数据

    6 H, i4 E+ ~, M, X1 ^5 e

" j1 T( x4 M; J. J( y. w! o
下方为串口的发送和接收图解:
  L2 a' @4 ?$ Y6 {8 o
微信图片_20230726144152_1.png 2 P2 o% k. I2 O' g+ T/ L5 i* q" d
; \5 ~+ t3 O6 h4 r5 w/ f0 Y
串口发送
4 N6 T- v7 f* Y
在配置串口的各个参数时,可以选择发送数据帧的数据位的大小,可选8位或9位。

' h& w( O8 v' }; y
串口发送数据实际上就是对发送数据寄存器TDR进行写操作。

' d+ L2 _  a9 W) V7 s
1. 当串口发送数据时,会检测发送移位寄存器是不是有数据正在移位,如果没有移位,那么这个数据就会立刻转移到发送移位寄存器里。准备发送。
; z; a0 A' X- @
2. 当数据移动到移位寄存器时,会产生一个TXE发送寄存器空标志位,该位描述如下。当TXE被置1,那么就可以在TDR写入下一个数据了。即发送下一个数据。

1 Z. s. a! s# S
微信图片_20230726144152.png " z+ \0 V. t8 H8 G! D" E* s; B

7 \0 P/ ^+ E' u* K0 v& u
3. 发送移位寄存器在发送器控制的控制下,向右移位,一位一位的把数据传输到TX引脚。
, V2 W( V; R& b, W1 d1 I. R
微信图片_20230726144153_3.png ( {6 U, p* L1 _5 U
+ T0 j" i1 A/ T9 A. C' u
4. 数据移位完成后,新的数据就会再次从TDR转移到发送移位寄存器里来,依次重复1-3的过程。通过读取TXE标志位来判断是否发送下一个数据。
; T' m8 `2 z8 C- r+ [! v& C
串口接收

  i& u/ i  G3 W0 k2 k/ N6 ]" A
数据从RX引脚通向接收移位寄存器,在接收控制的控制下,一位一位的读取RX的电平,把第一位放在最高位,然后右移,移位八次之后就可以接收一个字节了。

7 A7 ]& a9 [6 P! N6 d) @
当一个字节数据移位完成之后,这一个字节的数据就会整体的移到接收数据寄存器RDR里来。

1 ]2 e& L/ A, [$ m
在转移时会置RXNE接收标志位,即RDR寄存器非空,下方为该位的描述。当被置1后,就说明数据可以被读出。
  O6 `- R' r+ c. K5 @
微信图片_20230726144153_2.png
! w6 ]+ o' z) B/ c

9 V; c$ V$ U' N$ w- S6 f
下图即为串口接收的工作流程

  e; |1 z4 Y; v4 P0 e) _/ L# i
微信图片_20230726144153_1.png 2 a7 B9 k: q# e' J  v, }7 I5 H5 R
4 s7 d9 a4 o+ P- h5 e" u) m% Q
三、串口在STM32中的配置
0 N4 P" y, D8 Z$ ~: E$ d
首先要明确几点:使用STM32串口外设中的哪一个?串口发送或者接收数据?串口相关的参数配置?发送或接收是否使用到中断?
' R  `" {, @6 L' \& e! E& c' f
下方为串口发送的配置。
5 E3 D+ e# C& L5 t" }$ x: O
1. RCC开启USART、串口TX/RX所对应的GPIO口
. Z/ a! M+ }! _' B9 [
  1. RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);  //开启USART2的时钟
    & G7 y5 {  q- P0 Q0 ?6 P
  2. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);   //开启GPIOA的时钟
复制代码

( F5 w! y- S* X1 ^! G
2. 初始化GPIO口
& C% `5 R5 V2 Y1 [  y! o6 m
这里注意哈,根据自己的需求来配置GPIO口,发送和接收是都需要还是只需要其中一个。然后对应的根据引脚定义表来初始化对应的GPIO口。

# A# [  g& X4 d# N! f  _. m' F
USART2对应的引脚

# ^  M0 p) U7 y% b
微信图片_20230726144153.png $ D& V( Q9 C$ z9 W! W: p
: k* l/ d4 f9 h6 l
USART1对应的引脚
2 `- N# g" t7 ^, |; N( }
微信图片_20230726144154_4.png ; B$ V% |2 a" D0 {  L
6 Y* S, D5 G: U! A
这里根据手册来看,RX引脚模式配置成浮空输入或者上拉输入。TX引脚模式配置成复用推挽输出。

/ L% Z5 `3 c) M
**比如我这里只初始化TX发送端**
  1. //TX端% `2 T1 A9 V7 m$ Q8 ?
  2.     GPIO_InitTypeDef GPIO_InitStructure;
    2 e; t5 Y, g2 d4 u- G6 V
  3.     GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;  //复用推挽输出3 i- N! w2 D8 C" o  T% c+ G
  4.     GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2;   //USART2对应的TX端为GPIOA2! p+ M6 p1 J! X7 G
  5.     GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;  //50MhZ% v8 u; j' z8 s/ K1 B: a7 E
  6.     GPIO_Init(GPIOA,&GPIO_InitStructure);
复制代码

& ~4 x( A  S) N% J# N5 v
3. 串口初始化

% ?8 m4 @: d3 S6 O: R/ N
注意哈,USART_Init()这个函数,是用来配置串口的相关参数的。

$ A& }8 R1 w# p
  • USART_BaudRate 串口通信使用的波特率 一般是9600或者是115200,这里我们给9600
    ' `% P# N5 O* M3 u* s
  • USART_HardwareFlowControl 是否选择硬件流触发,一般这个我们也不选,所以选择无硬件流触发。
    " a1 ]* i, N/ V8 N
  • USART_Mode 这个参数要注意了哈,串口的模式,发送模式还是接收模式,还是两者都有

    9 h8 ]7 P) y$ J% b) s
  • USART_Parity 校验位,可以选择奇偶校验和不校验。没有需求就直接无校验
    4 h" K3 @6 \+ K* D0 a3 u2 x! M
  • USART_StopBits 停止位 有1、0.5、2位,我们这里选1位停止位
    % R& I. m# s  t1 D/ ]  W
  • USART_WordLength 数据位 有8位和9位可以选择
    0 \' @1 F* P2 N* g, S
  1. //串口初始化
    2 s% R6 S: x2 b3 g0 T; j
  2.   USART_InitTypeDef USART_InitStruct;
    2 g7 H; N0 C& ?8 N
  3.   USART_StructInit(&USART_InitStruct);  //初始默认值' B% f- y0 D1 p7 U; E6 X7 I+ D
  4.   USART_InitStruct.USART_BaudRate=9600;3 u0 i) N0 M2 x& e. q
  5.   USART_InitStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None;   //不使用硬件流触发8 n2 d- F  A1 m8 _4 C
  6.   USART_InitStruct.USART_Mode=USART_Mode_Tx;         //TX 发送模式
    $ g- }/ F8 [) p2 T( ^% M
  7.   USART_InitStruct.USART_Parity=USART_Parity_No;       //不选择校验
    4 K: N& y! k5 \0 {. ?1 b3 E
  8.   USART_InitStruct.USART_StopBits=USART_StopBits_1;      //停止位1位- q) w7 X, b9 ^- u
  9.   USART_InitStruct.USART_WordLength=USART_WordLength_8b;  //数据位8位
    ' o; v) E5 j2 q7 I  A# u
  10.   USART_Init(USART2,&USART_InitStruct);
复制代码

5 |" E6 D0 z# \+ w' e* J& Q
4. 串口使能
  1. //串口使能
    & o: ^/ c1 `$ q; t6 o
  2.    USART_Cmd(USART2,ENABLE);
复制代码
) R6 t- V( x2 p8 E$ v
5. 串口发送数据

9 E4 b4 D( J1 ]注意哈,我们要判断TXE标志位的状态。0,数据还没有被转移到移位寄存器;1,数据已经被转移到移位寄存器。

. T* N# d2 g- N
当TXE标志位为1时,就说明可以发送下一个数据了。详细过程可看上面串口发送的解释。
  1. void Serial_SendByte(uint16_t Byte)
    0 U% J/ \" n5 L
  2. {
    ; T' N* K: u1 y& r! u
  3.   USART_SendData(USART2,Byte);8 O- B# r3 k" t$ I
  4.   //0 表示数据还未转移到移位寄存器 循环等待 1 数据已经被转移到了移位寄存器可以发送数据
    9 w* Q) V( H. L7 W" `- b! m. X  X
  5.   while(USART_GetFlagStatus(USART2,USART_FLAG_TXE)==RESET);  //不需要手动清零 再次写入TDR时会自动清零    ?% W" y. f3 r
  6. }
复制代码
/ C& ]2 O2 I( |+ N
经过上述五步的配置,单片机就可以通过串口发送数据了。

2 E* s$ q# f; [+ S( a2 R
下方为发送数据图例,STM32向串口发送0x16数据
" ?3 l4 r6 Q) ]4 f8 D5 G# N
微信图片_20230726144154_3.png
7 b3 T  J3 ]* m5 {4 b. d, z& E
# d  a7 L% i3 s7 Y% \) M: C

7 L0 I6 e. `! G# `" A
四、串口接收的两种实现方式
上方是发送数据的例子,那么串口接收又该如何配置,又要在串口发送的例子上做哪些更改呢?

7 o0 b1 h$ l5 L" i0 M# j' s: r
这里我们可以通过查询或者中断的方式来进行接收数据的两种方式。

( j* }2 i* a# r, s/ s, @' s
  • 查询方式就是通过不断的查询RXNE标志位,通过判断RXNE位的状态来确定数据是否接收。

    : u! X$ V; j9 y0 j) P+ b; K3 D
  • 中断方式就是通过配置接收输出控制通道,配置NVIC,在中断服务子函数里进行数据的接收。

    # t8 a0 y  _+ @- |0 C' d5 U3 ?7 D) @
  [+ W0 a. U6 J5 u9 ^
1. 需要更改的地方
2 t9 r4 \0 V! M( t! [, ]9 D. D" R3 C. y
既然我们要实现串口的接收,那么就要配置串口RX引脚,在串口模式中添加USART_Mode_RX模式。
: }- a6 S  H. H( Z9 S! V
  • 初始化RX引脚

    ' V. X' r' g0 G# W
  1. //RX端
    - f6 Q. b* W" W" R. M" J8 c
  2.   GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;  //上拉输入
    6 e( i5 v5 A8 P3 H3 _
  3.     GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3;
    # a+ V( R, Y2 P, u2 y
  4.     GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;  //50MhZ
    & I( i/ C2 c8 G$ R1 s  J$ W
  5.     GPIO_Init(GPIOA,&GPIO_InitStructure);
复制代码
2 O3 e) S0 w* p. J7 e6 ~- y" R4 {  ^9 @

& \' O# y' K7 s
  • 添加串口模式
    0 R; H, B) B8 V5 [
  1. USART_InitStruct.USART_Mode=USART_Mode_Tx|USART_Mode_Rx;         //TX 发送模式  RX 接收模式
复制代码

3 l+ \" n7 \1 q0 S. v

% s3 W9 b/ Z# [  B
2. 查询RXNE标志位

4 u6 l0 u; z2 m- G) f2 d( L' \
这里我们还是来看一看RXNE标志位的描述
8 A9 I- D5 B$ C) @8 g
微信图片_20230726144154_2.png
9 l$ E+ Q! }4 @' T1 F

8 H9 W- {, R4 q+ l5 N3 {. R: F8 K
上图描述,为0时数据没有收到,为1时收到了数据,数据可以从RDR里读出
+ b, L  N3 r8 W1 J
所以在主程序里不断读取RXNE标志位,如果为1,表示数据可以读出
  1. uint8_t RX_Data;0 T* [# l% t& P
  2. int main()* h$ q4 r9 A$ u' K
  3. { + i9 n7 S: s8 g: ?" L( a
  4.     Serial_Init();0 n2 ]9 \' }: L7 r
  5.     Serial_SendByte(0x16);
    : H1 t' p* f+ \, n. `2 m4 D! b' |
  6.     while(1)
    ' E9 c% b  m/ u5 c
  7.     {9 ?* f1 d4 X' q$ |
  8.         if(USART_GetFlagStatus(USART2,USART_FLAG_RXNE)==SET)   //0 循环等待 1 可以接收数据4 r2 F5 L7 l6 k) O% e3 f. l, I5 }
  9.         {* ~3 {3 }' V- |2 v0 m
  10.             RX_Data=USART_ReceiveData(USART2);           
    , Y. V9 a0 y8 a8 `, h$ ~; a9 [
  11.       Serial_SendByte(RX_Data);- |1 y0 o" m) \# g. {
  12.         }" `0 a4 d- ^3 z# W
  13.     }# H5 v7 S# h# x% s
  14. }
复制代码
0 n* F0 b) z+ |0 r% F
下图为程序现象:pc向单片机发送数据0x15,单片机接收数据0x15,并且把接收到的数据作为数据发送到pc,在pc上显示0x15。

  z5 X- V5 |+ E$ O
微信图片_20230726144154_1.png
$ d( W8 x6 e* S. q9 ~4 }

6 |4 u2 c+ b: B6 J, T& ?
3. 使用中断

' ~8 k* z" D+ c7 k) C
  • 通过配置串口的接收作为中断源,开启中断输出控制,配置NVIC。开启中断通道。
    2 Q* O- h. ^% z- M) j
  1. //开启中断输出控制
    ' ?, a2 \6 o+ j2 V7 @& \6 h
  2.   USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);- D# A; Z# _) a2 [
  3.   
    ( O0 o3 b# F! O% x. r; a
  4.   //配置NVIC
    * ^- B$ p' w& A+ Z
  5.   NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    1 c8 ?" P. N  i& x- K4 p
  6.   NVIC_InitTypeDef NVIC_InitStruct;/ p8 s8 S! f' [. b
  7.   NVIC_InitStruct.NVIC_IRQChannel=USART2_IRQn;   //选择USART2的中断通道
    2 i3 ^& {/ S( `4 P
  8.   NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;    //中断使能
    1 r2 |5 j5 o" @9 e$ \" D
  9.   NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=1;
    * n# M( Q1 O, s& O, Q
  10.   NVIC_InitStruct.NVIC_IRQChannelSubPriority=1;
    ' k  |8 J* K! k9 _. i3 Y3 V) G
  11.   NVIC_Init(&NVIC_InitStruct);
复制代码

) z4 b$ I1 j& g0 f

: S) Y; l3 ^( C# Y
  • 中断服务子函数
    - |" W' t; K- Y
% l8 s+ |1 n5 @3 R! @
中断服务子函数写好后,就可以在中断里读取接收到的数据了。
1 u% `9 b/ L( Q
当接收到数据后,触发接收中断,主程序暂停执行。接收完数据后主程序回复执行。当接收到数据时,就触发中断。
  1. void USART2_IRQHandler(void)4 i' B, V# H& p0 W
  2. {. E+ @4 v' v+ Q3 k
  3.   if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET)   //RXNE 标志位为1 表示可以接收数据
    - J9 E8 g& g0 j, w# N
  4.   {: N1 X8 L5 L) E% p+ F& ~& P0 u
  5.     RX_Data=USART_ReceiveData(USART2);, x6 E. I8 _4 d! X) d& Q
  6.     Flag=1;7 k& R0 L# a# f# ]" `  R1 k
  7.     USART_ClearITPendingBit(USART2,USART_IT_RXNE);  //清除RXNE标志位
      E3 J, p6 n" w1 E8 g& ?
  8.   }
    % T/ E2 B  q& r, ?& @1 [
  9. }
复制代码

! n9 W/ K4 i6 M+ ?* r  @3 m- K
  • 主程序测试

    5 n! d) h1 l' ?& A. f7 i% c
  1. uint8_t RX_Data;
    9 ~0 Q% o" ^( G
  2. uint8_t Flag;* O6 ]$ f# P, j+ A' W6 P) w
  3. ! L+ B( |) r: o0 K% H/ f  \
  4. int main()
    " x9 B0 ]. H9 e( p, u
  5. {) y9 \: J1 H$ c% w+ e
  6.     Serial_Init();4 N7 Q7 t5 _5 n  L9 e0 D( X& M
  7.     Serial_SendByte(0x16);
    3 q/ W6 Q' H7 B3 L( `/ i1 Z  U
  8. : I' V# _0 J8 x" x7 E" @
  9. " x* D! R5 s* y1 q( ?) q. K
  10.     while(1)
    0 [0 U* ?- H+ m) w! U/ _, T
  11.     {- O; d# E4 F3 M2 L
  12.         if(Flag==1)% n2 P" x' A, I* v/ o7 B8 R' c
  13.         {3 l3 W  L4 l$ _
  14.             Serial_SendByte(RX_Data);; z& J( f. j2 ]7 t* l; A
  15.         }
    7 C6 k2 l. B$ i
  16.     }4 J3 B  B3 _( c1 M- t: W) H
  17. }
    # u% @$ C8 s' t1 C: `5 n
  18. : X: k; M: h* R* \5 j
  19. void USART2_IRQHandler(void)2 }' C# n+ R# h
  20. {
      k1 ~/ N# Y0 ^
  21.     if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET)   //RXNE 标志位为1 表示可以接收数据
    8 i! a" K$ N8 q9 K" z& H
  22.     {0 s  A$ W5 ]9 O8 v. g0 a0 E2 f/ T# i
  23.         RX_Data=USART_ReceiveData(USART2);5 G4 ?4 d( h: H) k( ?
  24.         Flag=1;
    1 ]8 w) q  c6 F! S
  25.         USART_ClearITPendingBit(USART2,USART_IT_RXNE);  //清除RXNE标志位
    4 p% g: c6 }5 B3 @+ i1 q
  26.     }# H# G4 o. Y$ r# c: z9 w
  27. }
复制代码

" S- y1 W) @/ a- D6 l9 C1 X# I) ~

* m% a. N9 x3 C$ w1 P+ s
下图为程序现象:可以看到,串口确实收到了数据,只是我把接收到的数据0xFE放在了while循环里,这说明数据接收是成功的,使用中断是可行的。

% h% a2 J( @  n
微信图片_20230726144154.png
4 V3 w/ j; s! X7 d5 l% z
: B9 ?% L4 N. I9 }) ~/ X

1 Z& Q$ P( v% C+ j5 g( o- O  h2 j
总结
到这里,就大致总结了串口的发送和接收。
4 f1 j6 m# W4 @# g# L
串口的配置,使用查询或者中断来接收数据。
2 A( J! B4 b1 d+ w( W( H* g( h
串口的使用会很常用到,所以在这里对串口做一个总结,也算是对之前知识的一个回顾和总结,加强印象。
) B0 V6 N' z; |) ^- t
如果有什么写的不对的地方,欢迎指正!
6 G3 E+ Z# R9 s6 R9 Q* Q4 c
转载自: 古月居
如有侵权请联系删除
2 }6 @, q: n1 X" p

5 q' T5 K# M& e4 j  a
* P" Q( Y0 V( }0 c& _$ t
收藏 评论0 发布时间:2023-7-26 14:43

举报

0个回答

所属标签

相似技术帖

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