12.3. - EL TEMPORIZADOR 8253 U 8254.

     El 8253/4 es un chip temporizador que puede ser empleado como reloj de tiempo real, contador de sucesos, generador de ritmo programable, generador de onda cuadrada, etc. En este capítulo, la información vertida estará relacionada con el 8254 que equipa a los AT, algo más potente que el 8253 de los PC/XT; sin embargo, las pocas diferencias serán comentadas cuando llegue el caso.

12.3.1 - DESCRIPCIÓN DEL INTEGRADO.

     Este circuito integrado posee 3 contadores totalmente independientes, que pueden ser programados de 6 formas diferentes.
D7..D0:BUS de datos bidireccional de 3 estados.
CLK 0:CLOCK 0, entrada de reloj al contador 0.
OUT 0:Salida del contador 0.
GATE 0:Puerta de entrada al contador 0.
CLK 1:CLOCK 1, entrada de reloj al contador 1.
OUT 1:Salida del contador 1.
GATE 1:Puerta de entrada al contador 1.
CLK 2:CLOCK 2, entrada de reloj al contador 2.
OUT 2:Salida del contador 2.
GATE 2:Puerta de entrada al contador 2.
A0..A1:Líneas de dirección para seleccionar uno de los tres contadores o el registro de la palabra de control.
-CS:Habilita la comunicación con la CPU.
-WR:Permite al 8254 aceptar datos de la CPU.
-RD:Permite al 8254 enviar datos a la CPU.
DESCRIPCIÓN FUNCIONAL

     El diagrama funcional del 8254, con la estructura interna de las diversas partes que lo componen, se muestra a la izquierda. A la derecha, diagrama de los bloques internos de un contador:


     El buffer del bus de datos, de 8 bits y tres estados, comunica el 8254 con la CPU. La lógica de lectura y escritura acepta entradas del bus y genera señales de control para las partes funcionales del 8254. Las líneas A0..A2 seleccionan uno de los tres contadores o el registro de la palabra de control, para poder leerlos o escribirlos. El registro de la palabra de control es seleccionado cuando A0=A1=1, este registro sólo puede ser escrito (se puede obtener información de estado, como se verá más adelante, con el comando read-back del 8254, no disponible en el 8253). Los contadores 1, 2 y 3 son idénticos en su funcionamiento, por lo que sólo se describirá uno; son totalmente independientes y cada uno de ellos puede ser programado en una modalidad diferente. Si se observa el esquema de un contador, a la derecha, se verá el registro de la palabra de control: aunque no es parte del contador propiamente dicho, afecta a su modo de funcionamiento. El registro de estado, cuando es transferido al correspondiente latch, contiene el valor en curso del registro de la palabra de control y alguna información adicional (como se verá después en el comando read-back). El contador propiamente dicho está representado en la figura por CE (Counting Element) y es un contador descendente síncrono de 16 bits que puede ser inicializado. OLM y OLL son dos latch de 8 bits (OL significa Output Latch; los subíndices M y L están relacionados con el más y el menos significativo byte, respectivamente); ambos son referenciados normalmente como un conjunto denominado OL a secas. Estos latches siguen normalmente la cuenta descendente de CE, pero la CPU puede enviar un comando para congelarlos y poder leerlos; tras la lectura continuarán siguiendo a CE. La lógica de control del contador se encarga de que un sólo latch esté activo a un tiempo, ya que el bus interno del 8254 es de 8 bits. CE no puede ser nunca leído directamente (lo que se lee es OL). De manera análoga, existen un par de registros CRM y CRL (CR significa Count Register) que almacenan la cuenta del contador y se la transmiten convenientemente a CE. Los valores de cuenta se escriben siempre sobre CR (y no directamente sobre CE). La lógica de control gestiona la conexión con el exterior a través de las líneas CLK, GATE y OUT.

DESCRIPCIÓN OPERACIONAL

     Tras el encendido del ordenador, el 8254 está en un estado indefinido; con un modo, valor de cuenta y estado de salida aleatorios. Es entonces cuando hay que programar los contadores que se vayan a emplear; el resto, no importa dejarlos de cualquier manera.

Programación del 8254.

     Para programar un contador del 8254 hay que enviar primero una palabra de control y, después, un valor de cuenta inicial. Los contadores se seleccionan con las líneas A0 y A1; el valor A0=A1=1 selecciona la escritura de la palabra de control (en la que se identifica el contador implicado). Por tanto, el 8254 ocupa normalmente 4 direcciones de E/S consecutivas ligadas a los contadores 0, 1, 2 y al registro de la palabra de control. Para enviar la cuenta inicial se utiliza simplemente el puerto E/S ligado al contador que se trate. El formato de la palabra de control es:


