初学51单片机之简易电子密码锁及PWM应用扩展

         前一篇博文提到了定时炸弹。从实用角度来说,这么危险的东西如果流出去将是一个很大的风险。因此考虑需要做个电子密码锁。

       由于笔者没有看过正规的关于密码锁的文档,做这个是笔者的一时兴起,这个密码锁的结构可能充满着野路子。

  密码锁基本功能:

1:以笔者博客名的后4位2024做为密码

2:在没有输入正确密码的时候,定时炸弹的控制按键功能不会被启用。

3:密码输入过程没有提示,因此只有知道密码的人才能方便使用。

  本案密码锁的功能是在上篇博文的代码上扩展的。

#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 code PasswordMap[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;        //倒计时启动标志
	bit EntelLongPress = 0;   //Entel长按标志
	bit LongPress = 0;        //长按标志
	bit Locksta = 0;          //按键转换状态防出错标志
	bit PasswordLock = 0;     //使能定时器键盘标志
	bit KeyLock = 1;          //使能密码键盘标志
	unsigned int  backword = 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 Password(unsigned int Num);
	
	void main()
	{
	   EA = 1;
		 ENLED = 0;
		 ADDR3 = 1;
		 ConfigTimer0(1); //定时1ms
		 ShowNumber(0);  //数码管显示0
		
		while(1)
		{
		  KeyDriver();               //调用按键驱动函数
			Password(2024);
			 
			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]];
		}
	}
	/* 按键动作函数,根据密码锁键码执行相应的操作,passcode 为按键键码 */
	void PasswordAction(unsigned char passcode)
	{  
		static unsigned char cnt = 0;
		static unsigned int buf[4] = {0,0,0,0};
		 
	  if(KeyLock == 1) //为1可以输入密码,为0密码输入屏蔽
		{
		  
			if(passcode >= 0x30 && passcode <= 0x39 | passcode == 0x1B)
			{ //确定输入的是数字键
			   cnt++;
				 switch(cnt)
			   {
			    case 1: buf[0] = (passcode - 0x30)*1000 ; break;
				  case 2: buf[1] = (passcode - 0x30)*100 ;  break;
				  case 3: buf[2] = (passcode - 0x30)*10 ;   break;
				  case 4: buf[3] = (passcode - 0x30)*1 ;    break;
				 default: break;
			   }
			}
			
			  cnt &= 0x03;
			 
			 if(cnt == 0)
			 {
			    backword = (buf[0]+buf[1]+buf[2]+buf[3]);
				  buf[0] = 0;
				  buf[1] = 0;
				  buf[2] = 0;
				  buf[3] = 0;
			 }
			 if(passcode == 0x1B) //初始化键
			  {
			    buf[0] = 0;
				  buf[1] = 0;
				  buf[2] = 0;
				  buf[3] = 0;
					cnt = 0;
			  }
		}
	}
	
	/* 密码设置函数 */
	void Password(unsigned int Num)
	{
	 if(Num == backword)
		  PasswordLock = 1;
	}
	
	
	/* 按键动作函数,根据键码执行相应的操作,keycode 为按键键码 */
	
	void KeyAction(unsigned char keycode)
	{
		if(PasswordLock == 1) //为1使能定时操作,为0屏蔽按键操作
		 {
			  KeyLock = 0;
		    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);
		    }
		 
	   }
	}
	  
	

/*按键驱动函数,检测按键动作,调度相应动作函数,需要在主函数中调用    */	
	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]);   //调用按键动作函数
								 PasswordAction(KeyCodeMap[i][j]); //调用密码锁按键动作
								
								   Locksta = 0;
							  }
							  backup[i][j] = KeySta[i][j];    //刷新前一次备份值
                              cnt = 0;
						}
						
						if(KeyDownTime[i][j] > 0)    //检测执行快速输入
						 {
						    if(KeyDownTime[i][j] >= TimeThr[i][j])
								{   
									  LongPress = 1;                   //达到阈值时执行一次动作
								    KeyAction(KeyCodeMap[i][j]);      //调用按键动作函数
									  PasswordAction(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
									      {
									       EntelLongPress = 1;
												 Locksta = 1;  //按键锁标志防止弹起进入短按函数
													
									      }
									   }																	   	
								}
						 } 
						 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秒定时器始终归零
		}
		
	}
	
	

