初学51单片机之长短键应用定时炸弹及扩展应用

51单片机RAM区域划分

51单片机的RAM分为两个部分,一块是片内RAM,一块是片外RAM。

data: 片内RAM从 0x00 ~0x7F 寻址范围(0-127) 容量共128B

idata:   片外RAM从 0x00~0xFF 寻址范围(0-255)    容量共256B

pdata:片外RAM从 0x00~0xFF 寻址范围(0-255)  容量共256B

xdata:片外RAM从 0x0000~0xFFFF 寻址范围(0-65535)容量共65536B

从上述的范围可以看出,data是idata的一部分,pdata是xdata的一部分

可以这么定义一个变量啊:unsigned char data a = 0,但事实上我们平时书写的时候是不写data的

因为在Keil默认的设置下,data是可以省略的。

片内RAM的访问速度会比片外的访问速度快,但是一般不用idata 0x80~0XFF这部分范围。因为这块通常用于中断与函数调用的堆栈。所以绝大部分情况下,使用内部RAM的时候,只用data就可以了。

STC89C52共512字节的RAM,分为256字节的片内RAM和256字节的片外RAM。一般情况下使用data区域,如果data不够用了,就用xdata。如果希望程序执行效率尽量高一点,就用pdata关键字来定义。

事实上真正的芯片外扩展很少用到了,虽然它还是叫片外RAM,但实际上它现在也在单片机内部,只是响应速度不太一样而已。

定时炸弹的基本要求

1:利用蜂鸣器鸣叫与点亮LED来表示炸弹爆炸。

2:可以用按键调整定时时间。长按调整按键可以是连续增加或减少定时时间。

3:ESC键清0暂停倒计时,Entel键开始倒计时,到了0秒爆炸。

上代码

#include <reg52.h>

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}
	};
