STM32单片机学习(14) —— STM32的串口外设

文章目录

概述

本文的末尾,我们还需要学习一下单片机USART外设的基本工作原理,再分析一下单片机引脚的使用及PC端接线。

通过以上内容的学习,我们就可以在下次,实现单片机与 PC 端之间的双向串口通信

STM32的USART模块

在上面我们已经了解了UART串口通信的基本内容,下面我们来学习一下STM32的USART模块的使用。

当然前面已经说过了,USART模块支持同步和异步,但我们一般只使用它的异步模式,也就是把USART当成UART使用。

让我们回到讲STM32F103C8T6外设模块时,看到的一张图,如下图所示:

这张图中:

挂载在APB2总线上的USART1以及挂载在APB1总线上的USART2、USART3以及UART4、UART5都可以用于实现串口通信,都属于串口通信外设。

当然,我们可以再通过查阅《引脚定义表》(STM32F103系列,中等密度芯片),就可以发现:

STM32F103C8T6单片机实际上只有三个USART外设,即USART1、USART2以及USART3。

其中只有USART1外设挂载在APB2外设总线上,如无特别的需求,建议优先考虑使用USART1外设。

波特率的设置原理

波特率的概念我们已经了解了:波特率指通信中每秒传输的比特数量。

那么STM32的USART外设是如何产生这个波特率的呢?

USART外设模块硬件内部中有专门的电路用于生成波特率,下图是一个简化的原理图:

为了讲清楚这个图,我们先来讲一下图中的"时钟"是什么意思。

USART外设时钟

这张图里的"时钟",指的是提供给 USART 外设的工作时钟(外设时钟)频率,此时钟频率经过后续一系列分频最终得到波特率.

USART外设的外设时钟是怎么来的呢?

在 STM32F103C8T6 中,USART 的时钟来源是固定规则的:

  1. USART1外设时钟来源于 APB2 外设总线时钟。
  2. USART2、USART3外设时钟来源于 APB1 外设总线时钟。

而两条外设总线APB1和APB2时钟,都来源于AHB系统总线时钟。可以查看我们之前讲过的单片机《系统结构图》:

USART外设时钟的来源参考下面的链条序列:

系统时钟 -> AHB系统总线时钟 -> APB1/APB2外设总线时钟 -> USART外设时钟

了解下列内容:

  1. 在默认情况下,基于最小系统板的外部晶振电路,单片机的系统主时钟频率是72MHz。
  2. 在默认情况下,AHB系统总线时钟不分频系统时钟,AHB系统总线时钟频率也是72MHz。
  3. 在默认情况下,APB2外设总线时钟也不分频AHB系统总线时钟,APB2外设总线时钟频率也是72MHz。
  4. 在默认情况下,APB1外设总线时钟1/2分频AHB系统总线时钟,APB1外设总线时钟频率是36MHz。

所以我们的结论就是:

  1. 如果选择使用挂载在APB2外设总线上的USART1外设,那么USART时钟频率是72MHz。
  2. 如果选择使用挂载在APB1外设总线上的USART2、USART3外设,那么USART时钟频率是36MHz。

USART外设优先级问题

前面我们已经讲过优先使用USART1外设,那么为什么呢?

结合前面的知识,有以下几点原因:

  1. USART1外设时钟频率,在默认情况下是系统主频率,频率更高,后续分频自然就更宽裕、更精准,且能设置更高的波特率。
  2. USART1 的外设时钟,默认情况下与系统主频一致,计算过程更直观,不容易出错。
  3. 单片机USART1外设的引脚用起来更方便,这一点后续小节会体现。

当然,如果USART1确实不可用,使用APB1外设总线上的USART2或USART3也没有本质区别,只需要注意APB1的1/2分频即可。

时钟周期和时钟频率

在以前的内容中,我们已经知道:

单片机的系统主时钟频率是 72MHz,而 USART1 外设的时钟频率,在默认情况下同样是 72MHz。

那么,在学习到这里时,你心中很自然地会产生以下两个问题:

  1. 时钟到底是什么啊?
  2. 时钟频率又是啥概念?

关于时钟,在讲GPIO外设开启时钟时,我们简单提到过。

当时,我们讲:

时钟/时钟信号,本质上是一种周期性变化的电平信号,时钟的作用是成给单片机内部电路提供"工作节拍"。

前面我们只知道这个"工作节拍"很有用,单片机运行没它不行。

