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

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

[复制链接]
攻城狮Melo 发布时间:2023-8-10 14:48
STM32的串口通信
3 H0 {2 f* y( h% L5 a" r
本文在于记录自己的学习过程中遇到的问题和总结,各种情况下串口通信在STM32的实际使用方面占有很大的比重,本文主要对串口通信做一个简要的总结。

/ d# B% y3 t% K7 q0 v

! W0 h( J" S: ^1 m; N8 y
一、STM32里的串口通信
* k3 d5 |: V- J, m
在STM32里,串口通信是USART,STM32可以通过串口和其他设备进行传输并行数据,是全双工,异步时钟控制,设备之间是点对点的传输。
8 n6 u1 u# o  \( s% c3 M4 r6 b
对应的STM32引脚分别是RX和TX端。STM32的串口资源有USART1、USART2、USART3.
/ O6 \/ J2 F# g" h' D' j! D
串口的几个重要的参数:

5 p* j5 N$ L/ b6 }
  • 波特率,串口通信的速率

    ! A" K& r; j8 I% M5 p: c9 E$ L! n
  • 空闲,一般为高电平
    ( {6 a) P9 I# x7 a: ~
  • 起始位,标志一个数据帧的开始,固定为低电平。当数据开始发送时,产生一个下降沿。(空闲–>起始位)

    3 V4 Q, B4 q1 `
  • 数据位,发送数据帧,1为高电平,0为低电平。低位先行。
      e1 j4 |  _6 `5 T
  • 比如 发送数据帧0x0F 在数据帧里就是低位线性 即 1111 0000

    2 s' v% g) {( @% T& r
  • 校验位,用于数据验证,根据数据位的计算得来。有奇校验,偶校验和无校验。

    ' f, S) V1 s, {2 W
  • 停止位,用于数据的间隔,固定为高电平。数据帧发送完成后,产生一个上升沿。(数据传输–>停止位)
    2 e7 p0 Z* F8 T

8 `! y  e2 t, I) ?. `% H
下方就是一个字节数据的传输过程,从图中可以看出,串口发送的数据一般都是以数据帧的形式进行传输,每个数据帧都由起始位,数据位,停止位组成, 且停止位可变。

$ M  B' |8 I6 j  h& s3 n- z0 I' f
微信图片_20230810144703_3.png
& a4 p- K1 ]  U& C7 P/ C' B! }9 ~
4 w/ n, V8 p* {% {
8 n; g+ z; N! P3 i/ F
二、串口的发送和接收
0 i1 t3 M3 d+ P8 M
USART是STM32内部集成的硬件外设,可以根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可以自动接收RX引脚的数据帧时序,拼接成一个字节数据,存放在数据寄存器里。

5 r* Z0 h' l& ]5 q0 h# H
当配置好USART的电路之后,直接读取数据寄存器,就可以自动发送数据和接收数据了。在发送和接收的模块有4个重要的寄存器
0 V5 v$ I( I5 E7 W# v4 z
  • 发送数据寄存器TDR

    & i  t+ l5 N* l* ]3 E
  • 发送移位寄存器,把一个字节的数据一位一位的移出去
    : U0 ^; s" f2 p1 |5 H
  • 接收数据寄存器RDR
    3 H5 y9 W$ N5 Y( a
  • 接收移位寄存器,把一个字节的数据

    # f1 f5 y* ?( N3 F2 N6 \' e) r! H
$ b& I1 N* b* T6 ~9 v! d  F
下方为串口的发送和接收图解:

5 u' N+ K& ~4 A9 s2 f: m( f
微信图片_20230810144703_2.png % _' A6 E# k) I
) q3 g* X2 a8 L
串口发送
  S5 Y6 M( z$ I/ Z
在配置串口的各个参数时,可以选择发送数据帧的数据位的大小,可选8位或9位。
, T" D; c1 O, Q8 H" z
串口发送数据实际上就是对发送数据寄存器TDR进行写操作。

# {# b* i  {0 |2 z/ m# O% a" r: v
1. 当串口发送数据时,会检测发送移位寄存器是不是有数据正在移位,如果没有移位,那么这个数据就会立刻转移到发送移位寄存器里。准备发送。
% u1 \1 F& }% F# C: a
2. 当数据移动到移位寄存器时,会产生一个TXE发送寄存器空标志位,该位描述如下。当TXE被置1,那么就可以在TDR写入下一个数据了。即发送下一个数据。
. c2 ~; g' p2 Y: p% b, Y
微信图片_20230810144703_1.png % Q& F4 K, k% u8 n5 E
% `8 e$ x1 e' r
3. 发送移位寄存器在发送器控制的控制下,向右移位,一位一位的把数据传输到TX引脚。

. V: A6 }/ g6 ^% t6 j6 D! Q
微信图片_20230810144703.png : B" ^$ z7 p' N1 K, }

* Y( ^) S" m6 H& ?
4. 数据移位完成后,新的数据就会再次从TDR转移到发送移位寄存器里来,依次重复1-3的过程。通过读取TXE标志位来判断是否发送下一个数据。
! z! @) g( O: m! e: q
串口接收

* W( T8 Z7 V% T- j% {
数据从RX引脚通向接收移位寄存器,在接收控制的控制下,一位一位的读取RX的电平,把第一位放在最高位,然后右移,移位八次之后就可以接收一个字节了。

( @2 c! P/ ?, ~$ y
当一个字节数据移位完成之后,这一个字节的数据就会整体的移到接收数据寄存器RDR里来。
" q$ T5 }; G5 D# Y; B
在转移时会置RXNE接收标志位,即RDR寄存器非空,下方为该位的描述。当被置1后,就说明数据可以被读出。
5 }; _' v6 b# }: D
微信图片_20230810144704_3.png
- Q2 p$ [& N) c3 d) p

1 u6 V- v. d: U3 A  n2 D0 X
下图即为串口接收的工作流程
3 Q& x0 ?2 D1 m5 v1 G. _- X1 g- S
微信图片_20230810144704_2.png
( M" c# H9 {5 R  t9 {" q; D' @; j3 D' K  B6 ?* }% @

7 U3 Y6 X1 t# c5 E  U, }
三、串口在STM32中的配置
; K- C5 o1 m' ^' a! H1 p- B
首先要明确几点:使用STM32串口外设中的哪一个?串口发送或者接收数据?串口相关的参数配置?发送或接收是否使用到中断?

5 |6 Y4 e! C& H
下方为串口发送的配置。
4 K2 P% F% @9 B' t" F2 c
1. RCC开启USART、串口TX/RX所对应的GPIO口
  1. RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);  //开启USART2的时钟- X, o: z7 ]; u0 j
  2. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);   //开启GPIOA的时钟
复制代码
/ k7 B% L, y. `. b
2. 初始化GPIO口
- k* J8 }* x0 v) g" N, |% w' B
这里注意哈,根据自己的需求来配置GPIO口,发送和接收是都需要还是只需要其中一个。然后对应的根据引脚定义表来初始化对应的GPIO口。
, X* u8 N" t6 s4 R
USART2对应的引脚

/ P# Z$ x6 k8 s5 q" v/ z
微信图片_20230810144704_1.png
. N. D: [. d2 \# G! g- t
2 U6 s* g0 u3 Z) L
USART1对应的引脚

* `6 l) N+ l$ }- s1 q& I
微信图片_20230810144704.png
9 I% z! A6 n0 R2 c. X9 {- c$ b

$ w7 n9 Q( J' r6 q4 B
这里根据手册来看,RX引脚模式配置成浮空输入或者上拉输入。TX引脚模式配置成复用推挽输出。

  |% }% k8 x, Q3 g; }) l9 D2 w
**比如我这里只初始化TX发送端**
  1. //TX端
    1 N% {0 H$ e$ Q5 ]
  2.     GPIO_InitTypeDef GPIO_InitStructure;$ f* @( q  x& @" h: i3 R
  3.     GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;  //复用推挽输出0 w/ u3 D3 W+ [- D0 ~: X5 b
  4.     GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2;   //USART2对应的TX端为GPIOA2
    ! W' `0 S7 p0 L& _% Y
  5.     GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;  //50MhZ
    / U) \/ y7 a. _4 D% W
  6.     GPIO_Init(GPIOA,&GPIO_InitStructure);
复制代码

/ A( v9 w; p8 W6 s# [
3. 串口初始化

+ _- b: I4 m' u. s3 ]. D# f
注意哈,USART_Init()这个函数,是用来配置串口的相关参数的。
7 d! J( Q, [) M' @2 _
  • USART_BaudRate 串口通信使用的波特率 一般是9600或者是115200,这里我们给9600

    ( x* Q0 n& c/ o9 A; W9 K
  • USART_HardwareFlowControl 是否选择硬件流触发,一般这个我们也不选,所以选择无硬件流触发。
      y, b& y% V7 R2 g" ~! ?- {
  • USART_Mode 这个参数要注意了哈,串口的模式,发送模式还是接收模式,还是两者都有

    ( M3 G2 n1 T$ s9 h0 @4 |, f
  • USART_Parity 校验位,可以选择奇偶校验和不校验。没有需求就直接无校验
    5 N5 B# c; U0 X' M* G, F
  • USART_StopBits 停止位 有1、0.5、2位,我们这里选1位停止位
    + H( a+ W# l5 G
  • USART_WordLength 数据位 有8位和9位可以选择
    1. //串口初始化+ M1 m2 R; T$ R4 ^( w8 Z: q( s! h; h. T
    2.   USART_InitTypeDef USART_InitStruct;7 B8 u8 ~& [  F5 H2 a3 }0 e* }
    3.   USART_StructInit(&USART_InitStruct);  //初始默认值
      : L# u9 f$ @# p2 l; N
    4.   USART_InitStruct.USART_BaudRate=9600;9 V: O+ S8 i/ p2 S
    5.   USART_InitStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None;   //不使用硬件流触发) s& K  L. V: x! m9 [- O  T
    6.   USART_InitStruct.USART_Mode=USART_Mode_Tx;         //TX 发送模式
        F3 @5 A9 P# K- l; [, \, C4 ^
    7.   USART_InitStruct.USART_Parity=USART_Parity_No;       //不选择校验) f* `0 R! c! c0 o# O
    8.   USART_InitStruct.USART_StopBits=USART_StopBits_1;      //停止位1位* z& [* F9 ]$ |% A. B* d% R4 o
    9.   USART_InitStruct.USART_WordLength=USART_WordLength_8b;  //数据位8位
      - ~" T6 U$ c8 P6 |
    10.   USART_Init(USART2,&USART_InitStruct);
    复制代码

    * v& o  s7 p+ |) r7 O# ^

  w5 H  |$ j5 G8 i$ M
4. 串口使能
  1. //串口使能' t+ @. ^: U7 H3 J- J
  2.    USART_Cmd(USART2,ENABLE);
复制代码

. q, B0 }3 y  V. M" S
5. 串口发送数据

0 E; k& i; T/ T+ V7 p# t  G  _注意哈,我们要判断TXE标志位的状态。0,数据还没有被转移到移位寄存器;1,数据已经被转移到移位寄存器。
! u" Q! w- r4 ?( X6 w- b1 `: k  l
当TXE标志位为1时,就说明可以发送下一个数据了。详细过程可看上面串口发送的解释。
  1. void Serial_SendByte(uint16_t Byte)
    , q9 Z+ b: {- m; _" S
  2. {
    " Q+ E' r; f7 ?" p- `4 w8 E
  3.   USART_SendData(USART2,Byte);6 @7 R7 D# Q8 t8 @% |$ v6 \( P
  4.   //0 表示数据还未转移到移位寄存器 循环等待 1 数据已经被转移到了移位寄存器可以发送数据0 P5 H* B8 x; p+ S
  5.   while(USART_GetFlagStatus(USART2,USART_FLAG_TXE)==RESET);  //不需要手动清零 再次写入TDR时会自动清零  0 B9 P7 @, h& t& K# K4 M/ b
  6. }
复制代码

& G- O' b% N7 g) h: Q( ~
经过上述五步的配置,单片机就可以通过串口发送数据了。
! b" W" ~2 a8 X/ j3 e
下方为发送数据图例,STM32向串口发送0x16数据
6 |8 F9 f5 a2 z, |$ N  @; f7 z& G
微信图片_20230810144705_3.png
0 {4 F, p4 @; W7 C' D; l0 F
7 s7 h0 T# i  x1 I& L/ S7 D  B4 u
四、串口接收的两种实现方式

5 q) W  k: |$ N& k! y+ a. Q
上方是发送数据的例子,那么串口接收又该如何配置,又要在串口发送的例子上做哪些更改呢?
/ _& L) ^1 x0 Q/ m1 ]6 _* Y
这里我们可以通过查询或者中断的方式来进行接收数据的两种方式。

1 G) N7 @; U' Z. i  K. F& X( L" x
  • 查询方式就是通过不断的查询RXNE标志位,通过判断RXNE位的状态来确定数据是否接收。

    3 w' Q. O  M" \5 n3 P
  • 中断方式就是通过配置接收输出控制通道,配置NVIC,在中断服务子函数里进行数据的接收。

    . S  f, l& P. Q& s: l' {
# l! e1 Z. g$ U  @! t: V3 l9 N
1. 需要更改的地方6 G# m& F( K) Z0 W8 P8 C+ z
  I3 ~5 h% a/ _
既然我们要实现串口的接收,那么就要配置串口RX引脚,在串口模式中添加USART_Mode_RX模式。
/ Z' M9 L3 V8 G+ m
  • 初始化RX引脚
    1. //RX端
      . t2 u! A8 ~7 ^! l$ z# B2 f, e/ r* f
    2.   GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;  //上拉输入
      4 f2 ?  U5 `  ?
    3.     GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3;9 j" k- ?8 X; J5 z4 E: ~
    4.     GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;  //50MhZ7 N4 H9 L: f# l: u3 X1 c0 x
    5.     GPIO_Init(GPIOA,&GPIO_InitStructure);
    复制代码

    / t8 N1 ^1 M2 j/ U7 F1 F" f
% p9 j/ ?7 }# [. g! g
  • 添加串口模式
    1. USART_InitStruct.USART_Mode=USART_Mode_Tx|USART_Mode_Rx;         //TX 发送模式  RX 接收模式
    复制代码
    6 r- _" {6 y9 t
0 K. B: n. ^* ^( k" y7 Z' \- q
2. 查询RXNE标志位

* B! W$ ^( W  q, G. y8 O% F/ U
这里我们还是来看一看RXNE标志位的描述
5 X* W8 k. f. ~5 {
微信图片_20230810144705_2.png
- E4 E+ m+ O1 U  b4 |6 H

! D% ?1 ?' K. h3 Z, i& M! ~
上图描述,为0时数据没有收到,为1时收到了数据,数据可以从RDR里读出

0 `+ X- J+ m3 A6 _/ N
所以在主程序里不断读取RXNE标志位,如果为1,表示数据可以读出
  1. uint8_t RX_Data;
    5 g1 R, t& y# [
  2. int main()
    4 Y0 o8 |- h- e0 L1 W
  3. { % B9 G, S. \+ S
  4.     Serial_Init();
    7 Y9 q( h: }6 [& l, ?( T" h- a0 ?3 k) G
  5.     Serial_SendByte(0x16);
    8 R+ b( W6 L# G- O
  6.     while(1)+ c) D# m. p5 B+ j" h
  7.     {
    : Z* O6 f2 f5 i( C* L
  8.         if(USART_GetFlagStatus(USART2,USART_FLAG_RXNE)==SET)   //0 循环等待 1 可以接收数据0 W. |; T- I# Q
  9.         {
      L0 `% N5 ?$ z6 e# u
  10.             RX_Data=USART_ReceiveData(USART2);           6 h7 E- ?) x' H* w: r& w
  11.       Serial_SendByte(RX_Data);& G; V' n' J3 a' o' B& E
  12.         }
    9 q5 w( F' U. D( G1 l
  13.     }
    3 L3 ^+ Y1 v5 t  e
  14. }
复制代码

2 q) u4 Y1 P% f  e6 h  W- T8 p
下图为程序现象:pc向单片机发送数据0x15,单片机接收数据0x15,并且把接收到的数据作为数据发送到pc,在pc上显示0x15。

/ h; ?! a* a7 G, d$ r# y
微信图片_20230810144705_1.png
1 p. c4 M1 \: G" K; }0 G
  D! k7 E3 m( I7 \& d$ U9 p
3. 使用中断
' H1 g2 K7 X1 I
  • 通过配置串口的接收作为中断源,开启中断输出控制,配置NVIC。开启中断通道。
    5 q" z$ X6 a3 R, n7 F# `9 l! i5 F
  1. //开启中断输出控制$ `2 ]& V: ^! v
  2.   USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);& o8 D' T" H- i- j7 a  D3 j# M3 f
  3.   
    ( `6 M4 D  C" R$ [/ \- ^6 B- C
  4.   //配置NVIC
    9 H+ l/ c! P1 V5 t* i* K3 a7 i
  5.   NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    5 L2 T8 [# E' `( w  Z1 g
  6.   NVIC_InitTypeDef NVIC_InitStruct;2 e5 q1 A6 B' ?; ~& q- A$ t' E
  7.   NVIC_InitStruct.NVIC_IRQChannel=USART2_IRQn;   //选择USART2的中断通道
    9 r9 Z7 S' X- C! ~* ?
  8.   NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;    //中断使能
    % z* e8 [# p  V6 I, G0 P) L, w
  9.   NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=1;- n/ m" e" N7 J5 W) E
  10.   NVIC_InitStruct.NVIC_IRQChannelSubPriority=1;- W2 o/ ~4 _' J
  11.   NVIC_Init(&NVIC_InitStruct);
复制代码

0 {5 j4 R. f! a* y- S
  • 中断服务子函数
    0 X2 M3 d+ z% h5 C" l/ j6 h1 z

. J4 ^9 `4 ?5 n1 x, |  X# D, b中断服务子函数写好后,就可以在中断里读取接收到的数据了。

' p1 J* h  l/ X6 d当接收到数据后,触发接收中断,主程序暂停执行。接收完数据后主程序回复执行。当接收到数据时,就触发中断。
  1. void USART2_IRQHandler(void)
    & v/ q7 T: K5 y! F, @- \
  2. {/ q* H& C/ Z+ ~6 Y
  3.   if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET)   //RXNE 标志位为1 表示可以接收数据( I3 ^, R$ o& m8 B  U
  4.   {+ l9 |1 V, Q4 Q1 r7 X
  5.     RX_Data=USART_ReceiveData(USART2);, m  k0 i* D9 Y: Y1 R
  6.     Flag=1;
    & q( H4 s4 m# W( n3 d
  7.     USART_ClearITPendingBit(USART2,USART_IT_RXNE);  //清除RXNE标志位: h$ B( v  L4 R) x4 Y
  8.   }
    + ^$ e% Y' B, q
  9. }
复制代码
" _" r7 x  K$ z" K
  • 主程序测试

    % Z; W: v2 Y) y0 K) H
  1. uint8_t RX_Data;
    2 f( `0 m- \/ ^  v$ r+ _2 R, a
  2. uint8_t Flag;9 o9 I5 ~* Z1 u  v7 {; V0 Z+ C5 ^! {
  3. , F4 A: T+ S6 H, P
  4. int main()
    ' ~8 }3 \5 o' [. `
  5. {4 x( C$ B" Z, t8 [$ J( C' g/ s) g
  6.     Serial_Init();
    $ h, h/ E) q+ n! t" g. F# M
  7.     Serial_SendByte(0x16);
    3 {- q! N/ m' r2 o

  8. 4 _& Q$ e, t( }9 J$ K1 J  n

  9. + p5 O# a" C* N* Q
  10.     while(1). p& m' y6 m( ]/ W# V2 O
  11.     {4 R" f( |) J) y/ b* n0 ?% _
  12.         if(Flag==1)
    . ~4 Y, Q: v. [/ |, g
  13.         {5 e% L" y. J, ^! N1 a7 |9 N
  14.             Serial_SendByte(RX_Data);0 x3 t% Q9 K* Z
  15.         }2 E% ~+ A) u. V+ h
  16.     }: D' n  F! n- X& n2 w) v2 k9 s
  17. }: `1 O4 ^: ]6 @) I

  18.   I/ T' f, _5 |& E9 Q2 b  `# E
  19. void USART2_IRQHandler(void)
    + E- l) F1 r& h# l3 R) L
  20. {$ c( c1 u# R# @
  21.     if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET)   //RXNE 标志位为1 表示可以接收数据% q; }2 C2 m( M" J; e3 X
  22.     {
    # {1 M: ^3 C- N/ ]* N( o  Q1 h1 o
  23.         RX_Data=USART_ReceiveData(USART2);- Q7 }6 A2 o2 M
  24.         Flag=1;6 H7 t% c, k1 O5 ~
  25.         USART_ClearITPendingBit(USART2,USART_IT_RXNE);  //清除RXNE标志位
    ( ?+ L3 |: x; w/ s/ h9 w$ b
  26.     }0 A- |# ^3 S  o: v/ U
  27. }
复制代码
) W0 w* Z# [( R) Y8 @- p
$ u2 A. J. ~8 u5 T  u
下图为程序现象:可以看到,串口确实收到了数据,只是我把接收到的数据0xFE放在了while循环里,这说明数据接收是成功的,使用中断是可行的。
  l7 R' C1 u4 [4 }" i, }/ N- \
微信图片_20230810144705.png
4 T% K8 i; D0 m0 E

. c% F0 S$ \- Z+ e4 V; m) C

+ q4 U* c# x& t( @/ Z0 D8 r
总结

: R$ v' ~) ~( C
到这里,就大致总结了串口的发送和接收。

% F, N' x; o7 @, A  D6 N) {1 q2 t
串口的配置,使用查询或者中断来接收数据。
& W' g4 O4 M7 D9 z: e/ T; V: K
串口的使用会很常用到,所以在这里对串口做一个总结,也算是对之前知识的一个回顾和总结,加强印象。
% ]) r. p# Z. V/ v4 E  r$ E
转载自: 古月居
如有侵权请联系删除
8 J- u$ G. V% U/ a& {. Z2 ^

, j% S' n& L0 p& v( P& |
收藏 评论0 发布时间:2023-8-10 14:48

举报

0个回答

所属标签

相似技术帖

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