1、使用定时器按照精确时间读秒倒计时,倒计时在LCD1602中居中显示,格式为mm:ss,每秒变化一次
2、默认倒计时10分钟,时间到后显示“Time over”“(((Boom))))”,同事文字开始闪烁。
3、加入两根线,使用中断判断当正确的一根被“剪断”时计时停止跳动。并且在第二行显示“SUCESS”,有且只有第二行文字闪烁。
4、加入随机因素,随机一根线为正确的线,如果“剪”错无事发生。
初始状态:进行各部分的初始化,将中断标志位置0;
待机状态:初始化完成后直接进入, 然后会在LCD1602中显示"Press To Plant",然后开始检测按钮状态,按钮按下开始生成随机数,然后进入已放置状态。
已放置状态(倒计时状态):不断刷新显示LCD1602上的倒计时,然后判断标志位的变化,如果满足要求则进入对应的状态,已拆除或者爆炸状态。
已拆解状态:倒计时停止,第二行“Success”闪烁显示。
倒计时结束爆炸状态:显示“Time Over”“(((Boom)))”。
1、LCD1602模块:
lcd1602_write_cmd(u8 cmd)
: 向LCD发送命令,根据LCD1602_4OR8_DATA_INTERFACE选择8位或4位数据接口发送命令。
lcd1602_write_data(u8 dat)
: 向LCD发送数据,根据LCD1602_4OR8_DATA_INTERFACE选择8位或4位数据接口发送数据。
lcd1602_init(void)
: 初始化LCD,根据LCD1602_4OR8_DATA_INTERFACE选择8位或4位数据接口初始化LCD参数。
lcd1602_clear(void)
: 清空LCD屏幕。
lcd1602_show_string(u8 x, u8 y, u8 *str)
: 在LCD上显示字符串,根据参数x、y定位显示的起始位置,根据LCD1602_4OR8_DATA_INTERFACE选择8位或4位数据接口显示字符串。代码中根据宏
LCD1602_4OR8_DATA_INTERFACE
的设置,可以选择LCD的数据接口为8位或4位。
LCD1602使用:
通信接口:LCD1602可以使用4位数据接口或8位数据接口。通过控制使能引脚、RS引脚和数据引脚,可以向LCD1602发送指令或数据。
初始化:在使用LCD1602之前,需要初始化LCD1602的参数,包括显示模式、光标设置、显示清屏等。初始化完成后,才能正常使用LCD1602进行显示操作。
操作速度:LCD1602的操作速度不能太快,因为LCD1602的内部需要一定的时间来响应和处理指令。因此,在发送指令或数据之间需要添加适当的延时。
电源供应:LCD1602通常需要接入适当的电压,一般是5V。同时,LCD1602还需接入背光灯的供电源。
坐标设置:LCD1602有两行16个字符的显示区域,可以通过设置行号和列号来设置显示字符的位置。要注意LCD1602的寻址范围。
清屏操作:在需要清空LCD屏幕内容的时候,需要发送清屏命令,并等待清屏完成。
字符显示:通过发送数据命令,可以在LCD1602上显示字符、数字和其他符号。可以通过控制光标位置来实现不同位置的显示。
可读性:LCD1602具有特定的观察角度和反射性,要确保LCD1602安装在合适的位置以得到最佳的显示效果。
2、 定时模块
void time0_init(void)
: 这个函数用于初始化定时器0。在函数中的操作包括:
TMOD|=0X01;
: 设置定时器0为工作方式1,即16位定时器。TH0=0XFC;
和TL0=0X18;
: 给定时器0赋初值,将TH0和TL0寄存器设置为0xFC18,使定时器初值为0xFC18,定时1ms。ET0=1;
: 打开定时器0中断允许。EA=1;
: 打开总中断。TR0=1;
: 打开定时器0,开始定时器计时。
void time0() interrupt 1
: 这是定时器0的中断函数,当定时器0计时完成时,会触发这个中断。在中断函数中的操作包括:
- 重置定时器初值为0xFC18,以便下一次定时。
- 静态变量
i
自增。- 如果
i
达到1000(1秒),则将_seconds
减一,即实现了一个1秒的计时效果。- 重置计数器
i
为0,以便下一次计时。
3、延时模块
void delay_10us(u16 ten_us)
: 这个函数实现了以10微秒为单位的延时函数。函数的实现是通过一个while循环,使变量ten_us
递减直到为0。在每次循环中,都会消耗大约10微秒的时间。这个函数适用于较小的延时粒度,例如在控制LCD1602等设备时可能需要较精确的延时。
void delay_ms(u16 ms)
: 这个函数实现了以毫秒为单位的延时函数。函数通过两层for循环实现延时。外层for循环控制延时的毫秒数,内层for循环为确保每个毫秒都持续一定时间而循环110次。这个函数适用于相对较长的延时,例如在需要较长暂停或延时操作时使用。
4、拆弹模块
void cut_line_init_32(void)
: 这个函数用于初始化外部中断0,其中的操作包括:
IE0=0;
: 中断标志位触发。EX0=1;
: 打开外部中断0。EA=1;
: 打开总中断开关。IT0=1;
: 外部中断0设为下降沿触发。PX0=1;
: 设置外部中断0的中断优先级。中断服务程序
void int0() interrupt 0 using 1
:这是外部中断0的中断服务程序。当外部中断0触发时,执行其中的操作:
- 将
cut
变量赋值为1,表示切割操作。- 将
ss
变量赋值为32,表示对应的索引值为32。
void cut_line_init_33(void)
: 这个函数用于初始化外部中断1,其中的操作类似于外部中断0的初始化,包括:
IE1=0;
: 中断标志位触发。EX1=1;
: 打开外部中断1。EA=1;
: 打开总中断开关。IT1=1;
: 外部中断1设为下降沿触发。PX1=1;
: 设置外部中断1的中断优先级。中断服务程序
void int1() interrupt 2 using 1
:这是外部中断1的中断服务程序。当外部中断1触发时,执行其中的操作:
- 将
cut
变量赋值为1,表示切割操作。- 将
ss
变量赋值为33,表示对应的索引值为33。其中,cut被置为1意味着引线已经被“剪断了”,改变ss的值用于判断是哪根引线断开了,两根引线分别连接P32和P33引脚,另外一段连接3.3V,当跳线被拔出时,出现电平跳变触发中断。
5、随机数模块
void initTimer(void)
: 这个函数用于初始化定时器,其中的操作包括:
TMOD = 0x01;
: 设置定时器0为工作模式1,即16位定时器模式。TH0 = 0xFC;
: 初始化定时器初值的高8位。TL0 = 0x18;
: 初始化定时器初值的低8位。TR0 = 1;
: 启动定时器0,开始计时。
unsigned char getRandom(void)
: 这个函数用于生成随机数,其中的操作包括:
TR0 = 0;
: 停止定时器0,暂停计时。randomNum = TH0;
: 将定时器0的计数值作为随机数。- 重新初始化定时器,以便下次生成随机数:
TH0 = 0xFC;
: 初始化定时器初值的高8位。TL0 = 0x18;
: 初始化定时器初值的低8位。TR0 = 1;
: 重新启动定时器0,继续计时。然后我们将随机数模2,这样通过这个变量来影响哪个跳线是真正可以影响炸弹的引线,具体操作方法为将随机数模2之后的right加ss后的和模2,如果等于0则进入已解除状态,反之没有影响。
LCD1602控制模块使用的是江协科技的封装:
lcd1602.c
- #include "lcd1602.h"
-
- #if (LCD1602_4OR8_DATA_INTERFACE==0)//8位LCD
- void lcd1602_write_cmd(u8 cmd)
- {
- LCD1602_RS=0;//选择命令
- LCD1602_RW=0;//选择写
- LCD1602_E=0;
- LCD1602_DATAPORT=cmd;//准备命令
- delay_ms(1);
- LCD1602_E=1;//使能脚E先上升沿写入
- delay_ms(1);
- LCD1602_E=0;//使能脚E后负跳变完成写入
- }
- #else //4位LCD
- void lcd1602_write_cmd(u8 cmd)
- {
- LCD1602_RS=0;//选择命令
- LCD1602_RW=0;//选择写
- LCD1602_E=0;
- LCD1602_DATAPORT=cmd;//准备命令
- delay_ms(1);
- LCD1602_E=1;//使能脚E先上升沿写入
- delay_ms(1);
- LCD1602_E=0;//使能脚E后负跳变完成写入
-
- LCD1602_DATAPORT=cmd<<4;//准备命令
- delay_ms(1);
- LCD1602_E=1;//使能脚E先上升沿写入
- delay_ms(1);
- LCD1602_E=0;//使能脚E后负跳变完成写入
- }
- #endif
-
- #if (LCD1602_4OR8_DATA_INTERFACE==0)//8位LCD
- void lcd1602_write_data(u8 dat)
- {
- LCD1602_RS=1;//选择数据
- LCD1602_RW=0;//选择写
- LCD1602_E=0;
- LCD1602_DATAPORT=dat;//准备数据
- delay_ms(1);
- LCD1602_E=1;//使能脚E先上升沿写入
- delay_ms(1);
- LCD1602_E=0;//使能脚E后负跳变完成写入
- }
- #else
- void lcd1602_write_data(u8 dat)
- {
- LCD1602_RS=1;//选择数据
- LCD1602_RW=0;//选择写
- LCD1602_E=0;
- LCD1602_DATAPORT=dat;//准备数据
- delay_ms(1);
- LCD1602_E=1;//使能脚E先上升沿写入
- delay_ms(1);
- LCD1602_E=0;//使能脚E后负跳变完成写入
-
- LCD1602_DATAPORT=dat<<4;//准备数据
- delay_ms(1);
- LCD1602_E=1;//使能脚E先上升沿写入
- delay_ms(1);
- LCD1602_E=0;//使能脚E后负跳变完成写入
- }
- #endif
-
- #if (LCD1602_4OR8_DATA_INTERFACE==0)//8位LCD
- void lcd1602_init(void)
- {
- lcd1602_write_cmd(0x38);//数据总线8位,显示2行,5*7点阵/字符
- lcd1602_write_cmd(0x0c);//显示功能开,无光标,光标闪烁
- lcd1602_write_cmd(0x06);//写入新数据后光标右移,显示屏不移动
- lcd1602_write_cmd(0x01);//清屏
- }
- #else
- void lcd1602_init(void)
- {
- lcd1602_write_cmd(0x28);//数据总线4位,显示2行,5*7点阵/字符
- lcd1602_write_cmd(0x0c);//显示功能开,无光标,光标闪烁
- lcd1602_write_cmd(0x06);//写入新数据后光标右移,显示屏不移动
- lcd1602_write_cmd(0x01);//清屏
- }
- #endif
-
-
- void lcd1602_clear(void)
- {
- lcd1602_write_cmd(0x01);
- }
-
- void lcd1602_show_string(u8 x,u8 y,u8 *str)
- {
- u8 i=0;
-
- if(y>1||x>15)return;//行列参数不对则强制退出
-
- if(y<1) //第1行显示
- {
- while(*str!='\0')//字符串是以'\0'结尾,只要前面有内容就显示
- {
- if(i<16-x)//如果字符长度超过第一行显示范围,则在第二行继续显示
- {
- lcd1602_write_cmd(0x80+i+x);//第一行显示地址设置
- }
- else
- {
- lcd1602_write_cmd(0x40+0x80+i+x-16);//第二行显示地址设置
- }
- lcd1602_write_data(*str);//显示内容
- str++;//指针递增
- i++;
- }
- }
- else //第2行显示
- {
- while(*str!='\0')
- {
- if(i<16-x) //如果字符长度超过第二行显示范围,则在第一行继续显示
- {
- lcd1602_write_cmd(0x80+0x40+i+x);
- }
- else
- {
- lcd1602_write_cmd(0x80+i+x-16);
- }
- lcd1602_write_data(*str);
- str++;
- i++;
- }
- }
- }
lcd1602.h
- #ifndef _lcd1602_H
- #define _lcd1602_H
-
- #include "public.h"
-
- //LCD1602数据口4位和8位定义,若为1,则为LCD1602四位数据口驱动,反之为8位
- #define LCD1602_4OR8_DATA_INTERFACE 0 //默认使用8位数据口LCD1602
-
- //管脚定义
- sbit LCD1602_RS=P2^6;//数据命令选择
- sbit LCD1602_RW=P2^5;//读写选择
- sbit LCD1602_E=P2^7; //使能信号
- #define LCD1602_DATAPORT P0 //宏定义LCD1602数据端口
-
-
- //函数声明
- void lcd1602_init(void);
- void lcd1602_clear(void);
- void lcd1602_show_string(u8 x,u8 y,u8 *str);
-
- #endif
延时部分:
public.c
- #include "public.h"
- void delay_10us(u16 ten_us)
- {
- while(ten_us--);
- }
-
- void delay_ms(u16 ms)
- {
- u16 i,j;
- for(i=ms;i>0;i--)
- for(j=110;j>0;j--);
- }
public.h
- #ifndef _public_H
- #define _public_H
-
- #include "reg52.h"
-
- typedef unsigned int u16; //对系统默认数据类型进行重定义
- typedef unsigned char u8;
- typedef unsigned long u32;
-
- void delay_10us(u16 ten_us);
- void delay_ms(u16 ms);
-
- #endif
还没做定义和声明分离的其他部分:
main.c
- #include "public.h"
- #include "lcd1602.h"
- #include "stdio.h"
- #include "string.h"
- int cut = 0;
- int right = 0;
- int randomValue = 0;
- int ss = 0;//标志位
- unsigned int _seconds = 600;//设置默认读秒数为600
- char _min[2] = {0};
- char _second[2] = {0};
- sbit button = P3^1;
- sbit red = P3^2; // 设置k3为红线
- sbit blue = P3^3;//设置k4为蓝线
- void Sucess(void);
-
- // 初始化定时器
- void initTimer(void) {
- TMOD = 0x01; // 设置为定时器模式
- TH0 = 0xFC; // 初始化定时器初值
- TL0 = 0x18;
- TR0 = 1; // 启动定时器
- }
-
- // 生成随机数
- unsigned char getRandom(void) {
- unsigned char randomNum;
-
- TR0 = 0; // 停止定时器
- randomNum = TH0; // 获取定时器计数值作为随机数
-
- // 重新初始化定时器
- TH0 = 0xFC;
- TL0 = 0x18;
- TR0 = 1;
-
- return randomNum;
- }
-
- void get_second(unsigned int All_seconds)//参数为总秒数
- {
- int second = All_seconds % 60;//对应的余下的秒数
- char second_temp[2] = {0};
- // 提取十位和个位
- int tens = second / 10; // 十位数
- int ones = second % 10; // 个位数
-
- second_temp[0] = tens + 48;
- second_temp[1] = ones + 48;
-
- _second[0] = second_temp[0];
- _second[1] = second_temp[1];
-
- }
-
- void get_min(unsigned int All_seconds)//参数为总秒数,返回值是存储在数组中的分钟数
- {
- char min_temp[2] = {0};
-
- int minutes;
- int tens;
- int ones;
-
- if(All_seconds >= 60)
- {
- minutes = All_seconds / 60;
- }
- if(All_seconds < 60)
- {
- minutes = 0;
- }
- // 提取十位和个位
- tens = minutes / 10; // 十位数
- ones = minutes % 10; // 个位数
-
- min_temp[0] = tens + 48;
- min_temp[1] = ones + 48;
-
- _min[0] = min_temp[0];
- _min[1] = min_temp[1];
-
- }
-
- void time0_init(void)//定时器初始化
- {
- TMOD|=0X01;//选择为定时器0 模式,工作方式1
- TH0=0XFC; //给定时器赋初值,定时1ms
- TL0=0X18;
- ET0=1;//打开定时器0 中断允许
- EA=1;//打开总中断
- TR0=1;//打开定时器
- }
-
- void time0() interrupt 1 //定时器0 中断函数
- {
-
-
- static u16 i;//定义静态变量i
- TH0=0XFC; //给定时器赋初值,定时1ms
- TL0=0X18;
- i++;
- if(i==1000)//如果达到时间
- {
- i=0;//重置计数器
- _seconds--;//发生效果:读秒数减一
- }
-
- }
-
-
- void cut_line_init_32(void)
- {
- IE0=0; //中断标志位触发
- EX0= 1; //打开外部中断0
- EA= 1; //打开总中断开关
- IT0= 1; //外部中断0设为低电平触发 // 1则为下降沿触发
- PX0=1; //中断优先级
- }
- void int0() interrupt 0 using 1
- {
- //编写用户所需的功能代码
- cut = 1;
- ss = 32;
- }
-
- void cut_line_init_33(void)
- {
- IE1=0; //中断标志位触发
- EX1= 1; //打开外部中断0
- EA= 1; //打开总中断开关
- IT1= 1; //外部中断1设为低电平触发 // 1则为下降沿触发
- PX1=1; //中断优先级
- }
- void int1() interrupt 2 using 1
- {
- //编写用户所需的功能代码
- cut = 1;
- ss = 33;
- }
- //拆弹成功状态
- void Sucess(void)
- {
- int time = _seconds;
- char min[2] = {0};
- char second[2] = {0};
- strcpy(min,_min);
- strcpy(second,_second);
-
-
- lcd1602_show_string(0,0," ");//第一行显示
- lcd1602_show_string(0,1," ");//第二行显示
-
- lcd1602_show_string(5,0,min);//第一行显示
- lcd1602_show_string(7,0,":");//第一行显示
- lcd1602_show_string(8,0,second);//第一行显示
- lcd1602_show_string(5,1,"Sucess");//第er行显示
- lcd1602_show_string(10,0," ");//第一行显示
- while(1)
- {
- delay_ms(200);
- lcd1602_show_string(0,1," ");//第二行显示
- lcd1602_show_string(5,1,"Sucess");//第er行显示
- }
- }
-
- //爆炸状态
- void Boom(void)
- {
- while(1)
- {
- lcd1602_show_string(0,0," ");//第一行显示
- lcd1602_show_string(0,1," ");//第二行显示
- delay_10us(20000);
- lcd1602_show_string(0,0," Time Over");//第一行显示
- lcd1602_show_string(0,1," (((Boom)))");//第二行显示
- delay_10us(80000);
- }
- }
-
- //倒计时状态
- void Planted(void)
- {
- lcd1602_show_string(0,1," ");//第er行显示
- lcd1602_show_string(0,0," ");//第er行显示
- time0_init();
- //*********************************************ce shi
-
-
- while(1)
- {
- get_min(_seconds);
- get_second(_seconds);
- lcd1602_show_string(5,0,_min);//第一行显示
- lcd1602_show_string(7,0,":");//第一行显示
- lcd1602_show_string(8,0,_second);//第一行显示
- lcd1602_show_string(10,0," ");//第一行显示
-
- if(_seconds == 0)
- {
- Boom();//进入爆炸状态
- }
- if((ss + right) % 2== 0 && cut == 1)
- {
- Sucess();//进入成功状态
- }
-
- }
- }
- //待机状态
- void Standby(void)
- {
- while(1)
- {
- lcd1602_show_string(0,0,"Press to ");//第一行显示
- lcd1602_show_string(0,1,"Plant.");//第二行显示
- if(button == 0)
- {
- delay_ms(50);//防抖
- randomValue = getRandom(); // 获取随机数
- // 在这里可以使用随机数进行其他操作
- right = randomValue % 2;
- Planted();//状态切换
-
- }
- }
- }
-
- void main()
- {
- initTimer(); // 初始化定时器
- cut_line_init_32();
- cut_line_init_33();
- lcd1602_init();//LCD1602初始化
-
- red = 0;
- blue = 0;
- while(1)
- {
- Standby(); // 进入待机模式
- }
- }
Gitee下载:Keil_5_51project: 51学习示例https://gitee.com/haozhe34866/keil_5_51project.git
效果视频:
【[51单片机]可随机引线“拆弹”LCD1602显示精确读秒】 https://www.bilibili.com/video/BV1FG3ne1EdF/?share_source=copy_web&vd_source=33adf9f4b4c4b1221219d2246aa376b1