10天学会嵌入式技术之51单片机-day-5

第十四章 定时器中断

14.1 需求描述

使用定时器中断的方式实现 LED 闪烁,具体要求是使用定时器 0 令 LED1 每 1 秒钟闪
烁一次。

14.2 定时器使用说明

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

根据定时器的工作原理,可以看出,定时的时间会受到以下几个因素的影响。

(1)脉冲计数器的位数
(2)脉冲计数的初始值
(3)时钟信号的频率

在使用定时器时,需要特别注意上述三个因素。定时器的具体用法如下,以定时器 0
为例。

14.2.2 启用定时器中断

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

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

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

14.2.3 选择定时器工作模式

STC89C52 系列的定时器都有多种工作模式,以适用不同的工作场景。因此开发者需
要根据自己的场景选择相应的工作模式。
 

(1)计数/定时方式

STC89C52 系列的定时器都有定时计数两种工作方式。定时方式用于产生精确的时
间延迟,计数方式用于统计外部脉冲信号的个数。

两种工作方式的本质是相同的,都是使用脉冲计数器对脉冲进行计数,只不过,定时
方式下的脉冲信号为系统时钟信号,而计数方式下,脉冲信号来自单片机外部引脚。

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

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

(2)工作模式

除了可以选择计数/定时方式之外,每个定时器还提供了多种工作模式供开发者选择。
定时器 0 共有 4 个工作模式,如下。

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

2)模式 1——16 位定时/计数器

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

3)模式 2——8 位自动重装载

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

4)模式 3——双 8 位定时/计数器
 

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

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

14.2.4 设置脉冲计数器初始值

由于 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=64414;
Th0=64414>>8;

14.2.5 启动定时器

在做完上述配置后,还需最后一步——启动定时器,启动之后定时器才会开始工作。
定时器的启动可由单片机内部的寄存器控制,也可由单片机的外部引脚控制。具体控制逻
辑如下图所示。
当 GATE=0 时,外部引脚(INT0,P3.2)无效,此时只能由内部寄存器 TR0 控制,
当 TR0=1 时,脉冲计数器开始计数,TR0=0 时,停止计数。
当 GATE=1 时,外部引脚(INT0,P3.2)生效,此时只有当内部寄存器 TR0 和外部引
脚 INT0 都为 1 时,脉冲计数器才开始计数,否则停止计数。

定时器 0 的 GATE 控制位位于 TMODE 寄存器,如下图所示

定时器 0 的 TR0 控制位,位于 TCON 寄存器,如下图所示。

14.2.6 定义中断服务程序

定时器 0 的中断号为 1,因此中断服务函数应定义为。
 

void Timer0_Hander() interrupt 1
{
//编写定时任务逻辑
}

14.3 软件设计

14.3.1 实现思路

(1)启用定时器 0 中断

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

(2)选择定时器 0 工作模式

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

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

// 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;
    }
}

14.3.2 完整代码

1)Util.h

在 Util.h 中增加以下宏定义。

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


2)Dri_Timer0.h

#ifndef __DRI_TIMER0_H__
#define __DRI_TIMER0_H__
#include <STC89C5xRC.H>
#include "Util.h"
void Timer0_Init();
#endif

(3)Dri_Timer0.c

#include "Dri_Timer0.h"
#define T1MS (65536 - FOSC / NT / 1000)
void Timer0_Init()
{
// 中断总开关
    EA = 1;
// 定时器 0 中断开关
    ET0 = 1;
// 定时器 0 工作方式
    TMOD &= 0xF0;
    TMOD |= 0x01;
// 脉冲计数器初始值
    TL0 = T1MS;
    TH0 = T1MS >> 8;
// 启动定时器
    TR0 = 1;
}

4)main.c

#include <STC89C5xRC.H>
#include "Util.h"
#include "Dri_Timer0.h"
#define T1MS (65536 - FOSC / NT / 1000) // 1MS 倒计时(12T 模式)
#define LED1 P00
void main()
{
    Timer0_Init();
    while (1) {}
    }
    void Timer0_Hander() interrupt 1
{
// 定义静态局部变量
    static unsigned int count = 0;
// 重新赋值脉冲计数器
    TL0 = T1MS;
    TH0 = T1MS >> 8;
// 统计中断次数
    if (count++ >= 500) {
        LED1 = ~LED1;
        count = 0;
    }
}

14.4 定时器封装

为使定时器使用起来更加方便和通用,我们可以将定时器代码进一步封装。

14.4.1 实现思路

封装的思想如下图所示

14.4.2 完整代码

1)Dri_Timer0.h

在 Dri 文件夹中新建 Dri_Timer0.h,内容如下:

#ifndef __DRI_TIMER0_H__
#define __DRI_TIMER0_H__
#include <STC89C5xRC.H>
#include "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

2)Dri_Timer0.c

在 Dri 目录中新建 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_callbacks[i]==callback)
      {
           s_callbacks[i]=callback;
           return 1;
      }
      
    }
   return 0; 
}
void Dri_Timer0_Handler() interrupt 1
{
      u8 i;
      //1.重装初始值
      TL0=64414;
      TH0=64414>>8;

      //2.轮询函数指针数组
      for ( i = 0; i < MAX_CALLBACK_COUNT; i++)
      {
            if (s_callbacks[i]!=NULL)
            {
                  s_callbacks[i]();
            }
            
      }
      
}

3)main.c

#include"Dri_Timer0.h"
#include<STC89C5xRC.H>
#include"Com_Util.h"

void LED_Blink()
{
    static u16 count=0;
    if (count>=500)
    {
        P00=~P00;
        count=0;
    }
}

void main()
{
    Dri_Timer0_Init();
    Dri_Timer0_RegisterCallback(LED_Blink);
    while(1){
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值