UNIDAD IV Manejo de Puertos de E/S

34
- 196 - UNIDAD IV Manejo de Puertos de E/S 4.1. Introducción ......................................................................................................197 4.2. Puerto Paralelo .................................................................................................197 4.2.1. Registros del Puerto Paralelo ....................................................................198 4.2.2. Exportar datos por el Puerto Paralelo ........................................................201 4.2.3. Importar datos del Puerto Paralelo.............................................................203 4.2.4. Puerto Paralelo en Windows XP ................................................................204 4.3. Puerto Serial ......................................................................................................212 4.3.1. Comunicación Serial ..................................................................................212 4.3.2. La Norma RS – 232C.................................................................................213 4.3.3. Puerto Serial en NULL ..............................................................................215 4.3.4. UART.........................................................................................................216 4.3.5. Registros del Puerto serial .........................................................................217 4.3.6. Transmisión y Recepción en la UART ......................................................221 4.3.7. Control del Puerto serial con Builder C++ ................................................222 4.3.7.1. Objeto Thread.............................................................................222 4.4. Ejercicios propuestos .......................................................................................228

Transcript of UNIDAD IV Manejo de Puertos de E/S

Page 1: UNIDAD IV Manejo de Puertos de E/S

- 196 -

UNIDAD IV Manejo de Puertos de E/S

4.1. Introducción ......................................................................................................197 4.2. Puerto Paralelo .................................................................................................197 4.2.1. Registros del Puerto Paralelo ....................................................................198 4.2.2. Exportar datos por el Puerto Paralelo ........................................................201 4.2.3. Importar datos del Puerto Paralelo.............................................................203 4.2.4. Puerto Paralelo en Windows XP................................................................204

4.3. Puerto Serial ......................................................................................................212 4.3.1. Comunicación Serial ..................................................................................212 4.3.2. La Norma RS – 232C.................................................................................213 4.3.3. Puerto Serial en NULL ..............................................................................215 4.3.4. UART.........................................................................................................216 4.3.5. Registros del Puerto serial .........................................................................217 4.3.6. Transmisión y Recepción en la UART ......................................................221 4.3.7. Control del Puerto serial con Builder C++ ................................................222

4.3.7.1. Objeto Thread.............................................................................222 4.4. Ejercicios propuestos .......................................................................................228

Page 2: UNIDAD IV Manejo de Puertos de E/S

- 197 -

����������������������������������������

� �� ��������� �������� �

4.1 Introducción

El envío y recepción de datos externos a la computadora se puede hacer por medio de los múltiples puertos que tiene un equipo para este propósito. Los puertos de Entrada y Salida (E/S) constituyen el medio por el cual el microprocesador de una computadora se comunica con su entorno. Existen puertos para cada interacción de la unidad de procesamiento principal con sus dispositivos auxiliares. Así, existe un puerto de entrada del teclado, un puerto de salida para el video, un puerto de entrada para el ratón, etc. La computadora Personal (PC) puede direccionar hasta 64K puertos de E/S. Cada puerto se designa por un número. A continuación se listan las direcciones en hexadecimal de los puertos más usuales de E/S.

DIRECCIÓN Desde Hasta

DESCRIPCIÓN

000 00F Controlador de DMA (acceso directo a memoria) 020 02F Controlador de interrupciones maestro 030 03F Controlador de interrupciones esclavo 040 043 Temporizador 060 060 Teclado 061 061 Altavoz 170 17F Primer disco duro 200 20F Puerto de juegos 278 27F Tercer puerto paralelo LPT3 2E8 2EF Puerto serie 4 COM4 2F8 2FF Puerto serie 2 COM2 370 377 Controlador de disco flexible 378 37F Segudo puerto paralelo LPT2 3B0 3BB Adaptador de video monocromo 3BC 3BF Primer puerto paralelo LPT1 3E0 3EF Puerto serie 3 COM3 3F8 3FF Puerto serie 1 COM1 220 22F Usualmente las tarjetas de sonido

4.2. Puerto Paralelo El puerto paralelo se utiliza generalmente para manejar impresoras. Sin embargo, dado que este puerto tiene un conjunto de entradas y salidas digitales, se puede emplear para hacer prácticas experimentales de lectura de datos y control de dispositivos.

Page 3: UNIDAD IV Manejo de Puertos de E/S

- 198 -

El puerto paralelo de una computadora es ideal para ser usado como herramienta de control de motores, relés, LED's, etc, él mismo posee un bus de datos de 8 bits y muchas señales de control, algunas de salida y otras de entrada que también pueden ser usadas fácilmente.

Las computadoras generalmente poseen solo uno de estos puertos, y nos referimos a él con el nombre de LPT1, pero con muy poco dinero se le puede adicionar una tarjeta con un segundo puerto paralelo, al que se nombraría LPT2.

Físicamente se puede acceder al puerto paralelo de la computadora por medio de un conector DB25 hembra. El aspecto y distribución de pines de este conector se muestra en la siguiente figura:

Para conectase al DB25 de la computadora se utiliza un conector DB25 macho, cuya distribución de pines se muestra en la siguiente figura:

4.2.1. Registros del Puerto Paralelo

El puerto paralelo está formado por 17 líneas de señales y 8 líneas de tierra. Las líneas de señales están formadas por tres grupos:

� 4 Líneas de control � 5 Líneas de estado � 8 Líneas de datos

En el diseño original las líneas de control son usadas para la interface, control e intercambio de mensajes desde la computadora a la impresora.

Las líneas de estado son usadas para intercambio de mensajes, indicadores de estado desde la impresora a la computadora (falta papel, impresora ocupada, error en la impresora).

Las líneas de datos suministran los datos de impresión de la computadora hacia la impresora y solamente en esa dirección. Las nuevas implementaciones del puerto permiten una comunicación bidireccional mediante estas líneas.

Page 4: UNIDAD IV Manejo de Puertos de E/S

- 199 -

Cada uno de estos grupos de líneas (control, estado, datos) puede ser referenciada de modo independiente mediante un registro de 8 bits. Cada registro del puerto paralelo es accesado mediante una dirección. El puerto paralelo tiene tres registros:

� Registro de datos � Registro de estado � Registro de control

Cada registro corresponde al grupo de líneas de señales respectivo de los cuales se forma el puerto paralelo.

En la siguiente tabla se muestra la relación que existe entre las líneas físicas del conector de la computadora y los registros.

DB25 Señal Registro Tipo Activo Sentido 1 Control 0 C0- Salida Bajo Invertido 2 Dato 0 D0 Salida Alto directo 3 Dato 1 D1 Salida Alto directo 4 Dato 2 D2 Salida Alto directo 5 Dato 3 D3 Salida Alto directo 6 Dato 4 D4 Salida Alto directo 7 Dato 5 D5 Salida Alto directo 8 Dato 6 D6 Salida Alto directo 9 Dato 7 D7 Salida Alto directo

10 Estado 6 S6+ Entrada Alto directo 11 Estado 7 S7- Entrada Bajo Invertido 12 Estado 5 S5+ Entrada Alto directo 13 Estado 4 S4+ Entrada Alto directo 14 Control 1 C1- Salida Bajo Invertido 15 Estado 3 S3+ Entrada Alto directo 16 Control 2 C2+ Salida Alto directo 17 Control 3 C3- Salida Bajo Invertido

18-25 Tierra

Un dato en alto es un 1, un dato en bajo es un 0. La entrada y salida son desde el punto de vista de la computadora. El sentido invertido significa que si en el registro de la computadora escribimos un bit en 1, la señal física que saldrá en el pin correspondiente será de 0, es decir la señal escrita se invierte. Si el pin es de entrada, entonces al introducir una señal de 0 por una línea de este tipo, el bit correspondiente leido del registro estará en 1 y no en 0 como la señal que se está introduciendo. Para estos pines entonces, se deben tomar las precauciones adecuadas al programar.

Por lo general, la dirección hexadecimal base del puerto paralelo en LPT1 es igual a 0378h (888 en decimal) y 0278h (632 en decimal) para el LPT2. Esto se puede verificar fácilmente en el setup de la computadora o bien en el cartel que generalmente la computadora muestra en el momento del booteo. Puede darse el caso que el LPT1 asuma la dirección 3BCh (956 en decimal) y el LPT2 378h.

Page 5: UNIDAD IV Manejo de Puertos de E/S

- 200 -

Las direcciones de cada registro a partir de la dirección base del puerto se obtienen de la siguiente forma:

� Base (Registro de datos) = base+0 � Registro de Estado = base + 1 � Registro de Control = base + 2

Por ejemplo, si encontramos que la dirección base del puerto paralelo es 0378h, entonces las direcciones del registro de datos, estado y control serán:

� Base (Registro de datos) = 0378h � Registro de Estado = 0379h � Registro de Control = 037Ah

Cada una de ellas permite accesar a los siguientes bits (descritos en la tabla general):