pdata unsigned long  KeyDownTime[4][4]= {
    {0, 0, 0, 0},{0, 0, 0, 0},{0, 0, 0, 0},{0, 0, 0, 0}
	};

	bit enBuzz = 0;           //蜂鸣器使能标记
	bit flag1s = 0;           //1s定时标志
	bit flagStart = 0;        //倒计时启动标志
	unsigned char T0RH = 0;   //T0重载值高字节
	unsigned char T0RL = 0;   //T0重载值低字节
	unsigned char CountDown = 0;  //倒计时计数器
	
	void ConfigTimer0(unsigned int ms);  //定时器0初值设定函数
	void ShowNumber(unsigned long num); //倒计时调整时间,数码管显示函数
	void KeyDriver();
	
	void main()
	{
	   EA = 1;
		 ENLED = 0;
		 ADDR3 = 1;
		 ConfigTimer0(1); //定时1ms
		 ShowNumber(0);  //数码管显示0
		
		while(1)
		{
		   KeyDriver();               //调用按键驱动函数
			if(flagStart && flag1s)    //倒计时启动且1秒定时到达时,处理倒计时
			{
			   flag1s = 0;
				 if(CountDown > 0)      //倒计时未到0时,计时器递减
				 {
				   CountDown--;        //
					 ShowNumber(CountDown); //刷新倒计时数字显示
					 if(CountDown == 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;                     //清零定时器0控制位
	TMOD |= 0x01;                     //选择定时器0的工作模式
	TH0 = T0RH;                       //定时器0高低字节赋值
	TL0 = T0RL; 
	ET0 = 1;                          //定时器0中断使能
	TR0 = 1;                          //使能定时器0
	}
	
	/*将一个无符号长整型的数字显示到数码管伤,num位待显示数字  */
	
	void ShowNumber(unsigned long num)
	{
	  signed char i;
		unsigned char buf[6]; //把长整形数,每个进制位上的数转化成十进制的数共6个存入数组
		for(i = 0; i <6; i++)
		{
		   buf[i] = num %10;
			 num = num / 10;
		}
		for(i = 5;i >=1;i--) //从高位起,遇到0转换为0xff(不显示),遇到非零则退出循环
		{
		  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)       //向上键,倒计时设定值每按一下加1
		 {  
			 if(CountDown < 9999)   //最大计数9999
			 {
			  CountDown++;
				 ShowNumber(CountDown);
			 }
		 }
		 
		 else if (keycode == 0x28) //向下键 倒计时设定值递减
		 {
		   if(CountDown >1)         //最小计时1s
			 {
			   CountDown--;
				 ShowNumber(CountDown);
			 }
		 }
		 
		 else if(keycode == 0x0D)  //回车键 ,启动倒计时
		 {
		   flagStart = 1;
		 }
		 else if(keycode == 0x1B)  //ESC 键 取消倒计时
		 {
		   enBuzz = 0;
			 LedBuff[6] = 0xFF;
			 flagStart = 0;
			 CountDown = 0;
			 ShowNumber(0);
		 }
		 
	}
	

/*按键驱动函数,检测按键动作,调度相应动作函数,需要在主函数中调用    */	
	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++)
		   {
			   if((keybuf[keyout][i] & 0x0F) == 0x00)
				 {//连续4次烧苗值为0,即4x4ms内都是按下状态时,可以认为按键已稳定的按下
				   KeySta[keyout][i] = 0;
					 KeyDownTime[keyout][i] += 4;//按下的持续时间累加
				 }
				 else if((keybuf[keyout][i] & 0x0F) == 0x0F)
				 { //连续4次扫描值为1,即4x4ms内都是弹起状态时,可认为按键已稳定的弹起
				    KeySta[keyout][i] = 1;
					  KeyDownTime[keyout][i] = 0;//按下的持续时间清零
				 }
			 }
			 
			 keyout++;           //输出索引递增
			 keyout &= 0x03;     //索引值逢4归0
			 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;  // 0xF8 = 1111 1000,位选索引值赋值到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();         //关闭蜂鸣器
		KeyScan();         //LED 扫描显示
		if(flagStart)      //按键扫描
		{                  //倒计时启动时处理1秒定时
		  tmr1s++;
			if(tmr1s >= 1000)
			{
			  tmr1s = 0;
				flag1s = 1;
			}
		}
		else
		{
		  tmr1s = 0;    //倒计时未启动时1秒定时器始终归零
		}
		
	}
	
	

笔者的博文是单片机学习笔记:开发板和一些源代码都来自金沙滩工作室的产品,如果对代码中所有语句感兴趣,需要相关的资料(原理图,原代码)可以在该处下载,免费的:青岛金思特电子有限公司

代码主体是来自教材,不过一般笔者都会有些扩展。而且这些代码不是复制粘贴的,是笔者一个字一个字敲出来的。主要是笔者的C语言也是初学水平,哈哈哈。如果有小伙伴也用这套教材学习,有问题在相应的博文下可以留言交流下,毕竟初学者才知道初学者的难处。

前文提到了单片机的RAM区域的划分,编译一下程序。可以看到

这里data = 70.3就是片内RAM,xdata = 144是片外RAM。可以看到data不是一个正整数,是因为定义了三个位变量。因此是70.3

看源代码的数组关键字pdata

如果删除该关键字会如何,看下图

然后发现报错了,data的值变大了,xdata的值变小了。前文提到data的容量范围是128B,如果都要存入片内RAM需要加上关键字idata,看下图

可以看到data范围已经超过了128但是没有报错,是因为该数组用上了关键字idata,不过一般不用这个区域,因此本案函数是用pdata关键字。

然后分析一下程序的工作流程:

思维导图的地址  https://docs.qq.com/s/bktVAiM_bl91s3118HZurW

用的是腾讯文档免费的流程图,不过有图形限制因此分成了两章。

看下结果视频倒计时炸弹_哔哩哔哩_bilibili

可以看到功能都有都正常工作了,当然正常的倒计时炸弹是不会有ESC键的,启动按键肯定也不可能是按一下就触发,如果不小心碰到了那就完犊子了,因此Entel必然需要长按触发。程序需要一点改动。

如果对矩阵按键部分逻辑不清楚的可以看一下笔者之前关于矩阵按键的博文

初学51单片机矩阵按键与消抖_矩阵键盘消抖-CSDN博客

初学51单片机矩阵按键与消抖2_单片机矩阵键盘获取键值如何消抖-CSDN博客

初学51单片机之矩阵按键的应用末篇_矩阵按键能做些什么-CSDN博客

本案矩阵部分有一处变化但是主体和之前是一样的,数码管显示部分也包括在里面。因此不在详细分析。

接上述需要改动的有三处:

1:全局变量声明 bit LongPress = 0;//长按标志置0

2:是KeyDriver()函数里面的变化

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,以准备下一次执行
									if(i == 3 && j == 2)   //注意entel键是3行2列,不是4行3列因为第1行一1列是0,0
									 {
									     LongPress = 1;
									 }
									   	
								}
						 } 
						 else                     // 按键弹起时复位阈值时间
						 {
						   TimeThr[i][j] = 1000;  // 恢复1s的初始阈值时间
						 }
					}
				 	
			}
	
	}

