按键输入的硬件连接(枭龙)
独立键盘:
其中KEY1按键连接在PA0上,可以作普通按键,也可以做待机唤醒输入,KEY2,KEY3,KEY4分别连接到STM32的PC3,PC2,PC1;
这四个按键都可以作为普通IO输入,这四个按键都是低电平有效。(貌似没有矩形键盘)。
读取输入电平函数
GPIO_PinState HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx, uint16_ GPIO_Pin);
作用:读取某个GPIO的输入电平。实际操作的是GPIOx_IDR寄存器。
实例: HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0);
寄存器代码
test.c
#include "sys.h"
#include "usart.h"
#include "delay.h"
#include "led.h"
#include "beep.h"
#include "key.h"
int main()
{
u8 key = 0;
Stm32_Clock_Init(9);
delay_init(72);
LED_Init();
BEEP_Init();
KEY_Init();
LED1 = 0;
LED2 = 0;
LED3 = 0;
while(1)
{
key = KEY_Scan(0);
if( key )
{
switch(key)
{
case KEY1_PRES:
LED1 = 1;
break;
case KEY2_PRES:
LED2 = 1;
break;
case KEY3_PRES:
LED3 = 1;
break;
case KEY4_PRES:
LED1 = 0;
LED2 = 0;
LED3 = 0;
break;
}
}
}
}
key.h
#ifndef __KEY_H
#define __KEY_H
#include "sys.h"
#define KEY1 PAin(0)
//KEY1 PA0 输入
#define KEY2 PCin(3)
#define KEY3 PCin(2)
#define KEY4 PCin(1)
#define KEY1_PRES 1
//按键返回值
#define KEY2_PRES 2
#define KEY3_PRES 3
#define KEY4_PRES 4
void KEY_Init(void);
u8 KEY_Scan(u8);
#endif
key.c
#include "key.h"
#include "delay.h"
void KEY_Init(void)
{
RCC->APB2ENR |= 1<<2; //GPIOA时钟使能
RCC->APB2ENR |= 1<<4; //GPIOC时钟使能
C
GPIOA->CRL &= 0xFFFFFFF0;
GPIOA->CRL |= 0x00000008; //PA0 普通下拉输入
GPIOA->ODR |= 1; //PA0 拉高
GPIOC->CRL &= 0xFFFF000F;
GPIOC->CRL |= 0x0000888F; //PC 2 3 4 普通下拉输入
GPIOC->ODR |= 7<<1; //PC 2 3 4 拉高
}
u8 KEY_Scan(u8 mode)
{
static u8 key_up = 1;//按下状态标志
if(mode)
key_up = 1;//支持连按
if(key_up&&(KEY1==0||KEY2==0||KEY3==0||KEY4==0))
{
delay_ms(10);
//延迟消抖
key_up = 0;
//松开标志,为下次做准备
if(KEY1 == 0) return 1;
else if(KEY2 == 0) return 2;
else if(KEY3 == 0) return 3;
else if(KEY4 == 0) return 4;
}
else if(KEY1 == 1 && KEY2 == 1 && KEY3 == 1 && KEY4 ==1)
key_up = 1;
return 0;
}
HAL库代码
main.c
#include "MyIncludes.h"
int main()
{
System_Init();
LED_Init();
SysTick_Init(NULL);
//NULL为空,相当于关闭systick滴答定时器把
Key_Init();//按键初始化
while(1)
{
Key_Read();
if(Key_Info.Num_Last != Key_Info.Num)
//如果过去的按键值不等于现在的,说明新的按键输入
{
Key_Info.Num_Last = Key_Info.Num;
//让过去的等于现在的,作用:判断是否有按键按下
if(Key_Info.Num != 0)
{
switch(Key_Info.Num)
{
case KEY_ONE:
LED_Open(LED1);
break;
case KEY_TWO:
LED_Close(LED1);
break;
case KEY_THREE:
LED_Open(LED2);
break;
case KEY_FOUR:
LED_Close(LED2);
break;
default:break;
}
}
}
}
}
key.h
#ifndef __KEY_H_
#define __KEY_H_
#include "stm32f1xx.h"
#include "stm32_types.h"
#include "stm32f1xx_hal.h"
#define KEY_SHAKE_DELAY 10
//按键去抖延时
#define UP_TRIGGER 1
//按键抬起触发
#define DOWN_TRIGGER 2
//按键按下触发
#define KEY_TRIGGER_MODE DOWN_TRIGGER
//按键触发方式,按键按下触发
enum key_num
{
KEY_ONE = 1,
KEY_TWO = 2,
KEY_THREE = 3,
KEY_FOUR = 4,
KEY_FIVE = 5,
KEY_SIX = 6,
KEY_SEVEN = 7,
KEY_EIGHT = 8,
KEY_NINE = 9,
};
//注意枚举里面是,(逗号),枚举括号后有个;(分号);
enum key_state
{
Key_UP = 1,
Key_DOWN = 2,
Key_KEEP = 3,
};
typedef struct
{
u8 Num; //按键
u8 State; //按键状态
u8 Num_Last; // 上次按键值
u8 Shake_LastNum; //上次 去抖键值
u32 Key_Delay_Cnt; // 按键去抖计数器
}_KEY_;
extern _KEY_ Key_Info;
void Key_Init(void);
//按键初始化
void Key_Read(void);
//按键读取
#endif
key.c
#include "key.h"
#include "delay.h"
_KEY_ Key_Info;
//结构体变量声明
void Key_Info_Init(void)
//按键结构体信息初始化
{
Key_Info.Num = 0;
//按键值为零
Key_Info.State = Key_UP;
//按键的状态为按键抬起
Key_Info.Num_Last = 0;
//上次按键的值为0
Key_Info.Key_Delay_Cnt = 0;
//去抖计数器清零
}
void Key_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
__GPIOA_CLK_ENABLE();
//使能GPIOA时钟
__GPIOC_CLK_ENABLE();
//使能GPIOC时钟,因为按键分别为
//PA0 PC1 PC2 PC3
GPIO_InitStruct.Pin = GPIO_PIN_1|GPIO_PIN_2|GPIO_PIN_3;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;//输入模式
GPIO_InitStruct.Pull = GPIO_NOPULL; // 上拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;//高速
HAL_GPIO_Init(GPIOC,&GPIO_InitStruct);//管脚初始化
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA,&GPIO_InitStruct);
Key_Info_Init();//按键初始化
}
__INLINE u8 Independent_Key_Scan(void)
{
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET )
{
return KEY_ONE;//KEY1按键按下(WKUP)
}
if(HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_3) == GPIO_PIN_RESET )
{
return KEY_TWO;
}
if(HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_2) == GPIO_PIN_RESET )
{
return KEY_THREE;
}
if(HAL_GPIO_ReadPin(GPIOC,GPIO_PIN_1) == GPIO_PIN_RESET )
{
return KEY_FOUR;
}
return 0;
}
void Key_Read(void)
{
u8 Key_CurrNum; //当前按键值
delay_ms(1);
//每隔1ms扫描一次按键
Key_CurrNum = 0;
//每次读数之前将上次读到的值清除
Key_CurrNum = Independent_Key_Scan();
//获取按键
if((Key_Info.Shake_LastNum == Key_CurrNum)&&(Key_CurrNum != 0))
//如果上次去抖的键值等于当前的键值
{
Key_Info.Key_Delay_Cnt++; //去抖计数
#if KEY_TRIGGER_MODE == DOWN_TRIGGER //按键按下触发
if( Key_Info.Key_Delay_Cnt > KEY_SHAKE_DELAY )
{
Key_Info.Key_Delay_Cnt = KEY_SHAKE_DELAY + 1;
//如果延时计数大于去抖说明有效,然后让延时计数一直等于11
Key_Info.State = Key_KEEP; //按键保持按下
}
if(Key_Info.Key_Delay_Cnt == KEY_SHAKE_DELAY )
{
Key_Info.Num = Key_Info.Shake_LastNum;
//去抖后的按键值赋给Key_Info.Num;
Key_Info.State = Key_DOWN;
//按键按下
}
#endif
}
else
//说明按下值与上次去抖的值不同
{
#if KEY_TRIGGER_MODE == DOWN_TRIGGER
//如果模式是按键按下触发
Key_Info.Key_Delay_Cnt = 0;
//计数器清零
Key_Info.Num = 0;
//按键值清零
Key_Info.State = Key_UP;
//按键状态抬起,因为不足去抖时间
goto KEY_END;
//就是将当前按键值赋予上次去抖值
#elif KEY_TRIGGER_MODE == UP_TRIGGER
//如果模式是抬起触发
if( Key_Info.Key_Delay_Cnt < KEY_SHAKE_DELAY )
//如果计数器小于按键去抖延时,说明按键无效
{
Key_Info.Key_Delay_Cnt = 0;
//计数器清零
Key_Info.Num = 0;
//按键值清零
Key_Info.State = Key_UP;
//按键状态抬起
goto KEY_END;
}
else
//如果计数器大于等于按键去抖延时,说明抬起的时间够
{
Key_Info.Key_Delay_Cnt = 0;
//清零计数器
Key_Info.Num = Key_Info.Shake_LastNum;
//记录按键值
Key_Info.State = Key_UP;
//按键状态抬起
goto KEY_END;
}
#endif
}
KEY_END:
Key_Info.Shake_LastNum = Key_CurrNum;
}
总结补充
按键实验main.c和key.h都很好理解,为什么
#define UP_TRIGGER 1
#define DOWN_TRIGGER 2
这个没有也可以,只是标记一下触发方式 1 2 其实没什么用
这个也很好理解,因为在后面有配置按键触发方式
#define KEY_TRIGGER_MODE DOWN_TRIGGER
主要的是key.c 有按键结构体信息初始化,按键IO初始化,按键扫描 按键读取,前三个中规中矩,按键读取感觉就有点绕,我的理解是这样的:
void Key_Read(void)思路:
首先声明一个当前按键值Key_CurrNum用来存取读取的按键值,
按键按下触发:
刚开始当前的当前按键值Key_CUrrNum不等于上次去抖按键值Shake_LastNum于是进入
计数器为零,按键为零,因为未达到按键去抖延时,所以,按键状态还是抬起,然后进入 KEY_END;让当前按键值Key_CUrrNum等于上次去抖按键值Shake_LastNum,如果没有继续,那么下次还执行上面的函数,如果继续按下那么第二次扫描当前按键值仍然等于上次去抖按键值Shake_LastNum,则进入
每进入一次,去抖计时加一,等去抖计时等于10的时候,说明按键按下,如果一直按,就是去抖计时大于10,则让去抖计时就等于按键去抖延时+1,应该是防止去抖计时过大按键抬起触发
首先要将
GPIO_PIN_RESET 改成 GPIO_PIN_RESET;
也是刚开始当前的当前按键值Key_CUrrNum不等于上次去抖按键值Shake_LastNum且去抖计时小于按键去抖延时于是进入
清零计数器,按键值清零,按键状态为抬起,并且将让当前按键值Key_CUrrNum等于上次去抖按键值Shake_LastNum,如果没有继续则下次还是进行上面图片中的函数,如果继续抬起则进入
开始去抖计时,直到去抖计时大于等于按键抖动延时,然后开始执行
清零计数器,(这里我考虑了一下如果不清零计数器,和按下触发一样,也就是将去抖计时等于按键抖动延时+1,会怎么样?但是感觉按键抖动延时时间很短,应该都差不多)
记录有效按键值,按键状态为抬起,如果一直抬起,则 经过按键抖动延时后再次记录有效按键值