
C4定时炸弹起爆模块
单片机电子设计
2019年12月6日
概要:此实验是以单片机(STC89C52)为核心,结合独立按键和矩阵键盘和蜂鸣器(代替引爆引线),实现C4炸药的设置起爆程序,独立按键用以控制启动拆弹密码输入程序,倒计时输入程序,以及启动按钮,程序启动后开始倒计时,在数码管右四显示,同时可以输入密码终止爆炸程序,输入密码实时显示在数码管左四。当对应位数输错时立即引爆,都数对时,终止爆炸。
原理图:

编程思想:外部中断0和1用以控制启动初始密码输入和倒计时输入程序,分别对应K1和K2的按下。密码和倒计时通过矩阵键盘输入,对应值为键值。定时器T0为计时方式用以执行倒计时程序。T1采用计数器一步溢出方式,在输入终止密码和倒计时后用以启动倒计时引爆程序。启动倒计时后,开始检测拆弹密码的输入。同时倒计时鸣笛启动,6秒之内鸣笛频率逐渐加快,6秒外响一秒停一秒。
流程框图:
- 核心代码:
基础键盘扫描:
char getkey()
{
char key_scan[]={0xef,0xdf,0xbf,0x7f};
char i=0,j=0;
for(i=0;i<4;i++)
{
P2=key_scan[i];
if((P2&0x0f)!=0x0f)
{
for(j=0;j<16;j++)
{
if(key_buf[j]==P2)
return j;
}}}
return -1;}
扫描P2口,如果键值按下则返回键值,无则返回-1;
密码输入程序:
void inputpsw()
{
int c,b;
for(c=0;c<4;c++)
{
psw[c]=16;
}
c=0;
key=-1;
while(1)
{
for(b=0;b<4;b++)
{
P2=dptr[b];
P0=led_mod[psw[b]];
delay(120);
}
k=key;
key=getkey();
if(c==4) break;
if(key!=-1&&k!=key)
{
psw[c]=key;
c++;
}
}
}
扫描矩阵键盘并将得到的键值存储在密码数组中,同时实时显示输入。
倒计时输入程序:
void inputtime()
{
int b,c;
tem=0;
for(b=2;b<4;b++)
{time[b]=16; }
b=0;
c=2;
while(1)
{
for(b=4;b<8;b++)
{
P2=dptr[b];
P0=led_mod[time[b-4]];
delay(120);
}
k=key;
key=getkey();
if(key!=-1&&k!=key&&c<4)
{
time[c]=key;
tem=tem*10+key;
c++;
}
if(c==4) break;
}
date=tem;
}
扫描矩阵键盘并将得到的键值存储在倒计时数组中,同时实时显示输入。
起爆程序运行模块:
k=key;
key=getkey();
if(key!=-1&&k!=key)
{
if(chai!=3)
{
if(key!=psw[chai])
{speaker=1;
displayde();}
else
{
try[chai]=key;
chai++;
}
}
else
{
displaysu();
}
}
if(date==-1)
{
speaker=1;
TR0=0;
displayde();
}
if(count==98)
{
if(ChangeM==0)
{
displaygoing();
P2=dptr[6];
P0=led_mod[date/10];
ChangeM=1;
if(date>5)
speaker=1;
}
else
{
displaygoing();
P2=dptr[7];
P0=led_mod[date%10];
ChangeM=0;
if(date>5)
speaker=0;
}
count=0;
date--;
TH0=0xd8;
TL0=0xf0;
}
else
{
if(change==0)
{
displaygoing();
P2=dptr[6];
P0=led_mod[date/10];
change=1;
}
else
{
displaygoing();
P2=dptr[7];
P0=led_mod[date%10];
change=0;
}
count++;
if(date<=5&&date>3)
{
if(count<25) speaker=0;
if(count<50&&count>24) speaker=1;
if(count<75&&count>49) speaker=0;
if(count>74) speaker=1;
}
if(date<=3&&date>1)
{
if(count<13) speaker=0;
if(count<25&&count>12) speaker=1;
if(count<38&&count>24) speaker=0;
if(count<50&&count>37) speaker=1;
if(count<63&&count>49) speaker=0;
if(count<75&&count>62) speaker=1;
if(count<87&&count>74) speaker=0;
if(count>86) speaker=1;
}
if(date<=1)
{
if(count<7) speaker=0;
if(count<13&&count>6) speaker=1;
if(count<19&&count>12) speaker=0;
if(count<25&&count>18) speaker=1;
if(count<32&&count>24) speaker=0;
if(count<38&&count>31) speaker=1;
if(count<44&&count>37) speaker=0;
if(count<50&&count>43) speaker=1;
if(count<57&&count>49) speaker=0;
if(count<63&&count>56) speaker=1;
if(count<69&&count>62) speaker=0;
if(count<75&&count>68) speaker=1;
if(count<82&&count>74) speaker=0;
if(count<88&&count>81) speaker=1;
if(count<94&&count>87) speaker=0;
if(count>93) speaker=1;
}
TH0=0xd8;
TL0=0xf0;
}}
定时启动,并定时为10ms,利用100个周期实现基础1s倒计时。同时侦测是否有拆弹码输入,如果有,则按位实时比较是否相同,不同立即引爆,否则拆弹完成,在倒计时周期中开关蜂鸣器,通过改变开关频率,实现倒计时越逼近0,响的越急促。
- 研究点:键盘按下的过程中可能一次按下出现抖动导致多次输入,所有我采用了自己设计的键盘消抖,放弃传统延时消抖,利用与上一次key值比较消抖,如果此次读取的值和上一次值相同,说明按键还未松开,一旦松开,键值为-1,再次按下必然不同,以此消抖。
- 源代码
#include"stdio.h"
#include"reg51.h"
#include"math.h"
char key=-1;
char k=-1;
bit change=0,begin=0,ChangeM=0;
code key_buf[]={0xee,0xde,0xbe,0x7e,0xed,0xdd,0xbd,0x7d,
0xeb,0xdb,0xbb,0x7b,0xe7,0xd7,0xb7,0x77};0-f对应的16进制数
code led_mod[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71,0x40,0x09}; //0-f和特殊显示值
code dptr[]={0x7f,0xbf,0xdf,0xef,0xf7,0xfb,0xfd,0xfe};//数码管位选值
code boom[]={0x40,0x7c,0x5c,0x5c,0x54,0x54,0x86,0x40};//显示“boom“
int psw[4],try[4];//存储密码和拆弹码
sbit speaker=P1^4;
int date,chai,dest;
int time[4],tem;//存储倒计时
int count=0;
void input();
void delay(char c)
{
char i;
for(i=0;i<c;i++);
}//自定义延时函数
void displayde()
{
char i=0;
while(1)
{
P2=dptr[i];//位选通
P0=boom[i];//位显示内容
i++;
if(i==8) i=0;//循环变量循环
delay(60);//刷新延迟
}}//数码管显示拆弹失败
void displaysu()
{
char i=0;
while(1)
{
P2=dptr[i];//同上
P0=0x80;//显示“。。。。。。”
i++;
if(i==8) i=0;
delay(60);
}}//数码管显示拆弹成功
void displaygoing()
{
int i;
for(i=0;i<4;i++)
{
P2=dptr[i];
P0=led_mod[try[i]];//显示已输入的拆弹密码
delay(10);
}//实时显示输入的拆弹密码
P2=dptr[4];
P0=led_mod[12];//显示“c”
delay(10);//刷新延迟
P2=dptr[5];
P0=led_mod[17];显示“=”
delay(10);//倒计时显示文本“c=”
}拆弹过程中输入实时显示
int getkey()
{
char key_scan[]={0xef,0xdf,0xbf,0x7f};
int i=0,j=0;
for(i=0;i<4;i++)
{
P2=key_scan[i];//输入键扫描码
if((P2&0x0f)!=0x0f)
{
for(j=0;j<16;j++)
{
if(key_buf[j]==P2)//判断哪个键按下
{
return j;//返回键值
}}}}
return -1;//没找到返回-1
}
void inputpsw()
{
int c,b;
for(c=0;c<4;c++)
{
psw[c]=16;
}//初始化显示“-------”
c=0;
key=-1;
while(1)
{
for(b=0;b<4;b++)
{
P2=dptr[b];
P0=led_mod[psw[b]];
delay(120);刷新延迟
}//实时刷新显示以输入的设置密码
k=key;
key=getkey(); //键盘消抖
if(c==4) break;//密码限定4位
if(key!=-1&&k!=key)
{
psw[c]=key;
c++;//存储输入的密码
}}}//设置拆弹密码程序
void inputtime()
{
int b,c;
tem=0;
for(b=2;b<4;b++)
{time[b]=16; }//初始化显示“—”
b=0;
c=2;
while(1)
{
for(b=4;b<8;b++)
{
P2=dptr[b];
P0=led_mod[time[b-4]];
delay(120);//刷新延迟
}//实时刷新显示设置的倒计时
k=key;
key=getkey();//键盘消抖
if(key!=-1&&k!=key&&c<4)
{
time[c]=key;
tem=tem*10+key;
c++;//处理和存储输入的倒计时
}
if(c==4) break;//限定倒计时为2位
}
date=tem;//保存倒计时数据
}
void inputP() interrupt 0//外部中断0,K1控制
{
inputpsw();//K1按下启动密码设置程序
}
void inputT() interrupt 2//外部中断1,K2控制
{
inputtime();//K2按下启动倒计时设置程序
}
void timer() interrupt 1//定时器T1
{
k=key;
key=getkey();//键盘消抖
if(key!=-1&&k!=key)
{
if(chai!=3)//限定拆弹密码位3位
{
if(key!=psw[chai])
{speaker=1;
displayde();}//按位比较输入值和密码,一不对立即引爆并显示失败
else
{
try[chai]=key;
chai++;//继续判断下一位
}
}
else //4位判断全部通过
{
displaysu();//显示拆弹成功
}}
if(date==-1)//倒计时到
{
speaker=1;
TR0=0;
displayde();//立即引爆,定时器关闭,显示拆弹失败
}
if(count==98)
{
if(ChangeM==0)//1s基础倒计时循环开始
{
displaygoing();//拆弹模块刷新
P2=dptr[6];
P0=led_mod[date/10];倒计时十位显示
ChangeM=1;//1s基础倒计时标志量刷新
if(date>5)
speaker=1;
}
else
{
displaygoing();//拆弹模块刷新
P2=dptr[7];
P0=led_mod[date%10];//倒计时个位刷新
ChangeM=0;
if(date>5)
speaker=0;//配合上面,在倒计时大于5s时周期两秒一响一灭
}
count=0;
date--;//倒计时减一
TH0=0xd8;
TL0=0xf0;重装计数初值
}
else
{
if(change==0)//个位十位交替显示
{
displaygoing();//刷新拆弹模块
P2=dptr[6];
P0=led_mod[date/10];//显示倒计时十位
change=1;
}
else
{
displaygoing();//拆弹模块刷新
P2=dptr[7];
P0=led_mod[date%10];//显示倒计时个位
change=0;
}
count++;
if(date<=5&&date>3)//倒计时3-5秒时蜂鸣器交替频率
{
if(count<25) speaker=0;
if(count<50&&count>24) speaker=1;
if(count<75&&count>49) speaker=0;
if(count>74) speaker=1;
}
if(date<=3&&date>1) //倒计时1-3秒时蜂鸣器交替频率
{
if(count<13) speaker=0;
if(count<25&&count>12) speaker=1;
if(count<38&&count>24) speaker=0;
if(count<50&&count>37) speaker=1;
if(count<63&&count>49) speaker=0;
if(count<75&&count>62) speaker=1;
if(count<87&&count>74) speaker=0;
if(count>86) speaker=1;
}
if(date<=1) //倒计时最后1秒时蜂鸣器交替频率
{
if(count<7) speaker=0;
if(count<13&&count>6) speaker=1;
if(count<19&&count>12) speaker=0;
if(count<25&&count>18) speaker=1;
if(count<32&&count>24) speaker=0;
if(count<38&&count>31) speaker=1;
if(count<44&&count>37) speaker=0;
if(count<50&&count>43) speaker=1;
if(count<57&&count>49) speaker=0;
if(count<63&&count>56) speaker=1;
if(count<69&&count>62) speaker=0;
if(count<75&&count>68) speaker=1;
if(count<82&&count>74) speaker=0;
if(count<88&&count>81) speaker=1;
if(count<94&&count>87) speaker=0;
if(count>93) speaker=1;
}
TH0=0xd8;
TL0=0xf0;//重装计数初值
}
}
void start() interrupt 3//计数器T1一步溢出方式,启动炸弹
{
char c;
chai=0;
for(c=0;c<4;c++)
{
try[c]=16;
}//初始化显示“----”
begin=1;
TH0=0xd8;
TL0=0xf0;//装入计数初值
TR0=1;//启动定时器T0
}
void main()
{
char c;
time[0]=12;
time[1]=17;//初始化数码管右2数据为“c=”
count=0;//周期次数置0
begin=0;//开启炸弹标志量为0
speaker=0;
EA=1;开总中断
TMOD=0xe1;//定时器T0定时方式1,T1计数方式
TCON=0x00;
ET0=1;
ET1=1;
EX0=1;
EX1=1;//开总中断和相应中断
TH1=0xff;
TL1=0xff;//一步溢出计数初值写入
TR1=1;//启动计数器T1
while(1)
{
if(begin!=1)//未启动炸弹
{
if(c>7) c=0;循环显示标志量循环
if(c==4)
{P2=dptr[c];
P0=led_mod[12];//数码管右一设置为“c”
c++;
delay(60);//刷新延迟
continue;
}
else if(c==5)
{P2=dptr[c];
P0=led_mod[17];//数码管右二设置为“=”
c++;
delay(60);//刷新延迟
continue;
}
else
{P2=dptr[c];
P0=0x40;//其他位置全部初始为“-”
c++;
delay(60);刷新延迟
}}}}