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

基于STM32串口通信经验分享

[复制链接]
攻城狮Melo 发布时间:2023-7-26 14:43
STM32的串口通信
' V9 S+ j% R8 S- g  l! X
本文在于记录自己的学习过程中遇到的问题和总结,各种情况下串口通信在STM32的实际使用方面占有很大的比重,本文主要对串口通信做一个简要的总结。
+ O, V# k" w- w/ f( Z* I4 E2 u
一、STM32里的串口通信
在STM32里,串口通信是USART,STM32可以通过串口和其他设备进行传输并行数据,是全双工,异步时钟控制,设备之间是点对点的传输。
6 }5 l5 X& ?7 V" K
对应的STM32引脚分别是RX和TX端。STM32的串口资源有USART1、USART2、USART3.
; G6 K* H, s: j' Z9 ?$ t
串口的几个重要的参数:
3 b9 R0 ]' x. L9 L3 q
  • 波特率,串口通信的速率
    # K5 i2 P: q+ \, D. [4 g% A: h# T
  • 空闲,一般为高电平

    1 Z8 k$ |& y: M) |
  • 起始位,标志一个数据帧的开始,固定为低电平。当数据开始发送时,产生一个下降沿。(空闲–>起始位)
    & L3 f/ H& b0 O, o7 `0 C
  • 数据位,发送数据帧,1为高电平,0为低电平。低位先行。

    ( {" ^% j/ s, I( S' q
  • 比如 发送数据帧0x0F 在数据帧里就是低位线性 即 1111 0000

    ; c8 f7 D" H3 I9 G
  • 校验位,用于数据验证,根据数据位的计算得来。有奇校验,偶校验和无校验。
    $ I5 I2 v- K; D, J1 R5 d
  • 停止位,用于数据的间隔,固定为高电平。数据帧发送完成后,产生一个上升沿。(数据传输–>停止位)

    & |1 G  L+ _6 F; e6 f2 {8 I
( p# h$ c( z; k( A& i0 a
下方就是一个字节数据的传输过程,从图中可以看出,串口发送的数据一般都是以数据帧的形式进行传输,每个数据帧都由起始位,数据位,停止位组成, 且停止位可变。
, G2 \3 U* O. O1 S/ s- U
微信图片_20230726144152_2.png
% C* ~$ j' K2 z1 Y$ i+ K5 A7 a" e
  o3 w! [- K/ A: Z

& Q/ B- U' U: B, t; @1 w- B2 H
二、串口的发送和接收
USART是STM32内部集成的硬件外设,可以根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可以自动接收RX引脚的数据帧时序,拼接成一个字节数据,存放在数据寄存器里。
3 _& n9 Z$ k8 A+ E* ], s) Y
当配置好USART的电路之后,直接读取数据寄存器,就可以自动发送数据和接收数据了。在发送和接收的模块有4个重要的寄存器
; K. z6 ]; p% E. W2 x( A: @3 P$ A
  • 发送数据寄存器TDR
    / ~2 ?0 U, h9 I% `: X; B0 s
  • 发送移位寄存器,把一个字节的数据一位一位的移出去
    $ Z$ g9 ]# b6 v8 p( i! z# p
  • 接收数据寄存器RDR
    3 q9 z* i0 z2 W, C( K
  • 接收移位寄存器,把一个字节的数据

    7 U$ m# H1 @5 _& p

% a% M3 Y4 @5 t/ a3 H& Y4 m# Y
下方为串口的发送和接收图解:

  j4 S8 Z8 ?, J7 p/ v9 R- u
微信图片_20230726144152_1.png 0 s3 h, W5 w3 P; o0 C+ Q

, H1 S9 R* T2 _6 o. I% e
串口发送
8 l8 B' u# J( l: g3 \
在配置串口的各个参数时,可以选择发送数据帧的数据位的大小,可选8位或9位。
! K1 g" X6 R( ^2 p# R! C7 d3 q
串口发送数据实际上就是对发送数据寄存器TDR进行写操作。

! o. j5 Y8 ]% i& v( ]
1. 当串口发送数据时,会检测发送移位寄存器是不是有数据正在移位,如果没有移位,那么这个数据就会立刻转移到发送移位寄存器里。准备发送。
* g6 x6 [; d7 j2 B
2. 当数据移动到移位寄存器时,会产生一个TXE发送寄存器空标志位,该位描述如下。当TXE被置1,那么就可以在TDR写入下一个数据了。即发送下一个数据。

4 R2 t8 w& }% N  z: U
微信图片_20230726144152.png
  ^" @+ U% b1 G0 V/ V9 h4 e