这个位置使能长按标志置1。数组[3][2](4行3列)对应的是Entel键,注意不是[4][3]。因为数组是从[0][0]0行0列开始的,一开始笔者也是[4][3]花了笔者不少时间找问题,一度以为是不是逻辑哪里出错了,结果竟然是这个问题。对于初学者来说真是要注意的问题。逻辑认识上某行某列到程序上要减1。

3:KeyAction()函数里的变化

void KeyAction(unsigned char keycode)
	{
	   if(keycode == 0x26)       //向上键,倒计时设定值每按一下加1
		 {  
			 if(CountDown < 9999)   //最大计数9999
			 {
			  CountDown++;
				 ShowNumber(CountDown);
			 }
		 }
		 
		 else if (keycode == 0x28) //向下键 倒计时设定值递减
		 {
		   if(CountDown >1)         //最小计时1s
			 {
			   CountDown--;
				 ShowNumber(CountDown);
			 }
		 }
		 
		 else if(keycode == 0x0D)  //回车键 ,启动倒计时
		 {
			 if(LongPress)
			   {
				  flagStart = 1;
					LongPress = 0;
				 }
		 }
		 else if(keycode == 0x1B)  //ESC 键 取消倒计时
		 {
		   enBuzz = 0;
			 LedBuff[6] = 0xFF;
			 flagStart = 0;
			 CountDown = 0;
			 ShowNumber(0);
		 }
		 
	}

进入Entel键把长按标志作为判断条件,实现了长按Entel键开始倒计时。

看结果视频:长按触发倒计时_哔哩哔哩_bilibili

可以看到短按无法触发倒计时了,只能长按才能触发倒计时。

在现实使用时,都希望能够较准确的控制长按时间,如果某个按键造成的后果很严重,必然要让长按的时间足够的长,来体现使用者强烈的主观意志。防止后悔,出现勿碰,不小心的说辞。保护开发者与使用者的基本权益。

本案应该如何操作呢:

看下程序

如图如果开关已经准确的按下了,之后每4个中断执行一次KeyDownTime[keyout][i] += 4;语句,而该语句是每执行一次加4,因此可以认为是每次进入中断加1。

KeyDownTime的值在KeyDriver();函数中与预先设定的值1000判断,因此可知:当按住开关,再经过1000次中断(1000ms)后进入长按功能:看下进入函数的后续语句