看一下结果视频:带有密码锁的电子定时炸弹_哔哩哔哩_bilibili

与前一篇博文的区别:重新加入了两个函数,如果对程序阅读有困难可以阅读下笔者前一篇博文

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

void PasswordAction(unsigned char passcode) :密码输入处理函数

void PasswordAction(unsigned char passcode)
	{  
		static unsigned char cnt = 0;
		static unsigned int buf[4] = {0,0,0,0};
		 
	  if(KeyLock == 1) //为1可以输入密码,为0密码输入屏蔽
		{
		  
			if(passcode >= 0x30 && passcode <= 0x39 | passcode == 0x1B)
			{ //确定输入的是数字键
			   cnt++;
				 switch(cnt)
			   {
			    case 1: buf[0] = (passcode - 0x30)*1000 ; break;
				  case 2: buf[1] = (passcode - 0x30)*100 ;  break;
				  case 3: buf[2] = (passcode - 0x30)*10 ;   break;
				  case 4: buf[3] = (passcode - 0x30)*1 ;    break;
				 default: break;
			   }
			}
			
			  cnt &= 0x03; //逢4归0
			 
			 if(cnt == 0)
			 {
			    backword = (buf[0]+buf[1]+buf[2]+buf[3]);
				  buf[0] = 0;
				  buf[1] = 0;
				  buf[2] = 0;
				  buf[3] = 0;
			 }
			 if(passcode == 0x1B) //初始化键
			  {
			    buf[0] = 0;
				  buf[1] = 0;
				  buf[2] = 0;
				  buf[3] = 0;
					cnt = 0;
			  }
		}
	}

void Password(unsigned int Num):密码设置函数,密码的更换会非常的方便。

	void Password(unsigned int Num)
	{
	 if(Num == backword)
		  PasswordLock = 1;
	}
	

前文提到预设密码是2024,如果要更改密码只需要在主函数中把2024改成你需要的4位数字。比如:

Password(2024);改成比如Password(6666);那么密码就会变成4个6。如果该处输入一个5位数的值那么密码锁将永远无法打开。

该密码锁的特点:

1:一旦输入正确的密码,密码锁功能将立刻解除,启动定时炸弹按键功能。只有重启才能再次进入密码锁功能。

2:初设密码用的都是短键,长键输入,会连续触发按键,导致你输入的数字出错。当然屏蔽掉相  关语句就不会有这种担忧,长短键输入都没问题。

删掉该句密码锁就没有长键功能了。如果密码锁保护的东西特别重要,即使他人拿到密码都无法打开密码锁,就可以采用长短键交替使能的方式。比如只有输入密码按键的方式是:短键——长键——短键——长键,才能正确打开密码锁,相当于锁上加锁。那么安全系数会更高,本案这边就不扩展了。

2:关于功能扩展方面,稍加改动一下PasswordAction(unsigned char passcode)就能够加长密码的位数,初设是4个。改动一下6个8个也是没问题的。

3:如果密码锁还需要警报功能,则可以在输入错误密码的,让蜂鸣器鸣叫。那么就有报警功能了。

4:当然本案是定时炸弹的密码锁,输错密码完全可以使炸弹进入倒计时,危险东西不要随便动手动脚。

长短键配合密码锁

