GPIO(General-purpose input/output),中文名叫通用型输入输出,是STM32可控制的引脚,最基本的功能就是控制输出高低电平以及检测输入电平高低,是非常重要的一个外设。在讲GPIO之前,我们先来了解一下端口和引脚的相关定义。
端口与引脚
端口(Port)是单片机内部(CPU单元)和外部IO的接口组 ,以PA端口为例,PA是PortA的缩写,除了PA端口之外还有PB~PH一共8个端口。每个端口有8到16个引脚(以芯片为准):PA有PA0到PA15。引脚(又称管脚,Pin)是芯片外接的一个个管腿,引脚有特殊的引脚比如电源,地引脚,晶振引脚,复位引脚,BOOT引脚之外,其余都可以看作是GPIO引脚,那么GPIO引脚有些是模块引脚,当定义为模块管脚时,PIN起模块中定义的功能。比如定义成SPI模块的管脚,那就按照具体芯片的规定,或者定义成MOSI,或者定义成MISO,或者定义成SCLK,这些不是任意的。那剩下的就是普通元器件接的GPIO,没有特殊化引脚。具体可参考下图。
GPIO模式配置
GPIO可以由软件配置成8种模式:
-
输入浮空 2.输入上拉 3.输入下拉 4.模拟输入 5.开漏输出 6.推挽输出 7.推挽式复用 8.开漏式复用
想理解好这些模式,我们需要重点分析下面的GPIO框图
保护二极管、上下拉电阻
先看图片右侧的1部分,这部分是输入输出共有的部分,包括两个保护二极管和上下拉电阻。
保护二极管作为与IO口最近的元件,起到保护内部芯片的作用。当引脚外部输入过高或是过低的电压时,保护二极管都会生效。当引脚电压高于VDD_FT 时, 上方的二极管导通,当引脚电压低于 VSS 时,下方的二极管导通,防止不正常电压引入芯片导致芯片烧毁。尽管有这样的保护,并不意味着 STM32 的引脚能直接外接大功率驱动器件。
上下拉电阻允许用户配置相应开关控制引脚的默认电平状态,当我们开启上拉开关时,IO引脚默认接在VDD上,开启下拉时,IO引脚默认接在VSS低电平。通过上下拉电阻的配置,我们可以消除引脚状态不确定的影响。只要引脚没有接入外部器件,或是已知外部设备不会干扰引脚电平时,我们可以确定当前引脚的电平状态。那如果我们将两个开关都断开,选择”既不上拉也不下拉模式“,我们称这种状态叫浮空模式,在这种模式下我们用电压表测引脚电压会得到不确定的电压值。我们一般情况下会选择上拉或是下拉来保证引脚有默认的电平状态。还要强调一点是,在芯片内部的上拉是弱上拉,即通过此上拉输出的电流是很弱的 ,也就是说配置成上拉之后并不会影响输出的结果。
上下拉电阻的作用:
- 在芯片上电后和引脚配置之前消除引脚不确定的状态
- 提高驱动能力,输出能力(上拉)
- 可代替跳线帽,选择BOOT模式启动
- 提高远距离信号线的抗干扰能力
- 配合开漏输出模式输出高电平
输出模式
再来看看图片下半部分,也就是输出的相关结构。我们从左往右看
可以看到控制输出的高低电平信号由两部分构成,一部分是芯片直接控制输出高低电平,这一部分操作是由两个寄存器完成:GPIOx_ODR寄存器和GPIOx_BSRR寄存器,另一部分输出为复用功能输出,所谓:“复用”功能,指的是片上外设对GPIO引脚进行控制,此时GPIO 引脚用作该外设功能的一部分,算是第二用途。 例如串口通讯时我们可以用某个GPIO引脚用作串口的发送引脚或是接收引脚,此时只需要将引脚配置成复用成串口功能即可,在原理图上我们看到一个引脚往往有许多功能,但是同时一个引脚只能使用一种功能,当引脚被复用为其他功能时,引脚的高低电平控制就不受ODR寄存器和BSRR寄存器控制了,同时也要注意,不是所有的引脚都可以复用成任意一种功能,即使是复用也是芯片合理分配的引脚,复用前要查阅原理图看该引脚是否有对应复用功能。
那不论哪种方式得到的输出信号,进入输出控制后,就有两种输出模式可以选择,而这两种输出模式则是通过两个MOS管P-MOS和N-MOS来进行输出。
首先是推挽输出(Pull-Push Mode 简称PP Mode),当输入高电平时,上方P-MOS管导通,下方N-MOS管关闭,对外输出高电平;当输入低电平时,N-MOS 管导通, P-MOS 关闭,对外输出低电平。 这样通过两个MOS管来进行高低电平的切换,使得负载能力和开关速度都很大的提高。推挽输出的低电平为0v,高电平为3.3v。
然后是开漏输出(Open Drain Mode 简称OD Mode),当引脚配置为开漏输出模式后,上方的 P-MOS 管完全不工作。如果我们控制输出为 0,低电平,则 P-MOS 管关闭, N-MOS 管导通,使输出接地,若控制输出为 1 (它无法直接输出高电平)时,则 P-MOS 管和 N-MOS 管都关闭,所以引脚既不输出高电平,也不输出低电平,为高阻态。 高阻态意思就是电阻值很高,既不是低电平也不是高电平,对外像断路一样,不被任何东西驱动也不能驱动任何东西,如果高阻态再输入下一级电路的话,对下级电路无任何影响,和没接一样。因为这个原因,所以单独的开漏模式下,并不能输出高电平,而是一定要搭配一个外部上拉才能输出高电平。因此开漏输出具有“线与”特性:当很多引脚都配置成开漏模式接在一起,那么只有当所有引脚都输出高阻态时,才能由外部上拉将电平拉高,一旦有一个引脚输出低电平,则整个线路相当于短路接地,输出低电平。IIC就是利用了开漏模式下的线与特性判断总线的占用状态。
还可以看到有一类输出它并不经过双MOS管结构,就是右下角的模拟信号输出,此时GPIO引脚用于DAC作为模拟电压输出通道,模拟信号直接输出到引脚,当GPIO用于模拟功能时(包括输入输出),引脚的上、下拉电阻是不起作用的,这个时候即使在寄存器配置了上拉或下拉模式,也不会影响到模拟信号的输入输出。
推挽输出有P-MOS和N-MOS控制,而开漏输出只有N-MOS有作用
总的来说,大多数情况下我们会选择推挽输出,其负载能力强且高低电平切换速度快。只有某些特殊情况下,我们才会选择开漏模式,例如I2C、 SMBUS 通讯等需要“线与”功能的总线电路中。 而且开漏模式具有推挽模式不具备的优点,由于推挽模式下电平只有0~3.3v,但是开漏输出的高电平取决于上拉电源,可以在外部上拉一个5v电源,这样开漏模式下的IO口可以输出5v的高电平。
输入模式
最后我们来看看上半部分的输入结构。从右往左看框图。
在输入模式下,输出被禁止。最终的数据寄存器每隔一个AHB1时钟周期更新一次当前引脚的电平状态。IO口的引脚在经过上下拉电阻引入后,如果有复用功能输入,则该引脚的电平状态不会经过施密特触发器直接由复用的外设读取。若是取消复用输入,则引入的电平会经过施密特触发器作用。简单的来说模拟量的电压信号经过施密特触发器后会变成0、1的数字信号。具体原理是当输入电压高于正向阈值电压,输出为高;当输入电压低于负向阈值电压,输出为低;当输入在正负向阈值电压之间,输出不改变。经过施密特触发器后的数字信号直接由输入数据寄存器(GPIOx_IDR)储存以供读取。如果GPIO 引脚用于 ADC 采集电压的输入通道时,用作“模拟输入”功能,此时信号是不经过施密特触发器的,因为经过施密特触发器后信号只有 0、 1 两种状态,所以 ADC 外设要采集到原始的模拟信号,信号源输入必须在施密特触发器之前。与模拟输出一样,上下拉电阻也是不起作用的。
寄存器
关于寄存器,在本文中,与GPIO相关的寄存器我只提及了名字,并没有强调其用法,是因为在初步的学习中我们通过调用库函数可以暂时忽略其底层的寄存器操作,但是了解寄存器的具体功能也是非常有必要的,那了解寄存器一定要查阅《STM32F4xx 参考手册》中对应外设的寄存器说明。
外设介绍
发光二极管
发光二极管简称为LED,长脚为正极,短脚为负极。右图中二极管带有发光剪头的图标就是LED在原理图中的符号。当 LED 内有电流通过时会发光,在安全电流范围内,电流越大,亮度越亮。
按键
按键简称KEY,分为独立按键和矩阵键盘,在这里我们只介绍独立按键。独立按键有四个针脚,其中有两对引脚是默认导通的,所以对外可以看作只有两个引脚,有些原理图中KEY也只有两个引脚。按键的一段接在芯片的IO口上,另一端要么接在VCC上要么接在GND上,这对我们配置按键有影响,需要我们查看原理图。
由于按键是机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。 因而在闭合及断开的瞬间均伴随有一连串的抖动,如下图所示。
这些忽高忽低的电平也会被单片机IO口捕获到,那这对我们的逻辑判断是非常不利的,所以我们要消除这些抖动,保留稳定的电平状态。按键消抖有硬件方案也有软件方案:硬件方案是在按键上并联一个电容,利用电容的充放电特性对抖动过程产生的电压毛刺进行平滑处理实现消抖。软件方案目前也有两种,因为人为按下或者松开开关,稳定的电平持续时间在100ms左右,抖动的时间一般不会超过10ms,所以我们的思路就是在检测到按键按下/松开时,等待10ms再进行检测,如果检测结果一致,则判断按键按下/松开。这种方法叫延时重采样法,具体有两种实现方法,这两种方法分别对应按键作为GPIO输入和GPIO作为外部中断(这个之后会讲到)。另一种方法叫持续采样法,也就是在检测到某个电平时对之后的一段时间周期进行持续检测,如果在采样周期内电平保持不变则确认按键按下/松开。
延时重采样法
-
优点:操作简单
-
缺点
-
如果延时太短,有可能两次采样时都处于抖动时间,因此可能引起误判;
-
如果延时太长,可能检测不出按键变换
-
持续采样法
-
优点:样本足够多,减少误判的可能性。
-
缺点:持续检测的时间太长(大于按键按下和释放的时间差),则可能无法检测按键的变换
按键检测完的输出有两种形式
另一种是像门铃一样的脉冲输出,每按下一次按键就输出一个脉冲信号 如key_out2
Key_out1的输出与Key_in的变换趋势相同,只是滤除了抖动成分;
Key_out2则是每当按键按下后输出一个高电平脉冲。在大多数的应用中会用到Key_out2所示功能。
虽然按键硬件上按下就是按下,松开后信号就马上断开,但是我们可以在软件上利用一个变量和非运算符 !来实现台灯一样的开关效果
实际操作
我们打开STM32CubeMX,选择MCU为STM32F427IIHx
再打开《Robomaster开发板用户手册》可以看到下方列表中10处有8个LED,13为自定义按键,18还有两个红绿LED
然后我们在A型板原理图pdf中搜索(Ctrl+F)LED和KEY,得到他们的引脚。这里可以看出,LED是一端是接在VCC也就是高电平上的,那么如果引脚输入高电平,发光二极管两边没有电势差,自然也就不会亮,如果引脚输入低电平,发光二极管就会依据电流大小发光。按键也是连在VCC上的,那相应地我们要给PB2配一个下拉电阻,如果浮空不能确定引脚的电平状态。
这些引脚在芯片原理图上也有标注(上图是片上外设原理图)
那个8XLED的没有直接注明是LED搜索不到,但是可以通过LED的符号查出他们的引脚
知道引脚之后我们在STM32CubeMX上进行配置
再配置LED的引脚
最后点击右上角的GENERATE CODE 生成代码
进入MDK-ARM文件夹,找到以uvprojx结尾的文件,双击我们会用keil打开它。可以在左侧的工程目录里看到Application/User/Core文件夹中除了默认的main.c外,STM32CubeMX还生成了gpio.c,接下来我们打开gpio.c来对比一下,看看CubeMX到底帮我们做了些什么工作。
可以看出,在STM32CubeMX中配置的代码都在keil中生成了本应该由用户自己写的配置代码,并且代码量也不小,因此ST公司才大力推行与STM32CubeMX配套使用的HAL库。
接下来就是我们开始编写代码了,那看先本次任务涉及的函数,之前谈到在function窗口内我们可以看到各个.c文件里有哪些函数。
我们今天重点学习以下三个GPIO相关的函数。
-
HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
-
HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState);
-
HAL_GPIO_TogglePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
先看HAL库中的ReadPin函数,HAL库中每个函数上方都有函数的相关说明,我们来一一解释一下:
-
brief:简要说明一下这个函数的作用,此处表明ReadPin函数可以读取某个特殊的引脚的输入电平状态
-
param:是参数parameter的缩写,说明中一共有两个@param说明这个函数有两个参数。
-
参数一:GPIOx,说明中说GPIOx,x可以是A~I(429是A到K)中任意一个来选中GPIO的端口
-
参数二:GPIO_Pin,GPIO_Pin可以选中端口中一个具体的引脚,x可以0~15
-
-
retval:是return value的缩写,此处说明ReadPin函数的返回值是读取到的电平状态
到此就介绍完了ReadPin函数,也就是说ReadPin函数只要我们能选中我们想要读取的引脚的端口和引脚号,ReadPin函数就能返回其当前的电平状态。那如果你还不知道应该怎么填写参数,或是不知道其返回值返回什么,那可以用一个keil中非常好用的功能:go to definition of,通俗地说如果你不知道一个函数、变量、宏定义、类型的定义是什么,你可以将鼠标悬停在其上方,右键然后go to definition of ,软件会自动跳转到相应的定义处。
那么类似地,我们来看看WritePin函数和TogglePin函数
如果你完全理解了上面的函数解读的话,你应该也能理解这两个函数的用法,这里我就不在像之前一样详细解释了。
-
WritePin函数:输入三个参数:引脚端口号,引脚号,电平状态,没有返回值。执行该函数,单片机会将你所选的引脚和电平状态将 该引脚的电平拉高或拉低。
-
TogglePin函数:输入两个参数:引脚端口号,引脚号。执行该函数单片机会将你所选的引脚电平状态反转,如果之前是高电平则置 低成低电平,如果之前是低电平则拉高成高电平。
最后我们介绍一个制作流水灯必要的函数:延时函数 HAL_Delay函数,这是一个毫秒级的延时函数。在stm32f4xx_hal.c里
用法也非常简单,如果你想延时1s,也就是1000ms,写成函数为HAL_Delay(1000);
接下来是代码实现,可以看到我所有的代码都是写在begin和end之间,防止被擦除。
流水灯的思路就是将一个LED亮100ms然后灭紧接着下一个LED亮100ms,当然100ms只是测试效果比较好的一种,你也可以试试延时不同时长达到不同效果的流水灯,但是必须要有延时,因为芯片内部执行代码的速度相当快,如果你将一个IO口电平拉高后马上置低,肉眼观察LED是不会亮的,那我是用A型板的8×自定义LED,如果真要写流水灯的话要写8×3也就是24行代码,这有些冗杂,那可以看到我用了6行代码就实现了流水灯,是因为我把我住了这些IO口的特点,我们在原理图上可以看到这8个IO口是PG1~PG8,所以他们的端口都是GPIOG,所以我自己宏定义了一个LED_GPIO_Port代表GPIOG,当然也可以直接用GPIOG,只是这样代码可读性高一些,在看看Pin1到Pin8,我们利用go to definition of 可以查到如下结果:
为了更好地展示原理,我将16进制数在右侧写成二进制,可以很明显的看到这些pin口地址是独立而且相邻的,也就是说如果我对地址0000 0011进行拉低电平,那么0脚和1脚的灯会同时亮,可以看到在宏定义的下方有个GPIO_PIN_ALL 的值为0xFFFF,转换为二进制就是每一位都是1,也就是选中了0~15中所有的pin口,那现在思路可以转换成0000 0001如何转换到0000 0010一直转换到1000 0000,我们可以以十进制作为对比,如果你有数 1,那你要得到10 ,100, 1000,……1000 0000,你的想法自然是×10,×102,……,那现在是二进制,你要从1转换到 10,自然要×2,转换到100,要×22,转换到1000 0000,就要×2?。可以看到我的程序中,对pin的判断是pin>128在重新归零,保证乘数pin从1到128都取得到。如果有学会位操作的,可以尝试用左移运算符来重写该功能,这里不做演示。
按键输入的代码就比较简单了,主要就是一个按键消抖,具体做法就是延时后再检测。
写好程序之后呢,我们就要将程序从电脑下载到开发板上,这涉及到单片机仿真器,也被叫做下载器或者调试器。市面上常用的调试器除了J-Link ,ST-Link还有CMSIS-DAP(有时简称DAP:Debug Access Port 即调试访问端口)。他们都兼容SWD模式和JTAG模式,DAP还有虚拟串口的功能,可以当做USB转TTL用。但三者中只有DAP是电脑免驱的,stlink和jlink都需要安装驱动,关于安装驱动网上的教程也非常多,可以自行安装。
ST-LINK V2 J-LINK CMSIS-DAP
J-TAG接口:JTAG(Joint Test Action Group,联合
标准的JTAG接口是6线:VCC、GND、TMS、TCK、TDI、TDO,分别为电源线两根、模式选择、时钟、数据输入和数据输出线
SWD接口:串行调试(Serial Wire Deb 、CLK、DIO,分别是电源信号线两根、串行时钟输入、串行数据输入输出
A型开发板留了SWD接口可供我们下载调试。接线就是一一对应。 计算机—USB接口—调试器—SWD接口—单片机。
<img src="https://gitee.com/Jiang-LH/picture-footage/raw/master/img/image-20210813145655804.png" alt="image-20210813145655804" style="zoom:50%;" />
如果VCC和GND都接正确的话,那么开发板上电后,配对的一对无线调试器的指示灯就会常亮,这时候我们再打开keil,点开魔术棒按钮
至此,全部的准备工作都已经完成,我们只需要点击烧录按钮观察流水灯效果和测试按键即可。学会原理之后,可以尝试各种灯效。
总结
本次课程我们需要重点掌握以下几点:
-
GPIO输入输出框图、GPIO的8种模式以及GPIO的三个函数
-
查看开发板手册查阅外设种类,以及通过芯片原理图查找外设相应引脚
-
STM32CubeMX的使用
-
如何利用keil中Function窗口来查阅函数用法
-
keil中 go to definition of 的功能
-