一、实验目的
1、了解读取和清零定时器标志位的方法。
2、了解定时器中断的方法。
3、了解定时器初始化设置的方法。
二、实验内容
1、完成读取定时器溢出标志位来控制流水灯
2、完成定时器中断服务函数控制流水灯
三、实验原理
只用一个定时器:
定时器级联:
四、实验电路与程序
1、软件实验一:读取定时器溢出标志位来控制流水灯。
1)实验要求:读取定时器溢出标志位并计数,每过一秒钟流水灯移位一位。
2)实验目的:1. 掌握定时器初始化设置方法;2. 掌握读取和清零定时器溢出标志位TF的方法。
3)实验说明:通过本实验,可以了解单片机定时器初始化设置方法,掌握读取和清零定时器溢出标志位TF的方法,同时也可以了解单片机编程,调试方法。
4)、程序框图
5)、代码
main.c
#include <reg52.h>1. #include <key.h>
2. #include <interrupt.h>
3. int task_flag;
4. int timer_flag;
5. int timer_flag_2;
6. void main()
7. {
8. int count=0;
9. Config_Timer();//初始化定时器
10. //Config_Timer2();
11. P1=0xFE;//初始化GPIO
12. while(1)//任务调度器
13. {
14. //定时器2控制流水灯
15. // if(TF2)
16. // {
17. // count++;
18. // TF2=0;
19. // TH2=(65536-50000)/256;
20. // TL2=(65536-50000)%256; //12M,定时器2赋初值
21. // if(count>20)
22. // {
23. // count=0;
24. // flow_forward();//流水灯正流
25. //
26. // }
27. // }
28. //定时器1控制流水灯
29. if(TF1)//如果定时器1溢出
30. {
31. count++;
32. TF1=0;//
33. TH1=(65536-50000)/256;
34. TL1=(65536-50000)%256; //12M,定时器1赋初值,每次计数50ms
35. if(count>20) //一共计20次,总共1s流一下灯
36. {
count=0;flow_back();//流水灯反流1.
2. }
3. }
4.
5. }
6. }
key.h7. #ifndef _KEY_H
8. #define _KEY_H
9. #include <reg52.h>
10. void flow_forward(void);
11. void flow_back(void);
12. #endif
key.c#include <key.h>1.
2. void flow_forward(void)
3. {
4. P1=(P1<<1)|(P1>>7);//循环左移1位
5. }
6. void flow_back(void)
7. {
8.
9. P1=(P1>>1)|(P1<<7);//循环右移1位
10.
11. }
interrupt.h#ifndef _INTERRUPT_H1. #define _INTERRUPT_H
2. #include <reg52.h>
3. #include <key.h>
4. void delay(unsigned int z);
5. void Config_Timer(void);
6. void Config_Timer2(void);
7. #endif
interrupt.c#include <interrupt.h>1.
2. void Config_Timer(void)
3. {
TMOD=0x11;//设定时器0和1为工作方式1(M1 M0为01),是向上计数TH0=(65536-50000)/256;//装初值12M晶振定时50ms数为45872,高位TL0=(65536-50000)%256;//低位EA=1;//开总中断ET0=1;//开定时器0中断TR0=1;//启动定时器01.
TH1=(65536-50000)/256;//装初值12M晶振定时50ms数为45872,高位TL1=(65536-50000)%256;//低位EA=1;//开总中断ET1=1;//开定时器1中断TR1=1;//启动定时器11. }
2.
3. void Config_Timer2(void)
4. {
5. TH2=(65536-50000)/256;
6. TL2=(65536-50000)%256; //定时器2赋初值
7. T2CON=0; //配置定时器2控制寄存器
8. IE=0xA0; //1010 0000开总中断,开外定时器2中断,可按位操作:EA=1; ET2=1;
9. TR2=1; //启动定时器2
10. }
2、软件实验二:定时器中断服务函数控制流水灯流水时间
1)实验要求:按键1按下时流水灯正流,按键0按下时流水灯反流,流水灯移位时间由定时器中断控制。
2)实验目的:1. 掌握中断服务子程序的编写方法; 2. 掌握定时器中断的配置方法。
3)实验说明:通过本实验,可以了解单片机掌握中断服务子程序的编写方法和定时器中断的配置方法,同时也可以了解单片机编程,调试方法。
4)、程序框图
1.定时时间由定时器0提供
2.定时时间由T0和T1级联提供
5)、代码
1.定时时间由定时器0或定时器2提供
TH1=(65536-50000)/256;//装初值11.0582晶振定时50ms数为45872,高位
main.c
1. #include <reg52.h>
2. #include <key.h>
3. #include <interrupt.h>
4. int task_flag;
5. int timer_flag;
6. int timer_flag_2;
7. void main()
8. {
9. Config_EXTI();//初始化外部中断
10. Config_Timer();//初始化定时器
11. P1=0xFE;//初始化GPIO
12. while(1)//任务调度器
13. {
14. if(timer_flag)//每1000ms执行一次任务
15. {
16. timer_flag=0;
17. switch (task_flag)//判断执行哪个任务
18. {
case 1 :flow_forward();//流水灯正流break;case 2 :flow_back();//流水灯反流break;1. }
2. }
3. }
4. }
key.h
1. #ifndef _KEY_H
2. #define _KEY_H
3. #include <reg52.h>
4. unsigned char key_scan(void);
5. void flow_forward(void);
6. void flow_back(void);
7. #endif
key.c
1. #include <key.h>
2.
3. void flow_forward(void)
4. {
5. P1=(P1<<1)|(P1>>7);//循环左移1位
6. }
7. void flow_back(void)
8. {
9.
10. P1=(P1>>1)|(P1<<7);//循环右移1位
11.
12. }
interrupt.h
1. #ifndef _INTERRUPT_H
2. #define _INTERRUPT_H
3. #include <reg52.h>
4. #include <key.h>
5. void delay(unsigned int z);
6. void Config_EXTI(void);
7. void Config_Timer(void);
8. extern int task_flag;
9. extern int timer_flag;
10. extern int timer_flag_2;
11. #endif
interrupt.c
1. #include <interrupt.h>
2. void delay(unsigned int z)//延时ms
3. {
4. unsigned int x,y;
5. for(x = z; x > 0; x–)
6. for(y = 114; y > 0 ; y–);
7. }
8. void Config_Timer(void)
9. {
TMOD=0x11;//设定时器0和1为工作方式1(M1 M0为01),是向上计数TH0=(65536-50000)/256;//装初值11.0582晶振定时50ms数为45872,高位TL0=(65536-50000)%256;//低位EA=1;//开总中断ET0=1;//开定时器0中断TR0=1;//启动定时器0
TL1=(65536-50000)%256;//低位EA=1;//开总中断ET1=1;//开定时器1中断TR1=1;//启动定时器11. }
2.
3. void Config_EXTI(void)
4. {
5. EA=1;//开中断总允许
6. IT0=1;//下降沿触发外部中断0
7. EX0=1;//开外部中断0
8. IT1=1;//下降沿触发外部中断1
9. EX1=1;//开外部中断1
10. }
11.
12. void EXT0_Handle() interrupt 0
13. {
14. delay(10);
15. task_flag=1;//开启正向流水灯任务
16. }
17.
18. void EXT1_Handle() interrupt 2
19. {
20. delay(10);
21. task_flag=2;//开启反向流水灯任务
22. }
23.
24. void T0_Handle() interrupt 1
25. {
static int num=0;1. //重装初值
TH0=(65536-50000)/256;//高位1. TL0=(65536-50000)%256;//低位
2.
3. if(num<20)//每1000ms把timer_flag置位
4. {
5. num++;
6. }
7. else
8. {
9. num=0;
10. timer_flag=~timer_flag;
11. }
12. }
13.
14. void T1_Handle() interrupt 3
15. {
static int num=0;TH1=(65536-50000)/256;//高位1. TL1=(65536-50000)%256;//低位
2.
3. if(num<20)//每1000ms把timer_flag置位
4. {
5. num++;
6. }
7. else
8. {
9. num=0;
10. timer_flag_2=~timer_flag_2;
11. }
12. }
2.定时时间由T0和T1级联提供
main.c1. #include <reg52.h>
2. #include <key.h>
3. #include <interrupt.h>
4. int task_flag;
5. int timer_flag;
6. int timer_flag_2;
7. void main()
8. {
9. Config_EXTI();//初始化外部中断
10. Config_Timer();//初始化定时器
11. P1=0xFE;//初始化GPIO
12. while(1)//任务调度器
13. {
14. if(timer_flag)//每1000ms执行一次任务
15. {
16. timer_flag=0;
17. switch (task_flag)//判断执行哪个任务
18. {
case 1 :flow_forward();//流水灯正流break;case 2 :flow_back();//流水灯反流break;1. }
2. }
3. }
4. }
interrupt.h
1. #ifndef _INTERRUPT_H
2. #define _INTERRUPT_H
3. #include <reg52.h>
4. #include <key.h>
5. void delay(unsigned int z);
6. void Config_EXTI(void);
7. void Config_Timer(void);
8. extern int task_flag;
9. extern int timer_flag;
10. extern int timer_flag_2;
11. sbit P2_0=P2^0;
12. #endif
interrupt.c
1. #include <interrupt.h>
2. void delay(unsigned int z)//延时ms
3. {
4. unsigned int x,y;
5. for(x = z; x > 0; x–)
6. for(y = 114; y > 0 ; y–);
7. }
8.
9.
10. void Config_Timer(void)
11. {
12. TMOD=0x61;//timer0方式1,timer1方式2
TH0=(65536-50000)/256;//装初值12M晶振定时50ms数为45872,高位TL0=(65536-50000)%256;//低位1.
2. TH1=256-10;//time1装初值20
3. TH1=256-10;
4.
5. IP=0x05;
6. EA=1;
7. ET0=1;
8. ET1=1;
9. TR0=1;
10. TR1=1;
11. }
12. void Config_EXTI(void)
13. {
14. EA=1;//开中断总允许
15. IT0=1;//下降沿触发外部中断0
16. EX0=1;//开外部中断0
17. IT1=1;//下降沿触发外部中断1
18. EX1=1;//开外部中断1
19. }
20.
21. void EXT0_Handle() interrupt 0
22. {
23. delay(10);
24. task_flag=1;//开启正向流水灯任务
25. }
26.
27. void EXT1_Handle() interrupt 2
28. {
29. delay(10);
30. task_flag=2;//开启反向流水灯任务
31. }
32.
33. void T0_Handle() interrupt 1
34. {
static int num=0;1. //重装初值
TH0=(65536-50000)/256;//高位1. TL0=(65536-50000)%256;//低位
2.
3. P2_0=!P2_0;//P2_0连T1输入
4. T1=P2_0;//如果P2_0直接连T1,这句可以注释掉
5. }
6.
7. void T1_Handle() interrupt 3
8. {
9. timer_flag=~timer_flag;
10. }
key.h
1. #ifndef _KEY_H
2. #define _KEY_H
3. #include <reg52.h>
4. unsigned char key_scan(void);
5. void flow_forward(void);
6. void flow_back(void);
7. #endif
key.c
1. #include <key.h>
2.
3. void flow_forward(void)
4. {
5. P1=(P1<<1)|(P1>>7);//循环左移1位
6. }
7. void flow_back(void)
8. {
9.
10. P1=(P1>>1)|(P1<<7);//循环右移1位
11.
12. }
汇编程序:13. ORG 0000H ;程序执行的起始地址
14. LJMP Main ;跳转到main函数
15.
16. ORG 000BH ;定时器中断0起始地址
17. LJMP 0100H ;定时器中断0服务子程序地址
18. ORG 001BH ;定时器中断1起始地址
19. LJMP 0200H ;定时器中断1服务子程序地址
20.
21. Main: ;主函数
22. MOV P1, #0FEH ;P1口初始化
23. MOV P2, #001H ;P2口初始化
24.
25. SETB EA ;开定时器中断
26. SETB ET0
27. SETB ET1
28. SETB TR0
29. SETB TR1
30.
31. MOV TMOD, #061H ;tim0方式1,tim1方式2
32. MOV TH0, #03CH ;(65536-45872)/256
33. MOV TL0, #0B0H ;12M晶振定时50ms
34. MOV TH1, #0F6H ;
35. MOV TL1, #0F6H ;自动加载值,每次计10次溢出
36.
37. LOOP: JMP LOOP ;while(1)死循环
38.
39. ORG 0100H ;定时器中断0服务子程序
40. TIM0:
41. MOV A, P2
42. XRL A, #01H
43. MOV P2, A ;通过P2^0给T1中断信号
44. MOV TH0, #03CH ;(65536-45872)/256
45. MOV TL0, #0B0H ;12M晶振定时50ms
46. RETI ;中断返回
47.
48. ORG 0200H ;定时器中断1服务子程序
49. TIM1:
50.
51. MOV A, P1
52. RL A
53. MOV P1, A ;P1移位一位,流水灯
54. RETI
55.
56. END
五、实验总结
- 在键盘扫描程序中,卡的比较久时间的是点灯。后面发现不同的开发板,灯的位置不一样。第二个卡的比较久的地方是判断P3口时,没有考虑到高四位的情况,考虑进去之后,switch p3就正常了。
2.设计键盘外部中断的时候,考虑到如果把流水灯放在中断回调函数里面进行的话,会导致执行回调函数的时候,别的中断来了会很麻烦。于是改为在回调函数里面设置标志位,while(1)里面根据标志位来选择执行哪个点灯代码。但是流水灯需要延时,如果用跑空循环来作延时的话,有点像是阻塞型任务,别的中断来的时候,中断套中断,就很麻烦。于是就开了一个定时器,定时器中断里面设置一个flag,每1秒钟flag置位一下,然后去看看要不要切换任务,以及执行哪个任务。这样子就给单片机节省出大量的资源来了。 - 汇编代码比较遗憾的地方是没有加进定时器中断,delay用跑空循环实现。
4.keil在option里面的target应该设置时钟的频率,proteus也应该设置晶振频率,否则在proteus里面仿真的时候,定时器的实际定时时间可能会有出入。
5.proteus如果接的上拉电阻名字叫“pullup”,则与之相连的单片机io口会一直是高电平,导致流水灯流不起来(因为FF移位之后还是FF),因此要使用普通电阻。
6.keil汇编时,十六进制数如果是以字母开头的,要加0,例如0FEH,不然会报错。