单片机长短按键的应用

发布者:楼高峰最新更新时间:2016-12-24 来源: eefocus关键字:单片机  长短按键 手机看文章 扫描二维码
随时随地手机看文章

在单片机系统中应用按键的时候,如果只需要按下一次按键加 1 或减 1,那用第 8 章学到的知识就可以完成了,但如果想连续加很多数字的时候,要一次次按下这个按键确实有点不方便,这时我们会希望一直按住按键,数字就自动持续增加或减小,这就是所谓的长短按键应用。


当检测到一个按键产生按下动作后,马上执行一次相应的操作,同时在程序里记录按键按下的持续时间,该时间超过 1 秒后(主要是为了区别短按和长按这两个动作,因短按的时间通常都达到几百 ms),每隔 200ms(如果你需要更快那就用更短的时间,反之亦然)就自动再执行一次该按键对应的操作,这就是一个典型的长按键效果。


对此,我们做了一个模拟定时炸弹效果的实例,提供给大家作为参考。打开开关后,数码管显示数字 0,按向上的按键数字加 1,按向下的按键数字减 1,长按向上按键 1 秒后,数字会持续增加,长按向下按键 1 秒后,数字会持续减小。设定好数字后,按下回车按键,时间就会进行倒计时,当倒计时到 0 的时候,用蜂鸣器和板子上的 8 个 LED 小灯做炸弹效果,蜂鸣器持续响,LED 小灯全亮。

纯文本复制

#include

sbit BUZZ = P1^6;

sbit ADDR3 = P1^3;

sbit ENLED = P1^4;

sbit KEY_IN_1 = P2^4;

sbit KEY_IN_2 = P2^5;

sbit KEY_IN_3 = P2^6;

sbit KEY_IN_4 = P2^7;

sbit KEY_OUT_1 = P2^3;

sbit KEY_OUT_2 = P2^2;

sbit KEY_OUT_3 = P2^1;

sbit KEY_OUT_4 = P2^0;

unsigned char code LedChar[] = { //数码管显示字符转换表

    0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,

    0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E

};

unsigned char LedBuff[7] = { //数码管+独立 LED 显示缓冲区

    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF

};

unsigned char code KeyCodeMap[4][4] = { //矩阵按键编号到标准键盘键码的映射表

    { 0x31, 0x32, 0x33, 0x26 }, //数字键 1、数字键 2、数字键 3、向上键

    { 0x34, 0x35, 0x36, 0x25 }, //数字键 4、数字键 5、数字键 6、向左键

    { 0x37, 0x38, 0x39, 0x28 }, //数字键 7、数字键 8、数字键 9、向下键

    { 0x30, 0x1B, 0x0D, 0x27 } //数字键 0、ESC 键、 回车键、 向右键

};

unsigned char KeySta[4][4] = { //全部矩阵按键的当前状态

    {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}

};

unsigned long pdata KeyDownTime[4][4] = { //每个按键按下的持续时间,单位 ms

    {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}

};

bit enBuzz = 0; //蜂鸣器使能标志

bit flag1s = 0; //1 秒定时标志

bit flagStart = 0; //倒计时启动标志

unsigned char T0RH = 0; //T0 重载值的高字节

unsigned char T0RL = 0; //T0 重载值的低字节

unsigned int CountDown = 0; //倒计时计数器

void ConfigTimer0(unsigned int ms);

void ShowNumber(unsigned long num);

void KeyDriver();

void main(){

    EA = 1; //使能总中断

    ENLED = 0; //选择数码管和独立 LED

    ADDR3 = 1;

    ConfigTimer0(1); //配置 T0 定时 1ms

    ShowNumber(0); //上电显示 0

   

    while (1){

        KeyDriver(); //调用按键驱动函数

        if (flagStart && flag1s){ //倒计时启动且 1 秒定时到达时,处理倒计时

            flag1s = 0;

            if (CountDown > 0){ //倒计时未到 0 时,计数器递减

                CountDown--;

                ShowNumber(CountDown); //刷新倒计时数显示

                if (CountDown == 0){ //减到 0 时,执行声光报警

                    enBuzz = 1;

                    //启动蜂鸣器发声

                    LedBuff[6] = 0x00; //点亮独立 LED

                }

            }

        }

    }

}

