找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 19577|回复: 42
打印 上一主题 下一主题
收起左侧

51单片机入门经验分享4-定时器的使用

  [复制链接]
跳转到指定楼层
楼主
本帖最后由 dabing89 于 2018-10-14 18:56 编辑

                                                    定时器的使用 ---20181013


好,继续更新这个帖子,前边我们点亮了流水灯,然而定时是用DELAY实现的,实际上在实际的项目中,DELAY基本上不会用,如果一个程序中,主循环有大量的DELAY出现,那么就不合理了,所以必须要用定时器 ,保证程序的实时性,这一贴,我们写一个程序,用定时器0来实现LED间隔500MS闪烁的效果   ,代码如下:   
  1. /*******************************************************************
  2. * 文件名  LED0 500MS闪烁
  3. * 描述:        点亮LED---用定时器的办法
  4. * 2018-09-19 调试通过
  5. * 功能  入门模板
  6. * 作者: 大核桃
  7. * 版本号:V1.00(2018.09.19)
  8. ********************************************************************/
  9. #include "config.h"
  10. #include "intrins.h"


  11. /*******************************************************************
  12. * 文件名 变量重新定义区域
  13. * 描述:        
  14. * 功 能
  15. * 作者:大核桃
  16. * 版本号:V1.00(2018.09.17)
  17. ********************************************************************/

  18. typedef unsigned char uint8;//无符号字符型
  19. typedef unsigned int  uint16;//无符号整型
  20. typedef unsigned long uint32;//无符号长整型


  21. /*******************************************************************
  22. * 文件名:位重新定义区域 函数前置声明
  23. * 描述:        
  24. * 功 能
  25. * 作者:大核桃
  26. * 版本号:V1.00(2018.09.17)
  27. ********************************************************************/
  28. void Delay500ms();                //@11.0592MHz
  29. void MCU_Port_Init(void);
  30. void Bsp_Tim0_Init(void);

  31. sbit LED0 = P1^0;
  32. sbit LED1 = P1^1;
  33. sbit LED2 = P1^2;
  34. sbit LED3 = P1^3;
  35. sbit LED4 = P1^4;
  36. sbit LED5 = P3^2;
  37. sbit LED6 = P0^0;
  38. sbit LED7 = P0^1;

  39. bit flag500ms = 0;//500ms定时标志位


  40. /*******************************************************************
  41. * 文件名 main函数入口
  42. * 描述:        
  43. * 功 能
  44. * 作者:大核桃
  45. * 版本号:V1.00(2018.09.17)
  46. ********************************************************************/
  47. void main(void)
  48. {
  49.         MCU_Port_Init();//端口模式初始化函数
  50.         Bsp_Tim0_Init();//定时器0初始化函数


  51.         while(1)
  52.         {           
  53.                 if(flag500ms)
  54.                 {
  55.                         P2 = 0XFE;//1111_1110;
  56.                 }
  57.                 else
  58.                 {
  59.                    P2 = 0XFF;//1111_1111;        
  60.                 }

  61.         }
  62. }

  63. /*******************************************************************
  64. * 文件名:void MCU_Port_Init(void)
  65. * 描述:        MCU端口上电初始化函数
  66. * 功 能
  67. * 作者:大核桃
  68. * 版本号:V1.00(2018.09.17)
  69. ********************************************************************/
  70. void MCU_Port_Init(void)
  71. {
  72.         //第0 和1位配置推完输出模式,大电流
  73.         P0M1 = 0XFC; //        1111_1100
  74.         P0M0 = 0X03; // 0000_0011

  75.         //第01234位配置推完输出模式,大电流,567配置高阻输入,用于ADC
  76.         P1M1 = 0XE0; //1110_0000        
  77.         P1M0 = 0X1F; //0001_1111

  78.         //P2配置位准双向口
  79.         P2M1 = 0X00; //0000_0000        
  80.         P2M0 = 0X00; //0000_0000

  81.         P2 = 0XFF;//P2口初始化为1

  82.         //P5配置位准双向口
  83.         P5M1 = 0X00; //0000_0000        
  84.         P5M0 = 0X00; //0000_0000

  85.         P5 = 0XFF;//P5口初始化为1

  86.         //P3 23467推完输出
  87.         P3M1 = 0X00; //0000_0000        
  88.         P3M0 = 0XFC; //1101_1100

  89.         P3 = 0X23;  //0010_0011


  90.         //上电IO默认是0
  91.         LED0 = 1;//输出1
  92.         LED1 = 0;
  93.         LED2 = 0;
  94.         LED3 = 0;
  95.         LED4 = 0;
  96.         LED5 = 0;
  97.         LED6 = 0;
  98.         LED7 = 0;//
  99.                
  100. }

  101. /*******************************************************************
  102. * 文件名:void Bsp_Tim0_Init(void)
  103. * 描述:        定时器0初始化函数
  104. * 功 能
  105. * 作者:大核桃
  106. * 版本号:V1.00(2018.09.19)
  107. ********************************************************************/
  108. void Bsp_Tim0_Init(void)                //1000微秒@11.0592MHz
  109. {
  110.         AUXR |= 0x80;                //定时器时钟1T模式
  111.         TMOD &= 0xF0;                //设置定时器模式
  112.         TMOD |= 0X01;      
  113.         TH0 = 0xD4;                    //设置定时初值
  114.         TL0 = 0xCD;                    //设置定时初值
  115.         TR0 = 1;                    //定时器0开始计时
  116.         ET0 = 1;            //使能定时器0的中断
  117.         EA = 1;             //打开总中断
  118. }

  119. /*******************************************************************
  120. * 文件名:TIM0_IRQ_Handler
  121. * 描  述:中断服务函数        
  122. * 功  能 中断服务标号 INT0 ET0 INT1 ET1 UART1 ADC LVD TIME2
  123. * 优先级:                      0            1    2         3          4           5   6    12
  124. * 版本号:V1.00(2018.09.19)
  125. ********************************************************************/
  126. void TIM0_IRQ_Handler(void) interrupt 1
  127. {
  128.     static uint16 tmr500ms = 0;

  129.         TH0 = 0xD4;                    //设置定时初值
  130.         TL0 = 0xCD;                    //设置定时初值

  131.         tmr500ms++;
  132.         if(tmr500ms >= 500)
  133.         {
  134.                 tmr500ms = 0;

  135.                 flag500ms = !flag500ms;        //500MS闪烁
  136.         }
  137.         
  138.                
  139. }
