文章目录
GPIO概念
General-purpose input/output通用型输入输出接口,类似于51的p0-p3
GPI 通用输入
GPO 通用输出,通过寄存器来控制其输入输出的电平。
在Stm32F407中,STM32F407ZGT6
- 一共有7组IO口,GPIOA到GPIOG。
- 每组IO口有16个IO
- 一共16X7=112个IO
- 外加2个PH0和PH1
一共114个IO口
GPIO的使用注意
STM32IO口哪些兼容5V
凡是数据手册中引脚描述,标有FT的标志就是兼容5V
一定不要接超过5V的电压
不要直接用IO口驱动感性负载(电机,继电器等),因为断开瞬间会产生很大的反电动势,将IO烧坏。(可以接一个泄放二极管)
默认不能做输出的GPIO
默认的五个口不能作为IO口输出
只有禁止了相应的端口,才能释放IO引脚
GPIO硬件
原理图
注释:I/O引脚:接收外部发出的信号或者向外部发出信号。
STM32的GPIO地址
STM32F407的GPIO地址
(出自STM32F407中文参考手册P192)
STM32F4的GPIOA寄存器地址映射
每组 GPIO 有 10 个寄存器,每个寄存器都是32位,占有 4 个地址,一共占用 40 个地址,地址偏移范围为(0x00~0x24)。
GPIO都是挂载在 AHB1 总线之上,所以它的基地址是由 AHB1 总线的基地址+GPIOA 在 AHB1总线上的偏移地址决定的。
stm32f4xx.h 定位到 GPIO_TypeDef
typedef struct
{
__IO uint32_t MODER;
__IO uint32_t OTYPER;
__IO uint32_t OSPEEDR;
__IO uint32_t PUPDR;
__IO uint32_t IDR;
__IO uint32_t ODR;
__IO uint16_t BSRRL;
__IO uint16_t BSRRH;
__IO uint32_t LCKR;
__IO uint32_t AFR[2];
} GPIO_TypeDef;
然后定位到:
#define GPIOA ((GPIO_TypeDef *) GPIOA_BASE)
GPIOA_BASE的宏定义:
#define GPIOA_BASE (AHB1PERIPH_BASE + 0x0000)
依次类推,可以找到最顶层:
#define AHB1PERIPH_BASE (PERIPH_BASE + 0x00020000)
#define PERIPH_BASE ((uint32_t)0x40000000)
所以我们便可以算出 GPIOA 的基地址位:
GPIOA_BASE= 0x40000000+0x00020000+0x0000=0x40020000
存储器映射表:
GPIO的八种工作模式
浮空输入
浮空输入GPIO_IN_FLOATING,可以做KEY识别,RX1。
- 浮空输入状态下,IO的电平状态是不确定的,完全由外部输入决定,如果在该引脚悬空的情况下,读取该端口的电平是不确定的。
带上拉输入
GPIO_IPU——IO内部上拉电阻输入。
- 将一个输入端口连接至高电平信号的电路拓扑中。这种情况,在外部没有将该输入口拉向地线时,输入端口处于高电平状态。
带下拉输入
GPIO_IPD—— IO内部下拉电阻输入。
- 将一个输入端口连接至低电平信号的电路拓扑中,一般为地。这种情况,在外部没有将该输入口拉向高电平时,输入端口处于低电平状态。
模拟输入
GPIO_AIN ——应用ADC模拟输入,或者低功耗下省电。
- 将连续的物理信号转换为数字信号(即模拟信号),将其送入数字电路中。通常,模拟输入要采用模数转换器(ADC)等外部元件来实现。
开漏输出
输出端相当于三极管的集电极,要得到高电平状态需要上拉电阻才行。适合于做电流型的驱动,其吸收电流的能力相对强(一般20mA以内)。
- 在开漏输出模式下,一个输出端口可以踢动低电平先后,但是无法提供高电平信号。
如上所示,当输出低电平时NPN晶体管被打开,输出端口拉向地。当输出引脚需要输出高电平时,NPN晶体管被关闭,输出端口处于断路状态。
GPIO_OUT_OD ——IO输出0接GND,IO输出1,悬空,需要外接上拉电阻,才能实现输出高电平。当输出为1时,IO口的状态由上拉电阻拉高电平,但由于是开漏输出模式,这样IO口也就可以由外部电路改变为低电平或不变。
- 也就是如果外部接上拉,你应该设为开漏输出。
- 可以读IO输入电平变化,实现C51的IO双向功能。
上下拉的详情可以参考:电路原理分析
开漏输出和推挽输出的区别最普遍的说法就是开漏输出无法真正输出高电平,即高电平时没有驱动能力,需要借助外部上拉电阻完成对外驱动。
- 开漏输出的这一特性一个明显的优势就是可以很方便的调节输出的电平,因为输出电平完全由上拉电阻连接的电源电平决定。所以在需要进行电平转换的地方,非常适合使用开漏输出。
- 开漏输出的这一特性另一个好处在于可以实现"线与"功能,所谓的"线与"指的是多个信号线直接连接在一起,只有当所有信号全部为高电平时,合在一起的总线为高电平;只要有任意一个或者多个信号为低电平,则总线为低电平。而推挽输出就不行,如果高电平和低电平连在一起,会出现电流倒灌,损坏器件。所以总线一般会使用开漏输出.
开漏输出应用:
- 模拟I2C使用开漏输出_OUT_OD,接上拉电阻,能够正确输出0和1;
推挽输出
推挽电路是两个参数相同的三极管或MOSFET,以推挽方式存在于电路中
- 各负责正负半周的波形放大任务
- 电路工作时,两只对称的功率开关管每次只有一个导通,所以导通损耗小、效率高。
- 输出既可以向负载灌电流,也可以从负载抽取电流。
- 推拉式输出级既提高电路的负载能力,又提高开关速度。
GPIO_OUT_PP ——IO输出0-接GND, IO输出1 -接VCC,读输入值是未知的。
- 推挽输出结构是由两个MOS或者三极管受到互补控制的信号控制,两个管子时钟一个在导通,一个在截止。
推挽输出的最大特点是可以真正的输出高电平和低电平,在两种电平下都具有驱动能力。 - 所谓的驱动能力,就是指输出电流的能力。对于驱动大负载(即负载内阻越小,负载越大)时,例如IO输出为5V,驱动的负载内阻为10ohm,于是根据欧姆定律可以正常情况下负载上的电流为0.5A(推算出功率为2.5W)。一般的IO不可能输出这么大的电流。于是造成的结果就是输出电压会被拉下来,达不到标称的5V。
- 推挽输出高低电平的电流都能达到几十mA。
- 当输出引脚需要高电平时,PMOS晶体管被打开,输出端口拉向VCC。当输出引脚需要低电平时,NMOS晶体管被打开,将输出端口拉向GND。
推挽输出的缺点是,如果当两个推挽输出结构相连在一起,一个输出高电平,另一个输出低电平,电流会从第一个引脚的VCC通过上端MOS再经过第二个引脚的下端MOS直接流向GND,也就是会发生短路,进而可能造成端口的损害。这也是为什么推挽输出不能实现" 线与"的原因。
- 推挽输出在输出的时候是通过单片机内部的电压,所以他的电压是不能改变的。
一般情况下,使用推挽输出。
注意:推挽状态下,是可以读取IO口的电平状态的。
复用功能的推挽输出
GPIO_AF_PP ——片内外设功能(I2C的SCL,SDA)。
复用功能的开漏输出
GPIO_AF_OD——片内外设功能(TX1,MOSI,MISO.SCK.SS)。
模式配置
模拟输入_AIN ——应用ADC模拟输入,或者低功耗下省电。
浮空输入_IN_FLOATING ——可以做KEY识别,RX1
开漏输出_Out_OD——应用于I2C总线; (STM32开漏输出若外部不接上拉电阻只能输出0)
STM32F407的GPIO寄存器
10个寄存器可以控制一组GPIO的16个IO口。
一个端口模式寄存器(GPIOx_MODER)
一个端口输出类型寄存器(GPIOx_OTYPER)
一个端口输出速度寄存器(GPIOx_OSPEEDR)
一个端口上拉下拉寄存器(GPIOx_PUPDR)
一个端口输入数据寄存器(GPIOx_IDR)
一个端口输出数据寄存器(GPIOx_ODR)
一个端口置位/复位寄存器(GPIOx_BSRR)
一个端口配置锁存寄存器(GPIOx_LCKR)
两个复用功能寄存器(低位GPIOx_AFRL & GPIOx_AFRH)
GPIOx_MODER端口模式寄存器
x = A…I
1-0 ModeRy 输入还是输出
GPIOx_OTYPER端口输出类型寄存器
x = A…I
中文参考手册7.4.2,187
15-0 OTy端口配置
GPIOx_OSPEEDR端口输出速度寄存器
GPIO port output speed register
OSPEEDRY 输出速度
GPIO相关库函数
GPIO某一位赋值与清零
可以先对寄存器的值进行&清零操作
GPIOA->CRL&=0XFFFFFF0F; //将第 4-7 位清 0
然后再与需要设置的值进行|或运算
GPIOA->CRL|=0X00000040; //设置相应位的值,不改变其他位的值
将 BSRR 寄存器的第 pinpos 位设置为 1,1左移了pinpos位
GPIOx->BSRR = (((uint32_t)0x01) << pinpos);
可以提高代码的可读性以及可重用性。
类似这样的代码很多:
GPIOA->ODR|=1<<5; //PA.5 输出高,不改变其他位
GPIO_SetBits/ResetBits
实现功能:控制某个GPIO引脚的输出电平(拉高 / 拉低)
GPIO_SetBits 拉高引脚输出电平
GPIO_ResetBits 拉低引脚输出电平
PBin() 和PBoutn()
顾名思义
#define PBout(n) BIT_ADDR(GPIOB_ODR_Addr,n) //输出
#define PBin(n) BIT_ADDR(GPIOB_IDR_Addr,n) //输入
GPIO_ReadOutputData()、GPIO_ReadInputData
uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);
一次读取一组 IO 口所有 IO 口输出状态
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);
读取一组 IO 口的一个或者几个 IO 口输入电平
GPIO_ReadOutputDataBit
uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
一次读取一组 IO 口中一个或者几个 IO 口的输出状态
GPIO_ReadInputDataBit()
通过调用函数 GPIO_ReadInputDataBit()来读取 IO 口的状态的。
GPIO_ReadInputDataBit(GPIOF, GPIO_Pin_5);
STM32F407的GPIO初始化
使能IO口时钟
这里需要注意的是:在配置 STM32 外设的时候,任何时候都要先使能该外设的时钟。
- 可以先参考407时钟的RCC相关函数
- 官方库提供了五个打开 GPIO 和外设时钟的函数分别为:
void RCC_AHB1PeriphClockCmd(uint32_t RCC_AHB1Periph, FunctionalState NewState);
void RCC_AHB2PeriphClockCmd(uint32_t RCC_AHB2Periph, FunctionalState NewState);
void RCC_AHB3PeriphClockCmd(uint32_t RCC_AHB3Periph, FunctionalState NewState);
void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);
void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);
由RCC函数可知,F407中GPIO是挂载在AHB下的。
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能 GPIOA 时钟
GPIO初始化
GPIO的每个 IO 口可以自由编程,但 IO 口寄存器必须要按 32 位字被访问。
代码
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_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
GPIO_Mode
一般外设使用时都用的是复用模式。
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_Speed
F407的GPIO_Speed与F103也是有区别的,一般使用50M就可以。
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
有四个可选值。实际上这就是配置的 GPIO对应的 OSPEEDR 寄存器的值。
GPIO_OType
GPIO_OType是F407新增的,GPIO 的输出类型设置,实际上是配置的 GPIO 的 OTYPER 寄存器的值。
typedef enum
{
GPIO_OType_PP = 0x00,
GPIO_OType_OD = 0x01
}GPIOOType_TypeDef;
输出推挽模式,那么选择值 GPIO_OType_PP。
输出开漏模式,那么设置值为 GPIO_OType_OD。
GPIO_PuPd
F407新增,设置 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 为下拉。所谓的下拉就是空闲的时候是低电平。
STM32F407端口复用
每个GPIO口有很多功能,默认的是作为普通IO口,此时不配置复用相关的东西。
- 当GPIO作为其他功能时,这个时候就要配置复用相关代码了
复用:内置外设基本上与I/O口共用管脚的,也就是I/O管脚的复用。
- 比如串口 1 的引脚对应的 IO 为 PA9,PA10.PA9,PA10 默认功能是 GPIO,所以当PA9,PA10 引脚作为串口 1 的 TX,RX 引脚使用的时候,那就是端口复用。
- stm32f4的所有外设没有默认引脚,当使用外设时必须要使用GPIO_PinAFConfig函数选择引脚进行端口复用。
- stm32f4的外部中断与其他外设分开了,外部中断使用SYSCFG进行操作。stm32f4的SYSCFG有独立的时钟
- 当使用AD时,选择模拟模式而不是复用(特殊)。
复用器
STM32F4 系列微控制器 IO 引脚通过一个复用器连接到内置外设或模块。
- 该复用器一次只允许一个外设的复用功能(AF)连接到对应的 IO 口。这样可以确保共用同一个 IO 引脚的外设之间不会发生冲突。
- 每个 IO 引脚(如GPIOA_Pin0)都有一个复用器,该复用器采用 16 路复用功能输入(AF0 到 AF15),可通过GPIOx_AFRL(针对引脚 0-7)和 GPIOx_AFRH(针对引脚 8-15)寄存器对这些输入进行配置,每四位控制一路复用:
1)完成复位后,所有 IO 都会连接到系统的复用功能 0(AF0)。
2)外设的复用功能映射到 AF1 到 AF13。
3)Cortex-M4 EVENTOUT 映射到 AF15。
复用器的图:
四个注意点: - JTAG/SWD:在器件复位之后,默认就是 JTAG/SWD 功能。
- RTC_REFIN:此引脚在系统复位之后要使用的话要配置为浮空输入模式。
- MCO1 和 MCO2:这些引脚在系统复位之后要使用的话要配置为复用功能模式。
- 除了 ADC 和 DAC 要将 IO 配置为模拟通道之外其他外设功能一律要配置为复用功能模式
GPIOx_AFRL 寄存器
32 位寄存器 GPIOx_AFRL 每四个位控制一个 IO 口,所以每个寄存器控制32/4=8 个 IO 口。寄存器对应四位的值配置决定这个 IO 映射到哪个复用功能 AF。
代码
串口1的发送接收引脚是PA9,PA10。当我们把PA9,PA10不用做GPIO,而用做复用功能串口1的发送接收引脚的时候,叫端口复用。也就是指明这个IO要做什么外设。
- USART2_RX串口2除了PA2,PA3。PD6,PD5也有USART2_RX的功能。
使用配置:
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//使能USART1时钟
//串口1对应引脚复用映射
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); //*PA9 连接 AF7,复用为 USART1_TX */
GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1); ///* PA10 连接 AF7,复用为 USART1_RX*/
//USART1端口配置
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
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
这句还是比较特殊的
GPIO_PinAFConfig
- 使用GPIO_PinAFConfig函数进行指定引脚的挂载。除ADC和DAC外的所有非GPIO功能,都要使用GPIO_PinAFConfig函数进行端口复用
- 端口复用没有时钟,直接使用即可。
- 作用就是将 IO 连接到所需的 AFx,AF代表的就是端口复用
复用外设选择:
#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))
Key实验设置
实现通过4 个按钮(WK_UP、KEY0、KEY1 和 KEY2),来控制板上的 2 个 LED(DS0 和 DS1)和蜂鸣器
其中 WK_UP 控制蜂鸣器,按一次叫,再按一次停;
KEY2 控制 DS0,按一次亮,再按一次灭;
KEY1 控制 DS1,效果同KEY2;
KEY0 则同时控制 DS0 和 DS1,按一次,他们的状态就翻转一次。
按键与端口的对应关系见(按键)
KEY_Init
#include "key.h"
#include "sys.h"
#include "delay.h"
//按键初始化函数
void KEY_Init(void) //IO 初始化
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|
RCC_APB2Periph_GPIOE,ENABLE); //使能 PORTA,PORTE 时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2|GPIO_Pin_3|GPIO_Pin_4;//GPIOE.2~4
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //设置成上拉输入
GPIO_Init(GPIOE, &GPIO_InitStructure); //初始化 GPIOE2,3,4
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //初始化 WK_UP-->GPIOA.0
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //PA0 设置成输入,下拉
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化 GPIOA.0
}
//按键处理函数
//返回按键值
//mode:0,不支持连续按;1,支持连续按;
//0,没有任何按键按下;1,KEY0 按下;2,KEY1 按下;3,KEY2 按下 ;4,KEY3 按下 WK_UP
//注意此函数有响应优先级,KEY0>KEY1>KEY2>KEY3!!
u8 KEY_Scan(u8 mode)
{
static u8 key_up=1; //按键按松开标志
if(mode)key_up=1; //支持连按
if(key_up&&(KEY0==0||KEY1==0||KEY2==0||KEY3==1))
{
delay_ms(10); //去抖动
key_up=0;
if(KEY0==0)return KEY0_PRES;
else if(KEY1==0)return KEY1_PRES;
else if(KEY2==0)return KEY2_PRES;
else if(KEY3==1)return WKUP_PRES;
}else if(KEY0==1&&KEY1==1&&KEY2==1&&KEY3==0)key_up=1;
return 0; // 无按键按下
}
KEY_Scan()函数,则是用来扫描这 4 个 IO 口是否有按键按下。
KEY_Scan()函数,支持两种扫描方式,通过 mode 参数来设置。
当 mode 为 0 的时候,KEY_Scan()函数将不支持连续按,扫描某个按键,该按键按下之后必须要松开,才能第二次触发,否则不会再响应这个按键,这样的好处就是可以防止按一次多次触发,而坏处就是在需要长按的时候比较不合适。
当 mode 为 1 的时候,KEY_Scan()函数将支持连续按,如果某个按键一直按下,则会一直返回这个按键的键值,这样可以方便的实现长按检测。
要注意的就是,该函数的按键扫描是有优先级的,最优先的是 KEY0,第二优先的是 KEY1,接着 KEY2,最后是 WK_UP 按键。
该函数有返回值,如果有按键按下,则返回非 0 值,如果没有或者按键不正确,则返回 0。
头文件 key.h 里面的代码:
#ifndef __KEY_H
#define __KEY_H
#include "sys.h"
#define KEY0 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4)//读取按键 0
#define KEY1 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3)//读取按键 1
#define KEY2 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2)//读取按键 2
#define WK_UP GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)//读取按键 3(WK_UP)
#define KEY0_PRES 1 //KEY0 按下
#define KEY1_PRES 2 //KEY1 按下
#define KEY2_PRES 3 //KEY2 按下
#define WKUP_PRES 4 //WK_UP 按下(即 WK_UP/WK_UP)
void KEY_Init(void); //IO 初始化
u8 KEY_Scan(u8); //按键扫描函数
#endif
主函数的代码
#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "beep.h"
//ALIENTEK 战舰 STM32 开发板实验 3
//按键输入实验
int main(void)
{
u8 key;
delay_init(); //延时函数初始化
LED_Init(); //LED 端口初始化
KEY_Init(); //初始化与按键连接的硬件接口
BEEP_Init(); //初始化蜂鸣器端口
LED0=0; //先点亮红灯
while(1)
{
key =KEY_Scan(0); //得到键值
if(key)
{ switch(t)
{ case WKUP_PRES: //控制蜂鸣器
BEEP=!BEEP;break;
case KEY2_PRES: //控制 LED0 翻转
LED0=!LED0;break;
case KEY1_PRES: //控制 LED1 翻转
LED1=!LED1;break;
case KEY0_PRES: //同时控制 LED0,LED1 翻转
LED0=!LED0;
LED1=!LED1;break;
}
}else delay_ms(10);
}
}
在仿真调试的时候MDK 不会考虑 STM32 自带的上拉和下拉,所以我们得自己手动设置一下,来使得其初始状态和外部硬件的状态一摸一样。
在 General Purpose I/O E 窗口内的 Pins 里面勾选 2、3、4 位,
要改变状态就把 Pins 的 PE2 取消勾选,再次执行过这句,得到 key 的值为 3。
位带操作
#define KEY0 PEin(4) //PE4
#define KEY1 PEin(3) //PE3
#define KEY2 PEin(2) //P32
#define WK_UP PAin(0) //PA0
LED实验设置
LED硬件连接图
当输出1的时候无电压差,灭。
当输出0 的时候有压差,亮。
应该设置推挽输出,上拉,默认就是灭。
读取输入电平
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);
第一个作用:读取某个GPIO的输入电平。实际操作的是GPIOx_IDR寄存器。
GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_5);//读取GPIOA.5的输入电平
第二个作用:读取某组GPIO的输入电平。实际操作的是GPIOx_IDR寄存器。
GPIO_ReadInputData(GPIOA);//读取GPIOA组中所有io口输入电平
读取输出电平
uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);
作用:读取某个GPIO的输出电平。实际操作的是GPIO_ODR寄存器。
GPIO_ReadOutputDataBit(GPIOA, GPIO_Pin_5);//读取GPIOA.5的输出电平
作用:读取某组GPIO的输出电平。实际操作的是GPIO_ODR寄存器。
GPIO_ReadOutputData(GPIOA);//读取GPIOA组中所有io口输出电平
设置输出电平
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
作用:设置某个IO口输出为高电平(1)。实际操作BSRRL寄存器
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
作用:设置某个IO口输出为低电平(0)。实际操作的BSRRH寄存器。
void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal);
这两个函数不常用,也是用来设置IO口输出电平。
初始化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;//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 最大可以提供 25mA 电流(来自数据手册),而蜂鸣器的驱动电流是 30mA左右。
引脚PF8 BEEp
NPN 三极管(S8050)来驱动蜂鸣器,R61 主要用于防止蜂鸣器的误发声。
- 当 PF.8 输出高电平的时候,蜂鸣器将发声
- 当 PF.8 输出低电平的时候,蜂鸣器停止发声。
初始化
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 拉低,
}