/* 配置并启动 T0,ms-T0 定时时间 */

void ConfigTimer0(unsigned int ms){

    unsigned long tmp; //临时变量

    tmp = 11059200 / 12; //定时器计数频率

    tmp = (tmp * ms) / 1000; //计算所需的计数值

    tmp = 65536 - tmp; //计算定时器重载值

    tmp = tmp + 28; //补偿中断响应延时造成的误差

    T0RH = (unsigned char)(tmp>>8); //定时器重载值拆分为高低字节

    T0RL = (unsigned char)tmp;

    TMOD &= 0xF0; //清零 T0 的控制位

    TMOD |= 0x01; //配置 T0 为模式 1

    TH0 = T0RH; //加载 T0 重载值

    TL0 = T0RL;

    ET0 = 1; //使能 T0 中断

    TR0 = 1; //启动 T0

}

    /* 将一个无符号长整型的数字显示到数码管上,num-待显示数字 */

void ShowNumber(unsigned long num){

    signed char i;

    unsigned char buf[6];

    for (i=0; i<6; i++){ //把长整型数转换为 6 位十进制的数组

        buf[i] = num % 10;

        num = num / 10;

    }

    for (i=5; i>=1; i--){ //从最高位起,遇到 0 转换为空格,遇到非 0 则退出循环

        if (buf[i] == 0){

            LedBuff[i] = 0xFF;

        }else{

            break;

        }

    }

   

    for ( ; i>=0; i--){ //剩余低位都如实转换为数码管显示字符

        LedBuff[i] = LedChar[buf[i]];

    }

}

/* 按键动作函数,根据键码执行相应的操作,keycode-按键键码 */

void KeyAction(unsigned char keycode){ //按键动作函数,根据键码执行相应动作

    if (keycode == 0x26){ //向上键,倒计时设定值递增

        if (CountDown < 9999){ //最大计时 9999 秒

            CountDown++;

            ShowNumber(CountDown);

        }

    }else if (keycode == 0x28){ //向下键,倒计时设定值递减

        if (CountDown > 1){ //最小计时 1 秒

            CountDown--;

            ShowNumber(CountDown);

        }

    }else if (keycode == 0x0D){ //回车键,启动倒计时

        flagStart = 1; //启动倒计时

    }else if (keycode == 0x1B){ //Esc 键,取消倒计时

        enBuzz = 0; //关闭蜂鸣器

        LedBuff[6] = 0xFF; //关闭独立 LED

        flagStart = 0; //停止倒计时

        CountDown = 0; //倒计时数归零

        ShowNumber(CountDown);

    }

}

/* 按键驱动函数,检测按键动作,调度相应动作函数,需在主循环中调用 */

void KeyDriver(){

    unsigned char i, j;

    static unsigned char pdata backup[4][4] = { //按键值备份,保存前一次的值

        {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}, {1, 1, 1, 1}

    };

    static unsigned long pdata TimeThr[4][4] = { //快速输入执行的时间阈值

        {1000, 1000, 1000, 1000}, {1000, 1000, 1000, 1000},

        {1000, 1000, 1000, 1000}, {1000, 1000, 1000, 1000}

    };

   

    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){ //按键按下时执行动作

                    KeyAction(KeyCodeMap[i][j]); //调用按键动作函数

                }

            }

            backup[i][j] = KeySta[i][j];

            if (KeyDownTime[i][j] > 0){

                if (KeyDownTime[i][j] >= TimeThr[i][j]){ //达到阈值时执行一次动作

                    KeyAction(KeyCodeMap[i][j]); //调用按键动作函数

                    TimeThr[i][j] += 200; //时间阈值增加 200ms,以准备下次执行

                }

            }else{ //按键弹起时复位阈值时间

                TimeThr[i][j] = 1000; //恢复 1s 的初始阈值时间

            }

        }

    }

}

/* 按键扫描函数,需在定时中断中调用 */

