计算机的定时器实现(上)

tips:这是一篇系列文章,总目录在这里哟~

1. 计算机基本组成

冯·诺依曼体系架构下的计算机,是由运算器、控制器和存储器组成的。现代计算机通常还会有外部存储器和各种外围的I/O设备组成。限于篇幅,我们只能简单的聊一聊运算器、控制器和存储器,不敢深入原理,否则就是另外一个专题了。大家如果有兴趣,我们后面可以再搞一个专题专门来聊一聊。

我以前上学的时候,听过一种很奇怪的说法,说是冯·诺依曼体系就是二进制计算机。其实冯·诺依曼跟二进制没啥关系,真要说二进制体系,那是香农的功劳。

(1)运算器和控制器

大家都知道现在这俩东西合起来被称为了CPU。因为他们被发明之初就密不可分了。
运算器通过一定的逻辑电路可以完成算术运算,最简单的就是加法运算。可以去了解一下半加器、全加器这些概念。现在,我只能说,运算器就是用来做二进制加法的机器。
控制器是另外一组逻辑电路,它可以控制计算机什么时候该读取指令,什么时候该进行运算了。而驱动控制器的装置就叫做振荡器。下文会有介绍。

(2)存储器

存储器就是存储bit的地方。根据类型不同,他们的实现方式也不同。

  • 寄存器:严格来说,寄存器也是一种存储器,因为它也能存储bit。寄存器是由一组触发器串联组成的。它的访问速度最快,但由于触发器的制造成本比较高,所以也只能在CPU内部用一用了。
  • 静态存储器SRAM:一般用作高速缓存。它也是由触发器组成的。当然高速缓存也有其他的实现方案。
  • 动态存储器DRAM:使用电容器来存储数据,电容器充电表示1,没充电表示0。电容器可以做到很小,就可以集成到芯片上了。只是电容器会放电,所以需要不断地充电来刷新。
  • 只读存储器ROM:一开始的ROM使用的是一种一次性写入的材料,所以只读;后来也有可擦写的ROM出现,也就是闪存。好处是掉电数据不丢失。
  • 其他:磁带、光盘、软盘、硬盘等

2. 晶振和时钟

振荡器,虽然其貌不扬,但却是计算机的心脏。利用继电器可以设计出机械振荡器,但机械振荡器的振动频率不稳定。我们现代电子计算机中使用的都是电子振荡器。电子振荡器也有很多种实现,计算机中使用的是晶体振荡器,简称晶振。
振荡器产生周期性的振荡信号(计算机中通常为方波)。控制器如果为上升沿触发,则每个周期都会被触发一次。从而驱动控制器工作。所以振荡器也就是我说的时间驱动力在物理层面最本质的体现。

时钟,计算机中有时钟的概念。我们经常听到的时钟主频的概念,就是指CPU中的控制器能稳定工作的频率。但振荡器不是位于CPU中的,而是位于主板中的。所以买不同CPU得搭配不同型号的主板,组装电脑也不能瞎组的。另一个时钟的概念,叫做RTC,即实时时钟,用来记录现实世界的时间。它通常都有多个电源,即使计算机断电,它也能用备用电源工作。主板上那个放纽扣电池的家伙,就是RTC。在各种嵌入式设备中,你也能看到RTC的影子。

3. 中断

计算机运行过程中,出现某些意外情况需主机干预时,机器能自动停止正在运行的程序并转入处理新情况的程序,处理完毕后又返回原被暂停的程序继续运行。这种电路设计叫做中断。
在这里插入图片描述

所以,通过一定的逻辑电路进行定时,到时间后触发产生的中断就叫做定时器中断。定时器每次触发中断,都可以执行中断处理逻辑(中断函数)。所以,我认为定时器中断是时间驱动力在物理层面的第二层体现。

① 什么?定时器怎么计时?那还是用晶振啊,知道了振荡周期,还算不出需要定时的时间吗?
② 中断是怎么实现的?中断是一种纯硬件电路的实现,逻辑电路的设计其实跟软件设计很像,也可以通过电路表示出与、或、非的关系。换句话说,软件能实现的功能,逻辑电路也一样能实现。这也是视频解码的时候,我们为什么会经常听到软解硬解了。关于逻辑电路怎么设计,这个又是另一个领域了,有兴趣可以一起探究下。