那么这个"工作节拍",具体有什么用呢?

下面通过一个案例来解释时钟信号的作用:

无论是学习、工作还是生活,我们都习惯用"秒""分钟""小时"等时间单位来描述和规划事情。

单片机虽然不是人,但它的工作过程同样离不开"时间"的概念。

只是,单片机并不能直接理解"1 秒""1 分钟"这样的抽象时间单位。

为了让单片机也能够"感知时间",就引入了时钟/时钟信号这一设计。

可以把时钟理解为单片机内部的"节拍器"或"心脏":

  1. 每打一个节拍或者心脏跳动一下,这就是一个**"时钟周期"**。
  2. 单片机可以通过统计"过了多少个时钟周期",来感知时间的流逝。

时钟信号每一次完整的周期性变化,称为一个时钟周期。

结合我们讲数字电路中的高低电平,更通俗直观的说:

一个高电平加上一个低电平,就组成了一个完整的时钟周期。

那么一个时钟周期到底有多长时间呢?

要想弄清楚这个问题,还需要了解一个概念:时钟频率。

时钟频率用于描述1秒钟内,有多少个时钟周期。单位是Hz,意为每秒多少个时钟周期。

比如STM32F103C8T6单片机,在默认情况下,系统主时钟频率是72MHz,也就意味着:

在1秒钟内,存在7200 0000个时钟周期。

在这种情况算下来,一个时钟周期就是1 / 72000000 秒钟,也就是约13.9纳秒一个时钟周期。

理解上述内容,我们给出以下几点结论:

  1. 单片机通过时钟信号来理解时间。
  2. 单片机理解的时间并不连续,它认为时间的基本单位是"时钟周期"。
  3. 在单片机内部执行的操作,比如CPU执行指令、外设处理数据等,都以时钟周期为基本时间单位,最少也需要执行1个时钟周期。
  4. 单片机完成某项工作的时间 = "执行时钟周期数" * "时钟周期时间"。

时钟频率和波特率的关系

时钟频率和波特率到底有啥关系呢?

在前面的内容中我们已经知道,波特率通常用于描述串口通信中每秒钟传输多少个比特。

换句话说:波特率决定了通信中,1个bit位数据在发送时,需要发送多长时间,需要维持多长时间的电平稳定表示数据发送。

这是人对波特率的理解。

但单片机不理解人类的时间,它对时间的认知需要用时钟周期来衡量。

于是,单片机对波特率的理解就是:发送1个bit位数据,发送时,需要持续多少个时钟周期的电平稳定。

基于这样的关系,USART外设在确定波特率时,都基于USART外设时钟频率,进行分频计算得到。

为什么需要分频呢?

因为USART串口外设支持的通信速率较慢,不可能1个时钟周期发送1个bit,所以必须分频,用很多个时钟周期加起来发送1个bit数据。

波特率和时钟周期的关系

那么在串口通信中,一个 bit 数据究竟要发送多少个时钟周期呢?

我们换一种机器视角来看这个问题。

STM32F103C8T6 在常见配置下,主频为 72 MHz。

这意味着:1 秒钟内有 72,000,000 个时钟周期。

也就是说:1 个时钟周期 = 1 / 72,000,000 秒。

1个时钟周期是1/72000000秒钟。

假如波特率是9600,那么传输一个bit需要1/9600秒钟。

于是1个bit的传输,需要的时钟周期是:(1/9600) / (1/72000000) = 7500个时钟周期

假如波特率是115200,那么传输一个bit需要1/115200秒钟。

于是1个bit的传输,需要的时钟周期是:(1/115200) / (1/72000000) = 625个时钟周期

所以:

发送 1 个 bit 所需的时钟周期数 = 外设时钟频率 / 波特率

当然,这里并不是说,串口每发送一个bit数据,CPU"傻等"这么多个周期。

而是:单片机的USART串口外设,需要数这么多时钟周期,来确定发出1个bit数据,并维持这么多时钟周期时间内电平稳定。

设置波特率分频

时钟的概念我们已经讲清楚了,下面我们默认使用USART1外设,外设时钟频率是72MHz,即系统默认主频率。

如上图所示,串口通信中的波特率,本质上就是对 72 MHz 时钟进行分频后得到的结果

  1. 第一级:波特率寄存器(BRR) 设定的,可变的分频因子
  2. 第二级:固定的 1/16 分频器。

