电子产品世界 » 论坛首页 » 高校社团 » 坤创E-Geek/天科大新电社 » 大白带你重玩STM32系列(九) ------按键检测与GPIO口的输入


共8条 1/1 1 跳转至

大白带你重玩STM32系列(九) ------按键检测与GPIO口的输入

助工
2017-07-27 17:42:39    评分

一场大雨的到来,给最近的连天酷暑降了降温。这么凉爽的好天气,怎能不用来继续搞搞32?开个玩笑,言归正传。

今天我们来讲一讲STM32 GPIO口的输入。在第六讲(大白带你重玩STM32系列(六)------使用库函数法点亮LED彩灯)中,我们了解了GPIO口的输出,并点亮了一个彩色LED发光二极管。那么对于STM32GPIO口来说,它的输入输出功能的使用和51单片机相比是有些差别的。51单片机的IO口,不需要经过配置,便可直接使用它的输入输出功能。而STM32的输入和输出是需要配置的,并且它有多达8种不同的输入输出模式,他们分别是:输入浮空,输入上拉,输入下拉,模拟输入;开漏输出,推挽输出,推挽复用输出,开漏复用输出这8种模式。何时使用哪种模式,要根据具体的情况而定。

在点亮LED发光二极管的时候,我们采用的是推挽输出,来增加GPIO口输出强度。那么在这一节中,对于按键这种输入设备,我们则应该采用GPIO口的输入模式,因为,在按键被按下时,与按键相关的GPIO口的状态会根据按键所接的电路结构发生相应的变化,STM32要想捕获到这种变化,就要开放GPIO口,让GPIO口能够把这个信号传递到芯片内部去。因此,需要将GPIO口配置成输入模式。这里我们将其配置成浮空输入。这样更容易检测到外部信号产生的变化。

下面我们先来了解一下按键的特点。

机械按键是目前见得较多的按键。而我们开发板上使用的是下图中第一行第一个,这种规格的轻触机械按键。如图。

这种按键的机械触点在断开、闭合时,由于触点的物理上存在的弹性作用,按键开关不会马上稳定接通或一下子断开,使用按键时会产生图中的带波纹信号,需要用软件消抖处理滤波,见下图。一般来讲是通过程序上延时10ms的方式进行消抖,而这种方法是不方便进行按键的输入检测的,同时,因为需要原地延时等待10ms,因此在一些快速响应的场合,还是略显低效。

因此我们推荐硬件电路进行按键的消抖。例如我使用的开发板,它的按键电路部分就带有硬件消抖的功能,现将其电路见下图,它利用电容充放电的延时,消除了波纹,从而简化软件的处理,软件只需要直接检测引脚的电平即可。这样一来,就不用在程序里再写10ms的消抖延时了。

从上面的原理图可知,按键在没有被按下的时候, GPIO 引脚的输入状态为低电平(按键所在的电路不通,引脚接地),当按键按下时, GPIO 引脚的输入状态为高电平(按键所在的电路导通,引脚接到电源)。只要我们检测引脚的输入电平,即可判断按键是否被按下。GPIO口的工作模式选择输入浮空就OK!

现在,我们就从程序方面来分析一下,应该怎样使用按键。

程序将要完成的要求如下:使用一个按键,来改变LED彩灯的色彩。每按一次,改变一种颜色。单片机上电复位后,LED灯发红光,当按键第一次按下,由红变绿;当按键第二次按下,由绿变蓝;当按键第三次按下,由蓝再变为红色。以此往复。

最终实现之后的效果,请摸下方视频链接观看:

http://v.youku.com/v_show/id_XMjkyNDYwNTc4NA==.html?spm=a2h3j.8428770.3416059.1

通过以上电路原理图我们可以看出Key1按键接在STM32的PA0端口上。而在第六讲(大白带你重玩STM32系列(六)------使用库函数法点亮LED彩灯)中我们了解了LED彩灯的使用方法。并知道LED彩灯的红绿蓝三极分别接在PB5、PB0、PB1三个GPIO口上。了解了硬件电路之后,我们就开始构造程序框架了。

程序怎么写,在我们的脑海中一定要形成一个清晰的大概流程之后,再开始动手。切忌边写边想,这样会多走很多弯路。对于本节中要完成的要求,我们捋出来编程要点如下,还是三大步骤:

(1)使能按键Key1和LED灯对应的 GPIO 端口时钟;
(2)初始化按键Key1和LED灯对应的 GPIO 目标引脚的工作模式(按键Key1为输入浮空,LED灯为推挽输出,50MHz)
(3)编写简单测试程序,检测按键的状态,实现按键控制 LED 灯。

照例,还是分别对三部分的主要代码进行解释说明。

在程序的最开始还是宏定义相关的代码。现将代码贴出并简要说明:

/* 定义KEY连接的GPIO端口 */