我们先以简单的8051单片机为例,说明定时器中断的概念和使用。然后再来看看在x86架构下,计算机如何实现定时器中断的。

4. 8051的定时器实现

51单片机是一种8位的微处理器,实现原理较为简单,比较适合我们用来理解一些硬件原理和操作。

(1)中断系统简介

典型的C51实现,中断系统有5个中断源(8052有 6个) ,2个优先级,可实现二级中断嵌套

中断编号中断名中断源
0外部中断0IE0(P3.2)
1定时器0溢出中断TF0
2外部中断1IE1(P3.3)
3定时器1溢出中断TF1
4串行口中断RI

可以看到,C51中断一共有三种类型:外部中断、定时器中断、串行口中断。
由于C51内部就有定时器逻辑电路和相关的寄存器,所以不借助外部芯片就可以实现定时器中断。但C51内部的定时器并不是实时时钟,也就是说断电后就会归零,所以每次使用时,都需要先进行初始化。我们也可以使用外部的RTC芯片作为中断源,这样我们将RTC芯片的对应输出端口连接到单片机的IE0或者IE1管脚上即可。

(2)中断控制寄存器

在C51内部还有一组用于中断控制的寄存器:

  1. 中断允许控制寄存器IE
  2. 定时器控制寄存器TCON
  3. 串口控制寄存器SCON
  4. 中断优先控制寄存器IP
  5. 定时器工作方式控制寄存器TMOD
  6. 定时器初值赋予寄存器(TH0/TH1,TL0/TL1)

我们一个一个来看一下。

IE寄存器

在这里插入图片描述
在这里插入图片描述

  • EX0:外部中断0允许位;
  • ET0:定时/计数器T0中断允许位;
  • EX1:外部中断1允许位;
  • ET1:定时/计数器T1中断允许位;
  • ES :串行口中断允许位;
  • EA :CPU中断允许(总允许)位。

可以看出,EA是总开关,所以必须为1;我们需要使用ET0定时器中断,那么ET0要为1。

TCON寄存器

在这里插入图片描述

  • IT0:外部中断0触发方式控制位
    • 当IT0=0时,为电平触发方式(低电平有效)
    • 当IT0=1时,为边沿触发方式(下降沿有效)
  • IE0:外部中断0中断请求标志位
  • IT1:外部中断1触发方式控制位
  • IE1:外部中断1中断请求标志位
  • TF0:定时/计数器T0溢出中断请求标志位
  • TF1:定时/计数器T1溢出中断请求标志位
  • TR0:定时/计数器T0工作控制标志位,1–开始计时,0–停止
  • TR1:定时/计数器T1工作控制标志位,1–开始计时,0–停止

SCON寄存器用于控制串口中断,不在这里展开。

IP控制器

在这里插入图片描述

  • PX0:外部中断0优先级设定位
  • PT0:定时/计数器T0优先级设定位
  • PX1:外部中断0优先级设定位
  • PT1:定时/计数器T1优先级设定位
  • PS :串行口优先级设定位

TMOD寄存器

TMOD是用来指定定时器工作方式的寄存器
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

(3)计数寄存器

TX0和TX1,当前T0和T1计数的值将存放在计数寄存器中
TX0又分为TH0和TL0,TX1又分为TH1和TL1

定时器初值的计算方式

时钟周期:假设我们使用12MHz的晶振,也就是说每秒震荡12M次;那么一个时钟周期就是12M的倒数。时钟周期就是最小的时间单位。
机器周期:1个时钟周期没办法让单片机干完所有事情,而12个时钟周期才能够完成基本的操作。

即:每计一次数,就会消耗掉一个机器周期=1/12M*12=1/1M=0.000001s=1μs

如果我想要每隔50ms定时器中断一次,我应该设置定时器的初值为:65536-50000=15536
换算为二进制就是:0011 1100 1011 0000

(4)定时器中断