其中,固定的 1/16 分频器是硬件决定的,无法修改;

但我们可以通过设置波特率寄存器,来获得一个可变的分频器:

若波特率寄存器的值为N,分频系数为N,就会得到一个1/N分频器,就会将输入的时钟频率除以N。

波特率的计算公式为:

波特率 = 72 000 000 / N / 16

串口通信的常见波特率是:9600和115200,尤其是115200是最常见的波特率设置。

假如选择波特率为115200,我们可以反推一下波特率寄存器中的分频系数:

72 000 000 / 16 / 115200 = 39.0625

这个分频系数不是一个整数,这是什么情况呢?波特率寄存器如何表示这个分频系数呢?

很明显分频系数是可以为小数的!!

为了回答这个问题,我们可以看一下波特率寄存器的构成,如下图所示:

波特率寄存器一共16位,分频系数为全部16位的值加起来,允许存在小数。

假如:

波特率寄存器的16位数据为:0001 1101 0100 1100

整数部分是0001 1101 0100:1 × 28 + 1 × 27 + 1 × 26 + 1 × 24 + 1 × 22 = 256 + 128 + 64 + 16 + 4 = 468

小数部分是1100:1 × 2-1 + 1 × 2-2 = 0.5 + 0.25 = 0.75

所以此时分频系数是468.75

此时的波特率为:72000 000 / 468.75 / 16 = 9600

这就是波特率为9600时的波特率寄存器取值。

115200波特率时,波特率寄存器需要存储39.0625这个分频系数,于是存储的16位数据就是:

0000 0010 0111 0001

因为:

整数部分是0000 0010 0111 = 1 × 25 + 1 × 22 + 1 × 21 + 1 × 20 = 32 + 4 + 2 + 1 = 39

小数部分是0001 = 1 × 2-4 = 0.0625

你搞懂了吗?

如果你搞懂了,肯定会产生这样的想法:

通过波特率寄存器设置分频,从而设置波特率,这也太麻烦了,需要慢慢算,如果不小心搞错了1位全盘皆错,这也太坑了。

实际上:

  1. 如果你选择寄存器的开发方式,需要手动配置波特率寄存器来设置波特率,那么这些麻烦是你需要承受的。
  2. 如果你选择使用标准库开发方式,乃至于更上层的开发方式,这些寄存器操作就被封装了,程序员只需要调用函数即可自动设置波特率。

采样和采样时机

现在我们已经对波特率有了一个比较全面的认知:

串口通信的双方,通过约定波特率,确定了1个bit位数据在传输时,占用多长时间。

对于单片机而言,波特率就是,确定了1个bit位数据在传输时,占用多少个时钟周期。

对于串口通信而言,发送端想要发送1个bit位数据,只需要在既定时钟周期内,保持信号线的电平稳定。

那么对于接收端呢?接收端如何获取发送端发送的数据呢?

对于任何串行通信而言,接收端采集数据,确定接收bit1还是bit0,都是一个重要的事情。

下面我们来讲解一下串口通信在这方面的特点,为了方便解释,我们引入两个标准术语:

  1. 采样:接收端在某一确定时刻,对信号线上的电平状态进行读取,并将其判定为逻辑 0 或逻辑 1 的过程。
  2. 采样时机:通信协议所规定的,接收端执行采样操作的具体时刻、时间点。

以串口通信为例,描述这两个术语,说人话就是:

  1. 采样:接收端在某个时刻,查看Rx引脚的输入电平,从而得知对方到底发送了bit1还是bit0。
  2. 采样时机:接收端到底啥时候去看Rx引脚,输入电平状态。

搞懂了"采样"和"采样时机"这两个术语之后,下面我们就来看串口通信协议在采样与采样时机方面的具体规定。

串口通信属于异步通信,双方只约定了一个波特率,用于确定通信中一个bit位的时长。

由于没有稳定的时钟信号来同步时序,所以对于串口通信这种异步通信而言,每个bit位只采样一次是不够的,容易出现误差。

大多数USART硬件外设在实现采样时,都会在一个bit的中点附近采样多次,然后采用投票判决来确定最终数据。

比如:在一个bit的中点附近采样三次,其中2次是1,1次是0,那么数据最终是1。