复制代码


因为在实际使用中,定时器和中断都是在一起配合使用,所以这儿我们就不分开,但是要说的是,定时器是硬件,是单片机内部存在的一个模块,而中断仅仅是一种处理问题的机制,上面这个d代码看着不多,但是消息量很大,我们一点一点解剖,理解了定时器的的原理,等你上手STM32的时候,定时器原理可以直接不看,直接拿来用就好了。
    先从STC89C52RC的开始说起,我们知道,STC89C52RC是标准51内核,在标准51的体系下,12个时钟周期是一个机器周期,啥意思呢?比如你的外部晶振是11.0592MHZ,那么11059200的倒数,也就是周期了,这个倒数叫做时钟周期,也叫震荡周期,算一下时间,1/11059200 = 0.0904US,这就是STC89C52的时钟周期,那么51单片机就规定,12个这样的时钟周期为一个机器周期,所以在乘以12,那么一个机器周期的数值是1.085US,注意这是在11.0592MHZ下,如果是在12MHZ下,那么一个机器周期就是1US,这就是定时器的时间基准。我们再来看下,如果我们用STC89C52来做一个500MS的定时器该怎么做呢?配置如下即可实现:
  1. /*******************************************************************
  2. * 文件名:void Bsp_Tim0_Init(void)
  3. * 描述:        定时器0初始化函数
  4. * 功 能
  5. * 作者:大核桃
  6. * 版本号:V1.00(2018.09.19)
  7. ********************************************************************/
  8. void Bsp_Tim0_Init(void)                //1000微秒@11.0592MHz
  9. {
  10.         TMOD &= 0xF0;                //设置定时器模式
  11.         TMOD |= 0X01;      
  12.         TH0 = 0xFC;                    //设置定时初值
  13.         TL0 = 0x66;                    //设置定时初值
  14.         TR0 = 1;                    //定时器0开始计时
  15.         ET0 = 1;            //使能定时器0的中断
  16.         EA = 1;             //打开总中断

  17. }

  18. /*******************************************************************
  19. * 文件名:TIM0_IRQ_Handler
  20. * 描  述:中断服务函数        
  21. * 功  能 中断服务标号 INT0 ET0 INT1 ET1 UART1 ADC LVD TIME2
  22. * 优先级:                      0            1    2         3          4           5   6    12
  23. * 版本号:V1.00(2018.09.19)
  24. ********************************************************************/
  25. void TIM0_IRQ_Handler(void) interrupt 1
  26. {
  27.     static uint16 tmr500ms = 0;

  28.         TH0 = 0xFC;                    //设置定时初值
  29.         TL0 = 0x66;                    //设置定时初值

  30.         tmr500ms++;
  31.         if(tmr500ms >= 500)
  32.         {
  33.                 tmr500ms = 0;

  34.                 flag500ms = !flag500ms;        //500MS闪烁
  35.         }
  36.         
  37.                
  38. }
