Sie sind auf Seite 1von 7

El puerto serie en Windows

Por: Salvador Pozo Coronado [Principal] [ndice de artculos]

Generalidades:
En Windows no es posible acceder a los dispositvos fsicos directamente, a travs de las direcciones de sus puertos. Por el contrario, todos los accesos (salvo que estemos programando un driver) deben hacerse a travs de funciones del API. Los puertos serie, por tratarse de dispositivos incluidos como parte de los PC desde sus comienzos, estn muy bien integrados en el API de Windows, por lo tanto, tenemos un amplio repertorio de funciones para manejarlos. El presente artculo no pretende estudiar en profundidad todas las funciones y opciones del API de Windows con referencia al puerto serie, slo intentar que aquellos que necesiten usar estos puertos en sus programas tengan unas nociones ms precisas sobre como lograrlo. Windows trata los puertos serie (y tambin el paralelo), como si se tratase de un fichero de entrada y salida ms. La nica peculiaridad es que su comportamiento es asncrono, y esta caracterstica influye mucho en el modo en que tenemos que programar nuestras aplicaciones cuando usen uno de estos puertos. El comportamiento asncrono se debe a varias caractersticas de este tipo de comunicacin, para empezar, los datos se envan secuencialmente, a una velocidad relativamente baja. El sistema tiene que estar preparado para recibir los datos en el momento en que estn disponibles, ya que si no acta as, se perdern irremisiblemente. En los ficheros normales, somos nosotros los que decidimos cundo y cmo leemos o escribimos los datos. Y como la velocidad de respuesta de estos ficheros es bastante buena, generalmenten no notamos que el programa se para mientras se procesan estas rdenes de lectura y escritura. Esto no pasa cuando se lee o se escribe de un puerto serie. Los datos que se reciben por uno de estos canales hay que leerlos cuando llegan, casi nunca sabremos cundo el dispositivo que tenemos conectado al otro extremo del cable

va a decidir enviarnos datos. En cuanto a la escritura, pasa algo parecido, no podemos preveer con precisin si el dispositivo al que enviamos los datos los va a procesar con la velocidad a la que se los enviamos, o si est o no preparado para recibirlos. Aunque el sistema operativo dispone de un buffer para almacenar los datos que se reciben, ese buffer es finito, y si nuestro programa no retira esos datos con cierta frecuencia, los perder. En cuanto a las transmisiones hacia afuera, pasa algo parecido. El dispositivo receptor puede tener una capacidad limitada para procesar los datos, de modo que el sistema debe estar preparado para esperar mientras el receptor procesa los datos y nos avisa de que est preparado para recibir ms. Estas caractersticas hacen que nuestro programa se complique un poco ms de lo que en principio pudiramos esperar. Pero como veremos, tampoco es para tanto.

Abrir un canal asociado a un puerto serie


Lo primero que necesitamos es un fichero asociado a nuestro puerto serie. Para eso usaremos la funcin del API CreateFile. CreateFile es una funcin con muchas opciones y que sirve para muchas cosas, pero ahora nos centraremos en el puerto serie. Veremos ahora los valores posibles de los siete parmetros que necesita esta funcin: 1. LPCTSTR lpFileName: nombre del fichero. Se trata de una cadena que contiene el nombre del puerto que queremos abrir. Los valores posibles son "COM1", "COM2", "COM3" y "COM4". 2. DWORD dwDesiredAccess: tipo de acceso. En general querremos leer y escribir en el puerto, por lo tanto especificaremos los valores GENERIC_READ | GENERIC_WRITE. 3. DWORD dwShareMode: modo en que se comparte el fichero. En nuestro caso, un puerto serie no puede ser compartido, de modo que usaremos 0 para este parmetro. 4. LPSECURITY_ATTRIBUTES lpSecurityAttributes: atributos de seguridad, especifican el modo en que el fichero se puede heredar por procesos hijos. En nuestro caso no queremos que eso ocurra, de modo que usamos el valor NULL.