这个过程详细描述如下:

  1. 接收端一旦检测到 Rx 引脚出现低电平,就认为可能出现起始信号,通信可能会开始。于是启动内部接收和定时逻辑。
  2. 接收端按照约定的波特率进行计时,在起始位持续时间的中间位置(也就是0.5bit的位置)附近,对 Rx 引脚输入进行多次采样。
  3. 如果经过多次采样并投票判决,最终结果为低电平,则确认起始位有效,通信正式开始。
  4. 以起始位为时间对齐基准:
    1. 后续的数据位和停止位,在每个bit的中间位置进行多次采样并投票判决,从而完成数据的正确接收。
    2. 每一个bit位数据,接收端都会采样多次,然后投票决定最终结果。
  5. 在串口通信中,这种"多次采样,然后投票判决"的采样方式,被称之为"过采样机制"。

如下图所示:

上述整个描述,我们可以记住以下精简结论:

串口通信的采样时机,是在每一个 bit 位数据持续时间的中点附近进行多次采样,然后投票判决得出最终结果。

那么接收端如何确定这个中间时刻呢?

接收端通过检测起始信号对齐时间,再加上双方约定好波特率,就能够找到0.5bit的时间位置,从而进行数据采样。

当然"过采样机制"本身是由USART硬件自动完成的,程序员在使用 USART 外设进行串口通信时,无需手动实现该机制。

波特率和通信速率

问题1:在串口通信中,最常使用的波特率是9600、115200。为什么波特率是这些特定的数值,而不是别的数值呢?

答:

  1. 这些波特率在常见的系统时钟频率下,都可以被波特率寄存器设置分频,较为精确的得到。这是一个很大的优势。
  2. 串口通信的设计目标不是追求高通信速度,而是更加追求实现简单、兼容性强且通信稳定。
  3. 115200 是在通信速度、稳定性和兼容性之间取得较好平衡的一个波特率。
  4. 115200,是串口通信在实际应用中的"默认波特率"!

问题2:串口通信确定波特率后,大约的通信速率如何进行计算?

以常见的115200波特率为例,每秒钟传输115200个bit数据,换算一下单位:

也就是:115200 / 8 / 1000 = 14.4KB/s

这就结束了吗?这就是通信速率吗?

当然不是,串口数据帧中的数据并非全都是有效数据。

以常见的"8N1"为例,有效数据是"8 / 10",停止位和起始位这两bit是无效数据。

所以波特率是115200,数据帧格式采用"8N1"的情况下,串口的通信速率是:

14.4 * (8 / 10) = 11.52KB/s

当然这是理论最大传输速率(协议层理论速率),实际总会存在一定损耗(软件与硬件层面都会存在损耗),应用时还需具体分析。

至此,关于USART外设波特率设置的相关原理,我们就搞清楚了,下面我们来研究一下USART外设处于数据帧的工作原理。

数据帧处理的工作原理

USART外设模块在处理数据帧发送和接收时,它的工作原理框图如下图所示:

USART外设在处理数据帧时:

  1. 单片机的USART外设在作为发送端时,存在一个"并->串"的过程,该过程由USART外设自行完成。
  2. 单片机的USART外设在作为接收端时,存在一个"串->并"的过程,该过程由USART外设自行完成。

由此看来,单片机的USART外设在实现串口通信时,帮我们完成了绝大多数工作。

这也是我们要使用USART硬件外设完成串口通信,而不是纯依靠软件模拟通信时序,实现通信的原因。

下面我们简单了解一下,USART是如何实现这些过程的,了解一下USART外设在通信中的工作。

USART外设在通信中的工作(了解)

以一个字节整数88的发送和接收过程举例,"并->串"的过程和"串 -> 并"的过程如下图所示:

具体到"并 -> 串"的过程

程序员将数据写入到发送数据寄存器中,USART外设的发送端内部电路,会自动将数据逐位移出,通过Tx引脚发送出去。

如下图所示:

仅通过移动寄存器的右移就可以实现数据,从最低有效位发送到最高有效位。

所以串口通信采用LSB First的比特序,硬件实现更简单。

具体到"串 -> 并"的过程

USART外设的接收端内部电路仍然通过移位寄存器的右移,简单地实现了这个过程。

并且最终将数据位存储到接收数据寄存器当中。

如下图所示:

当然,在串口通信中,USART外设做什么,我们只需要了解即可。

我们要把重点放在"程序员能做什么"上面,这样我们才能够使用USART外设实现串口通信。