if(KeyDownTime[i][j] > 0)    //检测执行快速输入
						 {
						    if(KeyDownTime[i][j] >= TimeThr[i][j])
								{                                     //达到阈值时执行一次动作
								    KeyAction(KeyCodeMap[i][j]);      //调用按键动作函数
									  TimeThr[i][j] += 200;             //时间阈值增加200ms,以准备下一次执行
									if(i == 3 && j == 2)   //注意entel键是3行2列,不是4行3列因为第1行一1列是0,0
									 {
									     LongPress = 1;
									 }
									   	
								}
						 } 
						 else                     // 按键弹起时复位阈值时间
						 {
						   TimeThr[i][j] = 1000;  // 恢复1s的初始阈值时间
						 }

可以看到执行了1次按键调用函数,然后把比较值提高了200即1000变成1200。即下次再使能长按功能需要再经过200次中断。

因此这个函数可以这么设计:

设置一个变量cnt :让cnt >= 10,如此进入函数的时间是(200ms*10)2s加上之前的1s则长按该开关的时间判断就变成了3s,而且不影响其他开关的长按时间。

看代码:

if(KeyDownTime[i][j] > 0)    //检测执行快速输入
						 {
						    if(KeyDownTime[i][j] >= TimeThr[i][j])
								{                                     //达到阈值时执行一次动作
								    KeyAction(KeyCodeMap[i][j]);      //调用按键动作函数
									  TimeThr[i][j] += 200;             //时间阈值增加200ms,以准备下一次执行
									  cnt++;
									  if(cnt >= 10)
									   {
                                          cnt = 0;
									    if(i == 3 && j == 2)   //注意entel键是3行2列,不是4行3列因为第1行一1列是0,0
									      {
									       LongPress = 1;
									      }
									   }																	   	
								}
						 } 
						 else                     // 按键弹起时复位阈值时间
						 {
						   TimeThr[i][j] = 1000;  // 恢复1s的初始阈值时间
						 }

结果视频就不上了,笔者这个已经试过了,没有问题的

至此本案倒计时的程序可以算基本完结了,但是以生活经验来说,对于生活中的电子产品,由于空间有限往往一个按键有两种不同的功能,短按的功能可能和长按的功能截然不同。以笔者的Switch游戏机来说:

短按电源键 :如果是黑屏,屏幕就变亮,如果是亮屏就变黑。

长按电源键:如果是黑屏,屏幕就变亮,如果是亮屏就进入关机选择界面。

对此,本案目前的程序需要些许改动才能实现长短键不同功能。对于开关动作可以这么设想,假设按住开关,短按功能你触不触发?如果你触发了长按功能怎么办?如果只使能长按功能,短按功能怎么办?毕竟长短按的功能不一样,如果是一样的可以按照本程序的逻辑来。

笔者前期的博文就有提到,一次开关动作包括两次状态变化:

1:从弹起状态进入按住状态

2:从按住状态回到弹起状态

开关的按键功能可以在状态1实现,也可以在状态2实现。之前的博文里笔者就演示了:按键开关按住加1和弹起加1的现象。因此长短按键的功能就可以分开实现了:

短按:开关动作状态2实现短按功能

长按:开关动作状态1实现长按功能

主要更改部分是KeyACtion()与void KeyDriver()函数

看代码

void KeyDriver()
	{
		 unsigned char i,j,cnt;
	   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 && LongPress == 0) //前态如果是0那么现态是1,开关从按住弹起
							  {
								if( Locksta == 0)
							   KeyAction(KeyCodeMap[i][j]);   //调用按键动作函数
								   Locksta = 0;
							  }
							  backup[i][j] = KeySta[i][j];    //刷新前一次备份值
						}
						
						if(KeyDownTime[i][j] > 0)    //检测执行快速输入
						 {
						    if(KeyDownTime[i][j] >= TimeThr[i][j])
								{   
									   LongPress = 1;                 //长按标志置1
								    KeyAction(KeyCodeMap[i][j]);      //调用按键动作函数
									  TimeThr[i][j] += 200;             //时间阈值增加200ms,以准备下一次执行
									  cnt++;
									  if(cnt >= 10)
									   {
											 cnt = 0;
									    if(i == 3 && j == 2)   //注意entel键是3行2列,不是4行3列因为第1行一1列是0,0
									      {
									       EntelLongPress = 1;//entel长按标志
										Locksta = 1; //按键锁标志,加长键开关弹起进入短键函数后,使按键动作无法使能避免产生错误结果
													
									      }
									   }																	   	
								}
						 } 
						 else                     // 按键弹起时复位阈值时间
						 {
						   TimeThr[i][j] = 1000;  // 恢复1s的初始阈值时间
						 }
					}
				 	
			}
	
	}