Operaciones de escritura.

     El 8254 es muy flexible a la hora de ser programado. Basta con tener en cuenta dos cosas: por un lado, escribir siempre primero la palabra de control, antes de enviar la cuenta inicial al contador. Por otro, dicha cuenta inicial debe seguir exactamente el formato seleccionado en la palabra de control (enviar sólo byte bajo, enviar sólo byte alto, o bien enviar ambos consecutivamente). Teniendo en cuenta que cada contador tiene su propio puerto y que la palabra de control indica el contador al que está asociada, no hay que seguir un orden especial a la hora de programar los contadores. Esto significa que, por ejemplo, se puede enviar la palabra de control de cada contador seguida de su cuenta inicial, o bien enviar todas las palabras de control para los 3 contadores y después las 3 cuentas iniciales; también es válida cualquier combinación intermedia de estas secuencias (por ejemplo: enviar la palabra de control para el contador 0, después la palabra de control para el contador 1, después la parte baja de la cuenta para el contador 0, luego la parte baja de la cuenta para el contador 1, la parte alta de la cuenta para el contador 0, etc...).

     Un nuevo valor de cuenta inicial puede ser almacenado en un contador en cualquier momento, sin que ello afecte al modo en que ha sido programado (el resultado de esta operación dependerá del modo, como se verá más adelante). Si se programa el contador para leer/escribir la cuenta como dos bytes consecutivos (bajo y alto), el sentido común indica que entre ambos envíos/recepciones no conviene transferir el control a una subrutina que utilice ese mismo contador para evitar un resultado incorrecto.

Operaciones de lectura.

     Existen tres posibles métodos para leer el valor de un contador en el 8254. El primero es el comando Read-Back, sólo disponible en el 8254 (y no en el 8253), como luego veremos. El segundo consiste en leer simplemente el contador accediendo a su puerto correspondiente: este método requiere inhibir la entrada CLK al contador (por ejemplo, a través de la línea GATE o utilizando circuitería exterior de apoyo) con objeto de evitar leer la cuenta en medio de un proceso de actualización de la misma, lo que daría un resultado incorrecto. El tercer método consiste en el comando de enclavamiento.

Comando de enclavamiento (Counter Latch Command).

     Este comando se envía cual si de una palabra de control se tratara (A1=A0=1): para diferenciarlo de ellas los bits 5 y 4 están a cero. En los bits 7 y 6 se indica el contador afectado. Los demás bits deben estar a cero para compatibilizar con futuras versiones del chip. Cuando se envía el comando, el OL del contador seleccionado queda congelado hasta que la CPU lo lee, momento en el que se descongela y pasa de nuevo a seguir a CE. Esto permite leer los contadores al vuelo sin afectar la cuenta en curso. Se pueden enviar varios de estos comandos a los diversos contadores, cuyos OL's quedarán enclavados hasta ser leídos. Si se envían varios comandos de enclavamiento al mismo contador, separados por un cierto intervalo de tiempo, sólo se considerará el primero (por tanto, la cuenta leída corresponderá al valor del contador cuando fue enclavado por vez primera).

     Por supuesto, el contador debe ser leído utilizando el formato que se definió al enviar la palabra de control; aunque en el caso de leer 16 bits, las dos operaciones no han de ser necesariamente consecutivas (se pueden insertar en el medio otras acciones relacionadas con otros contadores).

     Otra característica interesante (¿disponible tal vez sólo en el 8254?) consiste en la posibilidad de mezclar lecturas y escrituras del mismo contador. Por ejemplo, si ha sido programado para cuentas de 16 bits, es válido hacer lo siguiente: 1) leer el byte menos significativo, 2) escribir el nuevo byte menos significativo, 3) leer el byte más significativo, 4) escribir el nuevo byte más significativo.

Comando Read-Back.

     Sólo está disponible en el 8254, no en el 8253. Este comando permite leer el valor actual de la cuenta, así como averiguar también el modo programado para un contador y el estado actual de la patilla OUT, además de verificar el banderín de cuenta nula (Null Count) de los contadores que se indiquen. El formato del comando Read-Back es el siguiente:


     El comando Read-Back permite enclavar la cuenta en varios OL's de múltiples contadores de una sola vez, sin requerir múltiples comandos de enclavamiento, poniendo el bit 5 a cero. Todo funciona a partir de aquí como cabría esperar (los contadores permanecen enclavados hasta ser leídos, los que no son leídos permanecen enclavados, si el comando se reitera sólo actúa la primera vez reteniendo la primera cuenta...). También es posible enviar información de estado al latch OL, enclavándola para que puede ser leída con comodidad por el puerto que corresponda a ese contador. La palabra de estado tiene el siguiente formato:


     En D0..D5 se devuelve justo la misma información que se envió en la última palabra de control; en el bit D7 se entrega el estado actual de la patilla OUT del 8254, lo que permite monitorizar por software las salidas del temporizador economizando hardware en ciertas aplicaciones. El bit NULL COUNT (D6) indica cuándo la última cuenta escrita en CR ha sido transferida a CE: el momento exacto depende del modo de funcionamiento del contador. Desde que se programa un nuevo valor de cuenta, pasa un cierto tiempo hasta que éste valor pasa de CR a CE: leer el contador antes de que se haya producido dicha transferencia implica leer un valor no relacionado con la nueva cuenta. Por ello, según las aplicaciones, puede llegar a ser necesario esperar a que NULL COUNT alcance el valor 0 antes de leer. El funcionamiento es el siguiente:

