Programando (PC's) Basado en Polling o en Interrupt s? Cuando escribe un programa de comunicaciones, Ud. Tiene dos métodos disponibles. Puede hacer poll a la UART, para ver si hay algún dato nuevo disponible ó puede crear un manejador de interrupciones que extraiga los datos desde la UART cuando ella genera una interrupción. Hacer un Polling a la UART es el método mas lento y exige muchísimo a la CPU, por lo que podemos tener velocidades máximas de aproximadamente 34.8 KBPS antes de comenzar a perder datos. Algunas Pentium nuevas podrían obtener mejores velocidades. La otra opción es utilizar un manejador de interrupciones. Esto nos permite soportar fácilmente velocidades de 115.2K BPS, aún con computadoras de bajas prestaciones. Pero no debemos descartar totalmente el método de hacer el poll a la UART. Es un buén método de diagnóstico. Si Ud. No tiene conocimiento de la dirección de su tarjeta, ó cual IRQ está utilizando, podría polear la UART a diferentes direcciones para encontrar en que puerto tiene un módem conectado. Luego de conocer esa información, Ud. Puede armar e instalar una rutina de interrupciones para los IRQs comunes y habilitando un IRQ a la vez, usando el Programmable Interrupt Controller, podrá encontrar su IRQ, Y sin necesidad de usar un destornillador! Inicio El primer paso para usar los interrupts es conocer cual interrupt s utiliza su tarjeta serial. Usaremos los base addresses y los IRQ's de los puertos estandar. Los IRQ's 3 y 4 son los mas comunes. Los IRQ 5 y 7 son usados a veces. Interrupt Vectors Cuando conocemos el IRQ, el próximo paso es encontrar el interrupt vector o interrup de software como a veces se lo llama. Básicamente cualquier procesador 8086 tiene un set de 256 vectore sde interrución numerados de 0 a 255. Cada uno de esos vectores contiene un código de 4 bytes que es una dirección (o puntero ) a la Interrupt Service Routine (ISR). La tabla a continuaciónmuestra solamente los interrupts asociados con IRQ's. Los otros 240 no son de interés cuando programamos en comunicaciones del tipo RS-232. Por ejemplo, si utilizamos el COM3 que tiene un IRQ de 4, entonces el interrupt vector sería 0C en hex. De todas maneras, antes de proceder con la implantación de la rutina ó servicio para administrar la interrupción, deberíamos resguardar las anteriores direcciones de los vectores para poder restaurarlos al finalizar el programa. 8
INT (Hex) IRQ Common Uses 08 0 System Timer 09 1 Keyboard 0A 2 Redirected 0B 3 Serial Comms. COM2/COM4 0C 4 Serial Comms. COM1/COM3 0D 5 Reserved/Sound Card 0E 6 Floppy Disk Controller 0F 7 Parallel Comms. 70 8 Real Time Clock 71 9 Reserved 72 10 Reserved 73 11 Reserved 74 12 PS/2 Mouse 75 13 Maths Co-Processor 76 14 Hard Disk Drive 77 15 Reserved Tabla 12: Interrupt Vectors (Sólo para el Hardware) Interrupt Service Routine (ISR) Nuestra rutina manejadora de interrupts (ISR) se llama NuevoISRdePUERTO Se puede escribir aquí lo que se necesite hacer. void interrupt NuevoISRdePUERTO(...) int c; do c = inportb(puerto + 5); if (c & 1) buffer[cuantollega] = inportb(puerto); Cuantollega++; if (Cuantollega == 1024) Cuantollega = 0; }while (c & 1); outportb(0x20,0x20); } 9
Aquí controlamos si se recibe algún caracter y en ese caso sacarlo de la UART y colocarlo en un buffer contenido en la memoria. Nos mantenemos revisando la UART, si los FIFO's están habilitados, para que podamos sacar todos los datos disponibles al momento de la interrupción. La última línea contiene la instrucción outportb(0x20,0x20); que le indica al Programmable Interrupt Controller que el interrupt terminó. Ahora debemos ir dentro del Programmable Interrupt Controller (PIC). En la rutina anterior, suponemos que todo está listo para ser utilizado. Esto significa que los registros de la UART están correctamente seteados y está puesto el Programmable Interrupt Controller. El Programmable Interrupt Controller maneja los interrupts de hardware. Muchas PC's tendrán dos de ellos ubicados en diferentes direcciones. Uno maneja los IRQ's desde el 0 al 7 y el otro, los IRQ's 8 al 15. Los principales interrupts de comunicaciones residen en los IRQ's debajo de 7por ello se utiliza el PIC1, que está ubicado en 0020 Hex. Bit Disable IRQ Function 7 IRQ7 Parallel Port 6 IRQ6 Floppy Disk Controller 5 IRQ5 Reserved/Sound Card 4 IRQ4 Serial Port 3 IRQ3 Serial Port 2 IRQ2 PIC2 1 IRQ1 Keyboard 0 IRQ0 System Timer Tabla 13: PIC1 Control Word (0x21) Los multi-puertos son bastante comunes, por ello la tabla 14 incluye datos para el PIC2 que está ubicado en 0xA0. El PIC2 es responsible por los IRQ's 8 al 15. Opera exactamente igual que el PIC1 excepto que los EOI's (End of Interrupt) van al puerto 0xA0 mientras que la desabilitación (Masking) de los IRQ's se hace usando el port 0xA1. Bit Disable IRQ Function 7 IRQ15 Reserved 6 IRQ14 Hard Disk Drive 5 IRQ13 Maths Co-Processor 4 IRQ12 PS/2 Mouse 3 IRQ11 Reserved 2 IRQ10 Reserved 1 IRQ9 IRQ2 0 IRQ8 Real Time Clock Tabla 14: PIC2 Control Word (0xA1) 10
La mayoría de las iniciaciones del PIC's se hacen desde la BIOS. Nosotros deberemos preocuparnos por dos instrucciones. La primera es: outportb(0x21,(inportb(0x21) & PIC )); Si PIC vale 0xEF, será equivalente a: outportb(0x21,(inportb(0x21) & 0xEF); Que selecciona cual interrupt queremos desabilitar (Mask). Por lo tanto si queremos habilitar el IRQ4, tendríamos que restar 0x10 (16) del 0xFF (255) lo que da 0xEF (239). Eso significa que desabilitamos los IRQ's 7,6,5,3,2,1 y 0. Por lo tanto habilitamos el IRQ 4. Pero, que pasa si uno de esos IRQs ya está habilitado y nosotros venimos y lo desabilitamos?. Por ese motivo tomamos como entrada el valor actual del registro y usando la función AND (&) enviamos el byte de vuelta al registro con nuestros cambios incluídos. Esto se hace utilizando la instrucción: outportb(0x21,(inportb(0x21) & 0xEF);. Por ejemplo, si el IRQ5 está habilitado antes de que nosotros operemos, esta instrucción habilitará a ambos, el IRQ4 y al IRQ5 por lo que no realizaremos ningún cambio que pueda afectar a otros programas o TSR's. La otra instrucción es outportb(0x20,0x20); que indica un fin de interrupt al PIC. Utilizamos este comando al final de la rutina de servicio del interrupt, por lo que los interrupts de menor prioridad serán aceptados. Configuración de la UART Es buena idea detener la generación de interrupciones en la UART como la primera instrucción, de manera que la inicialización no pueda ser interrumpida por la UART. En este punto setearemos nuestros vectores de interrupt. El próximo paso es setear la velocidad a la cual se desea comunicar. Recordando el proceso, tendremos que setear el bit 7 (DLAB) del LCR para que podamos acceder los Divisor Latch High and Low Bytes. Decidimos establecer una velocidad de 9600Bits por segundo. Esto requiere un divisor por 12, por lo tanto nuestro divisor latch high byte será 0x00 y el divisor latch low byte será 0x0C. El próximo paso sería apagar el bit Divisor latch access para que podamos obtener el Interrupt Enable Register y los buffers de recepción y transmisión. Podríamos simplemente grabar un 0x00 al registro, limpiándolo completamente, pero considerando que deberemos establecer nuestro largo de palabra (word length), la paridad y otros en el line control register, podemos hacer todo al mismo tiempo. Decidimos establecer 8 bits, no parity y 1 stop bit que es algo común actualmente. Por ello grabamos 0x03 al line control register que también apagará el DLAB ahorrándonos una instrucción mas. La próxima línea de código enciende los buffer s FIFO. Nosotros establecimos el nivel de disparo (trigger level) en 14 bytes, por lo que los bits 6 y 7 están on. También habilitamos las FIFO's (bit 0). También es buena práctica limpiar los bufer s FIFO al momento de la inicialización. Ello eliminará cualquier basura que el último programa haya dejado en los buffer s FIFO. Dado que esos dos bits se resetean solos, no tendremos que hacer nada para apagarlos luego. Luego el DTR, RTS y OUT 2 son activados por la instrucción outportb(puerto + 4,0x0B);. Algunas tarjetas requieren OUT2 activo para requerimientos de interrupts, por lo que por las dudas, lo ponemos en high. Lo único que nos queda ahora es poner nuestros interrupts que fueron deliberadamente dejados para el final para que no interrumpieran nuestra inicialización. Nuestro manejador de interrupciones (interrupt handler) solamente se ocupa de los nuevos datos disponibles, por lo que setearemos la UART para que interrumpa solamente cuando se reciban nuevos datos. 11
Rutina Main (Loop) do if (Cuantollega!= CuantoSale) C_llega = buffer[cuantosale]; CuantoSale++; if (CuantoSale == 1024) CuantoSale = 0; printf("%c",c_llega); if (kbhit()) Tecla = getch(); outportb(puerto, Tecla); } while (Tecla!=27); Este loop se mantiene hasta que c = 27. Esto ocurre cuando se presiona la tecla ESC. La próxima sentencia IF controla si se presiona una tecla. (kbhit()) Si ocurre, obtiene el caracter usando la sentencia getch() y la manda al buffer. Entonces la UART transmite el caracter al módem. Asumimos que la persona que utiliza el programa no puede digitar mas rápido que la demora de la UART para enviar. De todas maneras, si el programa desea enviar algo, debería hacerse un checkeo para ver si el BIT 5 del Line Status Register está puesto intentando enviar un byte al transmitter register. 12