� Base (Registro de datos) = D0, D1, D2, D3, D4, D5, D6, D7 � Registro de Estado = S3, S4, S5, S6, S7 � Registro de Control = C0, C1, C2, C3

Si deseamos escribir un dato en el bus de salida de datos (pin 2 a 9) solo debemos escribir el byte correspondiente en la dirección hexadecimal 0378h (888 en decimal) cuando trabajamos con el LPT1 y 0278h (632 en decimal) cuando trabajamos con el LPT2. Los distintos bits de salida correspondientes al bus de datos no pueden ser escritos en forma independiente, por lo que siempre que se desee modificar uno se deberán escribir los ocho bits nuevamente.

Para leer el estado de los pines de entrada (10, 12, 13 y 15) se debe realizar una lectura a la dirección hexadecimal 0379h (889 en decimal) si trabajamos con el LPT1 o bien leer la dirección 0279h (633 en decimal) si trabajamos con el LPT2. La lectura será devuelta en un byte en donde el bit 6 corresponde al pin 10, el bit 5 corresponde al pin 12, el bit 4 corresponde al pin 13 y el bit 3 corresponde al pin 15.

En la siguiente figura se muestra los bits de los registros que le corresponde a cada línea del puerto paralelo:

Registro de Datos: Puerto 0378h

Registro de Estado: Puerto 0379h

Registro de Control: Puerto 037Ah

Page 6: UNIDAD IV Manejo de Puertos de E/S

- 201 -

4.2.2. Exportar datos por el Puerto Paralelo

La forma más eficaz de manejar el puerto paralelo es utilizando lenguaje ensamblador* que es el lenguaje de programación mas cercano al lenguaje de máquina de la computadora. Para introducir sentencias de ensamblador en C++, se utiliza la palabra reservada asm y se encierran entre llaves todas las sentencias que correspondan al lenguaje ensamblador. El formato es el siguiente: asm { sentencias de ensamblador }

Generalmente se envían datos por el puerto paralelo a través del registro de datos. Para sacar

datos por el puerto paralelo a través de este registro crearemos una función Enviar() cuya definición será la siguiente:

