STM32查询式按键输入[直接用寄存器]

当我们按下一个按键,LED灯做出反转,再按另一个,蜂鸣器随之响起,如何做到这些,这一章就带你领略——按键输入。
对于按键输入有两种方式:
1、查询式(不断检测GPIO口电平变化)
2、中断式(触发中断进入中断服务程序)
这一章先讲查询式,学过中断之后再讲中断式。
基本思路如下:
1、开启GPIO口时钟;
2、配置按键输入方式;
3、扫描按键是否输入;
4、根据按键做出动作。

目录

  • 一、电路连接
  • 二、按键配置
    • 1、时钟的开启
    • 2、上拉下拉配置
    • 3、按键检测
    • 4、根据按键做动作
  • 三、总程序

一、电路连接

STM32查询式按键输入[直接用寄存器]_第1张图片
STM32查询式按键输入[直接用寄存器]_第2张图片

四个按键:
WK_UP---->PA0
KEY2------->PE2
KEY1------->PE3
KEY0------->PE4
注意看自己开发板按键电路图

二、按键配置

1、时钟的开启

由电路图可得,按键涉及的IO口有PA,PE,那我们得开启GPIOA和GPIOE的时钟,关于开启外设时钟方法之前有讲到,故这里不再赘述,它需用到RCC_APB2ENR(外设时钟使能寄存器)。

2、上拉下拉配置

上拉是上拉至电源(高电平),下拉是拉至地(低电平)。那为什么要设置上拉下拉呢?我们的按键还没按下的时候,GPIO口输入的电平是不确定的,有可能高,有可能低,这时候外界对IO口可能会造成干扰,从而影响设备的动作,所以通过上拉下拉,把GPIO口电位钳置在高电平或低电平,来增强它的抗干扰能力。
STM32查询式按键输入[直接用寄存器]_第3张图片
根据电路图可以得知,WK_UP要设置为下拉,如果设置为上拉就是高电平,按键按下和松开都是高电平,没有变化,同理,KEY0、KEY1、KEY2要设置为上拉。
STM32查询式按键输入[直接用寄存器]_第4张图片
如图,设置上拉下拉我们还是用GPIOx_CRL(端口配置低寄存器),还有,就是最重要的,设置上拉,还需设置GPIOx_ODR(数据输出寄存器)。

3、按键检测

STM32查询式按键输入[直接用寄存器]_第5张图片
一个IO口需要输出高电平还是低电平,我们通过ODR来控制,IDR正好相反,这个寄存器就是用来检测该IO口是高电平还是低电平,我们具体来看使用方法:

  • 现在假设我们用按键KEY2(PE2),KEY2是上拉模式,不按下时,PE2引脚是高电平,GPIOE_IDR第2位值读出为1,按键按下时,接地,PE2引脚是低电平,GPIOE_IDR第2位值读出为0。

这样分析,我们便有了按键检测的依据,可以如下定义KEY2

#define	KEY2	((GPIOE_IDR&(1<<2))?1:0)	//KEY2->PE2
// (?:)这是一个三目运算符
// KEY2   ((GPIOE_IDR&(1<<2))?1:0)
//如果GPIOE_IDR&(1<<2)成立,那KEY2值为1,不成立则为0

四个按键都如此定义:

//---------------------按键配置---------------------------
#define WK_UP	((GPIOA_IDR&(1<<0))?1:0)	//WK_UP->PA0
#define KEY0	((GPIOE_IDR&(1<<4))?1:0)	//KEY0->PE4
#define KEY1	((GPIOE_IDR&(1<<3))?1:0)	//KEY1->PE3
#define	KEY2	((GPIOE_IDR&(1<<2))?1:0)	//KEY2->PE2

同样,所有IO口都可以检测,和上边方式一样

//--------------------------------------------------------
#define LED0	((GPIOB_IDR&(1<<5))?1:0)	//判断LED0状态
#define LED1	((GPIOE_IDR&(1<<5))?1:0)	//判断LED1状态
#define	BEEP	((GPIOB_IDR&(1<<8))?1:0)	//判断蜂鸣器状态

4、根据按键做动作

我们上边定义了按键,就可以根据返回的值是0还是1来判断按键有没有按下,这里强调一下,WK_UP是设置下拉,所以没按下时是0,按下了是1。

