stm32f407按键检测库函数版

   日期:2020-12-27     浏览:104    评论:0    
核心提示:stm32f407按键检测库函数版  开始之前呢先祝大家圣诞节快乐,同时参加明天的研究生考试的同学们一战成"硕",接下来我就直奔主题了。  今天我要通过库函数操作stm32f407上的按键实现控制LED小灯以及蜂鸣器,实现的功能如下:KEY0键控制LED0的亮灭KEY1键控制LED1的亮灭KEY2键同时控制控制LED0和LED1的亮灭转换WK_UP键控制蜂鸣器  这篇文章同时会涉及到LED和蜂鸣器,相当于是对前两次的学习进行一个复习,可能在前两篇文章中没有考虑到的细节今天都会尽量考虑进去,希

  开始之前呢先祝大家圣诞节快乐,同时参加明天的研究生考试的同学们一战成"硕",接下来我就直奔主题了。
  今天我要通过库函数操作stm32f407上的按键实现控制LED小灯以及蜂鸣器,实现的功能如下:

  • KEY0键控制LED0的亮灭
  • KEY1键控制LED1的亮灭
  • KEY2键同时控制控制LED0和LED1的亮灭转换
  • WK_UP键控制蜂鸣器

  这篇文章同时会涉及到LED和蜂鸣器,相当于是对前两次的学习进行一个复习,可能在前两篇文章中没有考虑到的细节今天都会尽量考虑进去,希望这篇文章可以更好的帮助到大家对GPIO的理解,废话不多说,下面正式开始。

LED的初始化配置

  想要点亮LED,首先我们需要确定LED在stm32f407开发板上的硬件电路连接,如下图所示:

从图上可以看出,两个LED属于共阳极连接,也就是说,当GPIO口输出高电平时,LED熄灭,当GPIO口输出低电平时,LED点亮,那么只需要配置好GPIO的输出,即可实现LED的亮灭,所以,我们还得知道控制LED的GPIO口是哪一个,毕竟stm32f4有70个用于控制LED的GPIO口;

我们可以从从上图中看到,控制LED0的是GPIOF_9,控制LED1的是GPIOF_10,同时我们还可以看到GPIOF_8是用来控制蜂鸣器(BEEP)的。知道了LED的硬件连接后,接下来就可以开始通过库函数配置我们的GPIO了。
  通过库函数来配置GPIO,我们需要用到的库函数是GPIO_Init,对于这个函数,有如下的说明:

void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);

从GPIO_Init的函数原型可以看出,需要用到两个参数,这两个参数分别是:

  • GPIO_TypeDef* GPIOx:用来指明配置的GPIO是7组GPIO中的哪一组,这里控制LED用到的是GPIOF,所以该参数只需填入宏定义GPIOF即可
  • GPIO_InitTypeDef* GPIO_InitStruct:从参数名字就可以知道这个参数是一个结构体的地址,所以在这里为了更好的说明,我们自定义一个结构体:
GPIO_InitTypeDef led_gpio;

该结构体的成员呢有以下几个:

  uint32_t GPIO_Pin;  //该参数对应的是某组GPIO中的某一个,毕竟一组GPIO有16个GPIO引脚
  					  //就如前面说到控制LED0的GPIO口是GPIOF_9;控制LED1的GPIO口是GPIOF_10;
  
  GPIOMode_TypeDef GPIO_Mode;  
  GPIOSpeed_TypeDef GPIO_Speed;
  GPIOOType_TypeDef GPIO_OType;  
  GPIOPuPd_TypeDef GPIO_PuPd;

GPIO_InitStruct的后面4个参数就是用于配置GPIO模式寄存器的,我在第一篇文章里说到,配置GPIO的模式需要用到4个寄存器,如果你没有看过我的第一篇文章的话,没有关系,我再来详细的说明一下:这里说到的寄存器分别是:

  • GPIOx_MODER:GPIO端口模式寄存器
  • GPIOx_OSPEEDR:GPIO端口输出速度寄存器
  • GPIOx_OTYPER:GPIO端口输出类型寄存器
  • GPIOx_PUPDR:GPIO端口上拉/下拉寄存器

