El PC es un ordenador extraño. En sistemas como el Amiga, por ejemplo, es indispensable el uso de los chips que la acompañan (tanto gráficos como sonoros) mediante ordenes hardware debidamente documentadas.
Los programadores del PC no disponen de estos chips ni de esta documentación y se tienen que arreglar con la utilización de los registros internos que la VGA usa para almacenar datos necesarios para su funcionamiento. Cosas como saber la localización del haz de electrones que redibuja la pantalla podría parecer algo meramente informativo pero puede ser usado para realizar vistosos efectos de paleta como las Copper Bars en modo texto (ver COPPER.EXE en los ejemplos del disco) y es indispensable para llevar una precisa sincronización y evitar parpadeos y "nieve" en las operaciones gráficas que requieran una gran velocidad.
Estos registros internos, que llevan parámetros importantes para la VGA, pueden ser accedidos mediante los puertos E/S a los que están conectados, que van desde el 3b0h al 3dfh y nos permiten su modificación y lectura, aunque aquí vamos a ver sólo los que nos interesan para la programación de los modos unchained. Hay que puntualizar que las tarjetas Super VGA disponen de nuevas funciones no estándar de control cuyo uso debe evitarse para asegurar la compatibilidad de nuestros programas programas en otras tarjetas, aunque aquí sólo se describirán aquellas funciones que puedan ser utilizadas en cualquier ordenador que disponga de una tarjeta VGA sin que haya ningún tipo de problema de compatibilidad.
Attribute Controller (ATTR_ADDR, puerto 3c0h): es el controlador de atributos, relacionado con todos los temas de colores, atributos, intensidad y parpadeo, paleta, etc... así como de control del color tanto del borde como de los planos y que está situado en el puerto 3c0h.
VGA Sequencer (SEQU_ADDR, puerto 3c4h): es el secuenciador de la VGA y maneja las funciones más críticas e importantes de la VGA tales como el reseteo de la tarjeta, el generador de carácteres, el Memory Mode Register (el cual contiene el bit CHAIN-4 que desencadenaba los 4 planos), etc... Su control se obtiene mediante el puerto 3c4h.
Graphics Controller (GRAC_ADDR, puerto 3c3h): es el controlador gráfico de la VGA, que estando situado en el puerto 3ceh da acceso a la lectura de planos (Ver GetPixelX();), al Miscellaneous Register, al Mode Control Register y a funciones de operaciones lógicas y reseteo de registros.
Cathode Ray Tube Controller (CRTC_ADDR, puerto 3d4h): maneja todos los datos referentes al tubo de rayos catódicos que refresca o redibuja la pantalla, en un sistema asociado al monitor. Mediante este controlador, situado en el puerto 3d4h, tenemos acceso a información como el comienzo y fin del retrazo (tanto vertical como horizontal), el número de líneas a redibujar (Total Vertical Register), a la posición del cursor y a otras muchas funciones de pantalla. Este chip tiene una protección contra escritura que debe ser eliminada antes de modificar cualquier registro que sea accedido mediante el CRTC_ADDR o puerto 3d4h, aunque se pueden leer sus registros sin manipular la protección. La manera de eliminar la protección la veremos en la sección dedicada a la programación de los controladores.
VGA Enabler/Disabler (VGAENABLE_ADDR, puerto 3c3h): situado en el puerto 3c3h, el least significant bit (LSB = bit 0) de este puerto puede anular el acceso a los puertos VGA.
Miscellaneous Output (MISC_ADDR, puerto 3c2h): mediante este importante puerto, el 3c2h, pueden realizarse funciones de selección de polaridad, reloj, etc... Su uso lo encontramos, por ejemplo, en la rutina de inicialización del modo X, forzando al monitor a refrescar a 60 Hz mediante la utilización de este registro.
El SVGA Chip Controller (CHIPSVGA_ADDR, puerto 3d6h) no es estándar y por ello no va a ser usado para nuestros ejemplos, pero es interesante que los lectores conozcan su dirección E/S (3d6h) y que sepan que es el puerto mediante el que se accede a los registros específicos de cada tarjeta Super VGA.
La programación de los registros es sencilla gracias a la conexión de estos a los puertos E/S, pero debido a la cantidad de registros y funciones de que disponemos hay que introducir un concepto denominado índice de puerto (Port Index).
MOV DX, offset Cadena MOV AH, 9 ; número de función INT 21h ; llamadaPero la misma interrupción la podemos usar para, por ejemplo, salir al MS-DOS:
MOV AH, 4Ch ; número de función INT 21h ; llamadaEl significado que tiene AH al llamar a la interrupción se puede comparar con el que tiene el índice de puerto, porque al igual que la interrupción 21h tiene diversas funciones, un puerto VGA puede controlar más de un registro, y necesitamos indicarle cual de ellos queremos leer o modificar. Así pues, el índice de puerto le indica al puerto al que se escribe cuál es el registro que queremos modificar de todos los que ese puerto controla. Únicamente hemos de enviar los parámetros tal y como los requiere cada controlador, y ese es el principal problema de la programación a bajo nivel de la VGA.
En la VGA existen varias maneras distintas de leer o modificar los registros. Esto es debido al sistema que sigue cada periférico:
inportb ( STATUS_ADDR ); outportb( ATTR_ADDR, index_port ); // index port outportb( ATTR_ADDR, valor ); // valorDespués de cada acceso al Attribute Controller se desactiva el refresco de la pantalla y, como se puede ver en el listado 1, al finalizar las operaciones de escritura activamos de nuevo este refresco mediante:
outportb( ATTR_ADDR, 0x20 ); //reactivar refrescoLISTADO 1:
void RegisterOut( int Controlador, char index, unsigned char valor ) { switch (Controlador) { case ATTR_ADDR: InPortb(STATUS_ADDR); // resetear flip-flop OutPortb(ATTR_ADDR, index); OutPortb(ATTR_ADDR, valor); break; case SEQU_ADDR: if (index == 1) { OutPortw(SEQU_ADDR, 0x0100); // resetear el secuenc. OutPortw(SEQU_ADDR, valor<<8 | 1); OutPortw(SEQU_ADDR, 0x0300); break; }; case GRAC_ADDR: case CRTC_ADDR: case CHIPSTECH_ADDR: OutPortw( Controlador , index | valor<<8); break; case MISC_ADDR: case VGAENABLE_ADDR: default: OutPortb(Controlador, valor); break; } OutPortb( ATTR_ADDR, 0x20 ); // permitir refresco VGA }- LECTURA:
inportb ( STATUS_ADDR ); outportb( ATTR_ADDR , index_port ); // enviar index-port valor = inportb( ATTR_ADDR+1 ); // leer valorPara probar el ATTR_ADDR disponemos de un ejemplo de cómo leer el color del borde de la pantalla VGA usando el Attribute Controller:
inportb ( STATUS_ADDR ); // limpiar flip-flop interno outportb( ATTR_ADDR , 11h ); // función leer borde color = inportb( ATTR_ADDR+1 ); // leer valor del puertoOtra manera de no desactivar el refresco de pantalla cuando se accede al Attribute Controller es activar el bit 5 del índice, que si está a 0 hace que la VGA deje de redibujar la pantalla. Esto lo podemos hacer de muchas maneras, aunque la más sencilla es sumarle 32 al índice (El valor que indica el bit 5: 32 = 100000b). Si nos olvidamos de activar el bit 5 no ocurrirá nada gracias a la línea añadida al final de las funciónes RegisterIn y RegisterOut que asegura el correcto uso de este controlador, al igual que ocurre en el anterior apartado de escritura.
outportb( CRTC_ADDR, index ); // índice de puerto outportb( CRTC_ADDR+1 , valor ); // valor a enviarEstas dos llamadas a outportb también se pueden concretar en un solo outport gracias a que, como se comentó en el número anterior, outport(), outpw() y out dx, ax envían el byte bajo del valor de 16 bits que se le pasa al puerto especificado y después envían a Puerto+1 el byte bajo, de manera que pueden reducirse estas dos órdenes a:
outport( CRTC_ADDR, index | valor<<8 ); //enviar wordDe esta manera, colocamos index en el byte bajo y valor en el byte alto (valor << 8) para que outport haga el trabajo en una sola orden. Mediante el OR ( | ) fundimos los dos bytes en un word.
Este sistema de programación de controladores debería sernos familiar ya que es el mismo que se usaba en el número anterior al seleccionar el plano de lectura de la VGA, controlado mediante la función 4 (Index port 4) del Graphics Controller:
outportb( GRAC_ADDR, 0x04 ); // índex 4 = selec. plano outportb( GRAC_ADDR +1, ( x&3 ) ); // plano a leerEste sistema es usado en muchas operaciones de inicialización, reseteo y control de la tarjeta, y nos permite controlar los 3 componentes de la misma manera, aunque hay que hacer notar que el CRTC Controller dispone de una protección contra escritura de registros que debe ser eliminada antes de intentar modificar cualquier registro que sea accedido mediante el puerto 3d4h. En operaciones de lectura no necesita ser eliminado. Su desprotección se realiza poniendo a 0 el bit 7 del índice o registro 11h, realizándose primero la lectura por el método ya descrito y después la desprotección. Este bit (bit 7) cuando está a uno impide la modificación de los registros y debe ser limpiado (bit 7 = 0) para realizar nuestras operaciones de escritura:
void UnProtectCRTC( void ) { int v ; outportb( CRTC_ADDR, 0x11 ); // Índice = 11h v = inportb( CRTC_ADDR + 1 ); // 3d5h v = v & 0x7F; // bit 7 = 0 outportb( CRTC_ADDR, 0x11 ); // Índice = 11h outport( CRTC_ADDR, 0x11 | (v << 8) ); }- LECTURA
outportb( GRAC_ADDR, index ); // index port v = inportb( GRAC_ADDR +1 ); // valor
if ( index == 1 ) { outport( SEQU_ADDR, 0x0100 ); outport( SEQU_ADDR, valor << 8 | 1 ); outport( SEQU_ADDR, 0x0300 ); } else outport( SEQU_ADDR, index | ( valor << 8 ) );El sistema de lectura es igual que el de los tres anteriores sea cual sea el registro al que se desea acceder.
La modificación o lectura consiste en leer o enviar el registro directamente al puerto sin preocuparse del Index Port. Esto se hace cuando un puerto controla un sólo registro, por lo que no puede existir ambigüedad sobre a cual nos referimos, que es como decir que ese puerto cumple una sola función. Son puertos de acceso directo porque el Index Port es ignorado y nos ocupamos únicamente de escribir o leer en el puerto correspondiente. La mayoría de puertos del PC son usados de esta manera, más sencilla y simplificada:
LECTURA :
valor = inportb( puerto );ESCRITURA:
outportb( puerto, valor );
LISTADO 2: unsigned char RegisterIn( int Controlador, char index ) { unsigned char valor; switch ( Controlador ) { case MISC_ADDR: valor = InPortb(0x3cc); break; /* 0x3c2 es de escritura y 0x3cc de lectura */ case ATTR_ADDR: InPortb(STATUS_ADDR); /* reseteamos el flip-flop */ /* el indice *DEBE* tener activado el */ /* bit 5. ( p. ej. sumandole 32 ); */ OutPortb(ATTR_ADDR, index); valor = InPortb(ATTR_ADDR+1); break; case SEQU_ADDR: case GRAC_ADDR: case CRTC_ADDR: case CHIPSTECH_ADDR: OutPortb(Controlador, index); /* las 3 son iguales */ valor = InPortb(Controlador+1); break; case VGAENABLE_ADDR: default: valor = InPortb(Controlador); /* resto de puertos */ break; } /* fin de switch */ OutPortb( ATTR_ADDR, 0x20 ); /* permitir escritura a pantalla */ return(valor); /* devolvemos el valor */ }
A continuación se describirá un registro con el que crearemos una copper bar como ejemplo de las posibilidades que nos brinda la programación de la VGA.
El Retrazo Vertical es el período de tiempo en el que el haz vuelve a (0,0) en diagonal desde la esquina inferior derecha tras haber terminado el refresco de todas las líneas de la pantalla y durante el cual los accesos a video memoria no aparecen en pantalla hasta que los redibuje en el siguiente ciclo, cosa que debemos aprovechar para realizar las operaciones gráficas. Como se puede ver en la tabla, el bit 3 cuando está a 0 nos indica que el haz de electrones está redibujando la pantalla y que no debemos realizar ninguna operación de escritura en VideoRam, por lo que debemos esperar a que esté a 1 para dibujar o volcar bitmaps y gráficos en pantalla.
Lo más práctico que se puede hacer es esperar a que esté a 0 (a que esté redibujando la pantalla), y luego esperar a que acabe de hacerlo (hasta que pase a estar a 1). Si sólo se comprobara esta última condición podríamos encontrarnos con el haz a punto de llegar a (0,0) con el consiguiente parpadeo en las operaciones gráficas. Veamos como sería el código de espera al retrazo vertical:
mov dx, 3dah vert1: in al,dx test al,8 jnz vert1 vert2: in al,dx test al,8 jz vert2El retrazo horizontal lo controlamos de igual manera gracias al bit 0 de este registro. Mediante estos dos retrazos ya podemos evitar los parpadeos en nuestras operaciones gráficas y realizar efectos como el que vamos a ver a continuación.
Para crear la Copper Bar se ha de seguir el siguiente algoritmo, que repitiéndose hasta que se pulse una tecla:
1. Esperar un retrazo vertical para cerciorarnos de que el haz de electrones
está situado en la esquina superior izquierda de la pantalla.
2. Si la variable Y controla la altura de la barra en la pantalla de texto,
esperarse Y retrazos horizontales significaría que el haz está situado ahora
donde debe estar la primera línea de la barra. Lo hacemos mediante un bucle y
pasamos al paso 3.
3. Para cada una de las líneas de la barra, cambiar la paleta del color de
fondo con una tonalidad distinta de la anterior de manera que el haz dibuja la
línea con el nuevo tono, pero como aún no ha llegado a redibujar la parte
superior e inferior de la pantalla, estas líneas siguen con el color negro de
fondo.
4. Incrementar o decrementar la variable Y según suba o baje la barra.
5. Cuando se hayan acabado todas las líneas de la barra, para que el rayo
continue dibujando la pantalla con el color de fondo normal cambiamos la
tonalidad para dejarla igual que el color de fondo, por ejemplo negro (0,0,0)
y saltamos de nuevo al paso 1.
En los ejemplos del CD tenemos las nuevas funciones que nos serán útiles en cuestiones gráficas: Ejemplo2.c contiene la manera de cambiar la paleta utilizando el hardware de la VGA mientras que Ejemplo3.c y Ejemplo4.c contienen funciones de retrazos verticales y horizontales. El listado Copperc.c contiene una versión C de la Copper Bar, más asequible a los programadores que no dominan ensamblador.
Pulse aquí para bajarse los ejemplos y listados del artículo (9 Kb).
Tabla 1: "Componentes controlados por la VGA."
Tabla 2: "Registros del Attribute Controller."
Tabla 3: "Registros del Graphics Controller."
Tabla 4: "Registros del VGA Sequencer."
Tabla 5: "Miscellaneous Output Register y
Status Register."
Santiago Romero