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

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

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

+ A7 `5 f4 [$ t
本文在于记录自己的学习过程中遇到的问题和总结,各种情况下串口通信在STM32的实际使用方面占有很大的比重,本文主要对串口通信做一个简要的总结。

. c; {* O. E- B
, Y1 K# m/ a; [) q8 H* g
一、STM32里的串口通信
; q- f- v6 c9 z$ A0 b7 n6 W2 n( ^
在STM32里,串口通信是USART,STM32可以通过串口和其他设备进行传输并行数据,是全双工,异步时钟控制,设备之间是点对点的传输。

. W: @3 j5 R9 v( q: X. Q4 x2 T
对应的STM32引脚分别是RX和TX端。STM32的串口资源有USART1、USART2、USART3.
- K4 y- V9 ~: v' \5 x! T
串口的几个重要的参数:

8 @0 x9 J: U; ]) w- p
  • 波特率,串口通信的速率

    3 M4 M; X( T' h! ?- S7 l1 U  e
  • 空闲,一般为高电平

    . T7 p5 _7 J3 w  X: @' L# }+ w
  • 起始位,标志一个数据帧的开始,固定为低电平。当数据开始发送时,产生一个下降沿。(空闲–>起始位)
    " ~" D* K( H: c/ t! ~* T* y' ]
  • 数据位,发送数据帧,1为高电平,0为低电平。低位先行。
    ! ~: _! S8 {) k( N, D+ d
  • 比如 发送数据帧0x0F 在数据帧里就是低位线性 即 1111 0000
    7 m$ B8 W5 G2 ]( q5 E! g0 f( e% A
  • 校验位,用于数据验证,根据数据位的计算得来。有奇校验,偶校验和无校验。
    2 f4 i6 P3 `8 l2 |, d9 T% g9 X1 U
  • 停止位,用于数据的间隔,固定为高电平。数据帧发送完成后,产生一个上升沿。(数据传输–>停止位)
    9 ~  x+ C% e' |. f$ ~
& |0 ~# \  [5 ^
下方就是一个字节数据的传输过程,从图中可以看出,串口发送的数据一般都是以数据帧的形式进行传输,每个数据帧都由起始位,数据位,停止位组成, 且停止位可变。

' M' {0 T/ x* N' f1 q3 h
微信图片_20230810144703_3.png
4 b- S9 a% ~* I8 Y1 Y3 l) `. \( }4 s4 Z) R0 s9 c, F
* k0 N1 q2 H1 J% O0 n/ }; j* d8 ]
二、串口的发送和接收
1 ?! b" \( E  o  A/ B% o" n
USART是STM32内部集成的硬件外设,可以根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可以自动接收RX引脚的数据帧时序,拼接成一个字节数据,存放在数据寄存器里。

0 ?% p+ L' I( v* g( [
当配置好USART的电路之后,直接读取数据寄存器,就可以自动发送数据和接收数据了。在发送和接收的模块有4个重要的寄存器
# G- d$ T0 ^! |! U' J
  • 发送数据寄存器TDR
    0 l$ _9 L- }7 G* i
  • 发送移位寄存器,把一个字节的数据一位一位的移出去

    ; _* ^5 U9 G3 s$ k7 A$ o! Q- s
  • 接收数据寄存器RDR
    / K! `; p- Q6 d. r/ G
  • 接收移位寄存器,把一个字节的数据
    ; C: f3 }3 V. }, P- {' N, }% M7 T: i: e
5 O8 T1 `: A. h, ^' G8 m" Z5 A4 u" t
下方为串口的发送和接收图解:

2 C3 r+ k# D% e5 [2 P# E/ q
微信图片_20230810144703_2.png
+ v5 I  q7 G" E& d" B
( N# V7 Z3 ^( H4 x7 @$ f
串口发送
2 m1 Z( F% D+ I
在配置串口的各个参数时,可以选择发送数据帧的数据位的大小,可选8位或9位。

4 C( w/ ~: o4 `5 R
串口发送数据实际上就是对发送数据寄存器TDR进行写操作。
6 o. U) r, A* T; J: g2 `8 l+ Q; R
1. 当串口发送数据时,会检测发送移位寄存器是不是有数据正在移位,如果没有移位,那么这个数据就会立刻转移到发送移位寄存器里。准备发送。
5 ^1 ^' W' F' ~, j% ~
2. 当数据移动到移位寄存器时,会产生一个TXE发送寄存器空标志位,该位描述如下。当TXE被置1,那么就可以在TDR写入下一个数据了。即发送下一个数据。
3 ^! v# i8 ]9 Y
微信图片_20230810144703_1.png
, a' c5 X; b# d6 |4 i% G3 b% ]6 S
: d* y4 G3 }  C2 b
3. 发送移位寄存器在发送器控制的控制下,向右移位,一位一位的把数据传输到TX引脚。
. V8 Y0 z5 y2 y' b$ ~
微信图片_20230810144703.png 9 N! d1 V, x& Y! `* y* Y7 l3 G$ ^