分别对应着GPIO_InitStruct的后四个成员
下面我就来具体说说要控制LED,这4个GPIOF分别需要怎么配置:
>GPIOF_MODER
  GPIOF_MODER寄存器可选的配置分别有:输入模式(复位状态)通用输出模式复用功能模式模拟模式,这里很容易想到,我们要向控制LED0,需要通过GPIOF_9输出高电平或低电平,所以毫无疑问我们需要将GPIOF_MODER寄存器配置成通用输出模式,对于该寄存器的四种模式在库函数中有如下的说明:

 GPIO_Mode_IN   = 0x00, 
 GPIO_Mode_OUT  = 0x01, 
 GPIO_Mode_AF   = 0x02, 
 GPIO_Mode_AN   = 0x03  

在此我们只需选择GPIO_Mode_OUT即可,一句话代码如下:

 led_gpio.GPIO_Mode = GPIO_Mode_OUT;

>GPIOF_OSPEEDR
  GPIOF_OSPEEDR寄存器可选的配置分别有:2MHz25MHz50MHz100MHz,在此我们选择100MHz的端口输出速度(对于速度的选择我也不太明白会有啥影响,或者说选择的标准是啥,希望明白的大佬可以在评论区里解答下,非常感谢),对于这4中选择在库函数中有如下定义:

 #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 

所以一句话代码如下:

 led_gpio.GPIO_Speed = GPIO_Speed_100MHz;

>GPIOx_OTYPER
  GPIOF_OSPEEDR寄存器可选的配置分别有:推挽输出开漏输出,在此我们选择推挽输出模式(同样不明白这两种模式的区别以及选择标准,希望有大佬可以在评论区解答下,感谢),对于这两种输出模式在库函数中有如下的定义:

typedef enum
{  
  GPIO_OType_PP = 0x00,	//推挽输出
  GPIO_OType_OD = 0x01	//开漏输出
}GPIOOType_TypeDef;

所以一句话代码如下:

 led_gpio.GPIO_OType = GPIO_OType_PP;

>GPIOx_PUPDR
  GPIOF_OSPEEDR寄存器可选的配置分别有:浮空上拉下拉,GPIOF_OSPEEDR的配置就比较自由,在此我选择上拉,以确保开发板上电时是熄灭的,对于这3种模式在库函数中有如下定义:

 GPIO_PuPd_NOPULL = 0x00,
 GPIO_PuPd_UP     = 0x01,
 GPIO_PuPd_DOWN   = 0x02

所以一句话代码如下

 led_gpio.GPIO_PuPd = GPIO_PuPd_UP;

到此,LED的GPIO配置就完成了,注意LED0和LED1配置的GPIO_pin是有区别的,同时不要忘了在调用GPIO_Init之前,一定要先使能GPIOF的外设时钟,这里需要用到的函数为:

 void RCC_AHB1PeriphClockCmd(uint32_t RCC_AHB1Periph, FunctionalState NewState)

对于该函数的第1个参数用来指明初始化的是哪一个外设时钟,有如下定义(只截取了一段):

 #define RCC_AHB1Periph_GPIOA ((uint32_t)0x00000001)
 #define RCC_AHB1Periph_GPIOB ((uint32_t)0x00000002)
 #define RCC_AHB1Periph_GPIOC ((uint32_t)0x00000004)
 #define RCC_AHB1Periph_GPIOD ((uint32_t)0x00000008)
 #define RCC_AHB1Periph_GPIOE ((uint32_t)0x00000010)
 #define RCC_AHB1Periph_GPIOF ((uint32_t)0x00000020)
 #define RCC_AHB1Periph_GPIOG ((uint32_t)0x00000040)

在此我们选择RCC_AHB1Periph_GPIOF即可,对于第2个参数是用于说明是否要初始化的,具体的定义如下:

 #define IS_FUNCTIONAL_STATE(STATE) (((STATE) == DISABLE) || ((STATE) == ENABLE))

很明显我们要选择ENABLE
那么到这里第一个阶段的工作就完成了,我们可以将以上的代码封装成一个LED初始化的.c文件,只需要在main函数中调用即可,led_init.c代码如下:

