灵感来源:
一位室友非常喜欢打"CS GO",经常因残局对炸弹的安放或拆除成功而高兴地大喊大叫(手动滑稽),于是我萌生了一个有趣的想法,用单片机来模拟出一个没有伤害的电子炸弹。
电子炸弹的具体功能:
首先用LCD1602来模拟电子炸弹的显示屏,在刚开始时,上边显示“Dismantle Bomb!”、“remote 6!”的字样,然后安放炸弹的人在输入“0748”后炸弹开始倒计时,允许输入错误无数次,直到输入成功为止。炸弹最多坚持30s,1s减一次,剩余的时间显示在屏幕的右下角。30s到20s之间,每2s响一次;20s到10s之间,每1s响一次;最后10s,每1s响2次。并且当响声的频率变快时,屏幕上会显示“COME ON!”的字样,然后再次恢复初始的显示。输入“0487”可以拆除炸弹,拆除成功后响两声,然后出现“Good job!”的字样。如果到时间未拆除或者连续三次密码输入失败,则拆除炸弹失败,会有5s中的长响,并且屏幕上有“LOSER!”的字样出现。
源代码:
#include <reg52.h>
#include <intrins.h>
#include <string.h>
typedef unsigned char uchar;
typedef unsigned int uint;
sbit DU=P2^6;
sbit WE=P2^7;//锁存器IO口位定义
sbit BEEP=P2^3;//蜂鸣器位定义
sbit RS=P3^5;
sbit RW=P3^6;
sbit EN=P3^4;//LCD1602对应管脚位定义
uchar code SMGwei[]={0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f};//1-8位数码管
uchar code SMGduan[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};//0-9数字
uchar zifu1[]="Dismantle Bomb!";
uchar zifu2[]="remote 6!";
uchar zifu3[]="COME ON!";
uchar zifu4[]="LOSER!";
uchar zifu5[]="Good job!";//表示五个要显示的字符串
uchar key,wei,flag,flbg,ms,s=30,T,F,x;//flag、flbg、T、F为标志位,s为炸弹总倒计时30s,x来记录密码错误的次数
uchar tabel1[4],tabel2[4];//1用来储存输入的开炸弹密码,2用来储存输入的拆炸弹密码
uchar tabel3[16]={0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f};//LCD1602第一行可见地址
uchar tabel4[16]={0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f};//LCD1602第二行可见地址
/******矩阵键盘输入模块******/
void delay(uint z)
{
uint x,y;
for(x=z;x>0;x--)
for(y=114;y>0;y--);
}
void keyscan1()//矩阵键盘
{
uchar temp;
P3=0XFF;
P3=0XFE;
temp=P3&0XF0;
if(temp!=0xf0)
{
delay(20);
P3=0XFE;
temp=P3&0XF0;
if(temp!=0xf0)
{
temp=P3;
switch(temp)
{
case 0xee:key=0;wei++;break;
case 0xde:key=1;wei++;break;
case 0xbe:key=2;wei++;break;
case 0x7e:key=3;wei++;break;
}
while(temp!=0xf0)
{
temp=P3&0XF0;
}
}
}
P3=0XFF;
P3=0XFD;
temp=P3&0XF0;
if(temp!=0xf0)
{
delay(20);
P3=0XFD;
temp=P3&0XF0;
if(temp!=0xf0)
{
temp=P3;
switch(temp)
{
case 0xed:key=4;wei++;break;
case 0xdd:key=5;wei++;break;
case 0xbd:key=6;wei++;break;
case 0x7d:key=7;wei++;break;
}
while(temp!=0xf0)
{
temp=P3&0XF0;
}
}
}
P3=0XFF;
P3=0XFB;
temp=P3&0XF0;
if(temp!=0xf0)
{
delay(20);
P3=0XFB;
temp=P3&0XF0;
if(temp!=0xf0)
{
temp=P3;
switch(temp)
{
case 0xeb:key=8;wei++;break;
case 0xdb:key=9;wei++;break;
case 0xbb:break;
case 0x7b:break;
}
while(temp!=0xf0)
{
temp=P3&0XF0;
}
}
}
P3=0XFF;
P3=0XF7;
temp=P3&0XF0;
if(temp!=0xf0)
{
delay(20);
P3=0XF7;
temp=P3&0XF0;
if(temp!=0xf0)
{
temp=P3;
switch(temp)
{
case 0xe7:break;
case 0xd7:break;
case 0xb7:break;
case 0x77:break;
}
while(temp!=0xf0)
{
temp=P3&0XF0;
}
}
}
}
/******储存打开炸弹的密码******/
void c1()
{
if(wei==1)
tabel1[0]=key;
if(wei==2)
tabel1[1]=key;
if(wei==3)
tabel1[2]=key;
if(wei==4)
{
tabel1[3]=key;
T=1;
}
}
/******储存拆除炸弹的密码******/
void c2()
{
if(wei==1)
tabel2[0]=key;
if(wei==2)
tabel2[1]=key;
if(wei==3)
tabel2[2]=key;
if(wei==4)
{
tabel2[3]=key;
F=1;
}
}
/******秒表计时******/
void Timer0Init()//50ms中断一次
{
TMOD|=0X01;
TH0=(65536-46082)/256;
TL0=(65536-46082)%256;
TR0=1;
ET0=1;
}
/******模拟炸弹“滴滴”的响声******/
void didi()
{
if((s<=30&&s>20)&&(s%2!=0))//炸弹30s倒计时,在20s之前2s响一次
{
BEEP=0;
delay(50);
BEEP=1;
}
if(s<=20&&s>10)//20s到10s之间1s响一次
{
BEEP=0;
delay(50);
BEEP=1;
}
if(s<=10&&s>0)//最后10s时1s响两次
{
BEEP=0;
delay(25);
BEEP=1;
delay(100);
BEEP=0;
delay(25);
BEEP=1;
}
}
/********LCD1602********/
void Read_Busy()
{
uchar busy;
P0=0XFF;
RS=0;
RW=1;
do
{
EN=1;
busy=P0;
EN=0;
}while(busy&0x80);
}
void Write_cmd(uchar cmd)
{
Read_Busy();
RS=0;
RW=0;
P0=cmd;
EN=1;
EN=0;
}
void Write_dat(uchar dat)
{
Read_Busy();
RS=1;
RW=0;
P0=dat;
EN=1;
EN=0;
}
void LCD1602Init()
{
Write_cmd(0x38);
Write_cmd(0x0c);
Write_cmd(0x01);
}
void LCDWrite1(uchar dat[])//在第一行写数据的函数
{
uchar i=0;
while(i<strlen(dat))
{
Write_cmd(tabel3[i]|0x80);
delay(2);
Write_dat(dat[i]);
i++;
}
}
void LCDWrite2(uchar dat[])//在第二行写数据的函数
{
uchar i=0;
while(i<strlen(dat))
{
Write_cmd(tabel4[i]|0x80);
delay(2);
Write_dat(dat[i]);
i++;
}
}
void main()
{
Timer0Init();
LCD1602Init();
LCDWrite1(zifu1);
LCDWrite2(zifu2);//一系列的初始化
while(1)
{
if(flag==0)//炸弹未打开时
{
keyscan1();
c1();
if(T==1)//输入密码满四位后
{
if(tabel1[0]==0&&tabel1[1]==7&&tabel1[2]==4&&tabel1[3]==8)//判断开启密码是否正确
{
flag=1;//正确的标志位,表示炸弹开始倒计时
wei=0;//数组地址归零
T=0;//输入密码完成标志位归零
EA=1;//打开总中断,表示倒计时开始
}
else
{
wei=0;
T=0;
}
}
}
if(flag==1)//炸弹开始倒计时后
{
Write_cmd(0x4e|0x80);
Write_dat((s/10+'0'));
Write_cmd(0x4f|0x80);
Write_dat((s%10+'0'));//在LCD1602右下角显示剩余秒数
if(s==20||s==10)//当蜂鸣器发声频率改变时,屏幕上显示“COME ON!”字样
{
Write_cmd(0x01);
LCDWrite1(zifu3);
delay(50);
}
if((s!=0)&&(s!=20&&s!=10))//显示完“COME ON!”后再次换回原来的显示
{
LCDWrite1(zifu1);
LCDWrite2(zifu2);
}
keyscan1();
c2();
if(F==1)//拆弹密码是否输入完成
{
if(tabel2[0]==0&&tabel2[1]==4&&tabel2[2]==8&&tabel2[3]==7)//判断拆弹密码是否正确
{
flbg=1;//正确的标志位
wei=0;
F=0;
EA=0;//关闭中断
Write_cmd(0x01);
LCDWrite1(zifu5);
BEEP=0;
delay(25);
BEEP=1;
delay(150);
BEEP=0;
delay(25);
BEEP=1;//蜂鸣器连响两次表示拆弹成功
delay(1000);
}
else
{
wei=0;
x++;
F=0;
}
}
if((s==0&&flag==1)||x==3)//判断是否拆弹失败
{
EA=0;
Write_cmd(0x01);
LCDWrite1(zifu4);
delay(50);
BEEP=0;
delay(5000);
BEEP=1;//蜂鸣器长鸣5s表示失败
flag=2;//标志位置2
delay(1000);
}
}
if(flag==2||flbg==1)//结束后清屏
{
Write_cmd(0x01);
}
}
}
void Timer0() interrupt 1
{
TH0=(65536-46082)/256;
TL0=(65536-46082)%256;
ms++;
if(ms==20)//计时1s后改变数码管中要显示的数据
{
s--;
ms=0;
if(s==0)
{
EA=0;//关闭中断
}
didi();
}
}
写在后边:
运用的模块一般的单片机上都有,没有什么特别的。LCD1602的显示个人习惯问题吧,还是更喜欢这样写。还有更加精简的LCD1602显示方式可以使代码长度进一步缩短,各位感兴趣的话可以查一查(提示:指针)。
如果觉得博主写的不错的话,各位小伙伴记得点一个赞哈~