复制代码



还是来解释下,首先定时器的配置步骤是这样的:
1.先设置TMOD这个寄存器,选择定时器0的模式寄存器,配置定时器0为16位不可重装载模式

2.设置定时器的定时初值,高八位和低八位
3.打开定时器的运行标志位,因为TCON是一个可位寻址的寄存器,所以直接TR0 = 1;就好。
4.使能定时器0的中断ET0
5.打开总中断EA
OK,这样就配置好了寄存器,定时器也可以工作了,然而我们了解定时器是怎么运行的了吗?没有!!!很多人不知道为啥是这个数值,而且定时器的的初值还有好几种写法,如果有人用了不一样的而写法,你一定要知道是等价的写法。


  关于初值的计算
   我们知道定时器0是一个16位的定时器,最大计数65536(0-65535)分为高八位和低八位,TH0存储的是高八位的数据,TL0存储的是低八位的数据,0XFC是一个16进制数值,换算10进制是252,0X66是102,我们知道低八位最大计数到255,TH0就变成1,然后进位,清零,又开始从0计数,那么我们可以算算这个初值是多少?252*256 +102 = 64614,而64614的16进制表示形式就是0XFC66,这样我们就搞清楚定时器的计时原理了,如下所示,初值代码可以改写成这样:
  1. TH0 = (65535 - 921) / 256;                    //设置定时初值
  2.         TL0 = (65535 - 921) % 256;                    //设置定时初值
复制代码
也就是说,我们让单片机从64614开始计数,到65535溢出,总共计数921个,而我们又知道1个机器周期是1.085US,那么921个机器周期是多少呢?921*1.085 = 1000US,正好是1MS的定时,我们在程序中让其溢出500次,那么不就是500MS了吗?就是这样来的,原理一定要搞清楚。不管什么STM32,64,128都是这样的原理{:lol:}。关于中断使用的时候,打开使能就好了,EA是总中断使能位,如果这个不打开,ET0单独打开是没用的,这才是一把手。
   关于STC15W系列的定时器
好了,既然我们搞清楚了,STC89C52的定时器的原理了,我们来看下,STC15W的定时器配置,因为我们都是定时1MS,那么,为啥初值不一样呢?我们来算下STC15W的这个初值对应的10进制数值是多少?是54477,好陌生的数字,怎么来的呢?65536-54477 = 11059,也就是说在STC15W的内核下,我们只要计数11059个,就可以达到1MS的定时,我们知道,STC15W是单周期的时钟,也就是说我们不12分频,我们直接就是一个时钟周期就是一个机器周期,(1/11059200)*11059 = 1ms,明白了吧?所以,本节的程序代码,也可以这样写,是一样的作用的。
  1. /*******************************************************************
  2. * 文件名:void Bsp_Tim0_Init(void)
  3. * 描述:        定时器0初始化函数
  4. * 功 能
  5. * 作者:大核桃
  6. * 版本号:V1.00(2018.09.19)
  7. ********************************************************************/
  8. void Bsp_Tim0_Init(void)                //1000微秒@11.0592MHz
  9. {
  10.         AUXR |= 0x80;                //定时器时钟1T模式
  11.         TMOD &= 0xF0;                //设置定时器模式
  12.         TMOD |= 0X01;      
  13. //        TH0 = 0xD4;                    //设置定时初值
  14. //        TL0 = 0xCD;                    //设置定时初值
  15.         TH0 = (65535 - 11059) / 256;//设置定时初值
  16.         TL0 = (65535 - 11059) % 256;//设置定时初值
  17.         TR0 = 1;                    //定时器0开始计时
  18.         ET0 = 1;            //使能定时器0的中断
  19.         EA = 1;             //打开总中断

  20. }
复制代码