程序员在通信中的工作(发送端)

基于USART外设实现串口通信,单片机作为发送端时,由程序员决定发送的数据是什么。

这是一句正确的废话!

再结合上面的工作原理图,在发送时,程序员真正需要执行的工作是:

将待发送的数据,写入发送数据寄存器当中,随后由USART外设自行实现"并 -> 串"将数据发送出去。

需要注意的是:

  1. 发送数据寄存器至多有9位有效空间,用于存储待发送数据帧的数据位。
  2. 发送数据寄存器属于硬件设备,USART外设将发送数据寄存器中的数据,完全发送出去是需要时间的。

那么问题来了:

直接无脑写发送数据寄存器,能够保证所有待发送数据都正确发送完成吗?

显然是不行的。

假如上一个数据位还没有发完,就立刻写发送数据寄存器,就会导致数据覆盖,从而丢失数据。

所以:

只能在确定发送数据寄存器为空的前提下,才能够写发送数据寄存器。

程序员在通信中的工作(接收端)

与之对应的,基于USART外设实现串口通信,单片机作为接收端时,由程序员决定读取接收的数据是什么。

这也是一句正确的废话!

前面讲过:

USART作为接收端时,会自动接收比特流,自动完成"串 -> 并"的过程,然后将接收到的数据位存储在接收数据寄存器!

接收数据寄存器同样是9位有效空间,用于临时缓存接收到的数据位。

所以:

在确定接收数据寄存器非空的前提下,程序员可以读接收数据寄存器,将接收到数据位读出来!

核心问题:如何判断寄存器状态

读到这里的同学,如果认真思考了,肯定会产生两个问题:

  1. 怎么知道USART外设的发送数据寄存器空了?
  2. 怎么知道USART外设的接收数据寄存器非空了?

如果不能解决上面两个问题,如果不能判断寄存器的状态,上面小节所讲的操作是无法实现的。

既然程序员有需求,那么单片机的设计生产厂商肯定需要满足这种需求。于是:

  1. 单片机的设计厂商会在某外设的硬件中,明确设置标志位寄存器,用于存储指示该外设各种运行状态的标志位。
  2. 基本上稍微复杂一些的外设都设有标志位寄存器,用于存储状态标志位,指示单片机运行状态。

比如USART外设中,就存在标志位寄存器,存储了一些指示状态的标志位。

USART外设三个常用标志位(重点)

使用USART外设时,三个最常用的,几乎绕不开的状态标志位如下表:

状态标志位名称 标志位的宏定义 英文全称及含义
TXE USART_FLAG_TXE 发送数据寄存器为空 (Transmit Data Register Empty)
TC USART_FLAG_TC 发送完成 (Transmission Complete)
RXNE USART_FLAG_RXNE 接收数据寄存器非空 (Receive Data Register Not Empty)

首先状态标志位是存在取值的,且取值只有两种:

  1. 1,即标志位名称指示的条件已经满足。
    1. SPL库用SET这个枚举值表示1,注意编码时要使用SET,不要使用魔法数1。
    2. 标志位取值为1时,通常称为置1、置位或者干脆就叫SET。
  2. 0,即标志位名称指示的条件还未满足。
    1. SPL库用RESET这个枚举值表示0,注意编码时要使用RESET,不要使用魔法数0。
    2. 标志位取值为0时,通常称为置0、复位、清零、清除或者干脆就叫RESET。

解释一下每一个标志位:

状态标志位TXE:

  1. 标志位本身翻译成中文,意为:发送数据寄存器是空的。
  2. 用于指示,发送数据寄存器是否为空的状态。
  3. TXE标志位置位时,表示发送数据寄存器已经是空的了。
  4. TXE标志位复位时,表示发送数据寄存器不是空的。

状态标志位TC:

  1. 标志位本身翻译成中文,意为:发送完成、数据传输完成。
  2. 注意,发送数据寄存器为空,并不意味着传输已经完成,因为数据可能还在移位寄存器中移位发送。
  3. TC标志位置位时,表示发送数据寄存器和发送端移位寄存器都为空,数据已经完全发送完毕了。
  4. TC标志位复位时,表示发送数据寄存器和发送端移位寄存器任一不为空。

状态标志位RXNE:

  1. 标志位本身翻译成中文,意为:接收数据寄存器非空
  2. 用于指示,接收数据寄存器是否为非空的状态。
  3. RXNE标志位置位时,表示接收数据寄存器是非空状态。
  4. RXNE标志位复位时,表示接收数据寄存器还是空的。