#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;        //倒计时启动标志
	bit EntelLongPress = 0;   //Entel长按标志
	bit LongPress = 0;        //长按标志
	bit Locksta = 0;          //按键转换状态防出错标志
	bit PasswordLock = 0;     //使能定时器键盘标志
	bit KeyLock = 1;          //使能密码键盘标志
	bit PressMark = 0;        //长按标记
	
	
	unsigned char PressStyle = 0;//按键方式
	unsigned int  backword = 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 Password(unsigned int Num);
	
	void main()
	{
	   EA = 1;
		 ENLED = 0;
		 ADDR3 = 1;
		 ConfigTimer0(2); //定时1ms
		 ShowNumber(0);  //数码管显示0
		
		while(1)
		{
		  KeyDriver();               //调用按键驱动函数
			Password(2024);
			 
			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]];
		}
	}
	/* 按键动作函数,根据密码锁键码执行相应的操作,passcode 为按键键码 */
	void PasswordAction(unsigned char passcode)
	{  
		static unsigned char cnt = 0;
		static unsigned int buf[4] = {0,0,0,0};
		static unsigned i = 0xFF;
		 
	  if(KeyLock == 1) //为1可以输入密码,为0密码输入屏蔽
		{
		   ShowNumber(passcode - 0x30);
			if(passcode >= 0x30 && passcode <= 0x39 | passcode == 0x1B)
			{ //确定输入的是数字键
			   cnt++;
		
				 switch(cnt)
			   {
			    case 1: buf[0] = (passcode - 0x30)*1000 ; i = i << 1 | PressMark;  break;
				  case 2: buf[1] = (passcode - 0x30)*100 ;  i = i << 1 | PressMark;  break;
				  case 3: buf[2] = (passcode - 0x30)*10 ;   i = i << 1 | PressMark;  break;
				  case 4: buf[3] = (passcode - 0x30)*1 ;    i = i << 1 | PressMark;  break;
				 default: break;
			   }
			}
			
			  cnt &= 0x03;
			 
			 if(cnt == 0)
			 {
				  PressStyle = i & 0x0F;
			    backword = (buf[0]+buf[1]+buf[2]+buf[3]);
				  buf[0] = 0;
				  buf[1] = 0;
				  buf[2] = 0;
				  buf[3] = 0;
				  i = 0xFF;
			 }
			 if(passcode == 0x1B) //初始化键
			  {
			    buf[0] = 0;
				  buf[1] = 0;
				  buf[2] = 0;
				  buf[3] = 0;
					cnt = 0;
					i = 0xFF;
			  }
		}
	}
	
	/* 密码设置函数 */
	void Password(unsigned int Num)
	{
	 if(Num == backword && PressStyle == 0xA)//1010
		  PasswordLock = 1;
	  
	 
	}
	
	
	/* 按键动作函数,根据键码执行相应的操作,keycode 为按键键码 */
	
	void KeyAction(unsigned char keycode)
	{
		if(PasswordLock == 1) //为1使能定时操作,为0屏蔽按键操作
		 {
			  KeyLock = 0;
		    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);
		    }
		 
	   }
	}
	  
	

/*按键驱动函数,检测按键动作,调度相应动作函数,需要在主函数中调用    */	
	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] = {  //快速输入执行的时间阈值
		   					 
			 {500,500,500,500},
			 {500,500,500,500},
			 {500,500,500,500},
			 {500,500,500,500}
			 
		  };
		 for(i = 0; i<4; i++)                  //循环扫描4*4的矩阵按键
			{
			  for(j = 0; j<4; j++)
				  {
					  if(backup[i][j] != KeySta[i][j]) //按键动作检查
						{	
							
							 if(PasswordLock)
							  {
							     if(backup[i][j] == 0 && LongPress == 0  ) //前态如果是0那么现态是1,开关从按住弹起
							    {
								      if( Locksta == 0)
							           KeyAction(KeyCodeMap[i][j]);   //调用按键动作函数
									    Locksta = 0;
							    }
									if(backup[i][j] == 0 && LongPress == 1  ) //前态如果是0那么现态是1,开关从按住弹起
									{
									   LongPress = 0;
									}
							  }
							
						    if(KeyLock == 1)//锁住密码锁
							   {
							     	if(backup[i][j] == 0 && PressMark == 0) //前态如果是0那么现态是1,开关从按住弹起
							       {		       
											 PasswordAction(KeyCodeMap[i][j]); //调用密码锁按键动作
							       }
										if(backup[i][j] == 0 && PressMark == 1)
										{
										   PressMark = 0;   
											
										}
							   } 
							 
							  backup[i][j] = KeySta[i][j];    //刷新前一次备份值
						  	cnt = 0;
								
						}
						
						if(KeyDownTime[i][j] > 0)    //检测执行快速输入
						 {
						    if(KeyDownTime[i][j] >= TimeThr[i][j])
								{   
									if(KeyLock ==1) //密码锁键盘使能
									 {																	 
									    if( PressMark == 0) 
									    {   							 	 
									        PressMark = 1;
												 
										       PasswordAction(KeyCodeMap[i][j]); //调用密码锁按键动作 										  
									     }
											TimeThr[i][j] += 100;             //时间阈值增加200ms,以准备下一次执行
										 
									 }
									
									  								
							if(PasswordLock == 1) 
									 {										 									 
										    LongPress = 1;                   //达到阈值时执行一次动作
								        KeyAction(KeyCodeMap[i][j]);      //调用按键动作函数									     
								        TimeThr[i][j] += 100;             //时间阈值增加200ms,以准备下一次执行
									      cnt++;
										 
									    if(cnt >= 11) 
									    {
											 cnt = 0;
									    if(i == 3 && j == 2)   //注意entel键是3行2列,不是4行3列因为第1行一1列是0,0
									       {
									        EntelLongPress = 1;
												  Locksta = 1;  //按键锁标志防止弹起进入短按函数
													
									       }
									     }		
									  }  
																									   	
								}
						 } 
					 else                     // 按键弹起时复位阈值时间
						 {
						   TimeThr[i][j] = 500;  // 恢复1s的初始阈值时间
						                         //矩阵函数cnt = 0复位语句语句不能放在该处
							                       // 每4次中断才能扫描到一次对应按住的按键
						 }                       //其他时间一直进入的都是else函数,把它的比较阈值赋值为500,因此cnt=0 不能放在这个位置。
					}
				 	
			}
	
	}
	
	/*按键扫描函数 ,需要在定时中断中调用  */
	
	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 >= 500)
			{
			  tmr1s = 0;
				flag1s = 1;
			}
		}
		else
		{
		  tmr1s = 0;    //倒计时未启动时1秒定时器始终归零
		}
		
	}
	
	

笔者后续还是写出了长短键的扩展代码:没有太大的区别整体上还是在相关的函数里面改动一下。

该函数的中断是2ms,原先的是1ms。所以现在状态判断时间是32ms,原先的是16ms。其它的没有太大的改动。现在的密码是2024 长短键的顺序是:长短长短 只有如此才能打开密码

看结果视频:长短键配合密码锁_哔哩哔哩_bilibili

如果要定制长短键的:如图

0xA=1010  1代表长0代表短。所示的是长短长短。如果你需要其它方式,比如长长短短的按键方式,那么只需赋值 PressStyle = 0xC; 0xC=1100;该处不能写成2进制的方式,因为keil的C编译器不支持2进制,用汇编的可以。

在前面的博文中关于PWM部分,笔者分享了单个呼吸灯的程序用法。

这次分享一下流水灯的PWM控制,流水灯在从一个LED点亮到另一个LED点亮的时候使它的PWM随之变化。视觉效果上不是很明显,主要是功能展示作用。

上代码:

# include<reg52.h>

//sbit PWMOUT = P0^0;
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;

unsigned long PeriodCnt = 0; //PWM周期计数值
unsigned char HighRH = 0;    //高电平重载值的高字节
unsigned char HighRL = 0;   //高电平重载值的低字节
unsigned char LowRH = 0;    //低电平重载值的高字节
unsigned char LowRL = 0;    //低电平重载值的低字节
unsigned char T1RH = 0;     //T1重载值的高字节
unsigned char T1RL = 0;     //T1重载值的低字节
unsigned char shift1 = 0x01;
unsigned char shift2 = 0x80;
unsigned char flagMove = 0;

void ConfigTimer1(unsigned int ms);
void ConfigPWM(unsigned int fr,unsigned char dc);
void LedMove();

void main()
{
  EA = 1;           //开启中断
	ENLED = 0;        //使能U3
	ADDR3 = 1;
	ADDR2 = 1;       //使能LED
	ADDR1 = 1;
	ADDR0 = 0;
	
	ConfigPWM(100,95);  //配置并启动PWM
	ConfigTimer1(1);  //用T1定时调整PWM
	while(1);
}

/* 配置并启动T1,ms为定时时间  */
void ConfigTimer1(unsigned int ms)
{
  unsigned long tmp;               //定义临时变量
	
	tmp = 11059200/12;             //定时器计数频率
	tmp = (tmp * ms)/1000;         //计算所需的计数值
	tmp = 65536 - tmp ;           //计数定时器重载值
	tmp = tmp +24 ;                    //补偿中断响应延时造成的误差
	T1RH = (unsigned char)(tmp >> 8);  //定时器重载值拆分高低字节
	T1RL = (unsigned char)tmp;
	TMOD &= 0x0F;                   //0000 1111 清零T1的控制位
	TMOD |= 0x10;                 //0001 0000 配置T1的模式为1
	TH1 = T1RH;                   //加载T1的重载值
	TL1 = T1RL;                  
	ET1 = 1;                      //使能T1中断
	TR1 = 1;                      //启动T1

}

/*配置并启动PWM,fr为频率,dc为占空比 */
void ConfigPWM(unsigned int fr , unsigned char dc)
{
  unsigned int high, low;
	
	PeriodCnt = (11059200/12) /fr;    //计算一个周期所需的计数值
	high = (PeriodCnt * dc) /100;     //计算高电平所学的计数值
	low = PeriodCnt - high;           //计算低电平所需的计数值
	high = 65536 - high +38;          //计算高电平的定时器重载值并补偿中断延时
	low = 65536 -low +38;             //计算低电平的定时器重载值并补偿中断延时
	HighRH = (unsigned char)(high >> 8); //高电平重载值拆分高低电平
	HighRL = (unsigned char)high;
	LowRH = (unsigned char)(low >> 8);  //低电平重载值拆分高低电平
	LowRL = (unsigned char)low;
	TMOD &= 0xF0;                   //清零T0的控制位
	TMOD |= 0x01;                   //配置T0为模式1
	TH0 = HighRH;                   //加载T0的重载值
	TL0 = HighRL;
	ET0 = 1;                        //使能T0中断
	TR0 = 1;                        //启动T0
	//PWMOUT = 1;                     //输出高电平
	
}

/* 占空比调整函数,频率不变只调整占空比 */

void AdjustDutyCycle(unsigned char dc)
{
  unsigned int high,low;
	
	high = (PeriodCnt * dc) / 100;  //计算高电平所需的计数值
	low  = PeriodCnt - high;        //计算低电平所需的计数值
	high = 65536 - high +38;        //计算高电平的定时器重载值并补偿中断延时
	low = 65536 - low +38;          //计算低电平的定时器重载值并补偿中断延时
	HighRH = (unsigned char)(high >> 8); //高电平重载值拆分为高低字节
	HighRL = (unsigned char)high;
	LowRH = (unsigned char)(low >> 8); //低电平重载值拆分为高低字节
	LowRL = (unsigned char)low;
	

}

/* T0中断服务函数,产生PWM输出 */
void interruptTimer0() interrupt 1
{
	  static bit PWM = 1;
		static char tmp1;       //定义中转变量
	  static char tmp2;
	  static int cnt = 0;
	 
	  cnt++;
	  if(cnt>=200 && PWM ==0) // 1s led移动一次
	  {
		
	     LedMove();
		  flagMove = 1;
	  }
		if(shift1 != 0x00)  //shift不是0的时候赋值给中转变量这里也可以用shift2判断
		  {
		   tmp1 = shift1;
		   tmp2 = shift2;
		  }
	 
		if(PWM == 1)
		{
			
		  TH0 = LowRH;
		  TL0 = LowRL;
			PWM = 0;
			shift1 = tmp1;
			shift2 = tmp2;
			P0 = ~(shift1+shift2);
		  
		}
		else
		{
		  TH0 = HighRH;
		  TL0 = HighRL;
			PWM = 1;
			shift1 = 0x00;    //低电平的时候所有的端口都置0(P0显示的时候是求反的)        
			shift2 = 0x00;
			if(cnt >= 200)
			{
			  cnt = 0;
				
				TL0 = TL0 + 29; //时间补偿
			}
			P0 = ~(shift1+shift2);  //低电平使能因此P0口求反
		}
	
 
 
}
/* LED移动函数 */
void LedMove()
{
    	 static bit dir1 = 0;                   
  	 // P0 = ~(shift1+shift2); //P0等于循环变量取反	 
	  				
	    if(shift1 == 0x08 && dir1 == 0)	//判断是否 shit1运动到端口4并且是向左移动
	     {
		    shift1 = shift1<<1;	//shift1在端口4 0x08的位置往左移一位
		    shift2 = shift2>>1;	//shift2在端口5	0x10的位置往右移一位,
		   }						//  因为是相对运动的因此判断了shift1的方向shift2 的方向就确认了
	    if(shift1 == 0x10 && dir1 == 1)	//判断是否 shift1运动到端口5并且是向右运动
		    {
		    shift1 = shift1>>1;  // shift1在端口5 0x10的位置并且向右移动一位
		    shift2 = shift2<<1;  // shift2在端口4 0x08的位置往左移一位
        }	
	    if(dir1 == 0 ) //判断是否 shift1往左移动
		    {
			     {
						 shift1 = shift1<<1; //循环变量1左移1位
			       shift2 = shift2>>1;
			     }	//循环变量2右移1位
			    if(shift1 ==0x80 && shift2 == 0x01)	//循环变量都移到最左端和最右端
			     {
				    dir1 = 1;		 //换向
				 
				    }
			  } 

      else  // 判断是否 shift1往右移动
	      {
		       {
	  	       shift1 = shift1>>1 ;	//换向后循环变量1右移
		         shift2 = shift2<<1 ;	//换向后循环变量2左移
	         }
	         if(shift1 == 0x01 && shift2 == 0x80)  //判断循环变量1到最右端且循环变量2到最 左端
		      {
		       dir1 = 0;	   //又开始换向换回到原先的方向
			 
			
		      }
			 
		     }
				 
}

/* T1中断服务函数,定时动态调整占空比 */

void interruptTimer1() interrupt 3
{
  static bit dir = 0;
	static unsigned char index = 1;

	unsigned char code table[7] = {            
	 95,75,55,5,55,75,95
	};
	
	
	TH1 = T1RH;
	TL1 = T1RL;

   
  if(flagMove == 1)
	  
	 {
		 flagMove = 0;
		 AdjustDutyCycle(table[index]);   
	   if(dir == 0)                               //从左到右 占空比赋值
	   {
	    index++;
		    if(index >= 6)
				{				   
					 dir = 1;				
				}
		    
			}
		 else
		 {
		   index--;
			                                       //从右到左 占空比赋值
		   if(index <= 0)
			  {
			   dir = 0;
			  }
		 }

    }
	  
		
	
	
}

看一下结果视频:PWM流水灯控制_哔哩哔哩_bilibili

可以看到LED每移动一次亮度都有变化,看LED光在数码管上的影子更明显。

  • 22
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值