7 O( v2 J+ o/ n" U
3. 发送移位寄存器在发送器控制的控制下,向右移位,一位一位的把数据传输到TX引脚。

2 p; l) @$ L8 }0 C, Y/ t/ c, l
微信图片_20230726144153_3.png
, O1 {$ C9 I& K! J; q9 O* M

) \$ i, b& a2 ]: N0 e1 y
4. 数据移位完成后,新的数据就会再次从TDR转移到发送移位寄存器里来,依次重复1-3的过程。通过读取TXE标志位来判断是否发送下一个数据。

0 }: }& r/ l5 e; |; v/ K
串口接收
! U& L/ E" l& j0 y
数据从RX引脚通向接收移位寄存器,在接收控制的控制下,一位一位的读取RX的电平,把第一位放在最高位,然后右移,移位八次之后就可以接收一个字节了。

2 E% o" }  C" `2 \
当一个字节数据移位完成之后,这一个字节的数据就会整体的移到接收数据寄存器RDR里来。
3 o( }. D- K5 r4 G  D. g
在转移时会置RXNE接收标志位,即RDR寄存器非空,下方为该位的描述。当被置1后,就说明数据可以被读出。

+ Y* e* n6 W( O/ X4 F- x" n: x* e. m9 y
微信图片_20230726144153_2.png 8 ?. `+ w; e" ?: e0 f0 j! u

& G, y/ R2 l0 X7 g2 u. n. L
下图即为串口接收的工作流程

% R8 X- o! r9 _  w8 J) m8 A$ b
微信图片_20230726144153_1.png
$ w5 }: a$ ]! z; R: d* f4 X2 V
, B% ^8 R- X. O0 G* O2 |  f! Q
三、串口在STM32中的配置

  E' [# ~) T- x0 K( \, E
首先要明确几点:使用STM32串口外设中的哪一个?串口发送或者接收数据?串口相关的参数配置?发送或接收是否使用到中断?

# Y% Q4 ]4 @+ i
下方为串口发送的配置。
- z2 D8 _1 p7 j( C8 D1 U
1. RCC开启USART、串口TX/RX所对应的GPIO口
7 b3 j: Q' |9 D& _6 n- u" h
  1. RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);  //开启USART2的时钟* D+ X+ P% `# u) c
  2. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);   //开启GPIOA的时钟
复制代码

