首页 测试文本

清翔零基础教你学51单片机_个人学习笔记(10)_时计数器的定时,计数和中断(理论+实践)

说明

本人使用的是清翔的51单片机开发板,如果型号相同最方便,但是如果型号不同也可以参考,因为芯片都是一样的,只是外设不同而已,使用时只需要对照自己的开发板原理图稍微修改下引脚即可。

本次笔记将对应视频教程的第28,29,30,31集 定时计数器的定时和计数(理论+实践)

如果笔记之中有任何错误,请在评论区指出,谢谢

一、什么是定时/计数器

这里仅认为我用的STC89C52RC有2个16位定时/计数器(为什么“仅”,见)清翔零基础教你学51单片机_个人学习笔记(9)_中断系统和外部中断四、51中断源 部分。

定时器和计数器从本质上来说都是计数器,如果计数系统内部脉冲,那么就是定时器,如果计数外部脉冲,那么就是计数器。(T0为P3.4引脚,T1为P3.5引脚),每计数一个脉冲,值+1。

定时器作用:定时计数器可以用于精确事件定时,PWM脉宽调制,波形发生,信号时序测量的方面。

二、定时/计数器的使用

2.1 步骤

  1. 启动定时/计数器(通过TCON寄存器控制,上次笔记截图里面也有)
  2. 设置定时/计数器工作模式(TMOD寄存器)
  3. 查询定时/计数器是否溢出(读TCON内TF位)

模式1和模式2用的较多

2.2  定时器/计数器0工作模式

2.2.1 模式0

 速率调节

2.2.2 模式1

16位,其余和模式0相同

2.2.3 模式2

TL0是真正用作计数的,TH0用于存放想要重装的值,每次TL0溢出,就会自动把TH0的值放入TL0,并且TH0的值不变。

2.2.4 模式3

2.3 定时器/计数器1工作模式

除模式3下停止计数外,其余和计数器0相同

三、 定时器0的使用示范 定时器

3.1 创建工程

复制上一份工程文件夹,修改名称为“10.定时计数器”,进入项目文件夹,打开工程文件,删除main.c函数的内容。

3.2 main.c 以16位定时器为例

给定时器赋初值,从给的值开始向上计数,从而设定要计数的时间长度。假设要计数50ms,也就是50 000us,机器周期是1.085us。50000/1.085=46,082.9≈46083,也就是说要计数这么多次才能到50ms,16位最大计数值是65535,那么就要从65535 - 46083=19452,十六进制就是4BFC,把4B赋值给TH0,把FC赋值给TL0

void main()
{
    uchar temp;
    uchar sec;
    timer0init(0x01, 1, 0x4b, 0xfc);    //设置T0为定时器,模式1(16位) 0000 0001
    while(1)
    {
        if  (TF0 == 1)
        {
            temp++;
            if (temp == 20)
            {
                sec++;
                if (sec == 10) //sec是uchar类型,最大值是255
                    sec = 0;
                temp = 0;
            }
            TH0 = 0x4B; //重新放入初值
            TL0 = 0xFC;
            TF0 = 0; //软件清零溢出标志位
        }
        SEG_DIS3(sec);
    }
}
 
void timer0init(uchar tmod, uchar tr0, uchar th0, uchar tl0)
{
    TMOD = tmod;
    TR0 = tr0;        //可位寻址,是否允许T0开始计数
    //给定时器赋初值,从给的值开始向上计数,从而设定要计数的时间长度
    TH0 = th0;
    TL0 = tl0;
}

SEG_DIS3函数之前有写过,是在数码管显示一个3位数,直接把之前的函数复制归来,并且在main.h写好函数声明就行了。

3.3 main.h

写好函数声明

void timer0init(uchar tmod, uchar tr0, uchar th0, uchar tl0);

3.4 现象说明

数码管最左边3个从000变到009,之后变成000,如此循环,每秒数字增加1

四、 定时器0使用示范 计数器

把P34和P10用杜邦线连起来

4.1 main.c 以16位定时器为例 (delay延时)

void main()
{   
 timer0init(0x05, 1, 0, 0);
    while(1)
    {
        if (TL0 == 10)
            TL0 = 0;
        SEG_DIS(1,TL0);
        LED1 = ~LED1;
        delay(300);
    }
}

0x05就是 0000 0101,表示设置为计数器模式,模式1

注意在main.h里面写sbit LED1 = P1^0;,否则会报错。

SEG_DIS()函数之前用过直接复制过来,注意写函数声明

不用SEG_DIS3()的原因,是因为要动态显示需要不停地扫描,但是这里用了delay(300),那么芯片就会去计算加减,无法扫描,数码管只会显示最后1位,前面两个只会在更新的时候闪一下

4.2 现象说明

LED1不停地闪烁,可以把LED1上方的跳线帽拔下来。数码管第1位从0~9不停的变化

4.3 用T1来延时,T0计数

main.c

