之前的电子钟程序中,用的按键消抖处理方法是10ms的延时,这种方法效率比较低

所以现在利用状态机原理重写一下,效率很高啊

4个独立按键中用到3个,

keys5用于切换对时分秒等状态,keys2是减小数值,keys3是增加数值

同时可以判断按键的"短按,长按,连发"等功能

小于2秒视为短按,

大于2秒视为长按,

在长按状态下每0.2秒自动连发一次, 这样对时的时候就不用按N次了

欢迎一起交流,qq 102351263   验证码 iteye

程序分很多个文件 ,Keil uVision4 打包

#include "MY51.H"
#include "keyScan.h"
#include "smg.h"
#include "myClock.h"void show();   //数码管显示extern s8  shi;
extern s8  fen;
extern s8  miao;
extern u8  changeTimeFlag;
extern u8  timeMultipleFlag;void main()
{startT0(10,100);  //开T0启定时器     10毫秒*100=1秒while(1){show();       }
}void T0_Work()  //T0定时器调用的工作函数
{u8 key_stateValue;u8* pKeyValue;*pKeyValue=0;key_stateValue=read_key(pKeyValue);if(timeMultipleFlag)  //到1秒了{timeMultipleFlag=0;     //标志清零clock();          //走时,秒++}if( (return_keyPressed==key_stateValue)&&(*pKeyValue==KEYS5_VALUE) ){      //短按keyS5时改变对时状态changeTimeState(); //改变changeTimeFlag的3种状态,分别修改时或分或秒}if((return_keyPressed==key_stateValue)||(key_stateValue&return_keyAuto) ){    //短按s2或s3可加减响应数值,长按keyS2或keyS3时每0.1秒加减一次数值if(changeTimeFlag)                //changeTimeFlag不为0时,允许修改{if(KEYS2_VALUE == *pKeyValue){changeTime(TRUE);    //KEYS2,秒++}if(KEYS3_VALUE == *pKeyValue){changeTime(FALSE);    //KEYS3,秒--}}}
}void show()  //显示时钟
{u8 oneWela,twoWela,threeWela,foreWela,fiveWela,sixWela; //oneWela是最左边的数码管sixWela =miao%10;fiveWela=miao/10;  foreWela=fen%10;threeWela=fen/10;twoWela=shi%10;oneWela=shi/10;displaySMG(oneWela,twoWela,threeWela,foreWela,fiveWela,sixWela,0xf5); //0xf5是小数点的位置
}
#ifndef _MY51_H
#define _MY51_H
#include <reg52.h>
#include <math.h>
#include <intrins.h>
#include "mytype.h"#define high   1   //高电平
#define low     0   //低电平#define led P1     //灯总线控制
sbit led0=P1^0;     //8个led灯,阴极送低电平点亮
sbit led1=P1^1;
sbit led2=P1^2;
sbit led3=P1^3;
sbit led4=P1^4;
sbit led5=P1^5;
sbit led6=P1^6;
sbit led7=P1^7;sbit lcdEN=P3^4;   //液晶通讯使能端en,高脉冲有效
sbit lcdRS=P3^5;   //液晶第4脚,RS,低电平是指令模式,高电平是数据模式
//sbit lcdR/W       //液晶第5脚,低电平是写入模式,因为我们只写不读,所以接地sbit csda=P3^2;      //DAC0832模数转换cs口
sbit adwr=P3^6;    //ADC0804这个同DAC0832
sbit dawr=P3^6;
sbit adrd=P3^7;    //ADC0804
sbit beep=P2^3;     //蜂鸣器void delayms(u16 ms);
void T0_Work();
void startT0(u32 ms,u16 t_multiple);
////#endif
#include "MY51.h"u8   TH0Cout=0 ;      //初值
u8   TL0Cout=0 ;
u16  T0IntCout=0;       //中断计数
u16  timeMultiple=0;     //中断复用时间的倍数
u8   timeMultipleFlag=0; //中断时间复用置位标志void delayms(u16 ms)     //软延时函数
{u16 i,j;for(i=ms;i>0;i--){for(j=113;j>0;j--){}}
}//开启定时器,定时完成后需要手动关闭TR0,否则将循环定时
//参数一是定时的毫秒数,参数二是定时的倍率数(定时复用)
void startT0(u32 ms,u16 t_multiple)      //定时器初始化设定
{   u32   N=11059.2*ms/12;                  //定时器总计数值TH0Cout =(65536-N)/256;               //装入计时值零头计数初值TL0Cout =(65536-N)%256;timeMultiple=t_multiple;TMOD=TMOD | 0x01;                    //设置定时器0的工作方式为1EA =OPEN;          //打开总中断ET0=OPEN;           //打开定时器中断TH0=TH0Cout;          //定时器装入初值TL0=TL0Cout;TR0=START;           //启动定时器
}/*  方法二,此方法用于长时间的定时,以利于减少中断次数,减小误差
void startT0(u32 one_ms,u16 two_multiple)
{   u32         N=11059.2*one_ms/12;       //定时器总计数值TH0Cout =(65536-N%65536)/256;             //装入计时值零头计数初值TL0Cout =(65536-N%65536)%256;T0IntCountAll=(N-1)/65536+1;            //总中断次数T0IntCountAll2=T0IntCountAll*two_multiple;TMOD=TMOD | 0x01;                        //设置定时器0的工作方式为1EA =OPEN;   //打开总中断ET0=OPEN;   //打开定时器中断TH0=TH0Cout;  //定时器装入初值TL0=TL0Cout;TR0=START;   //启动定时器
}*/void T0_times() interrupt 1 //T0定时器中断函数
{TH0=TH0Cout;      TL0=TL0Cout;T0IntCout++;if(T0IntCout==timeMultiple)  //复用定时器{  T0IntCout=0;            //中断次数清零,重新计时timeMultipleFlag=1;}T0_Work();                   //调用工作函数
}
#ifndef   _MYTYPE_H
#define   _MYTYPE_H/typedef float                             f32   ;
typedef double                        d64  ;
typedef float  const                   fc32 ;
typedef double  const               dc64  ;
typedef volatile float                vf32   ;
typedef volatile double             vd64  ;
//typedef volatile float     const   vfc32   ;
//typedef volatile double  const   vdc64  ;
//typedef signed long  s32;
typedef signed short s16;
typedef signed char   s8;typedef signed long  const sc32;  /* Read Only */
typedef signed short const sc16;  /* Read Only */
typedef signed char  const sc8;   /* Read Only */typedef volatile signed long  vs32;
typedef volatile signed short vs16;
typedef volatile signed char  vs8;//typedef volatile signed long  const vsc32;  /* Read Only */
//typedef volatile signed short const vsc16;  /* Read Only */
//typedef volatile signed char  const vsc8;   /* Read Only */typedef unsigned long  u32;
typedef unsigned short u16;
typedef unsigned char  u8;typedef unsigned long  const uc32;  /* Read Only */
typedef unsigned short const uc16;  /* Read Only */
typedef unsigned char  const uc8;   /* Read Only */typedef volatile unsigned long  vu32;
typedef volatile unsigned short vu16;
typedef volatile unsigned char  vu8;//typedef volatile unsigned long  const vuc32;  /* Read Only */
//typedef volatile unsigned short const vuc16;  /* Read Only */
//typedef volatile unsigned char  const vuc8;   /* Read Only */typedef enum {FALSE = 0, TRUE = !FALSE} bool;typedef enum {RESET = 0, SET = !RESET} FlagStatus, ITStatus;typedef enum {DISABLE = 0, ENABLE = !DISABLE} FunctionalState;typedef enum {CLOSE = 0, OPEN = !CLOSE} OPEN_CLOSE;
typedef enum {GND = 0, VCC = !GND} GND_VCC;
typedef enum {NO = 0, YES = !NO} YES_NO;
typedef enum {STOP = 0, START = !STOP} START_STOP;#define U8_MAX     ((u8)255)
#define S8_MAX     ((s8)127)
#define S8_MIN     ((s8)-128)
#define U16_MAX    ((u16)65535u)
#define S16_MAX    ((s16)32767)
#define S16_MIN    ((s16)-32768)
#define U32_MAX    ((u32)4294967295uL)
#define S32_MAX    ((s32)2147483647)
#define S32_MIN    ((s32)-2147483648)#endif
#ifndef _KEYSACN_H
#define _KEYSACN_H
#include <reg52.h>
#include "mytype.h"#define state_keyUp         0       //初始状态,未按键
#define state_keyDown       1       //键被按下
#define state_keyLong       2       //长按
#define state_keyTime       3       //按键计时态#define return_keyUp        0x00    //初始状态
#define return_keyPressed   0x01    //键被按过,普通按键
#define return_keyLong      0x02    //长按
#define return_keyAuto      0x04    //自动连发#define key_down             0      //按下
#define key_up              0xf0    //未按时的key有效位键值
#define key_longTimes       200     //10ms一次,200次即2秒,定义长按的判定时间
#define key_autoTimes       20      //连发时间定义,20*10=200,200毫秒发一次sbit keyS2=P3^4;   //4个独立按键
sbit keyS3=P3^5;
sbit keyS4=P3^6;
sbit keyS5=P3^7;#define KEYS2_VALUE              0xe0             //keyS2 按下
#define KEYS3_VALUE              0xd0              //keyS3 按下
#define KEYS4_VALUE              0xb0              //keyS4 按下
#define KEYS5_VALUE              0x70              //keyS5 按下//void KeyInit(void);        //初始化,io口未复用时可省略此步
static u8 getKey(void);      //获取P口的连接key的io值,其他io位屏蔽为0
u8 read_key(u8* pKeyValue);  //返回按键的各种状态,pKeyValue保存键值#endif
#include "keyScan.h"
#include <reg52.h>/*按键初始化,若io没有复用的话可以省略此步骤
void KeyInit(void)
{ keyS2 = 1 ; keyS3 = 1 ; keyS4 = 1 ; keyS5 = 1 ;//即P3|=0xf0;
}*/static u8 getKey(void)          //获取P3口值
{ if(key_down == keyS2){return KEYS2_VALUE ; }if(key_down == keyS3 ){return KEYS3_VALUE ; }if(key_down == keyS4 ){return KEYS4_VALUE ;}if(key_down == keyS5 ){return KEYS5_VALUE ; }return key_up ;    //0xf0  没有任何按键
}//函数每10ms被调用一次,而我们弹性按键过程时一般都20ms以上
//所以每次按键至少调用本函数2次
u8 read_key(u8* pKeyValue)
{static u8  s_u8keyState=0;        //未按,普通短按,长按,连发等状态static u16 s_u16keyTimeCounts=0;  //在计时状态的计数器static u8  s_u8LastKey = key_up ; //保存按键释放时的P3口数据u8 keyTemp=0;                  //键对应io口的电平s8 key_return=0;            //函数返回值keyTemp=key_up & getKey();  //提取所有的key对应的io口switch(s_u8keyState)           //这里检测到的是先前的状态{case state_keyUp:   //如果先前是初始态,即无动作{if(key_up!=keyTemp) //如果键被按下{s_u8keyState=state_keyDown; //更新键的状态,普通被按下 }}break;case state_keyDown: //如果先前是被按着的{if(key_up!=keyTemp) //如果现在还被按着{s_u8keyState=state_keyTime; //转换到计时态s_u16keyTimeCounts=0;s_u8LastKey = keyTemp;     //保存键值}else{s_u8keyState=state_keyUp; //键没被按着,回初始态,说明是干扰}}break;case state_keyTime:  //如果先前已经转换到计时态(值为3){  //如果真的是手动按键,必然进入本代码块,并且会多次进入if(key_up==keyTemp) //如果未按键{s_u8keyState=state_keyUp; key_return=return_keyPressed;    //返回1,一次完整的普通按键//程序进入这个语句块,说明已经有2次以上10ms的中断,等于已经消抖//那么此时检测到按键被释放,说明是一次普通短按}else  //在计时态,检测到键还被按着{if(++s_u16keyTimeCounts>key_longTimes) //时间达到2秒{s_u8keyState=state_keyLong;  //进入长按状态s_u16keyTimeCounts=0;         //计数器清空,便于进入连发重新计数key_return=return_keyLong;   //返回state_keyLong}//代码中,在2秒内如果我们一直按着key的话,返回值只会是0,不会识别为短按或长按的}}break;case state_keyLong:  //在长按状态检测连发  ,每0.2秒发一次{if(key_up==keyTemp) {s_u8keyState=state_keyUp; }else //按键时间超过2秒时{if(++s_u16keyTimeCounts>key_autoTimes)//10*20=200ms{s_u16keyTimeCounts=0;key_return=return_keyAuto;  //每0.2秒返回值的第2位置位(1<<2)}//连发的时候,肯定也伴随着长按}key_return |= return_keyLong;  //0x02是肯定的,0x04|0x02是可能的}break;default:break;}*pKeyValue = s_u8LastKey ; //返回键值return key_return;
}
#ifndef _51SMG_H_
#define _51SMG_H_#include <reg52.h>
#include "mytype.h"
sbit dula =P2^6;       //段选锁存器控制  控制笔段
sbit wela =P2^7;       //位选锁存器控制  控制位置#define dark 0x11    //在段中,0x11是第17号元素,为0是低电平,数码管不亮
#define dotDark 0xff    //小数点全暗时void displaySMG(u8 one,u8 two,u8 three,u8 four,u8 five,u8 six,u8 dot);  //数码管显示函数#endif
#include "smg.h"
#include "my51.h"u8 code table[]= {          //0~F外加小数点和空输出的数码管编码0x3f , 0x06 , 0x5b , 0x4f , // 0 1 2 30x66 , 0x6d , 0x7d , 0x07 , // 4 5 6 70x7f , 0x6f , 0x77 , 0x7c , // 8 9 A B0x39 , 0x5e , 0x79 , 0x71 , // C D E F0x80 , 0x00 ,0x40           // . 空  负号    空时是第0x11号也就是第17号元素};u8 code dotTable[]={        //小数点位置0xff ,                 //全暗0xfe , 0xfd , 0xfb ,   //1 2 30xf7 , 0xef , 0xdf     //4 5 6
};//数码管显示
void displaySMG(u8 oneWela,u8 twoWela,u8 threeWela,u8 fourWela,u8 fiveWela,u8 sixWela,u8 dot)
{   //控制6位数码管显示函数,不显示的位用参数dark,保留ADC0804的片选信号u8 csadState=0x80&P0;                 //提取最高位,即ADC0804的片选信号u8 tempP0=((csadState==0)?0x7f:0xff); //数码管位选初始信号,阴极全置高电平P0=tempP0;        //0x7f表示数码管不亮,同时ADC0804片选有效wela=1;         //注:wela和dula上电默认为1P0=tempP0;wela=0;P0=0;                //由于数码管是共阴极的,阳极送低电平,灯不亮,防止灯误亮dula=1;P0=0;dula=0;             //段选数据清空并锁定
//oneWela{  //消除叠影,数码管阴极置高电平,并锁存P0=tempP0;wela=1;         P0=tempP0;wela=0;}P0=0;          //低电平送到数码管阳极,避免数码管误亮dula=1;P0=table[oneWela]|((0x01&dot)?0x00:0x80);   //送段数据,叠加小数点的显示dula=0;P0=tempP0;          //送位数据前关闭所有显示,并保持csad信号wela=1;P0=tempP0 & 0xfe;   //0111 1110最高位是AD片选,低6位是数码管位选,低电平有效wela=0;delayms(1);/twoWela{  //消除叠影P0=tempP0;wela=1;         P0=tempP0;wela=0;}P0=0;dula=1;P0=table[twoWela]|((0x02&dot)?0x00:0x80);dula=0;P0=tempP0;wela=1;P0=tempP0 & 0xfd;    //0111 1101wela=0;delayms(1);/threeWela{  //消除叠影P0=tempP0;wela=1;           P0=tempP0;wela=0;}P0=0;dula=1;P0=table[threeWela]|((0x04&dot)?0x00:0x80);dula=0;P0=tempP0;wela=1;P0=tempP0 & 0xfb;    //0111 1011wela=0;delayms(1);/fourWela{  //消除叠影P0=tempP0;wela=1;          P0=tempP0;wela=0;}P0=0;dula=1;P0=table[fourWela]|((0x08&dot)?0x00:0x80);dula=0;P0=tempP0;wela=1;P0=tempP0 & 0xf7;   //0111 0111wela=0;delayms(1);/fiveWela{  //消除叠影P0=tempP0;wela=1;            P0=tempP0;wela=0;}P0=0;dula=1;P0=table[fiveWela]|((0x10&dot)?0x00:0x80);dula=0;P0=tempP0;wela=1;P0=tempP0 & 0xef;      //0110 1111wela=0;delayms(1);/sixWela{  //消除叠影P0=tempP0;wela=1;          P0=tempP0;wela=0;}P0=0;dula=1;P0=table[sixWela]|((0x20&dot)?0x00:0x80);dula=0;P0=tempP0;wela=1;P0=tempP0 & 0xdf;   //0101 1111wela=0;delayms(1);
}
#ifndef        _MYCLOCK_H
#define     _MYCLOCK_H
#include "mytype.h"
#include "my51.h"void clock(void);                    //走时
void changeTimeState(void);         //改变对时状态
void changeTime(bool add_or_sub);   //修改时间,true为增加,false为减少
#endif
#include "myClock.h"u8  changeTimeFlag=0;
s8  shi=22;   //对时
s8  fen=45;
s8  miao=0;
void clock(void)
{if(!changeTimeFlag)   //不在对时状态{miao++;if(miao>59){miao=0;fen++;}if(fen>59){fen=0;shi++;} if(shi>23){shi=0;}}
}void changeTimeState(void)      //在满足条件时改变对时状态,时或分或秒,同时改变指示灯
{changeTimeFlag=(++changeTimeFlag)%4;switch(changeTimeFlag){case 0:{led=0xff;                                  }break;case 1:{led=0xff;led7=0;}break;case 2:{led=0xff;led5=0;}break;case 3:{led=0xff;led3=0;}break;default:break;}
}void changeTime(bool add_or_sub)    //修改时分秒
{if(add_or_sub){switch(changeTimeFlag){case 1:{shi++;if(shi>23){shi=0;}                                      }break;case 2:{fen++;if(fen>59){fen=0;}}break;case 3:{miao++;if(miao>59){miao=0;}}break;default:break;}}else{switch(changeTimeFlag){case 1:{shi--;if(shi<0) {shi=23;}                                       }break;case 2:{fen--;if(fen<0){fen=59;}}break;case 3:{miao--;if(miao<0){miao=59;}}break;default:break;}  }
}

