代码、内容参考来自于包括《操作系统真象还原》、《一个64位操作系统的设计与实现》以及《ORANGE’S:一个操作系统的实现》。
1.概述
计算机中的时钟分为两种:内部时钟,外部时钟。
内部时钟是指处理器中内部元件,如运算器、控制器的工作时序,主要用于控制、同步内部工作过程的步调。内部时钟是由晶体振荡器产生的,简称晶振,它位于主板上,其频率经过分频之后就是主板的外频,处理器和南北桥之间的通信就基于外频。Intel处理器将此外频乘以某个倍数(也称为倍频)之后便称为主频。计算机取指令,执行指令中消耗的时钟周期都是基于主频的。内部时钟由处理器固件结构决定的,在出厂时就设置好了,无法改变,处理器内部原件的工作速度是最快的,所以内部时钟的时间单位粒度比较精细,通常都是ns纳秒级别。
外部时钟是指处理器与外部设备或外部设备之间通信时采用的一种时序,比如两个串口在通信时要首先指定波特率,只有波特率相同的两个串口之间才能通信成功。外部设备的速度对于处理器来讲就很慢了,所以其时钟的时间单位粒度比较大,一般是ms毫秒级别。 计时器的功能就是定时发信号。当到达了所计数的时间,计数器可以自动发一个输出信号,可以用该信号向处理器发出中断,这样处理器可以去执行相应的中断处理程序。或者用该信号直接启动某些外部设备。简单地说,其作用有点像高级开发语言中的回调函数。 和软件定时相比,硬件定时器不占用处理器,因此可以大大提升处理器利用率。
硬件定时器一般有两种计时的方式:
- 正计时:每一次时钟脉冲发生时,将当前计数值加1,直到与设定的目标终止值相等时,提示时间已到,典型的例子就是闹钟。
- 倒计时:先设定好计数器的值,每一次时钟脉冲发生时将计数值减1,直到为0时提示时间已到,典型的例子就是电风扇的定时。
8253 用的就是倒计时的方式
2.8253入门
8253内部有3个独立的计数器,分别是计数器0,计数器1,计数器2。他们的端口分别是0x40~0x42。 下图为8253内部结构

计数器又称为通道,每个计数器都完全相同,都是16位大小。这三个计数器的工作是不依赖的,可以各干各的。可以这样做的基础就是他们都有自己的一套寄存器资源,互不干扰,寄存器资源包括一个16位的计数初值寄存器,一个计数器执行部件,和一个输出锁存器,其中计数器执行部件是计数器中真正进行计数工作的元器件,其本质是一个减法计数器。
结构如图:

每个计数器都有三个引脚,CLK,GATE,OUT:
- CLK表示时钟输入信号,即计数器自己工作的节拍,也就是计数器自己的时钟频率。每当此引脚收到一个时钟信号,减法计数器就将计数值减1。连接到此引脚的脉冲频率最高为10MHz,8253为2MHz。
- GATE 表示门控输入信号,在某些工作方式下用于控制计数器是否可以开始计数,在不同工作方式下 GATE 的作用不同。
- OUT 表示计数器输出信号。 当定时工作结束,也就是计数值为 0 时,根据计数器的工作方式,会在OUT引脚上输出相应的信号。 此信号用来通知处理器或某个设备定时完成。 这样处理器或外部设备便可以执行相应的行为动作。
计数开始之前的计数初值保存在计数初值寄存器中,计数器执行部件(减法计数器)将此初值载入后,计数器的CLK引脚每收到一个脉冲信号,计数器执行部件(减法计数器)便将计数值减1,同时将当前计数值保存在输出锁存器中。 当计数值减到0时,表示定时工作结束,此时将通过OUT引脚发出信号,此信号可以用来向处理器发出中断请求,也可以直接启动某个设备工作。
计数初值寄存器用来保存计数器的初始值,它是16位宽度,我们对8253初始化时写入的计数初始值就保存在计数初值寄存器。 它的作用是为计数器执行部件准备初始计数值,之后的计数过程与它无关。 当计数器选择了某种重复计数的工作方式后,比如工作方式2和工作方式3,还需要将此计数初值重新装载到计数器执行部件中。
计数器执行部件是计数器中真正“计数”的部件,计数的工作是由计数器执行部件完成的,所以它才是真正实际的计数器。 8283是个倒计时计数器,原因是计数器执行部件是个16位的减法计数器,它从初值寄存器中拿到起始值,载入到自己的寄存器后便开始递减计数。 注意,计数过程中不断变化的值称为当前计数值,它保存在执行部件自己的寄存器中,初值寄存器中的值不受影响。
输出锁存器也称为当前计数值锁存器,用于把当前减法计数器中的计数值保存下来,其目的就是为了让外界可以随时获取当前计数值。 计数器中的计数值是不断变化的,处理器无法直接从计数器中获取当前计数值。 为了获取任意时刻的计数值,8253只有将它送到输出锁存器。这样处理器便能够从输出锁存器中获取瞬间计数值。 计数初值寄存器、计数器执行部件和输出锁存器都是16位宽度的寄存器,所以高8位和低8位都可以单独访问。 我们之后为其赋予初始计数值时就分为高8位和低8位分别操作。
三个计数器都有自己的用途:

3.8253 控制字

接下来的重点在在控制字寄存器,其操作端口是0x43,它是8位大小的寄存器。 控制字寄存器也称为模式控制寄存器,在控制字寄存器中保存的内容称为控制字,控制字用来设置所指定的计数器(通道)的工作方式、读写格式及数制,我们把计数器的工作方式、读写格式及数制暂称为控制模式。 三个计数器是独立工作的,每个计数器都必须明确自己的控制模式才知道该怎样去工作(计数),它们各自的控制模式都要以控制字的形式在同一个控制字寄存器中设定,控制字中有相关的字段来选定操作哪个计数器。 控制字由 8 位二进制组成,咱们从最高位开始介绍。

SC1和SC0位是选择计数器位,即Select Counter,或者叫选择通道位,即Select Channel。内部有3个独立的计数器,这三个计数器的控制字是共用同一个控制字寄存器写入的。这两位可组合 4个寄存器名称,二进制00b~10b分别对应计数器0~计数器2。
RW1和RW0位是读/写/锁存操作位,即Read/Write/Latch,用来设置待操作计数器(通道)的读写及锁存方式。计数器是16位宽度,当我们往计数器中写入计数初值时,或者读取计数器中的数值时,可以指定读写低8位,还是高8位。RW1和RW0这两位组合成4种读写方式,如上图。
M2~M0这三位是工作方式(模式)选择位,即Method或Mode。每个计数器有6种不同的工作方式,即方式0~方式5。
8253 的各个计数器都有两种计数方式:二进制方式和十进制方式,其中十进制方式就是用BCD码来表示。 BCD,即Binary-Coded Decimal,称为”二进制码的十进制数”。十进制最大数为9,也就是需要用4位二进制数来表示1位十进制数。
BCD位是数制位,用来指示计数器的计数方式是BCD码,还是二进制数。 当BCD位为1时,则表示用BCD码来计数,如0x1234,则表示十进制数1234。 BCD码的初始值范围是0~0x9999,也就是说 BCD 码所表示的十进制范围是 0~9999。 0 值则表示十进制 10000。 当BCD位为0时,则表示用二进制数来计数,如0x1234,则表示十进制4660。 二进制数的初始值范围是 0~0xFFFF,即十进制范围是 0~65535,0 值表示65536。
4.8253 工作方式
高电平,用数字1表示。 低电平,用数字0表示。 把电平数字由0变成1的一瞬间称为上升沿,把电平数字由1变成0的一瞬间称为下降沿。 8253 一共有 6 种方式:
- 模式0:计数结束中断方式(Interrupt on Terminal Count)
- 模式1:硬件可重触发单稳方式(Hardware Retriggerable One-Shot)
- 模式2:比率发生器(Rate Generator)
- 模式3:方波发生器(Square Wave Generator)
- 模式4:软件触发选通(Software Triggered Strobe)
- 模式5:硬件触发选通(Hardware Triggered Strobe)
计数器开始计数需要两个条件。
- GATE为高电平,即GATE为1,这是硬件控制的
- 计数初值已经写入计数器中的减法计数器,这是由软件out指令控制的。
当这两个条件具备后,计数器将在下一个时钟信号CLK的下降沿开始计数。
启动方式
以上这两个条件,按照“哪个未完成”来划分,可分为软件启动和硬件启动。
软件启动
软件启动是指上面硬件负责的条件1已经完成,也就是GATE已经为1,目前只差软件来完成条件2,即尚未写入计数初值,只要软件负责的条件准备好,计数器就开始启动。 当处理器用out指令往计数器写入计数初值,减法器将此初值加载后,计数器便开始计数。 工作方式0、2、3、4都是用软件启动计数过程。
硬件启动
硬件启动是指上面软件负责的条件2已经完成,即计数初值已写入计数器。 目前只差硬件来完成条件1了,也就是门控信号GATE目前还是低电平,即目前GATE=0,只要硬件负责的条件准备好,计数器就开始启动。 GATE引脚是由外部信号来控制的,只有当GATE由0变1的上升沿出现时,计数器才开始启动计数。 工作方式1、5都是用硬件启动计数过程。
停止方式
计数是停止分为强制终止和自动终止。
强制终止
有些工作方式中,计数器是重复计数的,当计时到期(计数值为0)后,减法计数器又会重新把计数初值寄存器中的值重新载入,继续下一轮计数,比如工作方式2和工作方式3都是采用此方式计数,此方式常见于需要周期性发信号的场合。对于采用此类循环计数工作方式的计数器,只能通过外加控制信号来将其计数过程终止,办法是破坏启动计数的条件:将GATE置为0即可。
自动终止
有些工作方式中,计数器是单次计数,只要定时(计数)一到期就停止,不再进行下一轮计数,所以计数过程自然就自动终止了。比如工作方式0、1、4、5都是单次计数,完成后自动终止。如果想在计数过程中将其终止,将GATE置0。
工作方式
下面详细介绍六种工作方式
计数结束中断方式(Interrupt on Terminal Count)
方式0也称为“计数结束输出正跳变信号”方式,其典型应用是作为事件计数器。
在方式0时,对8253任意计数器通道写入控制字,都会使该计数器通道的OUT变为低电平,直到计数值为0。当GATE为高电平(条件1),并且计数初值已经被写入计数器(条件2)后,此时计数器并未开始计数,计数器有自己的工作节奏,就是时钟信号CLK。计数工作会在下一个时钟信号的下降沿开始。方式0下的计数工作由软件启动,故当处理器用out指令将计数初值写入计数器,然后到计数器开始减1,这之间有一个时钟脉冲的延迟。之后,CLK引脚每次收到一个脉冲信号,减法计数器就会将计数值减 1。
计数值为0时,输出会从低电平跳变到高电平,因此这个信号可以接在8259A的中断引脚IR0上。计数工作会在下一个时钟信号的下降沿开始。
方式0进行计数时,计数器只是单次计数,计数为0时,并不会再将计数初值寄存器中的值重新载入。此方式中,门控信号GATE用于允许或禁止计数,当GATE=1时允许计数,GATE=0时则禁止计数。
硬件可重触发单稳方式(Hardware Retriggerable One-Shot)
方式1的典型应用是作为可编程单稳态触发器,其触发信号是GATE,这是由硬件来控制的,故此方式称为硬件可重触发单稳方式。
在方式1下,由处理器将计数初值写入计数器后,OUT引脚变为高电平。不过,无论此时GATE是高电平,还是低电平,计数器都不会启动计数,而是等待外部门控脉冲信号GATE由低到高的上升沿出现,这是由硬件启动的,之后才会在下一个时钟信号CLK的下降沿开始启动计数,同时会将OUT引脚变为低电平。此后,每当CLK引脚收到一个时钟脉冲信号时,在其下降沿,减法计数器便开始对计数值减1。
OUT引脚的低电平状态一直保持到计数为0,当计数为0时,OUT引脚产生由低到高的正跳变信号。
比率发生器(Rate Generator)
按照比率来分频,其典型应用就是分频器,故也称为分频器方式。
当处理器把控制字写入到计数器后,OUT端变为高电平。 在GATE为高电平的前提下,处理器将计数初值写入后,在下一个CLK时钟脉冲的下降沿,计数器开始启动计数,这属于软件启动。 当计数值为1时,OUT端由高电平变为低电平,此低电平的状态一直到计数为0,也就是持续一个CLK周期。 当计数为0时,OUT端又变为高电平,同时,计数初值又会被载入减法计数器,重新开始下一轮计数,从此周而复始地循环计数。
此方式的特点是计数器计数到达后,自动重新载入计数初值,不需要重新写入控制字或计数初值便能连续工作。当计数初值为N时,每N个CLK时钟脉冲,就会在OUT端产生一个输出信号,这样一来,输入信号CLK和输出信号OUT的关系是N:1。
方式2主要用在循环分频的场合。
方波发生器(Square Wave Generator)
在方式3下工作,就相当于一个方波发生器。当处理器把控制字写入到计数器后,OUT端输出高电平。在GATE为高电平的前提下,在处理器把计数初值写入计数器后的下一个CLK时钟脉冲的下降沿,计数器开始计数。
如果计数初值为偶数,在每一个CLK 时钟脉冲发生时,计数值均减 2,当计数值为0时,OUT 端由高电平变为低电平,并且自动重新载入计数初值,开始下一轮计数。 在新的一轮计数中,当计数值再次为0时,OUT端又会变成高电平,同时再次载入计数初值,又开始一轮新的计数。
如果计数初值为奇数,并且OUT端为高电平,则在第一个时钟脉冲的下降沿将计数减1,这样剩下的计数值便为偶数了,所以在之后的每个时钟脉冲,计数值都被减2。 当计数值变为0时, OUT端又变成低电平,同时自动从计数初值寄存器中载入计数初值开始下一轮计数。 注意,在新一轮计数中,第一个时钟脉冲会将计数值减3,这样剩下的计数值也为偶数,之后的每个时钟脉冲都会将计数值减2。 当计数值又减为0时, OUT端又重新回到高电平,同时自动从计数初值寄存器中载入计数初值,开始又一轮循环计数。
方式3和方式2类似,都是软件启动,并且OUT端都是周期性脉冲,用在循环计数的场合。
软件触发选通(Software Triggered Strobe)
当处理器把控制字写入到计数器后,OUT端变成高电平。在GATE为高电平的前提下,在处理器把计数初值写入计数器后的下一个CLK时钟脉冲的下降沿,计数器开始计数,所以是软件启动。
当计数值为1时,OUT端由高电平变为低电平,当计数值为0,即持续一个CLK时钟周期后,OUT端又回到高电平,此时计数器停止计数。此方式和方式0类似,都是单次计数,只有在重新写入控制字或重新写入计数初值时才会重新开启计数。
硬件触发选通(Hardware Triggered Strobe)
此方式与方式4类似,都是一次计数,区别是计数启动的方式不同,方式5是硬件启动。
方式5中,当处理器把控制字写入到计数器后,OUT端变成高电平。处理器把计数初值写入计数器后,计数工作要等到外部门控脉冲信号GATE由低到高的上升沿出现时才开启,这是由硬件启动的。
当计数值为1时,OUT端由高电平变为低电平,保持一个CLK周期,即计数值变为0时,OUT端又变为高电平,同时停止计数。
总结