PC和STM32的串口

串口通信是两台支持串口通信协议的设备之间的通信。此时只需要将它们各自的Tx引脚和Rx引脚连接起来,就可以实现双工通信。如下图所示:

现在我们已经了解了串口通信协议、以及单片机USART外设模块的工作原理。

现在我们期望使用SPL库,编程实现PC与STM32之间的串口双向通信,抛开具体使用什么函数,这种软件层面上的问题不谈。

硬件上面我们还有以下问题没有解决:

  1. PC端的串口接口在哪里?
  2. STM32的串口引脚在哪里?
  3. 如何设置和使用STM32的串口引脚?

这些问题,我们逐一来解释解决。

PC的串口

在最早期的PC计算机(台式机)上,主板通常还会配备串口,可以直接用于串口通信。

但现代PC计算机上早已不再自带传统的串口,因此我们需要借助额外的硬件设备来扩展串口功能。

最常见的就是USB 转 TTL 串口模块,它将 PC 的 USB 信号转换为 UART(TTL 电平)信号,使其可以与 STM32 进行串口通信。

在前面的课程内容中,我们已经介绍过USB转串口设备了。下面是直接复制过来的内容:

USB转TTL设备 是一种将计算机的USB接口转换为TTL电平的串行通信(UART协议)的设备,也有很多人把它叫做USB转串口设备

TTL(Transistor-Transistor Logic)电平是啥意思呢?

如下表所示:

逻辑状态 5V TTL(典型) 3.3V TTL(典型)
高电平(逻辑 1) 2V ~ 5V (通常为 5V 2V ~ 3.3V (通常为 3.3V
低电平(逻辑 0) 0V ~ 0.8V (通常为 0V 0V ~ 0.8V (通常为 0V

简单来说:

  1. 高电平(逻辑 1) :对于 5V TTL ,通常是 5V ;对于 3.3V TTL ,通常是 3.3V
  2. 低电平(逻辑 0) :一般是 0V(接地)。

USB转TTL串口设备,常用于PC和嵌入式设备单片机之间的串行通信。

USB转TTL可以完成ST-LINK的功能,比如供电,下载调试程序等,但我们主要使用它来进行UART串口通信。

使用USB转串口设备,可以使得没有串行接口的现代计算机能够与需要串行通信的设备(如微控制器、调试工具、工业设备等)进行连接和通信,在嵌入式领域的开发调试中非常常用。

我们使用的USB转TTL是基于CH340G芯片的,如下图所示:

大家可以找出配件盒当中USB转串口设备。

在USB接口的另一边有很多针脚,这些针脚中就包括Tx和Rx针脚(多了一个D字母是Data的意思,表示发送和接收数据)。

如果你之前已经安装过USB转串口驱动,那么在设备管理器中就可以看到以下内容:

可以先按下 "Win + R" 组合键,打开 "运行" 对话框,然后在对话框中输入"devmgmt.msc",最后回车。

如果打开设备管理器,在"其他设备"中看到"带有问号的USB Serial"设备,则表示此计算机还没有安装USB转TTL驱动。

安装完成后,在设备管理器中能够正常识别此设备即可:

以上即完成PC设备端的串口通信准备。

下面更重要的问题是STM32的串口通信引脚在哪里呢?

USART模块的引脚

STM32F103C8T6提供的USART外设模块的引脚一共有五种,如下图所示:

其中Rx和Tx引脚自不必多说。

硬件流控的两个引脚:

  1. RTS由接收端控制,决定是否允许对方发送数据,连接到发送端的CTS。
  2. CTS由发送端控制,决定是否允许自己发送数据,连接到接收端的RTS。

硬件流控适合在高速、大数据传输时保证数据传输稳定性,防止丢包,一般用于WiFi、4G/5G数据通信、蓝牙通信等场景。

在PC和单片机通信时,没有使用的必要。

CK同步时钟线则在USART当做同步串行通信时使用,用于同步设备间的时钟信号,我们也不需要用。

所以我们只需要关心STM32的Rx和Tx引脚就可以了。

那么这两个引脚在哪里呢?

STM32的Tx和Rx引脚

在之前我们已经学习过STM32F103C8T6的引脚分布图了,如下图所示:

其中有颜色标注的地方都是特殊引脚,那么其它位置的引脚中能找到Rx和Tx引脚吗?

显然是没有的。

那这两个引脚在哪里呢?

不要着急,我们慢慢分析。

STM32自带的USART外设模块不止一个,如下图所示:

前面已经讲过我们使用的单片机STM32F103C8T6,一共有3个USART外设,分别是USART1、USART2以及USART3。

其中我们优先使用USART1外设。

于是我们就进一步确定了,我们要使用的两个引脚为:USART1_Tx和USART1_Rx

引脚分布图上找不到这两个引脚,但是不要忘记了,引脚分布图上画出来的都是引脚的主功能,但引脚还有复用功能和重定义功能。

具体的引脚定义表可以参考之前的文档:引脚定义表

PA9引脚的工作模式设置

这里仅截取了PA9引脚和USART1_Tx功能相关的部分,如下图所示:

也就是说:PA9引脚,设置为复用工作模式,它可以充当USART1_Tx功能,即串口的发送端。

那么这个工作模式具体是什么模式呢?

首先,发送端的作用是输出高低电平,从而传输数据,所以它必然要使用输出向的工作模式。而输出向的工作模式,我们已经学习过两个了:

  1. 通用推挽输出模式
  2. 通用开漏输出模式

能不能用这两个模式呢?

显然不行,将PA9设置为这两个工作模式,它们就是普通的IO口,不是USART1_Tx功能。

于是我们想到了两个复用输出模式:

  1. 复用推挽输出模式
  2. 复用开漏输出模式

这两个所谓复用输出模式,我们之前没有讲,但实际上非常简单。查看引脚内部电路图,复用输出模式相关的电路如下图所示:

也就是说,通用输出和复用输出的核心差异在于引脚控制权的归属

  1. 通用输出模式:由 GPIO 外设 自己直接控制,程序通过写入 GPIO_ODR(输出数据寄存器) 来改变引脚电平。
  2. 复用输出模式:GPIO 将控制权 交给其他外设(如 USART、SPI、I2C 等),这些外设控制 GPIO 输出高低电平。

它们的核心区别在于控制权的变化,但推挽模式和开漏模式的本质没有变化:

  1. 推挽输出模式仍然是上下两个MOS管交替导通,引脚可以输出高电平和低电平。
    1. 通用推挽输出,输出的控制权在GPIO自身,输出数据寄存器写1就输出高电平,写0就输出低电平。
    2. 复用推挽输出,输出的控制权在其它外设模块,其它外设输出1,引脚就输出高电平,输出0,引脚就输出低电平。
  2. 开漏输出模式仍然是PMOS管始终断开,控制NMOS管的开闭,从而使得引脚可以输出低电平和高阻抗状态。
    1. 通用开漏输出,输出的控制权在GPIO自身,输出数据寄存器写1就输出高阻抗,写0就输出低电平。
    2. 复用开漏输出,输出的控制权在其它外设模块,其它外设输出1,引脚就输出高阻抗,输出0,引脚就输出低电平。

看了这么一段描述后,PA9应该设置为哪个复用输出模式,才能够支撑USART1_Tx的功能呢?

显然需要选择复用推挽输出模式,因为USART1_Tx需要直接输出高低电平,这显然是开漏模式做不到的。

而且应该把引脚的控制权交给USART1外设,而不是GPIO自己。

当然,这里还有一个小问题:

根据《引脚定义表》,PA9引脚实现复用功能时,引脚控制权可能是USART1外设,也可能是TIM1外设,那究竟属于哪个外设呢?

这个问题我们前面也讲解过了,哪一个外设开启控制权就属于谁,禁止同时开启两个外设!

于是,我们最终得出结论:

将PA9引脚设置为复用推挽输出模式(GPIO_Mode_AF_PP),并且开启USART1外设,此时PA9引脚功能就是USART1_Tx。

尤其是要注意:

USART1外设在使用上,除了类似GPIO的初始化外,还需要多一步手动开启的操作!!!

PA10引脚的工作模式设置

这里仅截取了PA10引脚和USART1_Rx功能相关的部分,如下图所示:

那么PA10引脚应该设置为什么工作模式呢?

首先肯定需要设置为输入模式,因为Rx引脚的作用是读取发送端输出的高低电平。

单片机的引脚输出是主动向操作,需要某个外设来控制输出,于是引脚的输出模式就有控制权问题,输出模式就存在通用和复用的区别!

但引脚的输入显然是一种被动向操作,不存在所谓控制权的概念:

  1. GPIO外设可以读输入,其它外设也可以同时读。
  2. 所以引脚的输入模式,没有通用和复用的区别。

其实这一点,我们仍然可以通过阅读,引脚输入模式的电路图来理解:

IO引脚只是负责将外部电平送到芯片内部,至于哪个外设来读这个电平都是可以的,而且允许同时读:

  1. GPIO外设读输入电平,其实就是读GPIO外设的输入数据寄存器,这个操作我们之前做过很多次了。
  2. 其它外设的内部电路,也会根据自己内部电路的处理来读取外部电平输入。

举一个例子:

对于PA10引脚来说,根据《引脚定义表》,此时GPIO、USART1以及TIM1三个外设都可以读PA10引脚输入,而且可以同时读。

问题1:PA10引脚需要设置为"复用"输入模式吗?

答:输入模式没有控制权的概念,输入模式也不存在"复用",只需要根据情况设置普通的输入模式即可。

问题2:引脚的复用输出模式时,开启哪一个外设引脚的输出控制权就在于谁,PA10引脚也需要开启谁就让谁读吗?

答:不需要,通过查看电路,谁想读都可以。PA10引脚的输入,理论上允许三个外设一起读。

问题3:真的可能出现两个,甚至多个外设一起读PA10引脚输入的情况吗?

答:理论上可以,硬件电路上允许,但实际上大概率不会。

每一种外设都有自身需要的引脚输入,USART1外设需要读数据帧的输入,其它外设肯定不需要读串口的数据帧。

所以,从实际情况出发,引脚的输入,在同一时刻,大概率还是仅有一个外设在读,哪怕理论上允许多个外设读取。

问题4:USART1外设需要读PA10引脚输入,那么PA10引脚应该设置为什么输入模式呢?

关于PA10输入模式,现在我们以下三个选项:浮空输入模式、上拉输入模式以及下拉输入模式。

答:

首先排除下拉输入模式,下拉输入模式时,引脚默认输入一个低电平。

而低电平是串口通信的数据帧起始位,如果选择下拉输入模式会导致接收端始终处于要接收数据的状态,显然不合理。

而浮空输入模式和上拉输入模式都可以选择使用:

  1. 可以选择浮空输入模式,因为浮空输入模式引脚默认不会输入任何电平,不会对输入电平产生影响。
  2. 上拉输入模式默认输入高电平,而高电平作为数据帧的空闲位,对串口而言一般不会造成什么影响,所以也是可以选择的。

上拉输入模式还有一个有趣的功能:

假设单片机Rx引脚和PC端的Tx引脚,存在断开或接线不稳定的情况,此时将PA10设置为上拉输入模式:

PA10引脚将在断路时保持默认高电平输入,而高电平是串口通信规定的空闲状态。

所以PA10引脚使用上拉输入模式,可以在接线断路时,避免因误检测杂乱信号导致的通信乱码问题。

总之,在接线可能会断开的场景中,可以优先考虑PA10引脚使用上拉输入模式,否则就将PA10设置为浮空输入模式即可。

相关推荐
栉甜1 小时前
APIs学习
前端·javascript·css·学习·html
吃好睡好便好2 小时前
说说梳头的保健作用
学习
都在酒里2 小时前
STM32标准库驱动L298N双H桥电机驱动模块(调速/正反转/多模式实战,附完整工程代码)
stm32·单片机·嵌入式硬件
wuxinyan1232 小时前
工业级大模型学习之路013:RAG零基础入门教程(第九篇):RAG幻觉治理
人工智能·学习·rag
Hello_Embed2 小时前
USB 学习指南+软硬件框架
网络·笔记·stm32·嵌入式·ai编程
99乘法口诀万物皆可变2 小时前
Simscape 学习路径图:从入门到精通的多物理域仿真指南
学习
wuxinyan1232 小时前
工业级大模型学习之路015:RAG零基础入门教程(第十一篇):系统重构与代码规范化
人工智能·python·学习·重构·rag
网络与设备以及操作系统学习使用者2 小时前
ARP报文保护触发与解决详解
运维·网络·学习·华为
wuxinyan1232 小时前
工业级大模型学习之路014:RAG零基础入门教程(第十篇):系统性能与资源优化
人工智能·学习·rag