OperaciónConsecuencias
A -Escribir al registro de la palabra de control (1)NULL COUNT = 1
B -Escribir al registro contador (CR) (2)NULL COUNT = 1
C -Nueva cuenta cargada en CE (CR ->CE)NULL COUNT = 0

     Si se enclava varias veces seguidas la palabra de estado, todas serán ignoradas menos la primera, por lo que el estado leído será el correspondiente al contador en el momento en que se enclavó por vez primera la palabra de estado.

     Se pueden enclavar simultáneamente la cuenta y la palabra de estado (en un comando Read-Back con D5=D4=0), lo que equivale a enviar dos Read-Back consecutivos. En este caso, y con independencia de quién de los dos hubiera sido enclavado primero, la primera lectura realizada devolverá la palabra de estado y la segunda la cuenta enclavada (que automáticamente quedará de nuevo desenclavada).

MODOS DE OPERACIÓN DEL 8254

MODO 0: Interrupt On Terminal Count (Interrupción al final de la cuenta).
     Es empleado típicamente para contar sucesos. Tras escribir la palabra de control, OUT está inicialmente en estado bajo, y permanecerá así hasta que el contador alcance el cero: entonces se pone a 1 y no volverá a bajar hasta que se escriba una nueva cuenta o una nueva palabra de control. La entrada GATE puesta a 0 permite inhibir la cuenta, sin afectar a OUT. El contador sigue evolucionando tras llegar a cero (0FFFFh, 0FFFEh, ...) por lo que lecturas posteriores del mismo devuelven valores pseudoaleatorios.
     Tras escribir la cuenta inicial y la palabra de control en el contador, la cuenta inicial será cargada en el próximo pulso del reloj conectado (CLK), pulso que no decrementa el contador: para una cuenta inicial N, OUT permanecerá a 0 durante N+1 pulsos del reloj tras escribir la cuenta inicial.
     Si se escribe una nueva cuenta en el contador, será cargada en el próximo pulso del reloj y el contador comenzará a decrementarse; si se envía una cuenta de dos bytes, el primer byte enviado inhibe la cuenta y OUT es puesto a cero inmediatamente (sin esperar a CLK): tras escribir el segundo byte, la cuenta será cargada en el siguiente pulso del reloj. Esto permite sincronizar la secuencia de conteo por software.

     Si se escribe una nueva cuenta mientras GATE=0, ésta será cargada en cualquier caso en el siguiente pulso del reloj: cuando GATE suba, OUT se pondrá en alto tras N pulsos del reloj (y no N+1 en este caso).


MODO 1: Hardware Retriggerable One-Shot (Monoestable programable).
     OUT será inicialmente alta y bajará en el pulso de reloj que sigue al flanco de subida de GATE, permaneciendo en bajo hasta que el contador alcance el cero. Entonces, OUT sube y permanece activo hasta el pulso del reloj que siga al próximo flanco de subida de GATE.
     Tras escribir la palabra de control y la cuenta inicial, el contador está preparado. Un flanco de subida de GATE provoca la carga del contador (CR -< CE) y que OUT baje en el próximo pulso del reloj, comenzando el pulso One-Shot de N ciclos de reloj de duración; el contador vuelve a ser recargado si se produce un nuevo flanco de subida de GATE, de ahí que OUT permanezca en bajo durante N pulsos de reloj tras la última vez que suceda esto. El pulso One-Shot puede repetirse sin necesidad de recargar el contador con el mismo valor. GATE no influye directamente en OUT.
     Si se escribe una nueva cuenta durante un pulso One-Shot, el One-Shot en curso no resulta afectado, a menos, lógicamente, que se produzca un nuevo flanco de subida de GATE: en ese caso, el contador sería recargado con el nuevo valor.


MODO 2: Rate Generator (Generador de ritmo).
     En este modo, el contador funciona como un divisor por N. Es empleado típicamente para las interrupciones de los relojes de tiempo real.
     OUT estará inicialmente en alto. Cuando el contador se decremente hasta el valor 1, OUT pasará a estado bajo durante un pulso del reloj; tras ello, volverá a subir y el contador se recargará con la cuenta inicial, repitiéndose el proceso. Este modo es, por tanto, periódico, y la misma secuencia se repite indefinidamente. Para una cuenta inicial N, la secuencia se repite cada N ciclos de reloj (CLK).
     Si GATE=0 la cuenta descendiente se detiene: si GATE es bajado durante un pulso de salida, OUT sube inmediatamente. Un flanco de subida en GATE provoca una recarga del contador con el valor de cuenta inicial en el siguiente pulso del reloj (después, como cabría esperar, OUT bajará tras los N pulsos del reloj correspondientes): GATE puede ser utilizado para sincronizar el contador.
     Tras escribir la palabra de control y la cuenta inicial, el contador será cargado en el próximo pulso del reloj: OUT bajará N pulsos de reloj después, lo que permite también una sincronización por software.
     Escribir un nuevo valor de cuenta durante el funcionamiento del contador no afecta a la actual secuencia de cuenta; si se recibe un flanco de subida de GATE antes del final del período el contador se recargará con ese nuevo valor de cuenta inicial tras el próximo pulso del reloj y volverá a comenzar, en caso contrario se recargará con el nuevo valor tras finalizar con normalidad el ciclo en curso.