void Enviar(char Dato) { asm { mov dx,378h //Mover valor 378h al registro dx mov al,Dato //Colocar el dato a enviar en el registro al out dx,al //Enviar al puerto 378h el valor de al } }

Se recibirá como parámetro un dato de 8 bits tipo char el cuál será el dato que se enviará por el registro de datos. Como ya se mencionó, por regla general este registro tiene la dirección 378h.

EJEMPLOS

4.1. Escribir el código del evento OnClick de un botón que al ser presionado enviará por el

puerto paralelo, un valor escrito previamente en la caja de edición Edit1. Utilizar la función Envia().

int x; void Enviar(char Dato); . . . void __fastcall TForm1::Button1Click(TObject *Sender) { x = Edit1 -> Text.ToInt(); Enviar(x); }

También se puede utilizar el registro de control (37Ah) para sacar datos adicionales por el

puerto paralelo. * Para profundizar en el tema del lenguaje ensamblador, consultar la bibliografía.

Page 7: UNIDAD IV Manejo de Puertos de E/S

- 202 -

4.2. Realizar un programa que ponga los pines C3 y C2 de las líneas de control del puerto paralelo en estado bajo (0), y los pines C1 y C0 en estado alto (1).

Primero crearemos una función que envíe datos por el registro de control (37Ah) del puerto paralelo: void EnviarPtoC(char Dato) { asm { mov dx,37Ah mov al,Dato out dx,al } }

El valor al que se desea poner las líneas de control del Puerto paralelo es:

0 0 0 0 0 0 1 1������������������������������

Los bits C3, C1 y C0 de este registro son negados. Por lo tanto, el valor que le pongamos a cada uno de estos bits en el registro de control, saldrá invertido en el pin correspondiente del puerto paralelo. Por lo tanto el valor que debemos establecer en el registro de control (37Ah) es:

0 0 0 0 0 0 1 1

0 0 0 0 1 0 0 0

������������������������������

Pines P.P. Registro 37Ah

Ese valor binario equivale a 8 decimal, por lo tanto las sentencias para enviar este valor quedaría así: int x = 8; EnviarPtoC(x); 4.3. Modificar la función EnviarPtoC para que además de enviar un dato por el registro de

control, primero invierta los bits negados de tal forma que el dato que se desee enviar a través de la función sea el que realmente salga en los pines del puerto paralelo.

void EnviarPtoC(char Dato) { Dato = 11 ^ Dato; //00001011 ^ Dato, invierte los bits negados de Dato

asm //Enviar el dato { mov dx,37Ah mov al,Dato out dx,al } }

Page 8: UNIDAD IV Manejo de Puertos de E/S

- 203 -

4.2.3. Importar datos del Puerto Paralelo

El registro que se utilizará para introducir información digital del mundo exterior será el registro de Estado, ya que es este el que tiene sus líneas de entrada en el puerto paralelo. Un dato binario de entrada generalmente se introducirá a la computadora por medio de sensores digitales o de interruptores. Estos últimos generalmente se utilizan cuando se están realizando pruebas a algún programa que controle el puerto paralelo.

La dirección del registro de Estado es generalmente 0379h. Las líneas de entrada del puerto

paralelo únicamente afectan a los 5 bits últimos bits de este registro. Los demás bits permanecen siempre en estado alto. El útimo bit de este registro es negado, esto quiere decir que si se introduce un estado alto por esta línea, en el registro se leerá un estado bajo y viceversa. A continuación se ilustra la correspondencia de los bits del registro de estado con los pines del puerto paralelo:

11 10 12 13 15 No tienen

�����������������������������������

Pines P.P. Registro 379h

Debido a que no hay forma de introducir datos en los 3 primeros bits del registro de Estado,

entonces no tiene caso tomarlos en cuenta. Por lo tanto, la función que utilizaremos para leer un dato tomará a los pines del 15, 13, 12, 10 y 11 como si fueran los 5 primeros bits del registro de Estado. Esto se logrará aplicando una rotación de bits a la derecha del dato leido, y posteriormente guardando el resultado en alguna variable. Para obtener el verdadero estado de los pines del puerto paralelo, también tendremos que negar, al final, el estado del pin 11. La definición de esta función es la siguiente: char leer (void) { char Dato; asm //Leer el registro de estado por medio de { //sentencias de lenguaje ensamblador. mov dx, 379h in al, dx mov Dato, al //Dejar el Valor del registro en la variable Dato. } Dato = Dato ^ 128; //Invertir el valor del bit S7 (128d = 10000000b) Dato = Dato >> 3; //Recorrer los bits de Dato, 3 posiciones a la derecha Dato = Dato & 31; //Hacer cero los 3 últimos bits. (31d = 00011111b). return Dato; //La función devuelve el valor de Dato. }

Esta función devolverá el verdadero valor leido de los pines 15, 13, 12, 10 y 11 como si fueran los únicos 5 bits del registro. EJEMPLO 4.4. Realizar un programa que cada segundo lea el valor de los pines de entrada del puerto

paralelo y muestre dicho valor en una etiqueta.

Page 9: UNIDAD IV Manejo de Puertos de E/S

- 204 -

Introduciremos el código en un control Timer que se ejecute cada segundo: char leer(void); . . . void __fastcall TForm1::Timer1Timer(TObject *Sender) { Label1 -> Caption = AnsiString((int)leer()); }

4.2.4. Puerto Paralelo en Windows XP Las funciones vistas anteriormente para acceder al puerto paralelo funcionan adecuadamente en las versiones 95, 98 y ME del sistema operativo Windows, debido a que no se tienen restricciones de acceso al Hardware de la computadora. Sin embargo, Windows NT, 2000 y XP no permiten acceder a los puertos de forma tan sencilla debido a que estos sistemas tienen más seguridad en cuanto al acceso al Hardware de la computadora. En ejecución, programas que contengan las funciones y código de los ejemplos anteriores no funcionarán bajo los ambientes de Windows XP, NT y 2000. Al ejecutarlos nos aparecería un mensaje como este:

Que indica que estamos tratando de acceder a una dirección con privilegios. Para resolver este problema se necesita un Driver o controlador que permita comunicar el Software con el Hardware (puerto paralelo). Después en el programa se debe escribir el código que permita manipular dicho controlador. El controlador que se probó para este libro de texto se llama: Porttalk. Toda la información sobre este controlador así como el controlador y los archivos necesarios para su instalación, pueden ser bajados de la siguiente página de Internet: www.geocities.com/tectuxtlabuilder. Una vez que se haya bajado un archivo comprimido (*.ZIP) de dicha página, seguir los siguientes pasos para instalar el controlador en la computadora: 1.- Descomprimir el archivo .ZIP 2.- Copiar el archivo porttalk.sys (que es el controlador) en la carpeta de sistema: C:\WINDOWS\SYSTEM32\DRIVERS 3.- Hacer doble clic sobre el archivo: porttalk.reg para copiar la información en el registro de windows. 4.- Reiniciar la computadora. 5.- Dar clic derecho sobre el icono de Mi PC y acceder a las PROPIEDADES.

Page 10: UNIDAD IV Manejo de Puertos de E/S

- 205 -

6.- En el cuadro de diálogo de las propiedades del sistema, elegir la pestaña Hardware y luego el botón “Administrador de dispositivos”, tal como se señala en la siguiente figura:

��������

7.- En la ventana que aparecerá, dar clic en el menú Ver/Mostrar dispositivos ocultos. 8.- Expandir la categoría de dispositivos denominada “Controladores que no son Plug and play”. 9.- Compobar que entre ellos se encuentre “PortTalk”. 10.- Si no está, hacer clic en el icono “Buscar cambios de hardware” y de esta forma aparecerá colgando de dicha categoría. 11.- El controlador se activa una vez que es reconocido pero si no lo hace, es necesario dar clic derecho sobre él y entrar a sus propieades. Desde ahí, seleccionar la opción “Utilizar este dispositivo (Habilitar)”. Con el controlador instalado, se crearán algunas funciones para manipular el puerto paralelo a través de dicho controlador. Seguir entonces los siguientes pasos: 1.- Abrir un nuevo proyecto en Builder C++ 2.- Crear una Nueva carpeta llamada: “PtoParalelo”, desde el Explorador de Windows o desde Builder C++. No importa la ubicación de dicha carpeta. 3.- Ir al menú File/New… y elegir la opción “Header File”. Se insertará un nuevo archivo de encabezado. Los archivos de encabezados contienen prototipos de funciones y son de extensión .H. 4.- Ir al menú File/Save y guardar este archivo de encabezado con el nombre de “PortTalk_ioctl.h” (Es necesario escribir la extensión), en la carpeta previamente creada “PtoParalelo”. 5.- En este archivo debe ir la definición de constantes utilizadas en funciones de Windows que llamaran al controlador de PortTalk. El código completo de este archivo de encabezado es el siguiente: #ifndef PortTalk_ioctlH #define PortTalk_ioctlH #define PORTTALK_TYPE 40000 #define IOCTL_IOPM_RESTRICT_ALL_ACCESS \ CTL_CODE(PORTTALK_TYPE, 0x900, METHOD_BUFFERED, FILE_ANY_ACCESS)

Page 11: UNIDAD IV Manejo de Puertos de E/S

- 206 -

#define IOCTL_IOPM_ALLOW_EXCUSIVE_ACCESS \ CTL_CODE(PORTTALK_TYPE, 0x901, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_SET_IOPM \ CTL_CODE(PORTTALK_TYPE, 0x902, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_ENABLE_IOPM_ON_PROCESSID \ CTL_CODE(PORTTALK_TYPE, 0x903, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_READ_PORT_UCHAR \ CTL_CODE(PORTTALK_TYPE, 0x904, METHOD_BUFFERED, FILE_ANY_ACCESS) #define IOCTL_WRITE_PORT_UCHAR \ CTL_CODE(PORTTALK_TYPE, 0x905, METHOD_BUFFERED, FILE_ANY_ACCESS) #endif

6.- Después de escribir este código, guardar los cambios en el archivo. 7.- Ir al menú File/New… y elegir la opción “Header File”. 8.- Guardar el nuevo archivo de encabezado con el nombre de “PortTalk.h” en la carpeta “PtoParalelo”. Este archivo contendrá los prototipos de las funciones que se encargarán de abrir el controlador y leer y escribir en el puerto paralelo a través de él. 9.- Escribir los siguientes prototipos de funciones en el archivo: #ifndef DriverH #define DriverH int AbrirDriver(void); //Función para abrir el controlador void EnviarDato(unsigned short DireccionPto, unsigned char byte); void CerrarDriver(void); //Función para cerrar controlador unsigned char LeerDato(unsigned short DireccionPto); #endif

Observe que se tienen funciones para inicializar controlador, abrir puerto, cerrar puerto, leer

y escribir al puerto.

10.- Guardar los cambios al archivo. 11.- Ir al menú File/New… y crear un nuevo archivo fuente (.CPP) 12.- Guardar el archivo en la carpeta “PtoParalelo” con el nombre de “PortTalk.cpp”. Este archivo contendrá las definiciones de las diferentes funciones señaladas en el archivo de cabecera “PortTalk.h”. Estas funciones utilizan directivas de los archivos de cabecera Windows.h y winioctl.h para el manejo de controladores del sistema operativo. En este libro de texto no se profundizará en dichas directivas. El código de dichas funciones será: //Contenido del archivo fuente PortTalk.cpp #include <vcl.h> #include <windows.h> //Se incluyen los archivos de #include <winioctl.h> //cabecera necesarios. #include "PortTalk_IOCTL.h" #include "PortTalk.h" //Aqui están las definiciones

Page 12: UNIDAD IV Manejo de Puertos de E/S

- 207 -

HANDLE PortTalk_Handle; /* Manejador para el controlador PortTalk*/ //----------------------------------------------------------------------- //Esta function Envía un dato de un byte en el Puerto especificado en el //parámetro DireccionPto. void EnviarDato(unsigned short DireccionPto, unsigned char byte) { unsigned int error; DWORD BytesReturned; unsigned char Buffer[3]; unsigned short * pBuffer; pBuffer = (unsigned short *)&Buffer[0]; *pBuffer = DireccionPto; Buffer[2] = byte; error = DeviceIoControl(PortTalk_Handle, //Enviar al IOCTL_WRITE_PORT_UCHAR, //Puerto &Buffer, //un byte de 3, //información y NULL, //verificar si 0, //hubo algún error. &BytesReturned, NULL);

if (!error) ShowMessage("Error Ocurrido al tratar de abrir PartTalk " + AnsiString(GetLastError()));

} //----------------------------------------------------------------------- //La siguiente función Abre el controlador de Paltalk para que se pueda //acceder a los puertos de la computadora a través de él. Esta función //debe ser llamada antes de cualquier función de lectura o escritura. int AbrirDriver(void) { int error; //Variable para indicar error de apertura PortTalk_Handle = CreateFile("\\\\.\\PortTalk", //Se asigna el GENERIC_READ, //controlador al 0, //manejador NULL, //declarado al OPEN_EXISTING, //inicio del FILE_ATTRIBUTE_NORMAL, //modulo. NULL); if(PortTalk_Handle == INVALID_HANDLE_VALUE) //Si hay error en la { //apertura… ShowMessage("PortTalk: No se pudo acceder al controlador,

asegúrese de que este instalado"); return -1; } return 0; } //-----------------------------------------------------------------------//Función que cierra el controlador PortTalk. Esta función debe ser // llamada cuando ya no se vaya a acceder a los puertos o al terminar el // el programa.

Page 13: UNIDAD IV Manejo de Puertos de E/S

- 208 -

void CerrarDriver(void) { CloseHandle(PortTalk_Handle); //Cerrar manejador del controlador. } //----------------------------------------------------------------------- //Función para leer un dato de un byte de un Puerto cuya dirección será //proporcionada como parámetro. La función devuelve el byte leido del //puerto. unsigned char LeerDato(unsigned short DireccionPto) { unsigned int error; DWORD BytesReturned; unsigned char Buffer[3]; unsigned short * pBuffer; pBuffer = (unsigned short *)&Buffer; *pBuffer = DireccionPto; error = DeviceIoControl(PortTalk_Handle, IOCTL_READ_PORT_UCHAR, &Buffer, 2, &Buffer, 1, &BytesReturned, NULL); if (!error) ShowMessage("Ocurrió un error mientras de leía el controlador "+ AnsiString(GetLastError())); return(Buffer[0]); }

13.- Guardar los cambios en el último archivo. Después de esto, para poder utilizar el controlador y las funciones de estos archivos en un programa, deberemos incluir en la misma carpeta, todos los archivos creados y además, agregar algunas directivas que incluyan dichos archivos en el proyecto Builder C++. En el siguiente ejemplo se ilustra el uso de PortTalk en un programa: EJEMPLO 4.5. Escribir el código del evento OnClick de un botón que al ser presionado, enviará por el

puerto paralelo un valor escrito previamente en la caja de edición Edit1.El sistema operativo utilizado es Windows XP.

Se utilizará el controlador PortTalk. Entonces lo primero que se debe hacer es copiar los

archivos: PortTalk_IOCTL.H, PortTalk.H y PortTalk.CPP creados previamente, a la misma carpeta o localidad en donde se guardará el proyecto del programa que se va a realizar.

En el proyecto del programa ir al menú Project/View Source… Para visualizar el archivo

principal. Al principio de este código se debe insertar la directiva USEUNIT para indicarle que se debe usar el archivo PortTalk.CPP en el proyecto:

Page 14: UNIDAD IV Manejo de Puertos de E/S

- 209 -

#include <vcl.h> #pragma hdrstop USERES("Project1.res"); USEUNIT("PortTalk.cpp"); //Utilizar PortTalk.cpp USEFORM("Unit1.cpp", Form1); //--------------------------------------------------------------------------- WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { . . .

Al inicio del módulo del formulario principal del proyecto se debe incluir el archivo PortTalk.H. Si se estuviese haciendo un programa con múltiples formularios, este archivo debe ser incluido en todos los módulos de los formularios. #include <vcl.h> #pragma hdrstop #include "Unit1.h" #include "PortTalk.h" //----------------------------------------------------------------------- #pragma package(smart_init) #pragma resource "*.dfm" TForm1 *Form1;

Abrir el controlador al inicio del programa y enviar el valor 0 al puerto: __fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner) { AbrirDriver(); //Función para inicializar el controlador EnviarDato(0x378,0); //Enviar cero al puerto 0x378 }

En evento OnClose del formulario principal se debe cerrar el controlador y enviar el dato cero: void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action) { EnviarDato(0x378,0); //Se envia cero al puerto 0x378 CerrarDriver(); //Función para cerrar el controlador }

En el evento OnClick del botón de envío se enviará el número escrito en Edit1: void __fastcall TForm1::Button1Click(TObject *Sender) { int x=0; x = Edit1 -> Text.ToInt(); EnviarDato(0x378,x); //Enviar al puerto 0x378 }

Page 15: UNIDAD IV Manejo de Puertos de E/S

- 210 -

Después de esto se debe de guardar el proyecto antes de compilarlo. Recuérdese que debe guardarse en la misma carpeta en donde se copiaron los archivos de código del controlador.

El código de los archivos anteriores también puede ser bajado de la página web:

www.geocities.com/tectuxtlabuilder. 4.6. Realizar un programa que controle el encendido de 8 lámparas de acuerdo a la

combinación hecha con 3 interruptores. Los interruptores se encuentran conectados a los bits S3, S4 y S5 del registro de estado del puerto paralelo; mientras que las lámparas están conectadas al Registro de datos del puerto paralelo. El encendido de las lámparas se hará de acuerdo a la siguiente tabla:

Interruptores (puerto 379h)

Lámparas (Puerto 378h)

S5 S4 S3 Valor Decimal

D7 D6 D5 D4 D3 D2 D1 D0 Valor Decimal

0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 1 1 0 0 0 0 0 0 1 0 2 0 1 0 2 0 0 0 0 0 1 0 0 4 0 1 1 3 0 0 0 0 1 0 0 0 8 1 0 0 4 0 0 0 1 0 0 0 0 16 1 0 1 5 0 0 1 0 0 0 0 0 32 1 1 0 6 0 1 0 0 0 0 0 0 64 1 1 1 7 1 0 0 0 0 0 0 0 128

La verificación del estado de los interruptores se hará cada vez que el usuario presione un botón en el programa. Visualizar también el valor del puerto de datos en binario por medio de una etiqueta.

Primeramente diseñar la siguiente interfaz gráfica:

Recordar que debemos copiar los archivos de PortTalk en la misma localidad en donde guardemos el proyecto y también agregaremos los siguientes archivos include al inicio del módulo del formulario:

#include "porttalk.h" //Prototipos porttalk #include "math.h" //Se utilizará la función de potencia AnsiString Binario (char Dato); //Prototipo para la función de conversión a //binario.

Page 16: UNIDAD IV Manejo de Puertos de E/S

- 211 -

Al iniciar el programa debemos abrir el controlador PartTalk tal como se hizo en el ejemplo anterior. Posteriormente, el código para el evento OnClick del botón VERIFICAR es el siguiente:

void __fastcall TForm1::VERIFICARClick(TObject *Sender) { char DatoLeido = 0, DatoEnviado = 0; DatoLeido = LeerDato(0x379); //Obetener dato DatoLeido = DatoLeido >> 3; //Recorrer a la derecha 3 bits DatoLeido = DatoLeido & 7; //Poner a cero los 5 bits mas significativos DatoEnviado =(char)pow(2,(double)DatoLeido); //Poner valor al dato de //salida de acuerdo a la tabla. EnviarDato(0x378,DatoEnviado); //Enviar dato de salida. Label1 -> Caption = Binario(DatoEnviado); //Imprimir dato enviado en } //binario.

Obsérvese que para codificar el valor del puerto 0x379 se necesitaron pocas líneas de código debido a que sólo se elevo el valor 2 a la potencia del número contenido en el puerto 0x379. Para visualizar en binario se utilizó la función Binario() cuya definición se encuentra en la unidad 3 de este libro de texto. Recuérde que se tiene que cerrar el controlador PortTalk en el evento OnClose de este programa, tal como se hizo en el ejemplo anterior.

Page 17: UNIDAD IV Manejo de Puertos de E/S

- 212 -

4.3. Puerto Serial

4.3.1. Comunicación serial

La comunicación serial consiste en transmitir los bits de datos uno por uno, y no en bloques de 8 como en la comunicación paralela. La transmisión de datos en serie es una de las más comunes para aquellas aplicaciones en las que la velocidad no es demasiado importante, o no es posible conseguirla. Además se utilizan para enviar datos a traves de largas distancias, ya que las comunicaciones en paralelo exigen demasiado cableado para ser operativas.

El puerto que se encarga de realizar las comunicaciones seriales en la computadora, es el Puerto serial. El puerto serial es más difícil de manipular que el puerto paralelo. En la mayoría de las ocasiones cualquier comunicación realizada a través del puerto serial será convertida en una comunicación paralela antes de ser empleada. Si hablamos del programa de control del puerto, tendremos más registros que atender que con un puerto paralelo estándar.

Algunas ventajas de utilizar el puerto serial son las siguientes:

1. No se necesitan tantos cables para transmitir como en la comunicación paralela. Generalmente, en la configuración de modo Null del puerto serial, únicamente se necesitan 3 cables para transmitir datos (transmisión, recepción y tierra), con el puerto paralelo se haría con 9 cables. Esto reduce los costos, sin embargo hay que tomar en cuenta el costo de los circuito de interface en los puntos terminales.

2. Los cables seriales pueden ser mas largos que los paralelos. Dependiendo de la velocidad

de transmisión, se pueden usar cables de hasta 15 metros. Esto es debido a que el puerto serial representa un bit en ‘1’ con voltajes entre -3 y -25 volts, y un ‘0’ entre +3 y +25 volts, esto quiere decir que hay una diferencia de 50 volts entre el 1 y el 0. El puerto paralelo representa el 0 con ‘0’ volts y el ‘1’ con 5 volts, por lo que la diferencia entre el 0 y el 1 es únicamente de 5 volts. Esto hace que la pérdida de información no sea un problema en los cables de puerto serial como en los paralelos cuando se utilizan en distancias largas.

3. Actualmente, el uso de los microcontroladores para comunicarse con la computadora se ha

hecho muy popular y eficiente. Muchos de estos dispositivos traen contruidos una interfaz de comunicación serial (SCI). Los microcontroladores se utilizan para convertir los datos en serie de la computadora recibidos por SCI, en datos paralelos utilizados para controlar infinidad de dispositivos externos. También se utilizan para introducir datos seriales a la computadora, provenientes de sensores externos.

Los dispositivos que utilizan cables seriales para comunicarse se dividen en 2 categorías.

Estas son DCE (Equipo de comunicación de datos) y DTE (Equipo terminal de datos). Los equipos de comunicación de datos son los dispositivos como un MODEM o un plotter; mientras que el equipo terminal de datos es la computadora en si.

Page 18: UNIDAD IV Manejo de Puertos de E/S

- 213 -

4.3.2. La Norma RS-232C

El RS-232C es un estándar diseñado en los 60s para comunicar un equipo terminal de datos o DTE (Data Terminal Equipment, la computadora) y un equipo de comunicación de datos o DCE (Data Communication Equipment, habitualmente un modem). El RS-232C constituye la tercera revisión de la antigua norma RS-232, propuesta por la EIA (Asociación de Industrias Electrónicas), realizándose posteriormente un versión internacional por el CCITT, conocida como V.24. Las diferencias entre ambas son mínimas, por lo que a veces se habla indistintamente de V.24 y de RS-232C (incluso sin el sufijo "C"), refiriéndose siempre al mismo estándar.

El puerto serial de la computadora es compatible con la norma RS-232C, y está presente

prácticamente en todas las computadoras actuales, a pesar de que poco a poco está siendo reemplazada por el puerto USB.

A) Especificaciones eléctricas:

La norma RS-232C especifica que:

� Un cero lógico (también conocido como “espacio” en las comunicaciones seriales) debe estar entre +3 y +25 volts. Generalmente se utilizan +12 volts.

� Un uno lógico (también conocido como “marca” en las comunicaciones seriales) debe estar entre -3 y -23 volts. Generalmente se utilizan -12 volts.

� La región entre -3 y +3 volts es indefinida. � El voltaje en circuito abierto no debe exceder los 25 volts, en referencia a tierra (GND). � La corriente en cortocircuito no debe exceder los 500 mA.

B) Conector:

La norma especifica dos tipos de conectores: DB-25 de 25 pines, que es similar al del puerto

paralelo, y el DB-9 de 9 pines, que es mas barato y más utilizado. En cualquier caso, no se suele emplear mas de 9 pines en el conector DB-25. Ambos conectores con machos en la parte trasera de la computadora, así que para conectar algún dispositivo, se necesitará un conector DB-25 o DB-9 hembra. A continuación se muestra una figura con la distribución de los pines en el DB-9. El DB-25 es similiar al del puerto paralelo.

�� �� ����

�� ���� ���

Page 19: UNIDAD IV Manejo de Puertos de E/S

- 214 -

La asignación de cada pin, tanto en el DB-25 como en el DB-9 se muestran en la siguiente tabla:

NÚMERO DE PIN DB-25 DB-9

SEÑAL DESCRIPCIÓN E/S

1 1 - Chasis (Protección eléctrica) - 2 3 TxD Transmit Data S 3 2 RxD Receive Data E 4 7 RTS Request To Send S 5 8 CTS Clear To Send E 6 6 DSR Data Set Ready E 7 5 SG Signal Ground - 8 1 CD/DCD (Data) Carrier Detect E

20 4 DTR Data Terminal Ready S 22 9 RI Ring Indicator E

El término de Entrada y Salida es con respectoa al DTE (la computadora).

La función de cada pin se muestra en la siguiente tabla:

SEÑAL DESCRIPCIÓN FUNCIÓN TxD Transmit Data Salida de datos seriales RxD Receive Data Recepción de datos seriales CTS Clear To Send Indica que el MODEM esta listo

para intercambiar datos DCD Data Carrier Detect Detección de portadora DSR Data Set Ready Indica al DTE que el MODEM

esta listo para comunicarse DTR Data Terminal Ready Indica al MODEM que el DTE

esta listo para comunicarse RTS Request To Send Indica al MODEM que el DTE

esta listo para intercambiar datos RI Ring Indicador Detección del sonido de llamada

C)Transmisión y Recepción de Datos: La transmisión y recepción de Datos a través del Puerto Serial utiliza los pines TxD (para

transmitir) y RxD (Para recibir). La comunicación del RS-232C es asíncrona, esto quiere decir que los datos no son enviados de acuerdo a una señal de reloj , sino que, cada byte de dato, es enviado utilizando un bit de inicio. La transmisión de un carácter o byte se realiza de la siguiente forma:

La línea que transmite los datos en serie está inicialmente en estado alto. Al comenzar la

transferencia, se envía un bit a 0 ó bit de inicio. Tras él irán los bits de datos a transmitir (puede configurarse para que sean 8, 7, 6 o 5 bits de datos); estos bits están espaciados con un intervalo temporal fijo y preciso, ligado a la velocidad de transmisión que se esté empleando. Tras ellos podría venir o no un bit de paridad generado automáticamente. El bit de paridad indica si el numero de bits transmitidos es par o impar se utiliza para detectar fallos en la transmisión. Al final,

Page 20: UNIDAD IV Manejo de Puertos de E/S

- 215 -

aparecerá un bit a 1, que es el bit de parada o bit de stop (también puede configurarse para que sea un bit y medio o 2 bits de stop). Lo de medio bit significa que la señal correspondiente en el tiempo a un bit dura la mitad; realmente, en comunicaciones se utiliza el término baudio para hacer referencia a las velocidades, y normalmente un baudio equivale a un bit por segundo. La presencia de bits de inicio y parada permite sincronizar el dispositivo emisor con el receptor, haciendo que los relojes de ambos vayan a la par. Es por esto que también se dice que el RS-232 es asíncrono por caracter y sincrono por bit. Normalmente el protocolo de comunicación utilizado es el 8N1, lo cual significa 8 bits de datos, sin paridad y con 1 bit de stop. A continuación se muestra el diagrama de transmisión de un dato con formato 8N1:

��� ������

� ����

Inicio Stop0 1 2 3 4 5 6 7�����

����������

� ��

Una vez comenzado la transmisión de un dato, los bits tienen que llegar uno detrás de otro a

una velocidad constante. Dicha velocidad también puede ser configurada, normalmente es de 9600 baudios o más.

Tanto el dispositivo a conectar como la computadora (o el programa terminal) tienen que

usar el mismo protocolo serie para comunicarse entre si. Puesto que el estándar RS-232 no permite indicar en que modo se esta trabajando, es el usuario quien tiene que decidirlo y configurar ambas partes. Como ya se ha visto, los parámetros que hay que configurar son:

� Número de bits de datos a transmitir � Bit de paridad � Numero de bits de stop � Velocidad de transmisión

Los pines que portan los datos son RXD y TXD. Las demás se encargan de otros trabajos:

DTR indica que la computadora está encendida, DSR que el aparato conectado a dicho puerto esta encendido, RTS que la computadora puede recibir datos (porque no esta ocupado), CTS que el aparato conectado puede recibir datos, y DCD detecta que existe una comunicación, presencia de datos.

4.3.3. Puerto serial en NULL

El modo NULL del puerto serial es utilizado para conectar dos DTEs juntos. Esta es una forma muy común, fácil y barata de transferir datos entre dos computadoras, entre una computadora y un microcontrolador con interfaz de comunicación serial, y para utilizar algunos protocolos de MODEM.

En el siguiente diagrama se muestra el cableado de pines en modo NULL:

Page 21: UNIDAD IV Manejo de Puertos de E/S

- 216 -

� �

���

�����

� �

���

�����

� ��

���

������

� ��

���

������

���� !��

���� ��

���� !��

���� ��

������������ ������������

Se puede observar que solo se necesitan 3 líneas para interconectarse: TxD, RxD y SG. El resto de los pines se encuentran interconectados entre sí como se observa en el diagrama. La teoria de funcionamiento de esta conexión es muy fácil de entender: los datos transmitidos por el dispositivo 1, deben ser recibidos por el dispositivo 2, entonces conectamos TxD del dispositivo 1 con RxD del dispositivo 2. Los datos transmitidos por el dispositivos 2 deben ser recibidos por el dispositivo 1, entonces conectamos TxD del dispositivo 2 con RxD del dispositivo 1. La terminal de tierra debe ser común para los dos.

Utilizando el modo NULL se puede realizar un arreglo llamado “conexión de

retroalimentación”, el cual consiste en conectar el pin transmisor del puerto con el pin receptor del mismo puerto, tal como se observa a continuación:

� �

��

�����

� ��

��

������

���� !

�� ���� ��

Esta conexión es muy útil cuando se estan proban programas que utilicen el puerto serial. Se

tiene la línea de transmisión y la de recepción interconectadas, de tal manera que todo lo que envíe el programa de prueba será inmediatamente recibido por el mismo puerto.

4.3.4. UART UART significa: “Transmisor-Receptor Asíncrono Universal”. Es un circuito integrado específico que utiliza la computadora para controlar el puerto serie. Este viene incluido sobre la tarjeta madre y algunas veces viene dentro de otros chips que también controlan otros puertos como el paralelo, puerto de juegos, floppy o discos duros. Normalmente se utilizan los chips de la serie 8250, que son los siguientes modelos:

� 8250. Primer UART en esta serie, con fallor, solo llega a 9600 baudios.

Page 22: UNIDAD IV Manejo de Puertos de E/S

- 217 -

� 8250A. Este UART es más rápido que el 8250. � 8250B. Muy similar al 8250. � 16450. Versión corregida del 8250, llega hasta 38.4 Kbps. Todavía muy común. � 16550. Primera generación de UARTS con buffers de E/S. Sin embargo no funciona. � 16550A. Es la UART más común y es utilizado para comunicaciones de alta velocidad, por

ejemplo los modems de 14.4 K y 28.8 K. En esta UART si funcionan los buffers FIFO. Vienen en las placas de la gama Pentium.

� 16650. UART muy reciente. Contiene un FIFO de 32 bits. � 16750. Producido por Texas Instruments. Contiene un FIFO de 64 bits.

4.3.5. Registros del Puerto serial Los registros del puerto serial son en realidad registros de la UART. Las computadoras compatibles pueden tener conectados, de manera normal, hasta 4 puertos seriales, nombrados COM1-COM4. Para controlar al puerto serie, la CPU emplea direcciones de puertos de E/S y líneas de interrupción (IRQ). Mediante los puertos de E/S se pueden intercambiar datos, mientras que las IRQ producen una interrupción para indicar al CPU que ha ocurrido un evento (por ejemplo, que ha llegado un dato, o que ha cambiado el estado de algunas señales de entrada). El CPU debe responder a estas interrupciones lo mas rápido posible, para que recoja el dato antes de que el siguiente lo sobrescriba. Sin embargo, las UART 16550A incluyen unos buffers de tipo FIFO, dos de 16 bytes (para recepción y transmisión), donde se pueden guardar varios datos antes de que el CPU los recoja. Esto también disminuye el numero de interrupciones por segundo generadas por el puerto serial. Las direcciones base de los puertos seriales y los líneas de interrupción que funcionan en la mayoría de las computadoras personales, se muestran en la siguiente tabla:

NOMBRE DIRECCIÓN BASE IRQ COM 1 3F8h 4 COM 2 2F8h 3 COM 3 3E8h 4 COM 4 2E8h 3

El principal problema reside en que sólo están previstas 2 interrupciones para los puertos serie. Ello implica que generalmente sólo 2 de los puertos podrán emplear interrupciones al mismo tiempo, debido a la arquitectura del bus ISA. Generalmente COM1 y COM3 compartirán la IRQ4 (INT 0Ch) y COM2/COM4 la IRQ3 (INT 0Bh). No importa compartir una misma IRQ en dos puertos siempre que no se usen conjuntamente, ya que en caso contrario puede haber problemas. Es por ello que, con el auge de las comunicaciones, los fabricantes de computadoras incluyeron un puerto especial PS/2 para el ratón, dejando así libre un puerto serie. Los registros del puerto serial son los siguientes:

Page 23: UNIDAD IV Manejo de Puertos de E/S

- 218 -

OFFSET DLAB MODO (R = LECTURA

W = ESCRITURA) NOMBRE SIGNIFICADO

+0 0 R RBR Receiver Buffer Register (Registro buffer de recepción)

+0 0 W THR Transmitter Holding Register (Registro de retención de transmisión)

+0 1 R/W DLL Divisor Match LSB (Divisor de velocidad, byte bajo)

+1 0 R/W IER Interrupt Enable Register (Registro de habilitación de interrupciones)

+1 1 R/W DLM Divisor Match MSB (Divisor de velocidad, byte alto)

+2 - R IIR Interrupt Identification Register (Registro de identificación de interrupciones)

+2 - W FCR FIFO Control Register (Registro de control FIFO – Solo 16550 -

+3 - R/W LCR Line Control Register (Registro de control de línea) El bit 7 es DLAB

+4 - R/W MCR Modem Control Register (Registro de control del MODEM)

+5 - R/W LSR Line Status Register (Registro de estado de línea)

+6 - R/W MSR Modem Status Register (Registo de estado del MODEM)

+7 - R/W SCR Scratch Register (Registro residual).

El OFFSET muestra los desplazamientos que hay que sumar a la dirección base para obtener la dirección del registro indicado. DLAB es el bit 7 del Registro de Control de Línea (LCR) y, junto con el sentido de acceso (lectura o escritura), es utilizado para que la UART pueda tener 12 registros con sólo 8 direcciones de puerto. Como se observa en la tabla, cuando DLAB esta en 1, se puede acceder a determinados registros, y al poner a DLAB en 0, podremos acceder a otros registros utilizando la misma dirección de puerto. Realmente, DLAB se emplea sólo puntualmente para poder acceder y programar los registros que almacenan el divisor de velocidad; el resto del tiempo, DLAB estará a 0 para acceder a otros registros más importantes.

A) LCR (Registro de Control de Línea)

Este registro controla el formato del carácter de datos. Sus bits son los siguientes:

Page 24: UNIDAD IV Manejo de Puertos de E/S

- 219 -

��������������������������������������������������������������������������������������������������

DLABBreak

ControlStickParity EPS

0 0 0 Sin paridad0 0 1 Paridad impar0 1 1 Paridad par1 0 1 Marca1 1 1 Espacio

0 0 Datos de 5 bits0 1 Datos de 6 bits1 0 Datos de 7 bits1 1 Datos de 8 bits

PEN STB WLS1 WLS0

Los bits WLS seleccionan el tamaño del dato empleado. STB indica el número de bits de stop, que pueden ser 1 (STB=0) ó 2 (STB=1), al trabajar con datos de 5 bits STB=1 implica 1.5 bits de stop. PEN (Parity Enable), EPS (Even Parity Select) y Stick Parity permiten establecer la paridad. Break Control debe ser puesto a 0. El bit DLAB (Divisor Latch Access Bit) puesto a 1 permite acceder a los Latches divisores DLL y DLM del BRG en lectura y escritura. Para acceder al RBR, THR y al IER debe ser puesto a 0.

B) LSR (Registro de Estado de Línea)

Este suele ser el primer registro consultado tras una interrupción. Este es un registro para

lectura únicamente. Sus bits son los siguientes:

��������������������������������������������������������������������������������������������������

0 TEMT THRE BI

Transmisor Vacío

BreakInterrupt

Error de bits destop

Error de paridad

Error desobre-escritura

Dato listoRegistro de retenciónde transmisiónvacío

FE PE OE DR

DR se activa cuando hay un carácter listo en el RBR (Registro Buffer de recepción) y es puesto a 0 cuando se lee el RBR. Los bits 1 al 4 de este registro (OE, PE, FE y BI) son puestos a 0 al consultarlos y al activarse pueden generar una interrupción de prioridad 1 si ésta interrupción está habilitada. OE se activa para indicar que el dato en el RBR no ha sido leído por la CPU y acaba de llegar otro que lo ha sobreescrito. PE indica si hay un error de paridad. FE indica si el carácter recibido no tiene los bit de stop correctos. BI se activa cuando la entrada de datos es mantenida en espacio (a 0) durante un tiempo superior al de transmisión de un carácter. THRE indica que la UART puede aceptar un nuevo carácter para la transmisión: este bit se activa cuando el THR (Registro de retención de transmisión) queda libre y se desactiva escribiendo un nuevo carácter en el THR. Se puede producir, si está habilitada; la interrupción THRE (prioridad 3); La indicación de interrupción se borra leyendo el IIR (Registro de identificación de interrupciones). La UART emplea un registro interno para ir desplazando los bit y mandar en serie (el Registro de desplazamiento de transmisión), dicho registro se carga desde el THR. Cuando ambos registros (THR y el Registro de transmisión de desplazamiento) están vacíos, TEMT se activa; volverá a desactivarse cuando se deje otro dato en el THR hasta que el último bit salga por el pin TxD. Esto significa que TEMT indica cuando se termina de transmitir un dato completamente.

Page 25: UNIDAD IV Manejo de Puertos de E/S

- 220 -

C) BRSR (Baut Rate Select Register o Registro seleccionador de velocidad)

Este registro esta formado del DLM(Divisor de velocidad byte alto) y DLL (Divisor de

velocidad byte bajo). Estos dos registros de 8 bits constituyen un valor de 16 bits que será el divisor que se aplicará a la frecuencia base para seleccionar la velocidad de transmisión y recepción a emplear. Dicha frecuencia base (que generalmente es de 1.8432 MHz) será dividida por 16 veces el valor almacenado aquí. Por ejemplo, para obtener una velocidad de 2400 baudios:

Por lo tanto al registro DLL, al cual se accede por medio de la dirección Base + 0 y DLAB = 1, debe dársele el valor de 48 y al registro DLM que es byte alto y se accede por medio de la dirección Base + 1 y DLAB = 1, debe dársele el valor 0, ya que el valor 48 únicamente necesita 1 byte. Si se tuviera que poner al BRSR un valor que ocupe mas de un byte, es ahí donde se asigna al registro DLM un valor distinto de cero. En la siguiente tabla se muestran algunas de las velocidades más comunes y los valores hexadecimales de los divisores alto y bajo:

Velocidad (BPS) Divisor (Decimal) DLM DLL 50 2340 09h 00h

300 384 01h 80h 600 192 00h C0h

2400 48 00h 30h 4800 24 00h 18h 9600 12 00h 0Ch

19200 6 00h 06h 38400 3 00h 03h 57600 2 00h 02h

115200 1 00h 01h

D) RBR (Registro Buffer de Recepción)