我们先来看看如何使用内部的定时器中断的操作。
首先根据上面的介绍,我们知道要使用定时器中断,首先要进行初始化设置:

  1. 给TMOD赋值,确定工作方式
  2. 给定时器赋初值,写入TH0,TL0或TH1,TL1
  3. 中断允许控制,EA和ET0 或ET1的置位为1
  4. TR0或TR1置位,启动定时器
// 定时器初始化,我们使用T0定时器
void timerInit()
{
    // 0000 0001
    // 高四位用于T1,所以全部为0;低四位用于T0,第四位GATE为0即可,第三位选用定时模式为0,第二位和第一位设置工作方式为16位定时器
    TMOD = 0x01;
    // 0011 1100 1011 0000 = 15536(十进制)
    TH0 = 0x3C;
    TL0 = 0xB0;
    // 总开关打开
    EA = 1;
    // T0开关打开
    ET0 = 1;
    // T0开始工作
    TR0 = 1;
}

完整程序如下:

/*********************************************************************************
  *FileName: DynamicDigitalTube
  *Author: wjc133
  *Version: 1.0.0
**********************************************************************************/
#include <8051.h>

#define A P2_4
#define B P2_3
#define C P2_2
#define SEGMENT P0

__code unsigned char NUMBER[] = {
    0xFC, // 0: 0000 0011   1111 1100
    0x60, // 1: 1001 1111   0110 0000
    0xDA, // 2: 0010 0101   1101 1010
    0xF2, // 3: 0000 1101   1111 0010
    0x66, // 4: 1001 1001   0110 0110
    0xB6, // 5: 0100 1001   1011 0110
    0xBE, // 6: 0100 0001   1011 1110
    0xE0, // 7: 0001 1111   1110 0000
    0xFE, // 8: 0000 0001   1111 1110
    0xF6, // 9: 0000 1001   1111 0110
};

/* 定义全局变量 */
unsigned char time;
unsigned int currentNum = 0;

/* 函数声明 */
void timerInit();
void timerInterrupt() __interrupt 1;
unsigned int getNumber(unsigned int n);
void display(unsigned int a);

void main()
{
    time = 0;
    timerInit();
    while (1)
    {
        display(currentNum);
    }
}

void timerInit()
{
    TMOD = 0x01;
    TH0 = 0x3C;
    TL0 = 0xB0;
    EA = 1;
    ET0 = 1;
    TR0 = 1;
}

/* 响应中断号为1的中断函数 */
void timerInterrupt() __interrupt 1
{
    TH0 = 0x3C;
    TL0 = 0xB0;
    if (++time == 20)
    {
        currentNum = getNumber(currentNum);
        time = 0;
    }
}

unsigned int getNumber(unsigned int n)
{
    if (n + 1 >= 1000)
    {
        return 0;
    }
    return n + 1;
}

void display(unsigned int a)
{
    // 拆解分析a的位数
    unsigned char i = 0;
    unsigned int d[8];
    do
    {
        d[i++] = a % 10;
        a = a / 10;
    } while (a > 0);

    // 显示每一位
    unsigned char ni = 0;
    unsigned int nj = 0;
    for (ni = 7; ni > 7 - i; ni--)
    {
        switch (ni)
        {
        case 0:
            A = 0;
            B = 0;
            C = 0;
            break;
        case 1:
            A = 1;
            B = 0;
            C = 0;
            break;
        case 2:
            A = 0;
            B = 1;
            C = 0;
            break;
        case 3:
            A = 1;
            B = 1;
            C = 0;
            break;
        case 4:
            A = 0;
            B = 0;
            C = 1;
            break;
        case 5:
            A = 1;
            B = 0;
            C = 1;
            break;
        case 6:
            A = 0;
            B = 1;
            C = 1;
            break;
        case 7:
            A = 1;
            B = 1;
            C = 1;
            break;
        default:
            break;
        }
        SEGMENT = NUMBER[d[7 - ni]];
        nj = 10;
        while (nj--)
            ;
        SEGMENT = 0x00;
    }
}

参考资料

  1. C51 定时器/计数器个人笔记,by YuQiao0303
  2. 51单片机中断详解(上),by Line
  3. C51单片机晶振频率
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值