当前位置:主页 > 查看内容

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

发布时间:2021-07-14 00:00| 位朋友查看

简介:当我们按下一个按键LED灯做出反转再按另一个蜂鸣器随之响起如何做到这些这一章就带你领略——按键输入。 对于按键输入有两种方式 1、查询式不断检测GPIO口电平变化 2、中断式触发中断进入中断服务程序 这一章先讲查询式学过中断之后再讲中断式。 基本思路如……

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

一、电路连接

在这里插入图片描述
在这里插入图片描述

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

二、按键配置

1、时钟的开启

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

2、上拉下拉配置

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

3、按键检测

在这里插入图片描述
一个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语言中学到自定义函数,调用完后,自定义函数里定义的
  变量就会释放存储空间,而静态变量就可以让变量持久存储
*/

这里有个消抖,要重视一下,对于我们这种按键,在按下的时候,电信号会有一个抖动,如下图:
在这里插入图片描述
消抖有两种方式,硬件消抖和软件消抖,我们用的就是软件消抖,利用延时的方法,去除抖动。
根据返回值,我们就可以做相应的动作:

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);
	}
}

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

;原文链接:https://blog.csdn.net/gelad_w/article/details/115680005
本站部分内容转载于网络,版权归原作者所有,转载之目的在于传播更多优秀技术内容,如有侵权请联系QQ/微信:153890879删除,谢谢!

推荐图文


随机推荐