El circuito receptor de la UART es programable para 5, 6, 7 u 8 bits de datos. En el caso de emplear menos de 8, los bits superiores de este registro quedan a 0. Los datos entran en serie por RxD (comenzando por el bit 0) en un registro de desplazamiento gobernado por el reloj de la UART, sincronizado con el bit de inicio. Cuando un carácter completa el registro de desplazamiento de recepción, sus bits son volcados al RBR y el bit DR del LSR es activado para indicar a la CPU que puede leer el RBR. El diseño de la UART permite la recepción continua de datos sin pérdidas: el RBR almacena siempre el último carácter recibido dando tiempo suficiente a la CPU para leerlo mientras simultáneamente está cargando el registro de desplazamiento con el siguiente; si la CPU tarda demasiado un nuevo dato podría aparecer en el RBR antes de haber leído el anterior, es entonces cuando se activa el bit OE (Sobreescritura) del LSR.

E) THR (Registro de Retención de Transmisión)

Frecuencia Base

(16) (Velocidad) =

1843200

(16) (2400) = 48

Page 26: UNIDAD IV Manejo de Puertos de E/S

- 221 -

El registro de retención de transmisión almacena el siguiente carácter que va a ser transmitido en serie mientras el registro de desplazamiento de transmisión está enviando el carácter actual. Cuando el registro de desplazamiento se vacíe, será cargado desde el THR para transmitir el nuevo carácter. Al quedar vacío THR, el bit THRE del registro LSR se activa. Cuando estén vacíos tanto el THR como el registro de desplazamiento de transmisión, el bit TEMT del LSR se activa.