关于在单片机中大量存在的& |运算的详细说明
    其实这个,不太想说,但无奈上网看到好多初学者根本不知道& |的作用,还是详细的说明下比较好,以定时器0的配置为例。
    AUXR |= 0x80; AUXR是一个辅助寄存器,这不需要多说,|= 0x80有什么讲究呢?|(或),是让某一位置1的意思,让那一位置一呢?很明显,让是1的那一位置一,0X80不就是最高位是1吗?那就是让最高位置一好了,有人说,这有啥用呢?本来不就是1吗?人家还有后半句,而其他位保持不变,其他位?啥位?等于0的那些对吧?也就是低7位不变了。这样操作有啥好处呢?我们知道ET0 = 1;TR0 = 1;之所以可以这样操作,是因为他们可以被位寻址,可以进行单独的位操作,而AUXR是不可以进行位寻址的,因此一次操作必须操作8个位,你想想看,AUXR这个寄存器的功能如下图:

   你能保证直接让AUXR = 0X80,不对其他位造成影响吗?现在大家的编程还比较简单,只有一个定时器,要是用到3个,4个定时器呢?这样不就互相干扰了吗?扯淡么?所以 & |的重要性也就凸现出来了。
   再来看后面这2句,很明显TMOD是不可以位寻址的,按照我们刚才的分析,|是让某一位置一,那么&,自然就是让某一位清零了,来看下0XF0,二进制是1111_0000,也就是低4位置0,高4位不要管,因为&是乘法运算啊,只要都是1,那么就是1,很明显让低4位清零,下一句是0000_0001,让最低为置一啊,对吧,要注意,这里的2句是连续操作的,不是单独的的操作,什么意思?前者的运算结果,又给了后者,所以我们总体来看这2句代码,先让低四位清零,高四位不变,然后将这个结果进行或运算,让最低位置一,而高7位都不变,因为任何数|还是任何数啊,对吧,这就达到了一个互不干扰的目的,这样的代码在STM32上好多好多的,都是起到一个互不干扰的作用。这样的做法可以确保定时器0和定时器1是独立的,如果我们不这样做,你看看是工作在啥模式?除了定时器配置在16位不可重装模式在,定时器1被配置在了16位自动重装定时器,我们没有使用定时器1,万一出错怎么办?这就不好了。
  

关于自动重装载和不可自动重装模式
  其实,没啥太大的区别,如果是自动重装,那么在中断服务函数中,TH0 TL0就不需要去再去重新赋值了,直接删掉就好,如果不是自动重装,则必须要加。还差点忘了一个事,你怎么确定你的定时是500MS呢?答案不能靠眼睛看把,看看示波器观察的结果,嗯,是对的。如下图,1S 1HZ的方波信号

好了,今天就到这里吧,代码上传 004 定时器的使用.rar (30.68 KB, 下载次数: 87)






评分

参与人数 2黑币 +11 收起 理由
新乡家电维修 + 5 很给力!
jstele + 6

查看全部评分

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏34 分享淘帖 顶13 踩
回复

使用道具 举报

沙发
ID:371529 发表于 2018-10-14 14:51 | 只看该作者
我不明白为啥这么好的分享浏览量这么低呢,楼主加油
回复

使用道具 举报

板凳
ID:74187 发表于 2018-10-14 15:28 | 只看该作者
哈哈我哈哈 发表于 2018-10-14 14:51
我不明白为啥这么好的分享浏览量这么低呢,楼主加油

谢谢支持
回复

使用道具 举报

地板
ID:410928 发表于 2018-10-26 09:53 | 只看该作者
楼主厉害
回复

使用道具 举报

5#
ID:388099 发表于 2018-10-27 16:29 | 只看该作者
感谢分享,正在学习单片机,
回复

使用道具 举报

6#
ID:406748 发表于 2018-10-27 22:41 | 只看该作者
感谢分享
回复

使用道具 举报

7#
ID:85743 发表于 2018-10-29 14:15 | 只看该作者

感谢分享,正在学习单片机,
回复

使用道具 举报

8#
ID:417624 发表于 2018-10-29 22:45 | 只看该作者
真的好实用,正好解决了我的疑惑,力赞!!!!!
回复

使用道具 举报

9#
ID:400074 发表于 2018-11-2 06:55 | 只看该作者
挺不错的资料
回复

使用道具 举报

10#
ID:218360 发表于 2018-11-15 18:25 | 只看该作者
咋没有专门写汇编语言的内容?
回复

使用道具 举报

11#
ID:156220 发表于 2018-11-20 08:49 | 只看该作者
谢谢楼主的分享,很详细的讲解
回复

使用道具 举报

12#
ID:244544 发表于 2018-11-20 11:33 | 只看该作者
好东东,下载学习下!!1
回复

使用道具 举报

13#
ID:400349 发表于 2018-11-21 01:22 来自手机 | 只看该作者
楼主,我有个地方不懂,从第二次溢出开始,进入中断后tmr500ms不是都会重新赋值为零吗,这样子不是不会大于或等于500吗,那怎么执行if函数
回复

使用道具 举报