void KeyScan(){

    unsigned char i;

    static unsigned char keyout = 0; //矩阵按键扫描输出索引

   

    static unsigned char keybuf[4][4] = { //矩阵按键扫描缓冲区

        {0xFF, 0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF, 0xFF},

        {0xFF, 0xFF, 0xFF, 0xFF}, {0xFF, 0xFF, 0xFF, 0xFF}

    };

   

    //将一行的 4 个按键值移入缓冲区

    keybuf[keyout][0] = (keybuf[keyout][0] << 1) | KEY_IN_1;

    keybuf[keyout][1] = (keybuf[keyout][1] << 1) | KEY_IN_2;

    keybuf[keyout][2] = (keybuf[keyout][2] << 1) | KEY_IN_3;

    keybuf[keyout][3] = (keybuf[keyout][3] << 1) | KEY_IN_4;

    //消抖后更新按键状态

    for (i=0; i<4; i++){ //每行 4 个按键,所以循环 4 次

        //连续 4 次扫描值为 0,即 4*4ms 内都是按下状态时,可认为按键已稳定的按下

        if ((keybuf[keyout][i] & 0x0F) == 0x00){

            KeySta[keyout][i] = 0;

            KeyDownTime[keyout][i] += 4; //按下的持续时间累加

        //连续 4 次扫描值为 1,即 4*4ms 内都是弹起状态时,可认为按键已稳定的弹起

        }else if ((keybuf[keyout][i] & 0x0F) == 0x0F){

            KeySta[keyout][i] = 1;

        }

    }

    KeyDownTime[keyout][i] = 0; //按下的持续时间清零

    //执行下一次的扫描输出

    keyout++; //输出索引递增

    keyout &= 0x03; //索引值加到 4 即归零

    switch (keyout){ //根据索引,释放当前输出引脚,拉低下次的输出引脚

        case 0: KEY_OUT_4 = 1; KEY_OUT_1 = 0; break;

        case 1: KEY_OUT_1 = 1; KEY_OUT_2 = 0; break;

        case 2: KEY_OUT_2 = 1; KEY_OUT_3 = 0; break;

        case 3: KEY_OUT_3 = 1; KEY_OUT_4 = 0; break;

        default: break;

    }

}

/* LED 动态扫描刷新函数,需在定时中断中调用 */

void LedScan(){

    static unsigned char i = 0; //动态扫描索引

    P0 = 0xFF; //关闭所有段选位,显示消隐

    P1 = (P1 & 0xF8) | i; //位选索引值赋值到 P1 口低 3 位

    P0 = LedBuff[i]; //缓冲区中索引位置的数据送到 P0 口

    if (i < 6){ //索引递增循环,遍历整个缓冲区

        i++;

    }else{

        i = 0;

    }

}

/* T0 中断服务函数,完成数码管、按键扫描与秒定时 */

void InterruptTimer0() interrupt 1{

    static unsigned int tmr1s = 0; //1 秒定时器

    TH0 = T0RH; //重新加载重载值

    TL0 = T0RL;

    if (enBuzz){ //蜂鸣器发声处理

        BUZZ = ~BUZZ; //驱动蜂鸣器发声

    }else{

        BUZZ = 1; //关闭蜂鸣器

    }

    LedScan(); //LED 扫描显示

    KeyScan(); //按键扫描

    if (flagStart){ //倒计时启动时处理 1 秒定时

        tmr1s++;

        if (tmr1s >= 1000){

            tmr1s = 0;

            flag1s = 1;

        }

    }else{ //倒计时未启动时 1 秒定时器始终归零

        tmr1s = 0;

    }

}

长按键功能实现的重点有两个:第一,是在原来的矩阵按键扫描函数 KeyScan 内,当检测到按键按下后,持续的对一个时间变量进行累加,其目的是用这个时间变量来记录按键按下的时间;第二,是在按键驱动函数 KeyDriver 里,除了原来的检测到按键按下这个动作时执行按键动作函数 KeyAction 外,还监测表示按键按下时间的变量,根据它的值来完成长按时的连续快速按键动作功能。