F) IIR (Registro de Identificación de Interrupciones) Existen 4 niveles de prioridad en las interrupciones generables por la UART, por este orden: 1. Estado de la línea de recepción. 2. Dato recibido disponible. 3. Registro de retención de transmisión vacío. 4. Estado del modem. La información que indica que hay una interrupción pendiente y el tipo de la misma es almacenada en el IIR. El IIR indica la interrupción de mayor prioridad pendiente. No serán reconocidas otras interrupciones hasta que el CPU envíe la señal de reconocimiento apropiada. En el registro IIR, el bit 0 indica si hay una interrupción pendiente (bit 0=0) o si no la hay (bit 0=1), esto permite tratar las interrupciones en modo polled consultando este bit. Los bits 1 y 2 indican el tipo de interrupción. Los restantes están a 0 en el 8250, pero el 16550 utiliza alguno más. En este libro de texto no se ahondará mas en este registro.

G) IER (Registro de Habilitación de Interrupciones)

Este registro de escritura se utiliza para habilitar los eventos que generarán interrupciones y, por consiguiente, van a ser solicitadas a la CPU. Deshabilitar el sistema de interrupciones inhibe el IIR y desactiva el aviso de interrupciones. Los bits que habilitan los diversos eventos que generan interrupciones del IER son los siguientes: ��������������������������������������������������������������������������������������������������

0 0 0 0

IER0: Poner a 1 para habilitar interrupción de dato disponible.IER1: Poner a 1 para habilitar interrupción de registro de retención de transmisión vacío.IER2: Poner a 1 para habilitar interrupción de error de recepción (bits 1 al 4 del LSR).IER3: Poner a 1 para habilitar interrupción ante el cambio del MSR (Registro de estado del modem).

IER3 IER2 IER1 IER0

4.3.6. Transmisión y Recepción En La UART

La sección de transmisión de la UART del puerto serial consiste en el Registro de Retención de transmisión (THR), el Registro de Desplazamiento de la Transmisión (TSR) y en la lógica de control asociada. Dos bits en el LSR indican si está vacío el THR (bit THRE) o el TSR (bit TEMT). El carácter de 5-8 bits a ser transmitido es escrito en el THR; la CPU debería realizar esta

Page 27: UNIDAD IV Manejo de Puertos de E/S

- 222 -

operación sólo si THRE está activo: este bit es activado cuando el carácter es copiado del THR al TSR durante la transmisión del bit de inicio.

Cuando el transmisor está inactivo, tanto THRE como TEMT están activos. El primer

carácter escrito provoca que THRE baje; tras completarse la transferencia vuelve a subir aunque TEMT permanecerá bajo mientras dure la transferencia en serie del carácter a través de TSR. Si un segundo carácter es escrito en THR, THRE vuelve a bajar y permanecerá bajo hasta que el TSR termine la transmisión, porque no es posible volcar el contenido de THR en TSR hasta que este último no acabe con el carácter que estaba transmitiendo. Cuando el último carácter ha sido transmitido fuera del TSR, TEMT vuelve a activarse y THRE también lo hará tras un cierto tiempo (el que tarda en escribirse THR en TSR).

