读取IO口输入电平调用的库函数为:
uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin);
读取IO口输入电平操作的寄存器为:
GPIOx_IDR:端口输入寄存器
使用位带操作读取IO口输入电平:
PEin(4) -读取GPIOE.4口电平
PEin(n) -读取 GPIOE.n口电平
1.使能按键对应IO口时钟。调用函数:RCC_APB2PeriphClockCmd();
2.初始化IO模式:上拉/下拉输入。调用函数:GPIO_lnit();
3.扫描IO口电平(库函数/寄存器/位操作)。
这个就是学51时最开始用的那个按键检测方法,简单
连续按意思是只要一直按住按键,按键就一直生效,比如增大音量键,按住不放,则音量会一直增加
如果要实现:按键按下,没有松开,只能算按下一次,这个函数无法实现。
u8 KEY_Scan(void)
{
if(KEY被按下)
{
delay_ms(10); //消抖
if(KEY确实被按下)
{
return KEY_Value;
}
return 无效值;
}
}
不支持连续按意思就是,按下按键了,就算不松手,也只算按键被按下一次,必须松手后,再按下,才能算第二次按下,比如开启空调,按住电源键不放,也只是当作按下一次按键,不会说是连续按,电源开了又关,关了又开,不会这样设计
增加一个按下标志位key_up,按下后置0,只有当松手时才置1,要key_up和引脚状态同时为真才返回按键值
u8 KEY_Scan(void)
{
static u8 key_up = 1;
if(key_up && KEY被按下)
{
delay_ms(10); //消抖
if(KEY确实被按下)
{
key_up = 0;
return KEY_Value;
}
}else if(KEY没有被按下){
key_up = 1;
}
return 没有按下
}
在不支持连续按的代码上添加一句 if(mode == 1) key_up = 1;即可,可通过对传入的参数mode进行判断来实现两种按键方式的切换
如果mode == 1,则key_up = 1,则支持连续按;因为key_up被初始化为1,第一次按下时,if(key_up && KEY被按下)判断为真,检测KEY确实被按下后,key_up被置为0,返回 KEY_Value 然后return退出,假如是一直按着按键的,那下一次进行按键检测时,if(mode == 1) 为真,key_up = 1,所以 if(key_up && KEY被按下)判断又为真,继续进入里面执行,继续返回KEY_Value,达到连续按效果
如果mode != 1,则判断为假,key_up = 1不会执行,则这条 if 判断等于没有,代码就是上面不支持连续按的效果
u8 KEY_Scan(u8 mode)
{
static u8 key_up = 1;
if(mode == 1) key_up = 1; //mode == 1,则支持连续按
if(key_up && KEY被按下)
{
delay_ms(10); //消抖
if(KEY确实被按下)
{
key_up = 0;
return KEY_Value;
}
}else if(KEY没有被按下)
{
key_up = 1;
}
return 没有被按下
}
所以要切换按键方式,在main函数while循环中调用KEY_Scan函数时,如果想支持连续按,则传入参数1,如果想不支持连续按,则传入参数不是1即可
void main()
{
while(1)
{
KEY = KEY_Scan(1); //支持连续按
KEY = KEY_Scan(0); //不支持连续按
…………
}
}
定义两个按键状态的宏定义,声明函数
#ifndef _KEY_H_
#define _KEY_H_
#include "stm32f10x.h"
//按键状态宏定义
#define KEY_DOWN 0
#define KEY_UP 1
void KEY_Init(void); //按键引脚初始化函数
u8 KEY_Scan(u8 mode); //按键检测函数
#endif
要先对按键所接到的引脚进行初始化,配置为上拉输入,因为按键另一端接地,按键没按下时,引脚内部上拉电阻作用将引脚拉高,当按键被按下时,接GND,CPU读取引脚输入为低电平
按键扫描函数通过参数mode切换支持长按或不支持长按
#include "KEY.h"
#include "delay.h"
/**
* @name KEY_Init
* @brief 按键初始化
* @param None
* @retval None
*/
void KEY_Init(void)
{
GPIO_InitTypeDef GPIO_Init_Structure;
//先初始化GPIOA的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//配置GPIOA_Pin_8
GPIO_Init_Structure.GPIO_Pin = GPIO_Pin_8; //按键接到了PA8引脚
GPIO_Init_Structure.GPIO_Mode = GPIO_Mode_IPU; //设置为上拉输入
//初始化PA8
GPIO_Init(GPIOA,&GPIO_Init_Structure);
}
/**
* @name KEY_Scan
* @brief 按键扫描
* @param mode:1:支持按键长按;其他:不支持按键长按
* @retval u8
*/
u8 KEY_Scan(u8 mode)
{
static u8 key_up = 1;
//读取PA8引脚的值
u8 KEY1 = GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_8);
if(mode == 1) key_up = 1;
//按键检测
if(key_up && (KEY1 == 0))
{
delay_ms(10); //消抖
//确定按键按下
if(KEY1 == 0)
{
key_up = 0; //记录这次按下
return KEY_DOWN; //返回按下标志,0
}
}else if(KEY1 == 1){ //按键没按下或者松开
key_up = 1;
}
return KEY_UP; //返回没按下标志,1
}
主函数while循环不断检测按键扫描函数,检测到函数返回按键按下标志后,执行亮灯操作
这里使用预编译指令,#ifndef #endif 和 #ifdef #endif,用宏定义KEY_MODE来进行支持长按和不支持长按的切换,这样就不用注释一大块的函数功能,只需注释宏定义语句就行了
当宏定义了KEY_MODE时,编译支持长按的语句;当注释掉宏定义了KEY_MODE时,编译不支持长按的语句
#include "LED.h"
#include "delay.h"
#include "KEY.h"
#define KEY_MODE 1
int main()
{
LED_Init(); //LED初始化
delay_init(); //延时初始化
KEY_Init(); //按键初始化
while(1)
{
#ifndef KEY_MODE
//不支持长按
if(KEY_Scan(0) == KEY_DOWN)
{
//GPIO_ResetBits(GPIOA,GPIO_Pin_1); //点亮LED
GPIOA->ODR ^= (0x01<<1); //对ODR1进行异或取反操作,按一次点亮,再按一次熄灭
}
#endif
#ifdef KEY_MODE
//支持长按
if(KEY_Scan(1) == KEY_DOWN)
{
GPIOA->ODR ^= (0x01<<1); //对ODR1进行异或取反操作,按一次点亮,再按一次熄灭
delay_ms(1000);
}
#endif
}
}
想实现按一次按键亮灯,再按一次就灭灯,这个实现简单,只要把LED的引脚电平翻转即可,但查看了GPIO标准库相关函数发现好像没有让引脚电平翻转的函数,下面是各个函数的功能
然后通过对寄存器进行操作,采用异或的功能,取反某一位,达到按一次亮,再按一次熄灭的效果
GPIOA->ODR ^= (0x01<<1); //对ODR1进行异或取反操作,按一次点亮,再按一次熄灭
在KEY.h头文件中添加状态机的枚举类型,定义枚举类型变量STA_KEY,并用extern声明为外部变量
#ifndef _KEY_H_
#define _KEY_H_
#include "stm32f10x.h"
//按键状态宏定义
#define KEY_DOWN 0
#define KEY_UP 1
//状态机按键枚举类型
typedef enum
{
KEY_Statues1,
KEY_Statues2
}STA_KEY_t;
//定义变量
extern STA_KEY_t STA_KEY;
void KEY_Init(void); //按键引脚初始化函数
u8 KEY_Scan(u8 mode); //按键检测函数
void STA_KEY1(void); //状态机,按键状态1
void STA_KEY2(void); //状态机,按键状态2
#endif
源文件中添加不同状态要执行的函数,状态1按一次按键LED灯亮1秒再熄灭,然后切换到状态2,状态2中按一次按键LED灯亮1秒再熄灭,然后再亮1秒再熄灭,最后再把状态切回到状态1
/**
* @name STA_KEY1
* @brief 状态机-按键状态1
* @param None
* @retval None
*/
void STA_KEY1()
{
GPIO_ResetBits(GPIOA,GPIO_Pin_1); //PA1置0,LED点亮
delay_ms(1000);
GPIO_SetBits(GPIOA,GPIO_Pin_1); //延时1秒后熄灭LED灯
delay_ms(1000);
//切换状态
STA_KEY = KEY_Statues2;
}
/**
* @name STA_KEY2
* @brief 状态机-按键状态2
* @param None
* @retval None
*/
void STA_KEY2()
{
GPIO_ResetBits(GPIOA,GPIO_Pin_1); //PA1置0,LED点亮
delay_ms(1000);
GPIO_SetBits(GPIOA,GPIO_Pin_1); //延时1秒后熄灭LED灯、
delay_ms(1000);
GPIO_ResetBits(GPIOA,GPIO_Pin_1); //PA1置0,LED点亮
delay_ms(1000);
GPIO_SetBits(GPIOA,GPIO_Pin_1); //延时1秒后熄灭LED灯
delay_ms(1000);
//切换状态
STA_KEY = KEY_Statues1;
}
主函数中当按键按下后,再用switch case 语句判断状态,并调用不同状态下的函数
#include "LED.h"
#include "delay.h"
#include "KEY.h"
#define KEY_MODE 1
STA_KEY_t STA_KEY; //定义状态变量
int main()
{
LED_Init(); //LED初始化
delay_init(); //延时初始化
KEY_Init(); //按键初始化
while(1)
{
#ifndef KEY_MODE
//不支持长按
if(KEY_Scan(0) == KEY_DOWN)
{
//GPIO_ResetBits(GPIOA,GPIO_Pin_1); //点亮LED
//GPIOA->ODR ^= (0x01<<1); //对ODR1进行异或取反操作,按一次点亮,再按一次熄灭
//状态机点灯
switch (STA_KEY)
{
case KEY_Statues1:STA_KEY1();break;
case KEY_Statues2:STA_KEY2();break;
default:STA_KEY = KEY_Statues1;break;
}
}
#endif
#ifdef KEY_MODE
//支持长按
if(KEY_Scan(1) == KEY_DOWN)
{
GPIOA->ODR ^= (0x01<<1); //对ODR1进行异或取反操作,按一次点亮,再按一次熄灭
delay_ms(1000);
}
#endif
}
}