当前位置: 首页 > article >正文

定时器中断方式

基于STC89C52RC芯片

使用说明

定时器的基本工作原理,是使用一个n位的脉冲计数器,对时钟信号的脉冲进行计数,每个脉冲加1,当脉冲计数器达到最大值(2n)时,也就是溢出时,触发定时器中断。

定时的时间会受到以下几个因素的影响。

  • 脉冲计数器的位数
  • 脉冲计数的初始值
  • 时钟信号的频率

启用定时器中断

STC89C52系列共有3个定时器,每个定时器都有其对应的中断。定时器0的中断允许控制位位于IE寄存器

在这里插入图片描述

开启定时器0的中断需要做出如下配置。

// 中断总开关
EA = 1;
// 定时器0中断开关
ET0 = 1;

定时器工作方式

STC89C52系列的定时器有两种工作方式

  • 定时:用于产生精确的时间延迟
  • 计数:用于统计外部脉冲信号的个数

两种工作方式的本质是相同的,都是使用脉冲计数器对脉冲进行计数

  • 定时方式下,脉冲信号为系统时钟信号
  • 计数方式下,脉冲信号来自单片机外部引脚

每个定时器都有一个控制位,用于设置计数/定时方式。定时器0的控制位是TMOD(Timer Mode,定时器模式)寄存器中的C/T(Counter/Timer)位。

在这里插入图片描述

该位的作用可参考下图(定时器结构图)

在这里插入图片描述

因此,如需使用定时器方式,应将C/T控制位设置为0,注意 TMOD寄存器不可位寻址。

定时器工作模式

  • 模式0——13位定时/计数器
    该模式下的脉冲计数器共有13位,最大计数为8192。如下图所示,TL0和TH0为两个8位寄存器,用于存储脉冲计数器数值,该模式下TL0只用到了低5位。

在这里插入图片描述

  • 模式1——16位定时/计数器
    该模式的脉冲计数器共有16位,最大计数为65536。如下图所示,TL0的8位和TH0的8位都用到了。

在这里插入图片描述

  • 模式2——8位自动重装载
    前两种模式,一次定时完毕后,如需再次定时,需要开发者重新为脉冲计数器设定初始值。而该模式可以在脉冲计数器溢出时,自动重新设置初始值,很适合用于执行周期性任务。
    该模式下,只用TL0寄存器用于存储脉冲计算器数值,TH0则用于存储脉冲计数器的初始值,每次TL0溢出之后, 都会自动将TH0的值重新装入TL0。

在这里插入图片描述

  • 模式3——双8位定时/计数器
    该模式下,TL0和TH0分别用作一个8位脉冲计数器,如果需要使用两个8位定时器可使用该模式。

在这里插入图片描述

这四种工作模式需要两个控制位进行设置,两个控制位位于TMOD(Timer Mode,定时器模式)寄存器

在这里插入图片描述

在这里插入图片描述

设置脉冲计数器初始值

由于51单片机定时器是在脉冲计数器溢出时触发中断,因此定时的长短需要通过脉冲计数器的初始值控制。因此在使用定时器时,需要先根据期望的定时长短计算出脉冲计数器的初始值。

下面以定工作模式1(16位)为例,介绍初始值的计算过程。

1)明确每个计数脉冲的时间
根据定时器的结构图可以看出,传递给脉冲计数器的信号是系统时钟信号经过分频后得到,且分频可选两种,分别是12分频和6分频,默认是12分频。

在这里插入图片描述

当前系统的时钟频率为11.0592MHz,也就是11059200Hz,所以计数脉冲的频率是11059200/12 Hz,因此一个计数脉冲的时间是12/11059200 s,大约是1.08us。

2)计算所需脉冲个数
明确每个计数脉冲的时长之后,在根据期望定时时长便能计算出所需脉冲个数。假如现在需要定时1ms,那么1ms需要的脉冲个数应为0.001/(12/11059200)。

3)计算脉冲计数器初始值
假如现在需要定时1ms,那么1ms需要的脉冲个数应为0.001/(12/11059200),因此定时器的初始值应为65536-0.001/(12/11059200),大约等于64614。
计算完毕后,需要将该值赋予TL0(低8位)和TH0(高8位),如下。

TL0 = 64614;
TH0 = 64614 >> 8;

启动定时器

两种方式
-单片机内部的寄存器控制
单片机的外部引脚控制