5. DWORD dwCreationDistribution: modo de creacin. Los puertos serie son dispositivos fsicos, por lo tanto, existen. El modo de creacin ser OPEN_EXISTING. 6. DWORD dwFlagsAndAttributes: atributos del fichero. Por ahora no nos interesa ninguno de estos atributos, usaremos el valor 0. 7. HANDLE hTemplateFile: plantilla de fichero. Se puede especificar un fichero existente del cual se copirarn los atributos. En nuestro caso no usaremos esta opcin, y usaremos el valor NULL para este parmetro.
idComDev = CreateFile("COM1", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);

Modificar los parmetros de transmisin


En general, necesitaremos establecer los parmetros de la lnea serie que vamos a usar. Tendremos que fijar la velocidad de transmisin, el nmero de bits de datos, la paridad, y los bits de stop. Y a veces algunos parmetros ms. Para hacer esto, primero recuperaremos los parmetros del canal que acabamos de abrir, los modificaremos y actualizaremos la coniguracin del canal. Para recuperar los parmetros usaremos la funcin GetCommState. Esta funcin nos devuelve una estructura DCB, que contiene la configuracin actual del puerto serie.
fSuccess = GetCommState(idComDev, &dcb);

De todos los valores que incluye la estructura DCB, de momento slo nos preocuparemos por unos pocos: 1. DWORD BaudRate: velocidad en baudios. Este parmetro puede tener los siguientes valores: CBR_110, CBR_300, CBR_600, CBR_1200, CBR_2400, CBR_4800, CBR_9600, CBR_14400, CBR_19200, CBR_38400, CBR_56000, CBR_57600, CBR_115200, CBR_128000 y CBR_256000. 2. BYTE ByteSize: tamao de los datos en bits. Tradicionalmente 7 u 8. 3. BYTE Parity: valor de paridad. Se admiten los valores EVENPARITY, para paridad par; MARKPARITY; NOPARITY, para no paridad y ODDPARITY, para paridad impar. 4. BYTE StopBits: Bits de stop. Admite los valores ONESTOPBIT, 1 bit de stop; ONE5STOPBITS, 1.5 bits de stop y TWOSTOPBITS, 2 bits de stop.

Una vez que hemos actualizado la estructura de datos podemos configurar el puerto envindosela mediante la funcin: GetCommState.
SetCommState(idComDev, &dcb);

Monitorizacin de eventos
Podemos especificar qu eventos de los que se pueden producir en el puerto serie nos interesa procesar o monitorizar. De este modo, el sistema operativo nos avisar cada vez que se produzca uno de esos eventos. El modo de especificar los eventos de inters es mediante una mscara, y para hacerlo usaremos la funcin SetCommMask. Se pueden especificar varios eventos, pero generalmente nos interesararn slo dos de ellos, al menos en la aplicaciones normales: 1. EV_RXCHAR: se ha recibido un carcter y se a colocado en el buffer de entrada. 2. EV_TXEMPTY: de ha enviado el ltimo carcter del buffer de salida. Para nuestro ejemplo slo monitorizaremos el evento EV_RXCHAR.
SetCommMask(idComDev, EV_RXCHAR);

Ahora ya tenemos el puerto serie en condiciones de transmitir o recibir informacin.

Escribir en el puerto serie


Para enviar caracteres al puerto serie se usa la funcin WriteFile. Sin embargo, como ya hemos explicado, no basta con enviar los caracteres al puerto serie, el destinatario puede interrumpir la transmisin si no es capaz de procesar los datos a la misma velocidad que se los enviamos, de modo que los datos que intentamos enviar pueden no ser enviados por completo. Para estar seguros de que enviamos toda la informacin que queremos, usaremos uno de los parmetros que devuelve la funcin, y que nos dice cuntos caracteres se han enviado. Colocando la funcin WriteFile en un bucle, podemos enviar los caracteres que an estn pendientes hasta que todos hayan sido enviados.