MODO 3: Square Wave Mode (Generador de onda cuadrada).
     Este modo es empleado normalmente para la generación de una señal de onda cuadrada. Este modo es similar al 2, con la diferencia de que la salida OUT conmuta al transcurrir la mitad de la cuenta: inicialmente está en alto, pero al pasar la mitad de la cuenta pasa a estado bajo hasta que la cuenta finaliza. Este modo es también periódico: la onda resultante para una cuenta inicial N tiene un período de N ciclos.
     Si GATE=0 la cuenta descendiente se detiene: si GATE es bajado durante un pulso de salida, OUT sube inmediatamente sin esperar ningún CLK. Un flanco de subida en GATE provoca una recarga del contador con el valor de cuenta inicial en el siguiente pulso del reloj: GATE puede ser utilizado para sincronizar el contador.
     Tras escribir la palabra de control y la cuenta inicial, el contador será cargado en el próximo pulso del reloj: también puede ser sincronizado por software.
     Escribir un nuevo valor de cuenta durante el funcionamiento del contador no afecta a la actual secuencia de cuenta; si se recibe un flanco de subida de GATE antes del final del medio-período el contador se recargará con ese nuevo valor de cuenta inicial tras el próximo pulso del reloj y volverá a comenzar, en caso contrario se recargará con el nuevo valor tras finalizar con normalidad el medio-ciclo en curso.
     Para valores de cuenta impares, la duración a nivel alto de OUT será un período de reloj mayor que la duración a nivel bajo.


MODO 4: Software Triggered Mode (Pulso Strobe iniciado por software).
     OUT está en alto al principio; cuando la cuenta inicial expira, OUT baja durante un pulso de reloj y luego vuelve a subir. El proceso se inicia cuando se escribe la cuenta inicial.
     GATE=0 inhibe el contador y GATE=1 lo habilita; GATE no influye en OUT. Tras escribir la palabra de control y la cuenta inicial, el contador será cargado en el próximo pulso del reloj: como ese pulso no decrementa el contador, para una cuanta inicial N, OUT no bajará hasta N+1 pulsos de CLK. Si se escribe una nueva cuenta durante el proceso, se cargará en el próximo pulso CLK y continuará el proceso de cuenta con la nueva cuenta escrita; si la cuenta es de 2 bytes, al escribir el primero no se altera el funcionamiento del contador hasta que se envíe el segundo.


MODO 5: Hardware Triggered Strobe (Pulso Strobe iniciado por hardware).
     OUT estará en alto al principio: con el flanco de subida de la señal GATE, el contador comienza a decrementar la cuenta. Cuando llega a cero, OUT baja durante un pulso CLK y luego vuelve a subir.
     Después de escribir la palabra de control y la cuenta inicial, el contador no será cargado hasta el pulso de reloj posterior al flanco de subida de GATE. Este pulso CLK no decrementa el contador: por ello, ante una cuenta inicial N, OUT no bajará hasta que pasen N+1 pulsos de reloj. GATE no afecta a OUT.
     Si una nueva cuenta inicial es escrita durante el proceso, la actual secuencia del contador no será alterada;  si se produce un flanco de subida en GATE antes de que la nueva cuenta sea escrita pero después de que expire la cuenta actual, el contador será cargado con la nueva cuenta en el próximo pulso del reloj.




