欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-06-01 14:29:16
...

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

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

 

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

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

 

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

小于2秒视为短按,

大于2秒视为长按,

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

 

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

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


51单片机学习笔记:基于状态机的按键对时程序(短按,长按,连发)
            
    
    博客分类: 51单片机 状态机电子钟 
 

 

#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的工作方式为1
	
	EA =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的工作方式为1
	
	EA =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 3
	0x66 , 0x6d , 0x7d , 0x07 , // 4 5 6 7
	0x7f , 0x6f , 0x77 , 0x7c , // 8 9 A B
	0x39 , 0x5e , 0x79 , 0x71 , // C D E F
	0x80 , 0x00 ,0x40           // . 空  负号    空时是第0x11号也就是第17号元素
 };

u8 code dotTable[]={		   //小数点位置
    0xff ,                 //全暗
	0xfe , 0xfd , 0xfb ,   //1 2 3
	0xf7 , 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上电默认为1
    P0=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 1101
    wela=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 1011
    wela=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 0111
    wela=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 1111
    wela=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 1111
    wela=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单片机学习笔记:基于状态机的按键对时程序(短按,长按,连发)
            
    
    博客分类: 51单片机 状态机电子钟 
  • 大小: 14.9 KB
相关标签: 状态机 电子钟