在PC/XT。机中用了一片8253-5,三个计数器的使用情况如下:
计数器0(CNT0)作软时钟的时间基准。程序设置CNT0为方式3,输出方波的周期约为55 ms(54.925493 ms),该信号送到中断控制器8259A的IR0端。因此,每隔55 ms产生一次中断请求。在中断服务程序中对时间基准信号进行计数,修改用来表示时间的相应内存单元(计满一秒,秒加1;计满60秒,分加1,秒清0。如次类推)。
计数器1(CNTl)作动态RAM的刷新定时。程序设置CNTl为方式2,每隔15.084μs产 生一个窄脉冲输出信号,请求执行动态lRAM的刷新操作(刷新一行),这样可保证在2 ms内将全部单元刷新一遍。CNTl的输出信号送往DMlA控制器8237A通道0的DMA请求输入端。当通道0执行DMA操作时,对动态RAM进行刷新。
计数器2(CNT2)也设置成方式3,输出的方波送到扬声器。用程序设定CNT2输出波形的频率和延续时间就能控制扬声器的音调和发音长短。
在PC/XT机中CNT0、CNTl、CNT2和控制字口的地址分别是40H、4lH、42H和43H。
在PC/AT机中用了一片8254-2。三个计数器的应用以及口地址安排和PC/XT机中一样。图6.27所示是PC/AT机中8254的连接示意图。

在与PC/AT兼容的高档微机中,一些外围接口芯片中集成有8254的功能。例如 82C206中集成了一片8254,82380中集成有4个独立的16位计数器,其功能与8254相同。
2.8253/8254应用举例
从8253/8254的六种工作方式不难设想它的应用是多方面的。但最基本的应用还是计数和定时。这里举三个例子。
例6.4用8253产生ADC的采样信号。
通常将通过A/D转换器(ADC)采集模拟量对应的数据说成采样,将启动A/D转换的信号称为采样信号。这里使用一片8253为ADC提供采样信号。要求采样频率可变(即两次采样的时间间隔可变),采样的持续时间可由程序控制,而采样的启动也由程序通过输出端口控制。
针对上述要求,可作如下安排:
①让计数器0工作在方式2(分频脉冲发生器),输出的周期性窄负脉冲反相后作为采样信号,而输出信号的周期就是采样周期;
②让计数器l工作在方式1(可编程单稳),其输出负脉冲的宽度决定采样的持续时间,为此将其输出反相后作为计数器0的门控信号;
③为了使采样的持续时间的变化范围更大,让计数器2工作在方式3(方波发生器),其输出作为计数器l的计数脉冲;
④用一固定的时钟信号作为计数器0和计数器2的计数脉冲,根据第①步的安排,时钟的频率取决于采样脉冲对高电平持续时间的需求;
⑤设置一个一位输出端口为计数器1和计数器2提供门控信号,该输出口由0到1的跳变触发单稳,即启动采样。
根据上述思路可画出逻辑电路图,如图6.28所示。该电路中时钟频率取5 MHz,是为了满足采样信号高电平宽度(即一个CLKO周期)为200 ns(=200×
10-9s=2×10-7s)的需求。