12.3.2 - EL 8254 EN EL ORDENADOR.

     Todos los AT y PS/2 llevan instalado un 8254 o algo equivalente; los PC/XT van equipados con un 8253, algo menos versátil; los PS/2 más avanzados tienen un temporizador con un cuarto contador ligado a la interrupción no enmascarable, si bien no lo consideraremos aquí. Todos los contadores van conectados a un reloj que oscila a una frecuencia de 1.193.180 ciclos por segundo (casi 1,2 Mhz). La dirección base en el espacio de E/S del ordenador elegida por IBM cuando diseñó el PC es la 40h. Por tanto, los tres contadores son accedidos, respectivamente, a través de los puertos 40h, 41h y 42h; la palabra de control se envía al puerto 43h.

     La señal GATE de los contadores 0 y 1 está siempre a 1; en el contador 2 es seleccionable el nivel de la línea GATE a través de bit 0 del puerto E/S 61h. La BIOS programa por defecto el contador 0 en el modo 3 (generador de onda cuadrada) y el contador 1 en el modo 2 (generador de ritmo); el usuario normalmente programa el contador 2 en el modo 2 ó 3.

     La salida del contador 0 está conectada a IRQ 0 (ligado a la INT 8, que a su vez invoca a INT 1Ch); este contador está programado por defecto con el valor cero (equivalente a 65536), por lo que la cadencia de los pulsos es de 1.193.180/65.536 = 18,2 veces por segundo, valor que determina la precisión del reloj del sistema, ciertamente demasiado baja. Se puede modificar el valor de recarga de este contador en un programa, llamando a la vieja INT 8 cada 1/18,2 segundos para no alterar el funcionamiento normal del ordenador, si bien no es conveniente instalar programas residentes que cambien permanentemente esta especificación: los programas del usuario esperan encontrarse el temporizador a la habitual y poco útil frecuencia de 18,2 interrupciones/segundo.

     La salida del contador 1 controla el refresco de memoria en todas las máquinas, su valor normal para el divisor es 18; aumentándolo se puede acelerar el funcionamiento del ordenador, con el riesgo -eso sí- de un fallo en la memoria, detectado por los chips de paridad -si los hay-, que provoca generalmente el bloqueo del equipo. De todas maneras, en los PC/XT se puede aumentar entre 19 y 1000 sin demasiados riesgos, acelerándose en ocasiones hasta casi un 10% la velocidad de proceso del equipo. En los AT la ganancia de velocidad es mucho menor y además este es un punto demasiado sensible que conviene no tocar para no correr riesgos, aunque se podría bajar hasta un valor 2-17 para ralentizar el sistema. Sin embargo, no es conveniente alterar esta especificación porque, como se verá más adelante, hay un método para realizar retardos (empleado por la BIOS y algunas aplicaciones) que se vería afectado.

     El contador 2 puede estar conectado al altavoz del ordenador para producir sonido; alternativamente puede emplearse para temporizar. Es el único contador que queda realmente libre para el usuario, lo que suele dar quebraderos de cabeza a la hora de producir sonido.

12.3.3 - TEMPORIZACIÓN.

     Los contadores 0 y 1, especialmente este último, ya están ocupados por el sistema; en la práctica el único disponible es el 2. Este contador ha sido conectado con el doble propósito de temporizar y de generar sonido. Para emplearlo en las temporizaciones, es preciso habilitar la puerta GATE activando el bit 0 del puerto 61h; también hay que asegurarse de que la salida del contador no está conectada al altavoz (a menos que se desee música mientras se cronometra) poniendo a 0 el bit 1 del mismo puerto (61h):

               IN     AL,61h
               AND    AL,11111101b     ; borrar bit 1  (conexión contador 2 con el altavoz)
               OR     AL,00000001b     ; activar bit 0 (línea GATE del contador 2)
               JMP    SHORT $+2        ; estado de espera para E/S
               OUT    61h,AL

     El siguiente programa de ejemplo, CRONOS.ASM, incluye dos subrutinas para hacer retardos de alta precisión. La primera de ellas, inic_retardo, hay que llamarla al principio para que programe el contador 2 del temporizador; la rutina retardo se encarga de hacer el retardo que se indique en AX (en unidades de 1/1193180 segundos).

; ********************************************************************
; *                                                                  *
; *   CRONOS.ASM  -  Subrutinas para hacer retardos de precisión.    *
; *                                                                  *
; *     INIT_RETARDO: llamarla al principio del todo.                *
; *     RETARDO:      Entregar en AX el nº de 1193180-avos de        *
; *                   segundo que dura el retardo (máximo 65400).    *
; *                                                                  *
; ********************************************************************


programa       SEGMENT
               ASSUME CS:programa, DS:programa


               ORG   100h
inicio:

               CALL  inic_retardo
               MOV   CX,20         ; 20 retardos
               MOV   AX,59659      ; de 50 milisegundos
retard:        CALL  retardo
               LOOP  retard
               INT   20h


inic_retardo   PROC
               PUSH  AX
               IN    AL,61h
               AND   AL,11111101b
               OR    AL,1
               JMP   SHORT $+2
               OUT   61h,AL
               MOV   AL,10110100b  ; contador 2, modo 2, binario
               JMP   SHORT $+2
               OUT   43h,AL
               POP   AX
               RET
inic_retardo   ENDP
retardo        PROC
               PUSH  AX
               PUSH  BX
               CLI
               OUT   42h,AL        ; parte baja de la cuenta
               MOV   AL,AH
               JMP   SHORT $+2
               OUT   42h,AL        ; parte alta
               JMP   SHORT $+2
               IN    AL,61h
               XOR   AL,1          ; bajar GATE
               JMP   SHORT $+2
               OUT   61h,AL
               XOR   AL,1          ; subir GATE
               JMP   SHORT $+2
               OUT   61h,AL
               STI
               JMP   SHORT $+2
               MOV   BX,0FFFFh
retardando:    MOV   AL,10000000b
               OUT   43h,AL        ; enclavamiento
               JMP   SHORT $+2
               IN    AL,42h        ; leer contador
               MOV   AH,AL
               JMP   SHORT $+2
               IN    AL,42h
               XCHG  AH,AL         ; AX = valor del contador
               CMP   AX,BX
               MOV   BX,AX
               JBE   retardando
               POP   BX
               POP   AX
               RET
retardo        ENDP