$ r" i) b$ [8 \; Z6 A4 }1 a7 g
4. 数据移位完成后,新的数据就会再次从TDR转移到发送移位寄存器里来,依次重复1-3的过程。通过读取TXE标志位来判断是否发送下一个数据。

: l& b  f9 I2 h% x0 P6 @: R
串口接收

" Z" W1 z" J# V
数据从RX引脚通向接收移位寄存器,在接收控制的控制下,一位一位的读取RX的电平,把第一位放在最高位,然后右移,移位八次之后就可以接收一个字节了。

& ]: T$ l2 ~' z/ T# u" ?
当一个字节数据移位完成之后,这一个字节的数据就会整体的移到接收数据寄存器RDR里来。

8 B+ \4 H" l3 |# Z( d
在转移时会置RXNE接收标志位,即RDR寄存器非空,下方为该位的描述。当被置1后,就说明数据可以被读出。

9 q& U# I" _& J, X! k7 H
微信图片_20230810144704_3.png , r  g8 \" s; _; x1 ?! d

( t) {0 r$ y# j6 A; q
下图即为串口接收的工作流程
  s2 S; p; ?- j; f2 x7 s
微信图片_20230810144704_2.png : N" a0 \/ S$ E) Z2 |5 J  @+ |
7 Z: Q6 X' {# d/ v7 k8 `

/ I5 R2 j& n; S
三、串口在STM32中的配置
5 k. x$ ~  ?  Z, K9 u0 i/ P
首先要明确几点:使用STM32串口外设中的哪一个?串口发送或者接收数据?串口相关的参数配置?发送或接收是否使用到中断?

+ l& r7 h6 D5 k/ G
下方为串口发送的配置。

: r, ~2 g% U+ @# |, P
1. RCC开启USART、串口TX/RX所对应的GPIO口
  1. RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);  //开启USART2的时钟
    ' C" |$ }/ c# B# }0 H
  2. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);   //开启GPIOA的时钟
复制代码

. |- j" T$ l1 l
2. 初始化GPIO口

: o8 e- W; Q8 i
这里注意哈,根据自己的需求来配置GPIO口,发送和接收是都需要还是只需要其中一个。然后对应的根据引脚定义表来初始化对应的GPIO口。