当要求采样频率为100 KHz(即采样周期为10-5s),每当一位输出口置1时,采样开始,持续时间为30秒,则各计数器的初值应作如下安排:
①计数器0:初值应为10-5s÷(2×10-7s)=50,显然,低字节取50,高字节取0。
②计数器1的初值与计数器2的初值的乘积应为30 s÷(2×10-7s)=15×107,分配给两个计数器有多种分法,这里让计数器1为15 000,计数器2为10 000。于是可计算出计数器1的低字节为152,高字节为58(58×256+152=15 000);计数器2的低字节为16,高字节为39(39×256+16=10 000)。
下面是对3个计数器进行初始化的程序段(设该8253的基地址为200 H):
outportb(Ox203,0x34); /*初始化计数器0为方式2*/
0utportb(0x200,50);
0utportb(0x200,O);
outportb(Ox203,Ox72); /*初始化计数器1为方式1*/
outportb(Ox201,152);
0utponb(Ox201,58);
outportb(Ox203,Oxb6); /*初始化计数器2为方式3*/
outportb(Ox202,16);
outportb(Ox202,39);
设一位输出口的口地址为210 H,则下面的一个语句启动采样操作:
outportb(Ox210,1);
而下面的一个语句是使输出口的输出变为0,为再次启动采样作准备:
0utportb(0x210,0);
例6.5用8254记录脉冲式电表输出脉冲的个数,以便计算用电量。
脉冲式电表除了通常的显示外,还可输出脉冲信号,每当用了一定的电量(取决于表的容量,如0.1度电或0.5度电等)时输出一个脉冲。将其输出接到8254的CLK端,则可记录在一段时间内的脉冲数(如图6.29所示),从而计算出用电量。
为了统计各用户的用电量,总是要定时地(或者说周期性地)获取刚过去的一个时间段中8254各个计数器记录的脉冲个数。这里有两种方法,其流程分别如图6.30的(a)和(b)所示。方法一:只对计数器预置一次,到了规定的时间就读计数器的当前值,然后用上次读入值减去本次读入值,如果结果为负,则将该结果加上65536(16位的模),否则对减的结果不作处理,这样就得到了本周期(即刚过去的一个时间段)该计数器输入脉冲的个数。值得注意的是,这种方法要求计数器计到终点时应能自动重新赋初值并且每来一个计数脉冲计数器的值减1,在8253/8254的六种方式中,能同时满足这两个要求的只有方式2,因此在初始化计数器时应设置成方式2。方法二:每次获得脉冲数后都要对计数器重新置初值(一般取最大值65 535),到了规定的时间读计数器的当前值,然后用预置值减去读入值,即可得到
本周期输入脉冲的个数(还需加l,因为初值的装入需要一个输入脉冲。为了加快速度,变减、加为一次,可直接用65 534减读入值)。由于一个周期内可能没有用电,一个脉冲也没有,这时预置值根本没有被装入,所以在读计数器当前值之前应读一次计数器的状态。判状态字的D6位,若为1,则表明本周期内电表未输出一个脉冲,故可确定脉冲个数为0。显然,方法二不能为8253所用。方法一对8253来说,也不合适,因为在整个采集脉冲数期间,可能有的电表没有输出一个脉冲,预置值根本没有被装入。

注意,在一个采集周期内,脉冲数不能超过65 535。如果超过了,则有两种解决方法:一是缩短采集周期,二是将2个或3个计数器串接起来使用。当然,也可选用容量大一些的电表。
例6.6用8253构成可编程定时中断请求产生电路。
图6.31所示的电路适用于具有ISA总线的PC系列机(包括与PC/AT兼容的高档微机),可用来产生所需时间间隔的定时中断请求信号,以便安排用户自己的中断处理。图中的74LS688、微型开关和排电阻组成了用户设定基地址电路(参看4.5.4小节)。这里8253的计数器0和计数器1串接成32位计数器,以便计数器0在计数输入脉冲频率一定的情况下(图中晶振的频率为1.8432 MHz)产生周期更长的中断请求信号。图中的AEN是ISA总线上的地址允许信号,为高电平时,表示正在进行DMA操作,此时地址总线上的地址是由DMA控制器发出的。本电路为非DMA操作方式,所以,应在AEN为低电平时工作。这里用AEN(经过了一个单向三态门)作74LS688的使能控制来实现这一目的。本电路的输出接ISA总线上的IRQ5。IRQ5在一般微机系统中是为用户保留的。