programa       ENDS
               END   inicio

     El procedimiento inic_retardo programa el contador 2 en el modo 2, con datos en binario y dejándolo listo para enviar/recibir secuencias de 2 bytes para la cuenta (primero el byte menos significativo y luego el alto). Las instrucciones JMP SHORT $+2 colocadas oportunamente (para saltar a la siguiente línea) evitan que las máquinas AT más antiguas fallen en dos operaciones de E/S consecutivas demasiado rápidas. El procedimiento retardo envía el nuevo valor de cuenta. A continuación baja y vuelve a subir la señal GATE, con objeto de provocar un flanco de subida en esta línea, lo cual provoca que el contador se cargue con el valor recién enviado de manera inmediata (de lo contrario, no se recargaría hasta acabar la cuenta anterior). Finalmente, entramos en un bucle donde se enclava continuamente la cuenta y se espera hasta que acabe. Lo más intuitivo sería comprobar si la cuenta es cero, pero esto es realmente difícil ya que cambia nada menos que ¡más de 1 millón de veces por segundo!. Por tanto, nos limitamos a comprobar si tras dos lecturas consecutivas la segunda es mayor que la primera ...¡no puede ser!... sí, si puede ser, si tras llegar a 0 el contador se ha recargado. De esta manera, el mayor valor admitido en AX al llamar es 65535, aunque no conviene que sea superior a 65400, para permitir que las recargas puedan ser detectadas en la máquina más lenta (un XT a 4.77 y en 135/1193180 segundos dispone de unos 540 ciclos, en los que holgadamente cubre este bucle).

     A la hora de emplear las rutinas anteriores hay que tener en cuenta dos consideraciones. Por un lado, están diseñadas para hacer pequeños retardos: llamándolas repetidamente, el bucle que hay que hacer (y las interrupciones que se producen durante el proceso) provoca que retarden más de la cuenta. Por ejemplo, en el programa principal, poniendo 1200 en CX en lugar de 20, el retardo debería ser de 60 segundos; sin embargo, comparando este dato con el contador de hora de la BIOS (en una versión ligeramente modificada del programa) resulta ser de casi 60,2 segundos. La segunda consideración está relacionada con las interrupciones: de la manera que está el listado, se puede producir una interrupción en la que algún programa residente utilice el contador 2 del temporizador, alterando el funcionamiento de las rutinas de retardo (por ejemplo, una utilidad de click en el teclado) o incluso provocando un fallo en la misma (si a ésta no le da tiempo a comprobar que ya es la hora): este es un aspecto a tener en cuenta en un caso serio. Se puede, por ejemplo, inhibir todas las interrupciones (o enmascar sólo las más molestas), aunque anular la interrupción del temporizador, la más peligrosa, provocaría un retraso de la hora del ordenador.

     Para hacer retardos o temporizaciones de más de 50 milisegundos, es más conveniente emplear el contador de hora de la BIOS (variable de 32 bits en 0040h:006Ch) que la INT 8 se encarga de incrementar 18,2 veces cada segundo y de volver a ponerlo a cero cada 24 horas. No es conveniente mirar el valor del contador de hora de la BIOS, sumarle una cantidad y esperar a que alcance dicha cantidad fija: la experiencia demuestra que eso produce a veces cuelgues del ordenador, no solo debido a que suele fallar cuando son las 23:59:59 sino también porque cuando se alcanza el valor esperado, por cualquier motivo (tal como un alargamiento excepcional de la rutina que controla INT 8 ó INT 1Ch debido a algún programa residente) puede que el programa principal no llegue a tiempo para comprobar que ya es la hora... y haya que esperar otras 24 horas a probar suerte. Lo ideal es contar las veces que cambia el contador de hora de la BIOS.

     Por último, como ejemplo ameno, el siguiente fragmento de programa hace que la hora del ordenador vaya diez veces más rápida -poco recomendable, aunque muy divertido- programando el contador 0 con un valor de cuenta 6553 (frente al 0=65536 habitual), de la siguiente manera:

               MOV     AL,00110110b     ; contador 0, operación 11b, datos binarios
               OUT     43h,AL
               MOV     BX,6553          ; valor de cuenta
               MOV     AL,BL
               JMP     SHORT $+2
               OUT     40h,AL          ; enviar byte bajo
               MOV     AL,BH
               JMP     SHORT $+2
               OUT     40h,AL          ; enviar byte alto
