资料下载链接:
链接:https://pan.baidu.com/s/1hsIibEmsB91xFclJd-YTYA?pwd=jauj
提取码:jauj
1. GPIO的基本结构
1.1 概述
GPIO(General Purpose Input Output)意思是通用输入输出口可配置为8种输入输出模式,其引脚电平:0V~3.3V,部分引脚可容忍5V(容忍5V的意思是可以在这个端口输入5V,相当于输入一个高电平,而输出一直都是3.3V不变)。IO口引脚定义图里面在IO口电平这一栏标注FT的就是可以容忍5.5V输入的。(如下图标红区域所示)
输出模式下可控制端口输出高低电平,用以驱动LED、控制蜂鸣器(直接通过GPIO来控制高低电平即可),如果要控制较大功率的电路则需要在此基础上加入驱动电路,模拟通信协议输出时序(例如I2C,SPI等某个芯片的特定协议)等功能。
输入模式下可读取端口的高低电平或电压,用于读取按键输入、外接模块电平信号输入(例如光敏电阻模块与热敏电阻模块)、ADC电压采集(模数转换器)、模拟通信协议接收数据(接收通信线上的数据)等
1.2GPIO的结构
1.2.1 GPIO的基本结构
上图就是GPIO的整体构造,其中紫色部分是APB2外设总线,在STM32中所有的GPIO都是挂载在APB2外设总线上的,而GPIO的命名是类似像GPIOA,GPIOB......等等这样来命名的,其中每个GPIO上有16个引脚命名为PX0到PX15(X代表的是哪个GPIO)。而每个GPIO里有寄存器和驱动器两部分,STM32中是内核通过APB2总线对寄存器来进行读写(高电平给1,低电平给0),由于STM32是32位单片机,所以每个寄存器都是32位,而GPIO的端口只有十六位,所以每个寄存器只有低十六位才有端口可以进行GPIO的输入输出操作,而高十六位是保持不变的。这里驱动器是增强信号的驱动能力,让信号可以更明显的传达给单片机。
GPIO的位结构(如下图所示)
绿色部分:这里接两个保护二极管是为了对输入电压进行限幅(保护内部电路),VDD接3.3V 的电压,VSS接0V的电压,当输入电压超过3.3V时上方二极管就会导通,电压流入VDD不进入内部电路,避免过高的电压对内部电路造成伤害,当输入电压超过3.3V时下方二极管就会导通,电流流入VSS不从内部电路汲取电流,对内部电路形成保护,而当电压在3.3V~0V时则直接流入内部电路,二极管不导通。
输入部分:当IO口进入输入模式的时候,这里接了一个上拉电阻和一个下拉电阻。上拉电阻至VDD,下拉电阻至VSS,这个开关是可以通过程序进行配置的,如果上面导通,下面断开,就是上拉输入模式,如果上面断开,下面导通,就是下拉输入模式。如果两个都断开,就是浮空输入模式,这里其实是为了给输入提供一个默认的输入电平,因为对于一个数字端口,输入不是高电平就是低电平,如果输入引角什么都不接,那就不知道是高电平还是低电平,这时候的输入就会处于一种浮空状态。引脚的输入电平会受到外界干扰而改变,为了避免引脚悬空导致输入数据不稳定,我们就需要加上拉电阻或下拉电阻。如果接入上拉电阻,当引脚悬空时,有上拉电阻来保证引脚的输入方式是高电平,如果接入下拉电阻,就默认为低电平的输入方式。同时这里的上拉电阻和下拉电阻的阻值是非常大的。是一种弱上拉和弱下拉目的是为了尽量保证不影响正常的输入操作接下来是一个施密特触发器,这里翻译错误,英文版的手册给的就是施密特触发器,而这个施密特特触发器的作用就是对输入电压进行整形的输入。如果输入电压大于某一阈值,输出就会瞬间变拉高为高电频,如果输入电压小于某一阈值,输出就会瞬间降为低电频。通过施密特触发器整形的波形就可以直接写入输入数据寄存器。就可以得到端口的输入电平。输入数据寄存器上面有两条线是连接单片机上面的外设,其中模拟输入是连接ADC模块的,ADC模块是要将模拟电路数字化,所以说要接到施密特触发器之前(为了将模拟信号准确数字化),而复用功能是连接到其他读取端口的外设上,所以要接到施密施密特触发器之后。(施密特触发器的具体作用如下图所示)
黑色部分代表具体的输入电压,而红色部分代表经过施密特触发器处理后的电压只有当输入电压达到高电平的阈值上限后才会变成高电平,当输入电压达到低电平的阈值下限后才会变为低电平。
输出部分:如果选择通过输出数据寄存器进行控制,就是普通的IO口来写这个数据寄存器的某一位,就可以操作对应的某个端口了。左边有一个叫做位设置清除寄存器。用来单独操作输出数据寄存器的某一位,而不影响其他位,而这个输出数据寄存器可以控制同时控制16个端口,并且这个寄存器只能整体读写。所以如果想单独控制其中某一个端口而不影响其他端口的话,就需要一些特殊的操作方式。第一种方式就是先读出这个寄存器,然后用按位或和按位与两种方式更改某一位,最后再将更改后的数据写回去即可。第二种方式是通过设置位设置和位清除寄存器,如果我们要对某一位进行置1的操作,在位设置寄存器上的对应位写1即可,剩下不需要操作的位写0,这样内部就会有电路生成。自动将输出数据集存器中对应位置为1,而剩下写0的位则保持不变,这样就保证了只操作其中一位而不影响其他位,并且这是一步到位的操作。如果想对某一位进行清零的操作,就在位清除寄存器的对应位写1即可。这样内部电路就会把这位清零了。接下来输出控制之后,就接到两个MOS管,上面是P—MOS,下面是N—MOS。这个MOS管就是一种电子开关,我们的信号通过控制开关的导通和关闭。开关负责将IO口接到VDD或者VSS。在这里可以选择推挽、开路或关闭三种输出方式。在推挽输出模式下,P—MOS和N—MOS均有效。数据寄存器为1是上导,上管导通,下管断开,输出直接接到VDD,就是输出高电平。数据寄存器为零时,上管断开,下管导通,输出直接接到VSS,就是输出低电平。这种模式下,高电平均有较强的驱动能力,所以推挽输出模式也可以叫做强推输出模式在推广输出模式下STM32对IO口具有绝对的控制性,,在开漏输出模式下,这个P—MOS是无效的,只有N—MOS是在工作的。数据寄存器为1时,下管断开,这个输出相当于断开,已经是高阻模式。数据寄存器为零时,下管导通,输出直接接到VSS,也就是输出低电频,这种模式下只有低电平有驱动能力,高电平是没有驱动能力的。开路模式是可以作为通信协议的驱动方式(I2C),在多机通信的情况下,这个模式可以避免各个设备的相互干扰。另外,开路模式还可以用于输出五伏的电频信号,(在IO口外接一个上拉电阻到五伏的电源),当输入电平低电平时,由内部的N—MOS直接接到VSS.当输出高电平时,由外部的上拉电阻拉高至五伏,这样就可以输出5伏的电频信号,用兼容一些5V电平的设备,这就是开。这就是开路输出的主要用途,剩下的一种状态就是关闭,这个是当引角配置为输入模式的时候,两个MOS管都无效,也就输出关闭端口的电频由外部信号来控制的(输入模式)。
1.2.2GPIO的输出与输入模式
GPIO的输出模式有以下四种方式:
(1)开漏输出:数字输出,可输出引脚电平,高电平为高阻态,低电平接VSS。
(2)推挽输出:数字输出 可输出引脚电平,高电平接VDD,低电平接VSS。
(3)复用开漏输出:数字输出 由片上外设控制,高电平为高阻态,低电平接VSS。
(4)复用推挽输出:数字输出,由片上外设控制,高电平接VDD,低电平接VSS。
(5)浮空输入:数字输入 可读取引脚电平,若引脚悬空,则电平不确定
(6)上拉输入:数字输入 可读取引脚电平,内部连接上拉电阻,悬空时默认高电平
(7)下拉输入:数字输入 可读取引脚电平,内部连接下拉电阻,悬空时默认低电平
(8)模拟输入:模拟输入 GPIO无效,引脚直接接入内部ADC
本章节用到的普通输出与输入模式的硬件电路在上面对GPIO的位结构中有讲解,所以接下来重点讲解GPIO的复用推挽输出与复用开漏输出和模拟输入:
模拟输入的基本原理图如下:
GPIO模拟输入原理图
由此图可以看出,输出是断开的,并且施密特触发器也是关闭的,所以整个GPIO的结构是没有用的,所以模拟输入是直接从引脚接入单片机上面的外设(ADC),所以当我们使用ADC的时候给GPIO引脚配置为模拟输入即可。
复用推挽输出与复用开漏输出具体硬件原理图如下所示:
GPIO的复用功能配置
由此图和上面GPIO的位结构图对比可以看到复用功能设置时输出控制并没有和输出数据寄存器相连接,引脚电平高低的控制权转移到了与单片机外设上,由片上外设控。而在输入部分片上外设也可以读取电平的高低,同时普通的输入也是有效的,也可以同时接收电平信号。对于GPIO的八种输入输出功能,除了模拟输入关闭数字输入的功能,在其他七种模式中数字输入模式都是有效的。
2. GPIO输出的实验案例
2.1 对LED和蜂鸣器的简单介绍
LED:发光二极管,正向通电点亮,反向通电不亮(判断LED正负极一般两种方式:第一种方式是从侧面观察两条引出线在管体内的形状.较小的是正极 ,第二种方式是看引脚的长短,引脚长的为正极,短的为负极)。
实物图与电路图如下:
插件式发光二极管
有源蜂鸣器:内部自带振荡源,将正负极接上直流电压即可持续发声,频率固定
无源蜂鸣器:内部不带振荡源,需要控制器提供振荡脉冲才可发声,调整提供振荡脉冲的频率,可发出不同频率的声音(STM32所用是有源蜂鸣器)。
2.2 LED灯闪烁实验
由于我们拿到的开发板是没有组装,需要我们手动组装,具体接线如下图所示。(本实验只需要将最小系统板与一个LED灯连接起来)
实物图如下:
实验内容:
LED闪烁即是用延时函数来实现的(给LED亮灭的时间进行控制即可达到闪烁的效果)延时函数在我们配置好STM32CubeMX之后会自动生成,我们只需要拿来使用即可。注意再次强调工程名最好不是中文路径,中文路径可以生成代码,但是容易出错。
由接线图可得我们给LED接线的是PA0引脚所以在STM32CubeMX中将PA0设置为输出模式即可,剩余其他配置按照工程模板不变即可生成初始代码。
cubemx操作
得到初始化代码后每次都要进行如下操作才能使用STlink将代码下载到单片机中:点击魔术棒点Debug,仿真器选择STlink然后点Settings在flash Download中勾选Reset and Run。
具体手打代码只需要实现给LED亮灭的时间通过延时函数控制即可。
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
HAL_GPIO_WritePin(LED_GPIO_Port,LED_Pin,GPIO_PIN_RESET );
//电平给低让LED亮起来
HAL_Delay(300);//进行延时
HAL_GPIO_WritePin(LED_GPIO_Port,LED_Pin,GPIO_PIN_SET );
//电平给高熄灭LED
HAL_Delay(300);//进行延时
}
LED_GPIO_Port与LED_Pin是对GPIOA和GPIO_PIN_0的宏定义让代码可读性提高。
2.3 LED流水灯实验
流水灯的接线图相比LED闪烁就是多接了几个LED其他相比LED闪烁没有任何变化(接线图如下所示)。
实物图如下
实验内容:
这个实验只需要通过延时函数和HAL_GPIO_WritePin不断对电平写高写低再加上延时函数即可,在STM32中配置时只需要对这些LED的引脚设为输出模式即可。
cubemx配置
具体手写代码如下:
while (1)
{
HAL_GPIO_WritePin(LED0_GPIO_Port,LED0_Pin,GPIO_PIN_RESET );
//拉低电平让第一个LED亮起来
HAL_Delay(300);//延时
HAL_GPIO_WritePin(LED0_GPIO_Port,LED0_Pin,GPIO_PIN_SET );
//拉高电平熄灭第一个LED
HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_RESET );
//拉低电平让第二个LED亮起来
HAL_Delay(300);//延时
HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_SET );
//拉高电平熄灭第二个LED
HAL_GPIO_WritePin(LED2_GPIO_Port,LED2_Pin,GPIO_PIN_RESET );
//拉低电平让第三个LED亮起来
HAL_Delay(300);//延时
HAL_GPIO_WritePin(LED2_GPIO_Port,LED2_Pin,GPIO_PIN_SET );
//拉高电平熄灭第三个LED
/*下方代码重复上述步骤即可。*/
HAL_GPIO_WritePin(LED3_GPIO_Port,LED3_Pin,GPIO_PIN_RESET );
HAL_Delay(300);
HAL_GPIO_WritePin(LED3_GPIO_Port,LED3_Pin,GPIO_PIN_SET );
HAL_GPIO_WritePin(LED4_GPIO_Port,LED4_Pin,GPIO_PIN_RESET );
HAL_Delay(300);
HAL_GPIO_WritePin(LED4_GPIO_Port,LED4_Pin,GPIO_PIN_SET );
HAL_GPIO_WritePin(LED5_GPIO_Port,LED5_Pin,GPIO_PIN_RESET );
HAL_Delay(300);
HAL_GPIO_WritePin(LED5_GPIO_Port,LED5_Pin,GPIO_PIN_SET );
HAL_GPIO_WritePin(LED6_GPIO_Port,LED6_Pin,GPIO_PIN_RESET );
HAL_Delay(300);
HAL_GPIO_WritePin(LED6_GPIO_Port,LED6_Pin,GPIO_PIN_SET );
HAL_GPIO_WritePin(LED7_GPIO_Port,LED7_Pin,GPIO_PIN_RESET );
HAL_Delay(300);
HAL_GPIO_WritePin(LED7_GPIO_Port,LED7_Pin,GPIO_PIN_SET );
}
2.4 蜂鸣器报警
蜂鸣器的具体接线图如下:
实物图如下:
实验内容:
实现蜂鸣器报警的原理与LED闪烁基本一致,让蜂鸣器发出声响,我们只需要将蜂鸣器的IO口给低电平,让电流导通即可。而对于STM32的配置也如上面两个实验一样如法炮制,由于蜂鸣器接的IO口是PB12,所以将PB12设置成输出模式即可,而具体的手打代码也与LED闪烁类似,只不过是宏定义名称不同,具体手打代码如下。
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
HAL_GPIO_WritePin(BEZZER_GPIO_Port,BEZZER_Pin,GPIO_PIN_RESET );
//BEZZER是对IO口PB12的宏定义
HAL_Delay(500);
HAL_GPIO_WritePin(BEZZER_GPIO_Port,BEZZER_Pin,GPIO_PIN_SET );
HAL_Delay(500);
}
3. GPIO输入的实验案例
3.1 按键的硬件电路与光敏传感器的硬件电路
3.1.1 按键的硬件电路
在介绍按键的硬件电路之前先认识一下按键:按键是最常见的输入设备,按下导通,松手断开。按钮按下去的时候,下面两个引脚就接通,松手后这两个引角就自动断开:
本图旁边这个图按键抖动原理,如果不对按键进行消抖的话,会出现按键按下一次,单片机反应多次的现象,而实现按键消抖最常用的办法是按键按下和松手时进行延时,耗过按键抖动时间,直接进入平稳的电平。
按键的具体接线方式有以下四种方式:
上图中,1与2是下接按键的方式,3与4是上接按键的方式,一般单片机就是用下接按键的方式(与LED的接入方式相吻合),图1下接按键就是外接GND负极,这样当按键按下时候电路导通,直接给单片机引脚给低电平,而当按键松手时,此时单片机引脚处于浮空状态,浮空状态时单片机引脚不确定,这个时候要求IO口的输入模式必须是上拉输入模式(当按键没有被按下时上拉输入模式认定引脚此时为高电平),否则会出现引脚电压不明确的状态,而图2对比图1就多了一个上拉电阻,这样当按键松手之后,上拉电阻就会起作用将引脚拉到高电平,这样引脚电压就会确定下来,所以此时IO口的输入模式可以是上拉输入,也可以是下拉输入。(图3与图4的接法原理与上述两种相似并且不太 常用,所以不做过多赘述)
3.1.2 传感器的硬件电路
首先认识传感器模块:传感器模块:传感器元件(光敏电阻/热敏电阻/红外接收管等)的电阻会随外界模拟量的变化而变化,通过与定值电阻分压即可得到模拟电压输出,再通过电压比较器进行二值化即可得到数字电压输出。(实物图如下)
内部集成电路如下图所示:
传感器的电路原理图
以上这些电路中的字母所表示的含义如下:
VCC:电源正极 3.3V—5V,GND:电源负极,DO(Digital Output):数字量输出接口,AO(Analog Output):模拟量输出接口,LED1:电源指示灯,LED2:数字量输出指示灯,LM393:在此用作电压比较器,R2:电位器(用于调节模块的灵敏度)
工作原理:
电压比较器工作过程,当用LM393电压比较器时,如果阳极电压大于阴极电压,那么输出将会使高电平,如果阴极电压大于阳极电压,将会输出低电平。
DO是LM393电压比较器的输出端,其阳极连接到了R1电阻与N1电阻之间,作被比较电压,而阴极接到了一个电位器上,作校准电压,所以当阳极的电压大于校准电压时,LM393电压比较器DO端口输出高电位,反之亦然,但是DO口本就通过电阻连接VCC,所以DO口默认输出高电位,只有当被比较电压低于校准电压时,DO口才输出低电压,此时LED2灯发光。
灵敏度调节:灵敏度调节是通过R2电位器进行调节的,当电位器端的电压调的较小时,也就是电压比较器的阴极电压比较小时,NTC热敏电阻将需要承受更高的温度,才能使阳极电压小于阴极电压,从而输出低电位,以上就是关于传感器颞部集成电路的自我认识。
而单片机相连的电路如下图所示:
stm32的某个io口与传感器模块相连的原理图
由于是集成的模块电路所以只需要做好接线就可以,具体接线就是GND接GND,VCC接VCC,然后DO随便接一个IO口即可,而AO的原理将会在ADC模块进行讲解,现在不做赘述。
3.2 按键控制LED
按键控制LED的具体组装图如下图所示
具体实物图如下所示:
实验内容:
本实验要求按键能独立控制LED的亮灭,设灯的初始状态为灭,按键第一次按下之后灯亮,按键再次按下之后灯灭,两个按键能独立控制两个LED实现亮灭,具体STM32Cube MX与Keil MDK配置如下:
首先在STM32CubeMX中将PA1与PA2配置为输出模式,将PB1与PB 11配置为输入模式(如下图所示)
配置为输入模式
然后设置GPIO口的时候由于上一节讲到的按键输入是上拉输入模式所以应该将按键配置成上拉输入模式
上拉输入模式
其他的设置保持不变,然后得到初始化代码,然后从本节实验开始要掌握模块化编程具体操作如下:
为了让代码的可读性更高我们不能让所有的手打代码都都在主函数中写出,而对于像LED与Key这样的驱动函数我们把它放在一起处理。
(1)点击manage project items然后在group组里面新建一个文件组Hardware。
添加组
(2)在MDK—ARM的文件夹中再新建一个驱动函数的文件名称与(1)中名称最好相同。
磁盘路径上新建文件夹
(3)点击魔术棒再点C/C++中的include path 将在MDK—ARM中新建的文件加入进来。
导入包含路径
(4)在Hardware中添加.c文件与.h文件时要保证路径在Hardware中
添加Item到组
这样就能创建一个.c文件与.h文件将驱动程序封装起来。
接下来开始打手动代码具体按键控制LED操作代码如下
//LED模块代码
#include "main.h"
#include "gpio.h"
void LED1_ON(void)
{
HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_RESET );//点亮LED灯
}
void LED1_OFF(void)
{
HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_SET );//熄灭LED灯
}
void LED1_Turn(void)//检测LED灯的状态
{
if(HAL_GPIO_ReadPin(LED1_GPIO_Port,LED1_Pin)==0)//如果灯此时亮着将电平拉高熄灭LED
LED1_OFF();
else//如果灯此时灭着将电平拉低点亮LED
LED1_ON();
}
/*LED2与LED1代码实现一致所以不作注释*/
void LED2_ON(void)
{
HAL_GPIO_WritePin(LED2_GPIO_Port,LED2_Pin,GPIO_PIN_RESET );
}
void LED2_OFF(void)
{
HAL_GPIO_WritePin(LED2_GPIO_Port,LED2_Pin,GPIO_PIN_SET );
}
void LED2_Turn(void)
{
if(HAL_GPIO_ReadPin(LED2_GPIO_Port,LED2_Pin)==0)
LED2_OFF();
else
LED2_ON();
}
//按键代码模块
#include "main.h"
#include "gpio.h"
uint8_t Get_KeyNum(void)
{
uint8_t KeyNum=0;
if(HAL_GPIO_ReadPin(Key1_GPIO_Port,Key1_Pin)==0)//判断按键是否按下
{
HAL_Delay(10);//按下消抖
while(HAL_GPIO_ReadPin(Key1_GPIO_Port,Key1_Pin)==0);//检验是否松手
HAL_Delay(10);//松手消抖
KeyNum=1; //获取键值
}
if(HAL_GPIO_ReadPin(Key2_GPIO_Port,Key2_Pin)==0)
{
HAL_Delay(10);
while(HAL_GPIO_ReadPin(Key2_GPIO_Port,Key2_Pin)==0);
HAL_Delay(10);
KeyNum=2;
}
return KeyNum;
}
//主函数模块
while (1)
{
KeyNumber=Get_KeyNum();//获取键值
if(KeyNumber==1)//控制LED1
LED1_Turn();//LED1状态反转
if(KeyNumber==2)//控制LED2
LED2_Turn(); //LED2状态反转
}
3.3 光敏传感器控制蜂鸣器
光敏传感器控制蜂鸣器的接线图如下图所示:
实物图如下图所示:
实验内容:
光敏传感器控制蜂鸣器报警的实验要求,当周围光很弱的时候,蜂鸣器会发出报警,当周围光亮足够时,蜂鸣器不响。所以做好实验我们就要用光敏来控制蜂鸣器报警,与按键控制LED有相同之处具体STM32Cube MX与Keil MDK配置如下:
首先根据接线图来将蜂鸣器连接的引脚PB12配置成输出模式,将光敏传感器的引脚PB13配置成上拉输入模式,其他配置保持不变:
cube配置pb13为上拉输入
对于手打代码,仍然需要进行模块化编程对蜂鸣器模块进行封装处理,具体操作方式与按键控制LED中基本一致,直接看代码模块:
//蜂鸣器模块代码:
#include "main.h"
#include "gpio.h"
void Buzzer_ON(void)//蜂鸣器响
{
HAL_GPIO_WritePin(Buzzer_GPIO_Port,Buzzer_Pin,GPIO_PIN_RESET);
}
void Buzzer_OFF(void)//关闭蜂鸣器
{
HAL_GPIO_WritePin(Buzzer_GPIO_Port,Buzzer_Pin,GPIO_PIN_SET);
}
void Buzzer_Turn(void)//蜂鸣器状态反转
{
if(HAL_GPIO_ReadPin(Buzzer_GPIO_Port,Buzzer_Pin)==0)
Buzzer_OFF();
else
Buzzer_ON();
}
//主函数代码模块:
while (1)
{
//判断周围光照强弱
if(HAL_GPIO_ReadPin(LightSensor_GPIO_Port,LightSensor_Pin)==1)
Buzzer_ON();
else
Buzzer_OFF();
}