void main(){    
    uchar temp;
    timer1init(0x10, 1, 0x4b, 0xfc);    //定时器1初始化为定时模式,设置初值计时50ms
    timer0init(0x05, 1, 0, 0);
    while (1)
    {
        if  (TF1 == 1)
        {
            temp++;
            if (temp == 2)
            {
                LED1 = ~LED1;
                temp = 0;
            }
            TH1 = 0x4B; //T1重新放入初值
            TL1 = 0xFC;
            TF1 = 0; //T1软件清零溢出标志位
        }
        if(TH0 != 0)
        {
            TH0 = 0;
            TL0 = 0;
        }
        SEG_DIS3(TL0);
    }
}
 
void timer1init(uchar tmod, uchar tr1, uchar th1, uchar tl1)
{
    TMOD |= tmod;   //注意是“或等于”,只把想要的位置更改成1而不去动其他位置
    TR1 = tr1;  //可位寻址,是否允许T1开始计数
    TH1 = th1;  //设置初值
    TL1 = tl1;
}
 
void timer0init(uchar tmod, uchar tr0, uchar th0, uchar tl0)
{
    TMOD |= tmod;   //注意是“或等于”,只把想要的位置更改成1而不去动其他位置
    TR0 = tr0;        //可位寻址,是否允许T0开始计数
    //给定时器赋初值,从给的值开始向上计数,从而设定要计数的时间长度
    TH0 = th0;
    TL0 = tl0;
}

注意,定时/计数器初始化函数我把TMOD改成了或等于。

然后在main.h把函数声明写进去就可以了。

4.4 现象说明

数码管3个都可以显示,从000显示到255,再变成000循环。

五、定时器0使用示范 定时器中断

5.1 main.c

uchar temp;
uchar sec;    //由于在中断函数也使用了这两个变量,所以定义为全局变量
 
void main() {    
        //定时器中断
        //T0定时器模式 中断
 
    timer0init(0x01, 1, 0x4b, 0xfc);    //设置T0为定时器,模式1(16位) 0000 0001
    while(1)
    {
        SEG_DIS3(sec);
    }
    
}
 
void timer0() interrupt 1
{
    temp++;
    if (temp == 20)
    {
        sec++;
        if (sec == 255) //sec是uchar类型,最大值是255
            sec = 0;
        temp = 0;
    }
    TH0 = 0x4B; //重新放入初值
    TL0 = 0xFC;
}
 
void timer1init(uchar tmod, uchar tr1, uchar th1, uchar tl1)
{
    EA = 1;
    ET1 = 1;
    TMOD |= tmod;   //注意是“或等于”,只把想要的位置更改成1而不去动其他位置
    TR1 = tr1;  //可位寻址,是否允许T1开始计数
    TH1 = th1;  //设置初值
    TL1 = tl1;
}
 
void timer0init(uchar tmod, uchar tr0, uchar th0, uchar tl0)
{
    EA = 1;         //打开总中断
    ET0 = 1;        //打开T0中断
    TMOD |= tmod;   //注意是“或等于”,只把想要的位置更改成1而不去动其他位置
    TR0 = tr0;        //可位寻址,是否允许T0开始计数
    //给定时器赋初值,从给的值开始向上计数,从而设定要计数的时间长度
    TH0 = th0;
    TL0 = tl0;
}

注意在定时器初始化函数里面我把中断打开了

5.2 现象说明

数码管3位数从0~254循环显示1秒增加1

六、利用定时器中断进行数码管动态扫描

main.c

uchar temp;
void main()
{
    timer0init(0x01, 1, 0xfc, 0x65);    //设置T0为定时器,模式1(16位) 0000 0001
    while(1)
    {
        key_scan();
        if (keyval == 16)
            temp++;
        else if (keyval == 17 && temp > 0)
            temp--;
        keyval = 20;
    }
}
 
void timer0() interrupt 1
{
    SEG_DIS3(temp);
    TH0 = 0xfc;
    TL0 = 0x65;
}

这里我设置的定时时长为1ms,也可以自己计算改其他数值

while(1)也可以这样写

    while(1)
    {
        if (key_s2 == 0)
        {
            delay(20);
            if (key_s2 == 0)
                temp++;
            while(!key_s2);
        }
        else if (key_s3 == 0 && temp > 0)
        {
            delay(20);
            if (key_s3 == 0)
                temp--;
            while(!key_s3);
        }
    }

不使用key_scan函数,直接判断对应按键是否按下,它的好处就是可以在按下之后数码管值立刻改变,而不必等到松手后才改变。

6.1 现象说明

按下s2,数码管3位数+1,按下s3,数码管数值-1,当数码管数值为0,s3无效

本次笔记将对应视频教程的第28,29,30,31集 定时计数器的定时和计数(理论+实践),到此结束,我感觉内容还是比较清晰的,关于定时计数器的模式0和模式3,视频教程中也并未详细说明,感兴趣的朋友可以根据知识点自行研究。至于8位自动重装模式也比较好理解,设置好要重装的数就行,其实如果用模式1,在初始化和中断里面再给他手动重装,差不多相当于一个16位手动重装定时器,可以用来代替模式2。

下一次笔记将对应视频教程的第32,33集 串口通信(理论+实践)

如果笔记之中有任何错误,请在评论区指出,谢谢

posted @ 2025-11-08 15:18  BO_S  阅读(13)  评论(0)    收藏  举报
页脚 测试文本