Bootstrap

【蓝桥杯】单片机学习(5)——独立按键与矩阵按键

1、基础介绍

常用的按键电路有两种形式:独立式按键和矩阵式按键
以CD107D开发板为例,电路图如下:
按键电路图2-3短接实现独立按键,1-2短接实现矩阵按键。
值得一提的是按键消抖,有软件消抖和硬件消抖两种方法。硬件消抖即在按键上并联一个电容,利用电容的充放电特性来对抖动过程中产生的电压毛刺进行平滑处理,从而实现消抖,但因为成本较高,且使得电路更加复杂,所以并不常用。软件消抖可以借助简单的延时,更优的方法是借助中断。例如:启用定时中断,每2ms进行一次中断,扫描一次按键状态并且存储起来,连续扫描8次后,看看这连续8次的按键状态是否一致。8次按键的时间是16ms,如果在这16ms内按键的状态一直保持一致,即可以确定按键处于稳定的状态,而非抖动的状态。具体应用参考下面的实例:

2、独立按键

2-3短接,此时S4~S7一端接地,只需要检测P3^0, P3^1, P3^2, P3^3是否为0(按下时为0,弹起为1)。
实例:

//S7每按下一次数码管显示数字加1,到10清零
#include"reg52.h"

sfr P4 = 0xC0;

sbit KeyOut_1 = P3^0;
sbit KeyOut_2 = P3^1;
sbit KeyOut_3 = P3^2;
sbit KeyOut_4 = P3^3;

sbit KeyIn_4 = P3^4;
sbit KeyIn_3 = P3^5;
sbit KeyIn_2 = P4^2;
sbit KeyIn_1 = P4^4;

typedef unsigned char u8;
typedef unsigned int u16;

unsigned char code SMG[] = {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8, 0x80,
                            0x90,0x88,0x80,0xc6,0xc0,0x86,0x8e,0xbf,0x7f};//数码管显示字符转换表
bit KeySta = 1; //按键的当前状态

void CloseFucker();
void TimeInit();
void Keydriver();
void Keyscan();

void main()
{
    CloseFucker(); //关闭其他无用的模块
    TimeInit();   //定时器初始化 

    P2 = (P2&0x1f)|0xC0;//138译码器选择数码管位
    P0 = 0x01;          //选择数码管DS1_1
    P2 = (P2&0x1f)|0xe0;//138译码器选择数码管段
    P0 = SMG[0];        //默认显示0
    
    while(1)
    {
        Keydriver();
    }    
}

void InterruptTimer0() interrupt 1
{
    TH0 = 0xF8; //重新加载初值
    TL0 = 0xCD;
    Keyscan();
}

void CloseFucker()
{
	P2 = (P2 & 0x1F) | 0x80;
	P0 = 0xFF;
	P2 = P2 & 0x1F;
	
	P2 = (P2 & 0x1F) | 0xA0;
	P0 = 0xAF;
	P2 = P2 & 0x1F;
}

void TimeInit()
{
    EA = 1;       //打开中断总开关
    ET0 = 1;      //打开中断T0开关
    TMOD = 0x01;  //设置定时器模式
    TR0 = 1;      //打开定时器T0
    TH0 = 0xF8;   //设置初值,定时2ms
    TL0 = 0xCD;       
}

void Keydriver()
{
    bit backup = 1; //按键值备份,保存前一次的扫描值
    u8 cnt = 0;     //按键计数,记录按键按下的次数
    if(KeySta!=backup) //当前值与前次值不相等,说明按键此时有动作
       {
            if(backup != 0) //前次值不为0,说明当前值为0,即按键按下
            {
                cnt++;    //按键次数加1
                if(cnt>=10)
                {
                    cnt = 0; //加到10即清零
                }
                P0 = SMG[cnt];  //计数值显示到数码管上
            }
            backup = KeySta;     //更新备份值
       }     
}

void Keyscan()
{
    static u8 keybuf = 0xFF;    //扫描缓冲区,保存一段时间内的扫描值

    keybuf = (keybuf<<1)|KeyOut_1;  //缓冲区左移一位,并将当前扫描值存入最低位
    if(keybuf == 0x00) //连续8次扫描值为0,即16ms内都是按下状态,认为已稳定按下
    {
        KeySta = 0;// 当前按键值更新为0
    }
    else if(keybuf == 0xff) // 连续8次扫描值为1,即16ms内都是按下状态,认为已稳定弹起
    {
        KeySta = 1; //  当前按键值更新为1
    }
    else
    {}
}

3、矩阵键盘

有4个keyout输出,改用1ms中断判断四次采样值,这样消抖时间还是16ms(1X4X4)。

#include "reg52.h"

sfr  P4  = 0xC0;

sbit KeyOut_1 = P3^0;
sbit KeyOut_2 = P3^1;
sbit KeyOut_3 = P3^2;
sbit KeyOut_4 = P3^3;

sbit KeyIn_1 = P4^4; 
sbit KeyIn_2 = P4^2;
sbit KeyIn_3 = P3^5;
sbit KeyIn_4 = P3^4;

typedef unsigned char u8;
typedef unsigned int u16;

