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

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

[复制链接]
攻城狮Melo 发布时间:2023-8-10 14:48
STM32的串口通信

- [6 U3 ^3 H) r. D7 r+ d  l
本文在于记录自己的学习过程中遇到的问题和总结,各种情况下串口通信在STM32的实际使用方面占有很大的比重,本文主要对串口通信做一个简要的总结。
0 i! p0 k: n) o* P6 ~
4 l/ C; F* c9 U0 \% w
一、STM32里的串口通信
- V( d+ u' k( J6 n
在STM32里,串口通信是USART,STM32可以通过串口和其他设备进行传输并行数据,是全双工,异步时钟控制,设备之间是点对点的传输。
+ h. K1 C0 l0 B# g. C* ]; z( k  t5 F0 W
对应的STM32引脚分别是RX和TX端。STM32的串口资源有USART1、USART2、USART3.
/ _7 h( i+ p; d0 V& o
串口的几个重要的参数:
! E7 q& M$ Y) m# X8 w: j
  • 波特率,串口通信的速率
    ! R: X3 D5 A  V6 a/ T
  • 空闲,一般为高电平

    * p; B% l1 r% D* Q" L
  • 起始位,标志一个数据帧的开始,固定为低电平。当数据开始发送时,产生一个下降沿。(空闲–>起始位)
    ; q( T: W  w; L$ E
  • 数据位,发送数据帧,1为高电平,0为低电平。低位先行。

    ' ]  B0 ?( Y! N# T
  • 比如 发送数据帧0x0F 在数据帧里就是低位线性 即 1111 0000

    ! s) ?- |: \2 }" B& [! R* |
  • 校验位,用于数据验证,根据数据位的计算得来。有奇校验,偶校验和无校验。
    # U6 Y: e9 e5 ]% b9 a2 U
  • 停止位,用于数据的间隔,固定为高电平。数据帧发送完成后,产生一个上升沿。(数据传输–>停止位)

    $ Z, E6 ^/ l, T3 b; A$ v4 O0 U6 A$ ]

* {' d* e9 N7 w/ c: x8 u
下方就是一个字节数据的传输过程,从图中可以看出,串口发送的数据一般都是以数据帧的形式进行传输,每个数据帧都由起始位,数据位,停止位组成, 且停止位可变。
4 i+ R& V( a8 g
微信图片_20230810144703_3.png   z; L. w; M) O8 r4 l" m* Z

5 T& ?' A6 a6 ]" p# v

7 t8 r; U! O" Q/ w( ]
二、串口的发送和接收
( E4 }1 _6 @4 J- G: t
USART是STM32内部集成的硬件外设,可以根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可以自动接收RX引脚的数据帧时序,拼接成一个字节数据,存放在数据寄存器里。
4 R# o8 p. F! U7 p  G
当配置好USART的电路之后,直接读取数据寄存器,就可以自动发送数据和接收数据了。在发送和接收的模块有4个重要的寄存器
& S* J3 c! U6 F, `6 S; l# W
  • 发送数据寄存器TDR
    # o" \4 j" |. r; }5 i6 |7 s+ a! }
  • 发送移位寄存器,把一个字节的数据一位一位的移出去
    & n# ~- T4 l. k
  • 接收数据寄存器RDR

    $ s" ]% f+ _$ L
  • 接收移位寄存器,把一个字节的数据
    ! Y4 X+ R( S" \; z3 v
# y. t/ _9 e+ m3 k& I$ s2 C2 t1 u
下方为串口的发送和接收图解:
. P1 ^* b+ J1 A& `1 L, N; s+ M
微信图片_20230810144703_2.png
5 e$ l% ]& q* Q0 p: p2 p7 L  N

% A/ i/ ?' ^" q6 i: k. w
串口发送

. x1 H# g  ^; b( c- q
在配置串口的各个参数时,可以选择发送数据帧的数据位的大小,可选8位或9位。
9 ^% [) }5 |- s4 w
串口发送数据实际上就是对发送数据寄存器TDR进行写操作。
! |3 R2 K0 V! J3 ^9 o
1. 当串口发送数据时,会检测发送移位寄存器是不是有数据正在移位,如果没有移位,那么这个数据就会立刻转移到发送移位寄存器里。准备发送。