#define    KEY1_GPIO_CLK     RCC_APB2Periph_GPIOA

#define    KEY1_GPIO_PORT    GPIOA

#define    KEY1_GPIO_PIN GPIO_Pin_0

/* 定义LED连接的GPIO端口 */

// R-红色

#define LED1_GPIO_PORT GPIOB /* GPIO端口 */

#define LED1_GPIO_CLK RCC_APB2Periph_GPIOB /* GPIO端口时钟 */

#define LED1_GPIO_PIN GPIO_Pin_5 /* 连接到SCL时钟线的GPIO */

// G-绿色

#define LED2_GPIO_PORT GPIOB /* GPIO端口 */

#define LED2_GPIO_CLK RCC_APB2Periph_GPIOB /* GPIO端口时钟 */

#define LED2_GPIO_PIN GPIO_Pin_0 /* 连接到SCL时钟线的GPIO */

// B-蓝色

#define LED3_GPIO_PORT GPIOB /* GPIO端口 */

#define LED3_GPIO_CLK RCC_APB2Periph_GPIOB /* GPIO端口时钟 */

#define LED3_GPIO_PIN GPIO_Pin_1 /* 连接到SCL时钟线的GPIO */

以上代码根据按键的硬件连接, 把检测按键输入LED彩灯 GPIO 端口、 GPIO 引脚号以及GPIO 端口时钟使用宏定义封装起来了。在之后的程序中,我们可以用宏定义中我们定义的特殊名称来替代这些端口和引脚号。更直观更方便同时也增强了程序的可读性。下面就是相关的代码段的编写:

(1)使能按键Key1和LED灯对应的 GPIO 端口时钟。

/*开启按键端口的时钟*/

RCC_APB2PeriphClockCmd(KEY1_GPIO_CLK,ENABLE);

/*开启LED相关的GPIO外设时钟*/

RCC_APB2PeriphClockCmd( LED1_GPIO_CLK | LED2_GPIO_CLK | LED3_GPIO_CLK, ENABLE);

以上两行代码,分别打开了Key1和LED灯对应的 GPIO 端口时钟。在进行GPIO口等外设的使用时,必须首先打开该外设所对应时钟线上的时钟信号才行。
(2)初始化按键Key1和LED灯对应的 GPIO 目标引脚的工作模式(按键Key1为输入浮空,LED灯为推挽输出,50MHz)

按键GPIO初始化函数代码如下:

void Key_GPIO_Config(void)

{

GPIO_InitTypeDef GPIO_InitStructure;

/*开启按键端口的时钟*/

RCC_APB2PeriphClockCmd(KEY1_GPIO_CLK,ENABLE);

//选择按键的引脚

GPIO_InitStructure.GPIO_Pin = KEY1_GPIO_PIN;

// 设置按键的引脚为浮空输入

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;

//使用结构体初始化按键

GPIO_Init(KEY1_GPIO_PORT, &GPIO_InitStructure);

}

函数执行流程如下:
(1) 使用 GPIO_InitTypeDef 定义 GPIO 初始化结构体变量,以便下面用于存储 GPIO 配置。
(2) 调用库函数 RCC_APB2PeriphClockCmd 来使能按键的 GPIO 端口时钟,调用时我们使用“|”操作同时配置两个按键的时钟。
(3) GPIO 初始化结构体赋值,把引脚初始化成浮空输入模式,其中的 GPIO_Pin 使用宏“KEY1_GPIO_PIN”来赋值,使函数的实现方便移植。 由于引脚的默认电平受按键电路影响,所以设置成浮空输入。
(4) 使用以上初始化结构体的配置,调用 GPIO_Init 函数向寄存器写入参数,完成 GPIO 的初始化,这里的 GPIO 端口使用“KEYx_GPIO_PORT”宏来赋值,也是为了程序移植方便。
(5) 使用同样的初始化结构体,只修改控制的引脚和端口,初始化其它按键检测时使用的GPIO 引脚。

LED彩灯对应的GPIO口的初始化,在第六讲(大白带你重玩STM32系列(六)------使用库函数法点亮LED彩灯)中已经介绍过,在此不再讲解。有需要的小伙伴请摸上方蓝字部分链接,移步第六讲查看。


(3)编写简单测试程序,检测按键的状态,实现按键控制 LED 灯。

首先,要在程序中进行的是按键状态的检测,也就是说我们需要写程序分析,什么时候按键被按下了。我们可以通过检测对应引脚的电平来判断按键状态,看是否有按键被按下。代码如下:

/* 按键按下标置宏

*  按键按下为高电平,设置 KEY_ON=1, KEY_OFF=0

*  若按键按下为低电平,把宏设置成KEY_ON=0 ,KEY_OFF=1 即可

*/

#define KEY_ON 1

#define KEY_OFF 0