unsigned char code SMG[] = {0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xf8,0x80,
                            0x90,0x88,0x80,0xc6,0xc0,0x86,0x8e,0xbf,0x7f}; //数码管显示字符转换表

unsigned char KeySta[4][4]= {{1,1,1,1},{1,1,1,1},{1,1,1,1},{1,1,1,1}};     //矩阵按键的当前状态
u8 KeyCodeMap[4][4] = {	
                        {0,1, 2, 3,},
                	    {4, 5, 6, 7},
	                    {8,9,10,11},
	                    {12,13,14,15} };   //索引值

void CloseFucker();          //关闭无用的模块
void TimeInit();             //定时器初始化
void KeyAction(u8 keycode);  //按键功能
void Keydriver();            //循环检测4*4的矩阵按键
void Keyscan();              //按键扫描

void main ()
{
    CloseFucker();      //关闭其他无用的模块
    TimeInit();        //定时器初始化

    P2 = (P2&0x1f)|0xC0;//138译码器选择数码管位
    P0 = 0x01;          //选择数码管DS1_1
    P2 = (P2&0x1f)|0xe0;//138译码器选择数码管段
    P0 = SMG[0];        //默认显示0
    while (1)
    {
       Keydriver();    
    } 
}

void CloseFucker()
{
	P2 = (P2 & 0x1F) | 0x80;
	P0 = 0xFF;
	P2 = P2 & 0x1F;
	
	P2 = (P2 & 0x1F) | 0xA0;
	P0 = 0xAF;
	P2 = P2 & 0x1F;
}

void TimeInit()
{
    EA = 1;       //打开中断总开关
    ET0 = 1;      //打开中断T0开关
    TMOD = 0x01;  //设置定时器模式
    TR0 = 1;      //打开定时器T0
    TH0 = 0xFC;   //设置初值,定时1ms
    TL0 = 0x67;       
}

void Keydriver()
{
    u8 i,j;
    static u8 backup[4][4] ={{1,1,1,1},{1,1,1,1},
                             {1,1,1,1},{1,1,1,1}};  //按键值备份,保存前一次按键的值
    for(i = 0;i<4;i++)                              //循环检测4*4的矩阵按键
        {
            for(j = 0;j<4;j++)
            {
                 if(backup[i][j]!= KeySta[i][j])     //检测按键是否有动作
                {
                    if(backup[i][j] != 0)            //按键按下时执行动作
                    {
                        //P0 = SMG[4*i+j];
                        KeyAction(KeyCodeMap[i][j]); //将编号显示到数码管
                    }
                    backup[i][j] = KeySta[i][j];    //更新前一次的备份值
                }
             }
        }
}

void KeyAction(u8 keycode)
{
    if((keycode >= 0) && (keycode <= 15))
	{
	 	P0 =SMG[keycode];
	}
}

void Keyscan()
{
    u8 i;
    static u8 keyout = 0;      //矩阵按键扫描输出索引
    static u8 keybuf[4][4] = {
        {0xFF,0xFF,0xFF,0xFF},{0xFF,0xFF,0xFF,0xFF},
        {0xFF,0xFF,0xFF,0xFF},{0xFF,0xFF,0xFF,0xFF},
    };     //矩阵按键扫描缓冲区

    //将一行的四个按键值移入缓冲区
    keybuf[keyout][0] = (keybuf[keyout][0]<<1) | KeyIn_1;  // 按键按下时,KeyIn_1为0
    keybuf[keyout][1] = (keybuf[keyout][1]<<1) | KeyIn_2;
    keybuf[keyout][2] = (keybuf[keyout][2]<<1) | KeyIn_3;
    keybuf[keyout][3] = (keybuf[keyout][3]<<1) | KeyIn_4;

    //消抖后更新按键状态
    for(i = 0;i<4;i++)  //每行四个按键,循环四次
    {
        if((keybuf[keyout][i]&0x0f) == 0x00)
        {
            KeySta[keyout][i] = 0;//连续四次扫描值为0,即4*4ms内都是按下状态,可认为按键已稳定按下
        }
        else if((keybuf[keyout][i]&0x0f) == 0x0f)
        {
            KeySta[keyout][i] = 1;//连续四次扫描值为1,即4*4ms内都是弹起状态,可认为按键已稳定弹起
        }
     }
        keyout++;   //输出索引递增
        keyout = keyout & 0x03;    //索引值加到4即归零
        switch(keyout)             //根据索引,释放当前输出引脚,拉低下次的引脚
        {
            case 0: KeyOut_4 = 1;KeyOut_1 = 0;break;
            case 1: KeyOut_1 = 1;KeyOut_2 = 0;break;
            case 2: KeyOut_2 = 1;KeyOut_3 = 0;break;
            case 3: KeyOut_3 = 1;KeyOut_4 = 0;break;
            default : break;
        }   //注意逻辑顺序,switch()放这里可确保距离下一次查询按键状态至少有1毫秒的缓冲时间(中断1ms进入一次)
}

void InterruptTimer0() interrupt 1
{
    TH0 = 0xFC;  //重新加载初值
    TL0 = 0x67;   
    Keyscan();
}

前一篇: 单片机学习(4)——点阵LED

下一篇: 单片机学习(6)——蜂鸣器+继电器

;