unsigned int KEY_SCANF()
{
	static unsigned int key_up=1;		
	if(key_up&&((KEY2==0)||(KEY1==0)||(KEY0==0)||(WK_UP==1)))
	{
		SysTick_ms(10);				//消抖
		key_up=0;									
		if(KEY2==0)return 1;			//控制LED0
		else if(KEY1==0)return 2;		//控制LED1
		else if(KEY0==0)return 3;		//控制蜂鸣器
		else if(WK_UP==1)return 4;		//LED反转
	}
	else if((KEY2==1)&&(KEY1==1)&&(KEY0==1)&&(WK_UP==0))
					key_up=1;
					return 0;
}
/* static是静态变量,C语言中学到自定义函数,调用完后,自定义函数里定义的
  变量就会释放存储空间,而静态变量就可以让变量持久存储
*/

这里有个消抖,要重视一下,对于我们这种按键,在按下的时候,电信号会有一个抖动,如下图:
STM32查询式按键输入[直接用寄存器]_第6张图片
消抖有两种方式,硬件消抖和软件消抖,我们用的就是软件消抖,利用延时的方法,去除抖动。
根据返回值,我们就可以做相应的动作:

int main(void)
{															
	unsigned int key;						//定义一个变量接收返回值
	System_clock(9);						//打开HSE高速时钟
	LED();									//LED灯初始化
	KEY();									//按键初始化
	GPIOB_ODR&=~(1<<5);						//打开LED0
	while(1)
	{
		key=KEY_SCANF();					//获取键值
		if(key)
		{
			switch(key)
			{
				case 1:
							PB5=~(PB5);		//LED灯取反,若亮就灭,灭就亮
					break;
				case 2:
							PE5=~(PE5);		//LED灯取反,若亮就灭,灭就亮
					break;
				case 3:
							PB8=~(PB8);		//蜂鸣器取反
					break;
				case 4:
							PB5=~(PB5);
							PE5=~(PE5);
					break;
			}
		}
		SysTick_ms(10);
	}
}

三、总程序