在这里插入图片描述

  • 当GATE=0时,外部引脚(INT0,P3.2)无效,此时只能由内部寄存器TR0控制,当TR0=1时,脉冲计数器开始计数,TR0=0时,停止计数。
  • 当GATE=1时,外部引脚(INT0,P3.2)生效,此时只有当内部寄存器TR0和外部引脚INT0都为1时,脉冲计数器才开始计数,否则停止计数。

定时器0的GATE控制位位于TMODE寄存器

在这里插入图片描述

定时器0的TR0控制位,位于TCON寄存器

在这里插入图片描述

实现思路

1)启用定时器0中断

// 中断总开关
EA = 1;
// 定时器0中断开关
ET0 = 1;

2)选择定时器0工作模式
首先需要明确定时/计数的工作方式,其次还需选择脉冲计数器的工作模式。此处选择计时+模式1(16位),具体配置如下。

在这里插入图片描述

另外由于TMOD寄存器不可位寻址,所以可在设置工作模式的同时,将 GATE位也一并设置好,当前案例不需要外部引脚控制定时器,因此将GATE设置为0即可。
TMOD寄存器低四位的值应为0001,而高四位的值应保持原值

// GATE=0;C/T=0;M1=0,M0=1
TMOD &= 0xF0;
TMOD |= 0x01;

3)设置脉冲计数器的初始值
当前需求是令LED1每秒钟闪烁一次,具体来说就是每隔0.5s改变一下LED1的状态,显然这是一个周期性任务。对于该任务,我们可以先考虑为定时器定时0.5s,然后在定时器中断触发后,再次定时0.5s,这样就能实现周期性任务了。
但是需要注意,0.5s所需的脉冲个数为0.5/(12/11059200)= 460800个,显然已经超出了16位置脉冲计数器的最大值(65536),也就是说定时器不支持0.5s的长时间定时。
针对这种情况,就需要令脉冲计数器溢出多次来达到期望的定时时长,具体来讲就是设定一个较短的定时,比如1ms,中断之后,再次定时1ms,直到达到期望的定时时长之后,再去执行具体的任务。
综上所述,对于当前需求,我们就可以将定时时长设置为1ms,每次中断之后,就再定时1ms,除此之外,我们还需要对中断的次数进行统计,每中断500次,就改变一下LED1的状态,这样就能够实现0.5s的周期性任务了。
所以脉冲计数器的初始值应该设置为65536-0.001/(12/11059200)= 64614,具体如下。

TL0 = 64614;
TH0 = 64614 >> 8;

4)启动定时器
由于GATE已经设置为0,因此只需将TR0设置为1,即可启动定时器。

// 启动定时器
TR0 = 1;

5)定义中断服务程序
按照前文的描述,中断服务程序需要完成如下任务。
(1)重新装载脉冲计数器
(2)统计脉冲次数,每500次改变一次LED1的状态
具体代码如下。

void Timer0_Hander() interrupt 1
{
    //定义静态局部变量
    static unsigned int count = 0;

    //重新状态脉冲计数器
    TL0 = 64614;
    TH0 = 64614 >> 8;

    //统计中断次数
    if (count++ >= 500) {
        LED1  = ~LED1;
        count = 0;
    }
}

实现代码

Com_Util.h

#ifndef _UTIL_H_
#define _UTIL_H_
#include <INTRINS.H>

#define FOSC 11059200 // 晶振频率
#define NT   12       // 单片机的工作周期为12T

// 8bit无符号数
typedef unsigned char u8;
// 16bit无符号数
typedef unsigned int u16;
// 32bit无符号数
typedef unsigned long u32;

/**
 * @brief 延时一定时长
 *
 * @param count 延时时长,单位1ms
 */
void Com_Util_Delay1ms(u16 count);

#endif

Com_Util.c

#include "Com_Util.h"

void Com_Util_Delay1ms(u16 count) //@11.0592MHz
{
    u8 i, j;

    while (count > 0) {
        count--;
        _nop_();
        i = 2;
        j = 199;
        do {
            while (--j);
        } while (--i);
    }
}

Dri_Timer0.h

#ifndef __DRI_TIMER0_H__
#define __DRI_TIMER0_H__
#include <STC89C5xRC.H>
#include "Com_Util.h"

typedef void (*Timer0_Callback)(void);

#define MAX_CALLBACK_COUNT 4

/**
 * @brief 定时器初始化
 *
 */
void Dri_Timer0_Init();

/**
 * @brief 提供注册入口,用这个函数注册完成的函数,会以1000Hz的频率被调用
 *
 * @return 成功返回1,失败返回0
 *
 */
bit Dri_Timer0_RegisterCallback(Timer0_Callback);

