STM32学习笔记(2)——按键输入实验
零、按键基本认识
1、防抖
按键机械触点断开、闭合的时候,由于触点的弹性作用,按键开关不会马上稳定接通或一下子断开,而是会产生一些波纹信号,这些波纹信号会干扰高低电平的判断。如下图所示,在按键按下的前后均有信号抖动:
为了解决这个问题,有一些电路自带消抖功能,利用电容充放电的延时,消除了干扰波纹,从而简化了软件的处理,软件只需检测引脚的高低电平即可。这叫硬件消抖。
然而,我们的实验板却没有硬件消抖功能,因此,当用户按下按键时,软件需要延时一会儿(一般为10ms左右),待引脚的输入电平稳定后再判断高低电平。这叫软件消抖。
大致的思路如下:
uint8_t KEY_Scan(void)
{
if(KEY按下)
{
delay_ms(10);//延时10-20ms,防抖。
if(KEY确实按下)
{
return KEY_Value;
}
return 无效值;
}
}
返回的结果只有两种:要么按键确实被按下了,要么按键确实没被按下。
2、支持连续按
支持连续按的意思是,用户一直按着按键,相应的外设就会一直工作,直到用户松手,相应的外设才不工作。简单来说就是用户按一次不松手算很多次按下。其实,上面的程序就实现了这个功能,思路跟上面是一样的。
我们重复写一遍。大致的思路如下:
uint8_t KEY_Scan(void)
{
if(KEY按下)
{
delay_ms(10);//延时,防抖。
if(KEY确实按下)
{
return KEY_Value;
}
return 无效值;
}
}
3、不支持连续按
不支持连续按的意思是,即使用户一直按着按键,但相应的外设不会一直工作,响一下就不响了。简单来说就是用户按一次不松手只能算一次按下。
大致的思路如下:
uint8_t KEY_Scan(void)
{
static uint8_t iskey = 0; //记录之前按键有无被按过
//初始化iskey为0,表明按键之前没被按过
if(!iskey && KEY按下) //如果按键之前没被按过且现在按键被按下了
{
delay_ms(10);//延时,防抖
iskey = 1; //标记按键已经被按下
if(KEY确实按下)
{
return KEY_VALUE;
}
}else if(KEY没有按下)
iskey = 0; //如果按键没被按下(即松开),则标记按键没被按过
return 没有按下
}
4、STM32F103精英上按键的电路图
电路图如下如所示:
从电路图我们可以看到,精英版为我们提供了两种按键,WK_UP接VCC电源,而KEY0和KEY1接地。因此,WK_UP连接的端口为下拉输入模式,KEY0和KEY1连接的端口为上拉输入模式。
当单片机读到的电平为低电平时,说明KEY0和KEY1被按下,而WK_UP没有被按下;
当单片机读到的电平为高电平时,说明KEY0和KEY1没有被按下,而WK_UP被按下。
最后,WK_UP接的是PA0端口,KEY0接的是PE4端口,KEY1接的是PE3端口。
一、按键实验初体验
1.支持连续按
本程序实现功能:按下按键,相应外设会工作,若按键不松手则外设一直工作。换言之,外设工作与否取决于按键是否被按下。
程序如下:
/* =====key.h=====*/
#ifndef __KEY_H
#define __KEY_H
#include "sys.h"
/* 检测按键电平情况的函数 */
#define KEY0 GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_4)
#define KEY1 GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_3)
#define KEY_UP GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0)
/* 由上到下分别是:上拉输入式按下、下拉输入式按下 */
#define IPU_ON 0
#define IPD_ON 1
/* 由上到下均是不同按键对应的编号:全部未按下为0、key0为1、key1为2、wk_up为3 */
#define ALL_KEY_UNPRS 0
#define KEY0_PRS 1
#define KEY1_PRS 2
#define KEY_UP_PRS 3
void KEY_Init(void);
u8 KEY_Scan(void);
#endif
/* =====key.c===== */
#include "stm32f10x.h"
#include "key.h"
#include "delay.h"
void KEY_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOE, ENABLE);
/* KEY0 & KEY1 -- Ground */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_4; /* PinE3 -- KEY1, PinE4 -- KEY0 */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; /* 上拉输入 */
GPIO_Init(GPIOE, &GPIO_InitStructure);
/* KEY_UP(WK_UP) -- VCC */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; /* PinA0 -- KEY_UP */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; /* 下拉输入 */
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
u8 KEY_Scan(void)
{
if(KEY0 == IPU_ON || KEY1 == IPU_ON || KEY_UP == IPD_ON)
{
delay_ms(10);
if(KEY0 == IPU_ON)
return KEY0_PRS;
else if(KEY1 == IPU_ON)
return KEY1_PRS;
else if(KEY_UP == IPD_ON)
return KEY_UP_PRS;
}
return ALL_KEY_UNPRS;
}
/* =====main.c===== */
#include "stm32f10x.h"
#include "sys.h"
#include "beep.h"
#include "key.h"
#include "led.h"
#include "delay.h"
int main()
{
u8 key = 0;
delay_init();
KEY_Init();
LED_Init();
Beep_Init();
while(1)
{
key = KEY_Scan();
switch(key)
{
case KEY0_PRS:
LED0_ON;
while(KEY_Scan() != ALL_KEY_UNPRS); /* 松手检测,这里的作用是:
/* 如果用户不松手,程序将会卡在这个地方,相应的外设也会持续工作 */
break;
case KEY1_PRS:
LED1_ON;
while(KEY_Scan() != ALL_KEY_UNPRS);
break;
case KEY_UP_PRS:
BEEP_ON;
while(KEY_Scan() != ALL_KEY_UNPRS);
break;
default:
LED0_OFF;
LED1_OFF;
BEEP_OFF;
break;
}
}
}
2.不支持连续按
本程序实现功能:按下按键,相应外设会工作,且经过300ms后不工作。这就是说若按键不松手,外设不会一直工作。
将以上部分程序修改如下:
/* =====key.c===== */
/* key_Scan函数修改如下,其他部分不变 */
u8 KEY_Scan(void)
{
static int isPrsBefore = 0;
if((isPrsBefore == 0) && (KEY0 == IPU_ON || KEY1 == IPU_ON || KEY_UP == IPD_ON))
{
delay_ms(10);
isPrsBefore = 1;
if(KEY0 == IPU_ON)
return KEY0_PRS;
else if(KEY1 == IPU_ON)
return KEY1_PRS;
else if(KEY_UP == IPD_ON)
return KEY_UP_PRS;
}else if (KEY0 == !IPU_ON && KEY1 == !IPU_ON && KEY_UP == !IPD_ON)
isPrsBefore = 0;
return ALL_KEY_UNPRS;
}
/* =====main.c===== */
#include "stm32f10x.h"
#include "sys.h"
#include "beep.h"
#include "key.h"
#include "led.h"
#include "delay.h"
int main()
{
u8 key = 0;
delay_init();
KEY_Init();
LED_Init();
Beep_Init();
while(1)
{
key = KEY_Scan();
switch(key)
{
case KEY0_PRS:
LED0_ON;
delay_ms(300);
break;
case KEY1_PRS:
LED1_ON;
delay_ms(300);
break;
case KEY_UP_PRS:
BEEP_ON;
delay_ms(300);
break;
default:
LED0_OFF;
LED1_OFF;
BEEP_OFF;
break;
}
}
}
二、综合实验
以下我们实现一个功能:按下按键,相应外设工作状态将反转。按键不松手,工作状态不会改变(即不支持连续按)。
为实现工作状态反转功能,本程序使用位带操作读取IO口输入电平和输出电平。这种办法实现了与51类似的IO口控制功能,比如51里是这样用的:sbit LED0 = P2^6;
在STM32里是这样用的:#define LED0 PEin(5)
,意思是读取GPIOE.5口的输入电平;#define LED0 PEout(5)
,意思是读取GPIOE.5口的输出电平。
完整代码如下(头文件sys.h为正点原子资料盘自带文件):
/* =====led.h===== */
#ifndef __LED_H
#define __LED_H
#define LED0_OFF GPIO_SetBits(GPIOB, GPIO_Pin_5)
#define LED0_ON GPIO_ResetBits(GPIOB, GPIO_Pin_5)
#define LED1_OFF GPIO_SetBits(GPIOE, GPIO_Pin_5)
#define LED1_ON GPIO_ResetBits(GPIOE, GPIO_Pin_5)
#define LED0 PBout(5) //位带操作
#define LED1 PEout(5)
void LED_Init(void);
#endif
/* =====led.c===== */
#include "stm32f10x.h"
#include "led.h"
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOE, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; /* 推挽输出 */
/* Definition: void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct) */
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_Init(GPIOE, &GPIO_InitStructure);
LED0_OFF;
LED1_OFF;
}
/* =====beep.h===== */
#ifndef __BEEP_H
#define __BEEP_H
#define BEEP_ON GPIO_SetBits(GPIOB, GPIO_Pin_8)
#define BEEP_OFF GPIO_ResetBits(GPIOB, GPIO_Pin_8)
#define BEEP PBout(8)
void Beep_Init(void);
#endif
/* =====beep.c===== */
#include "stm32f10x.h"
#include "beep.h"
void Beep_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOB, &GPIO_InitStructure);
BEEP_OFF;
}
/* =====key.h===== */
#ifndef __KEY_H
#define __KEY_H
#include "sys.h"
/* Definition: uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin) */
#define KEY0 GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_4)
#define KEY1 GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_3)
#define KEY_UP GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0)
#define IPU_ON 0
#define IPD_ON 1
#define ALL_KEY_UNPRS 0
#define KEY0_PRS 1
#define KEY1_PRS 2
#define KEY_UP_PRS 3
void KEY_Init(void);
u8 KEY_Scan(void);
#endif
/* =====key.c===== */
#include "stm32f10x.h"
#include "key.h"
#include "delay.h"
void KEY_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOE, ENABLE);
/* KEY0 & KEY1 -- Ground */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_4; /* PinE3 -- KEY1, PinE4 -- KEY0 */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; /* 上拉输入 */
GPIO_Init(GPIOE, &GPIO_InitStructure);
/* KEY_UP(WK_UP) -- VCC */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; /* PinA0 -- KEY_UP */
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; /* 下拉输入 */
GPIO_Init(GPIOA, &GPIO_InitStructure);
}
u8 KEY_Scan(void)
{
static int isPrsBefore = 0;
if((isPrsBefore == 0) && (KEY0 == IPU_ON || KEY1 == IPU_ON || KEY_UP == IPD_ON))
{
delay_ms(10);
isPrsBefore = 1;
if(KEY0 == IPU_ON)
return KEY0_PRS;
else if(KEY1 == IPU_ON)
return KEY1_PRS;
else if(KEY_UP == IPD_ON)
return KEY_UP_PRS;
}else if (KEY0 == !IPU_ON && KEY1 == !IPU_ON && KEY_UP == !IPD_ON)
isPrsBefore = 0;
return ALL_KEY_UNPRS;
}
/* =====main.c===== */
#include "stm32f10x.h"
#include "sys.h"
#include "beep.h"
#include "key.h"
#include "led.h"
#include "delay.h"
int main()
{
u8 key = 0;
delay_init();
KEY_Init();
LED_Init();
Beep_Init();
while(1)
{
key = KEY_Scan();
switch(key)
{
case KEY0_PRS:
LED0 = !LED0; //工作状态实现反转
break;
case KEY1_PRS:
LED1 = !LED1;
break;
case KEY_UP_PRS:
BEEP = !BEEP;
break;
default:
break;
}
}
}