/*

* 函数名:Key_Scan

* 描述  :检测是否有按键按下

* 输入  :GPIOx:x 可以是 A,B,C,D或者 E

* GPIO_Pin:待读取的端口位

* 输出  :KEY_OFF(没按下按键)、KEY_ON(按下按键)

*/

uint8_t Key_Scan(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin)

{

/*检测是否有按键按下 */

if(GPIO_ReadInputDataBit(GPIOx,GPIO_Pin) == KEY_ON )

{

/*等待按键释放 */

while(GPIO_ReadInputDataBit(GPIOx,GPIO_Pin) == KEY_ON);

return KEY_ON;

}

else

return KEY_OFF;

}

在这里我们定义了一个 Key_Scan 函数用于扫描按键状态。 GPIO 引脚的输入电平可通过 IDR STM32 GPIO_ReadInputDataBit 来获取位状态,该函数输入 GPIO 端口及引脚号,函数返回该引脚的电平状态,高电平返回 1,低电平返回 0 Key_Scan 函数中以 GPIO_ReadInputDataBit 的返回值与自定义的宏“KEY_ON”对比,若检测到按键按下,则使用 while 循环持续检测按键状态,直到按键释放,按键释放后 Key_Scan 函数返回一个“KEY_ON”值;若没有检测到按键按下,则函数直接返回“KEY_OFF”。若按键的硬件没有做消抖处理,需要在这个 Key_Scan 函数中做软件滤波,防止波纹抖动引起误触发。如果对固件库中的函数不熟的同学,建议可以参照STM32f10固件库使用手册中文版》进行学习。

再接下来,我们就开始写主函数,在主函数中实现我们想要的效果了。首先我们在主函数main()中初始化 LED 灯及按键后,在 while 函数里不断调用 Key_Scan 函数,并判断其返回值,若返回值表示按键按下,则改变 LED 灯的状态。

现将整个程序的代码贴出如下:


#include "stm32f10x.h"


/* 定义KEY连接的GPIO端口 */

#define    KEY1_GPIO_CLK     RCC_APB2Periph_GPIOA
#define    KEY1_GPIO_PORT    GPIOA			   
#define    KEY1_GPIO_PIN		 GPIO_Pin_0


 /* 按键按下标置宏
	*  按键按下为高电平,设置 KEY_ON=1, KEY_OFF=0
	*  若按键按下为低电平,把宏设置成KEY_ON=0 ,KEY_OFF=1 即可
	*/
#define KEY_ON	1
#define KEY_OFF	0
/* 定义LED连接的GPIO端口 */
// R-红色
#define LED1_GPIO_PORT    	GPIOB			              /* GPIO端口 */
#define LED1_GPIO_CLK 	    RCC_APB2Periph_GPIOB		/* GPIO端口时钟 */
#define LED1_GPIO_PIN		GPIO_Pin_5			        /* 连接到SCL时钟线的GPIO */

// G-绿色
#define LED2_GPIO_PORT    	GPIOB			              /* GPIO端口 */
#define LED2_GPIO_CLK 	    RCC_APB2Periph_GPIOB		/* GPIO端口时钟 */
#define LED2_GPIO_PIN		GPIO_Pin_0			        /* 连接到SCL时钟线的GPIO */

// B-蓝色
#define LED3_GPIO_PORT    	GPIOB			              /* GPIO端口 */
#define LED3_GPIO_CLK 	    RCC_APB2Periph_GPIOB		/* GPIO端口时钟 */
#define LED3_GPIO_PIN		GPIO_Pin_1			        /* 连接到SCL时钟线的GPIO */
/**
  * @brief  配置按键用到的I/O口
  * @param  无
  * @retval 无
  */
void Key_GPIO_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	/*开启按键端口的时钟*/
	RCC_APB2PeriphClockCmd(KEY1_GPIO_CLK,ENABLE);
	
	//选择按键的引脚
	GPIO_InitStructure.GPIO_Pin = KEY1_GPIO_PIN; 
	// 设置按键的引脚为浮空输入
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; 
	//使用结构体初始化按键
	GPIO_Init(KEY1_GPIO_PORT, &GPIO_InitStructure);
}

 /*
 * 函数名:Key_Scan
 * 描述  :检测是否有按键按下
 * 输入  :GPIOx:x 可以是 A,B,C,D或者 E
 *		     GPIO_Pin:待读取的端口位 	
 * 输出  :KEY_OFF(没按下按键)、KEY_ON(按下按键)
 */
uint8_t Key_Scan(GPIO_TypeDef* GPIOx,uint16_t GPIO_Pin)
{			
	/*检测是否有按键按下 */
	if(GPIO_ReadInputDataBit(GPIOx,GPIO_Pin) == KEY_ON )  
	{	 
		/*等待按键释放 */
		while(GPIO_ReadInputDataBit(GPIOx,GPIO_Pin) == KEY_ON);   
		return 	KEY_ON;	 
	}
	else
		return KEY_OFF;
}

