参考https://blog.csdn.net/qq_38351824/article/details/89066141
    周期概念
    振荡周期:振荡源的周期,晶振周期或外加振荡周期
    状态周期:2个振荡周期,S周期
    机器周期:1机器周期=6状态周期=12振荡周期
    指令周期:完成1条指令所占用的全部时间,以机器周期为单位

    51单片机的周期*

    振荡周期=1/12us;
    状态周期=1/6us;
    机器周期=1us;
    指令周期=1~4us;

    概念
    两组定时器/计数器,既可以定时, 又可以计数;
    定时器/计数器工作的过程是自动完成的,不需要CPU的参与;
    根据机器内部的时钟或者是外部的脉冲信号对寄存器中的数据加1;
    重复加1的工作可以交给定时器/计数器处理;
    可以实现精确定时作用。

    实质
    就是累加计算器,每来一个脉冲(来自外部脉冲源T0或T1)自动加1,计数器每过一位由0变1,全为1时溢出,使相应的中断标志位(TCON中TF0或TF1)置1,向cpu发出中断请求。
    定时模式,则表示定时时间已到;
    计数模式,则表示计数值已满。

    定时/计数器工作方法
    image.png
    软件:即手写循环延时函数

    结构
    image.png
    定时/计数器的实质是加1计数器(16位),由高8位和低8位两个寄存器THx和TLx组成。
    TMOD是定时/计数器的工作方式寄存器,确定工作方式和功能;( 定时器0和1分别是0x01和0x10)
    TCON是控制寄存器,控制T0、T1的启动和停止及设置溢出标志。

    TMOD
    工作方式寄存器
    低四位用于T0,高四位用于T1。
    image.png
    GATE: 门控位 定时器/计数器 - 图4C/T :定时/计数模式选择位。C/T =0为定时模式;C/T =1为计数模式。
    M1M0:工作方式设置位。定时/计数器有四种工作方式。
    image.png
    TCON(0~3位用于控制外部中断,这里用高四位)
    TF1(TCON.7):T1溢出中断请求标志位。溢出时置1,CPU响应中断后自动清0。硬件自动操作,软件也可;
    TR1(TCON.6):T1运行控制位。
    TR1置1时,T1开始工作;
    TR1置0时,T1停止工作。
    TR1由软件置1或清0。可控制定时/计数器的启动与停止。
    TF0(TCON.5):T0溢出中断请求标志位,其功能与TF1类同。
    TR0(TCON.4):T0运行控制位,其功能与TR1类同。

    运用
    初始化程序应完成如下工作:
    对TMOD赋值,以确定T0和T1的工作方式;
    计算初值,并将其写入TH0、TL0或TH1、TL1;
    中断方式时,则对EA赋值,开放定时器中断;
    使TR0或TR1置位,启动定时/计数器定时或计数。

    工作方式
    /方式0
    IMG_E3135.JPG
    方式0为13位计数,由TL0的低5位(高3位未用)和TH0的8位组成。
    TL0的低5位溢出时向TH0进位,TH0溢出时,置位TCON中的TF0标志,向CPU发出中断请求。
    计数初值计算的公式为:X=2^13-N;

    定时器模式时有:N=t/ Tcy;
    门控位GATE具有特殊的作用。当GATE=0时,经反相后使或门输出为1,此时仅由TR0控制与门的开启,与门输出 1时,控制开关接通,计数开始;当GATE=1时,由外中断引脚信号控制或门的输出,此时控制与门的开启由外中断引脚信号和TR0共同控制。当TR0=1时,外中断引脚信号引脚的 高电平启动计数,外中断引脚信号引脚的低电平停止计数。 这种方式常用来测量外中断引脚上正脉冲的宽度。

    /方式1
    IMG_E3136.JPG
    计数个数与计数初值的关系为:X=2^16-N;
    例:计时50ms,用65536-50000再化为16进制,3CB0,3C给TL1,B0给TH1,等价于
    image.png
    这种算法一般不好用,建议stc软件生成
    image.png
    image.png

    1. #include "reg52.h"
    2. typedef unsigned int u8;
    3. typedef unsigned char u16;
    4. u8 i,num;
    5. u8 code smgduan[]={0x3f, 0x06, 0x4f, 0x66, 0x6d, 0x7d, 0x07,
    6. 0x7f, 0x6f, 0x77, 0x7c, 0x39, 0x5e, 0x79, 0x71}; //段选字码,存在数组中
    7. sbit D1=P2^0;
    8. void delay(u8 z)
    9. {
    10. u8 x,y;
    11. for(x=z; x>0; x--)
    12. {
    13. for(y=70; y>0; y--);
    14. }
    15. }
    16. void main()
    17. {
    18. TMOD=0x01; //设置定时器0为工作方式1
    19. TH0=(65536-50000)/256;
    20. TL0=(65536-50000)%256;
    21. EA=1; //开总中断
    22. ET0=1; //开定时器0中断
    23. TR0=1; //启动定时器0
    24. wela=1;
    25. P0=0xea; //先锁一个全部点亮
    26. wela=0;
    27. while(1)
    28. {
    29. if(i==20) //定时
    30. {
    31. i=0; //重回0,再累加20次,记1s
    32. num++;
    33. dula=1; //段选释放
    34. P0=smgduan[i];
    35. dula=0;
    36. delay(1000);
    37. }
    38. }
    39. }
    40. void enter0() interrupt 1 //进入定时器中断函数
    41. {
    42. TH0=(65536-50000)/256;
    43. TL0=(65536-50000)%256; //重装初值,再计数
    44. i++; //要20次中断,过1秒,i才能到20,再到main中运行,实现定时
    45. }

    /方式2
    IMG_3138.JPG
    自动重装初值,无需软件清0;
    TH1和TL1作为两个独立存储器用;
    如图。
    计数个数与计数初值的关系为:X=2^8-N
    工作方式2特别适合于用作较精确的脉冲信号发生器。

    /方式3
    方式3只适用于定时/计数器T0,定时器T1处于方式3时相当于TR1=0停止计数。T1不能再方式3工作,可独立工作。
    将T0分成为两个独立的8位计数器TL0和TH0 。
    IMG_E3140.JPG
    此时,T1在工作方式2,当作波特率发生器使用。

    1. void Timer0Init()
    2. {
    3. TMOD|=0X01; //定时器0,寄存器配置低4位,或运算不影响其他位
    4. TH0=0XFC; //gao8
    5. TL0=0X18; //di 8
    6. ET0=1; //t0计时器打开中断
    7. EA=1; //打开总中断
    8. TR0=1; //置位,开启定时器
    9. }

    IMG_3142.JPG

    定时器初值计算
    可以用软件计算
    机器周期也就是CPU完成一个基本操作所需要的时间,机器周期=1/单片机的时钟频率,51单片机内部时钟频率是外部时钟的12分频,也就是说当外部晶振的频率输入到单片机里面的时候要进行12分频。
    比如说你用的是12MHZ的晶振,那么单片机内部的时钟频率就是12/12MHZ,当你使用12MHZ的外部晶振的时候,机器周期=1/1M=1us。
    而我们定时1ms的初值是多少呢,1ms/1us=1000,也就是要计数1000个数,初值=65535-1000+1(因为实际上计数器计数到66636才溢出)=64536=FC18H
    也可以这样理解:
    定时器一旦启动,它便在原来的数值上开始加1计数,若在程序开始时,我们没有设置TH0和TL0,它们的默认值都是0,假设时钟频率为12MHz,12个时钟周期为一个机器周期,那么此时机器周期就是1µs,计满TH0和TL0就需要2^16-1个数,再来一个脉冲计数器溢出,随即向CPU申请中断。因此溢出一次共需65536µs,约等于65.5ms,如果我们要定时50ms的话,那么就需要先给TH0和TL0装一个初值,在这个初值的基础上计50000个数后,定时器溢出,此时刚好就是50ms中断一次,当需定时1s时,我们写程序时当产生20次50ms的定时器中断后便认为是ls,这样便可精确控制定时时间了。要计50000个数时,TH0和TL0中应该装入的总数是65536-50000=15536,把15536对256求模:15536/25 6=60装入TH0中,把15536对256求余:15536%256=176装入TL0中。

    下面是led通过定时器闪烁的例子。

    1. //led闪烁
    2. void main()
    3. {
    4. Timer0Init();
    5. while(1);
    6. }
    7. void Time0() interrupt 1 //中断
    8. {
    9. static u8 i; //一定要整型
    10. TH0=0XFC;
    11. TL0=0X18; //溢出后寄存器为0,重新装载
    12. i++;
    13. if(i==1000) //1s,因为1毫秒中断,要重复1000次
    14. {
    15. i=0;
    16. led=~led;
    17. }
    18. }

    注意:一般我们在中断服务程序中不要写过多的处理语句,因为如果语句过多,中断服务程序中的代码还未执行完毕,而下一次中断又来临,这样我们就会丢失这次中断,当单片机循环执行代码时,这种丢失累积出现,程序便完全乱套。一般我们遵循的原则是:能在主程序中完成的功能就不在中断函数中写,若非要在中断函数中实现功能,那么一定要高效、简洁。

    与数码管结合

    1. #include "reg52.h"
    2. typedef unsigned char u8;
    3. typedef unsigned char u16;
    4. u8 i;
    5. u8 code smgduan[]={0x3f, 0x06, 0x4f, 0x66, 0x6d, 0x7d, 0x07,
    6. 0x7f, 0x6f, 0x77, 0x7c, 0x39, 0x5e, 0x79, 0x71}; //段选字码,存在数组中
    7. sbit D1=P2^0;
    8. void delay(u8 z)
    9. {
    10. u8 x,y;
    11. for(x=z; x>0; x--)
    12. {
    13. for(y=70; y>0; y--);
    14. }
    15. }
    16. void main()
    17. {
    18. EA=1;
    19. EX0=1;
    20. IT0=1; //或TCON=0x01;
    21. wela=1;
    22. P0=0xea; //先锁一个全部点亮
    23. wela=0;
    24. while(1)
    25. {
    26. for(i=0;i<16; i++)
    27. {
    28. dula=1; //段选释放
    29. P0=smgduan[i];
    30. dula=0;
    31. delay(1000);
    32. }
    33. }
    34. }
    35. void enter0() interrupt 0 //跟一个中断函数
    36. {
    37. D1=0; //中断出来点个灯
    38. }