/**
 * @brief 反注册回调函数,反注册的函数不会再被周期调用
 *
 * @return bit 反注册的结果,成功位1,失败为0
 */
bit Dri_Timer0_DeregisterCallback(Timer0_Callback);
#endif

Dri_Timer0.c

#include "Dri_Timer0.h"
#include <STDIO.H>

#define T1MS (65536 - FOSC / NT / 1000)

static Timer0_Callback s_timer0_callbacks[MAX_CALLBACK_COUNT];

void Dri_Timer0_Init()
{
    u8 i;
    // 总中断开关
    EA = 1;

    // 定时器中断开关
    ET0 = 1;

    // 设置定时器0的工作模式:16位定时器
    TMOD &= 0xF0;
    TMOD |= 0x01;

    // 设置定时器的初始值
    TL0 = T1MS;
    TH0 = T1MS >> 8;

    // 定时器0的开关
    TR0 = 1;

    for (i = 0; i < MAX_CALLBACK_COUNT; i++)
    {
       s_timer0_callbacks[i] = NULL;
    }
}

bit Dri_Timer0_RegisterCallback(Timer0_Callback callback)
{
    // 判断这个函数有没有被注册过
    u8 i;
    for (i = 0; i < MAX_CALLBACK_COUNT; i++)
    {
        if (s_timer0_callbacks[i] == callback)
        {
            // 如果该函数被注册过,直接返回
            return 1;
        }
    }

    // 注册该函数
    for (i = 0; i < MAX_CALLBACK_COUNT; i++)
    {
        if (s_timer0_callbacks[i] == NULL)
        {
            s_timer0_callbacks[i] = callback;
            return 1;
        }
    }

    return 0;
}

bit Dri_Timer0_DeregisterCallback(Timer0_Callback callback)
{
    u8 i;
    for (i = 0; i < MAX_CALLBACK_COUNT; i++)
    {
        if (s_timer0_callbacks[i] == callback)
        {
            s_timer0_callbacks[i] = NULL;
            return 1;
        }
    }
    return 0;
}

/**
 * @brief 1ms调用一次这个函数
 *
 */
void Dri_Timer0_Func() interrupt 1
{
    u8 i;

    // 定义下次进入时钟中断的时间
    TL0 = T1MS;
    TH0 = T1MS >> 8;

    // 调用所有的回调函数
    for (i = 0; i < MAX_CALLBACK_COUNT; i++)
    {
        if (s_timer0_callbacks[i])
        {
           s_timer0_callbacks[i]();
        }
    }
}

main.c

#include <STC89C5xRC.H>
#include "Dri_Timer0.h"
#define LED    P20
static u16 s_counter = 0;
void ToggleLED() {
    s_counter++;
    // 每500ms切换LED亮灭
    if (s_counter >= 500) {
        s_counter = 0;
        LED = ~LED;
    }
}
void main() {
    Dri_Timer0_Init();
    // 在定时器回中注册回调函数ToogleLED
    // 这样ToogleLED这个函数会每ms被调用一次
    Dri_Timer0_RegisterCallback(ToggleLED);
    while (1);
}

http://www.kler.cn/a/488390.html

相关文章:

  • JVM实战—12.OOM的定位和解决
  • EasyCVR视频汇聚平台如何配置webrtc播放地址?
  • FFmpeg Muxer HLS
  • docker-compose安装canal并利用rabbitmq同步多个mysql数据
  • Erlang语言的网络编程
  • 【PPTist】公式编辑、插入音视频、添加动画
  • VUE + pdfh5 实现pdf 预览,主要用来uniappH5实现嵌套预览PDF
  • openGauss系列 --启动和停止服务器
  • Garnet:微软官方基于.Net 8开源缓存系统,可无需任何改动直接替代Redis,而且还更高性能!
  • S变换matlab实现
  • 2025 最新React面试题
  • 【ASP.NET学习】Web Forms创建Web应用
  • Moq与xUnit在C#单元测试中的应用
  • 比亚迪夏直插家用MPV腹地,“迪王”开启全面销冠新征程
  • 观察者模式详解
  • HTTP-响应协议
  • React Context用法总结
  • Rancher运维三板斧:告警设置、日志管理与数据备份恢复
  • 走进 JavaScript 世界:掌握核心技能
  • Golang中使用 Mqtt
  • 计算机网络 笔记 数据链路层 2
  • docker(目录挂载、卷映射)
  • HTML实战课堂之启动动画弹窗
  • 高级软件工程-复习
  • CancerGPT :基于大语言模型的罕见癌症药物对协同作用少样本预测研究
  • 【Leetcode 热题 100】394. 字符串解码