初学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光在数码管上的影子更明显。