void KeyAction(unsigned char keycode)
	{
	   if(keycode == 0x26)       //向上键,倒计时设定值每按一下加1
		 {  
			 if(CountDown < 9999)   //最大计数9999
			 {
				 LongPress = 0;  //长按标志清零
			   CountDown++;
				 ShowNumber(CountDown);
				 
			 }
		 }
		 
		 else if (keycode == 0x28) //向下键 倒计时设定值递减
		 {
		   if(CountDown >1)         //最小计时1s
			 {
				 LongPress = 0;
			   CountDown--;
				 ShowNumber(CountDown);
			 }
		 }
		 
		 else if(keycode == 0x0D)  //回车键 ,启动倒计时
		 {
			  
			 		 if(EntelLongPress |  Locksta == 1)
			   {
				  flagStart = 0;
					EntelLongPress = 0;
					LongPress = 0;
				 }
				 else
				 {
				    flagStart = 1;
			      LongPress = 0;
				 }
		 }
		 else if(keycode == 0x1B)  //ESC 键 取消倒计时
		 {
			 LongPress = 0;
		   enBuzz = 0;
			 LedBuff[6] = 0xFF;
			 flagStart = 0;
			 CountDown = 0;
			 ShowNumber(0);
		 }
		 
	}

该程序与之前的相比引入了2个新的变量,原先的LongPress改成EntelLongPress 

新的变量是 LongPress LockSta, 这两个变量的作用是定义:一般长键状态,按键锁标志,加长键开关弹起进入短键函数后,使按键动作无法使能避免产生错误结果

此函数把按键状态分为3种:

1:普通的短键触发功能

2:普通的长键触发功能

3:Entel键的加长长键触发功能

至此本篇定时炸弹长短键应用扩展结束,看下结果视频:长短键功能循环倒计时_哔哩哔哩_bilibili

可以看到Entel键的长短键切换,上下键的长短键切换正常,没有问题。

然后分享下最近关于中断方面的一些感受:51单片机是串行执行代码的,因此在视觉上的感受同时发生的事情也是1句1句执行的,只是速度很快罢了。如果真要说有什么好像与之并行的。那就是定时器,只要初值化设置好它就一直计时直到溢出停止。期间无论程序是在等待还是在执行什么都不会影响定时器计时。

程序执行如图:

     一般来说程序是在主函数与中断之间互相穿插执行的。对于本案来说只有定时器0中断,因此它是在主函数与定时器0中断之间循环执行的。一般主函数循环执行某个函数,会需求中断函数提供相应参数在主函数中执行。因此就需要设置的中断时间T>(t+t0)。

     中断的重载值一般都会在中断函数最前面执行,然后定时器就开始计时了。如果中断内部执行时间+主函数执行时间>  中断设置的间隔时间,那么就无法完整执行完一次主函数,又进入了中断函数,就可能让前一次中断传递的参数没有起作用。那么就会产生一些不好的结果。

如果笔者在中断函数里加一个时间延迟函数while(100--)近似1ms的延时函数,那么该程序就会无法正常工作,因为它一跳出中断,主函数没有执行,中断响应又到了。那么按键功能就无法正确执行了。

看视频:中断时间过长_哔哩哔哩_bilibili

可以看到主函数初始化0显示花的时间都变长了,并且按键不起作用。

至此博文到此结束。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/751109.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ADC位数、增益调制与参考电压

