通过前面学习,我们基本掌握了 STM32F4 的 IO 口操作,那么要想让单片机与外接设备通信该怎么办呢? 接下来就是我们需要学习的。在计算机设备与设备之间或集成电路之间常常需要进行数据传输,这一次我们将学习 STM32F4 的串口,教大家如何使用 STM32F4 的串口来发送和接收数据。
目录
一、数据通信的基础概念
1.1 数据通信的基础概念
简单理解数据通信是指计算机与计算机之间以及计算机与其他外部设备之间的信息交换。 在单片机的应用中,数据通信是必不可少的一部分,比如:单片机和上位机、单片机和外围器件之间,它们都有数据通信的需求。由于设备之间的电气特性、传输速率、可靠性要求各不相同,于是就有了各种通信类型、通信协议,我们最常见的有:USART、IIC、SPI、CAN、 USB 等。这也是面试的重点,我们知道 STM32F4 芯片内含有非常多的通信接口,学习这些通信接口前,我们很有必要了解下通信的基本概念。
1.1.1 通信的目的
将一个设备的数据传送到另一个设备,扩展硬件系统。
1.1.2 通信协议
制定通信的规则,通信双方按照协议规则进行数据收发。
1.2 数据通信的分类
数据通信的方式可以分为多种:
- 按照数据传送方式可分为串行通信和并行通信。
- 按照数据通信的数据同步方式,可分为异步通信和同步通信。
- 按照数据的传输方向又可分为单工、半双工和全双工通信。
下面我们就来简单介绍这几种通信方式。
1.2.1 串行通信和并行通信
按数据通信方式分类,可分为串行通信和并行通信两种。
串行通信: 串行通信是指使用一条数据线,将数据一位一位地依次传输,每一位数据占据一个固定的时间长度。
其只需要少数几条线就可以在系统间交换信息,特别适用于计算机与计算机、计算机与外设之间的远距离通信。如下图所示,串行通信的特点:传输线少,长距离传送时成本低,且可以利用电话网等现成的设备,但数据的传送控制比并行通信复杂。
并行通信:并行通信通常是将数据字节的各位用多条数据线同时进行传送,通常是 8 位、16 位、32 位等数据一起传输。
如下图所示,并行通信的特点:控制简单、传输速度快;由于传输线较多,长距离传送时成本高且接收方的各位同时接收存在困难,抗干扰能力差。
可以这么理解:并行通信就像多个车道的公路,可以同时传输多个数据位的数据,而串行通信就像单个车道的公路,同一时刻只能传输一个数据位的数据。如下图所示:
很明显,因为一次可传输多个数据位的数据 ,在数据传输速率相同的情况下,并行通讯传输的数据量要大得多,而串行通讯则可以节省数据线的硬件成本(特别是远距离时)以及 PCB 的布线面积,串行通讯与并行通讯的特性对比见表。
不过由于并行传输对同步要求较高,且随着通讯速率的提高,信号干扰的问题会显著影响通讯性能,现在随着技术的发展,越来越多的应用场合采用高速率的串行差分传输。
1.2.2 异步通信和同步通信
根据数据同步方式,通信又可分为同步通信和异步通信。可以根据通讯过程中是否有使用到时钟信号进行简单的区分。
同步通信:同步通信要求通信双方共用同一时钟信号,在总线上保持统一的时序和周期完成信息传输。
在同步通信中,收发设备双方共用一根信号线表示时钟信号,在时钟信号的驱动下双方进行协调,同步数据,见图。通讯中通常双方会统一规定在时钟信号的上升沿或下降沿对数据线进行采样。优点:可以实现高速率、大容量的数据传输,以及点对多点传输。缺点:要求发送时钟和接收 时钟保持严格同步,收发双方时钟允许的误差较小,同时硬件复杂。
⼀根数据线,⼀根时钟信号线。⽽且还可以看到⼀个时钟周期T标示了1位数据的宽度。由于数据传送是由发送端发出、接收端接收的,因此,发送和接受的双方必须有一个统一的T。我们把这个固定时间长度T称为串行通信的同步时钟信号。
异步通信:异步通信不需要时钟信号,而是在数据信号中加入开始位和停止位等一些同步信号,以便 使接收端能够正确地将每一个字符接收下来,某些通信中还需要双方约定传输速率。
在异步通信中不使用时钟信号进行数据同步,它们直接在数据信号中穿插一些同步用的信号位如起始位和停止位,以便使接收端能够正确地将每一个字符接收下来,或者把主体数据进行打包,以数据帧的格式传输数据,见图,某些通讯中还需要双方约定数据的传输速率,以便更好地同步。优点:没有时钟信号,硬件简单,双方时钟可允许一定误差。缺点:通信速率较低,只适用点对点传输。
- 起始位:起同步作⽤ 通信线路上没有数据传输时⼀般处于逻辑“1”的状态即高电平。当发送设备有数据发送时,它⾸先发送⼀个逻辑“0”信号,这个低电平的逻辑“0”就是起始位。当接收设备检测到这个低电平后,就开始准备接收数据位信号。
- 数据位: 表示要传输的数据。⼀般为8位,可⽤于传输⼀个字节的数据。
- 校验位: 该位由⽤户决定,有三种选择:⽆校验、奇校验、偶校验 奇偶校验:让原有数据序列中,包括要加上的⼀位1的个数为奇数或偶数。
- 停⽌位: ⽤来标识我⼀帧数据的结束。可以是1位、1.5位、或2位⾼电平。这样通信线路⼜回到逻辑1的状态。
⼩结:在异步通信中,数据帧常采⽤1位起始位、8位数据位、⽆校验位、1位停⽌位这样的格 式。这样⼀个数据帧⻓度为10位。
同步通信:共用同一时钟信号
异步通信:没有时钟信号,通过在数据信号中加入起始位和停止位等一些同步信号
1.2.3 单工、半双工与全双工通信
根据数据传输方向,通信又可分为全双工、半双工和单工通信。它们主要以信道的方向来区分,见图及表。
- 单工是指数据传输仅能沿一个方向,不能实现反方向传输,如校园广播。
- 半双工是指数据传输可以沿着两个方向,但是需要分时进行,如对讲机。
- 全双工是指数据可以同时进行双向传输,日常的打电话属于这种情形。
- 这里注意全双工和半双工通信的区别:半双工通信是共用一条线路实现双向通信,如IIC通信,而全双工是利用两条线路,一条用于发送数据,另一条用于接收数据,如串口通信和SPI通信。
仍以公路来类比,全双工的通讯就是一个双向车道,两个方向上的车流互不相干;半双工则像乡间小道那样,同一时刻只能让一辆小车通过,另一方向的来车只能等待道路空出来时才能经过;而单工则像单行道,另一方向的车辆完全禁止通行。
1.3 波特率
衡量通信性能的一个非常重要的参数就是通信速率,通常以比特率(Bitrate) 来表示。比特率是每秒钟传输二进制代码的位数,单位是:位/秒(bps)。如每秒钟传送 240 个字符,而每个字符格式包含 10 位(1 个起始位、1 个停止位、8 个数据位),这时的比特率为: 10 位×240 个/秒 = 2400 bps。在后面会遇到一个“波特率”的概念,它表示每秒钟传输了多少个码元。而码元是通信信号调制的概念,通信中常用时间间隔相同的符号来表示一个二进制数字,这样的信号称为码元。如常见的通信传输中,用 0V 表示数字 0, 5V 表 示数字 1,那么一个码元可以表示两种状态 0 和 1,所以一个码元等于一个二进制比特位,此时波特率的大小与比特率一致;如果在通信传输中,有 0V、2V、 4V 以及 6V 分别表示二进制数 00、 01、 10、 11,那么每个码元可以表示四种状态,即两个二进制比特位,所以码元数是二进制比特位数的一半,这个时候的波特率为比特率的一半。由于很多常见的通信中一个码元都是表示两种状态, 所以我们常常直接以波特率来表示比特率。可以看出采用二进制的时候,波特率和比特率数值上相等。但是这里要注意,它们的相等 只是数值相等,其意义上不同,看波特率和波特率单位就知道。由于我们的所用的数字系统都是二进制的,所以有部分人久而久之就直接把波特率和比特率混淆了。
1.3.1 定义
每秒种传送二进制数码的位数(对于二进制系统,码元数等于二进制位比特数),单位为Baud。
1.3.2 大小
通常,异步通信所采用的标准波特率数值为1200的倍数,如2400、4800、9600、115200 等。
1.3.3 在异步通信中的重要性
①收发双方要实现正常的通信,必须采用相同的波特率和相同的数据帧格式。②有了相同的波特率,位宽(传送一位码元所用的时间)也就确定了,这样才能准确的获得帧格式中的各个部分。
1.4 常见串行通信接口
二、串口(RS232)
串口是一种应用十分广泛的通讯接口,串口成本低、容易使用、通信线路简单,可实现两个设备的互相通信,单片机的串口可以使单片机与单片机、单片机与电脑、单片机与各式各样的模块互相通信,极大地扩展了单片机的应用范围,增强了单片机系统的硬件实力。串口作为 MCU 的重要外部接口,同时也是软件开发重要的调试手段,其重要性不言而喻。 现在基本上所有的 MCU 都会带有串口,STM32 自然也不例外。探索者 STM32F4 开发板板载了 1 个 USB 串口和 2 个 RS232 串口,我们本次介绍的是通过 USB 串口和电脑通信。
2.1 通信接口
2.1.1 概念
通信接口是一种规范:①硬件上的规范:规范通信设备之间连接的物理形式、电气信号(电平标准)。②软件规范:通信协议、数据格式。
2.1.2 作用
将来自发送方的数字信号转换成适合传输介质的信号并送到接收方,同时将接收方传回来的信息转换成适合发送方处理的数字信号。决定了两个设备之间能否互连和通信。
2.1.3 通信接口类型
- USB接口:应用很广泛
- 以太网接口:网口
- 无线接口: 蓝牙、WI-FI、NFC
- 串行接口/并行接口
2.2 串口通信简介
2.2.1 什么是串口通信?
串口通信(Serial Communication)设备间通过使用串行接口来通信称之为串口通信,外设和计算机间,按位进行传输数据的一种通信方式,属于串行通信方式。串口(串行通信接口)是一种通信接口标准,它规定了接口的电气标准,没有规定接口插件电缆以及使用的协议。
2.2.2 串口通信协议简介
在计算机科学里,大部分复杂的问题都可以通过分层来简化。如芯片被分为内核层和片上外设;STM32 标准库则是在寄存器与用户代码之间的软件层。对于通讯协议,我们也以分层的方式来理解,最基本的是把它分为物理层和协议层。
- 物理层:规定通讯系统中具有机械、电子功能部分的特性,确保原始数据在物理媒体的传输。
- 协议层:主要规定通讯逻辑, 统一收发双方的数据打包、解包标准。简单来说物理层规定我们用嘴巴还是用肢体来交流, 协议层则规定我们用中文还是英文来交流。
下面我们分别对串口通信协议的物理层及协议层进行讲解。
1. 串口通信协议的物理层:规定了通信的接⼝和信号的电平标准。
电平标准: 电平标准是数据1和数据0的表达方式,是传输电缆中人为规定的电压与数据的对应关系。
串口通信的接口标准有很多,有 RS-232C、RS-232、RS-422A、RS-485 等。 常用的就是 RS-232(DB9) 和 RS-485。我们以RS232为例,它的接口如下:
但是,现在我们在工业控制中使⽤的串⼝通信,⼀般只使用RXD、TXD 和GND三条信号线,裁剪掉了有关同步的信号线,构成了常用的异步串口通信。 如下图所示
常用的电压标准如下:
从上面可以看出来,RS-232 是用正负电压来表示逻辑状态,与晶体管-晶体管逻辑集成电路(TTL)以高低电平表示逻辑状态的规定正好相反,二者的电平标准不相同,不能直接进行通信。而我们 STM32 芯片使用的就是 TTL 电平,所以要实现 STM32 与计算机的串口通信(假设老式的计算机是RS232接口),需要进行 TTL与 RS-232电平转换,通常使用的电平转换芯片MAX3232。通信的示意图如下所示:
随着科技的发展,RS-232 在工业上还有广泛的使用,但是在商业技术上,已经慢慢的使用 USB 转串口取代了 RS-232 串口。我们只需要在电路中添加一个 USB 转串口芯片,就可以实现 USB 通信协议和标准 UART 串行通信协议的转换,而我们开发板上的 USB 转串口芯片是 CH340C 这个芯片,它们的通信示意图如下所示。
2. 串口通信协议的协议层:在串口通讯的协议层中,规定了数据包的内容,它由起始位、主体数据、校验位以及停止位组成,通讯双方的数据包格式要约定一致才能正常收发数据,其组成见图。
串口通信协议数据包组成可以分为波特率和数据帧格式两部分接下来讲解串口通信协议:
1. 波特率
本章主要讲解的是串口异步通信,异步通信是不需要时钟信号的,但是这里需要我们约定好两个设备的波特率。波特率表示每秒钟传送的码元符号的个数,所以它决定了数据帧里面每 一个位的时间长度。两个要通信的设备的波特率一定要设置相同,我们常见的波特率是 4800、 9600、115200 等。
2. 数据帧格式
数据帧格式需要我们提前约定好,串口通信的数据帧包括起始位、停止位、有效数据位以 及校验位。
⚫ 起始位和停止位
串口通信的一个数据帧是从起始位开始,直到停止位。数据帧中的起始位是由一个逻辑 0 的数据位表示,而数据帧的停止位可以是 0.5、1、1.5 或 2 个逻辑 1 的数据位表示,只要双方约 定一致即可。
⚫ 有效数据位
数据帧的起始位之后,就接着是数据位,也称有效数据位,这就是我们真正需要的数据, 有效数据位通常会被约定为 5、6、7 或者 8 个位长。有效数据位是低位(LSB)在前,高位(MSB) 在后,也就是先发低位再发高位。
⚫ 校验位
校验位可以认为是一个特殊的数据位。校验位一般用来判断接收的数据位有无错误,检验方法有:奇检验、偶检验、0 检验、1 检验以及无检验。下面分别介绍一下:
- 奇校验是指有效数据为和校验位中“1”的个数为奇数,比如一个 8 位长的有效数据为: 10101001,总共有 4 个“1”,为达到奇校验效果,校验位设置为“1”,最后传输的数据是 8 位 的有效数据加上 1 位的校验位总共 9 位。
- 偶校验与奇校验要求刚好相反,要求帧数据和校验位中“1”的个数为偶数,比如数据帧:11001010,此时数据帧“1”的个数为 4 个,所以偶校验位为“0”。
- 0 校验是指不管有效数据中的内容是什么,校验位总为“0”,1 校验是校验位总为“1”。
- 无校验是指数据帧中不包含校验位。 我们一般是使用无校验的情况。
2.3 串口通信的实际作用
USART 只需两根信号线即可完成双向通信,对硬件要求低,使得很多模块都预留 USART 接口来实现与其他模块或者控制器进行数据传输,比如 GSM 模块,WIFI 模块、蓝 牙模块等等。在硬件设计时,注意还需要一根“共地线”。 我们经常使用 USART 来实现控制器与电脑之间的数据传输。这使得我们调试程序非常方便,比如我们可以把一些变量的值、函数的返回值、寄存器标志位等等通过 USART 发送到串口调试助手,这样我们可以非常清楚程序的运行状态,当我们正式发布程序时再 把这些调试信息去除即可。 我们不仅仅可以将数据发送到串口调试助手,我们还可以在串口调试助手发送数据给 控制器,控制器程序根据接收到的数据进行下一步工作。
三、STM32F4 的 USART 介绍
3.1 STM32F407 的串口简介
STM32F4 的串口分为两种:USART(即通用同步异步收发器)和 UART(即通用异步收发 器)。UART 是在 USART 基础上裁剪掉了同步通信功能,只剩下异步通信功能。USART/UART都可以与外部设备进行全双工异步通信,简单区分同步和异步就是看通信时需不需要对外提供时钟输出,我们平时用串口通信基本都是异步通信,不需要提供时钟信号,只要波特率双方协定一致,就可以正常通信。 STM32 的串口输出的是 TTL 电平信号,如果需要 RS-232 标准的信号可使用 MAX3232 芯 片进行转换,而我们是通过 USB 转串口芯片 CH340C 来与电脑的上位机进行通信。
USART 在 STM32 中应用最多的是 printf 输出调试信息,当我们需要了解程序内的一些变量数据信息时,可以通过 printf 输出函数将这些信息打印到串口助手上显示,这样一来就给我们调试程序带来了极大的方便。
3.2 STM32的USART的功能简介
3.2.1 概念
USART(Universal Synchronous/Asynchronous Receiver/Transmitter)通⽤同步/异步收发器。 是串口通信的硬件支持电路。它同样是一个外设,最常使用的是UART(通⽤异步收发器)。
3.2.2 作用过程
USART是STM32内部集成的硬件外设,可根据数据寄存器的一个字节数据按照协议规范⾃动生成数据帧时序,从TX引脚发送出去,也可自动接收RX引脚的数据帧时序,按照协议规范拼接为一个字节数据,存放在数据寄存器里。使用USART就直接读写发送和接收数据寄存器即可。
3.2.3 特点
自带波特率发生器。-----其实质类似于时钟分频。可配置数据长度(8位、9位),停止位⻓度(1、1.5、2 5)可选校验位,支持同步模式、硬件流控(防止接收端接受慢而丢失数据)。
3.2.4 STM32F407的USART/UART资源
STM32F4 有 4 个 USART 和 2 个 UART,其中 USART1 和 USART6 的时钟源来于 APB2 时钟,其最大频率为84MHz,其他4个串口的时钟源可以来于APB1时钟,其最大频率为42MHz。
3.3 USART框图(非常重要)
STM32 的 USART 功能框图包含了 USART 最核心内容,掌握了功能框图,对 USART 就有一个整体的把握,在编程时就思路就非常清晰,见图。
从上面的框图我们知道:CPU向数据寄存器写入要发送的值或者读取数据寄存器的值外在表现其实就是一个,但是其实内部可以等同于两个寄存器使用:
为了方便大家理解,我们把整个框图分成几个部分来介绍:
3.3.1 功能引脚
3.3.2 数据寄存器(重点)
USART 数据寄存器(USART_DR)只有低 9 位有效,并且第 9 位数据是否有效要取决于 USART 控制寄存器 1(USART_CR1)的 M 位设置,当 M 位为 0 时表示 8 位数据字长,当 M 位为 1 表示 9 位数据字长,我们一般使用 8 位数据字长。 USART_DR 包含了已发送的数据或者接收到的数据。USART_DR 实际是包含了两个寄存器,一个专门用于发送的可写 TDR,一个专门用于接收的可读 RDR。当进行发送操作时,往 USART_DR 写入数据会自动存储在 TDR 内;当进行读取操作时,向 USART_DR 读取数据会自动提取 RDR 数据。 TDR 和 RDR 都是介于系统总线和移位寄存器之间。串行通信是一个位一个位传输的, 发送时把 TDR 内容转移到发送移位寄存器,然后把移位寄存器数据每一位发送出去,接收 时把接收到的每一位顺序保存在接收移位寄存器内然后才转移到 RDR。 USART 支持 DMA 传输,可以实现高速数据传输,具体 DMA 使用将在 DMA 章节讲 解。
下面这张图也非常重要理解理解!!
3.3.3控制单元(重点)
USART 有专门控制发送的发送器、控制接收的接收器,还有唤醒单元、中断控制等等。 使用 USART 之前需要向 USART_CR1 寄存器的 UE 位置 1 使能 USART。发送或者接收数 据字长可选 8 位或 9 位,由 USART_CR1 的 M 位控制。
发送器
发送器根据M位的状态发送8位或9位的数据字。当发送使能位(TE)被设置时,发送移位寄存器中的数据在TX脚上输出,相应的时钟脉冲 在CK脚上输出。一个字符帧发送需要三个部分:起始位+数据帧(可能有校验位)+停止位。每个字符(一个数据帧)之前都有一个高电平的起始位,之后跟着的停止位,其数目可配置,数据帧就是我们要发送的 8 位或 9 位数据,数据是从最低位开始传输的,停止位是一定时间周期的高电平。
配置步骤:
深入理解TXE位与TC位:
清零TXE位总是通过对数据寄存器的写操作(CPU 或 DMA)来完成的,当TXE位已经被硬件置1它表明:
- 数据已经从TDR移送到移位寄存器,数据发送已经开始(发送移位寄存器正在一位一位向外传输数据)
- TDR寄存器被清空
- 下一个数据可以被写进USART_DR寄存器而不会覆盖先前的数据,如果TXEIE位被设置,此标志将产生一个中断。
如果此时USART正在发送数据(发送移位寄存器正在一位一位向外传输数据),对USART_DR寄存器的写操作把数据存进TDR寄存器,并在当前传输结束时把该数据复制进移位寄存器,也就是说移位寄存器里面的数据并不会被覆盖,所以我觉得只要你发送一帧数据等待TXE置1,就算是发送多帧数据时最后也不用等待TC=1。如果此时USART没有在发送数据,处于空闲状态,对USART_DR寄存器的写操作直接把数据放进移位寄存器,数据传输开始,TXE位立即被置起。
当一帧发送完成时(停止位发送后)并且设置了TXE位,TC位被置起,如果USART_CR1寄存器中的TCIE位被置起时,则会产生中断:
使用下列软件过程清除TC位:
1.读一次USART_SR寄存器;
2.写一次USART_DR寄存器。
TC位也可以通过软件对它写’0’来清除。此清零方式只推荐在多缓冲器通信模式下使用
在发送数据时,编程的时候有几个比较重要的标志位我们来总结下:
接收器
如果将 USART_CR1 寄存器的 RE 位置 1,使能 USART 接收,使得接收器在 RX 线开始搜索起始位。在确定到起始位后就根据 RX 线电平状态把数据存放在接收移位寄存器内。接收完成后就把接收移位寄存器数据移到 RDR 内,并把 USART_SR 寄存器的 RXNE 位置1,同时如果 USART_CR2 寄存器的 RXNEIE 置 1 的话可以产生中断。
当一字符被接收到时,
- RXNE位被置1。它表明移位寄存器的内容被转移到RDR。换句话说,数据已经被接收并且可以被读出。
- 如果RXNEIE位被设置,产生中断。
- 在多缓冲器通信时,RXNE在每个字节接收后被置起,并由DMA对数据寄存器的读操作而清零。
- 在单缓冲器模式里,由软件读USART_DR寄存器完成对RXNE位清除,RXNE标志也可以通过对它写0来清除。RXNE位必须在下一字符接收结束前(接收移位寄存器接收满)被清零(要将数据读出),以避免溢出错误(移位寄存器的数据会被覆盖)。
溢出错误
如果RXNE还没有被复位(还没有读出DR寄存器的数据),又接收到一个字符,则发生溢出错误,数据只有当RXNE位被清零后才能从移位寄存器转移到RDR寄存器。RXNE标记是接收到每个字节后被置位的。如果下一个数据已被收到或先前DMA请求还没被服务时,RXNE标志仍是1,溢出错误产生。
中断控制
USART 有多个中断请求事件,如下:
3.5 USART 初始化结构体详解
标准库函数对每个外设都建立了一个初始化结构体,比如 USART_InitTypeDef,结构体成员用于设置外设工作参数,并由外设初始化配置函数,比如 USART_Init()调用,这些设定参数将会设置外设相应的寄存器,达到配置外设工作环境的目的。 初始化结构体和初始化库函数配合使用是标准库精髓所在,理解了初始化结构体每个成员意义基本上就可以对该外设运用自如了。初始化结构体定义在 stm32f4xx_usart.h 文件 中,初始化库函数定义在 stm32f4xx_usart.c 文件中,编程时我们可以结合这两个文件内注 释使用。
- USART_BaudRate:波特率设置。一般设置为 2400、9600、19200、115200。标准 库函数会根据设定值计算得到 USARTDIV 值 , 并设置 USART_BRR 寄存器值。
- USART_WordLength:数据帧字长,可选 8 位或 9 位。它设定 USART_CR1 寄存器的 M 位的值。如果没有使能奇偶校验控制,一般使用 8 数据位;如果使能了奇偶校验则一般设置为 9 数据位。
- USART_StopBits:停止位设置,可选 0.5 个、1 个、1.5 个和 2 个停止位,它设定 USART_CR2 寄存器的 STOP[1:0]位的值,一般我们选择 1 个停止位。
- USART_Parity : 奇 偶 校 验 控 制 选 择 , 可 选 USART_Parity_No( 无校验 ) 、 USART_Parity_Even( 偶校验 ) 以 及 USART_Parity_Odd( 奇 校 验 ) , 它 设 定 USART_CR1 寄存器的 PCE 位和 PS 位的值。
- USART_Mode:USART 模式选择,有 USART_Mode_Rx 和 USART_Mode_Tx, 允许使用逻辑或运算选择两个,它设定 USART_CR1 寄存器的 RE 位和 TE 位。
- USART_HardwareFlowControl:硬件流控制选择,只有在硬件流控制模式才有效, 可选有⑴使能 RTS、⑵使能 CTS、⑶同时使能 RTS 和 CTS、⑷不使能硬件流。
四、IO 引脚复用器和映射
4.1 复用的概念
STM32F4 有很多的内置外设,这些外设的外部引脚都是与 GPIO 复用的。也就是说,一个 GPIO 如果可以复用为内置外设的功能引脚,那么当这个 GPIO 作为内置外设使用的时候,就叫做复用。 这里以串口使用为例给大家讲解具体的引脚复用的配置。
通用:IO端口的输入或输出是由GPIO外设控制,我们称之为通用
复用:IO端口的输入或输出是由其它非GPIO外设控制,我们称之为复用.
4.2 IO引脚复用器
STM32F4 系列微控制器 IO 引脚通过一个复用器连接到内置外设或模块。该复用器一次只允 许一个外设的复用功能(AF)连接到对应的 IO 口。这样可以确保共用同一个 IO 引脚的外设之间不会发生冲突。 每个 IO 引脚都有一个复用器,该复用器采用 16 路复用功能输入(AF0 到 AF15),可通过 GPIOx_AFRL(针对引脚 0-7)和 GPIOx_AFRH(针对引脚 8-15)寄存器对这些输入进行配置,每四位控制一路复用:
1)完成复位后,所有 IO 都会连接到系统的复用功能 0(AF0)。
2)外设的复用功能映射到 AF1 到 AF13。
3)Cortex-M4 EVENTOUT 映射到 AF15。
接下来,我们简单说明一下这个图要如何看,举个例子,探索者 STM32F407 开发板的原 理图上 PC11 的原理图如图所示:
如上图所示,PC11 可以作为 SPI3_MISO/U3_RX/U4_RX/SDIO_D3/DCMI_D4/I2S3ext_SD 等复用功能输出,这么多复用功能,如果这些外设都开启了,那么对 STM32F1 来说,那就可 能乱套了,外设之间可互相干扰,但是 STM32F4,由于有复用功能选择功能,可以让 PC11 仅 连接到某个特定的外设,因此不存在互相干扰的情况。
上图复用器是针对引脚 0-7,对于引脚 8-15,控制寄存器为 GPIOx_AFRH。从图中可以看出。 当需要使用复用功能的时候,我们配置相应的寄存器 GPIOx_AFRL 或者 GPIOx_AFRH,让对应引 脚通过复用器连接到对应的复用功能外设。这里我们列出 GPIOx_AFRL 寄存器的描述, GPIOx_AFRH 的作用跟 GPIOx_AFRL 类似,只不过 GPIOx_AFRH 控制的是一组 IO 口的高八位, GPIOx_AFRL 控制的是一组 IO 口的低八位。
从表中可以看出,32 位寄存器 GPIOx_AFRL 每四个位控制一个 IO 口,所以每个寄存器控制 32/4=8 个 IO 口。寄存器对应四位的值配置决定这个 IO 映射到哪个复用功能 AF。对于外设复用功能的配置,除了 ADC 和 DAC 要将 IO 配置为模拟通道之外其他外设功能一律要配置为复用功能模式,这个配置是在 IO 口对应的 GPIOx_MODER 寄存器中配置的。同时要配置 GPIOx_AFRH 或者 GPIOx_AFRL 寄存器,将 IO 口通过复用器连接到所需要的复用功能对应的 AFx。PA9 连接 AF7 可以复用为串口 1 的发送引脚 USART1_TX,PA10 连接 AF7 可以复用为串口 2 的接收引脚 USART1_RX。 接下来我们以串口 1 为例来讲解
怎么配置 GPOPA.9,GPIOA.10 口为串口 1 复用功能?
1)首先,我们要使用 IO 复用功能外设,必须先打开对应的 IO 时钟和复用功能外设时钟。
/*使能 GPIOA 时钟*/ RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); /*使能 USART1 时钟*/ RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
2)其次,我们在 GIPOx_MODER 寄存器中将所需 IO(对于串口 1 是 PA9,PA10)配置为复用 功能(ADC 和 DAC 设置为模拟通道)。
3)再次,我们还需要对 IO 口的其他参数,例如类型,上拉/下拉以及输出速度。
上面两步,在我们库函数中是通过 GPIO_Init 函数来实现的,参考代码如下:
/*GPIOA9 与 GPIOA10 初始化*/ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//速度 50MHz GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出 GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉 GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化 PA9,PA10
4)最后,我们配置 GPIOx_AFRL 或者 GPIOx_AFRH 寄存器,将 IO 连接到所需的 AFx。 这些步骤对于我们使用库函数来操作的话,是调用的 GPIO_PinAFConfig 函数来实现的。
具 体操作代码如下:
/*PA9 连接 AF7,复用为 USART1_TX */ GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); /* PA10 连接 AF7,复用为 USART1_RX*/ GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1);
对于函数 GPIO_PinAFConfig 函数,入口第一个第二个参数很好理解,可以确定是哪个 IO, 对于第三个参数,实际上我们确定了这个 IO 到底是复用为哪种功能之后,这个参数也很好选 择,因为可选的参数在 stm32f4xx_gpio.h 列出来非常详细,如下
#define IS_GPIO_AF(AF) (((AF) == GPIO_AF_RTC_50Hz) ||((AF) == GPIO_AF_TIM14) || \ ((AF) == GPIO_AF_MCO) || ((AF) == GPIO_AF_TAMPER) || \ ((AF) == GPIO_AF_SWJ) || ((AF) == GPIO_AF_TRACE) || \ ((AF) == GPIO_AF_TIM1) || ((AF) == GPIO_AF_TIM2) || \ ((AF) == GPIO_AF_TIM3) || ((AF) == GPIO_AF_TIM4) || \ ((AF) == GPIO_AF_TIM5) || ((AF) == GPIO_AF_TIM8) || \ ((AF) == GPIO_AF_I2C1) || ((AF) == GPIO_AF_I2C2) || \ ((AF) == GPIO_AF_I2C3) || ((AF) == GPIO_AF_SPI1) || \ ((AF) == GPIO_AF_SPI2) || ((AF) == GPIO_AF_TIM13) || \ ((AF) == GPIO_AF_SPI3) || ((AF) == GPIO_AF_TIM14) || \ ((AF) == GPIO_AF_USART1) || ((AF) == GPIO_AF_USART2) || \ ((AF) == GPIO_AF_USART3) || ((AF) == GPIO_AF_UART4) || \ ((AF) == GPIO_AF_UART5) || ((AF) == GPIO_AF_USART6) || \ ((AF) == GPIO_AF_CAN1) || ((AF) == GPIO_AF_CAN2) || \ ((AF) == GPIO_AF_OTG_FS) || ((AF) == GPIO_AF_OTG_HS) || \ ((AF) == GPIO_AF_ETH) || ((AF) == GPIO_AF_OTG_HS_FS) || \ ((AF) == GPIO_AF_SDIO) || ((AF) == GPIO_AF_DCMI) || \ ((AF) == GPIO_AF_EVENTOUT) || ((AF) == GPIO_AF_FSMC))
参考这些宏定义标识符,能很快找到函数的入口参数。
五、USART 串口通信配置步骤(重点)
接下来我们将主要 从库函数操作层面结合寄存器的描述,告诉你如何设置串口,以达到我们最基本的通信功能。 通过 USB 串口和电脑通信,串口设置的一般步骤可以总结为如下几个步骤,思路如下:对于复用功能的 IO,我们首先要使能 GPIO 时钟, 然后使能相应的外设时钟,同时要把 GPIO 模式设置为复用。这些准备工作做完之后,剩下的 当然是串口参数的初始化设置,包括波特率,停止位等等参数。在设置完成只能接下来就是使 能串口,这很容易理解。同时,如果我们开启了串口的中断,当然要初始化 NVIC 设置中断优先 级别,最后编写中断服务函数。
- 串口时钟使能,GPIO 时钟使能。
- 设置引脚复用器映射:调用 GPIO_PinAFConfig 函数。
- GPIO 初始化设置:要设置模式为复用功能。
- 串口初始化:设置波特率,字长,奇偶校验等参数。
- 开启中断并且初始化 NVIC,使能中断(如果需要开启中断才需要这个步骤)。
- 使能串口。
- 编写中断处理函数:函数名格式为 USARTxIRQHandler(x 对应串口号)。
下面,我们就简单介绍下这几个与串口基本配置直接相关的几个固件库函数。这些函数和 定义主要分布在 stm32f4xx_usart.h 和 stm32f4xx_usart.c 文件中。
1) 串口时钟和 GPIO 时钟使能。
串口是挂载在 APB2 下面的外设,所以使能函数如下,GPIO 时钟使能,就非常简单,因为我们使用的是串口 1,串口 1 对应着芯片引脚 PA9,PA10,所以这里我们只需要使能 GPIOA 时钟即可如下。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//使能 USART1 时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能 GPIOA 时钟
2) 设置引脚复用器映射
引脚复用器映射配置方法在我们上讲解非常清晰,调用函数如下,因为串口使用到 PA9,PA10,所以我们要把 PA9 和 PA10 都映射到串口 1。所以这里我们要调用两次函数。 对于 GPIO_PinAFConfig 函数的第一个和第二个参数很好理解,就是设置对应的 IO 口,如 果是 PA9 那么第一个参数是 GPIOA,第二个参数就是 GPIO_PinSource9。第三个参数,实际我们不需要去记忆,只需要根据我们讲解的快速组织代码技巧里面,去相应的配置文件找到外设对应的 AF 配置宏定义标识符即可,串口 1 为 GPIO_AF_USART1。
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); //PA9 复用为 USART1
GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1);//PA10 复用为 USART1
3) GPIO 端口初始化,模式设置:PA9 和 PA10 要设置为复用功能。
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; //GPIOA9 与 GPIOA10
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //速度 50MHz
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽复用输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化 PA9,PA10
4) 串口初始化:设置波特率,字长,奇偶校验等参数
串口初始化是调用函数 USART_Init 来实现的,具体设置方法如下:
USART_InitStructure.USART_BaudRate = bound;//一般设置为 9600;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为 8 位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;//收发模式
USART_Init(USART1, &USART_InitStructure); //初始化串口
5) 使能串口
使能串口调用函数 USART_Cmd 来实现,具体使能串口 1 方法如下:
USART_Cmd(USART1, ENABLE); //使能串口
6) 串口数据发送与接收。
STM32F4 的发送与接收是通过数据寄存器 USART_DR 来实现的,这是一个双寄存器,包 含了 TDR 和 RDR。当向该寄存器写数据的时候,串口就会自动发送,当收到数据的时候,也 是存在该寄存器内。 STM32 库函数操作 USART_DR 寄存器发送数据的函数如下,通过该函数向串口寄存器 USART_DR 写入一个数据。STM32 库函数操作 USART_DR 寄存器读取串口接收到的数据的函数如下,通过该函数可以读取串口接受到的数据。
STM32 库函数操作 USART_DR 寄存器发送数据的函数如下:通过该函数向串口寄存器 USART_DR 写入一个数据。
void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);
STM32 库函数操作 USART_DR 寄存器读取串口接收到的数据的函数是:通过该函数可以读取串口接受到的数据。
uint16_t USART_ReceiveData(USART_TypeDef* USARTx);
7) 读取串口状态
串口的状态可以通过状态寄存器 USART_SR 读取。USART_SR 的各位描述如图所示:
这里我们关注一下两个位,第 5、6 位 RXNE 和 TC。
RXNE(读数据寄存器非空),当该位被置 1 的时候,就是提示已经有数据被接收到了,并 且可以读出来了。这时候我们要做的就是尽快去读取 USART_DR,通过读 USART_DR 可以将 该位清零,也可以向该位写 0,直接清除。
TC(发送完成),当该位被置位的时候,表示 USART_DR 内的数据已经被发送完成了。如 果设置了这个位的中断,则会产生中断。该位也有两种清零方式:1)读 USART_SR,写 USART_DR。2)直接向该位写 0。
在我们固件库函数里面,读取串口状态的函数是:
FlagStatus USART_GetFlagStatus(USART_TypeDef* USARTx, uint16_t USART_FLAG);
这个函数的第二个入口参数非常关键,它是标示我们要查看串口的哪种状态,比如上面讲解的 RXNE(读数据寄存器非空)以及 TC(发送完成)。例如我们要判断读寄存器是否非空(RXNE),操 作库函数的方法是:
USART_GetFlagStatus(USART1, USART_FLAG_RXNE);
我们要判断发送是否完成(TC),操作库函数的方法是:
USART_GetFlagStatus(USART1, USART_FLAG_TC);
这些标识号在 MDK 里面是通过宏定义定义的:
#define USART_IT_PE ((uint16_t)0x0028)
#define USART_IT_TC ((uint16_t)0x0626)
#define USART_IT_RXNE ((uint16_t)0x0525)
……//(省略部分代码)
#define USART_IT_NE ((uint16_t)0x0260)
#define USART_IT_FE ((uint16_t)0x0160)
8) 开启中断并且初始化 NVIC,使能相应中断,主要为发送中断和接收中断
这一步如果我们要开启串口中断才需要配置 NVIC 中断优先级分组。通过调用函数 NVIC_Init 来设置。
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;//抢占优先级 3
NVIC_InitStructure.NVIC_IRQChannelSubPriority =3; //响应优先级 3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ 通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化 VIC 寄存器、
同时,我们还需要使能相应中断,使能串口中断的函数是:
void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT, FunctionalState NewState)
这个函数的第二个入口参数是标示使能串口的类型,也就是使能哪种中断,因为串口的中断类型有很多种。比如在接收到数据的时候(RXNE 读数据寄存器非空),我们要产生中断,这就是接收中断,那么我们开启中断的方法是:
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启中断,接收到数据中断
我们在发送数据结束的时候(TC,发送完成)要产生中断,这就是发送中断。那么方法是:
USART_ITConfig(USART1,USART_IT_TC,ENABLE);
9) 获取相应中断状态
当我们使能了某个中断的时候,当该中断发生了,就会设置状态寄存器中的某个标志位。 经常我们在中断处理函数中,要判断该中断是哪种中断,使用的函数是:
ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT)
比如我们使能了串口发送完成中断,那么当中断发生了, 我们便可以在中断处理函数中调用这 个函数来判断到底是否是串口发送完成中断,方法是:返回值是 SET,说明是串口发送完成中断发生。
USART_GetITStatus(USART1, USART_IT_TC)
10) 中断服务函数
串口 1 中断服务函数如下, 当发生中断的时候,程序就会执行中断服务函数。然后我们在中断服务函数中编写我们相应的逻辑代码即可。
void USART1_IRQHandler(void) ;
通过以上一些寄存器的操作外加一下 IO 口的配置,我们就可以达到串口最基本的配置了, 关于串口更详细的介绍
六、printf 重定向
这一小节我们来学习如何在 STM32 上使用 printf 输出函数。相信只要学习过 C 语言的朋友,都会使用 printf 函数。那么如何通过 printf 函数将信息打印在串口调试助手上显示呢。
6.1 printf 重定向简介
我们知道 C 语言中 printf 函数默认输出设备是显示器,如果要实现在串口或者 LCD 上显示,必须重定义标准库函数里调用的与输出设备相关的函数。比如 使用 printf 输出到串口,需要将 fputc 里面的输出指向串口,这一过程就叫重定向。 那么如何让 STM32 使用 printf 函数呢?很简单,只需要将 fputc 里面的输出指向 STM32 串口即可,fputc 函数有固定的格式,我们只需要在函数内操作 STM32 串口即可,代码如下:
第八行的意思是:等待发送完毕,即将一个字节数据全部写入到发送移位寄存器,才能向发送数据寄存器写入下一个值!!即:标志位TXE为0说明,数据还未全部移到发送数据寄存器,我就死循环等着它移动,一旦它移动完毕,标志位TXE会自动的置为1,此时便退出循环,可以继续向发送数据寄存器写入下一个值!
当使用 printf 函数时,自动会调用 fputc 函数,而 fputc 函数内又将输出设备重定义为 STM32 的 USART1,所以要输出的数据就会在串口 1 上输出。如果要让其他的串口也使用 printf 函数,只需要修改下串口号即可.在 KEIL 中使用 printf 一定要勾选“微库”选项,否则不会输出。配置如下:
当使用 printf 函数时,自动会调用 fputc 函数,而 fputc 函数内又将输出 设备重定义为 STM32 的 USART1,所以要输出的数据就会在串口 1 上输出。
SART 只需两根信号线即可完成双向通信,对硬件要求低,使得很多模块都预留 USART 接口来实现与其他模块或者控制器进行数据传输,比如 GSM 模块,WIFI 模块、蓝 牙模块等等。我们不仅仅可以将数据发送到串口调试助手,我们还可以在串口调试助手发送数据给 控制器,控制器程序根据接收到的数据进行下一步工作。
至此,我们的本次的学习就结束了。这一节我们就讲解到这里,希望能对大家的开发有帮助。 如有兴趣,感谢点赞、关注、收藏,若有不正地方,还请各位大佬多多指教!