En la recepción, los datos en serie asíncronos entran por el pin RxD. El estado inactivo de la línea se considera el '1' lógico. Un circuito de detección de bit de inicio está continuamente buscando una transición alto - bajo que interrumpa el estado inactivo. Cuando la detecta, se resetea un contador interno y cuenta 7½ pulsos de reloj (tener en cuenta que la frecuencia base es dividida por 16), posicionándose en el centro del bit de inicio. El bit de inicio se considera válido si RxD continúa aún bajo en ese momento. La validación del bit de inicio evita que algún ruido en la línea sea confundido con un nuevo carácter.

El LCR tiene toda la información necesaria para la recepción: tamaño del carácter (5-8 bits), número de bits de stop, si hay paridad o no, etc. La información de estado que se genere será depositada en el LSR. Cuando un carácter es transmitido desde el Registro de Desplazamiento de la Recepción (RSR) al Registro Buffer de Recepción (RBR), el bit DR del LSR se activa. La CPU lee entonces el RBR, lo que hace bajar de nuevo DR. Si el carácter no es leído antes de que el siguiente carácter que se está formando pase del RSR al RBR, el bit OE (overrun) del LSR se activa. También se puede activar PE en el LSR si hay un error de paridad. Finalmente, la circuitería que chequea la validez del bit de stop podría activar el bit FE del LSR en caso de error. 4.3.7. Control del Puerto serial con Builder C++ Hay varias formas de realizar un programa que controle el puerto serial, una de ellas es utilizando el lenguaje ensamblador. Otra es utilizar un lenguaje de programación de alto nivel, como Builder C++, el cual cuenta con las funciones necesarias para establecer las propiedades iniciales de la comunicación serial sin tener que meterse a fondo con el control de cada uno de los registros analizados anteriormente. 4.3.7.1. Objeto Thread Un objeto Thread es una clase especial de objeto de Builder C++ que se encarga de simular la ejecución de un conjunto de instrucciones en el mismo instante de tiempo en que se ejecuta el programa que está llamando a dicho objeto Thread. Es decir, mientras se ejecuta los eventos de algún programa principal, se pueden estar ejecutando otros programas representados a través de objetos Thread. Es como ejecutar varios programas al mismo tiempo pero que están relacionados entre sí, ya que pueden compartir variables, datos e incluso pueden influir en la ejecución de los otros. De esta forma, se puede utilizar un objeto Thread para realizar un programa en el que, al

Page 28: UNIDAD IV Manejo de Puertos de E/S

- 223 -

mismo tiempo en el que se llevan a cabo eventos variados y se envían datos por el puerto serial, también se está esperando la llegada de algún dato por el puerto serial. El programa principal se encargará de ejecutar los diversos eventos y de envíar datos por el puerto serial, y el objeto Thread de esperar la llegada de un dato y después visualizarlo por medio de algún componente, o almacenarlo en alguna variable global que compartan tanto el objeto Thread como el programa principal. A estos programas se les llama de multiproceso porque ejecutan varias sentencias al mismo tiempo. Para insertar un objeto Thread se siguen los siguientes pasos:

1. En un nuevo proyecto de Builder, ir al menú File/New… y elegir Thread Object (Objeto Thread).

2. Escribir el nombre de la clase del objeto Thread. 3. Se tendrá un nuevo módulo de código llamado Unit2.cpp. Suponiendo que el nombre de

la clase fue ObjetoThread, Unit2.cpp aparecería de la siguiente forma: #include <vcl.h> #pragma hdrstop #include "Unit2.h" #pragma package(smart_init) //--------------------------------------------------------------------------- // Important: Methods and properties of objects in VCL can only be . . . //--------------------------------------------------------------------------- __fastcall ObjetoThread::ObjetoThread(bool CreateSuspended) : TThread(CreateSuspended) { } //--------------------------------------------------------------------------- void __fastcall ObjetoThread::Execute() { //---- Place thread code here ---- } //---------------------------------------------------------------------------

Se observan 2 métodos: ObjetoThread() y Execute(), en el primero se escriben las sentencias de inicialización del objeto Thread y en el segundo el código que se ejecutará con el objeto Thread. (Es decir el que se ejecutará junto con el programa principal). Dentro del código del objeto Thread no se pueden manipular directamente componentes del programa principal, como cajas de edición, etiquetas, etc. Esto se tiene que hacer a través de alguna función que luego debe ser llamada con el método Synchronize(). Por ejemplo, si a través del código del objeto Thread quisieramos visualizar un texto en una etiqueta del formulario Form1, tendríamos que crear primeramente una función dentro del módulo de código de Thread, que se encargue de eso: void __fastcall ObjetoThread::Visualiza(void)

Page 29: UNIDAD IV Manejo de Puertos de E/S

- 224 -