* l( ^: @' ]/ b: L$ U
2. 初始化GPIO口
/ e% X" L% P& J
这里注意哈,根据自己的需求来配置GPIO口,发送和接收是都需要还是只需要其中一个。然后对应的根据引脚定义表来初始化对应的GPIO口。
6 q" l9 T" Q2 w+ Z
USART2对应的引脚

; b4 w! A& s8 }. G; l2 q9 y
微信图片_20230726144153.png
1 Y- q) ^. g/ z/ ~
$ g) s) ?- o% q! X7 |( S
USART1对应的引脚
  c6 x' Q6 Y% }
微信图片_20230726144154_4.png / x0 @/ j6 U) q& [5 ^! I

0 L* e! z# V7 v4 {9 |
这里根据手册来看,RX引脚模式配置成浮空输入或者上拉输入。TX引脚模式配置成复用推挽输出。

. b, V+ ^4 G! g
**比如我这里只初始化TX发送端**
  1. //TX端% v; @( w9 O1 }0 O. R& t
  2.     GPIO_InitTypeDef GPIO_InitStructure;
    7 [" J+ L6 j( ]& _  H* N
  3.     GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;  //复用推挽输出
    , c; {- v" l: F0 W
  4.     GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2;   //USART2对应的TX端为GPIOA2
    5 j( u0 a( Z' J( V- Y: r+ P
  5.     GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;  //50MhZ# x+ z1 {# g" P! [# b
  6.     GPIO_Init(GPIOA,&GPIO_InitStructure);
复制代码
2 W# q! s" u; `& b( A
3. 串口初始化

: {- Y. p  {$ K8 r0 I4 v# ]
注意哈,USART_Init()这个函数,是用来配置串口的相关参数的。
, w# F0 m3 \. o5 _! s9 E
  • USART_BaudRate 串口通信使用的波特率 一般是9600或者是115200,这里我们给9600

    ; g2 e6 I& i6 \
  • USART_HardwareFlowControl 是否选择硬件流触发,一般这个我们也不选,所以选择无硬件流触发。
    5 t0 u# _- E- N( l/ l6 d
  • USART_Mode 这个参数要注意了哈,串口的模式,发送模式还是接收模式,还是两者都有
    1 Y3 W8 W  `, a5 s7 S
  • USART_Parity 校验位,可以选择奇偶校验和不校验。没有需求就直接无校验

    9 J3 r: }# D. c4 i
  • USART_StopBits 停止位 有1、0.5、2位,我们这里选1位停止位
    & A9 y# t: d8 U6 m. `8 l$ V
  • USART_WordLength 数据位 有8位和9位可以选择

    ' [! j/ r' q! k, j
  1. //串口初始化% l/ q6 Z% K, v7 D/ `  P) n
  2.   USART_InitTypeDef USART_InitStruct;9 @0 |1 o6 M7 ?2 N% Y4 b1 _; m
  3.   USART_StructInit(&USART_InitStruct);  //初始默认值% k  I  y- P. y+ Q- s' l
  4.   USART_InitStruct.USART_BaudRate=9600;6 J8 w1 h# B, y- L1 c+ h) f/ }
  5.   USART_InitStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None;   //不使用硬件流触发( A" _  n" C1 t) t$ w# F
  6.   USART_InitStruct.USART_Mode=USART_Mode_Tx;         //TX 发送模式
    - k& o) X- v( N+ g
  7.   USART_InitStruct.USART_Parity=USART_Parity_No;       //不选择校验
    9 ~# G" a" S2 k% r# s/ O
  8.   USART_InitStruct.USART_StopBits=USART_StopBits_1;      //停止位1位" S  k& A( y- v" S8 q! @
  9.   USART_InitStruct.USART_WordLength=USART_WordLength_8b;  //数据位8位
    , T  @- `( y% V1 [
  10.   USART_Init(USART2,&USART_InitStruct);
复制代码
5 N: X( E4 a! V9 P3 ^0 m& ~
4. 串口使能
  1. //串口使能
    ( h) |  n" C: i) r" h
  2.    USART_Cmd(USART2,ENABLE);
复制代码

3 \: ]/ N) x' w' O$ p# \/ z, {' c& z
5. 串口发送数据

* Q6 }$ S- o/ g, @6 R注意哈,我们要判断TXE标志位的状态。0,数据还没有被转移到移位寄存器;1,数据已经被转移到移位寄存器。

. w6 M! n5 C0 @% t: L2 O
当TXE标志位为1时,就说明可以发送下一个数据了。详细过程可看上面串口发送的解释。
  1. void Serial_SendByte(uint16_t Byte)- M2 ~9 P) ?9 ?' k
  2. {
    # V* b' Z- t8 F' ]
  3.   USART_SendData(USART2,Byte);& c5 O8 y3 }+ s9 a" i% o
  4.   //0 表示数据还未转移到移位寄存器 循环等待 1 数据已经被转移到了移位寄存器可以发送数据
    $ m' Q) m" v% m/ A/ f
  5.   while(USART_GetFlagStatus(USART2,USART_FLAG_TXE)==RESET);  //不需要手动清零 再次写入TDR时会自动清零  
    2 E( @1 v" w# R$ v) k% X+ A
  6. }
复制代码

# B, Z* _- q2 g2 b7 H' L
经过上述五步的配置,单片机就可以通过串口发送数据了。
  i  [0 J1 _( a4 c' L7 m( ?, R
下方为发送数据图例,STM32向串口发送0x16数据

3 q1 C1 g$ F1 Y2 Z6 ^
微信图片_20230726144154_3.png ! G% k: y# A" ~; n

  F/ c) L2 F7 F( l! m4 E! z

3 s: ^# y: k  m" o
四、串口接收的两种实现方式
上方是发送数据的例子,那么串口接收又该如何配置,又要在串口发送的例子上做哪些更改呢?

2 W8 |; Z2 `: D: F" t; S8 ?. G
这里我们可以通过查询或者中断的方式来进行接收数据的两种方式。

- z6 p3 {1 F- I( S& m
  • 查询方式就是通过不断的查询RXNE标志位,通过判断RXNE位的状态来确定数据是否接收。

    . a% W9 k4 i) f+ D5 m
  • 中断方式就是通过配置接收输出控制通道,配置NVIC,在中断服务子函数里进行数据的接收。
    5 ^' z5 R" F. L* E; K, z
; t8 S6 S" G+ d* h% o+ T
1. 需要更改的地方
. @* A# j6 R& E5 {$ I: ^% `9 {0 v  G. ^" r1 L6 u) v
既然我们要实现串口的接收,那么就要配置串口RX引脚,在串口模式中添加USART_Mode_RX模式。
8 [! `; H/ q  r- x0 v
  • 初始化RX引脚

    / ?4 t/ D$ H7 g4 Z; q) I
  1. //RX端( C" S( G" B) W( D) x6 z
  2.   GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;  //上拉输入1 W8 n; N0 }3 D  H
  3.     GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3;- U/ ?- U" H7 Y+ Z# D" F' \4 c
  4.     GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;  //50MhZ
    3 h+ w' K% [& m% _  M
  5.     GPIO_Init(GPIOA,&GPIO_InitStructure);
复制代码

$ f- r( H4 F6 ^3 c6 u1 ~
) H3 \" {3 Q( i% E0 b
  • 添加串口模式
    5 J" I$ m& W; o& Q' M# p1 a+ h
  1. USART_InitStruct.USART_Mode=USART_Mode_Tx|USART_Mode_Rx;         //TX 发送模式  RX 接收模式
复制代码
' i8 @) m3 V% Q" J) @' L4 x
# A0 u6 R6 A9 G* W8 ?. [8 D- q
2. 查询RXNE标志位
% |7 g7 o; _, g  a1 j; M  d
这里我们还是来看一看RXNE标志位的描述

4 D# k+ D% J1 s+ t
微信图片_20230726144154_2.png
$ h9 H& u4 y( K
% O5 a( R/ \$ m' q
上图描述,为0时数据没有收到,为1时收到了数据,数据可以从RDR里读出

3 }. I+ R2 O/ T6 c
所以在主程序里不断读取RXNE标志位,如果为1,表示数据可以读出
  1. uint8_t RX_Data;
    . r0 x8 r% ~1 w5 @5 S' G
  2. int main()
    4 i2 H! q7 t0 b$ J
  3. {
    / J( R# U8 `3 y/ ^7 \6 e
  4.     Serial_Init();
    7 I/ y5 T, k: H
  5.     Serial_SendByte(0x16);* @0 W7 c& x3 q3 A5 ^& W0 ?
  6.     while(1)7 N, F5 l7 v& r( g
  7.     {
    / E" C$ R% ?" [, q2 e' r
  8.         if(USART_GetFlagStatus(USART2,USART_FLAG_RXNE)==SET)   //0 循环等待 1 可以接收数据: Z0 e6 r4 t/ F
  9.         {
    / x% J. A7 s4 G
  10.             RX_Data=USART_ReceiveData(USART2);           0 W; r% J9 [6 |, Q2 X0 o! L, Z2 {
  11.       Serial_SendByte(RX_Data);) d( h  ~" d4 T$ a
  12.         }
    ; E5 s. o  e4 V2 U) O6 l: V% k4 r2 r
  13.     }$ M* P* i+ I; f5 B, s" C8 }
  14. }
复制代码

; W% f8 H8 s$ y3 i
下图为程序现象:pc向单片机发送数据0x15,单片机接收数据0x15,并且把接收到的数据作为数据发送到pc,在pc上显示0x15。

1 x9 f7 _) S9 p8 D$ n! j7 G
微信图片_20230726144154_1.png
& y  L( O; M4 o+ I. |' g8 t; r: [

4 I. ~) l4 @# o3 a$ N) L/ t
3. 使用中断

, O* y( ^1 C8 L
  • 通过配置串口的接收作为中断源,开启中断输出控制,配置NVIC。开启中断通道。
    7 r& n/ k& z' @; q. q3 N. x
  1. //开启中断输出控制
    4 l. s* n+ A: f
  2.   USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);
    : A, U( D9 b+ l6 A2 ?
  3.   ' S, E- m( J+ a* ]- w" W# Y
  4.   //配置NVIC6 ]# M0 C' i/ V5 M( l; B; h0 s- R
  5.   NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 5 X9 z8 p7 P+ l* M# `
  6.   NVIC_InitTypeDef NVIC_InitStruct;
    0 ?% ~( c/ n7 H  D
  7.   NVIC_InitStruct.NVIC_IRQChannel=USART2_IRQn;   //选择USART2的中断通道
    ' k& M( y1 ]" ~$ L
  8.   NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;    //中断使能
    3 l* R$ w2 A3 n% \$ F
  9.   NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=1;
    ( }) @) E: `8 @! r  q" ?- ^& f9 q
  10.   NVIC_InitStruct.NVIC_IRQChannelSubPriority=1;
    5 q3 g2 g$ T( r, z$ }& Z. ]
  11.   NVIC_Init(&NVIC_InitStruct);
复制代码
8 u  O' K) ?# A) r
4 P/ x) F% p0 H0 d
  • 中断服务子函数

    % G! H7 L6 R& ~. G, u. h

% R7 T& c5 @3 M! z5 _5 G4 T2 i中断服务子函数写好后,就可以在中断里读取接收到的数据了。
9 O1 e8 W7 G, T& r8 ?, c: x9 V# C
当接收到数据后,触发接收中断,主程序暂停执行。接收完数据后主程序回复执行。当接收到数据时,就触发中断。
  1. void USART2_IRQHandler(void)
    1 Q7 U" {9 M3 n; t
  2. {6 I: d$ \. `4 C6 s# Y* t; a
  3.   if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET)   //RXNE 标志位为1 表示可以接收数据9 `$ V. }8 T! a2 A# H4 B
  4.   {8 ~/ s$ m: S: a( }0 q  o- g
  5.     RX_Data=USART_ReceiveData(USART2);
    7 G7 K1 x4 s1 [& U7 i5 p
  6.     Flag=1;# ]9 h; H, ~. ^3 }. b. v
  7.     USART_ClearITPendingBit(USART2,USART_IT_RXNE);  //清除RXNE标志位
    ' D  g7 ?/ d' s/ R; h
  8.   }
    4 k8 u  @, l8 c3 k6 }  @& U
  9. }
复制代码
3 z  D. b  t, r  t
  • 主程序测试

    5 l) ~7 T2 _$ C
  1. uint8_t RX_Data;+ ^( @# a: W. |# y( G" ?8 U8 R) ]
  2. uint8_t Flag;% \: S/ T6 X. m2 i/ J, d- ]7 b

  3. - I* ~- u5 K3 X$ U# R
  4. int main()
    4 B1 `* e+ o4 j* E
  5. {
    . t+ @+ @! Y: \7 h# O8 @" Z
  6.     Serial_Init();
    - K* N! `& u, z" I+ E/ I# l
  7.     Serial_SendByte(0x16);
    ! I( R) i# `. F4 a
  8. ( I: ^7 f( v4 T1 x2 y0 w% O

  9. 1 q. k! B! h; A0 V0 h
  10.     while(1)9 c2 |4 q/ G$ ~6 k" J5 p4 H) a" j
  11.     {
    3 z1 i  F' Q. C9 E$ D
  12.         if(Flag==1)
    / O( l6 \8 X  R( C9 ^* Y
  13.         {9 c1 T9 U5 G" X" d9 `
  14.             Serial_SendByte(RX_Data);
    2 p2 M$ Z2 z9 G4 q) N
  15.         }
    ) C- N8 f+ q7 T2 w7 [
  16.     }
    $ h3 ]7 T) O" X% T  \1 ?5 P
  17. }
    " N4 b  f$ B- \. A& L

  18. . y" @$ [$ k. n- I* U8 l
  19. void USART2_IRQHandler(void)
    ; E& x" q* n  }1 S; H, T2 e  v
  20. {
    : h  k0 @4 s7 }$ I
  21.     if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET)   //RXNE 标志位为1 表示可以接收数据0 P- N; D) W9 q# c
  22.     {3 o+ r. g: B8 V1 W# w( @9 B# S: Z
  23.         RX_Data=USART_ReceiveData(USART2);
    . a! A8 [' B, ^4 J& Q: B
  24.         Flag=1;
    ; V8 Z) _8 v3 D# ?- z
  25.         USART_ClearITPendingBit(USART2,USART_IT_RXNE);  //清除RXNE标志位
    ! V+ O% d& c# Q" w. ^
  26.     }6 M8 l2 s4 e  B% _- Y, v, A
  27. }
复制代码
6 ?! h4 [# Q4 \: d* q6 ]. b
* s! @: {$ s5 u& E/ @1 L  N3 s6 d
下图为程序现象:可以看到,串口确实收到了数据,只是我把接收到的数据0xFE放在了while循环里,这说明数据接收是成功的,使用中断是可行的。
# }7 V4 x: ~- i& T3 g  B
微信图片_20230726144154.png
! K, X* ~% o' H: N; }

5 a- h8 G7 ?0 N8 @

* n( r  l3 e9 v8 k# @! W3 h
总结
到这里,就大致总结了串口的发送和接收。
+ E$ T1 b7 J. g* w
串口的配置,使用查询或者中断来接收数据。

# Q, f5 d0 b, l6 @
串口的使用会很常用到,所以在这里对串口做一个总结,也算是对之前知识的一个回顾和总结,加强印象。

% B- G0 e5 S: f
如果有什么写的不对的地方,欢迎指正!

& v$ i9 d- P2 ]/ r
转载自: 古月居
如有侵权请联系删除
# X5 ^, [- l9 |5 m; Q3 `; f

1 |$ F0 M5 a: N; t% q4 @6 p6 r, G  O; ]9 R" T& W
收藏 评论0 发布时间:2023-7-26 14:43

举报

0个回答

所属标签

相似技术帖

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