将高频的输入脉冲信号 CLK 转换为低频的输出信号 OUT,此信号就是时钟中断信号。并不是所有的工作方式都能让计数器周期性地发出中断信号。
假设计数器0工作在方式2下,下面介绍下中断信号产生的原理:
CLK引脚上的时钟脉冲信号是计数器的工作频率节拍,三个计数器的工作频率均是1.19318MHz,即一秒内会有1193180次脉冲信号。每发生一次时钟脉冲信号,计数器就会将计数值减1,也就是1秒内会将计数值减1193180次1。当计数值递减为0时,计数器就会通过OUT引脚发出一个输出信号,此输出信号用于向处理器发出时钟中断信号。一秒内会发出多少个输出信号,取决于计数值变成0的速度,也就是取决于计数初始值是多少。默认情况下计数器0的初值寄存器值是0,即表示65536,计数值从65536变成0需要修改65536次,一秒内发输出信号的次数为1193180/65536,约等于18.206,即一秒内发的输出信号次数为18.206次,时钟中断信号的频率为18.206Hz。1000毫秒/ (1193180/65536)约等于54.925,这样相当于每隔55毫秒就发一次中断。
我们要做的就是重新设定中断发生的频率,让中断发生得快一些。
所以:
1193180/计数器0的初始计数值=中断信号的频率
1193180/中断信号的频率=计数器 0的初始计数值
8253初始化步骤:
- 向控制字寄存器端口0x43中写入控制字。用控制字为指定使用的计数器设置控制模式,控制模式包括该计数器工作时采用的工作方式、读写格式及数制。
- 在所指定使用的计数器端口写入计数初值。计数初值要写入所使用的计数器所在的端口,即若使用计数器0,就要把计数初值往0x40端口写入,若使用的是计数器1,就要把计数初值往0x41端口写入,依次类推。计数初值寄存器是16位,高8位和低8位可单独使用,所以初值是8位或16位皆可。若初值是8位,直接往计数器端口写入即可。若初值为16位,必须分两次来写入,先写低8位,再写高8位。
5.参考
郑钢著操作系统真象还原
田宇著一个64位操作系统的设计与实现
丁渊著ORANGE’S:一个操作系统的实现