关键字:单片机  长短按键 引用地址:单片机长短按键的应用

上一篇:51单片机RAM区域的划分
下一篇:单片机串行通信介绍

推荐阅读最新更新时间:2024-03-16 15:26

51单片机水温控制LCD显示加VB上位机温度曲线绘制
现功能,1L水由1KW电炉加热,要求水温在一定范围内人工设定,并能随着环境温度改变自动调节,以维持设定的温度不变。 矩阵键盘输入设定温度,LCD1602显示温度值,VB上位机绘制温度曲线 /******************************************************************************* * 实验名 :温度显示实验 * 使用的IO : * 实验效果 :1602显示温度 * 注意 : *************************************************
[单片机]
AVR单片机的CPU内部结构的详细资料说明
  学单片机那么久了,感觉想要深入,还得看汇编语言,至少得了解单片机内部结构。下面就以ATmega16为例,介绍一下AVR单片机结构和汇编语言。   如上两图,左图是虚线框内AVR CPU的内核结构,右图是AVR单片机内核结构的方框图,可以看出AVR单片机的数据总线 (CPU字长)是8位的,也就说它是8位单片机。 AVR采用了Harvard结构,具有独立的数据和程序总线,CPU在执行一条指令的同时,就将PC中指定的下一条指令取出,构成了一级流水线运行方式,实现了一个时钟周期执行一条指令,数据吞吐量高达1MIPS/MHz。 AVR CPU内核由几个重要的部分组成,它们分别是:   A.算数逻辑单元ALU(Arithmetic
[单片机]
AVR<font color='red'>单片机</font>的CPU内部结构的详细资料说明
单片机在电源设计中的应用
电源设计人员经常面临种种互相对立的要求。一方面要缩小体积、降低成本,另一方面又要提供更多功能并提高输出功率。受原理上的限制,模拟电源本身的功能有限,而模拟电源控制器的设计更是越来越复杂。由于这一原因,有些设计人员转向了纯数字电源设计。然而,对于许多设计人员来说,如此快速地转向不熟悉的领域并不容易。比较可行的一种折衷方法是采用传统模拟电源,但增加数字单片机做为前端。 这种设计的优点在于电源本身的控制仍然使用模拟技术来实现。因此电源设计人员不需要从头重新开始全数字设计就可以为现有设计增加新的功能。采用这种方法,设计中仍然使用熟悉的误差放大器、电流检测以及电压检测电路。当然,尽管有些设计单元(如补偿网络)仍然采用分立器件实现,但其余
[电源管理]
STM32单片机中RTC的秒中断的原理解析
RTC(Real Time Clock)是实时时钟的意思,它其实和TIM有点类似,也是利用计数的原理,选择RTC时钟源,再进行分频,到达计数的目的。 该文主要讲述关于RTC的秒中断功能,这个功能类似SysTIck系统滴答的功能。RTC秒中断功能其实是每计数一次就中断一次。注意,这里所说的秒中断并非一定是一秒的时间,它是由RTC时钟源和分频值决定的“秒”的时间,当然也是可以做到1秒钟中断一次。 本文章提供的实例工程,其实验效果是: 主函数间隔0.5秒LED变化一次; 秒中断一次打印数据“RTC Sec.。.”; 也就是LED变化一次,串口打印一次数据“RTC Sec.。.” 扩展部分的功能RTC计数:可以实现RTC闹钟,本文
[单片机]
STM32<font color='red'>单片机</font>中RTC的秒中断的原理解析
MSP430单片机按键程序
MSP 430 单片机 按键程序 #include msp430x14x.h #include key.h void Init_Port(void) { //将P1口所有的管脚在初始化的时候设置为输入方式 P1DIR = 0; //将P1口所有的管脚设置为一般I/O口 P1SEL = 0; // 将P1.4 P1.5 P1.6 P1.7设置为输出方向 P1DIR |= BIT4; P1DIR |= BIT5; P1DIR |= BIT6; P1DIR |= BIT7; //先输出低 电平 P1OUT = 0x00; // 将中断寄存器清零 P1IE = 0;
[单片机]
基于51单片机的太阳能路灯控制系统
简介:太阳能路灯控制系统:51单片机练手项目,简单可复制。 带太阳能充电功能,oled显示, 白天根据光强判断开关灯晚上开灯,二级菜单可以设置时间日期。 太阳能充电:传统锂电池充电芯片TP4056,使用6V太阳能板,给3.7V18650电池充电。 经过资料显示 18650电池尽量不要让其电压低于2.7V,所以后级供电电路(5V升压电路)MT3608启动引脚EN脚 连接了LM393制成的电压比较器。和电池电压比较,电池电压低于2.7v,MT3608启动脚拉低关断。 供电:使用升压芯片MT3608给单片机供电,让电池电压稳定在5.1V,来提供稳定电压。 #include reg52.h #include oled.h
[单片机]
基于51<font color='red'>单片机</font>的太阳能路灯控制系统
STM32F3 MCU最小BOM表及元器件参数选型
STM32F3xx系列是高集成和易于开发的32位MCU,具有实时功能、数字信号处理、低功耗与低电压操作特性,可广泛用于消费、医疗、便携式健身、系统监控与测量的实际应用。 STM32F3xx系列整合了带有DSP与FPU指令、工作频率为72MHz的32位ARM Cortex-M4内核、高级模拟外设以及嵌入式Flash和SRAM存储器。由于集成了高效的电源结构和多种功耗模式,STM32F3xx降低了应用级功耗并简化了应用设计。 1. 电源方案 STM32F3xxxx器件集实时功能、数字信号处理和低电压操作、高度集成的模拟外设于一身,具有优化的电源结构和多种供电方案。 图1. STM32F3xxxx器件供电方案 •
[单片机]
STM32F3 <font color='red'>MCU</font>最小BOM表及元器件参数选型
八位微控制器有哪些可以节约代码空间的代码优化技巧?
本文将介绍一些优化技术,帮助设计人员节约多达 10% 的代码空间,从而让容量有限的程序存储器支持更多新特性和补丁。 良好的操作方法 许多程序员在 32 位处理器上学习编写软件,如 Intel 的 Pentium 处理器或某种 ARM 平台。不过,嵌入式领域的软件编写需要不同的思路。在 32 位 CPU 上,存储比特位的最佳方法通常是使用 32 位变量。对 8 位处理器而言,最好的办法就是采用单字节。像增强型 8051s 等某些处理器可能提供特殊的 1 位变量。 嵌入式处理器通常会超出标准的哈佛架构将存储器分散到不同的存储器空间中,有的相互重叠,有的又是相互分离。例如,8051 中常见的存储器空间包括 CODE、XDATA、DA
[单片机]
八位<font color='red'>微控制器</font>有哪些可以节约代码空间的代码优化技巧?
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
  • 学习ARM开发(16)
    ARM有很多东西要学习,那么中断,就肯定是需要学习的东西。自从CPU引入中断以来,才真正地进入多任务系统工作,并且大大提高了工作效率。采 ...
  • 学习ARM开发(17)
    因为嵌入式系统里全部要使用中断的,那么我的S3C44B0怎么样中断流程呢?那我就需要了解整个流程了。要深入了解,最好的方法,就是去写程序 ...
  • 学习ARM开发(18)
    上一次已经了解ARM的中断处理过程,并且可以设置中断函数,那么它这样就可以工作了吗?答案是否定的。因为S3C44B0还有好几个寄存器是控制中 ...
  • 嵌入式系统调试仿真工具
    嵌入式硬件系统设计出来后就要进行调试,不管是硬件调试还是软件调试或者程序固化,都需要用到调试仿真工具。 随着处理器新品种、新 ...
  • 最近困扰在心中的一个小疑问终于解惑了~~
    最近在驱动方面一直在概念上不能很好的理解 有时候结合别人写的一点usb的例子能有点感觉,但是因为arm体系里面没有像单片机那样直接讲解引脚 ...
  • 学习ARM开发(1)
  • 学习ARM开发(2)
  • 学习ARM开发(4)
  • 学习ARM开发(6)
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved