当前位置: 首页 > news >正文

初学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); //定时1msShowNumber(0);  //数码管显示0while(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; // 作用:高位是零则不显示elsebreak;}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归0switch(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++;elsei = 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归0if(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); //定时1msShowNumber(0);  //数码管显示0while(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; // 作用:高位是零则不显示elsebreak;}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)//1010PasswordLock = 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归0switch(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++;elsei = 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;        //使能U3ADDR3 = 1;ADDR2 = 1;       //使能LEDADDR1 = 1;ADDR0 = 0;ConfigPWM(100,95);  //配置并启动PWMConfigTimer1(1);  //用T1定时调整PWMwhile(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的模式为1TH1 = 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为模式1TH0 = 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光在数码管上的影子更明显。

相关文章:

  • 二维码登录的原理
  • vue根据文字长短展示跑马灯效果
  • Kafka-服务端-副本同步-源码流程
  • 编程入门:从零开始学习编程的方法与步骤
  • Java List操作详解及常用方法
  • 【Llama 2的使用方法】
  • 大学生放学后一定要做的4件事情
  • PO模式简介
  • 什么是有效的电子签名?PDF电子签名怎样具备法律效力?
  • 发电机保护屏的作用及其重要性
  • 亚马逊等跨境电商测评怎么做?
  • Chapter8 透明效果——Shader入门精要学习笔记
  • 【愤怒的小方块案例 Objective-C语言】
  • Java实现数据结构——不带头单链表
  • 墨烯的Java技术栈-数据结构与算法基础-010
  • php的引用
  • Angular 响应式表单 基础例子
  • conda常用的命令
  • FastReport在线报表设计器工作原理
  • JavaScript新鲜事·第5期
  • leetcode386. Lexicographical Numbers
  • leetcode46 Permutation 排列组合
  • Unix命令
  • windows下mongoDB的环境配置
  • 阿里云前端周刊 - 第 26 期
  • 创建一个Struts2项目maven 方式
  • 后端_MYSQL
  • 将回调地狱按在地上摩擦的Promise
  • 漂亮刷新控件-iOS
  • 前嗅ForeSpider中数据浏览界面介绍
  • 实现菜单下拉伸展折叠效果demo
  • 事件委托的小应用
  • ​ 全球云科技基础设施:亚马逊云科技的海外服务器网络如何演进
  • # Swust 12th acm 邀请赛# [ K ] 三角形判定 [题解]
  • (52)只出现一次的数字III
  • (el-Transfer)操作(不使用 ts):Element-plus 中 Select 组件动态设置 options 值需求的解决过程
  • (Git) gitignore基础使用
  • (Matalb时序预测)PSO-BP粒子群算法优化BP神经网络的多维时序回归预测
  • (Oracle)SQL优化技巧(一):分页查询
  • (牛客腾讯思维编程题)编码编码分组打印下标题目分析
  • (三)SvelteKit教程:layout 文件
  • (一)使用Mybatis实现在student数据库中插入一个学生信息
  • (转)visual stdio 书签功能介绍
  • (转)关于pipe()的详细解析
  • ***检测工具之RKHunter AIDE
  • .equal()和==的区别 怎样判断字符串为空问题: Illegal invoke-super to void nio.file.AccessDeniedException
  • .helper勒索病毒的最新威胁:如何恢复您的数据?
  • .NET CORE 第一节 创建基本的 asp.net core
  • .NET Core Web APi类库如何内嵌运行?
  • .Net Core与存储过程(一)
  • .net6Api后台+uniapp导出Excel
  • .NET项目中存在多个web.config文件时的加载顺序
  • .php文件都打不开,打不开php文件怎么办
  • .so文件(linux系统)
  • @PreAuthorize与@Secured注解的区别是什么?