Los registros de las tarjetas gráficas representan acceder al nivel más bajo (más bajo significa más directo a hardware) en cuanto a programación gráfica se refiere. El objetivo es proporcionar unos conocimientos generales en lo que son los registros de las tarjetas gráficas, su uso y más tarde particularizar en algunos de ellos para conseguir una solución hardware a un problema en un programa gráfico.
Como vimos el mes pasado, cada tarjeta SVGA tiene su set de registros y por cuestiones de compatibilidad no convenía programar estas tarjetas a nivel de circuitería, de manera que solucionaremos esto usando el set de registros de las tarjetas VGA, set estándar para todos los adaptadores de este tipo y con el que las SVGA son totalmente compatibles, funcionando los programas que desarrollemos en cualquier tarjeta gráfica VGA o superior (por compatibilidad descendente).
Los registros de las tarjetas gráficas contienen pues toda la información que define cómo debe el ordenador representar en el monitor los datos que hay en la VRAM, es decir, un registro almacena la resolución horizontal de pantalla, otro la vertical, otro la velocidad de refresco del monitor, los pixels por scanline, y así con todos los parámetros necesarios para esta representación.
Como veremos en el presente artículo, los registros pueden ser accedidos mediante los puertos E/S a los que están conectados, que van desde el 3b0h al 3dfh, permitiéndonos su modificación y lectura.
-Attribute Controller (puerto 3c0h): es el controlador de atributos, relacionado con todos los temas de colores, atributos, etc, accediéndose por el puerto 3c0h.
-VGA Sequencer (puerto 3c4h): es el secuenciador de la VGA y gestiona sus funciones más críticas e importantes (reseteo de la tarjeta, generador de carácteres, etc).
-Graphics Controller (puerto 3c3h): es el controlador gráfico de la VGA, dando acceso a la lectura de planos, reseteo de registros, etc.
- Cathode Ray Tube Controller (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.
-El SVGA Controller (puerto ???h) podemos considerar que estará presente en las tarjetas SuperVGA, conteniendo los registros extendidos para este tipo de tarjetas. Su puerto de acceso varía según el fabricante de la tarjeta, de ahí los problemas de compatibilidad en la programación de estas tarjetas.
En principio sabemos que tenemos 4 controladores en la tarjeta gráfica (ATTRC, VGASEQ, GRAPHC y CRTC, descritos arriba) cada uno de ellos con una dirección E/S (un puerto) asignado para su acceso. Estos 4 controladores tienen cada uno una serie de registros y el objetivo es saber cómo se accede a un registro determinado y un controlador determinado.
Supongamos que queremos cambiar el color del borde de la pantalla (normalmente negro) a azul. Miramos en cualquier manual de referencia que contenga los registros de la VGA y vemos que existe un registro llamado <<Border Color Register>> al que se accede mediante el Attribute Controller. En el manual pondrá algo así como:
Attribute Controller (3c0h): Border Color Register, index 11h, r/w. Bits (0-5): VGA screen border color.El significado de esto es que el Border Color Register es el registro número 11h (index 11h) del ATTRC, que es de lectura y escritura (r/w), es decir, que podemos leer su valor y modificarlo, y que si los bits del 0 al 5 de este byte indican el color del borde (0=negro, 1=azul, etc).
Simplificando conceptos: los registros VGA pueden ser de escritura, de lectura, o de escritura y lectura (w, r, r/w), y cada registro de un controlador tiene un número asociado (index port o índice de puerto) que indica dentro del total de registros de un controlador a cuál nos referimos. Ahora tan sólo habría que acceder al registro nº 11h del ATTRC (puerto 3c0h), escribiendo allí el color que queremos que tome el borde, en nuestro caso azul (1).
La función que tiene el index port al acceder a un registro se puede comparar con el que tiene el registro AH (subfunción) al llamar a una interrupción, porque al igual que la interrupción 10h tiene diversas funciones, un controlador VGA puede controlar más de un registro, y necesitamos indicarle cuál 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.
Lo único complicado en este momento es que a cada controlador se accede de una manera, por lo que el procedimiento para leer (o escribir) un registro del Attribute Controller (usaremos abreviaturas a partir de ahora) no se siguen los mismos pasos para leer uno del CRTC, por ejemplo. Lo que vamos a hacer ahora es proporcionar la manera de programar cada uno de estos controladores y ver qué registros posee cada uno, de manera que ya tendremos acceso a la totalidad de ellos. Para simplificar la lectura vamos a definir las siguientes direcciones E/S:
#define ATTRC 3c0h #define VGASEQ 3c4h #define GRAPHC 3ceh #define CRTC 3d4hAunque cada controlador se acceda de diferente forma, al final del artículo desarrollaremos un procedimiento general que contemple todos los casos y permita leer/modificar cualquier registro llamando a dicha función, simplificando así el trabajo con los registros.
->Escritura: Se envía al controlador el Index Port (registro a modificar) y se escribe el valor que se le desea asignar en el puerto asociado, PUERTO+1.
void GRAPHCwrite( index, valor ) { outportb( GRAPHC, index ); outportb( GRAPHC+1, valor ); }Estas dos llamadas a outportb también se pueden concretar en un solo outport() (o <out dx, ax>) de 1 word, que envía el byte bajo al puerto especificado y el byte alto a puerto+1. Para los amantes de la optimización visual, las 2 sentencias anteriores se reducen a un solo outport( GRAPHC, index|valor<<8 );
->Lectura: La lectura es igual de sencilla y consiste en envíar el índice de puerto al controlador y leer del puerto contiguo, tal y como hace el siguiente código:
char GRAPHCread( index ) { char valor; outportb( GRAPHC, index ); valor = inportb( GRAPHC+1 ); return(valor); }
->Escritura: Si vamos a modificar algún registro habremos de desproteger los registros de CRTC, como se indica unas líneas más abajo. Después basta con realizar la modificación:
void CRTCwrite( index, valor ) { outportb( CRTC, index ); outportb( CRTC+1, valor ); }->Lectura: Para realizar una operación de lectura no es necesario quitar la protección del CRTC, ya que no modificamos sus registros. Basta pues con leer los valores mediante:
char CRTCread( index ) { char valor; outportb( CRTC, index ); valor = inportb( CRTC+1 ); return(valor); }->Desprotección: La desprotección se realiza poniendo a 0 el bit 7 del index 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 valor; outportb( CRTC, 0x11 ); valor = inportb( CRTC+1 ); valor = valor & 0x7F; outportb( CRTC, 0x11 ); outportb( CRTC+1, valor); }Basta además con realizar una sóla vez la desprotección y restaurarla antes de salir de nuestro programa (activando el bit 7 con un procedimiento similar).
->Escritura: Tan sólo hay que considerar la excepción del registro 1:
void VGASEQwrite( index, valor ) { if ( index == 1 ) { outport( VGASEQ, 0x0100 ); outport( VGASEQ, valor<<8|1 ); outport( VGASEQ, 0x0300 ); } else { outportb( VGASEQ, index ); outportb( VGASEQ+1, valor ); } }->Lectura: El sistema de lectura es igual que el de los anteriores sea cual sea el registro al que se desea acceder.
char VGASEQread( index ) { char valor; outportb( VGASEQ, index ); valor = inportb( VGASEQ+1 ); return(valor); }
->Escritura: Antes de poder acceder al ATTRC debemos limpiar un "flip-flop" interno de la VGA que controla la lectura y escritura. Esto se hace leyendo del Status Register (al que llamaremos STATUS_P), situado en el puerto 3dah. Para resetear el flipflop (un componente hardware que no nos interesa) basta con leer del puerto, ignorando el valor leido.
Después de leer de este puerto de estado, se le envía al Attribute Controller el número de índice de puerto (el registro que se quiere modificar) y posteriormente el valor que se desea asignar al registro.
#define STATUS_P 3dah void ATTRCwrite( index, valor ) { inportb (STATUS_P); outportb( ATTRC, index ); outportb( ATTRC, valor ); outportb( ATTRC, 0x20 ); }Después de cada acceso al Attribute Controller se desactiva el refresco de la pantalla y, como se puede ver en el listado anterior, al finalizar las operaciones de escritura activamos de nuevo este refresco mediante:
outportb( ATTRC, 0x20 );También puede reactivarse este refresco activando el bit 5 del index al escribirlo en el ATTRC.
->Lectura: Al igual que en la escritura, debe leerse primero del puerto de estado (3dah) y después enviar el índice de puerto al ATTRC. El valor del registro que queremos leer lo obtendremos del puerto ATTRC+1:
char ATTRCread( index ) { char valor; inportb ( STATUS_P ); outportb( ATTRC, index ); valor = inportb( ATTRC+1 ); outportb( ATTRC, 0x20 ); }
->Escritura: outportb(puerto, valor);
->Lectura: valor = inportb(puerto);
LISTADO 1: Lectura de registros VGA. char ReadVGAREG( int port, char index ) { char valor; switch ( port ) { case ATTRC: inportb(STATUS_P); outportb(ATTRC, index); valor = inportb(ATTRC+1); outportb( ATTRC, 0x20 ); break; case VGASEQ: case GRAPHC: case CRTC: outportb(port, index); valor = inportb(port+1); break; default: valor = inportb(port); break; } return(valor); } LISTADO 2: Escritura de registros VGA. void WriteVGAREG( int port, char index, char valor ) { switch (port) { case ATTRC: inportb(STATUS_P); outportb(ATTRC, index); outportb(ATTRC, valor ); outportb( ATTRC, 0x20 ); break; case VGASEQ: if (index == 1) { outport(VGASEQ, 0x0100);. outport(VGASEQ, valor<<8|1); outport(VGASEQ, 0x0300); break; }; case GRAPHC: case CRTC: outport( port, index|valor<<8); break; default: outportb(port, valor); break; } }Con las nuevas funciones desarrolladas, retomemos el ejemplo que planteábamos al principio sobre cambiar el color del borde. Sabemos que el Border Color Register está situado en el Attribute Controller, index 11h. El cambio del color del borde a azul (1) se reduce a:
WriteVGAREG( ATTRC, 0x11, 1 );
TABLAS 1, 2, 3, 4 y 5: Algunos registros VGA ---------------------------------------------------------------------- Port-Index: 02h Port: 03c4h Registro: Color plane write enable register Descrip.: Controla la escritura en los diferentes planos de la VRAM. Bit 7, 6 Reservados Bit 3 Permitir escritura en plano 3. Bit 2 Permitir escritura en plano 2. Bit 1 Permitir escritura en plano 1. Bit 0 Permitir escritura en plano 0. ---------------------------------------------------------------------- Port-Index: 04h Port: 03c4h Registro: Memory mode register Descrip.: Bit 4-7 Reservados Bit 3 Bit Chain 4. (b3=1 -> modos de 256 colores). Bit 2 Direccionamiento Odd/even. Bit 1 Activado si hay más de 64 de VRAM. Bit 0 b0=1: modo alfanumérico. b0=0: modo gráfico. ---------------------------------------------------------------------- Port-Index: 01h Port: 3d4h Registro: Horizontal display enable register Descrip.: Nº total de carácteres visualizados (horiz.) menos 1. ---------------------------------------------------------------------- Port-Index: 02h Port: 3d4h Registro: Start Horizontal blanking register Descrip.: Carácter en el que empieza el blanqueo. ---------------------------------------------------------------------- Port-Index: 04h Port: 3d4h, 3b4h Registro: Start Horizontal retraze register Descrip.: Carácter en que empieza el retrazo horizontal. ----------------------------------------------------------------------- Port-Index: 0ah Port: 3d4h Registro: Cursor Start Register Descrip.: Indica línea inicial (0-16) donde empieza el cursor hardware. Bit 7, 6 : reservados. Bit 5 : Cursor off Bit 4-0 : Cursor Start ----------------------------------------------------------------------- Port-Index: 0bh Port: 3d4h Registro: Cursor End Register Descrip.: Indica línea final (0-16) donde empieza el cursor hardware. Bit 7 : reservado. Bit 6, 5 : Cursor skew. Bit 4-0 : Cursor end. ----------------------------------------------------------------------- Port-Index: 13h Port: 3d4h, 3b4h Registro: Offset/Logical Screen Width register Descrip.: Anchura lógica entre 2 scanlines sucesivos. Port-Index: Port: 3dah Registro: CTRC Status register Descrip.: Indica el estado del retrazo vert. Y horiz. Bit 3: Retrazo vertical: 0 = refrescando. 1 = volviendo. Bit 0: Retrazo horizontal: 0 = refrescando. 1 = volviendo. ---------------------------------------------------------------------- Port-Index: 05h Port: 03ceh Registro: Mode register Descrip.: Especifica el modo de lectura y escritura de los datos. Bit 7 Reservado (0). Bit 6 (b6=1) modo de 256 colores. Bit 5 Modo de rotación de registros. Bit 4 Modo odd/even de direccionamiento. Bit 3 Lectura de datos (b3=0 leer, b3=1 comparar). Bit 2 Reservado (0). Bit 1, 0 Modo de escritura de datos. 0 = escritura directa, 1 = transferencia VRAM a VRAM, 2 = Uso de color o patrón. ---------------------------------------------------------------------- Port-Index: 06h Port: 03ceh Registro: Miscellaneous register Descrip.: Define datos de distintos tipos (mapeado, modo, etc). Bit 7-4 Reservados Bit 3-2 Segmento de memoria (g_window) y su tamaño. 00 = A000h -> 128k 01 = A000h -> 64k 10 = B000h -> 32k 11 = B800h -> 32k Bit 1 Odd/even enable (used in text modes) Bit 0 Graphics mode enable ---------------------------------------------------------------------- Port-Index: 10h Port: 03c0h Registro: Mode control register Descrip.: Datos referentes a los modos de vídeo. Bit 6 (VGA) Si b6=1, la anchura del pixel = 8 (256 colores). Bit 5 (VGA) Si 0, se ignora la comparación de línea. Bit 4 Reservado Bit 3 Si b3=1, el bit 7 del byte de atributo significa parpadeo. Si es 0, ese bit significa intensidad. Bit 2 Si b2=1: caracteres de 9 bits. Bit 1 Modo monocromo si es 1. Color si es 0. Bit 0 b0=1->modo gráfico. B0=0->modo de texto. ---------------------------------------------------------------------- Port-Index: 11h Port: 03c0h Registro: Screen border color register Descrip.: Indica el color del overscan (borde). ---------------------------------------------------------------------- Port-Index: - Port: 03c7h Registro: Lookup table read index register. Descrip.: Indica el color a leer en el registro 03c9h (cambia el puntero dentro de la paleta interna de la tarjeta de vídeo). ---------------------------------------------------------------------- Port-Index: - Port: 03c8h Registro: Lookup table write index register. Descrip.: Indica el color a escribir en el registro 03c9h (cambia el puntero dentro de la paleta interna de la tarjeta de vídeo). ---------------------------------------------------------------------- Port-Index: - Port: 03c9h Registro: Lookup table data register. Descrip.: Apunta a la paleta interna (actual), dentro del color especificado con los registros 03c8h o 03c7h, y en este puerto podemos modificar esta tabla (cambio/lectura de la paleta). dentro de la paleta interna de la tarjeta de vídeo). ----------------------------------------------------------------------
Como ya sabemos, en el monitor hay un haz de electrones (tubo de rayos catódicos) que bombardea el fósforo del monitor componiendo pixel a pixel la imagen final entre 60 y 70 veces por segundo, tomando de la VRAM los bytes que representan cada color. Al final de cada línea horizontal se produce un retorno a la siguiente línea (Retrazo Horizontal), y al finalizar el retrazado completo el haz vuelve a (0,0) (incluido el borde) desde el final de la pantalla (como vimos en el nº 2). A este tiempo de retorno se le llama, pues, Retrazo Vertical. En este período de tiempo el haz está volviendo y no redibujando, de manera que podemos aprovechar este tiempo muerto para escribir en VRAM sabiendo que hasta el próximo refresco no aparecerán los cambios que estamos realizando (deben ser cambios muy rápidos). De esta manera nos aseguramos que no habrá parpadeo ni nieve debido a estas escrituras. Como se puede ver en la descripción, el bit 3 de este registro indica:
Bit 3 = 0 -> Haz redibujando la pantalla. Bit 3 = 1 -> Haz volviendo a (0,0).La manera de esperar este tiempo muerto para redibujado de pantallas consiste en esperar a que el haz se encuentre redibujando (=refrescando) la pantalla (bit3=0), y luego esperar a que acabe de hacerlo (bit3=1), volviendo. 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 (WaitVRetrace()):
mov dx, 3dah vert1: in al,dx test al,8 jnz vert1 vert2: in al,dx test al,8 jz vert2El código está implementado en assembler porque tras su ejecución tenemos un tiempo mínimo (el que tarde el haz en volver a (0,0)) para realizar cambios en VRAM, de manera que la sincronización ha de ser lo más rápida posible. Con funciones C/PASCAL (outportb(),port[]) habría saltos, accesos a pila, retornos, etc, malgastando un tiempo demasiado importante.
Con este mismo registro también podemos sincronizarnos con el retrazo horizontal mediante el bit 1 (como se hace en las barras de copper, por ejemplo). Otra posible aplicación es generar (para cualquier tipo de juego, animación o efecto) los fotogramas en una pantalla virtual, esperar el fin de retrazado y volcar esta pantalla virtual sobre la VRAM, repitiendo continuamente el proceso.
Por ejemplo, observando la tabla 5 vemos que los 3 registros descritos tienen una gran utilidad, ya que a través de ellos puede accederse a la paleta actual (leerla o modificarla) directamente mediante la tarjeta gráfica, tal y como se hizo en el nº 4 de este curso (aunque ahora el lector ya puede comprender la metodología de aquellas rutinas).
Santiago Romero .
Pulse aquí para bajarse los ejemplos y listados del artículo (6 Kb).
Figura 1: "Controladores de una tarjeta de vídeo."
Santiago Romero