{ Form1 -> Label1 -> Caption = “Texto que se muestra en Label1”; } Después, para llamar dicha función se utiliza el método Synchronize(): void __fastcall ObjetoThread::Execute() { //Sentencias Synchronize(Visualiza); } EJEMPLO 4.7. Realizar un programa que envíe los caracteres escritos en un componente TMemo a

través del puerto serial, y que muestre en otro componente TMemo los caracteres recibidos. Utilizar un objeto Thread y el protocolo 8N1 para la comunicación.

El componente TMemo es un área en la que se puede escribir varias líneas de texto (ver

Unidad 5). Se encuentra en la página Standard de la paleta de componentes con el ícono: . Entonces, utilizando dos de estos componentes (Memo1 y Memo2), crear la siguiente interfaz gráfica:

En el control Memo1 (el de arriba) se escribirán los caracteres a enviar y en Memo2 se visualizarán los caracteres recibidos por el puerto serial.

Después de esto, insertar un objeto Thread, el nombre del objeto será: TSerial. Ahora

tenemos 2 módulos de código: Unit1.cpp y Unit2.cpp. El segundo corresponde al objeto Thread. Al inicio del módulo de Unit1.cpp agregar las siguientes sentencias:

#include <vcl.h> #pragma hdrstop #include "Unit1.h" #include "Unit2.h" //Cabecera de la Unidad 2 (Objeto Thread) //Variables globales

Page 30: UNIDAD IV Manejo de Puertos de E/S

- 225 -

HANDLE hComm = NULL; //Manejador de puerto serial COMMTIMEOUTS ctmoNew = {0}, ctmoOld; //Variables de tiempos de puerto serial TSerial *LeerPuerto; //Apuntador a objeto Thread de la clase TSerial //------------------------------------------------------------------ #pragma package(smart_init) #pragma resource "*.dfm" . . .

En el evento OnCreate del formulario principal, agregar el siguiente código: void __fastcall TForm1::FormCreate(TObject *Sender) { Memo1->Clear(); //Limpiar Memo1 DCB dcbCommPort; // abrir el puerto, reemplazar "com1" por "com2", "com3", etc. para abrir // otro puerto hComm = CreateFile("COM1", GENERIC_READ | GENERIC_WRITE,0,0,OPEN_EXISTING,0,0); if(hComm == INVALID_HANDLE_VALUE) //Si el puerto no puede abrirse { ShowMessage("No se pudo abrir el puerto"); //enviar mensaje de error y Application->Terminate(); //terminar el programa } // Establecer los tiempos límites de lectura y escritura del puerto serial GetCommTimeouts(hComm,&ctmoOld); ctmoNew.ReadTotalTimeoutConstant = 100; ctmoNew.ReadTotalTimeoutMultiplier = 0; ctmoNew.WriteTotalTimeoutMultiplier = 0; ctmoNew.WriteTotalTimeoutConstant = 0; SetCommTimeouts(hComm, &ctmoNew); // Establecer el protocolo 8N1 a 9600 Baudios, no paridad, 8 bits de datos y // 1 bit de stop. dcbCommPort.DCBlength = sizeof(DCB); GetCommState(hComm, &dcbCommPort); BuildCommDCB("9600,N,8,1", &dcbCommPort); SetCommState(hComm, &dcbCommPort); // Activar el Objeto Thread a partir de este momento se comienza a ejecutar el // código del método Execute() del objeto Thread al mismo tiempo que el // programa principal LeerPuerto = new TSerial(false); }

En el evento OnClose del formulario principal debemos cerrar el puerto serial y terminar el Thread. void __fastcall TForm1::FormClose(TObject *Sender, TCloseAction &Action) { // Terminar el Thread LeerPuerto->Terminate(); // Esperar a que termine de cerrar y luego cerrar el puerto Sleep(250); PurgeComm(hComm, PURGE_RXABORT);

Page 31: UNIDAD IV Manejo de Puertos de E/S

- 226 -

SetCommTimeouts(hComm, &ctmoOld); CloseHandle(hComm); //Cierra puerto }

Cada vez que se escriba un carácter en Memo1 este debe ser enviado a través del puerto serial. Por lo tanto en el evento OnKeyPress de Memo1 debe ir el siguiente código: void __fastcall TForm1::Memo1KeyPress(TObject *Sender, char &Key) { // Transmitir cualquier cosa escrita en Memo1 // Esto Prevee el envio de caracteres no imprimibles por el puerto if((Key < ' ' || Key > 'z')) Key = 0; else TransmitCommChar(hComm, Key); //Transmitir el caracter }

Lo que sigue corresponde a Unit2.cpp. En el inicio del módulo de código correspondiente al objeto Thread (TSerial) debemos agregar las siguientes sentencias: #include <vcl.h> #pragma hdrstop #include "Unit2.h" #include "Unit1.h" //incluir módulo de código del formulario principal #pragma package(smart_init) extern HANDLE hComm; //Varible manejadora del puerto, declarada externa char InBuff[100]; //cadena de caracteres que almacenará los

//caracteres recibidos. //-----------------------

Como se mencionó antes, el objeto Thread se encargará de verificar la llegada de datos por el puerto serial, por lo tanto, en el método Execute() de TSerial irá el siguiente código: void __fastcall TSerial::Execute() { //---- Place thread code here ---- DWORD dwBytesRead; // Variable tipo Doubleword FreeOnTerminate = true; //Esto hace que el objeto Thread sea destruido

//cuando el código termine. // Se intenta leer algo del puerto serial indefinidamente, y si hay algo, lo // visualiza. while(1) { ReadFile(hComm, InBuff, 50, &dwBytesRead, NULL); if(dwBytesRead) { InBuff[dwBytesRead] = 0; // acaba la cadena en nulo Synchronize(Muestralo); //Llama a la función que visualizará el dato en } //el Memo2, recuerde que esto no se puede hacer } //directamente. }

En este mismo módulo definimos la función Muestralo():

Page 32: UNIDAD IV Manejo de Puertos de E/S

- 227 -

void __fastcall TSerial::Muestralo() { //no se toma en cuenta los caracteres no imprimibles que se puedan recibir Form1->Memo2->SetSelTextBuf(InBuff); //Visualiza el carácter en Memo2 }

El prototipo de esta función (en realidad estamos definiendo un método) irá en el archivo de encabezado de Unit2.cpp que es Unit2.h. Para visualizarlo dar clic derecho sobre el módulo Unit2.cpp y elegir la opción: Open source /Header File. Ahí agregamos el prototipo: . . . private: protected: void __fastcall Execute(); void __fastcall Muestralo(void); //Prototipo del método public: . . .

Mucho de los conceptos manejados en este ejemplo son explicados en la unidad siguiente, que trata de la programación orientada a objetos. Se puede probar este ejemplo conectando dos computadoras con el puerto serial en modo NULL; obviamente ambas computadoras deben estar corriendo el mismo programa del ejemplo. Si no se cuenta con 2 computadoras entonces utilizar el modo NULL con conexión de retroalimentación, de esta forma todo lo que se envíe por Memo1 debe recibido por la misma computadora y visualizada en Memo2. Este programa funciona en Windows XP.

Page 33: UNIDAD IV Manejo de Puertos de E/S

- 228 -

4.4. Ejercicios propuestos 1. Realizar un programa con la siguiente interfaz gráfica:

a) Presionando IZQUIERDA el valor se desplaza un bit a la izquierda cada segundo (utilizar un control Timer).

b) Si se vuelve a presionar IZQUIERDA el desplazamiento se detiene. c) De forma análoga funcionará el botón DERECHA. d) Si se presiona DERECHA mientras se rota a la izquierda, comenzar a rotar a la derecha. e) Por cada desplazamiento, enviar el dato visualizado por el puerto paralelo. f) Al comenzar el programa, se debe pedir al usuario el dato inicial que se presentará, ya sea

por medio de una Inputbox() o por medio de otro formulario, dicho dato debe ser numérico entre 0 y 255 de lo contrario se volverá a pedir el dato inicial.

2. Realizar un programa con la siguiente interfaz gráfica:

a) Al elegir la opción Izquierda del menú principal, el dato comenzará a rotar un bit a la izquierda por cada intervalo de tiempo elegido de los botones de radio.

b) Si se elige Derecha se rota un bit a la derecha, cada tiempo elegido. c) Doble hace la rotación del bit que está en 1 de izquierda a derecha. d) Secuencia rota el bit que está en 1 a la izquierda pasando por los 8 bits de datos 3 veces,

luego lo rota a la derecha pasando por los 8 bits de datos 3 veces y finalmente hace la rotación doble 3 veces.

e) DETENER detiene la rotación. f) Todo dato visualizado en la etiqueta debe ser enviado por el puerto paralelo. g) El tiempo de rotación de un bit en todas las ocasiones será el elegido de los botones de

radio.

Page 34: UNIDAD IV Manejo de Puertos de E/S

- 229 -

3. Realizar un programa con la siguiente interfaz gráfica:

a) Se tendrán los mismos tipos de rotaciones que en el ejercicio anterior solo que esta vez dependerán del valor introducido por el registro de entrada (379h) del puerto paralelo, de acuerdo a la siguiente tabla:

Entrada (puerto 379h)

S5 S4 S3 Valor Decimal

Salida (Puerto 378h)

0 0 1 1 Rotar un bit a la izquierda cada 250 mS 0 1 0 2 Rotar un bit a la derecha cada 500 mS 0 1 1 3 Hacer rotación doble (igual que el ejercicio anterior) 1 0 0 4 Hacer secuencia de rotaciones (igual que el ejercicio anterior)

b) Los bits deben ser visualizados en la etiqueta de la ventana y deben ser enviados por el

puerto de salida.