位数&#xff1a;12bit、10bit、8bit 一般就是对应的ADC值分别为&#xff1a;4095、1023、255&#xff0c;也就选用对应位数时ADC的最大值。 增益的作用 增益设置用于放大或缩小输入信号&#xff0c;使其适配到ADC的输入范围。增益设置可以通过配置SAADC的通道配置寄存器来实…

java基于ssm+jsp 毕业生就业信息管理系统

1管理员功能模块 管理员输入个人的用户名、密码、角色登录系统&#xff0c;这时候系统的数据库就会在进行查找相关的信息&#xff0c;如果我们输入的用户名、密码不正确&#xff0c;数据库就会提示出错误的信息提示&#xff0c;同时会提示管理员重新输入自己的用户名、密码&am…

高通安卓12-安卓系统定制1

1.改变系统默认语言 从build/make/target/product/full_base.mk 2.修改开机图片 安卓原版操作方式 找到生成脚本&#xff1a;device\qcom\common\display\logo\logo_gen.py 其中readme.txt有操作说明 命令&#xff1a; sudo apt-get install python-imaging python ./logo_…

[AIGC] Doris:一款高效的MPP数据仓库引擎

在大数据处理的领域中&#xff0c;Apache Doris&#xff08;原百度 Palo&#xff09;是一个高效的MPP&#xff08;大规模并行处理&#xff09;数据仓库&#xff0c;最初由百度开发&#xff0c;现在已经成为Apache的孵化项目。 (图片取自百度) – 文章目录 1. Doris的基础知识…

RocketMQ:日常开发中有哪些使用MQ的场景

什么是消息队列&#xff1f; 消息队列是一种通信方法&#xff0c;允许应用程序通过发送和接收消息来互相通信。这些消息/任务/指令存储在一个中间介质中&#xff08;即队列&#xff09;&#xff0c;并由生产者发送&#xff0c;消费者接收。 使用场景 场景一&#xff1a;任务…

输出100以内的质数

质数&#xff1a;只能被1和自身整除的数 let count; for(let i2; i<100; i){for(let j1; j<i; j){if(i % j 0){// 只要能被整除&#xff0c;count就加1count;}} if(count 2) {// 从1到自身被整除完之后&#xff0c;如果count只有两次&#xff0c;则说明i为质数co…

【技巧】如何检查多个GPU之间是否支持P2P通信

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 需要用到cuda_samples&#xff1a;GitHub - NVIDIA/cuda-samples 该工具的详细解释可以看这个&#xff1a; 【知识】详细介绍 CUDA Samples 示例工程…

[数据集][目标检测]电力场景下电柜箱门把手检测数据集VOC+YOLO格式1167张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;1167 标注数量(xml文件个数)&#xff1a;1167 标注数量(txt文件个数)&#xff1a;1167 标注…

Leetcode Hot100之链表

1.相交链表 解题思路 快慢指针&#xff1a;分别求出两个链表的长度n1和n2&#xff0c;在长度较长的那个链表上&#xff0c;快指针先走n2 - n1&#xff0c;慢指针再出发&#xff0c;最后能相遇则链表相交 时间复杂度O(mn)&#xff0c;空间复杂度O(1)代码# Definition for singl…

白敬亭章若楠甜度报表的难哄大师

#白敬亭章若楠&#xff0c;甜度爆表的难哄大师#&#x1f389;&#x1f389;&#x1f389;各位小伙伴们&#xff0c;你们还记得那个让我们心跳加速、嘴角上扬的CP组合吗&#xff1f;没错&#xff0c;就是白敬亭和章若楠&#xff01;他们可是凭借一部新剧&#xff0c;再次让我们感…

520. 检测大写字母

题目 我们定义&#xff0c;在以下情况时&#xff0c;单词的大写用法是正确的&#xff1a; 全部字母都是大写&#xff0c;比如 “USA” 。单词中所有字母都不是大写&#xff0c;比如 “leetcode” 。如果单词不只含有一个字母&#xff0c;只有首字母大写&#xff0c;比如 “Go…

