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

基于STM32串口通信经验分享

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

" K- k, Z+ c( Z6 C
本文在于记录自己的学习过程中遇到的问题和总结,各种情况下串口通信在STM32的实际使用方面占有很大的比重,本文主要对串口通信做一个简要的总结。

& }, g' b7 N7 ?
一、STM32里的串口通信
在STM32里,串口通信是USART,STM32可以通过串口和其他设备进行传输并行数据,是全双工,异步时钟控制,设备之间是点对点的传输。

" k0 w0 ?& E1 D9 s' E  A* v
对应的STM32引脚分别是RX和TX端。STM32的串口资源有USART1、USART2、USART3.

/ e+ u4 E) C6 J9 ]1 o
串口的几个重要的参数:
5 }# v% g3 y# D! z! f( j
  • 波特率,串口通信的速率

    1 b5 Y: J; R, m: V) @' j
  • 空闲,一般为高电平
    3 a' a0 S+ s  T: Y$ k
  • 起始位,标志一个数据帧的开始,固定为低电平。当数据开始发送时,产生一个下降沿。(空闲–>起始位)
    9 [: L- P( w% C0 J# h, U' }0 H& z: k4 F
  • 数据位,发送数据帧,1为高电平,0为低电平。低位先行。

    0 J7 H% v3 i% Q& z7 S4 i9 G
  • 比如 发送数据帧0x0F 在数据帧里就是低位线性 即 1111 0000

    - D4 l2 t' ^( ?' i5 o: y9 K) N% O
  • 校验位,用于数据验证,根据数据位的计算得来。有奇校验,偶校验和无校验。

    ) n/ Y! e$ D' x
  • 停止位,用于数据的间隔,固定为高电平。数据帧发送完成后,产生一个上升沿。(数据传输–>停止位)
    5 _; K  F$ l- k2 G

8 F& T/ c8 c# C. n& Y+ i1 A; p
下方就是一个字节数据的传输过程,从图中可以看出,串口发送的数据一般都是以数据帧的形式进行传输,每个数据帧都由起始位,数据位,停止位组成, 且停止位可变。
* P7 j3 q* D$ C
微信图片_20230726144152_2.png
- K" `3 b2 U2 ?1 J$ ^( R8 [: o7 Y" q1 T0 r: k
  M- a/ X8 c3 b  v% a
