注:此单片机型号为 STC15F2K60S2.
独立按键
原理
这是一个矩阵按键的电路图,要使得其进入独立按键模式,就要把跳线帽连接2、3.
(注:跳线帽是外部可移动的元件。)
此时,即为独立按键模式。
按键原理
以 S7 按键为例,其左端连接接地,右端连接 P30,由于一开始所有 P 口都默认为高电平,则只要检测到 P30 为低电平,则说明按键 S7 被按下。
消抖
当按键被按下的时候,电路导通接地,I/O口为低电平;
当按键未被按下时,电路断开,I/O口保持高电平。
但一般的按键所用开关为机械弹性开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个按键开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开。因而在闭合及断开的瞬间均伴随有一连串的抖动,假如不加以处理,会导致按键被识别为按下多次。为了不产生这种现象而作的措施就是按键消抖。
消抖有硬件消抖和软件消抖两种方法,但我们不可能去更换硬件,所以主要采用软件消抖 —— 写一个延迟 5ms 左右的函数。
代码
入门代码
//key.c
/****************************************************************************
* Copyright (C), 2022,Moqim
* 文件名: main.c
* 内容简述:检测独立按键被按下的键值
*
* 文件历史:
* 版本号 日期 作者 说明
* 01a 2022-05-18 Moqim 创建该文件
*/
#include "key.h"
//为方便使用,重新定义管脚名
sbit S7 = P3^0;
sbit S6 = P3^1;
sbit S5 = P3^2;
sbit S4 = P3^3;
/****************************************************************************
* 函数名: Key_Scan()
* 功 能: 按键检测
* 输 入: 无
* 输 出: unsigned char类型的数值,表示键值
*/
unsigned char Key_Scan(void)
{
if (S4 == 0)
{
Key_Delay(10);
if (S4 == 0)
return 4;
}
else if (S5 == 0)
{
Key_Delay(10);
if (S5 == 0)
return 5;
}
else if (S6 == 0)
{
Key_Delay(10);
if (S6 == 0)
return 6;
}
else if (S7 == 0)
{
Key_Delay(10);
if (S7 == 0)
return 7;
}
return 0;
}
/****************************************************************************
* 函数名: Key_Delay()
* 功 能: 延时
* 输 入: unsigned int num:延时num毫秒
* 输 出: 无
*/
void Key_Delay(unsigned int num)
{
int i, j;
for (i = 0; i<num; i++)
for(j = 0; j<625; j++);
}
//key.h
#ifndef __KEY_H
#define __KEY_H
#include "stc15f2k60s2.h"
unsigned char Key_Scan(void);
void Key_Delay(unsigned int num);
#endif /*__KEY_H*/
//main.c
/****************************************************************************
* Copyright (C), 2022,Moqim
* 文件名: main.c
* 内容简述:实现LED和SEG显示按键对应数值
*
* 文件历史:
* 版本号 日期 作者 说明
* 01a 2022-05-18 Moqim 创建该文件
*/
//头文件调用区
#include "stc15f2k60s2.h"
#include "led.h"
#include "seg.h"
#include "key.h"
//变量定义区
//函数声明区
void Delay_ms(unsigned int num);
void Key_Disp(void);
//Main Body
int main(void)
{
LED_Init(); //关闭LED
SEG_Init(); //关闭数码管
while (1)
{
Key_Disp(); //执行按键函数
}
}
/****************************************************************************
* 函数名: Key_Disp()
* 功 能: 按下按键,LED和SEG显示对应数值
* 输 入: 无
* 输 出: 无
*/
void Key_Disp(void)
{
static unsigned char Key_Val = 0; //定义无符号字符型的静态变量,表示当前键值
static int i = 0; //一个用于调用LED和SEG的参数
Key_Val = Key_Scan(); //获取当前键值
switch (Key_Val) //判断键值,执行内容
{
case 4:i = 4;break;
case 5:i = 5;break;
case 6:i = 6;break;
case 7:i = 7;break;
default:i = 0;break;
}
if (i > 0)
{
LED_Ctrl(0x01 << i - 1); //点亮对应序号的LED
smg1 = i; //数码管显示对应键值
}
else
{
LED_Ctrl(0x00); //LED全灭
smg1 = 0; //数码管显示0
}
SEG_Scan(); //数码管显示
Delay_ms(200); //增加显示时间
}
/****************************************************************************
* 函数名: Delay_ms()
* 功 能: 延时
* 输 入: unsigned int num:延时num毫秒
* 输 出: 无
*/
void Delay_ms(unsigned int num) //软件延时不精确,凑合用吧
{
int i, j;
for(i = 0; i<num; i++)
{
for(j = 0; j<625; j++);
}
}
LED 和 SEG 代码请参考之前博客。
进阶代码
void key_press(int* p) //设定一个变量,可返回至数码管以检查代码是否正确执行;
{
if (P30 == 0) { //当检测到P30为低电平时;
delay(5); //消抖;
if (P30 == 0) //若仍为低电平,说明按键被按下;
(*p)++; //执行需要的程序;
while (P30 == 0);//一直执行程序,直到P30!=0,即按键被松开;
}
}
但上述代码有一个缺点,即长按按键会一直执行程序,
所以,改进代码如下——
void key_press(int* p) //设定一个变量,可返回至数码管以检查代码是否正确执行;
{
bit S7 = 0; //设定一个位标量,令其为0;
if ((P30 == 0)&&(S7 == 0)) { //当检测到P30为低电平且S7为0时;
delay(5); //消抖
if ((P30 == 0) && (S7 == 0)) //若仍为低电平且S7为0,说明按键被按下;
S7 = 1; //令S7为1;
}
if ((P30 == 1) && (S7 == 1)) { //当检测到P30为高电平且S7为1时,说明已被按下过,且现在处于抬起状态;
(*p)++; //执行需要的程序;
S7 = 0; //复位S7;
}
}
这样,每次按完一次才会执行一次,长按则不会影响。
参考资料:bilibili@一蓑烟雨任平生 a—— 基于蓝桥杯单片机的 STC15 学习教程。
消抖改进(按一下只执行一次,长按也一样)
来源:上课认真听讲。
//前提条件:按下一个键(S4为例),该变量读到的是4(已经通过key_read()函数获取按键数值了)
//按下为4,抬起为0
//way 1,设置一个参考变量
u8 flag = 0;
if(key_read == 4 && flag == 0){//读取S4
flag = 1;
//按键按一下执行的代码
}
if(flag == 1 && key_read() != 4){
flag = 0;
}
//way 2
//比较当前值和前一次值,
u8 last_key_value = 0;//上一次的值
u8 current_key_value = key_read();//当前值
if(current_key_value > last_key_value && current_key_value == 4){//如果当前值大于上一次的值,且当前值为4
//执行语句
}
last_key_value = current_key_value;//把当前值赋给上一次的值
//way 3
//用异或
u8 key_val = 0;//结果值
u8 last_key_value = 0;//上一次的值
u8 current_key_value = 0;//当前值
current_key_value = key_read();//当前值赋值
key_val = current_key_value ^ last_key_value & current_key_value;
//异或完后(相异为1),若按下(当前值为4),与上当前值后不变;若抬起(当前值为0),与上后为0;
//若长按,则异或结果为0,0与上任何数都是0;
last_key_value = current_key_value;
switch(key_val){
case 4:if(++ work_state == 4)work_state = 1;break;
case 5:
case 6:
case 7:
}
补充
2022.04.04
我现在常用的:
void Key_Proc(void){
static unsigned char ucKey_Val = 0;
static unsigned char ucKey_Down = 0;
static unsigned char ucKey_Old = 0;
ucKey_Val = Key_Scan();//从按键判断函数中获取键值
ucKey_Down = ucKey_Val & (ucKey_Val ^ ucKey_Old);//当前键值与上一次键值异或,若相同则为0,则ucKey_Down为0;相异则反之。
ucKey_Old = ucKey_Val;
switch(ucKey_Down){//短按用ucKey_Down,长按用ucKey_Old
case 4:break;
default:break;
}
}