51单片机学习笔记:基于状态机的按键对时程序(短按,长按,连发)相关推荐

  1. 51单片机学习笔记-1简介及点灯

    51单片机学习笔记 文章目录 51单片机学习笔记 1. 51单片机简介 1.1 安装软件 1.2 单片机简介 2. LED灯 2.1点亮一个LED 2.1.1原理分析 2.1.2 创建工程 2.2LE ...

  2. AutoLeaders控制组—51单片机学习笔记

    文章目录 AutoLeaders控制组-51单片机学习笔记 1.1单片机及开发板介绍 单片机介绍 单片机应用领域 STC89C52单片机 内部结构 开发板介绍 2.1点亮一个Led 新建工程 编程 认 ...

  3. 单片机c语言北航,【下载资料】《51单片机学习笔记》北航版

    原标题:[下载资料]<51单片机学习笔记>北航版 如果手机下载有问题,请移步至电脑端,链接:https://forum.mianbaoban.cn/t/topic/36906 内容简介 本 ...

  4. 51单片机学习笔记2 仿真器的使用及STC89Cxx简介

    51单片机学习笔记2 仿真器的使用及STC89Cxx简介) 一.连接步骤 1. 硬件连接 2. 安装软件驱动 3. 检查是否安装成功 二.仿真步骤 1. 打开一个51工程 2. 选择仿真设备 3. S ...

  5. 51单片机学习杂记——基于STC89C52RC

    51单片机学习杂记--基于STC89C52RC 我是看的b站郭天祥老师的课,说实话,我觉得我能力不是很够,所以记得很杂.废物了属于是. 接下来就是正文了 基本的元器件以及字母符号含义: 电容:帮助晶振 ...

  6. [51单片机学习笔记TWO]----蜂鸣器

    蜂鸣器音乐播放实验 首先应该了解一下蜂鸣器音乐播放的原理,在这里我只讲一下电磁式蜂鸣器驱动原理(还有一种是压电式蜂鸣器): 电磁式蜂鸣器驱动原理: 蜂鸣器发声原理是电流通过电磁线圈,使电磁圈产生磁场来 ...

  7. 51单片机学习笔记5 流水灯实现及蜂鸣器控制

    51单片机学习笔记5 流水灯实现及蜂鸣器控制 一.流水灯 1. 硬件电路 2. 代码实现 (1) 点亮一个LED的基本操作 (2) 使用算术左移实现流水灯 (3) 使用库文件左移函数 二.蜂鸣器 1. ...

  8. 51单片机学习笔记1 简介及开发环境

    51单片机学习笔记1 简介及开发环境 一.51单片机 1. STC89C52单片机简介 2. 命名规则 3. 封装 (1)PDIP (2)LQFP (3)PLCC (4)PQFP 二.STC8051结 ...

  9. 【51单片机学习笔记】基于STC11F04E的蜂鸣器音乐播放器

    微型播放器                                     --基于STC11F04E的蜂鸣器控制 青岛科技大学 信息科学技术学院 集成162 Listen C 一.简介 1. ...

