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设置为浮空输入模式即可。

相关推荐
一路往蓝-Anbo4 小时前
第三篇:ADC 与模拟前端
stm32·嵌入式硬件·嵌入式·硬件设计
努力小周7 小时前
STM32智能安防系统
c语言·stm32·单片机·嵌入式硬件·物联网·计算机网络·pcb工艺
袁小皮皮不皮8 小时前
1.HCIP BFD 学习笔记(优化版)
服务器·网络·笔记·网络协议·学习·智能路由器·ip
装不满的克莱因瓶8 小时前
【自动驾驶领域】学习 Cityscapes 数据集——城市街景语义理解的标准基准
人工智能·pytorch·python·深度学习·学习·机器学习·自动驾驶
清辞8539 小时前
产品经理需求推进流程
大数据·深度学习·学习·产品经理
华科大胡子9 小时前
在STM32上跑通TinyML
stm32·单片机·嵌入式硬件
YM52e9 小时前
鸿蒙PC ArkTS 声明合并问题深度解析与最佳实践
学习·华为·harmonyos·鸿蒙·鸿蒙系统
海兰10 小时前
【实用程序】电商销售分析仪表盘 — 从零搭建一个AI参与的全栈数据洞察系统
人工智能·学习·算法
iCxhust10 小时前
C#进程管理程序
开发语言·汇编·stm32·单片机·c#·微机原理
ken223211 小时前
在 Libreoffice Calc中输入自定义表情字符时,需要保存之后,才能正常显示
学习