WriteFile puede retornar con valor false si se ha producido un error. Sin embargo, uno de los errores no es tal, el error ERROR_IO_PENDING en realidad slo nos informa de que no se ha completado la operacin de escritura. En caso de recibir ese error, debemos continuar enviado datos al puerto serie.
void EscribirSerie(char *buf) { char Buffer[1024]; DWORD n, l, p, err; strcpy(Buffer, buf); l = strlen(Buffer); // Longitud de los datos p = 0; // Posicin actual de los datos a enviar while(l) { if(!WriteFile(idComDev, &Buffer[p], 1, &n, NULL)) { err = GetLastError(); if(err != ERROR_IO_PENDING) { Comunicacion = false; return; } } l -= n; p += n; } }

Esperar a que haya algo para leer


Por la misma naturaleza de las transmisiones, y debido a que nuestro ordenador normalmente ser muy rpido en comparacin con las velocidades de transmisin, la mayor parte del tiempo no estaremos recibiendo nada por el puerto serie. Ya hemos definido la mscara de eventos que queremos recibir del puerto serie. Para saber si se ha producido uno de esos eventos tenemos que llamar a la funcin WaitCommEvent. Pero, tal como hemos configurado el puerto serie, esta funcin no regresa mientras no se produzca uno de los eventos que hemos definido. Esto nos impide que nuestro programa realice otras tareas, como por ejemplo, enviar informacin al puerto. Necesitamos un mecanismo que avise a nuestro programa cuando existan datos para leer, pero no debemos bloquear el sistema preguntando constantemente si hay algo preparado. Ni siquiera debemos bloquear nuestro programa, a menudo hay otras cosas que hacer adems de esperar la llegada de nueva informacin.

La mejor forma es introducir la funcin WaitCommEvent en un hilo distinto del de nuestro programa principal. Podemos hacer que ese hilo espere a que se produzca un evento, y cuando eso ocurra, que lo procese. En nuestro caso, slo se puede producir un evento, de modo que una funcin posible para procesarlo es:
// Hilo de escucha del puerto serie: DWORD Hilo(LPDWORD lpdwParam) { DWORD dwEvtMask; do { if(WaitCommEvent(idComDev, &dwEvtMask, NULL)) if(dwEvtMask & EV_RXCHAR) LeeSerie(); } while(true); return 0; }

Esta funcin coloca la funcin WaitCommEvent en un bucle infinito, y cuando detecta el evento EV_RXCHAR, hace una llamada a la funcin LeeSerie(), que procesa los datos recibidos. Por supuesto, en algn lugar del programa, debemos lanzar el hilo, esto se hace con la funcin CreateThread.
hHilo = CreateThread(NULL, 0, Hilo, &param, 0, &id);

Leer desde el puerto serie


Lo primero que necesitamos saber es cuantos caracteres hay en el buffer de entrada. Para eso podemos usar la funcin ClearCommError. Esta funcin nos actualiza una estructura COMSTAT, uno de cuyos miembros es cbInQue, que nos dice cuantos caracteres hay en el buffer de entrada. Con ese dato podemos llamar a la funcin ReadFile, y leer todos los caracteres que haya en el buffer.
// Leer datos del puerto serie: void LeeSerie() { int i, j, k; DWORD x; COMSTAT cs; // Actualizar COMSTAT, sirve para // averiguar el nmero de bytes en el buffer de entrada: ClearCommError(idComDev, &x, &cs);

// Leer cs.cbInQue caracteres: ReadFile(idComDev, cad, cs.cbInQue, &x, NULL); // Actualizar el fin de cadena: cad[x]=0; // Mostrar caracteres ledos: cout << cad; return; }

Programa de ejemplo
Con esto ya es suficiente para crear un programa de comunicaciones serie sencillo, pero que ilustra la mayor parte de los casos que necesiten comunicacin serie. El siguiente ejemplo es un monitor de comunicacin serie en modo texto. Pueden usarse dos programas en ordenadores diferentes, conectados con un cable serie, y es posible comunicarse entre ellos. Yo he probado este programa como terminar serie de un VAX, y salvo por las secuencias de escape propias de estos terminales, el programa funciona perfectamente.

Das könnte Ihnen auch gefallen