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

阅读更多

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

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

 

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

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

 

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

小于2秒视为短按,

大于2秒视为长按,

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

 

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

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


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

 

#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 
#include 
#include 
#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 
#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 

/*按键初始化,若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 
#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;
	    }	
	}
}

 

  • 7.1基于状态机的电子钟.rar (74.2 KB)
  • 下载次数: 99
  • 51单片机学习笔记:基于状态机的按键对时程序(短按,长按,连发)_第2张图片
  • 大小: 14.9 KB
  • 查看图片附件

你可能感兴趣的:(状态机,电子钟)