void led_init(void){ 
	GPIO_InitTypeDef led_gpio;	//定义的GPIO模式初始化结构体
	
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF,ENABLE);
	
	led_gpio.GPIO_Pin = GPIO_Pin_9||GPIO_Pin_10;//采用或运算可同时对LED1和LED0进行初始化
	led_gpio.GPIO_Mode = GPIO_Mode_OUT;
	led_gpio.GPIO_Speed = GPIO_Speed_100MHz;
	led_gpio.GPIO_OType = GPIO_OType_PP;
	led_gpio.GPIO_PuPd = GPIO_PuPd_UP;
	GPIO_Init(GPIOF,&led_gpio);
}

BEEP(蜂鸣器)的初始化配置

  在了解了GPIO的配置过程后,后续的共组就容易的多了。同样的,想要配置好BEEP,首先我们需要先知道BEEP的硬件是怎样连接的,从而知道应该对哪一个GPIO口进行配置,从前面的LED的硬件连接图上可以找的BEEP连接的是GPIOF_8,那么下面我直接上代码,详细的内容见代码的注释,毕竟今天的重点是按键:

void beep_init(){ 
	GPIO_InitTypeDef beep_gpio;	//定义的GPIO模式初始化结构体
	
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF,ENABLE);
	
	beep_gpio.GPIO_Mode = GPIO_Mode_OUT;	//配置GPIO模式为通用输出模式
	beep_gpio.GPIO_OType = GPIO_OType_PP;	//配置GPIO输出模式为推挽输出
	beep_gpio.GPIO_Pin = GPIO_Pin_8;		//选择配置8号引脚
	beep_gpio.GPIO_PuPd = GPIO_PuPd_UP;		//配置GPIO端口为上拉模式
	beep_gpio.GPIO_Speed = GPIO_Speed_100MHz;	//配置GPIO输出速度为100MHz
	GPIO_Init(GPIOF,&beep_gpio);	//调用GPIO初始化函数
}

KEY的初始化配置与扫描

KEY的初始化配置

  首先逃不掉的还是硬件连接图:

  从图中可以看出,对于按键KEY0,KEY1,KEY2,当它们连接到的GPIO端口为上拉模式时,如按键按下,此时GPIO口读到低电平,若按键没有按下,则GPIO口读到高电平,二对于KEY_UP来说就正好相反,当它对应的GPIO口为下拉模式时,没有按键按下,GPIO口读到第电平,按键按下则读到高点配,也就意味着我们对KEY_num和KEY_UP的配置是不一样的,同时,我们现在需要的不再是GPIO的通用输出模式,而是输入模式了,而且此处我们不需要再配置GPIO的输出模式(即推挽输出或者开漏输出),有了这样的认识后,我么们再来确定需要配置哪写GPIO口吧:


由上图可以知道,对于KEY0,KEY1,KEY2需要配置的GPIO分别是GPIOE_4GPIOE_3GPIOE_2,而KEY_UP配置的GPIO是GPIOA_0。在此,模仿LED与BEEP的配置过程可以有一下代码:

void key_init(void){ 
	GPIO_InitTypeDef key_gpio;	//定义GPIO初始化的结构体
	
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE|RCC_AHB1Periph_GPIOA,ENABLE);
	
	key_gpio.GPIO_Pin = GPIO_Pin_4|GPIO_Pin_3|GPIO_Pin_2;//采用或运算可同时对KEY0,KEY1和KEY2进行初始化
	key_gpio.GPIO_Mode = GPIO_Mode_IN;		//配置GPIO模式为输入模式
	key_gpio.GPIO_Speed = GPIO_Speed_100MHz;//配置GPIO输出速度为100MHz
	key_gpio.GPIO_PuPd = GPIO_PuPd_UP;		//配置GPIO端口为上拉模式
	GPIO_Init(GPIOE,&key_gpio);				//调用GPIO初始化函数
	
	key_gpio.GPIO_Pin = GPIO_Pin_0;			//对KEY_UP进行初始化
	key_gpio.GPIO_PuPd = GPIO_PuPd_DOWN;	//配置GPIO端口为下拉模式
	GPIO_Init(GPIOA,&key_gpio);				//调用GPIO初始化函数 
}

KEY的扫描

  想要按下不同的按键实现不同的功能,就需要知道按下的键是哪一个,然后通过按下的按键控制相应的设备,就需要循环扫描按键以确定键值,下面使用伪代码说明一下按键扫描的过程:

//key_scan.c
void key_scan{ 
	if(有按键按下){ 
 		延时10ms消抖;
 		if(确实有按键按下)	
 			return 键值
 		else return 无效值;
 	}
}
//main.c
int main(){ 
	while(1){ 
		int key = key_scan();
		switch(key){ 
			case 1 : 点亮LED;break;
			case 2 : 熄灭LED;break;
			...
		}
		延时10ms
	}
}	

  这样便实现了通过按键控制不同设备了,但是存在一个问题,当我按下某一个按键不放时,key_scan函数永远检测到有按键按下并且返回该键键值,再main函数中就会一直点亮或熄灭LED,如何避免呢,在此可以很巧妙的运用static定义一个静态的变量作为按键按下与否的标志位(简单来说就是保存一下上一次按键的结果),直接上伪代码解释(只需再原来的按键扫描的基础上修改即可)

//key_scan.c
void key_scan{ 
	static KEY_STATE = 1;
	if(KEY_STATE = 1 && 有按键按下){ 
 		延时10ms消抖;
 		KEY_STATE = 0	//按键有效从而更新状态
 		if(确实有按键按下)	
 			return 键值
 		else if(没有按键按下)	KEY_STATE= 1;//重新更新状态 
 	}
 	return 0}

可能不好理解,可以试着多循环几次应该就会明朗许多,那么具体的按键扫描函数如下所示:

u8 key_scan(u8 mode){ 
	static u8 key_state = 1;
	if(mode) key_state = 1;
	if(key_state && (KEY0 == 0 || KEY1 == 0 || KEY2 == 0 || WKUP == 1)){ 
		delay_ms(10);
		key_state = 0;
		if(KEY0 == 0)	return 1;
		else if(KEY1 == 0)	return 2;
		else if(KEY2 == 0)	return 3;
		else if(WKUP == 1)	return 4;
	}else if(KEY0 == 1 && KEY1 == 1 && KEY2 == 1 && WKUP == 0)
		key_state = 1;
	return 0;	
}

在此需要说明的是:判断是否有按键按下,我们需要调用函数实现

GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4)

在以上的按键扫描函数中KEY0 == 0这样的表示即为GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4),是在头文件中进行了宏定义而已,具体如下:

//key.h
#define KEY0 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4)
#define KEY1 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3)
#define KEY2 GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_2)
#define WKUP GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)

分别对应判读KEY0,KEY1,KEY2以及KEY_UP是否按下。
至此所有的流程都已搞定,接下啦就是main函数的类容了:

int main(void){ 
	u8 key;
	delay_init(168);
	led_init();
	beep_init();
	key_init();
	LED0 = 0;
	LED1 = 0;
	while(1){ 
		key = key_scan(0);
		if(key){ 
			switch(key){ 
				case LED0_PRES:	LED0 = !LED0; break;
				case LED1_PRES:	LED1 = !LED1; break;
				case BEEP_PRES:	BEEP = !BEEP; break;
				case CHAG_PRES:	{ 
					LED0 = !LED0;
					LED1 = !LED1;
				}
				break;
			}
		}else delay_ms(10);
	}
}

同样这里需要注意的是对于LED和蜂鸣器的操作我采用了位操作的方式,位操作的相关内容可以参考我上一篇的文章,这里也是采用的宏定义方式,具体如下:

//led.h
#define LED0 PFout(9)
#define LED1 PFout(10)
//beep.h
#define BEEP PFout(8)

总结一下:经常会在CSDN上查找大佬的文章来学习,但没想过写一篇文章这么恼火,花了我两个半小时才搞定,太难了,不过一定要坚持呀!!!

 
打赏
 本文转载自:网络 
所有权利归属于原作者,如文章来源标示错误或侵犯了您的权利请联系微信13520258486
更多>最近资讯中心
更多>最新资讯中心
0相关评论

推荐图文
推荐资讯中心
点击排行
最新信息
新手指南
采购商服务
供应商服务
交易安全
关注我们
手机网站:
新浪微博:
微信关注:

13520258486

周一至周五 9:00-18:00
(其他时间联系在线客服)

24小时在线客服