• 51单片机定时炸弹-准确计时-两根线随机一根触发中断可“拆弹“(AT89C52)


    一、设计介绍:

    1、使用定时器按照精确时间读秒倒计时,倒计时在LCD1602中居中显示,格式为mm:ss,每秒变化一次

    2、默认倒计时10分钟,时间到后显示“Time over”“(((Boom))))”,同事文字开始闪烁。

    3、加入两根线,使用中断判断当正确的一根被“剪断”时计时停止跳动。并且在第二行显示“SUCESS”,有且只有第二行文字闪烁。

    4、加入随机因素,随机一根线为正确的线,如果“剪”错无事发生。

    二、程序解释:

    (1)逻辑解释:

    初始状态:进行各部分的初始化,将中断标志位置0;

    待机状态:初始化完成后直接进入, 然后会在LCD1602中显示"Press To Plant",然后开始检测按钮状态,按钮按下开始生成随机数,然后进入已放置状态。

    已放置状态(倒计时状态):不断刷新显示LCD1602上的倒计时,然后判断标志位的变化,如果满足要求则进入对应的状态,已拆除或者爆炸状态。

    已拆解状态:倒计时停止,第二行“Success”闪烁显示。

    倒计时结束爆炸状态:显示“Time Over”“(((Boom)))”。

    (2)模块功能解释

    1、LCD1602模块:

    1. lcd1602_write_cmd(u8 cmd): 向LCD发送命令,根据LCD1602_4OR8_DATA_INTERFACE选择8位或4位数据接口发送命令。

    2. lcd1602_write_data(u8 dat): 向LCD发送数据,根据LCD1602_4OR8_DATA_INTERFACE选择8位或4位数据接口发送数据。

    3. lcd1602_init(void): 初始化LCD,根据LCD1602_4OR8_DATA_INTERFACE选择8位或4位数据接口初始化LCD参数。

    4. lcd1602_clear(void): 清空LCD屏幕。

    5. 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使用:

    1. 通信接口:LCD1602可以使用4位数据接口或8位数据接口。通过控制使能引脚、RS引脚和数据引脚,可以向LCD1602发送指令或数据。

    2. 初始化:在使用LCD1602之前,需要初始化LCD1602的参数,包括显示模式、光标设置、显示清屏等。初始化完成后,才能正常使用LCD1602进行显示操作。

    3. 操作速度:LCD1602的操作速度不能太快,因为LCD1602的内部需要一定的时间来响应和处理指令。因此,在发送指令或数据之间需要添加适当的延时。

    4. 电源供应:LCD1602通常需要接入适当的电压,一般是5V。同时,LCD1602还需接入背光灯的供电源。

    5. 坐标设置:LCD1602有两行16个字符的显示区域,可以通过设置行号和列号来设置显示字符的位置。要注意LCD1602的寻址范围。

    6. 清屏操作:在需要清空LCD屏幕内容的时候,需要发送清屏命令,并等待清屏完成。

    7. 字符显示:通过发送数据命令,可以在LCD1602上显示字符、数字和其他符号。可以通过控制光标位置来实现不同位置的显示。

    8. 可读性:LCD1602具有特定的观察角度和反射性,要确保LCD1602安装在合适的位置以得到最佳的显示效果。

    2、 定时模块

    1. 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,开始定时器计时。
    2. void time0() interrupt 1: 这是定时器0的中断函数,当定时器0计时完成时,会触发这个中断。在中断函数中的操作包括:

      • 重置定时器初值为0xFC18,以便下一次定时。
      • 静态变量i自增。
      • 如果i达到1000(1秒),则将_seconds减一,即实现了一个1秒的计时效果。
      • 重置计数器i为0,以便下一次计时。

    3、延时模块

    1. void delay_10us(u16 ten_us): 这个函数实现了以10微秒为单位的延时函数。函数的实现是通过一个while循环,使变量ten_us递减直到为0。在每次循环中,都会消耗大约10微秒的时间。这个函数适用于较小的延时粒度,例如在控制LCD1602等设备时可能需要较精确的延时。

    2. void delay_ms(u16 ms): 这个函数实现了以毫秒为单位的延时函数。函数通过两层for循环实现延时。外层for循环控制延时的毫秒数,内层for循环为确保每个毫秒都持续一定时间而循环110次。这个函数适用于相对较长的延时,例如在需要较长暂停或延时操作时使用。

    4、拆弹模块

    1. 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。
    2. 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、随机数模块

    1. void initTimer(void): 这个函数用于初始化定时器,其中的操作包括:

      • TMOD = 0x01;: 设置定时器0为工作模式1,即16位定时器模式。
      • TH0 = 0xFC;: 初始化定时器初值的高8位。
      • TL0 = 0x18;: 初始化定时器初值的低8位。
      • TR0 = 1;: 启动定时器0,开始计时。
    2. 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

    1. #include "lcd1602.h"
    2. #if (LCD1602_4OR8_DATA_INTERFACE==0)//8位LCD
    3. void lcd1602_write_cmd(u8 cmd)
    4. {
    5. LCD1602_RS=0;//选择命令
    6. LCD1602_RW=0;//选择写
    7. LCD1602_E=0;
    8. LCD1602_DATAPORT=cmd;//准备命令
    9. delay_ms(1);
    10. LCD1602_E=1;//使能脚E先上升沿写入
    11. delay_ms(1);
    12. LCD1602_E=0;//使能脚E后负跳变完成写入
    13. }
    14. #else //4位LCD
    15. void lcd1602_write_cmd(u8 cmd)
    16. {
    17. LCD1602_RS=0;//选择命令
    18. LCD1602_RW=0;//选择写
    19. LCD1602_E=0;
    20. LCD1602_DATAPORT=cmd;//准备命令
    21. delay_ms(1);
    22. LCD1602_E=1;//使能脚E先上升沿写入
    23. delay_ms(1);
    24. LCD1602_E=0;//使能脚E后负跳变完成写入
    25. LCD1602_DATAPORT=cmd<<4;//准备命令
    26. delay_ms(1);
    27. LCD1602_E=1;//使能脚E先上升沿写入
    28. delay_ms(1);
    29. LCD1602_E=0;//使能脚E后负跳变完成写入
    30. }
    31. #endif
    32. #if (LCD1602_4OR8_DATA_INTERFACE==0)//8位LCD
    33. void lcd1602_write_data(u8 dat)
    34. {
    35. LCD1602_RS=1;//选择数据
    36. LCD1602_RW=0;//选择写
    37. LCD1602_E=0;
    38. LCD1602_DATAPORT=dat;//准备数据
    39. delay_ms(1);
    40. LCD1602_E=1;//使能脚E先上升沿写入
    41. delay_ms(1);
    42. LCD1602_E=0;//使能脚E后负跳变完成写入
    43. }
    44. #else
    45. void lcd1602_write_data(u8 dat)
    46. {
    47. LCD1602_RS=1;//选择数据
    48. LCD1602_RW=0;//选择写
    49. LCD1602_E=0;
    50. LCD1602_DATAPORT=dat;//准备数据
    51. delay_ms(1);
    52. LCD1602_E=1;//使能脚E先上升沿写入
    53. delay_ms(1);
    54. LCD1602_E=0;//使能脚E后负跳变完成写入
    55. LCD1602_DATAPORT=dat<<4;//准备数据
    56. delay_ms(1);
    57. LCD1602_E=1;//使能脚E先上升沿写入
    58. delay_ms(1);
    59. LCD1602_E=0;//使能脚E后负跳变完成写入
    60. }
    61. #endif
    62. #if (LCD1602_4OR8_DATA_INTERFACE==0)//8位LCD
    63. void lcd1602_init(void)
    64. {
    65. lcd1602_write_cmd(0x38);//数据总线8位,显示2行,5*7点阵/字符
    66. lcd1602_write_cmd(0x0c);//显示功能开,无光标,光标闪烁
    67. lcd1602_write_cmd(0x06);//写入新数据后光标右移,显示屏不移动
    68. lcd1602_write_cmd(0x01);//清屏
    69. }
    70. #else
    71. void lcd1602_init(void)
    72. {
    73. lcd1602_write_cmd(0x28);//数据总线4位,显示2行,5*7点阵/字符
    74. lcd1602_write_cmd(0x0c);//显示功能开,无光标,光标闪烁
    75. lcd1602_write_cmd(0x06);//写入新数据后光标右移,显示屏不移动
    76. lcd1602_write_cmd(0x01);//清屏
    77. }
    78. #endif
    79. void lcd1602_clear(void)
    80. {
    81. lcd1602_write_cmd(0x01);
    82. }
    83. void lcd1602_show_string(u8 x,u8 y,u8 *str)
    84. {
    85. u8 i=0;
    86. if(y>1||x>15)return;//行列参数不对则强制退出
    87. if(y<1) //第1行显示
    88. {
    89. while(*str!='\0')//字符串是以'\0'结尾,只要前面有内容就显示
    90. {
    91. if(i<16-x)//如果字符长度超过第一行显示范围,则在第二行继续显示
    92. {
    93. lcd1602_write_cmd(0x80+i+x);//第一行显示地址设置
    94. }
    95. else
    96. {
    97. lcd1602_write_cmd(0x40+0x80+i+x-16);//第二行显示地址设置
    98. }
    99. lcd1602_write_data(*str);//显示内容
    100. str++;//指针递增
    101. i++;
    102. }
    103. }
    104. else //第2行显示
    105. {
    106. while(*str!='\0')
    107. {
    108. if(i<16-x) //如果字符长度超过第二行显示范围,则在第一行继续显示
    109. {
    110. lcd1602_write_cmd(0x80+0x40+i+x);
    111. }
    112. else
    113. {
    114. lcd1602_write_cmd(0x80+i+x-16);
    115. }
    116. lcd1602_write_data(*str);
    117. str++;
    118. i++;
    119. }
    120. }
    121. }

    lcd1602.h

    1. #ifndef _lcd1602_H
    2. #define _lcd1602_H
    3. #include "public.h"
    4. //LCD1602数据口4位和8位定义,若为1,则为LCD1602四位数据口驱动,反之为8位
    5. #define LCD1602_4OR8_DATA_INTERFACE 0 //默认使用8位数据口LCD1602
    6. //管脚定义
    7. sbit LCD1602_RS=P2^6;//数据命令选择
    8. sbit LCD1602_RW=P2^5;//读写选择
    9. sbit LCD1602_E=P2^7; //使能信号
    10. #define LCD1602_DATAPORT P0 //宏定义LCD1602数据端口
    11. //函数声明
    12. void lcd1602_init(void);
    13. void lcd1602_clear(void);
    14. void lcd1602_show_string(u8 x,u8 y,u8 *str);
    15. #endif

    延时部分:

    public.c

    1. #include "public.h"
    2. void delay_10us(u16 ten_us)
    3. {
    4. while(ten_us--);
    5. }
    6. void delay_ms(u16 ms)
    7. {
    8. u16 i,j;
    9. for(i=ms;i>0;i--)
    10. for(j=110;j>0;j--);
    11. }

    public.h

    1. #ifndef _public_H
    2. #define _public_H
    3. #include "reg52.h"
    4. typedef unsigned int u16; //对系统默认数据类型进行重定义
    5. typedef unsigned char u8;
    6. typedef unsigned long u32;
    7. void delay_10us(u16 ten_us);
    8. void delay_ms(u16 ms);
    9. #endif

    还没做定义和声明分离的其他部分:

    main.c

    1. #include "public.h"
    2. #include "lcd1602.h"
    3. #include "stdio.h"
    4. #include "string.h"
    5. int cut = 0;
    6. int right = 0;
    7. int randomValue = 0;
    8. int ss = 0;//标志位
    9. unsigned int _seconds = 600;//设置默认读秒数为600
    10. char _min[2] = {0};
    11. char _second[2] = {0};
    12. sbit button = P3^1;
    13. sbit red = P3^2; // 设置k3为红线
    14. sbit blue = P3^3;//设置k4为蓝线
    15. void Sucess(void);
    16. // 初始化定时器
    17. void initTimer(void) {
    18. TMOD = 0x01; // 设置为定时器模式
    19. TH0 = 0xFC; // 初始化定时器初值
    20. TL0 = 0x18;
    21. TR0 = 1; // 启动定时器
    22. }
    23. // 生成随机数
    24. unsigned char getRandom(void) {
    25. unsigned char randomNum;
    26. TR0 = 0; // 停止定时器
    27. randomNum = TH0; // 获取定时器计数值作为随机数
    28. // 重新初始化定时器
    29. TH0 = 0xFC;
    30. TL0 = 0x18;
    31. TR0 = 1;
    32. return randomNum;
    33. }
    34. void get_second(unsigned int All_seconds)//参数为总秒数
    35. {
    36. int second = All_seconds % 60;//对应的余下的秒数
    37. char second_temp[2] = {0};
    38. // 提取十位和个位
    39. int tens = second / 10; // 十位数
    40. int ones = second % 10; // 个位数
    41. second_temp[0] = tens + 48;
    42. second_temp[1] = ones + 48;
    43. _second[0] = second_temp[0];
    44. _second[1] = second_temp[1];
    45. }
    46. void get_min(unsigned int All_seconds)//参数为总秒数,返回值是存储在数组中的分钟数
    47. {
    48. char min_temp[2] = {0};
    49. int minutes;
    50. int tens;
    51. int ones;
    52. if(All_seconds >= 60)
    53. {
    54. minutes = All_seconds / 60;
    55. }
    56. if(All_seconds < 60)
    57. {
    58. minutes = 0;
    59. }
    60. // 提取十位和个位
    61. tens = minutes / 10; // 十位数
    62. ones = minutes % 10; // 个位数
    63. min_temp[0] = tens + 48;
    64. min_temp[1] = ones + 48;
    65. _min[0] = min_temp[0];
    66. _min[1] = min_temp[1];
    67. }
    68. void time0_init(void)//定时器初始化
    69. {
    70. TMOD|=0X01;//选择为定时器0 模式,工作方式1
    71. TH0=0XFC; //给定时器赋初值,定时1ms
    72. TL0=0X18;
    73. ET0=1;//打开定时器0 中断允许
    74. EA=1;//打开总中断
    75. TR0=1;//打开定时器
    76. }
    77. void time0() interrupt 1 //定时器0 中断函数
    78. {
    79. static u16 i;//定义静态变量i
    80. TH0=0XFC; //给定时器赋初值,定时1ms
    81. TL0=0X18;
    82. i++;
    83. if(i==1000)//如果达到时间
    84. {
    85. i=0;//重置计数器
    86. _seconds--;//发生效果:读秒数减一
    87. }
    88. }
    89. void cut_line_init_32(void)
    90. {
    91. IE0=0; //中断标志位触发
    92. EX0= 1; //打开外部中断0
    93. EA= 1; //打开总中断开关
    94. IT0= 1; //外部中断0设为低电平触发 // 1则为下降沿触发
    95. PX0=1; //中断优先级
    96. }
    97. void int0() interrupt 0 using 1
    98. {
    99. //编写用户所需的功能代码
    100. cut = 1;
    101. ss = 32;
    102. }
    103. void cut_line_init_33(void)
    104. {
    105. IE1=0; //中断标志位触发
    106. EX1= 1; //打开外部中断0
    107. EA= 1; //打开总中断开关
    108. IT1= 1; //外部中断1设为低电平触发 // 1则为下降沿触发
    109. PX1=1; //中断优先级
    110. }
    111. void int1() interrupt 2 using 1
    112. {
    113. //编写用户所需的功能代码
    114. cut = 1;
    115. ss = 33;
    116. }
    117. //拆弹成功状态
    118. void Sucess(void)
    119. {
    120. int time = _seconds;
    121. char min[2] = {0};
    122. char second[2] = {0};
    123. strcpy(min,_min);
    124. strcpy(second,_second);
    125. lcd1602_show_string(0,0," ");//第一行显示
    126. lcd1602_show_string(0,1," ");//第二行显示
    127. lcd1602_show_string(5,0,min);//第一行显示
    128. lcd1602_show_string(7,0,":");//第一行显示
    129. lcd1602_show_string(8,0,second);//第一行显示
    130. lcd1602_show_string(5,1,"Sucess");//第er行显示
    131. lcd1602_show_string(10,0," ");//第一行显示
    132. while(1)
    133. {
    134. delay_ms(200);
    135. lcd1602_show_string(0,1," ");//第二行显示
    136. lcd1602_show_string(5,1,"Sucess");//第er行显示
    137. }
    138. }
    139. //爆炸状态
    140. void Boom(void)
    141. {
    142. while(1)
    143. {
    144. lcd1602_show_string(0,0," ");//第一行显示
    145. lcd1602_show_string(0,1," ");//第二行显示
    146. delay_10us(20000);
    147. lcd1602_show_string(0,0," Time Over");//第一行显示
    148. lcd1602_show_string(0,1," (((Boom)))");//第二行显示
    149. delay_10us(80000);
    150. }
    151. }
    152. //倒计时状态
    153. void Planted(void)
    154. {
    155. lcd1602_show_string(0,1," ");//第er行显示
    156. lcd1602_show_string(0,0," ");//第er行显示
    157. time0_init();
    158. //*********************************************ce shi
    159. while(1)
    160. {
    161. get_min(_seconds);
    162. get_second(_seconds);
    163. lcd1602_show_string(5,0,_min);//第一行显示
    164. lcd1602_show_string(7,0,":");//第一行显示
    165. lcd1602_show_string(8,0,_second);//第一行显示
    166. lcd1602_show_string(10,0," ");//第一行显示
    167. if(_seconds == 0)
    168. {
    169. Boom();//进入爆炸状态
    170. }
    171. if((ss + right) % 2== 0 && cut == 1)
    172. {
    173. Sucess();//进入成功状态
    174. }
    175. }
    176. }
    177. //待机状态
    178. void Standby(void)
    179. {
    180. while(1)
    181. {
    182. lcd1602_show_string(0,0,"Press to ");//第一行显示
    183. lcd1602_show_string(0,1,"Plant.");//第二行显示
    184. if(button == 0)
    185. {
    186. delay_ms(50);//防抖
    187. randomValue = getRandom(); // 获取随机数
    188. // 在这里可以使用随机数进行其他操作
    189. right = randomValue % 2;
    190. Planted();//状态切换
    191. }
    192. }
    193. }
    194. void main()
    195. {
    196. initTimer(); // 初始化定时器
    197. cut_line_init_32();
    198. cut_line_init_33();
    199. lcd1602_init();//LCD1602初始化
    200. red = 0;
    201. blue = 0;
    202. while(1)
    203. {
    204. Standby(); // 进入待机模式
    205. }
    206. }

    四、遇到的问题

    1. 语法问题:if中的判断没有使用双等号,导致程序一旦进入“待机模式”就会直接跳入“已拆解模式”。
    2. 细节问题:在初始化中断时,复制粘贴后记得修改寄存器名称,例如将IE0 = 0,改为IE1 = 0,不然中断无法正常触发。
    3. 细节问题:使用引脚前要查看对应开发版原理图手册和开发手册,避免使用了冲突的引脚,或者该引脚没有需要的功能。
    4. 编辑器使用问题:由于有段时间没有使用keil创建新工程,导致忘了如何添加路径,只需要在三个小箱子里加入文件后点击小魔术棒,然后点击C51加入路径就行了。

    五、资源共享

    Gitee下载:Keil_5_51project: 51学习示例icon-default.png?t=N7T8https://gitee.com/haozhe34866/keil_5_51project.git

    效果视频:

    【[51单片机]可随机引线“拆弹”LCD1602显示精确读秒】 https://www.bilibili.com/video/BV1FG3ne1EdF/?share_source=copy_web&vd_source=33adf9f4b4c4b1221219d2246aa376b1

  • 相关阅读:
    C语言入门(十)函数
    Impossible WHERE noticed after reading const tables
    vue-element-admin 常用工具、命令、安装及报错处理方法、注意事项等
    Git中tag的用法及作用
    【机器学习合集】深度学习模型优化方法&最优化问题合集 ->(个人学习记录笔记)
    map && set
    【毕业设计】基于Arduino的智能灌溉系统 - 嵌入式 单片机 物联网
    如何使用测试仪进行400G交换机性能测试
    传统 API 管理与测试过程正面临严峻的挑战
    【信号去噪】基于鲸鱼算法优化VMD实现信号去噪附matlab代码
  • 原文地址:https://blog.csdn.net/qq_74227445/article/details/139922702