论坛» 高校专区» 坤创E-Geek/天科大新电社

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

高工
2017-07-27 17:42 1楼

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

今天我们来讲一讲STM32 GPIO口的输入。在第六讲(大Z带你重玩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端口上。而在第六讲(大Z带你重玩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_PINGPIO_Pin_0

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

// R-红色

#define LED1_GPIO_PORTGPIOB/* GPIO端口 */

#define LED1_GPIO_CLKRCC_APB2Periph_GPIOB/* GPIO端口时钟 */

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

// G-绿色

#define LED2_GPIO_PORTGPIOB/* GPIO端口 */

#define LED2_GPIO_CLKRCC_APB2Periph_GPIOB/* GPIO端口时钟 */

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

// B-蓝色

#define LED3_GPIO_PORTGPIOB/* GPIO端口 */

#define LED3_GPIO_CLKRCC_APB2Periph_GPIOB/* GPIO端口时钟 */

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


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

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

/* 按键按下标置宏

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

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

*/

#define KEY_ON1

#define KEY_OFF0

/*

* 函数名: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);

returnKEY_ON;

}

else

return KEY_OFF;

}

在这里我们定义了一个Key_Scan函数用于扫描按键状态。GPIO引脚的输入电平可通过IDRSTM32GPIO_ReadInputDataBit来获取位状态,该函数输入GPIO端口及引脚号,函数返回该引脚的电平状态,高电平返回1,低电平返回0Key_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的王道。

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


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




专家
2017-07-28 08:41 2楼
不错的硬件消抖电路,学习了。
菜鸟
2017-07-28 18:36 3楼
硬件消抖比软件消抖好用多了
菜鸟
2017-07-28 18:52 4楼
帖子好详细,这么多按键,大开眼界。
高工
2017-08-04 14:37 5楼

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

菜鸟
2017-08-29 19:41 6楼

一眨眼两年了

院士
2017-08-30 17:30 7楼

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

菜鸟
2017-09-09 18:20 8楼

好久没更新了?

菜鸟
2019-05-08 19:34 9楼

很喜欢的帖子,希望能持续更新,学到不少东西!

共9条 1/1 1 跳转至

回复

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