Un método genial para hacer retardos y controlar timeouts en AT.

     Aunque ausente en todos los manuales de referencia técnica y en todos los libros relacionados con la programación de PC, existe un método muy fácil y eficiente para temporizar disponible en todos los ordenadores AT. Pese a no estar documentado, un programa muy usual como es el KEYB del MS-DOS (a partir de la versión 5.0 del sistema) lo utiliza en todos los AT, sin importar el modelo. Por ello, cabe suponer que seguramente los futuros equipos mantendrán la compatibilidad en este aspecto. Sucede que la salida del contador 1 del 8254, encargada del refresco de la memoria, controla de alguna manera desconocida (tal vez a través de un flip-flop) la generación de una onda cuadrada de unos 33 KHz que puede leerse a través del bit 4 del puerto 61h (no se trata de la salida OUT del contador 1: éste está programado en modo 2 y no genera precisamente una onda cuadrada). El contador 1 es programado por la BIOS en todos los PC con una cuenta 18, conmutando el nivel de la salida cada segundo 1193180/18 = 66287,77 veces. Para hacer un determinado retardo basta con contar las veces que el bit cambia de nivel: la función en ensamblador retardo_asm() del programa de ejemplo lo ilustra. Este método es especialmente interesante en los programas residentes que precisen retardos de precisión, para sonido u otras tareas, tales como limitar la duración máxima de una comprobación en un bit de estado a unos milisegundos o microsegundos (control de timeouts); la principal ventaja es que no se modifica en absoluto la configuración de ningún chip que pueda estar empleando el programa principal, empezando por el 8254. Además, no requiere preparación previa alguna. Para los más curiosos, decir que el bit 5 del puerto 61h es la salida OUT del contador 2 del 8254 (la línea OUT del contador 2 del 8253 de los PC/XT también puede consultarse a través del bit 5, pero del puerto 62h).

     El único inconveniente del método es la alta frecuencia con que cambia el bit: esta misma rutina escrita en C podría no ser suficientemente ágil para detectar todas las transiciones en las máquinas AT más lentas a 6 MHz. A partir de 8 MHz sí puede ser factible, como evidencian las pruebas realizadas, aunque hay que extremar las precauciones para que el código compilado sea lo bastante rápido: utilizar las dos variables registro que realmente soportan los compiladores y huir de la aritmética de 32 bits, como puede observarse en la función retardo_c() del programa de ejemplo. Una mala codificación o compilador podrían hacer inservible el método incluso en una máquina a 16 ó 20 MHz. Para no tener problemas, es mejor emplear la versión en ensamblador, escrita en un C no mucho menos estándar. La macro MICRO() ayuda a seleccionar con más comodidad el retardo, indicándolo en mus, aunque implica una operación en coma flotante que por sí sola añade unos 100 mus de retardo adicionales en un 386-25 sin coprocesador y con las librerías de Borland.


/********************************************************************/
/*                                                                  */
/*    Programa de demostración del método de retardo basado en la   */
/*    monitorización de los ciclos de refresco de memoria del AT.   */
/*                                                                  */
/********************************************************************/

#include <dos.h>

#define MICRO(microseg) ((long)(microseg/15.08573727))

void retardo_asm(), retardo_c();


void main()
{
   /* cuatro formas de hacer un mismo retardo de precisión */

   retardo_asm (66267L);            /* un segundo */
   retardo_asm (MICRO(1000000L));   /* otro segundo (¡más claro!) */
   retardo_c   (66267L);            /* ahora en C */
   retardo_c   (MICRO(1000000L));   /* la otra alternativa */
}


void retardo_asm (long cuenta)   /* método ensamblador recomendado */
{
            asm   push  ax
            asm   push  cx
            asm   push  dx
            asm   mov   cx,word ptr cuenta        /* DX:CX = cuenta */
            asm   mov   dx,word ptr [cuenta+2]
            asm   jcxz  fin_l           /* posible cuenta baja nula */
  esp_ref:  asm   in    al,61h
            asm   and   al,10h                      /* aislar bit 5 */
            asm   cmp   al,ah
            asm   je    esp_ref          /* esperar cambio de nivel */
            asm   mov   ah,al
            asm   loop  esp_ref            /* completar cuenta baja */
  fin_l:    asm   and   dx,dx
            asm   jz    fin_ret         /* posible cuenta alta nula */
            asm   dec   dx
            asm   jmp   esp_ref            /* completar cuenta alta */
  fin_ret:  asm   pop   dx
            asm   pop   cx
            asm   pop   ax
}


void retardo_c (long cuenta)   /* método en C no recomendado */
{
  register a, b;
  unsigned cuenta_h, cuenta_l;

  cuenta_h=cuenta >> 16;  cuenta_l=cuenta & 0xFFFF;

  do
    do {
      while (a==(b=inportb(0x61) & 0x10));
      a=b;
    } while (cuenta_l--);
  while (cuenta_h--);
}

12.3.4 - SÍNTESIS DE SONIDO.

     La producción de sonido es uno de los puntos más débiles de los ordenadores compatibles, que sólo superan por muy escaso margen a alguno de los micros legendarios de los 80, si bien las tarjetas de sonido han solventado el problema. Pero aquí nos conformaremos con describir la programación del altavoz. En todos los PCs existen dos métodos diferentes para generar sonido, con la utilización del 8254 o sin él, que veremos por separado.

