前一篇博文提到了定时炸弹。从实用角度来说,这么危险的东西如果流出去将是一个很大的风险。因此考虑需要做个电子密码锁。
由于笔者没有看过正规的关于密码锁的文档,做这个是笔者的一时兴起,这个密码锁的结构可能充满着野路子。
密码锁基本功能:
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
与前一篇博文的区别:重新加入了两个函数,如果对程序阅读有困难可以阅读下笔者前一篇博文
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光在数码管上的影子更明显。