1 B! {- S6 D2 R, k2 |
2. 当数据移动到移位寄存器时,会产生一个TXE发送寄存器空标志位,该位描述如下。当TXE被置1,那么就可以在TDR写入下一个数据了。即发送下一个数据。

4 x& `- R2 n" w! I; K& K
微信图片_20230810144703_1.png
) }  s8 {2 A7 H4 ?& S$ G% ?

1 y) g+ O/ s# c! W5 T1 o+ T5 G
3. 发送移位寄存器在发送器控制的控制下,向右移位,一位一位的把数据传输到TX引脚。

& A1 R, R% |8 A; q9 A
微信图片_20230810144703.png . v8 T% l' q, W% h) }6 M

; y1 e. `/ A) d# _0 H
4. 数据移位完成后,新的数据就会再次从TDR转移到发送移位寄存器里来,依次重复1-3的过程。通过读取TXE标志位来判断是否发送下一个数据。
1 C7 e& f1 k: i" O( i# H
串口接收

' V  G& F' c$ i; _( a- V- N
数据从RX引脚通向接收移位寄存器,在接收控制的控制下,一位一位的读取RX的电平,把第一位放在最高位,然后右移,移位八次之后就可以接收一个字节了。
7 q  j3 |+ i* M8 h; \) X/ L4 {2 V+ Z: m
当一个字节数据移位完成之后,这一个字节的数据就会整体的移到接收数据寄存器RDR里来。
, l" r- S6 k* P
在转移时会置RXNE接收标志位,即RDR寄存器非空,下方为该位的描述。当被置1后,就说明数据可以被读出。

/ h' f4 m) u4 ~$ t! g9 A
微信图片_20230810144704_3.png 8 I. ^; C/ a, v4 M( h6 G$ Q
; S8 [9 U0 D1 G$ l& d5 \! H9 \* r
下图即为串口接收的工作流程
9 o% S* G: B1 |% e- Q5 J  Y/ t
微信图片_20230810144704_2.png
  {+ s- \; X' K3 P9 l  S8 M3 l( S/ w: S7 _
, \! n; d3 T! F. a5 R/ N2 Y& K! v6 e
三、串口在STM32中的配置
# q2 n, n4 s$ O
首先要明确几点:使用STM32串口外设中的哪一个?串口发送或者接收数据?串口相关的参数配置?发送或接收是否使用到中断?
4 d. `9 b1 Y4 M& y; Z1 {- d4 w
下方为串口发送的配置。
7 X+ M9 M2 k# ]& k2 C* Z$ m/ m
1. RCC开启USART、串口TX/RX所对应的GPIO口
  1. RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);  //开启USART2的时钟0 W: B. @( j# u/ a
  2. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);   //开启GPIOA的时钟
复制代码

. n2 d  @% N* a- G
2. 初始化GPIO口
  V# w9 J/ s- C( K; [" W
这里注意哈,根据自己的需求来配置GPIO口,发送和接收是都需要还是只需要其中一个。然后对应的根据引脚定义表来初始化对应的GPIO口。
- w  [" Q2 ~9 X
USART2对应的引脚

# M$ u6 ?3 y0 F1 {( a" [! v
微信图片_20230810144704_1.png * V- {& V* |& [$ N

# P+ ?( l2 h- |  W% v
USART1对应的引脚
4 K. V( X) [. y1 U1 T8 n" t
微信图片_20230810144704.png
1 {* ]! l6 T% q: l

" G2 O- q4 X' [3 Q" x' P- Z
这里根据手册来看,RX引脚模式配置成浮空输入或者上拉输入。TX引脚模式配置成复用推挽输出。
9 m9 G% b! e8 |. D3 I- x
**比如我这里只初始化TX发送端**
  1. //TX端
    # c+ ]6 d: z: R$ J: i  R( Y/ k, N
  2.     GPIO_InitTypeDef GPIO_InitStructure;
    # z* l( c) e/ W3 S
  3.     GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;  //复用推挽输出
    7 K/ [+ p6 D6 B8 a& u
  4.     GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2;   //USART2对应的TX端为GPIOA2
    3 f, L; @4 I9 G, w3 t
  5.     GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;  //50MhZ) T4 ]" f% O% [4 J, R0 _
  6.     GPIO_Init(GPIOA,&GPIO_InitStructure);
复制代码
% I( C0 f% Z  }
3. 串口初始化
% V( q& ?. ]: j( x3 {- @0 X
注意哈,USART_Init()这个函数,是用来配置串口的相关参数的。
8 J7 a. K7 N, ]
  • USART_BaudRate 串口通信使用的波特率 一般是9600或者是115200,这里我们给9600
      o# S8 v4 b- s1 |, O6 C! K
  • USART_HardwareFlowControl 是否选择硬件流触发,一般这个我们也不选,所以选择无硬件流触发。
    ) S# }- n9 J$ \/ |
  • USART_Mode 这个参数要注意了哈,串口的模式,发送模式还是接收模式,还是两者都有
    , v) h3 A- A( y$ I
  • USART_Parity 校验位,可以选择奇偶校验和不校验。没有需求就直接无校验
    ) t' u$ M: }! C
  • USART_StopBits 停止位 有1、0.5、2位,我们这里选1位停止位
    6 ?* N$ w  S2 B* w7 b
  • USART_WordLength 数据位 有8位和9位可以选择
    1. //串口初始化# e- c4 y2 f( W3 e: b
    2.   USART_InitTypeDef USART_InitStruct;
      0 [- E9 [6 l/ X2 K/ T! @7 c( j# ?
    3.   USART_StructInit(&USART_InitStruct);  //初始默认值4 A, f& D8 ^% [& k+ z" C
    4.   USART_InitStruct.USART_BaudRate=9600;
      5 f* w6 z, x& Q9 ]
    5.   USART_InitStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None;   //不使用硬件流触发& K5 ^$ W* M& |
    6.   USART_InitStruct.USART_Mode=USART_Mode_Tx;         //TX 发送模式
      $ {9 X6 t1 ^/ K& S5 m% W
    7.   USART_InitStruct.USART_Parity=USART_Parity_No;       //不选择校验, {# y' b8 p, G! T! b
    8.   USART_InitStruct.USART_StopBits=USART_StopBits_1;      //停止位1位2 V2 s) i7 H3 e$ T
    9.   USART_InitStruct.USART_WordLength=USART_WordLength_8b;  //数据位8位
      8 ?) l+ t7 q4 ^7 d4 _  L# j
    10.   USART_Init(USART2,&USART_InitStruct);
    复制代码
    : D/ l+ m2 I9 [% S( G, |0 r$ |) `
6 Q. Z; B! f! [9 [% O8 f2 a
4. 串口使能
  1. //串口使能
    " ~$ b5 Z4 ~$ _7 c
  2.    USART_Cmd(USART2,ENABLE);
复制代码

7 Q5 u$ a' k! M8 K7 U2 Z
5. 串口发送数据
; r! N1 t4 U7 n: y( H
注意哈,我们要判断TXE标志位的状态。0,数据还没有被转移到移位寄存器;1,数据已经被转移到移位寄存器。
( R( w. q" C* }5 X" p& `5 _
当TXE标志位为1时,就说明可以发送下一个数据了。详细过程可看上面串口发送的解释。
  1. void Serial_SendByte(uint16_t Byte)$ x; }" B) K! P/ m. m
  2. {; W- J+ y& K' V, j
  3.   USART_SendData(USART2,Byte);, @8 e  s# I6 L' {  J  a
  4.   //0 表示数据还未转移到移位寄存器 循环等待 1 数据已经被转移到了移位寄存器可以发送数据
    3 N  D9 o6 h. Y2 ^* X' @
  5.   while(USART_GetFlagStatus(USART2,USART_FLAG_TXE)==RESET);  //不需要手动清零 再次写入TDR时会自动清零  3 g) }0 {5 d# ^' ^6 T
  6. }
复制代码
1 u. _0 Y' C2 D! W
经过上述五步的配置,单片机就可以通过串口发送数据了。

  s' |& i" n$ F  s
下方为发送数据图例,STM32向串口发送0x16数据

1 X6 H% u* U6 C. H3 G
微信图片_20230810144705_3.png
4 T$ C! s$ m: J: V) _/ ^

& u  S$ P; Y6 s1 ~& q; Q
四、串口接收的两种实现方式

& f6 s- Y6 W2 P) L
上方是发送数据的例子,那么串口接收又该如何配置,又要在串口发送的例子上做哪些更改呢?

# e& a. G$ a2 a" I* B; }# X% T
这里我们可以通过查询或者中断的方式来进行接收数据的两种方式。
- V0 j( q0 L' z2 l! D
  • 查询方式就是通过不断的查询RXNE标志位,通过判断RXNE位的状态来确定数据是否接收。
    , d6 b# P4 d4 E& @* ~
  • 中断方式就是通过配置接收输出控制通道,配置NVIC,在中断服务子函数里进行数据的接收。

    2 \8 b. |6 k1 z+ J6 m9 H% {
( t% o0 E! N. X. @- A' H- l
1. 需要更改的地方
3 R4 X% L/ e% B3 G, [' ~% i
; |# Q. K1 v5 i
既然我们要实现串口的接收,那么就要配置串口RX引脚,在串口模式中添加USART_Mode_RX模式。
# ^" I; d# n: d2 p- I
  • 初始化RX引脚
    1. //RX端
      # r$ K/ T1 e. n( c
    2.   GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;  //上拉输入
      ( N2 g/ E6 O+ B8 c; I
    3.     GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3;
      # L/ `: n8 v- n0 t+ s% H
    4.     GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;  //50MhZ/ Q; [- V) ~( p/ \
    5.     GPIO_Init(GPIOA,&GPIO_InitStructure);
    复制代码
    , g, {/ v* L: `$ T1 R; H
) q% A8 w( C+ N" \: m4 x
  • 添加串口模式
    1. USART_InitStruct.USART_Mode=USART_Mode_Tx|USART_Mode_Rx;         //TX 发送模式  RX 接收模式
    复制代码
    7 x- R6 u4 B8 g' r

. j& Y% `' W8 ?
2. 查询RXNE标志位

, L. j* e; P/ k. m4 @8 @
这里我们还是来看一看RXNE标志位的描述
" W& L  z7 ^! B! Z0 n
微信图片_20230810144705_2.png
( F1 C8 T& p4 r! W7 k7 K$ c

  i! J. K" u: E9 b
上图描述,为0时数据没有收到,为1时收到了数据,数据可以从RDR里读出

& o% Y- J' |- A5 `
所以在主程序里不断读取RXNE标志位,如果为1,表示数据可以读出
  1. uint8_t RX_Data;
    4 N- Y9 J5 u/ U' d
  2. int main(); r. Z1 P/ J8 m) J
  3. {
    + n: A+ l7 O. A7 L4 ]: G9 @
  4.     Serial_Init();
    9 |* S& ]0 s! e- ?, c, c% c* q
  5.     Serial_SendByte(0x16);0 k  U  G' ?! _3 {. \8 {5 _
  6.     while(1)2 @9 U" c8 X/ N/ t$ D% _3 o9 i
  7.     {
    3 d. Y! a( K, ^/ `
  8.         if(USART_GetFlagStatus(USART2,USART_FLAG_RXNE)==SET)   //0 循环等待 1 可以接收数据
    $ R1 w+ c* j9 n- y
  9.         {
    2 V4 R$ V9 \8 g# s- a, P
  10.             RX_Data=USART_ReceiveData(USART2);           % R# G2 w. E  `/ J5 c
  11.       Serial_SendByte(RX_Data);
    + E2 W/ v: I" N! I( D+ ]! {- @8 d- y  M
  12.         }* f# b0 P. v7 C! n# t
  13.     }
    8 k! w9 @" @. w+ V. p/ @$ q+ Z! F
  14. }
复制代码

8 @' t2 O+ t9 r
下图为程序现象:pc向单片机发送数据0x15,单片机接收数据0x15,并且把接收到的数据作为数据发送到pc,在pc上显示0x15。
, R$ V% C! p+ e% `+ M$ |3 g) }  g
微信图片_20230810144705_1.png 8 o: U3 ~! n; w1 n0 a
5 S  \7 [- p' l) ?! v- g
3. 使用中断

: R3 q1 i# z) Y' u3 |
  • 通过配置串口的接收作为中断源,开启中断输出控制,配置NVIC。开启中断通道。
    ) t0 T% ~5 L6 E  R' v
  1. //开启中断输出控制
    9 d7 ]- K" J7 P
  2.   USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);, T4 a: X1 ?8 A+ t0 D9 k; L
  3.   : i, U7 R4 E* p2 s$ v
  4.   //配置NVIC/ V  S% Z) m' }  x
  5.   NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    9 P# Z* O+ W: A+ J' E9 z
  6.   NVIC_InitTypeDef NVIC_InitStruct;
    * F5 l/ P5 c! s3 ~1 v5 {$ ^
  7.   NVIC_InitStruct.NVIC_IRQChannel=USART2_IRQn;   //选择USART2的中断通道
    : J) y! p# l# L$ a5 i) c( ~0 p1 D3 H
  8.   NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;    //中断使能
    ( d1 M* z$ Q5 a% L
  9.   NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=1;
    4 E' r7 ]2 m3 l. V- ]
  10.   NVIC_InitStruct.NVIC_IRQChannelSubPriority=1;
    : z: W3 T5 r" G/ z! f
  11.   NVIC_Init(&NVIC_InitStruct);
复制代码

- Z, J# M4 F" c% ]
  • 中断服务子函数
    $ C9 T9 j* H5 P9 T1 a

5 m: a: o+ K3 h6 l: z中断服务子函数写好后,就可以在中断里读取接收到的数据了。

' Y, A9 O/ W( ^" I1 p; ~3 M/ |当接收到数据后,触发接收中断,主程序暂停执行。接收完数据后主程序回复执行。当接收到数据时,就触发中断。
  1. void USART2_IRQHandler(void)
    % V# l' K6 W6 r* v( }3 [9 X
  2. {
    + a0 W% C+ G' [7 Q: ^
  3.   if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET)   //RXNE 标志位为1 表示可以接收数据
    1 _; A+ M' Y- a0 z/ ^
  4.   {
      b' g3 f+ `$ i; p) u" c% @  \
  5.     RX_Data=USART_ReceiveData(USART2);+ H$ O6 C1 W6 e. b) K
  6.     Flag=1;
    " l% n, |# U3 t1 }
  7.     USART_ClearITPendingBit(USART2,USART_IT_RXNE);  //清除RXNE标志位" u9 Z& `* ^1 F' E
  8.   }, p4 G& E8 a  ^) @9 F! X$ p0 E* ^
  9. }
复制代码
8 K- p7 A6 c1 O; f% P
  • 主程序测试

    " o- M. C3 P- _$ q
  1. uint8_t RX_Data;& {# x6 f) Z/ S: [
  2. uint8_t Flag;6 ^9 q% ^) F$ N8 O3 t  @& P
  3. ( D1 x  g5 d4 ^% \; O' K- g
  4. int main()( Q; V0 U9 E- R; u/ Z
  5. {( O! h% K) Y/ x" f
  6.     Serial_Init();1 `' Q7 u6 O) H+ c# Y0 k3 a
  7.     Serial_SendByte(0x16);
    % J% M) r. B! _: R+ X8 h/ }5 M3 n

  8. 3 d9 ]9 a9 w1 I% h8 ~
  9. 6 R1 W' u: h# X7 f# R, p) P4 _6 O
  10.     while(1)
    # i4 v; f% E$ L( F1 B" W
  11.     {2 h  W  X# o' n3 r& [
  12.         if(Flag==1)8 ?6 t* X) j& p+ r
  13.         {
    ) Y; b7 m& d4 u7 Z- D
  14.             Serial_SendByte(RX_Data);6 Z- u: G9 t3 S! D. z7 ?
  15.         }
      }, a5 h; G/ \
  16.     }
    8 D/ v8 N4 Z! H! Q2 V
  17. }
    & J% I* k7 g9 F2 V7 {" ?* t- u( e

  18. ; Q3 n( _7 X  K$ J, ^+ e! R% p2 p6 J
  19. void USART2_IRQHandler(void)
    ) {! V8 s" c) j: |* ]
  20. {
    1 t/ W8 \4 L# ^: C. S7 G( t$ b! q/ O1 U
  21.     if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET)   //RXNE 标志位为1 表示可以接收数据
    + k5 d  g, A; Y5 S
  22.     {
    + K* r0 G- H1 e
  23.         RX_Data=USART_ReceiveData(USART2);
    4 X/ v2 _" K3 w0 N+ j1 s1 W  A
  24.         Flag=1;+ l  V2 Y( z& |; D( c3 o
  25.         USART_ClearITPendingBit(USART2,USART_IT_RXNE);  //清除RXNE标志位
    : d1 I  `* M$ J1 x+ L
  26.     }
    5 K2 b6 T5 Q/ O6 _& e+ y+ r. y
  27. }
复制代码

6 a( w9 E  @/ c( G2 B) f. M

: S% J9 Z0 m) [$ v! b) q, }: q3 ^
下图为程序现象:可以看到,串口确实收到了数据,只是我把接收到的数据0xFE放在了while循环里,这说明数据接收是成功的,使用中断是可行的。

: `& v) Q' K7 U) ^7 \+ g1 ?
微信图片_20230810144705.png
) V7 L& x. `% r. W7 a/ @8 v1 X; {

' z8 P2 l6 K5 Z; \! |$ ^
* q6 i1 X* w2 b& ]1 U0 v/ N
总结

# Q- E0 d) D+ t
到这里,就大致总结了串口的发送和接收。

: ~) n2 ]6 B2 v
串口的配置,使用查询或者中断来接收数据。
4 S' ?3 _9 s9 t
串口的使用会很常用到,所以在这里对串口做一个总结,也算是对之前知识的一个回顾和总结,加强印象。
$ E7 z; E- `8 d
转载自: 古月居
如有侵权请联系删除

- h8 d6 u  _- ~2 q* B

0 c2 X, ~5 t* }' z9 S
收藏 评论0 发布时间:2023-8-10 14:48

举报

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