Control directo del altavoz.

     El altavoz del ordenador está ligado en todas las máquinas al bit 1 del puerto E/S 61h. Si se hace cambiar este bit (manteniéndolo durante cierto tiempo alto y durante cierto tiempo bajo, repitiendo el proceso a gran velocidad) se puede generar una onda cuadrada de sonido. Cuanto más deprisa se realice el proceso, mayor será la frecuencia del sonido. Por fortuna, la baja calidad del altavoz del PC redondea la onda cuadrada y produce un sonido algo más musical de forma involuntaria. No existe, en cualquier caso, control sobre el volumen, que dada la calidad del altavoz también está en función de la frecuencia. Este método de producción de sonido tiene varios inconvenientes. Por un lado, la frecuencia con que se hace vibrar al bit que lo produce, si no se tiene mucho cuidado, está a menudo más o menos ligada a la capacidad de proceso del ordenador: esto significa que el sonido es más grave en máquinas lentas y más agudo en las rápidas. Esto es particularmente grave y evidente cuando las temporizaciones se hacen con bucles de retardo con registros de la CPU: la frecuencia del sonido está totalmente a merced de la velocidad de la máquina en que se produce. Es por ello que el pitido de error que produce el teclado es a menudo distinto de unos ordenadores a otros, aunque tengan el mismo KEYB instalado. Otro gran inconveniente de este método es que las interrupciones, fundamentalmente la del temporizador, producen fuertes interferencias sobre el sonido. Por ello, es normal tenerlas inhibidas, con el consiguiente retraso de la hora. Por último, un tercer gran inconveniente es que la CPU está completamente dedicada a la producción de sonido, sin poder realizar otras tareas mientras tanto.

     Antes de comenzar a producir el sonido con este método hay que bajar la línea GATE del 8254, ya que cuando está en alto y se activa también el bit 1 del puerto E/S 61h, el temporizador es el encargado de producir el sonido (este es el segundo método, como veremos). Por tanto, es preciso poner primero a cero el bit 0 del mismo puerto (61h):

                  CLI                    ; evitar posible INT 8, entre otras
                  IN     AL,61h
                  AND    AL,11111110b
                  JMP    SHORT $+2       ; estado de espera para E/S
                  OUT    61h,AL          ; bajar GATE del contador 2 del 8254
                  MOV    CX,100h         ; 256 vueltas
     otro_ciclo:  PUSH   CX
                  IN     AL,61h
                  XOR    AL,2            ; invertir bit 1
                  JMP    SHORT $+2
                  OUT    61h,AL
                  MOV    CX,300          ; constante de retardo
     retardo:     LOOP   retardo
                  POP    CX
                  LOOP   otro_ciclo
                  STI
Control del altavoz por el temporizador.

     El otro método posible consiste en emplear el contador 2 del temporizador conectado al altavoz; así, enviando el período del sonido (1.193.180/frecuencia_en_Hz) a dicho contador (programado en modo 3), éste se encarga de generar el sonido. Esto permite obtener sonidos idénticos en todos los ordenadores. Existe el pequeño problema de que la duración del sonido ha de ser múltiplo de 1/18,2 segundos si se desea utilizar el reloj del sistema para determinarla (un bucle de retardo sería, una vez más, dependiente de la máquina) ya que el contador 2 está ahora ocupado en la producción de sonido y no se puede usar para temporizar (al menos, no sin hacer malabarismos). Alternativamente, se podría evaluar la velocidad de la CPU para ajustar las constantes de retardo o aumentar la velocidad de la interrupción periódica.

     Para emplear este sistema, primero se prepara el contador 2 para temporizar (poniendo a 1 el bit 0 del puerto 61h) y luego se conecta su salida al altavoz (poniendo a 1 el bit 1 del puerto 61h). Al final, conviene borrar ambos bits de nuevo. Ahora no es preciso inhibir las interrupciones para garantizar la calidad del sonido:

                 MOV     AL,10110110b    ; contador 2, modo 3, operación 11b, datos binarios
                 OUT     43h,AL          ; programar contador 2
                 MOV     AX,2711         ; 1.193.180 / 440 Hz (nota LA) = 2711
                 JMP     SHORT $+2
                 OUT     42h,AL
                 MOV     AL,AH
                 JMP     SHORT $+2
                 OUT     42h,AL          ; frecuencia programada
                 JMP     SHORT $+2
                 IN      AL,61h
                 OR      AL,00000011b
                 JMP     SHORT $+2
                 OUT     61h,AL          ; altavoz sonando
                 MOV     CX,0
     demora:     LOOP    demora          ; esperar un cierto tiempo por el peor método
                 IN      AL,61h
                 AND     AL,11111100b
                 JMP     SHORT $+2
                 OUT     61h,AL          ; altavoz callado

     Las frecuencias en Hz de las distintas notas musicales están oficialmente definidas y los músicos suelen tenerlas en cuenta a la hora de afinar los instrumentos. La escala cromática temperada, adoptada por la American Standards Asociation en 1936, establece el LA4 como nota de referencia en 440 Hz. En general, una vez conocidas las frecuencias de las notas de una octava, las de la octava siguiente o anterior se obtienen multiplicando y dividiendo por dos, respectivamente. La fórmula de abajo permite obtener las frecuencias de las notas asignándolas un número (a partir de 6 y hasta 88; el LA de 440 Hz es la nota 49) con una precisión razonable, máxime teniendo en cuenta que van a ir a parar al altavoz del PC. Tal curiosa relación se verifica debido a que la respuesta del oído humano es logarítmica, lo que ha permitido reducir a simples matemáticas el viejo saber milenario de los músicos.


  • Volver al Índice