1、GPIO简介
GPIO(general porpose intputoutput):通用输入输出端口的简称。可以通过软件控制其输出和输入。stm32芯片的GPIO引脚与外部设备连接起来,从而实现与外部通信,控制以及数据采集的功能。
在STM32F4xx芯片上的GPIO口被分成各个组,一共有7组IO口,以A、B、C、D、E、F、G等命名(不同的stm32芯片,引脚数不同,可能只有其中几组,从A开始标记),称为GPIOA—GPIOG。每组IO口有16个IO,一共16X7=112个IO,外加2个PH0和PH1,一共114个IO口。
以GPIOA为例,分别为GPIOA0、GPIOA1、GPIOA2…GPIOA15。在stm32中,每组16个引脚固定不变,具体有多少组引脚,则需要根据具体的芯片确定。一般我们也称GPIOA0为PA0,以此类推,GPIOA0–GPIOA15对应PA0—PA15。
2、GPIO工作方式
平时接触最多的也就是推挽输出、开漏输出、上拉输入这三种。下面是每一种工作方式的介绍:
4种输入模式
(1) GPIO_Mode_IN_FLOATING 浮空输入
VDD和VSS所在路径的两个开关同时断开。此时没有上拉和下拉的情况,所以当IO口没有接输入的时候,此时的电平会是一个不确定的值,也就是我们所说的浮空。电平会处于一个跳变的状态,一会高,一会低。只有输入了一个高/低电平才会确定下来。我们知道,由于电源的特性,或者是由于外部开关输入的特性,输入的数字信号,极有可能会出现脉冲等噪声的影响,为了让我们的波形更好看,或者信号更加清晰,所以就设置了TTL施密特触发器这个东西。经过之后,我们就会把这个数字信息存储在输入数据寄存器中。 这样我们就读到了IO过来的数字信号
优势:这一种输入模式的电平会完全取决于外部电路而与内部电路无关。有时候会用作对开关按键的读取。 但是在没有外部电路接入的时候,IO脚浮空会使得电平不确定
大白话:如果一个I/O口被我们配置成浮空输入状态,那么图中下半部分的输出模式是不工作的,只有上半部分工作。电平从I/O口引脚输入,首先经过可以配置的上拉和下拉电阻,然后②TTL施密特触发器就会打开,这样I/O引脚的电平就可以存入输入数据寄存器中,CPU就可以从输入数据寄存器中读取I/O引脚的状态。
(2) GPIO_Mode_IPU 上拉输入
上拉的好处就是输入的电平不会上下浮动而导致输入信号不稳定,在没有信号输入的情况下可以稳定在高电平
(3) GPIO_Mode_IPD 下拉输入
下拉输入的好处就是输入的电平不会上下浮动而导致输入信号不稳定,在没有信号输入的情况下可以稳定在低电平。
(4) GPIO_Mode_AIN 模拟输入
在我们使用单片机的时候,我们有时候需要用AD采集到IO口上面的真实电压。这就有了我们所需要的模拟输入。为了让外部的电压真实的读取到单片机的AD模块,我们既不能闭合上拉和下拉的开关,也不能让信号经过施密特触发器。
优势:可以让AD读取电压。还可以在低功耗模式下运行,实现省电的作用。
4种输出模式
(1) GPIO_Mode_Out_OD 开漏输出(带上拉或者下拉)
开漏模式下,“输出控制”不会控制P-MOS管,“输出控制”只会向N-MOS管栅极加一定电压,两个MOS管都处于截止状态,两个漏极处于悬空状态,称之为漏极开路。“输出控制”取消栅极的施加电压,P-MOS管依旧处于高阻态,N-MOS管导通,整体对外为低电平。
开漏输出模式可以等效将下图中灰色框的P-MOS管看作不存在。即该模式下只能输出低电平,若要输出高电平,则需要外接电阻,所接的电阻称为上拉电阻,此时输出电平取决于此时上拉电阻的外部电源电压情况,如下图中蓝色框的外部电路。
(2) GPIO_Mode_AF_OD 开漏复用功能(带上拉或者下拉)
和开漏功能不同的是,输出控制电路中输入1或者0是来自于片上外设(复用功能输出)
(3) GPIO_Mode_Out_PP 推挽式输出(带上拉或者下拉)
推挽输出:可以输出高,低电平,连接数字器件;推挽结构一般是指两个三极管分别受两互补信号的控制,总是在一个三极管导通的时候另一个截止。高低电平由 IC 的电源低定。
也就是说,在写寄存器中 输出控制电路中写‘1’,P-MOS管就会导通,N-MOS管截止,I/O引脚接收到的就是‘1’高电平。在写寄存器中 输出控制电路中写‘0’,N-MOS管就会导通,P-MOS管截止,I/O引脚接收到的就是‘0’低电平。然后读入电路可以读取I/O引脚输出的电平状态。
(4) GPIO_Mode_AF_PP 推挽式复用功能(带上拉或者下拉)
和推挽功能不同的是,输出控制电路中输入1或者0是来自于片上外设(复用功能输出)
4种最大输出速度
2MHZ 25MHz 50MHz 100MHz
3、点亮LED灯
最开始学习STM32的时候就是要点亮LED灯开始(相当于软件中的“hello world”)。
3.1 LED原理图详细
3.2 STM32控制LED原理
若想LED0(DS0)亮,只需要LED1端为低电平,这样LED0这个发光二极管,右端为高电平、左端为低电平,电流可以通过LED0,则LED0亮。若想LED0灭,只需要LED1端为高电平,LED0两端的电平都为高,电流不能通过LED0,LED0灭。左端的LED0连接的是芯片的PF9引脚,这样,只需要使用程序,控制芯片的PF9引脚输出高、低电平即可控制LED0的亮灭。至于左边R41的电阻(阻值为510R),称为上拉电阻(R41连接到高电平),加上510R的电阻,是为了防止电路中电流过大,导致烧了LED0。
3.3 GPIO相关类型
GPIO相关的类型几乎都在stm32f4xx_gpio.h文件中。相比较前面的寄存器,我们写代码的时候,一般更关注本节GPIO相关的宏定义、枚举类型这些部分。在开发时,我们一般直接使用标准库、使用定义好的宏定义、枚举完成对寄存器的配置,而不是对照寄存器手册,一点点配置寄存器。
//位带操作
#define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFFF)<<5)+(bitnum<<2))
#define MEM_ADDR(addr) *((volatile unsigned long *)(addr))
#define BIT_ADDR(addr, bitnum) MEM_ADDR(BITBAND(addr, bitnum))
#define GPIOA_ODR_Addr (GPIOA_BASE+20) //0x40020014
#define GPIOB_ODR_Addr (GPIOB_BASE+20) //0x40020414
#define GPIOC_ODR_Addr (GPIOC_BASE+20) //0x40020814
#define GPIOD_ODR_Addr (GPIOD_BASE+20) //0x40020C14
#define GPIOE_ODR_Addr (GPIOE_BASE+20) //0x40021014
#define GPIOF_ODR_Addr (GPIOF_BASE+20) //0x40021414
#define GPIOG_ODR_Addr (GPIOG_BASE+20) //0x40021814
#define GPIOH_ODR_Addr (GPIOH_BASE+20) //0x40021C14
#define GPIOI_ODR_Addr (GPIOI_BASE+20) //0x40022014
#define GPIOA_IDR_Addr (GPIOA_BASE+16) //0x40020010
#define GPIOB_IDR_Addr (GPIOB_BASE+16) //0x40020410
#define GPIOC_IDR_Addr (GPIOC_BASE+16) //0x40020810
#define GPIOD_IDR_Addr (GPIOD_BASE+16) //0x40020C10
#define GPIOE_IDR_Addr (GPIOE_BASE+16) //0x40021010
#define GPIOF_IDR_Addr (GPIOF_BASE+16) //0x40021410
#define GPIOG_IDR_Addr (GPIOG_BASE+16) //0x40021810
#define GPIOH_IDR_Addr (GPIOH_BASE+16) //0x40021C10
#define GPIOI_IDR_Addr (GPIOI_BASE+16) //0x40022010
#define PAout(n) BIT_ADDR(GPIOA_ODR_Addr,n) //输出
#define PAin(n) BIT_ADDR(GPIOA_IDR_Addr,n) //输入
#define PBout(n) BIT_ADDR(GPIOB_ODR_Addr,n)
#define PBin(n) BIT_ADDR(GPIOB_IDR_Addr,n)
#define PCout(n) BIT_ADDR(GPIOC_ODR_Addr,n)
#define PCin(n) BIT_ADDR(GPIOC_IDR_Addr,n)
#define PDout(n) BIT_ADDR(GPIOD_ODR_Addr,n)
#define PDin(n) BIT_ADDR(GPIOD_IDR_Addr,n)
#define PEout(n) BIT_ADDR(GPIOE_ODR_Addr,n)
#define PEin(n) BIT_ADDR(GPIOE_IDR_Addr,n)
#define PFout(n) BIT_ADDR(GPIOF_ODR_Addr,n)
#define PFin(n) BIT_ADDR(GPIOF_IDR_Addr,n)
#define PGout(n) BIT_ADDR(GPIOG_ODR_Addr,n)
#define PGin(n) BIT_ADDR(GPIOG_IDR_Addr,n)
#define PHout(n) BIT_ADDR(GPIOH_ODR_Addr,n)
#define PHin(n) BIT_ADDR(GPIOH_IDR_Addr,n)
#define PIout(n) BIT_ADDR(GPIOI_ODR_Addr,n)
#define PIin(n) BIT_ADDR(GPIOI_IDR_Addr,n)
3.4 点亮LED(GPIO输出)
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
//LED端口定义
#define LED0 PFout(9) // DS0
#define LED1 PFout(10) // DS1
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);//使能GPIOF时钟 每个外设使用之前都要使能
//GPIOF9,F10初始化设置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;//LED0和LED1对应IO口
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通输出模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
GPIO_Init(GPIOF, &GPIO_InitStructure);//初始化GPIO
GPIO_SetBits(GPIOF,GPIO_Pin_9 | GPIO_Pin_10);//GPIOF9,F10设置高,灯灭
}
int main(void)
{
//delay_init(168); //初始化延时函数 可以暂时忽略
LED_Init(); //初始化LED端口
/**下面是通过直接操作库函数的方式实现IO控制**/
while(1)
{
GPIO_ResetBits(GPIOF,GPIO_Pin_9); //LED0对应引脚GPIOF.9拉低,亮 等同LED0=0;
GPIO_SetBits(GPIOF,GPIO_Pin_10); //LED1对应引脚GPIOF.10拉高,灭 等同LED1=1;
delay_ms(500); //延时300ms
GPIO_SetBits(GPIOF,GPIO_Pin_9); //LED0对应引脚GPIOF.0拉高,灭 等同LED0=1;
GPIO_ResetBits(GPIOF,GPIO_Pin_10); //LED1对应引脚GPIOF.10拉低,亮 等同LED1=0;
delay_ms(500); //延时300ms
}
}
/**
*******************下面注释掉的代码是通过 位带 操作实现IO口控制**************************************
int main(void)
{
delay_init(168); //初始化延时函数
LED_Init(); //初始化LED端口
while(1)
{
LED0=0; //LED0亮
LED1=1; //LED1灭
delay_ms(500);
LED0=1; //LED0灭
LED1=0; //LED1亮
delay_ms(500);
}
}
**************************************************************************************************
**/
/**
*******************下面注释掉的代码是通过 直接操作寄存器 方式实现IO口控制**************************************
int main(void)
{
delay_init(168); //初始化延时函数
LED_Init(); //初始化LED端口
while(1)
{
GPIOF->BSRRH=GPIO_Pin_9;//LED0亮
GPIOF->BSRRL=GPIO_Pin_10;//LED1灭
delay_ms(500);
GPIOF->BSRRL=GPIO_Pin_9;//LED0灭
GPIOF->BSRRH=GPIO_Pin_10;//LED1亮
delay_ms(500);
}
}
**************************************************************************************************
**/
4、按键(GPIO输入)
4.1 功能描述
STM32F4 的 IO口做输入使用的时候,是通过调用函数 GPIO_ReadInputDataBit()来读取 IO 口的状态的。STM32F4 开发板上载有的 4 个按钮(KEY_UP、KEY0、KEY1 和 KEY2),来控制板上的 2 个 LED(DS0 和 DS1)和蜂鸣器,其中 KEY_UP 控制蜂鸣器,按一次叫,再按一次停;KEY0 控制 DS0,按一次亮,再按一次灭;KEY1 控制 DS1,效果同 KEY2;KEY2 则同时控制 DS0 和 DS1,按一次,它们的状态就翻转一次。
4.2 原理图
STM32F4开发板上的按键 KEY0 连接在 PE4 上、KEY1 连接在 PE3上、KEY2 连接在 PE2上、KEY_UP连接在 PA0上。
这里需要注意的是:KEY0、KEY1、KEY2是低电平有效的,而KEY_UP是高电平有效的,并且外部都没有上下拉电阻,所以,需要在STM32F4 内部设置上下拉。
4.3 程序
key文件
/*key.h*/
#ifndef __KEY_H
#define __KEY_H
#include "sys.h"
/*下面的方式是通过直接操作库函数方式读取IO*/
#define KEY0 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4) //PE4
#define KEY1 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3) //PE3
#define KEY2 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2) //PE2
#define WK_UP GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0) //PA0
/*下面方式是通过位带操作方式读取IO*/
/*
#define KEY0 PEin(4) //PE4
#define KEY1 PEin(3) //PE3
#define KEY2 PEin(2) //P32
#define WK_UP PAin(0) //PA0
*/
#define KEY0_PRES 1
#define KEY1_PRES 2
#define KEY2_PRES 3
#define WKUP_PRES 4
void KEY_Init(void); //IO初始化
u8 KEY_Scan(u8); //按键扫描函数
#endif
/*key.c*/
#include "key.h"
#include "delay.h"
//按键初始化函数
void KEY_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA|RCC_AHB1Periph_GPIOE, ENABLE);//使能GPIOA,GPIOE时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4; //KEY0 KEY1 KEY2对应引脚
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;//普通输入模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100M
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
GPIO_Init(GPIOE, &GPIO_InitStructure);//初始化GPIOE2,3,4
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;//WK_UP对应引脚PA0
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN ;//下拉
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA0
}
//按键处理函数
//返回按键值
//mode:0,不支持连续按;1,支持连续按;
//0,没有任何按键按下
//1,KEY0按下
//2,KEY1按下
//3,KEY2按下
//4,WKUP按下 WK_UP
//注意此函数有响应优先级,KEY0>KEY1>KEY2>WK_UP!!
u8 KEY_Scan(u8 mode)
{
static u8 key_up=1;//按键按松开标志
if(mode)key_up=1; //支持连按
if(key_up&&(KEY0==0||KEY1==0||KEY2==0||WK_UP==1))
{
delay_ms(10);//去抖动
key_up=0;
if(KEY0==0)return 1;
else if(KEY1==0)return 2;
else if(KEY2==0)return 3;
else if(WK_UP==1)return 4;
}else if(KEY0==1&&KEY1==1&&KEY2==1&&WK_UP==0)key_up=1;
return 0;// 无按键按下
}
led文件
/*led.h*/
#ifndef __LED_H
#define __LED_H
#include "sys.h"
//LED端口定义
#define LED0 PFout(9) // DS0
#define LED1 PFout(10) // DS1
void LED_Init(void);//初始化
#endif
/*led.c*/
#include "led.h"
//初始化PF9和PF10为输出口.并使能这两个口的时钟
//LED IO初始化
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);//使能GPIOF时钟
//GPIOF9,F10初始化设置
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通输出模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
GPIO_Init(GPIOF, &GPIO_InitStructure);//初始化
GPIO_SetBits(GPIOF,GPIO_Pin_9 | GPIO_Pin_10);//GPIOF9,F10设置高,灯灭
}
beep文件
/*beep.h*/
#ifndef __BEEP_H
#define __BEEP_H
#include "sys.h"
//LED端口定义
#define BEEP PFout(8) // 蜂鸣器控制IO
void BEEP_Init(void);//初始化
#endif
/*beep.c*/
#include "beep.h"
//初始化PF8为输出口
//BEEP IO初始化
void BEEP_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);//使能GPIOF时钟
//初始化蜂鸣器对应引脚GPIOF8
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;//普通输出模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;//下拉
GPIO_Init(GPIOF, &GPIO_InitStructure);//初始化GPIO
GPIO_ResetBits(GPIOF,GPIO_Pin_8); //蜂鸣器对应引脚GPIOF8拉低,
}
main.c文件
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h" //上面有这些文件
#include "beep.h"
#include "key.h"
int main(void)
{
u8 key; //保存键值
delay_init(168); //初始化延时函数
LED_Init(); //初始化LED端口
BEEP_Init(); //初始化蜂鸣器端口
KEY_Init(); //初始化与按键连接的硬件接口
LED0=0; //先点亮红灯
while(1)
{
key=KEY_Scan(0); //得到键值
if(key)
{
switch(key)
{
case WKUP_PRES: //控制蜂鸣器
BEEP=!BEEP;
break;
case KEY0_PRES: //控制LED0翻转
LED0=!LED0;
break;
case KEY1_PRES: //控制LED1翻转
LED1=!LED1;
break;
case KEY2_PRES: //同时控制LED0,LED1翻转
LED0=!LED0;
LED1=!LED1;
break;
}
}else delay_ms(10);
}
}