功夫不负有心人,相信学习至此你已经掌握了入门STM32基础知识。希望通过前面的学习,你已经掌握了 STM32 开发的工具和方法。本篇博客我们将和大家一起来学习 STM32 的一个最基础设,这些外设实际项目中经常会用到,希望大家认真学习和掌握,以便将来更好、更快的完成实际项目开发。 任何一个单片机,最简单的外设莫过于 IO 口的高低电平控制了,可以学习中文参考手册的通用 I/O(GPIO)章节,特别是在涉及到寄存器功能部分。通过本章的学习,让大家学会如何控制 STM32 的 GPIO 输出高低电平。
目录
4.1 GPIO 端口模式寄存器 (GPIOx_MODER) (x =A..I)
4.2 GPIO 端口输出类型寄存器 (GPIOx_OTYPER) (x = A..I)
4.3 GPIO 端口输出速度寄存器 (GPIOx_OSPEEDR) (x = A..I)
4.4 GPIO 端口上拉/下拉寄存器 (GPIOx_PUPDR) (x = A..I)
一、STM32F407 GPIO 简介
1.1 什么是GPIO
GPIO(general purpose intput output)是通用输入输出端口的简称,可以通过软件来控制其输入和输出。STM32 芯片的 GPIO 引脚与外部设备连接起来, 从而实现与外部通讯、控制以及数据采集的功能。
GPIO的作用:负责采集外部器件的信息或者控制外部器件工作,即输入输出
- 输出模式下:可控制端口输出高低电平,用以驱动LED、控制蜂鸣器、模拟通信协议输出时序等
- 输入模式下:可读取端口的高低电平或电压,用于读取按键输入、外接模块电平信号输入、ADC电压采集、模拟通信协议接收数据等。最基本的输入功能是检测外部输入电平,如把 GPIO 引脚连接到按键,通过电平高低区分按键是否被按下。
1.2 基本概念
1.2.1 GPIO的特点
- 不同型号,IO口数量可能不一样,可通过选型手册快速查询;
- 快速翻转,每次翻转最快只需要两个时钟周期(F1最高速度可以到50Mhz);
- 每个IO口都可以做中断;
- 支持8种工作模式;
1.2.2 STM32 GPIO的分类
STM32 芯片的 GPIO 被分成很多组,每组有 16 个引脚,如型号为 STM32F4IGT6 型号的芯 片有 GPIOA、GPIOB、GPIOC 至 GPIOG 共 7 组 GPIO,共有 112 个 IO口,可供我们编程使⽤。芯片一共 144 个引脚,其中 GPIO 就占了一大部分,所有的 GPIO 引脚都有基本的输入输出功能。
1.2.3 GPIO电气特性
这里重点说一下 STM32F407 的 IO 电平兼容性问题,STM32F407 的绝大部分 IO 口,都兼容 5V,至于到底哪些是兼容 5V 的,请看 STM32F407ZG 的数据手册,见表大容量 STM32F40xxx 引脚定义,凡是有 FT 标志的, 都是兼容 5V 电平的 IO 口,可以直接接 5V 的外设(注意:如果引脚设置的是模拟输入模式, 则不能接 5V!),凡是不带 FT 标志的,就建议大家不要接 5V 了,可能烧坏 MCU。
1.2.3 GPIO引脚分布
我们开发板上使用的 STM32 型号是 STM32F407ZGT6,此芯片共有 144 引脚, 芯片引脚图如下图所示。那么是不是所有引脚都是 GPIO 呢?当然不是,STM32 引脚可以分为这么几大类:
对于这么多 GPIO 管脚,我们怎么知道具体某个引脚有什么功能呢?很简单, 可以查阅 STM32 芯片数据手册获取信息,我们可以获取引脚的名字、引脚类型、引脚容忍的电压值和引脚复 用功能等信息。这个我们开发板芯片原理图内已经将引脚所有功能都标进去了, 所以后面也不需要查找具体引脚有什么功能,直接看原理图即可。
二、GPIO的结构框图
前面我们讲解了 STM32 GPIO 的基本概念及引脚分类。现在我们看下 STM32 GPIO 内部的结构是怎样的。先来整体角度看下GPIO的简单框图,理解到底是如何发挥作用的。
每个GPIO端口对应16个引脚,例GPIOA(PA0~PA15),内核CPU就可以通过AHB1总线对GPIO的寄存器读写,完成输出电平和读取电平的功能,从而实现对采集外部器件的信息或者控制外部器件工作,即输入输出的作用,因此本质上是操作寄存器,从而实现对外部电路的控制,我们编程本质上也就是对寄存器进行读写!
GPIO 具体结构图如下图所示。
从上图中可以看出 GPIO 内部结构还是比较复杂的,只要将这张 GPIO 结构图理解好,那么关于 GPIO 的各种应用模式将非常清楚。图中最右端 I/O 端口就是 STM32 芯片的引脚,其它部分都在 STM32 芯片内部。上图中我们将每部分都用红线圈起来标号了,按照顺序我们逐一讲解。
2.1 保护二极管
引脚内部加上这两个保护二级管可以防止引脚外部过高或过低的电压输入, 当引脚电压高于 VDD_FT 或 VDD 时,上方的二极管导通吸收这个高电压,当引脚电压低于 VSS 时,下方的二极管导通,防止不正常电压引入芯片导致芯片烧毁。 尽管 STM32 芯片内部有这样的保护,但并不意味着 STM32 的引脚就无所不能,如 果直接将引脚连接大功率器件,比如电机,那么要么电机不转,要么烧坏芯片。 如果要驱动一些大功率器件,必须要加大功率及隔离电路驱动。也可以说 STM32 引脚是用来做控制,而不是做驱动使用的。
2.2 上下拉电阻
从图中可以看到,它们阻值大概在 30~50K 欧之间,上拉和下拉电阻上都有一个开关,通过配置上下拉电阻开关(这两个开关由寄存器控制),可以控制引脚的默认状态电平。STM32引脚的上、下拉以及浮空模式的配置是通过 GPIOx_PUPDR 寄存器控制的,大家可以通过《STM32F4xx 中文参考手册》查阅。
当开启上拉时,引脚默认电压为高电平,开启下拉时,引脚默认电压为低电平,这样就可以消除引脚不定状态的影响。当然也可以将上拉和下拉的开关都关断,这种状态我们称为浮空模式,一旦配置成这个模式,引脚的电压是不确定的,如果用万用表测量此模式下管脚电压时会发现只有 1 点几伏,而且还不时改变,所以一般情况下我们都会给引脚设置成上拉或者下拉模式,使它有一个默认状态。
STM32 内部的上拉其实是一个弱上拉,也就是说通过此上拉电阻输出的电流很小,如果想要输出一个大电流,那么就需要外接上拉电阻了。
2.3 施密特触发器
施密特触发器就是一种整形电路,可以将非标准方波,整形成方波。对于标准施密特触发器,当输入电压高于正向阈值电压,输出为高;当输入电压低于负向 阈值电压,输出为低;当输入在正负向阈值电压之间,输出不改变,也就是说输出由高电准位翻转为低电准位,或是由低电准位翻转为高电准位对应的阈值电压是不同的。只有当输入电压发生足够的变化时,输出才会变化,因此将这种元件命名为触发器。这种双阈值动作被称为迟滞现象,表明施密特触发器有记忆性。从本质上来说,施密特触发器是一种双稳态多谐振荡器。 施密特触发器可作为波形整形电路,能将模拟信号波形整形为数字电路能够处理的方波波形,而且由于施密特触发器具有滞回特性,所以可用于抗干扰,其应用包括在开回路配置中用于抗扰,以及在闭回路正回授/负回授配置中用于实现多谐振荡器。
2.4 输入数据寄存器
看 GPIO 结构框图的上半部分,它是 GPIO 引脚经过上、下拉电阻后引入的,它连接到施密特触发器,信号经过触发器后,模拟信号转化为 0、1 的数字信号,然后存储在输入数据寄存器 GPIOx_IDR中,通过读取该寄存器就可以了解 GPIO 引脚的电平状态。
2.5 模拟输入输出
当 GPIO 引脚用于 ADC 采集电压的输入通道时,用作“模拟输入”功能, 此时信号是不经过施密特触发器的,因为经过施密特触发器后信号只有 0、1 两 种状态,ADC 外设要采集到原始的模拟信号,信号源输入必须在施密特触发器之前。类似地,当 GPIO 引 脚用于 DAC 作为模拟电压输出通道时,此时作为“模拟输出”功能,DAC 的模拟信号输出就不经过双 MOS 管结构了,在 GPIO 结构框图的右下角处,模拟信号直接输出到引脚。 同时,当 GPIO 用于模拟功能时(包括输入输出),引脚的上、下拉电阻是不起作用的,这个时候即使在寄存器配置了上拉或下拉模式,也不会影响到模拟信号的输入输出。
2.6 复用功能输入
与“复用功能输出”模式类似,在复用功能输入模式时,GPIO 引 脚的信号传输到 STM32 其他片上外设,由该外设读取引脚的状态。同样,如我们使用 USART 串口通讯时,需要用到某个 GPIO 引脚作为通讯接收引脚,这个时候就可以把该 GPIO 引脚配置成 USART 串口复用功能,使 USART 可以通过该通讯引脚的接收远端数据。
2.7 输出数据寄存器
双 MOS 管结构电路的输入信号,是由 GPIO“输出数据寄存器 GPIOx_ODR”提供的,因此我们通过修改输出数据寄存器的值就可以修改 GPIO 引脚的输出电平。而“置位/复位寄存器 GPIOx_BSRR”可以通过修改输出数据寄存器的值从而影响电路的输出。
2.8 复用功能输出
“复用功能输出”中的“复用”是指 STM32 的其它片上外设对 GPIO 引脚进行控制, 此时 GPIO 引脚用作该外设功能的一部分,算是第二用途。由于 STM32 的 GPIO 引脚具有第二功能,因此当使用复用功能的时候,也就是通过其他外设复用功能输出信号与 GPIO 数据寄存器一起连接到双 MOS 管电路 的输入,其中梯形结构是用来选择使用复用功能还是普通 IO 口功能。例如我们使用 USART 串口通讯时,需要用到某个 GPIO 引脚作为通讯发送引脚,这个时候就可以把该 GPIO 引脚配置成 USART 串口复用功能,由串口外设控制该引脚, 发送数据。
2.9 P-MOS 和 N-MOS 管
GPIO 引脚经过两个保护二极管后就分成两路,上面一路是“输入模式”, 下面一路是“输出模式”。对于输出模式,线路经过一个由 P-MOS 和 N-MOS 管组成的单元电路,这让 GPIO 引脚具有了推挽和开漏两种输出模式。
所谓推挽输出模式,是根据 P-MOS 和 N-MOS 管的工作方式命名的。在该结构单元输入一个高电平时,P-MOS 管导通,N-MOS 管截止,对外输出高电平(3.3V)。 在该单元输入一个低电平时,P-MOS 管截止,N-MOS 管导通,对外输出低电平(0V)。 如果当切换输入高低电平时,两个 MOS 管将轮流导通,一个负责灌电流(电流输出到负载),一个负责拉电流(负载电流流向芯片),使其负载能力和开关速度都比普通的方式有很大的提高。下图为推挽输出模式的等效电路。
在开漏输出模式时,不论输入是高电平还是低电平,P-MOS 管总处于关闭状态。当给这个单元电路输入低电平时,N-MOS 管导通,输出即为低电平。当输入高电平时,N-MOS 管截止,这个时候引脚状态既不是高电平,又不是低电平,我们称之为高阻态。如果想让引脚输出高电平,那么引脚必须外接一个上拉电阻, 由上拉电阻提供高电平。开漏输出模式等效电路图如下图所示。
在开漏输出模式中还有一个特点,引脚具有“线与”关系。就是说如果有很多个开漏输出模式的引脚接在一起,只要有一个引脚为低电平,其他所有管脚都为低,即把所有引脚连接在一起的这条总线拉低了。只有当所有引脚输出高阻态 时这条总线的电平才由上拉电阻的 VDD 决定。如果 VDD 连接的是 3.3V,那么引 脚输出的就是 3.3V,如果 VDD 连接的是 5V,那么引脚输出的就是 5V。因此如果想要让 STM32 管脚输出 5V,可以选择开漏输出模式,然后在外接上拉电阻的电源 VDD 选择 5V 即可,前提是这个 STM32 引脚是容忍 5V 的。开漏输出模式一般应 用在 I2C、SMBUS 通讯等需要“线与”功能的总线电路中。还可以用在电平不匹配的场合中,就如上面说的输出 5V 一样。
推挽输出模式一般应用在输出电平为 0-3.3V 而且需要高速切换开关状态的场合。除了必须要用开漏输出模式的场合,我们一般选择推挽输出模式,它既可以输出高电平,也可以输出低电平。要配置引脚是开漏输出还是推挽输出模式可以使用 GPIOx_OTYPER 寄存器,寄存器详细 内容可以参考《STM32F4xx 中文参考手册》“通用 I/O(GPIO)”章节。
三、GPIO的八种工作模式
上面我们对 GPIO 的基本结构图中的关键器件做了详细介绍,下面分别介绍 GPIO 八种工作模 式:四种输入、四种输出模式,对应结构图的工作情况。
在输入模式时, 施密特触发器打开, 输出被禁止。 数据寄存器每隔 1 个 AHB1 时钟周期更新一次,可通过输入数据寄存器 GPIOx_IDR 读取 I/O 状态。 其中 AHB1 的时钟如按默认配置一般为 168MHz。输入模式可以配置为上拉、下拉以及浮空模式。
在输出模式中,输出使能,推挽模式时双 MOS 管以推挽方式工作,输出数据寄存器 GPIOx_ODR 可控制 I/O 输出高低电平。开漏模式时,只有 N-MOS 管 工作,输出数据寄存器可控制 I/O 输出高阻态或低电平。 输出速度可配置,有 2MHz\25MHz\50MHz\100MHz 的选项。此处的输出速度即 I/O 支持的高低电平状 态最高切换频率,支持的频率越高,功耗越大,如果功耗要求不严格,把速度设 置成最大即可。此时施密特触发器是打开的,即输入可用,通过输入数据寄存器 GPIOx_IDR 可读取 I/O 的实际状态。用于输出模式时,可使用上拉、 下拉模式 或浮空模式。但此时由于输出模式时引脚电平会受到 ODR 寄存器影响,而 ODR 寄存器对应引脚的位为 0,即引脚初始化后默认输出低电平, 所以在这种情况 下,上拉只起到小幅提高输出电流能力,但不会影响引脚的默认状态。 除了模拟输入的这种模式会关闭数字输入功能其他七种模式,都可以通过输入寄存器读取I/O状态,例:在模拟I2C实验中把GPIO的工作模式配置为开漏输出时同时也可以读取引脚电平状态,现在不知道不要紧后面会详细讲解
3.1 浮空输入
3.2 上拉输入
3.3 下拉输入
3.4 模拟输入
3.5 开漏输出
在开漏输出模式下,施密特触发器是打开的,所以 IO 口引脚的电平状态会被采集到输 入数据寄存器中,如果对输入数据寄存器进行读访问可以得到 IO 口的状态。也就是说开漏输出 模式下,我们可以对 IO 口进行读数据。
开漏输出的具体的理解描述如下:STM32 的开漏输出模式是数字电路输出的一种,从结果上看它只能输出低 电平 Vss 或者高阻态。
- 开漏模式下,P-MOS 管是一直截止的,所以 P-MOS 管的栅极一直接 VSS。如果输出数据 寄存器设置为 0 时,经过“输出控制”的逻辑非操作后,输出逻辑 1 到 N-MOS 管的栅极,这 时 N-MOS 管就会导通,使得 I/O 引脚接到 VSS,即输出低电平。
- 如果输出数据寄存器设置为 1 时,经过“输出控制器”的逻辑非操作后,输出逻辑 0 到 NMOS 管的栅极,这时 N-MOS 管就会截止。因为 P-MOS 管是一直截止的,使得 I/O 引脚呈现高 阻态,即不输出低电平,也不输出高电平。因此要 I/O 引脚输出高电平就必须接上拉电阻。这 时可以接内部上拉电阻,或者接一个外部上拉电阻。由于内部上拉电阻的阻值较大,所以只是 “弱上拉”。需要大电流驱动,请接外部的上拉电阻。此外,上拉电阻具有线与特性,即如果有 很多开漏模式的引脚连在一起的时候,只有当所有引脚都输出高阻态,电平才为 1,只要有其 中一个为低电平时,就等于接地,使得整条线路都为低电平 0。我们的 IIC 通信(IIC_SDA)就用到这个原理。
3.6 推挽输出
另外在推挽输出模式下,施密特触发器也是打开的,我们可以读取 IO 口的电平状态。
STM32 的推挽输出模式,从结果上看它会输出低电平 VSS 或者高电平 VDD。 推挽输出跟开漏输出不同的是,推挽输出模式 P-MOS 管和 N-MOS 管都用上。同样地,我们根 据参考手册推挽模式下的输出描述,根据手册描述可以把“输 出控制”简单地等效为一个非门。
推挽输出的具体的理解描述如下:
- 如果输出数据寄存器设置为 0 时,经过“输出控制”的逻辑非操作后,输出逻辑 1 到 P-MOS 管的栅极,这时 P-MOS 管就会截止,同时也会输出逻辑 1 到 N-MOS 管的栅极,这时 N-MOS管就会导通,使得 I/O 引脚接到 VSS,即输出低电平。
- 如果输出数据寄存器设置为 1 时,经过“输出控制”的逻辑非操作后,输出逻辑 0 到 NMOS 管的栅极,这时 N-MOS 管就会截止,同时也会输出逻辑 0 到 P-MOS 管的栅极,这时 PMOS 管就会导通,使得 I/O 引脚接到 VDD,即输出高电平。
上面的描述可以知道,推挽输出模式下,P-MOS 管和 N-MOS 管同一时间只能有一个 MOS 管是导通的。当引脚高低电平切换时,两个管子轮流导通,一个负责灌电流,一个负责拉电流, 使其负载能力和开关速度都有很大的提高。 由于推挽输出模式输出高电平时,是直接连接 VDD ,所以驱动能力较强,可以做电流型 驱动,驱动电流最大可达 25mA。该模式也是最常用的输出模式。
3.7 复用开漏输出
一个 IO 口可以是通用的 IO 口功能,还可以是其他外设的特殊功能引脚, 这就是 IO 口的复用功能。一个 IO 口可以是多个外设的功能引脚,我们需要选择作为其中一个 外设的功能引脚。当选择复用功能时,引脚的状态是由对应的外设控制,而不是输出数据寄存器。除了复用功能外,其他的结构分析请参考开漏输出模式。
另外在开漏式复用功能模式下,施密特触发器也是打开的,我们可以读取 IO 口的电平状 态,同时外设可以读取 IO 口的信息。
3.8 复用推挽输出
复用功能介绍请查看开漏式复用功能,结构分析请参考推挽输出模式, 这里不再赘述。
四、GPIO的寄存器
通过对 GPIO 寄存器写入不同的参数,就可以改变 GPIO 的工作模式,要了解具体寄存器时一定要查阅《STM32F10X-中文参考手册》中对应外设的寄存器说明。我们在讲解固件库之前会首先对重要寄存器进行一个讲解,这样是为了大家对寄存器有个初步的了解。大家学习固件库,并不需要记住每个寄存器的作用,而只是通过了解寄存器来对外设一些功能有个大致的了解,这样对以后的学习也很有帮助。首先要提一下,在固件库中,GPIO 端口操作对应的库函数函数以及相关定义在文件 stm32f4xx_gpio.h 和 stm32f4xx_gpio.c 中。
STM32F4 每组通用 I/O 端口包括 :
- 4 个 32 位配置寄存器(MODER、OTYPER、OSPEEDR 和 PUPDR)、
- 2 个 32 位数据寄存器(IDR 和 ODR)、
- 1 个 32 位置位/复位寄存器 (BSRR)、
- 1 个 32 位锁定寄存器 (LCKR)
- 2 个 32 位复用功能选择寄存器(AFRH 和 AFRL)等。
这样,STM32F4 每组 IO 有 10 个 32 位寄存器控制,其中常用的有 4 个配置寄存器+2 个数 据寄存器+2 个复用功能选择寄存器,共 8 个,如果在使用的时候,每次都直接操作寄存器配置 IO,代码会比较多,也不容易记住,所以我们在讲解寄存器的同时会讲解是用库函数配置 IO 的方法。STM32F4 的 IO 可以由软件配置成上面 8 种模式中的任何一种。
因为寄存器太多不可能一个个列出来讲,以后基本就是只会把重要的寄存器拿出来讲述,希望大家尽快培养自己学会看手册的能力。下面先看 GPIO 的 4 个 32 位配置寄存器:
4.1 GPIO 端口模式寄存器 (GPIOx_MODER) (x =A..I)
该寄存器是 GPIO 口模式控制寄存器,用于控制 GPIOx(STM32F4 最多有 9 组 IO,用大 写字母表示,即 x=A/B/C/D/E/F/G/H/I,下同)的工作模式,寄存器描述如图所示。
每组 GPIO 下有 16 个 IO 口,该寄存器共 32 位,每 2 个位控制 1 个 IO,因此32位的寄存器就可以控制GPIO16个口的端口模式。
我们看看这个寄存器的复位值,然后用复位值举例说明一下这样的配置值代表什么意思。比如 GPIOA 的复位值 是 0xABFF FFFF,低 16 位都是 1,也就是 PA0~PA7 默认都是模拟模式。高 16 位的值是 0xABFF, 也就是 PA8~PA12 默认是模拟模式,PA13\PA14\PA15 则默认是复用功能模式。而 GPIOB 的复 位值是 0xFFFF FEBF,只有 PB3 默认是复用功能模式,其他默认都是模拟模式。这四个默认是 复用功能模式的 IO 口都是 JTAG 功能对应的 IO 口。
4.2 GPIO 端口输出类型寄存器 (GPIOx_OTYPER) (x = A..I)
该寄存器用于控制 GPIOx 的输出类型,寄存器描述如图所示。
该寄存器仅用于输出模式,在输入模式(MODER[1:0]=00/11 时)下不起作用。该寄存器低 16 位有效,每一个位控制一个 IO 口,复位后,该寄存器值均为 0,也就是在输出模式下 IO 口 默认为推挽输出。
4.3 GPIO 端口输出速度寄存器 (GPIOx_OSPEEDR) (x = A..I)
该寄存器用于控制 GPIOx 的输出速度,寄存器描述如图所示。
该寄存器仅用于输出模式,在输入模式(MODER[1:0]=00/11 时)下不起作用。该寄存器低 16 位有效,每两个位控制一个 IO 口。
4.4 GPIO 端口上拉/下拉寄存器 (GPIOx_PUPDR) (x = A..I)
该寄存器用于控制 GPIOx 的上拉/下拉,寄存器描述如图 所示。
该寄存器每两个位控制一个 IO 口,用于设置上下拉,复位后,该寄存器值一般为 0,即无 上拉或下拉。
上面这 4 个配置寄存器就是用来配置 GPIO 的相关模式和状态,它们通过不同的配置组合方法,就决定我们所说的 8 种工作模式。下面,我们来列表阐述,如表所示。
4.5 如何使用库函数初始化GPIO的配置
上面, 我们讲解了 4 个重要的配置寄存器。顾名思义,配置寄存器就是用来配置 GPIO 的相关模式和状态,接下来我们讲解怎么在库函数初始化 GPIO 的配置。GPIO 相关的函数和定义分布在固件库文件 stm32f4xx_gpio.c 和头文件 stm32f4xx_gpio.h 文 件中。
在固件库开发中,操作四个配置寄存器初始化 GPIO 是通过 GPIO 初始化函数完成:
这个函数有两个参数,第一个参数是用来指定需要初始化的 GPIO 对应的 GPIO 组,取值范围 为 GPIOA~GPIOI。第二个参数为初始化参数结构体指针,结构体类型为 GPIO_InitTypeDef。 下面我们看看这个结构体的定义。
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
定位到 GPIO_Init 函数体处,双击入口参数类型 GPIO_InitTypeDef 后右键选择“Go to definition of …”可以查看结构体的定义:
typedef struct
{
uint32_t GPIO_Pin;
GPIOMode_TypeDef GPIO_Mode;
GPIOSpeed_TypeDef GPIO_Speed;
GPIOOType_TypeDef GPIO_OType;
GPIOPuPd_TypeDef GPIO_PuPd;
}GPIO_InitTypeDef;
下面我们通过一个 GPIO 初始化实例来讲解这个结构体的成员变量的含义。 通过初始化结构体初始化 GPIO 的常用格式是:
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9//GPIOF9
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通输出模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
GPIO_Init(GPIOF, &GPIO_InitStructure);//初始化 GPIO
上面代码的意思是设置 GPIOF 的第 9 个端口为推挽输出模式,同时速度为 100M,上拉。从上面初始化代码可以看出,结构体 GPIO_InitStructure 的第一个成员变量 GPIO_Pin 用来 设置是要初始化哪个或者哪些 IO 口,这个很好理解;第二个成员变量 GPIO_Mode 是用来设置 对应 IO 端口的输出输入端口模式,这个值实际就是配置我们前面讲解的 GPIOx 的 MODER 寄 存器的值。在 MDK 中是通过一个枚举类型定义的,我们只需要选择对应的值即可:
typedef enum
{
GPIO_Mode_IN = 0x00, /*!< GPIO Input Mode */
GPIO_Mode_OUT = 0x01, /*!< GPIO Output Mode */
GPIO_Mode_AF = 0x02, /*!< GPIO Alternate function Mode */
GPIO_Mode_AN = 0x03 /*!< GPIO Analog Mode */
}GPIOMode_TypeDef;
GPIO_Mode_IN 是用来设置为复位状态的输入,GPIO_Mode_OUT 是通用输出模式, GPIO_Mode_AF 是复用功能模式,GPIO_Mode_AN 是模拟输入模式。
第三个参数 GPIO_Speed 是 IO 口输出速度设置,有四个可选值。实际上这就是配置的 GPIO 对应的 OSPEEDR 寄存器的值。在 MDK 中同样是通过枚举类型定义:
typedef enum
{
GPIO_Low_Speed = 0x00, /*!< Low speed */
GPIO_Medium_Speed = 0x01, /*!< Medium speed */
GPIO_Fast_Speed = 0x02, /*!< Fast speed */
GPIO_High_Speed = 0x03 /*!< High speed */
}GPIOSpeed_TypeDef;
/* Add legacy definition */
#define GPIO_Speed_2MHz GPIO_Low_Speed
#define GPIO_Speed_25MHz GPIO_Medium_Speed
#define GPIO_Speed_50MHz GPIO_Fast_Speed
#define GPIO_Speed_100MHz GPIO_High_Speed
这里需要说明一下,实际我们的输入可以是 GPIOSpeed_TypeDef 枚举类型中 GPIO_High_Speed 枚举类型值,也可以是 GPIO_Speed_100MHz 这样的值,实际上 GPIO_Speed_100MHz 就是通 过 define 宏定义标识符定义出来的,它跟 GPIO_High_Speed 是等同的。
第四个参数 GPIO_OType 是 GPIO 的输出类型设置,实际上是配置的 GPIO 的 OTYPER 寄 存器的值。在 MDK 中同样是通过枚举类型定义:
typedef enum
{
GPIO_OType_PP = 0x00,
GPIO_OType_OD = 0x01
}GPIOOType_TypeDef;
如果需要设置为输出推挽模式,那么选择值 GPIO_OType_PP,如果需要设置为输出开漏模式, 那么设置值为 GPIO_OType_OD。
第五个参数 GPIO_PuPd 用来设置 IO 口的上下拉,实际上就是设置 GPIO 的 PUPDR 寄存 器的值。同样通过一个枚举类型列出:
typedef enum
{
GPIO_PuPd_NOPULL = 0x00,
GPIO_PuPd_UP = 0x01,
GPIO_PuPd_DOWN = 0x02
}GPIOPuPd_TypeDef;
这三个值的意思很好理解,GPIO_PuPd_NOPULL 为不使用上下拉,GPIO_PuPd_UP 为上拉, GPIO_PuPd_DOWN 为下拉。我们根据我们 需要设置相应的值即可。
这些入口参数的取值范围怎么定位,怎么快速定位到这些入口参数取值范围的枚举类型, 在我们上篇博客“快速组织代码”章节有讲解,不明白的朋友可以翻回去看一下,这里 我们就不重复讲解,在后面的实验中,我们也不再去重复讲解怎么定位每个参数的取值范围的方法。 看完了 GPIO 的参数配置寄存器,接下来我们看看 GPIO 输入输出电平控制相关的寄存器。
4.6 端口输入数据寄存器(IDR)
4.6.0 IDR寄存器介绍
该寄存器用于获取 GPIOx 的输入高低电平,寄存器描述如图所示。
该寄存器用于读取某个 IO 的电平,如果对应的位为 0(IDRy=0),则说明该 IO 输入的是低 电平,如果是 1(IDRy=1),则表示输入的是高电平。该寄存器为只读寄存器,并且只能以 16 位的形式读出。读出的值为对应 IO 口的状态。
4.6.1 如何使用固件库函数实现?
库函数相关函数为:
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);
前面的函数是用来读取一组 IO 口的一个或者几个 IO 口输入电平,后面的函数用来一次读取一 组 IO 口所有 IO 口的输入电平。
4.7 端口输出数据寄存器(ODR)
4.7.0 寄存器介绍
该寄存器用于控制 GPIOx 的输出高电平或者低电平,寄存器描述如图所示。
该寄存器低 16 位有效,分别对应每一组 GPIO 的 16 个引脚。当 CPU 写访问该寄存器,如 果对应的某位写 0(ODRy=0),则表示设置该 IO 口输出的是低电平,如果写 1(ODRy=1),则表 示设置该 IO 口输出的是高电平,y=0~15。 除了 ODR 寄存器,还有一个寄存器也是用于控制 GPIO 输出的,它就是 BSRR 寄存器。
4.7.1 如何使用固件库函数实现?
在固件库中设置 ODR 寄存器的值来控制 IO 口的输出状态是通过函数 GPIO_Write 来实现 的:该函数一般用来往一次性一个 GPIO 的多个端口设值。
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);
使用实例如下:
GPIO_Write(GPIOA,0x0000);
大部分情况下,设置 IO 口我们都不用这个函数,后面我们会讲解我们常用的设置 IO 口电平的函数。 同时读 ODR 寄存器还可以读出 IO 口的输出状态,库函数如下,这两个函数功能类似,只不过前面是用来一次读取一组 IO 口所有 IO 口输出状态,后面的函数用来一次读取一组 IO 口中一个或者几个 IO 口的输出状态。
uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);
uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
4.8 端口置位/复位寄存器(BSRR)
4.8.0 寄存器介绍
接下来我们看看 32 位置位/复位寄存器 (BSRR),顾名思义,这个寄存器是用来置位或者 复位 IO 口,该寄存器和 ODR 寄存器具有类似的作用,都可以用来设置 GPIO 端口的输出位是 1 还是 0。寄存器描述如下:
为什么有了 ODR 寄存器,还要这个 BDRR 寄存器呢?我们先看看 BSRR 的寄存器描述, 首先 BSRR 是只写权限,而 ODR 是可读可写权限。BSRR 寄存器 32 位有效,对于低 16 位(0- 15),我们往相应的位写 1(BSy=1),那么对应的 IO 口会输出高电平,往相应的位写 0(BSy=0), 对 IO 口没有任何影响,高 16 位(16-31)作用刚好相反,对相应的位写 1(BRy=1)会输出低电 平,写 0(BRy=0)没有任何影响,y=0~15。 也就是说,对于 BSRR 寄存器,你写 0 的话,对 IO 口电平是没有任何影响的。我们要设置 某个 IO 口电平,只需要相关位设置为 1 即可。而 BSRR 寄存器ODR 寄存器,我们要设置某个 IO 口电平, 我们首先需要读出来 ODR 寄存器的值,然后对整个 ODR 寄存器重新赋值来达到设置某个或者 某些 IO 口的目的,而 BSRR 寄存器,我们就不需要先读,而是直接设置即可,这在多任务实时 操作系统中作用很大。还有一个好处,就是 BSRR 寄存器改变引脚状态的时候, 不会被中断打断,而 ODR 寄存器有被中断打断的风险。
总的来说,建议大家使用BSRR寄存器控制输出!
BSRR 寄存器使用方法如下:
GPIOA->BSRR=1<<1; //设置 GPIOA.1 为高电平
GPIOA->BSRR=1<<(16+1)//设置 GPIOA.1 为低电平;
4.8.1 如何使用固件库函数实现?
库函数操作 BSRR 寄存器来设置 IO 电平的函数如下,函数 GPIO_SetBits 用来设置一组 IO 口中的一个或者多个 IO 口为高电平。GPIO_ResetBits 用来设置一组 IO 口中一个或者多个 IO 口为低电平。
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
比如我们要设置 GPIOB.5 输出高,方法为:
GPIO_SetBits(GPIOB,GPIO_Pin_5);//GPIOB.5 输出高
设置 GPIOB.5 输出低电平,方法为:
GPIO_ResetBits(GPIOB,GPIO_Pin_5);//GPIOB.5 输出低
4.9 复用功能选择寄存器(AFRH 和 AFRL)
这两个寄存器是用 来设置 IO 口的复用功能的。关于这两个寄存器的配置以及相关库函数的使用,在我们上篇博客IO 引脚复用和映射有详细讲解,这里我们就不做过多的说明。
五、固件库操作GPIO的函数
在固件库中,GPIO 端口操作对应的库函数函数以及相关定义在文件 stm32f4xx_gpio.h 和 stm32f4xx_gpio.c 中。对于其他的外设对应的库函数同样可以在相应的源文件找到。不需要死记,知道怎么用,怎么看数据手册,参数应该怎样传递就可以了。
六、GPIO配置的基本步骤
七、通用外设驱动模型(四步法)(掌握)
上面我们简单介绍了GPIO外设的基本配置步骤,在后面深入学习后,我们学习到更多的外设,那么,我们应该按照什么的编码思路来驱动我们的外设呢?那就是按照下面的思路,需要注意的是:对于任何一个外设,我们想要使用它,就应该首先对其初始化(配置相应的寄存器)。
上面我们讲解了 STM32F4 IO 口的基本知识以及固件库操作 GPIO 的一些函数方法,下一次我们将通过两个实验:点亮一个LED灯(掌握)、通过一个按键控制一个LED灯亮灭(掌握)来应用我们所学的知识, 这一节我们就讲解到这里,希望能对大家的开发有帮助。 如有兴趣,感谢点赞、关注、收藏,若有不正地方,还请各位大佬多多指教!