14#
ID:74187 发表于 2018-11-21 15:40 | 只看该作者
陈臣塵 发表于 2018-11-21 01:22
楼主,我有个地方不懂,从第二次溢出开始,进入中断后tmr500ms不是都会重新赋值为零吗,这样子不是不会大于 ...

void TIM0_IRQ_Handler(void) interrupt 1
{
    static uint16 tmr500ms = 0;

        TH0 = 0xFC;                    //设置定时初值
        TL0 = 0x66;                    //设置定时初值

        tmr500ms++;//这个你应该没有看到
回复

使用道具 举报

15#
ID:421232 发表于 2018-12-13 11:39 | 只看该作者
谢谢楼主分享
回复

使用道具 举报

16#
ID:450718 发表于 2018-12-24 14:33 | 只看该作者
学习 学习
回复

使用道具 举报

17#
ID:461889 发表于 2019-1-23 11:46 | 只看该作者
感谢分享
回复

使用道具 举报

18#
ID:345426 发表于 2019-3-23 21:42 | 只看该作者
谢谢分享好好学习。
回复

使用道具 举报

19#
ID:511868 发表于 2019-4-14 14:46 | 只看该作者
谢谢分享好好学习
回复

使用道具 举报

20#
ID:505803 发表于 2019-4-14 16:34 | 只看该作者
这个文章真是太接地气了,基本上把定时掰开了讲清楚了
说的非常好,初学者都能够看懂,
受益良多
回复

使用道具 举报

21#
ID:278200 发表于 2019-6-15 14:24 | 只看该作者
感谢分享!!!
回复

使用道具 举报

22#
ID:138119 发表于 2019-7-28 18:23 | 只看该作者
很适合初学者,或者这方面理解不清的朋友!
讲的非常好,楼主加油~~~~~
回复

使用道具 举报

23#
ID:546770 发表于 2019-7-28 18:37 | 只看该作者
这是真的好贴,加油!
回复

使用道具 举报

24#
ID:600135 发表于 2019-8-15 08:31 来自手机 | 只看该作者
楼主 是怎么自学的
回复

使用道具 举报

25#
ID:689073 发表于 2020-2-11 07:21 | 只看该作者


感谢分享,正在学习单片机,
回复

使用道具 举报

26#
ID:781074 发表于 2020-6-16 15:12 | 只看该作者
谢谢楼主的分享,很详细的讲解
回复

使用道具 举报

27#
ID:785551 发表于 2020-9-2 16:36 | 只看该作者
楼主,还有后续经验分享吗??
回复

使用道具 举报

28#
ID:816540 发表于 2020-9-15 22:38 | 只看该作者
讲解的很清楚,谢谢!
回复

使用道具 举报

29#
ID:863412 发表于 2021-1-12 16:38 | 只看该作者
楼主高手,谢谢了!
回复

使用道具 举报

30#
ID:878420 发表于 2021-1-23 20:14 | 只看该作者
感谢分享,正在学习单片机!
回复

使用道具 举报

31#
ID:883622 发表于 2021-2-10 20:52 | 只看该作者
回复

使用道具 举报

32#
ID:513213 发表于 2021-3-2 08:50 | 只看该作者
感谢分享,正在学习单片机
回复

使用道具 举报

33#
ID:928284 发表于 2021-7-9 17:57 | 只看该作者
温习定时器的使用,多看看有启发。
回复

使用道具 举报

34#
ID:497561 发表于 2022-3-7 09:43 | 只看该作者
对初学者很友发,值得多看几次
回复

使用道具 举报

35#
ID:1001745 发表于 2022-4-9 18:53 | 只看该作者
这样的C教材还是第一次拜读,比那些所谓名校的教材更加通俗易懂
回复

使用道具 举报

36#
ID:1007896 发表于 2022-5-1 20:34 | 只看该作者
别的不服,就服楼主。&、|、TH0、TL0讲得非常非常清楚!
回复

使用道具 举报

37#
ID:962396 发表于 2022-6-22 17:28 | 只看该作者
谢谢楼主我会来学习的
回复

使用道具 举报

38#
ID:260656 发表于 2022-10-19 10:44 | 只看该作者
就是定时器搞不懂,这文章太好了,万分感谢!
回复

使用道具 举报

39#
ID:1049825 发表于 2022-10-31 20:19 | 只看该作者
听君一席话,胜读十年书,拜谢!
回复

使用道具 举报

40#
ID:958799 发表于 2022-12-29 16:44 | 只看该作者
说实话,这是我看过讲定时器讲的最清楚的了,点赞
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表