现将基地址设为150H,要求产生周期为40 ms的方波作中断请求信号。预置的初值应为:40×10-3÷(1÷l 843 200)=73 728。显然,一个16位的计数器是容纳不了的,所以电路中将两个计数器串接起来。将预置值73 728分配给计数器0和计数器1有多种分法,这里置计数器0为32,置计数器1为2 304(32×2 304=73 728)。这样,计数器0的低字节应为32,高字节应为0;计数器1的低字节应为O,高字节应为9(9×256+0=2 304)。
以下是用8086/8088汇编语言编写的主程序以及中断服务程序的框架结构。
STACK SEGMENT PARA STACK ’STACK’ }
STAPN DW 512 DUP(?)
TOP EQ $- STAPN
STACK ENDS
;这里堆栈段的定义仅仅是形式(否则汇编连接时会出现警告错误),
;而实际使用的是当前栈,即操作系统或高级语言使用的栈
CODE SEGMENT PARA PUBLIC ’CODE’
……… ;数据区(用DB、DW伪指令安排,这里是数据、代码共用一个段)
START PROC FAR
ASSUME CS:CODE
PUSH DS
MOV AX,0
PUSH AX
ASSUME DS:CODE
MOV AX.CS
MOV DS.AX
MOV DX,153H ;初始化计数器0为方波发生器
MOV AL.36H
OUT DX.AL
MOV DX.150H
MOV BX.32
MOV AL.BL
OUT DX.AL
MOV AL.BH
OUT DX.AL
MOV DX,153H ;初始化计数器1为方波发生器
MOV AL,76H
OUT DX,AL
MOV DX,151H
MOV BX,2304
MOV AL,BL
OUT DX,AL
MOV AL,BH
OUT DX,AL
MOV DX,OFFSET INTSERV;调用DOS功能(25H)设置类型号ODH的中断向量
MOV AX,CS
NOV DS,AX ;入口参数:DS:段地址
MOV AL,0DH ;DX:段内偏移量
MOV AH,25H ;AL:中断类型号
INT 21H
MOV DX,1000H ;调用DOS功能(31H)程序驻留退出
MOV AH,31H ;保留地址空间为1000H*16=64 KB
INT 1H
;使用汇编语言编写主程序时,中断服务程序不必驻留
INTSERV PROC FAR ;中断服务程序应是远调用
PUSH DS ;保护现场(根据中断服务程序使用寄
PUSH AX ;存器的情况有选择地入栈)
PUSH BX
PUSH CX
PUSH DX
PUSH SI
PUSH DI
MOV AX,CS ;DS指向中断服务程序数据区
MOV DS,AX
STI ;开中断
…… ;具体的中断服务
MOV AL,20H ;发中断结束命令EOI
OUT 20H,AL
POP DI ;恢复现场
POP SI
POP DX
POP CX
POP BX
POP AX
POP DS
IRET ;中断返回
INTSERV ENDP
START ENDP
CODE ENDS
END START
下面是用Turbo C编写的主程序以及中断服务程序的框架结构。
# include<dos.h>
…… /*引用程序中用到的其他头文件*/
…… /*定义外部变量,用于中断程序和主程序的信息交换*/
void interrupt intp() /*中断服务程序*/
{enable(); /*开中断*/
...… /*具体的中断服务*/
outportb(0x20,Ox20); /*发中断结束命令*/
{
main()
{unsigned char old—mask;
...... /*定义其他局部变量*/
dISAble(); /*关中断*/
old mask=inportb(0x21); /*取系统原中断屏蔽字*/
setvect(0xod,intp); /*设置中断类型号0DH的中断向量*/
outportb(0x153,0x36); /*初始化计数器0为方波发生器*/
outportb(Oxl50,32);
outportb(Oxl50,0);
OHtportb(0x153,0x76); /*初始化计数器l为方波发生器*/
outportb(Oxl51,0);
outportb(Oxl51,9);
outportb(Ox21,old_mask&Oxdf); /*允许响应0DH的中断*/
enable(); /*开中断*/
}
这里是在微机中断系统中加人用户的外部硬中断,所以在主程序中要做两项工作:一是设置中断向量,即将自编的中断服务程序的入口地址纳入中断向量表;二是开放自加中断源(当然该中断源应接到系统为用户保留的中断请求输入端)所对应的中断屏蔽。由于是硬中断,所以在中断服务程序中具体的中断服务结束之后,且在中断返回之前应发中断结束命令。
上面说明了在DOS环境下加入外部硬中断用户应做的工作。实际上,在Window环境下,用户也可加入自己的外部硬中断。所不同的是,windows把中断源当成设备来管理,中断处理是设备管理的一部分,所以需要编写设备驱动程序。关于Windows设备驱动程序的编写,有专著,也有一些论文,读者可参考学习。