//--------------APB2使能时钟寄存器------------------------
#define RCC_APB2ENR		*((unsigned volatile int*)0x40021018)
//----------------GPIOA配置寄存器-------------------------
#define GPIOA_CRL		*((unsigned volatile int*)0x40010800)
#define GPIOA_IDR		*((unsigned volatile int*)0x40010808)
//----------------GPIOB配置寄存器-------------------------
#define GPIOB_CRL		*((unsigned volatile int*)0x40010C00)
#define GPIOB_CRH		*((unsigned volatile int*)0x40010C04)
#define	GPIOB_ODR		*((unsigned volatile int*)0x40010C0C)
//----------------GPIOE配置寄存器 ------------------------
#define GPIOE_CRL		*((unsigned volatile int*)0x40011800)
#define	GPIOE_ODR		*((unsigned volatile int*)0x4001180C)
#define	GPIOE_IDR		*((unsigned volatile int*)0x40011808)
//--------------------位带定义--------------------------------
#define PB5				*((unsigned volatile int*)0x42218194)
#define PE5				*((unsigned volatile int*)0x42230194)
#define PB8				*((unsigned volatile int*)0x422181A0)
//------------------RCC时钟寄存器-------------------------
#define RCC_CR			*((unsigned volatile int*)0x40021000)
#define RCC_CFGR		*((unsigned volatile int*)0x40021004)
//--------------FLASH闪存存储器接口-----------------------
#define FLASH_ACR		*((unsigned volatile int*)0x40022000)
//---------------------按键配置---------------------------
#define WK_UP	((GPIOA_IDR&(1<<0))?1:0)			//WK_UP->PA0
#define KEY0	((GPIOE_IDR&(1<<4))?1:0)			//KEY0->PE4
#define KEY1	((GPIOE_IDR&(1<<3))?1:0)			//KEY1->PE3
#define	KEY2	((GPIOE_IDR&(1<<2))?1:0)			//KEY2->PE2
//-----------------SysTick寄存器地址----------------------
#define SysTick_Base	0xE000E010
#define SysTick	((SysTick_Typedef*)SysTick_Base)
//-----------------SysTick寄存器定义----------------------
typedef struct
{
	volatile unsigned long CTRL;			//控制和状态寄存器
	volatile unsigned long RELOAD;			//重装载寄存器
	volatile unsigned long VAL;				//当前值寄存器
	volatile unsigned long CALIB;			//校准寄存器
}SysTick_Typedef;
//------------------系统时钟配置---------------------------
void System_clock(unsigned char PLL)
{
	unsigned int Clock_OK;
	RCC_CR|=1<<16;					//开启HSE高速外部时钟
	while(!(RCC_CR&(1<<17)));		//等待HSE开启成功
	RCC_CFGR|=4<<8;					//0x00000400 AHB不分频;APB2不分频;APB1二分频
	FLASH_ACR|=0x2;					//FLASH缓冲
	RCC_CFGR|=1<<16;				//HSE输出作为PLL输入时钟
	PLL=PLL-2;						//选择PLL倍频2--9
	RCC_CFGR|=PLL<<18;				//PLL九倍频输出
	RCC_CR|=1<<24;					//PLL使能
	while(!(RCC_CR&(1<<25)));		//等待PLL使能成功
	RCC_CFGR|=2<<0;					//选择PLL为系统时钟
	do								//等待系统时钟设置成功
	{
		Clock_OK=RCC_CFGR&0x0c;
	}
	while(Clock_OK!=0x08);
}
//设置完成后系统时钟:SYSCLK=72MHZ;AHB:HCLK=72MHZ;APB2:PCLK=72MHZ;APB1:PCLK1=36MHZ
//----------------------滴答定时器---------------------------
void SysTick_ms(unsigned int time)
{
	unsigned long num;
	SysTick->VAL=0;							//计数器清零
	SysTick->RELOAD=9000*time;				//重装载计数值
	SysTick->CTRL|=1<<0;					//定时器使能,打开定时器
	do
	{
		num=SysTick->CTRL;
	}
	while((num&0x01)&&!(num&(1<<16)));		//等待计数器到0
	SysTick->CTRL&=~(1<<0);					//关闭计数器
	SysTick->VAL=0;							//计数器清零
}
//-----------------------按键检测---------------------------
unsigned int KEY_SCANF()
{
	static unsigned int key_up=1;
	if(key_up&&((KEY2==0)||(KEY1==0)||(KEY0==0)||(WK_UP==1)))
	{
		SysTick_ms(10);						//消抖
		key_up=0;										
		if(KEY2==0)return 1;				//控制LED0
		else if(KEY1==0)return 2;			//控制LED1
		else if(KEY0==0)return 3;			//控制蜂鸣器
		else if(WK_UP==1)return 4;			//LED反转
	}
	else if((KEY2==1)&&(KEY1==1)&&(KEY0==1)&&(WK_UP==0))
					key_up=1;
					return 0;
}
//-----------------------按键初始化---------------------------
void KEY(void)
{
	RCC_APB2ENR|=1<<2;					//GPIOA时钟
	RCC_APB2ENR|=1<<6;					//GPIOE时钟
	
	GPIOA_CRL&=0xFFFFFFF0;				//引脚初始化
	GPIOA_CRL|=0x00000008;				//PA0下拉
	
	GPIOE_CRL&=0xFFF000FF;				//引脚初始化
	GPIOE_CRL|=0x00088800; 				//PE2、PE3、PE4上拉	   
	GPIOE_ODR|=7<<2;					//PE2上拉
}
//------------------------LED初始化---------------------------
void LED(void)
{
	RCC_APB2ENR|=1<<3;					//APB2-GPIOB外设时钟使能	
	RCC_APB2ENR|=1<<6;					//APB2-GPIOE外设时钟使能
	
	GPIOB_CRL&=0xFF0FFFFF;				//设置位清零	
	GPIOB_CRL|=0x00200000;				//PB5推挽输出
	GPIOB_ODR|=1<<5;					//设置初始灯为灭
	
	GPIOE_CRL&=0xFF0FFFFF;				//设置位清零
	GPIOE_CRL|=0x00200000;  			//PE5推挽输出
	GPIOE_ODR|=1<<5;					//设置初始灯为灭	
	
	GPIOB_CRH&=0xFFFFFFF0;				//引脚初始化
	GPIOB_CRH|=0x00000002;				//PB8推挽
	GPIOE_ODR&=~(1<<8);					//蜂鸣器不工作
}
//-------------------------主函数-----------------------------
int main(void)
{															
	unsigned int key;						//定义一个变量接收返回值
	System_clock(9);						//打开HSE高速时钟
	LED();									//LED灯初始化
	KEY();									//按键初始化
	GPIOB_ODR&=~(1<<5);						//打开LED0
	while(1)
	{
		key=KEY_SCANF();					//获取键值
		if(key)
		{
			switch(key)
			{
				case 1:
							PB5=~(PB5);
					break;
				case 2:
							PE5=~(PE5);
					break;
				case 3:
							PB8=~(PB8);
					break;
				case 4:
							PB5=~(PB5);
							PE5=~(PE5);
					break;
			}
		}
		SysTick_ms(10);
	}
}

建议大家写程序的时候,养成一个好习惯,多用自定义函数,自定义函数就是把一个一个功能模块化了,在主函数只需要调用一下,代码看起来简洁,不杂乱,后期调试的时候也不影响别的模块,如果代码全写在主函数,在后期维护时,会带来很大的不便。

你可能感兴趣的:(stm32)