Beruflich Dokumente
Kultur Dokumente
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.
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);
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);
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; } }
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, ¶m, 0, &id);
// 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.