. M) _: I3 T- e% n/ c$ c# `
USART2对应的引脚
. e) l- z$ D: y: U4 w
微信图片_20230810144704_1.png
$ G7 m6 c- L( \; |5 M4 e! N
; x+ {( Y: x6 M7 @, I  u
USART1对应的引脚

9 d# P4 s$ O7 V4 S+ K" f! q3 G
微信图片_20230810144704.png
9 X9 x/ ~) s0 [, k  R" W

" Z; M. u: {& z: w5 ^8 t. v
这里根据手册来看,RX引脚模式配置成浮空输入或者上拉输入。TX引脚模式配置成复用推挽输出。
9 }0 H7 P3 C: L# J
**比如我这里只初始化TX发送端**
  1. //TX端
    6 j$ ?# f8 o$ K. o$ _1 \! X
  2.     GPIO_InitTypeDef GPIO_InitStructure;
    9 @, w  S9 g9 d
  3.     GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;  //复用推挽输出
    8 [2 a- y2 r- {, \" c: ~. t
  4.     GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2;   //USART2对应的TX端为GPIOA2  G) q9 u; R5 [, }( H8 x( i
  5.     GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;  //50MhZ
    $ t: k* P5 B% q* ~9 {: W& f* s/ k
  6.     GPIO_Init(GPIOA,&GPIO_InitStructure);
复制代码
- @' o" [* G. p& [* D$ {# c! H
3. 串口初始化
' |% G( {7 R5 t8 X
注意哈,USART_Init()这个函数,是用来配置串口的相关参数的。
: Z* X: X6 z  A% P
  • USART_BaudRate 串口通信使用的波特率 一般是9600或者是115200,这里我们给9600
    $ A2 ~8 T" f' y
  • USART_HardwareFlowControl 是否选择硬件流触发,一般这个我们也不选,所以选择无硬件流触发。
    * P  Q9 x% j4 V1 O+ }
  • USART_Mode 这个参数要注意了哈,串口的模式,发送模式还是接收模式,还是两者都有
    3 p+ [% r6 _+ z
  • USART_Parity 校验位,可以选择奇偶校验和不校验。没有需求就直接无校验

    ! \" w4 a% J6 q7 X* J
  • USART_StopBits 停止位 有1、0.5、2位,我们这里选1位停止位
    5 [* x) E' n1 J. {% O* f. T
  • USART_WordLength 数据位 有8位和9位可以选择
    1. //串口初始化
      1 @2 D, D' H6 c; ?5 \
    2.   USART_InitTypeDef USART_InitStruct;) x3 n, I9 s2 H" I( F) j# H
    3.   USART_StructInit(&USART_InitStruct);  //初始默认值* `- F2 q4 R& u5 e$ [# G
    4.   USART_InitStruct.USART_BaudRate=9600;
      # X' y4 M: {9 z9 y# h
    5.   USART_InitStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None;   //不使用硬件流触发) g, d- z) o3 N4 k4 S& l" [4 U  X
    6.   USART_InitStruct.USART_Mode=USART_Mode_Tx;         //TX 发送模式% X& t1 t4 m0 L4 ]8 F
    7.   USART_InitStruct.USART_Parity=USART_Parity_No;       //不选择校验. J6 o+ u* Y( u6 p6 E5 R- D" o! K
    8.   USART_InitStruct.USART_StopBits=USART_StopBits_1;      //停止位1位
      7 |4 {9 t6 h$ Q9 P
    9.   USART_InitStruct.USART_WordLength=USART_WordLength_8b;  //数据位8位9 ]) F4 J. ?: m2 v0 N$ K
    10.   USART_Init(USART2,&USART_InitStruct);
    复制代码
    & c- H; K/ I' @* s" v# P2 }, W
! i1 x+ {( Z" t! ]4 Z+ ~; `
4. 串口使能
  1. //串口使能
    7 u  Y1 G* C/ v! t5 {5 V: o/ f
  2.    USART_Cmd(USART2,ENABLE);
复制代码
! N* N3 _2 T+ O) O$ ~7 d
5. 串口发送数据

: a# K0 `5 h+ a: A) n2 p注意哈,我们要判断TXE标志位的状态。0,数据还没有被转移到移位寄存器;1,数据已经被转移到移位寄存器。

; b+ l" ]- X: _& t+ S6 W! V" |
当TXE标志位为1时,就说明可以发送下一个数据了。详细过程可看上面串口发送的解释。
  1. void Serial_SendByte(uint16_t Byte)3 U$ t" N2 s! G  b5 S
  2. {. L# b5 A. X2 Y4 ~9 b  \9 F5 f
  3.   USART_SendData(USART2,Byte);
    2 U( g; E: T( m9 D! R6 L) x
  4.   //0 表示数据还未转移到移位寄存器 循环等待 1 数据已经被转移到了移位寄存器可以发送数据
    4 s" Q7 r2 G, N& ?. @8 s
  5.   while(USART_GetFlagStatus(USART2,USART_FLAG_TXE)==RESET);  //不需要手动清零 再次写入TDR时会自动清零  
    ) M/ J: A3 b  U, A  j1 B* U/ j6 _
  6. }
复制代码

6 Y" ]6 d7 p- m& I
经过上述五步的配置,单片机就可以通过串口发送数据了。
2 S1 ^* C6 `. p. k5 r+ H. |
下方为发送数据图例,STM32向串口发送0x16数据
0 E5 F8 n0 `7 O4 ~  R
微信图片_20230810144705_3.png
3 k1 [$ V: ?: b) N% V& t- e
4 U# l5 Q. x  }6 p$ ^
四、串口接收的两种实现方式
7 ~& i$ \; P3 s3 k. r
上方是发送数据的例子,那么串口接收又该如何配置,又要在串口发送的例子上做哪些更改呢?

2 a- H8 t7 X1 \9 E3 ~+ z# ^
这里我们可以通过查询或者中断的方式来进行接收数据的两种方式。

' R4 j2 C- N) ?0 T5 U
  • 查询方式就是通过不断的查询RXNE标志位,通过判断RXNE位的状态来确定数据是否接收。
    4 W- l8 k, S- ]3 D. }$ R2 `
  • 中断方式就是通过配置接收输出控制通道,配置NVIC,在中断服务子函数里进行数据的接收。
    - h$ u% O; u! v- R8 R

6 W6 V" s8 h5 W& ]  K
1. 需要更改的地方
7 K! H2 T1 L8 B9 s( k
6 U/ K+ n$ C2 }- q  X
既然我们要实现串口的接收,那么就要配置串口RX引脚,在串口模式中添加USART_Mode_RX模式。

( d0 v8 X4 x4 K7 r# t* a
  • 初始化RX引脚
    1. //RX端& ~& H  h0 {4 t( j7 i
    2.   GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;  //上拉输入
      - j  D. m9 o" Q
    3.     GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3;* {) b! ]& E# B  W$ ~" m
    4.     GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;  //50MhZ
      : j' ]2 Q* \$ P* q3 M
    5.     GPIO_Init(GPIOA,&GPIO_InitStructure);
    复制代码
    ; `7 |, I7 w4 c/ c0 c/ Q" f3 i8 D: E
' X# ^6 q' C- r  Y
  • 添加串口模式
    1. USART_InitStruct.USART_Mode=USART_Mode_Tx|USART_Mode_Rx;         //TX 发送模式  RX 接收模式
    复制代码
    5 R1 y1 g: u1 v8 i4 ?, ~

6 V; Y0 B; @% y5 w* |) X
2. 查询RXNE标志位

$ e/ S" C# L- O( {
这里我们还是来看一看RXNE标志位的描述
: b; h. j( \1 A7 N' Q, a
微信图片_20230810144705_2.png
6 b& x; U0 \2 q( M
; v- V) G) }$ O
上图描述,为0时数据没有收到,为1时收到了数据,数据可以从RDR里读出
, R! b* e5 ]6 s" n8 q( X
所以在主程序里不断读取RXNE标志位,如果为1,表示数据可以读出
  1. uint8_t RX_Data;1 I) C. s$ A' k9 K
  2. int main()
    " v0 j3 j4 ^: E% \# S" H/ s
  3. {
    ' @% W- B0 E1 z" y+ n
  4.     Serial_Init();2 H0 u: ~7 Z, Z* g0 S* K
  5.     Serial_SendByte(0x16);, I1 ~1 i5 t6 R5 `- s
  6.     while(1)* k' M9 M3 `; u+ A$ p6 F# l' s6 X& r
  7.     {" ?: F' M! s0 X  z& V( l' x
  8.         if(USART_GetFlagStatus(USART2,USART_FLAG_RXNE)==SET)   //0 循环等待 1 可以接收数据, o  T! F4 e7 N- I1 _4 p
  9.         {$ j: |( ?6 |! V0 R- S6 t7 P9 D
  10.             RX_Data=USART_ReceiveData(USART2);           
    0 Y: h5 m6 f4 E! K
  11.       Serial_SendByte(RX_Data);
    ' V$ [' q  B8 D( j% h
  12.         }/ z# @* }0 ~3 E$ i6 l& p, p
  13.     }8 q4 k4 }  h$ a0 P* q' Z  W1 n
  14. }
复制代码
7 ^0 O5 Z* k8 T, Z' Y
下图为程序现象:pc向单片机发送数据0x15,单片机接收数据0x15,并且把接收到的数据作为数据发送到pc,在pc上显示0x15。
4 f# _( p% Z; R
微信图片_20230810144705_1.png
+ m+ b$ _: J, r6 x& j! l3 u

, N  y7 ~- t) F' E" O: q' J
3. 使用中断
' t8 S3 \# b' H8 U0 A
  • 通过配置串口的接收作为中断源,开启中断输出控制,配置NVIC。开启中断通道。
    7 F! m* P! f' p
  1. //开启中断输出控制
    " y- W3 _$ v2 n. m# F! N
  2.   USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);: ?& d5 ~& [6 Q2 B  p, Z
  3.   
    0 U# Z' ]" [" a5 S+ t( F% @
  4.   //配置NVIC3 n. c6 ~# B+ g, {, e5 x
  5.   NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); / |( R1 W; b4 P' Y! N, Z# O
  6.   NVIC_InitTypeDef NVIC_InitStruct;5 w' l- L  x# X- V% A3 B: t- N! H
  7.   NVIC_InitStruct.NVIC_IRQChannel=USART2_IRQn;   //选择USART2的中断通道
    # k3 B$ d5 l; W! R: |+ ?
  8.   NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;    //中断使能* h  s$ O/ y9 F. @2 o- y
  9.   NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=1;% @! {( D; v0 L$ M2 O9 m& h: a( a7 z' L( N
  10.   NVIC_InitStruct.NVIC_IRQChannelSubPriority=1;% E3 V' M' T2 e
  11.   NVIC_Init(&NVIC_InitStruct);
复制代码

1 Z/ [. ^# T2 ~' I
  • 中断服务子函数

    ' d6 j8 x$ a& ^1 F
; d9 H& ]7 o. |# R- v7 R: T
中断服务子函数写好后,就可以在中断里读取接收到的数据了。
9 Y: b" S2 }+ _
当接收到数据后,触发接收中断,主程序暂停执行。接收完数据后主程序回复执行。当接收到数据时,就触发中断。
  1. void USART2_IRQHandler(void)
    : j. D5 U' }' _
  2. {$ @) M' c1 F2 ~5 G
  3.   if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET)   //RXNE 标志位为1 表示可以接收数据' N7 `1 C  J2 L& L3 `( C( K5 B5 V
  4.   {
    1 h( T9 e- f2 k% W
  5.     RX_Data=USART_ReceiveData(USART2);0 N9 J# F4 Q+ I/ [1 p
  6.     Flag=1;# b1 a# n1 j% h5 W
  7.     USART_ClearITPendingBit(USART2,USART_IT_RXNE);  //清除RXNE标志位) L# u, V/ X. {0 |/ s/ Q; p& a
  8.   }
    $ d. M! o+ s/ V  [9 ]  H; b" t
  9. }
复制代码
& A: F. ^0 {2 E, x7 s; j
  • 主程序测试
    4 N; _$ s+ |8 O
  1. uint8_t RX_Data;4 ^8 I* m6 |; V  E" A% g
  2. uint8_t Flag;: x( A; R  Z* d4 P  f% N. `5 B
  3. % g1 g  Y) @' U1 l
  4. int main()8 f" H6 ~9 U2 E$ h1 v
  5. {! i$ ]( h& Q! y* Z7 X9 s% d* z
  6.     Serial_Init();* T5 ?! _* i0 C3 p4 H9 o4 V4 d
  7.     Serial_SendByte(0x16);9 }% T" u% H4 V+ b1 z5 |
  8. " r3 F! y% B# \$ C4 q# K
  9.   p: N+ o7 {; ~- N9 _* ~2 }1 k
  10.     while(1)
    ; i3 B& q" u- w3 H) T$ S) q$ k
  11.     {
    % Z& q5 S8 z; y: c  D" ^$ R
  12.         if(Flag==1)
    / a9 I+ R  W! B- A# L# |1 E% ]
  13.         {+ ~8 G% W: P, Z+ [: f% W
  14.             Serial_SendByte(RX_Data);# u! x+ K+ U- o; z( g
  15.         }1 l# G) Y8 ]/ A5 v+ ]" e+ B( G$ v
  16.     }: y$ w  T, F6 f) F
  17. }3 Q5 y+ N5 F, z

  18. 1 Q* @- q9 [. a- z
  19. void USART2_IRQHandler(void)
    * c: i/ [6 [0 j( L  Q: Y- B
  20. {
    ( B! Z, Z0 T9 i# r  z! I- y6 F7 a  e
  21.     if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET)   //RXNE 标志位为1 表示可以接收数据
    : ]8 u; u. C& H- S; E6 l1 [2 ]
  22.     {
    5 x# [) @% ^7 m2 ^. k: U; [( o
  23.         RX_Data=USART_ReceiveData(USART2);2 S. C- A1 n! s/ w( _/ R, @
  24.         Flag=1;
    : ]$ d1 R, Q9 Q- y" b2 S
  25.         USART_ClearITPendingBit(USART2,USART_IT_RXNE);  //清除RXNE标志位' a. F; O+ B& q
  26.     }
    5 m4 I* O! ^/ p1 f8 |- C& q3 i
  27. }
复制代码

/ O* {8 Y; `; \6 c: r6 p

8 G" r" W1 E2 p. B% G! j" H
下图为程序现象:可以看到,串口确实收到了数据,只是我把接收到的数据0xFE放在了while循环里,这说明数据接收是成功的,使用中断是可行的。

" Y) T- n7 d# F5 E! c
微信图片_20230810144705.png ' n) K0 X6 M- R* H  t3 j& f

# c  s& F8 K4 v1 F+ @9 X
8 ?6 A2 V; M# l# x& E9 a! \
总结
3 l, n& I! P0 X, @# K' B, ?0 Y
到这里,就大致总结了串口的发送和接收。
2 K6 P# n2 f7 S! r
串口的配置,使用查询或者中断来接收数据。

+ }) B# t! x3 }( q+ i& u
串口的使用会很常用到,所以在这里对串口做一个总结,也算是对之前知识的一个回顾和总结,加强印象。

+ }! v4 M& ^9 K3 N
转载自: 古月居
如有侵权请联系删除
. w4 z0 D- }* T% I

' E/ S) R$ `4 t# ~, t
收藏 评论0 发布时间:2023-8-10 14:48

举报

0个回答

所属标签

相似技术帖

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