void LED_GPIO_Config(void)
{		
		/*定义一个GPIO_InitTypeDef类型的结构体*/
		GPIO_InitTypeDef GPIO_InitStructure;

		/*开启LED相关的GPIO外设时钟*/
		RCC_APB2PeriphClockCmd( LED1_GPIO_CLK | LED2_GPIO_CLK | LED3_GPIO_CLK, ENABLE);
		/*选择要控制的GPIO引脚*/
		GPIO_InitStructure.GPIO_Pin = LED1_GPIO_PIN;	

		/*设置引脚模式为通用推挽输出*/
		GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;   

		/*设置引脚速率为50MHz */   
		GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 

		/*调用库函数,初始化GPIO*/
		GPIO_Init(LED1_GPIO_PORT, &GPIO_InitStructure);	
		
		/*选择要控制的GPIO引脚*/
		GPIO_InitStructure.GPIO_Pin = LED2_GPIO_PIN;

		/*调用库函数,初始化GPIO*/
		GPIO_Init(LED2_GPIO_PORT, &GPIO_InitStructure);
		
		/*选择要控制的GPIO引脚*/
		GPIO_InitStructure.GPIO_Pin = LED3_GPIO_PIN;

		/*调用库函数,初始化GPIOF*/
		GPIO_Init(LED3_GPIO_PORT, &GPIO_InitStructure);

		/* 关闭所有led灯	*/
		GPIO_SetBits(LED1_GPIO_PORT, LED1_GPIO_PIN);
		
		/* 关闭所有led灯	*/
		GPIO_SetBits(LED2_GPIO_PORT, LED2_GPIO_PIN);	 
    
    /* 关闭所有led灯	*/
		GPIO_SetBits(LED3_GPIO_PORT, LED3_GPIO_PIN);
}

void Led_Turn_on_R(void)  
{  
    /* Turn On LED1 */  
    GPIO_ResetBits(LED1_GPIO_PORT, LED1_GPIO_PIN);  
}  
  
void Led_Turn_on_G(void)  
{  
    /* Turn On LED2 */  
    GPIO_ResetBits(LED2_GPIO_PORT, LED2_GPIO_PIN );  
}  
  
void Led_Turn_on_B(void)  
{  
    /* Turn On LED3 */  
    GPIO_ResetBits(LED3_GPIO_PORT, LED3_GPIO_PIN);  
}  
  
void Led_Turn_off_all(void)  
{  
    /* Turn Off All LEDs */  
    GPIO_SetBits(GPIOB , LED1_GPIO_PIN|LED2_GPIO_PIN|LED3_GPIO_PIN);  
}  

/**
  * @brief  主函数
  * @param  无
  * @retval 无
  */ 
int main(void)
{	
	u8 KEY_FLAG = 0; 
 
	/* LED端口初始化 */
	LED_GPIO_Config();
	

	/* 按键端口初始化 */
	Key_GPIO_Config();
	
	/* 轮询按键状态,若按键按下则反转LED */
	while(1)                            
	{	   
		if( Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON  )
		{
			/*改变按键Key1的标志位,用以控制LED灯的色彩改变*/
			KEY_FLAG ++;
			if (KEY_FLAG == 3)
				KEY_FLAG = 0;
		} 
		switch(KEY_FLAG) 
		{
			case 0 : Led_Turn_off_all(); Led_Turn_on_R();break ; //发红光
			case 1 : Led_Turn_off_all(); Led_Turn_on_G();break ; //发绿光
			case 2 : Led_Turn_off_all(); Led_Turn_on_B();break ; //发蓝光
		}

		
	}
}



好了,本节就到此为止。在这一讲当中,我们学会了使用GPIO口的输入模式,以及轻触按键的使用。希望大家下去勤总结勤练习,多写程序才是学好STM32的王道。

“温故而知新,可以为师矣”。


大白带你重玩STM32系列(十)------STM32的延时函数







关键词: STM32     按键     检测     输入     时钟     初始化     #de    

专家
2017-07-28 08:41:25    评分
2楼
不错的硬件消抖电路,学习了。

菜鸟
2017-07-28 18:36:28    评分
3楼
硬件消抖比软件消抖好用多了

菜鸟
2017-07-28 18:52:42    评分
4楼
帖子好详细,这么多按键,大开眼界。

工程师
2017-08-04 14:37:44    评分
5楼

继续啊   等着看各种接口的使用


菜鸟
2017-08-29 19:41:43    评分
6楼

一眨眼两年了


专家
2017-08-30 17:30:24    评分
7楼

我觉得软件消抖是必须要有的。


菜鸟
2017-09-09 18:20:32    评分
8楼

好久没更新了?


共8条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]