一场大雨的到来,给最近的连天酷暑降了降温。这么凉爽的好天气,怎能不用来继续搞搞32?开个玩笑,言归正传。
今天我们来讲一讲STM32 GPIO口的输入。在第六讲(大Z带你重玩STM32系列(六)------使用库函数法点亮LED彩灯)中,我们了解了GPIO口的输出,并点亮了一个彩色LED发光二极管。那么对于STM32的GPIO口来说,它的输入输出功能的使用和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_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口的初始化,在第六讲(大Z带你重玩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的王道。
“温故而知新,可以为师矣”。
大Z带你重玩STM32系列(十)------STM32的延时函数