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

news2024/12/23 6:25:33

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 >= 11,如此进入函数的时间是(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 >= 11)
									   {
                                          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.coloradmin.cn/o/1873051.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

总结一下Linux、Windows、Ubuntu、Debian、CentOS等到底是啥?及它们的区别是什么

小朋友你总是有很多问好 你是否跟我一样&#xff0c;不是计算机科班出身&#xff0c;很多东西都是拿着在用&#xff0c;并不知道为什么&#xff0c;或者对于它们的概念也是稀里糊涂的&#xff0c;比如今天说的这个。先简单描述下&#xff0c;我先前的疑问&#xff1a; Linux是…

爬取电商商品详情数据的经验分享(数据已封装API可调用)

一、引言 随着电子商务的蓬勃发展&#xff0c;商品详情数据成为了商家、数据分析师和研究者们关注的焦点。这些数据不仅可以帮助商家了解市场趋势、优化产品策略&#xff0c;还能为研究者提供丰富的数据源&#xff0c;以支持各种学术研究。然而&#xff0c;获取这些数据的难度…

鸿蒙开发设备管理:【@ohos.brightness (屏幕亮度)】

屏幕亮度 该模块提供屏幕亮度的设置接口。 说明&#xff1a; 本模块首批接口从API version 7开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。 导入模块 import brightness from ohos.brightness;brightness.setValue setValue(value: number):…

如何快捷批量处理图片?图片批量改大小、格式、尺寸的方法

怎么把图片批量修改成同一尺寸呢&#xff1f;图片在日常工作和生活中有很多的用途&#xff0c;每天都会需要使用不同类型的图片来获取我们需要的内容。在使用图片的时候&#xff0c;经常会遇到比较常见的几个限制问题&#xff0c;比如图片大小、图片尺寸、图片格式等&#xff0…

【深度学习】图形模型基础(1):使用潜在变量模型进行数据分析的box循环

1.绪论 探索数据背后的隐藏规律&#xff0c;这不仅是数据分析的艺术&#xff0c;更是概率模型展现其威力的舞台。在这一过程中&#xff0c;潜在变量模型尤为关键&#xff0c;它成为了数据驱动问题解决的核心引擎。潜在变量模型的基本理念在于&#xff0c;那些看似复杂、杂乱无…

U-Net for text-to-image

1. Unet for text-to-image 笔记来源&#xff1a; 1.hkproj/pytorch-stable-diffusion 2.understanding u-net a comprehensive tutorial 3.Deep Dive into Self-Attention by Hand 4.Towards Understanding Cross and Self-Attention in Stable Diffusion for Text-Guided Im…

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 英文单词联想(100分) - 三语言AC题解(Python/Java/Cpp)

&#x1f36d; 大家好这里是清隆学长 &#xff0c;一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&#x1f497; &#x1f…

使用uniapp编写微信小程序

使用uniapp编写微信小程序 文章目录 使用uniapp编写微信小程序前言一、项目搭建1.1 创建项目方式1.1.1 HBuilderX工具创建1.1.2 命令行下载1.1.3 直接Gitee下载 1.2 项目文件解构1.2.1 安装依赖1.2.2 项目启动1.2.3 文件结构释义 1.2 引入uni-ui介绍 二、拓展2.1 uni-app使用uc…

CVPR 2024最佳论文分享:生成图像动力学

CVPR 2024最佳论文分享&#xff1a;生成图像动力学 CVPR&#xff08;Conference on Computer Vision and Pattern Recognition&#xff09;是计算机视觉领域最有影响力的会议之一&#xff0c;主要方向包括图像和视频处理、目标检测与识别、三维视觉等。近期&#xff0c;CVPR 2…

盘点7款适合团队使用的知识库工具

作为一名技术爱好者和企业管理者&#xff0c;我深知知识库工具在日常工作中的重要性。 无论是个人笔记管理还是企业知识共享&#xff0c;知识库工具都能极大地提升我们的工作效率和信息管理水平。 根据麦肯锡全球研究院报告显示&#xff0c;使用知识库工具可以帮助个人或者企…

JavaWeb-day28_HTML

今日内容 零、 复习昨日 一、HTML 零、 复习昨日 一、Web开发 前端三大件 HTML ,页面展现CSS , 样式JS (JavaScript) , 动起来 二、HTML 2.1 HTML概念 ​ 网页&#xff0c;是网站中的一个页面&#xff0c;通常是网页是构成网站的基本元素&#xff0c;是承载各种网站应用的平台…

普乐蛙景区9d电影体验馆商场影院娱乐设备旋转飞行影院

今天与大家聊聊VR娱乐新潮流&#xff0c;我们普乐蛙的新品——旋转飞行影院&#xff01;裸眼7D环幕影院&#xff0c;话不多说上产品&#xff01;我们通过亲身体验来给大家讲讲这款高性价比新品的亮点。 想象一下走上电动伸缩梯&#xff0c;坐进动感舱&#xff0c;舱门缓缓合上&…

RuoYi_Cloud本地搭建

目录 1.先进入若依官网下载源码 2.在git链接在idea本地打开 3.建立数据库 &#xff08;1&#xff09;创建一个ruoyi_cloud数据库&#xff0c;设定好账号密码 &#xff08;2&#xff09;建表 4.配置nacos &#xff08;1&#xff09;nacos官网下载2.0.x以上的版本 &#…

Java常量、变量、成员内部类

文章目录 1.常量2.变量3.成员内部类4.变动 1.常量 实例常量&#xff1a;只用final修饰&#xff0c;是某个具体类的实例 静态常量&#xff1a;finalstatic修饰&#xff0c;属于类&#xff0c;所有实例共享同一个类常量 2.变量 实例变量(成员变量)&#xff1a;定义在类内部但在…

上海App开发测试需要注意的内容

在上海app开发中&#xff0c;测试发挥着至关重要的作用。及时、专业的对app进行测试&#xff0c;能够快速发现app存在的漏洞与问题&#xff0c;从而及时进行修正&#xff0c;确保app的顺利上线与发布。那么&#xff0c;在上海app开发测试的过程中&#xff0c;需要注意哪些内容呢…

1.驱动程序框架

驱动是用来控制和操作硬件的软件。 在linux下&#xff0c;一切皆文件。当我们write一个文件时&#xff0c;内核通过文件的file_operations结构体(include/linux/fs.h)来找到对应的驱动函数&#xff0c;最终调用的是存储介质(ssd&#xff0c;硬盘等)驱动提供的write函数(这中间…

米联客FDMA驱动OV5640摄像头—基于野火Zynq7020开发板

使用米联客的ddr3缓存方案 FDMA驱动OV5640摄像头在RGB888屏幕上显示。 总体BLOCK DESIGN框架图 RTC框架图 FDMA设置 FDMA控制器设置 帧选择IP设置 IP核封装及代码在工程文件中 参考 FDMA3.1数据缓存方案全网最细讲解&#xff0c;自创升级版&#xff0c;提供3套视频和音频缓存…

python案例-自动识别图片数字并进行填充,小键盘数字键练习工具轻松达到最高评级!ddddocr+pyauotgui

🌈所属专栏:【python】✨作者主页: Mr.Zwq✔️个人简介:一个正在努力学技术的Python领域创作者,擅长爬虫,逆向,全栈方向,专注基础和实战分享,欢迎咨询!您的点赞、关注、收藏、评论,是对我最大的激励和支持!!!🤩🥰😍 目录 前言 测试工具界面 代码完成思…

VMware Workstation环境下DNS的安装配置,并使用ubuntu来测试

需求说明: 某企业信息中心计划使用IP地址17216.11.0用于虚拟网络测试,注册域名为xyz.net.cn.并将172.16.11.2作为主域名的服务器(DNS服务器)的IP地址,将172.16.11.3分配给虚拟网络测试的DHCP服务器,将172.16.11.4分配给虚拟网络测试的web服务器,将172.16.11.5分配给FTP服务器…

python水仙花数 青少年编程电子学会python编程等级考试三级真题解析2022年3月

python水仙花数 2022年3月 python编程等级考试级编程题 一、题目要求 1、编程实现 明明请你帮忙寻找100-999之间的所有"水仙花数”,并统计个数。"水仙花数"是指一个三位数各位数字的立方和等于该数本身,例如:1531*1*15*5*53*3*3。要求输出结果如下所示: 153…