二、串口的发送和接收
USART是STM32内部集成的硬件外设,可以根据数据寄存器的一个字节数据自动生成数据帧时序,从TX引脚发送出去,也可以自动接收RX引脚的数据帧时序,拼接成一个字节数据,存放在数据寄存器里。
0 x" j3 {5 p+ d
当配置好USART的电路之后,直接读取数据寄存器,就可以自动发送数据和接收数据了。在发送和接收的模块有4个重要的寄存器
1 e, x+ ^, K. V+ e, _) p# b
  • 发送数据寄存器TDR
    0 f* B, {- |" {+ R9 d5 `' m" A7 W
  • 发送移位寄存器,把一个字节的数据一位一位的移出去

    # _6 }; q, e; h
  • 接收数据寄存器RDR
    / q5 i' E6 C  G, Z- C
  • 接收移位寄存器,把一个字节的数据
    7 [- c  u0 [1 H: E

: `, x( ~5 N; Y
下方为串口的发送和接收图解:

% _+ M" @8 h# {1 j9 [  ~3 J0 p
微信图片_20230726144152_1.png
2 H" K* ]$ f7 o5 h2 B( P0 M
3 D" U' i5 F& v* v2 E
串口发送
. b+ K/ J; Y5 @0 c$ }! W" p
在配置串口的各个参数时,可以选择发送数据帧的数据位的大小,可选8位或9位。

, i5 f2 Q/ R" p
串口发送数据实际上就是对发送数据寄存器TDR进行写操作。
4 x* W& V' }, w0 L  Z, K& N
1. 当串口发送数据时,会检测发送移位寄存器是不是有数据正在移位,如果没有移位,那么这个数据就会立刻转移到发送移位寄存器里。准备发送。
! c! U' F( g4 `* |
2. 当数据移动到移位寄存器时,会产生一个TXE发送寄存器空标志位,该位描述如下。当TXE被置1,那么就可以在TDR写入下一个数据了。即发送下一个数据。

" e$ Y: j& j& d; Q; {
微信图片_20230726144152.png " b* R& t9 a0 J* \" W; Y' V
* d- J/ |$ z7 v# {: }
3. 发送移位寄存器在发送器控制的控制下,向右移位,一位一位的把数据传输到TX引脚。

# U2 V! L9 H1 B$ T" a! O+ X/ l
微信图片_20230726144153_3.png
. N3 k8 U* x) v; z4 V1 X

0 M. Y6 ^, g  r
4. 数据移位完成后,新的数据就会再次从TDR转移到发送移位寄存器里来,依次重复1-3的过程。通过读取TXE标志位来判断是否发送下一个数据。

9 M& |9 |) E2 G6 m0 h! U  C
串口接收

+ d6 m& s  q$ Q! w8 W0 q
数据从RX引脚通向接收移位寄存器,在接收控制的控制下,一位一位的读取RX的电平,把第一位放在最高位,然后右移,移位八次之后就可以接收一个字节了。
* i5 b2 p: v9 t3 a# x% r5 X
当一个字节数据移位完成之后,这一个字节的数据就会整体的移到接收数据寄存器RDR里来。

  H" _+ }1 v7 F' J, h7 I+ j
在转移时会置RXNE接收标志位,即RDR寄存器非空,下方为该位的描述。当被置1后,就说明数据可以被读出。
; t5 Z, F. w- u# O
微信图片_20230726144153_2.png 6 n% K7 T+ w! ?; y* M/ S

7 K9 N# v$ m( F- c! p
下图即为串口接收的工作流程
3 r7 A. r" u5 c: L- C& A, u  z
微信图片_20230726144153_1.png
2 g4 {4 \. g" C" o: d
0 ^; J5 j  c/ {, J
三、串口在STM32中的配置

# o  ?6 X3 f$ W+ J- D) j
首先要明确几点:使用STM32串口外设中的哪一个?串口发送或者接收数据?串口相关的参数配置?发送或接收是否使用到中断?

7 P% g7 b2 Q9 T
下方为串口发送的配置。

2 F! [! o- n( [3 e
1. RCC开启USART、串口TX/RX所对应的GPIO口
4 F4 [+ ^9 r1 p, i
  1. RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);  //开启USART2的时钟
    6 a) A# ~7 D/ |
  2. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);   //开启GPIOA的时钟
复制代码

! a! C9 N4 B* ]( k4 ]
2. 初始化GPIO口
; s; `8 C- g, d% t6 b/ J; l
这里注意哈,根据自己的需求来配置GPIO口,发送和接收是都需要还是只需要其中一个。然后对应的根据引脚定义表来初始化对应的GPIO口。
+ e+ U/ \/ [6 J$ b& ?4 T8 |
USART2对应的引脚
2 \, I2 |$ Q, R  j) \$ w1 S4 m
微信图片_20230726144153.png 3 ^1 Y( \3 K- t7 h3 k
/ x: n6 N1 q7 t$ a5 e1 w
USART1对应的引脚
: `2 c4 m' A3 @! _& f4 w) ^
微信图片_20230726144154_4.png
( e0 Y) P' G3 M6 w

. B3 R+ u- _( U% G. A
这里根据手册来看,RX引脚模式配置成浮空输入或者上拉输入。TX引脚模式配置成复用推挽输出。

% x% l4 H7 j$ s! a
**比如我这里只初始化TX发送端**
  1. //TX端
    8 O2 {9 L! `6 j1 Q1 Z7 ?
  2.     GPIO_InitTypeDef GPIO_InitStructure;
    ' a/ K" {7 @: ~
  3.     GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;  //复用推挽输出, k1 c$ G' u' c5 E0 h/ @
  4.     GPIO_InitStructure.GPIO_Pin=GPIO_Pin_2;   //USART2对应的TX端为GPIOA2
    % `$ J# v$ H% A. @% w, Z! h
  5.     GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;  //50MhZ4 B7 y" w$ P3 X: W5 e
  6.     GPIO_Init(GPIOA,&GPIO_InitStructure);
复制代码
" n4 T# Y! R) G/ t) I" R3 M
3. 串口初始化

, ^6 l# V; m0 ~9 T1 n8 r+ n
注意哈,USART_Init()这个函数,是用来配置串口的相关参数的。
8 Y4 O9 I) H0 }- U4 ^& c# I
  • USART_BaudRate 串口通信使用的波特率 一般是9600或者是115200,这里我们给9600

    * Z& V% K# ?  t' ^  l( c' N+ C, N
  • USART_HardwareFlowControl 是否选择硬件流触发,一般这个我们也不选,所以选择无硬件流触发。
    2 z$ p" @% B- o/ i$ @/ i
  • USART_Mode 这个参数要注意了哈,串口的模式,发送模式还是接收模式,还是两者都有
    ) V7 ^1 A& H) O, u
  • USART_Parity 校验位,可以选择奇偶校验和不校验。没有需求就直接无校验

      J) O7 b4 T, C4 {3 o4 z) k8 S; c
  • USART_StopBits 停止位 有1、0.5、2位,我们这里选1位停止位

    6 I4 N8 K( i2 G3 P
  • USART_WordLength 数据位 有8位和9位可以选择

    " z( l2 W$ C9 m6 q$ _
  1. //串口初始化
    $ ~, K. S+ Z, L! Z
  2.   USART_InitTypeDef USART_InitStruct;
    1 P7 K* B0 k. w! y% I7 k7 B+ e8 [8 W
  3.   USART_StructInit(&USART_InitStruct);  //初始默认值
    3 {. w" p( I9 t
  4.   USART_InitStruct.USART_BaudRate=9600;
    ! O- L1 K( ?$ h) F, q' m5 p( q' J
  5.   USART_InitStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None;   //不使用硬件流触发" c+ M" [/ C  O/ A$ G
  6.   USART_InitStruct.USART_Mode=USART_Mode_Tx;         //TX 发送模式5 M9 y9 w( A& M0 Q  G: a
  7.   USART_InitStruct.USART_Parity=USART_Parity_No;       //不选择校验
      C0 Q- H* k( }: v: ]  {% |
  8.   USART_InitStruct.USART_StopBits=USART_StopBits_1;      //停止位1位+ k+ ~  ^9 T" ^# ?- F8 H
  9.   USART_InitStruct.USART_WordLength=USART_WordLength_8b;  //数据位8位' d0 Y% s, l8 I) `
  10.   USART_Init(USART2,&USART_InitStruct);
复制代码
- B3 {, j" P8 W+ L% f* B3 H7 y1 w
4. 串口使能
  1. //串口使能
    % P: r( B6 r+ T, V- o+ d
  2.    USART_Cmd(USART2,ENABLE);
复制代码

2 A( M7 q0 m% W# J$ L9 R( p$ |
5. 串口发送数据

1 q1 y* O3 B) B! l) J- P注意哈,我们要判断TXE标志位的状态。0,数据还没有被转移到移位寄存器;1,数据已经被转移到移位寄存器。
+ e* {6 B* C- }1 R
当TXE标志位为1时,就说明可以发送下一个数据了。详细过程可看上面串口发送的解释。
  1. void Serial_SendByte(uint16_t Byte): x- u  j$ F' _  H% U" W, Y" E/ r
  2. {5 |# x! X2 m2 n! A% _! W
  3.   USART_SendData(USART2,Byte);
    ' G. y1 `4 d/ h! N4 {: f
  4.   //0 表示数据还未转移到移位寄存器 循环等待 1 数据已经被转移到了移位寄存器可以发送数据
    ( X7 O- g! C$ `0 B$ s1 F7 H
  5.   while(USART_GetFlagStatus(USART2,USART_FLAG_TXE)==RESET);  //不需要手动清零 再次写入TDR时会自动清零  : U. e) G7 z1 o4 A0 S5 ?% W
  6. }
复制代码

8 P: v1 D2 ^! n. w2 i7 g6 r
经过上述五步的配置,单片机就可以通过串口发送数据了。
  p" s# C/ e: o7 \4 v$ Y* _
下方为发送数据图例,STM32向串口发送0x16数据

+ T& Z' Z; z$ Y  S5 w( P
微信图片_20230726144154_3.png ' r- Q3 Q& ?. o& ~
. f5 y- E- _; X+ F% Z0 ]. }! ]+ {, ?9 _

% L) ~8 @/ R, b; j# ]* V- `
四、串口接收的两种实现方式
上方是发送数据的例子,那么串口接收又该如何配置,又要在串口发送的例子上做哪些更改呢?
3 E8 i. V5 D) W9 ~# _3 N" i! z, l
这里我们可以通过查询或者中断的方式来进行接收数据的两种方式。

9 K8 f6 j' {' R
  • 查询方式就是通过不断的查询RXNE标志位,通过判断RXNE位的状态来确定数据是否接收。
    & X+ J0 J( F5 [
  • 中断方式就是通过配置接收输出控制通道,配置NVIC,在中断服务子函数里进行数据的接收。

    0 {# U1 E3 M# S, m1 ^  |$ X

: O* y  k4 l0 `( D
1. 需要更改的地方( x! c) G* `  I. A3 u& U- o1 @
9 {+ P9 @2 \' b, a2 O2 B3 l
既然我们要实现串口的接收,那么就要配置串口RX引脚,在串口模式中添加USART_Mode_RX模式。

/ e% p: k# ?, S7 t
  • 初始化RX引脚

    8 q% D! \; k( {* C3 w& d+ Y
  1. //RX端/ X: a; x& c/ p7 \
  2.   GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;  //上拉输入* ]  A0 ~/ {, @4 {+ u1 H
  3.     GPIO_InitStructure.GPIO_Pin=GPIO_Pin_3;
    3 \* c5 {! ]2 g1 c  l" T3 g
  4.     GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;  //50MhZ* n1 ^* r6 V9 I  O2 ]& J# {6 w
  5.     GPIO_Init(GPIOA,&GPIO_InitStructure);
复制代码
1 ]$ K' r( ^2 N5 T" x

4 P' }& R, W2 s6 }
  • 添加串口模式

    , p- _, C8 t5 J+ u, O: [
  1. USART_InitStruct.USART_Mode=USART_Mode_Tx|USART_Mode_Rx;         //TX 发送模式  RX 接收模式
复制代码
% ^. l# Q- C9 l) d# w6 R
! x2 \- M. ~9 J& F3 ^
2. 查询RXNE标志位

% h& w5 W  H9 `/ t( M; o
这里我们还是来看一看RXNE标志位的描述

" \8 j0 \9 r& K# O
微信图片_20230726144154_2.png * Y' U7 V/ I' k5 {# g3 o% q8 n

, |8 O" n) C, Q! c9 K
上图描述,为0时数据没有收到,为1时收到了数据,数据可以从RDR里读出
; l; U, b: ?9 D: K
所以在主程序里不断读取RXNE标志位,如果为1,表示数据可以读出
  1. uint8_t RX_Data;) {( `: T' G5 K& ]. K1 [
  2. int main()
    % [: v/ r2 I8 x( C- u
  3. { 6 N- n4 E9 _  P- h0 Z9 ^; [4 ?7 h  m
  4.     Serial_Init();; P6 L# h8 F% P: l5 d
  5.     Serial_SendByte(0x16);
    ) M3 r, n0 L; l; b& ~
  6.     while(1)4 E" ]2 x. O0 j8 X7 \. a: E* ~- S
  7.     {
    ; D8 e! ]1 w4 B2 C" B
  8.         if(USART_GetFlagStatus(USART2,USART_FLAG_RXNE)==SET)   //0 循环等待 1 可以接收数据
    * m1 ]% F/ h5 g& y$ L7 ~9 Y. E
  9.         {* _4 E, X& Q5 M: e2 d; [9 z. `% i
  10.             RX_Data=USART_ReceiveData(USART2);           
    $ `$ ?7 M- g+ d  Z( w* O. K
  11.       Serial_SendByte(RX_Data);0 \3 O3 p" F) P+ b  _9 e$ E% J  G
  12.         }: ^4 C: }. O: P2 B6 U- q4 D" C
  13.     }
    & r  R4 \& o. T  ~) r
  14. }
复制代码

+ o5 I* @  u0 e7 @! Q& s
下图为程序现象:pc向单片机发送数据0x15,单片机接收数据0x15,并且把接收到的数据作为数据发送到pc,在pc上显示0x15。
; Q! W/ c6 g- Z# G
微信图片_20230726144154_1.png , w' V% ]2 ~0 V3 ~7 S& L6 i
$ P# {% u9 r0 N# r6 J3 x( s) }
3. 使用中断
: [, A' \/ S/ m) V# ]2 t
  • 通过配置串口的接收作为中断源,开启中断输出控制,配置NVIC。开启中断通道。
    5 q) x0 E" h5 ?# ~8 y
  1. //开启中断输出控制5 |( s2 ]/ O5 h0 ]$ E4 I
  2.   USART_ITConfig(USART2,USART_IT_RXNE,ENABLE);
    4 Q. d& d7 j5 a% k: i
  3.   
    ' N& Z2 {0 R. E8 Q6 a
  4.   //配置NVIC
    % J# X& D. Z/ e2 N# U
  5.   NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    2 E  b0 ?! C" ]# w- }
  6.   NVIC_InitTypeDef NVIC_InitStruct;! w: f) b5 Y5 \( V- p5 _* Y
  7.   NVIC_InitStruct.NVIC_IRQChannel=USART2_IRQn;   //选择USART2的中断通道
    5 c1 r/ X2 B- P3 o
  8.   NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;    //中断使能; Y, `, ^3 g$ i, |
  9.   NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=1;
    6 V4 K$ Z5 V2 X; T) a
  10.   NVIC_InitStruct.NVIC_IRQChannelSubPriority=1;
    4 M; C# x0 Z8 S/ l2 V0 K
  11.   NVIC_Init(&NVIC_InitStruct);
复制代码
* a/ j! z, d8 I( }* ^5 O/ n
) @$ M7 i, M+ w# r6 M) K" }" t
  • 中断服务子函数

    7 Q  P. V: S4 p7 G2 e

& @2 T& n$ L" }% O+ R4 W6 T! ]/ B中断服务子函数写好后,就可以在中断里读取接收到的数据了。

8 g+ f1 o0 n- B, ^; o2 f当接收到数据后,触发接收中断,主程序暂停执行。接收完数据后主程序回复执行。当接收到数据时,就触发中断。
  1. void USART2_IRQHandler(void)
      W/ n7 v0 V" e  B* U, Q& y% `
  2. {) A  L2 m4 r% f
  3.   if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET)   //RXNE 标志位为1 表示可以接收数据# G. q5 P" ?( V/ U2 K! W
  4.   {
    & j- V$ ^2 w2 K0 q0 N" h
  5.     RX_Data=USART_ReceiveData(USART2);
    8 F& f1 a# }2 H
  6.     Flag=1;9 Z3 {8 ~3 I( C# `) Y
  7.     USART_ClearITPendingBit(USART2,USART_IT_RXNE);  //清除RXNE标志位/ M! a: N" X2 V, H; R+ ~+ U* @
  8.   }
    * R% D, s* w* b! X' T7 p9 ~! X8 c( J2 Y
  9. }
复制代码
5 ?1 B/ P: W$ `% H; k' ^& F* }, t5 w  X
  • 主程序测试

    5 ]: W2 O* M1 Q$ i, Z9 c: S' [
  1. uint8_t RX_Data;1 o, e. e" @! a( {
  2. uint8_t Flag;
      n( b3 A2 C: V$ C) C& E- w

  3. 3 K( ]$ U9 D- D4 A0 T: q" Y3 Q
  4. int main(), f* d$ d1 O% o% U9 ^% g
  5. {
    : f' b) W6 K4 n, L3 F# ]; @
  6.     Serial_Init();
    4 V& G2 q& b; E7 A! Z) I
  7.     Serial_SendByte(0x16);
    * W6 Q( p8 }6 a6 v
  8. 9 g$ N5 X2 r! r1 A  B7 k
  9. ! d# R# t0 z3 |0 m  k+ V7 {
  10.     while(1)6 O, M2 f; U1 u" ^/ K
  11.     {* D( B+ ~. b6 H# S3 \+ ]
  12.         if(Flag==1)
    1 q# B+ K6 e, B: H% K3 m  v2 a
  13.         {
    2 f4 ^, Q6 }' G- T/ X) ?
  14.             Serial_SendByte(RX_Data);7 h) a) l8 t( N7 o4 n
  15.         }4 b5 u+ I* W" h# T
  16.     }  g/ V7 X3 b0 z
  17. }
    1 K% Q2 V5 S1 m. H: s' P* \0 I& [

  18. 5 A2 `, d0 m5 t
  19. void USART2_IRQHandler(void)
    . e% e# Q. r" ^
  20. {
    , h& M3 J/ ~+ O4 @# W( r
  21.     if(USART_GetITStatus(USART2,USART_IT_RXNE)==SET)   //RXNE 标志位为1 表示可以接收数据- q6 q9 G) U2 ~; k) k
  22.     {. R; T$ Q: y9 D7 C
  23.         RX_Data=USART_ReceiveData(USART2);
    1 R. v" D/ D" I, Q& n
  24.         Flag=1;8 v" O0 x3 F) w1 \6 m# p( N
  25.         USART_ClearITPendingBit(USART2,USART_IT_RXNE);  //清除RXNE标志位
    + e2 ?4 ~3 C( T/ s7 V3 ?
  26.     }
      a9 E0 M3 q* z$ c- \
  27. }
复制代码
* I+ `' n# B( t% k

% \# y' l# m& r
下图为程序现象:可以看到,串口确实收到了数据,只是我把接收到的数据0xFE放在了while循环里,这说明数据接收是成功的,使用中断是可行的。
4 d5 s& m( d7 ]. {5 J4 E
微信图片_20230726144154.png
' A1 L/ D- x' N3 |

7 b5 c) F, b/ P& G" N8 p: _1 i

7 R% m; m. a% M: h3 N- g& M
总结
到这里,就大致总结了串口的发送和接收。
; ]; n! O9 ]  m, B
串口的配置,使用查询或者中断来接收数据。

% a" ^& Y8 B6 _( c+ d1 z; F5 u( o
串口的使用会很常用到,所以在这里对串口做一个总结,也算是对之前知识的一个回顾和总结,加强印象。

  \% r8 S% v  H4 l$ L
如果有什么写的不对的地方,欢迎指正!
/ T# `% O6 W7 `3 s9 t: O. F! q  y
转载自: 古月居
如有侵权请联系删除
# O0 S3 X/ c* L& C" R' ^
) V5 s, D8 Y4 A9 p: Q2 q% ^" p

& @$ ^3 @8 t2 f/ {) E4 c+ H3 l# ^/ e
赞 收藏 评论0 发布时间:2023-7-26 14:43

举报

0个回答

所属标签

相似技术帖

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版