World of Warcraft [CLASSIC] plugin lua

World of Warcraft [CLASSIC] plugin lua 魔兽世界lua脚本插件 World of Warcraft API - Wowpedia - Your wiki guide to the World of Warcraft D:\World of Warcraft\_classic_\Interface\AddOns zwf.lua function CountdownFunc()CountdownFrame CreateFrame("Fram…

常见的字符串函数(包含头文件string.h)和字符函数(2)

八. strstr函数 1.strstr的定义 char *strstr( const char *str1, const char *str2 ); ->1. strstr查找子串(str2)在字符串(str2)中第一次出现的位置&#xff0c;记录并返回该位置的指针&#xff0c;如果找不到&#xff0c;则返回NULL ->2. str1&#xff1a;查找字符…

不用再找了,这是大模型实践最全的总结

随着ChatGPT的迅速出圈&#xff0c;加速了大模型时代的变革。对于以Transformer、MOE结构为代表的大模型来说&#xff0c;传统的单机单卡训练模式肯定不能满足上千&#xff08;万&#xff09;亿级参数的模型训练&#xff0c;这时候我们就需要解决内存墙和通信墙等一系列问题&am…

Mysql索引的实现原理,B+Tree,WAL

InnoDB 引擎&#xff0c;每一个数据表有两个文件 .frm和.ibd&#xff0c;分别为表结构&#xff0c;数据和索引&#xff0c;数据挂在主索引的叶子节点上&#xff0c;此主索引称为聚簇索引。 MyISAM 引擎&#xff0c;每一个数据表有三个文件.frm和.MYI和.MYD&#xff0c;分别为表…

测试报告-HTMLTestRunner报告优化(中/英文)

引用原始的HTMLTestRunner.py文件生成的测试报告在美观性不是很好&#xff0c;使用在此文件基础上优化后的HTMLTestReportCN.py文件(生成的报告为中文)、HTMLTestReportEN.py文件(生成的报告为英文)。 1 首先新建一个Python项目 例如&#xff1a;testHtmlReport 创建case包&am…

指纹浏览器是什么?跨境多账号安全如何保证?

随着电子商务的蓬勃发展&#xff0c;越来越多的商家选择开设多店来扩大经营规模。然而多店运营也带来了一系列的挑战&#xff0c;其中之一就是账号安全。 1. 了解反检测浏览器和代理服务器 在我们开始讨论如何有效地使用反检测浏览器之前&#xff0c;我们首先需要了解这两个工…

如何用亚马逊合作伙伴网络快速上线跨境电商

目前跨境电商已成为行业发展主流&#xff0c;如何快速、低成本打造品牌海外独立站和智能客服营销中心、构建全链路跨境电商体系是出海电商商家都会遇到的难题。亚马逊云科技凭借与亚马逊电商平台易于集成的先天优势成为首选的电商解决方案平台。本文介绍了如何用亚马逊云科技平…

SpringCloud分布式微服务链路追踪方案:Skywalking

一、引言 随着微服务架构的广泛应用&#xff0c;系统的复杂性也随之增加。在这种复杂的系统中&#xff0c;应用通常由多个相互独立的服务组成&#xff0c;每个服务可能分布在不同的主机上。微服务架构虽然提高了系统的灵活性和可扩展性&#xff0c;但也带来了新的挑战&#xf…

深度学习论文撰写实验对比分析时复现其它论文方法的问题

&#x1f4aa; 专业从事且热爱图像处理&#xff0c;图像处理专栏更新如下&#x1f447;&#xff1a; &#x1f4dd;《图像去噪》 &#x1f4dd;《超分辨率重建》 &#x1f4dd;《语义分割》 &#x1f4dd;《风格迁移》 &#x1f4dd;《目标检测》 &#x1f4dd;《暗光增强》 &a…