最新文章

  1. 淘宝文件系统大文件结构
  2. properties 配置回车_在Ubuntu上部署基于Docker的RSSHub,并配置SSL证书
  3. 第三次学JAVA再学不好就吃翔(part42)--内部类概述
  4. 使用ASP.NET Core 实现Docker的HealthCheck指令
  5. 图说Oracle基础知识
  6. 深入理解Linux内核链表
  7. 【转】Word 2010 取消拼写/语法检查,隐藏红线/绿线
  8. 网络创新激活西部科技,戴尔2013软件定义网络圆桌会谈的启示
  9. hⅰgh怎么读音发音英语_gh的发音规律
  10. 为什么找不到使用rem的网站
  11. Android获得全局进程信息以及进程使用的内存情况
  12. matlab设计单神经元系统框图,单神经元自适应系统
  13. 读 Joseph J. Rotman 之《抽象代数基础教程》
  14. js 实现单击、双击事件
  15. ios苹果越狱教程(奥德赛)
  16. MaxDOS 网刻服务端网刻教程。
  17. 计算机桌面图片打不开显示内存不足,电脑上的windows图片查看器提示内存不足如何解决...
  18. 大表哥有个项目,10W预算,让我顺手做了算了......
  19. 微信小程序代码大于2M的一种解决方法
  20. 青龙面板薅羊毛教程之矿二代每日保底1R

热门文章

  1. GDPR中的三大主体:用户,数据所有者,以及数据传输者
  2. java opencv 提取车牌_opencv-车牌区域提取
  3. 做程序员已经有白头发的进来聊聊
  4. NR PUCCH UCI
  5. 面试官:你说一说MySQL查询慢应该怎么办?
  6. Jetson Nano v4.6.3:安装系统、U盘启动、安装SDK、安装PyTorch GPU、YOLOv5+DeepStream部署
  7. 真骨传导耳机推荐,列举五款不踩雷的骨传导耳机
  8. 2014Esri全球用户大会——亮点系列之产品技术
  9. 腾讯云服务器初始配置并配置python3环境
  10. OpenTSDB查询代码解析