Вы находитесь на странице: 1из 34

EL API DEL PUERTO SERIE

Introduccin

Contenido
FUNCIONES EL API WIN32 PARA COMUNICACIONES SERIE
LNEAS DE CONTROL DE FLUJO
BUFFER DEL TRANSMISOR Y DEL RECEPTOR
LEER Y ESCRIBIR EN EL PUERTO
CLASE TWinSerCom PARA TRANSMITIR Y RECIBIR CON WIN32
UN CHAT BAJO WINDOWS
ENVIAR Y RECIBIR TRAMAS
CONTROL DEL PUERTO MEDIANTE EVENTOS
PROCESOS Y SUBPROCESOS
RECEPCIN EN SEGUNDO PLANO

10.1. FUNCIONES DEL API WIN32 PARA COMUNICACIONES SERIE

La utilizacin del las funciones del API WIN32 para trabajar con el puerto serie garantiza la
portabilidad del cdigo entre distintas versiones de WINDOWS (95/98/NT) y la estabilidad de
los programar que desarrollemos. La lista de funciones y estructuras que permiten programar el
puerto serie son ms de dos docenas, se irn explicando segun se necesiten. Trabajar con un
puerto serie se asemeja mucho al trabajo con ficheros mediante flujos, al trabajar con un puerto
serie se pueden distinguir 4 fases.

1.- Operacin de apertura: sirve para obtener un manejador o handle y comprobar si el sistema
operativo ha podido tomar el control del dispositivo para nuestra aplicacin.

2.- Configuracin: Sirve par establecer los parmetros de la comunicacin: velocidad, paridad
etc as como el tipo de acceso: mediante sondeo o mediante eventos.

3.- Acceso al puerto para leer o escribir en el: Hay que tener en cuenta que el acceso al puerto
siempre se realiza a travs de BUFFER uno para la transmisin (escritura) y otro para la
recepcin (lectura).

4.- Cierre del puerto para que otros programas puedan hace uso de l.

Las funciones que se irn presentando a continuacin han sido resumidad para dejar slo
aquellos parmetros que determinan el proceso de comunicacin.

1.- Apertura y cierre del puerto

Para abrir el puerto se utiliza la funcin CreateFile() , esta funcin es una funcin genrica que
permite acceder a recursos de naturaleza diversa: puertos de comunicaciones, ficheros,
directorios, discos, consolas etc.

CreateFile()
HANDLE CreateFile(
LPCTSTR lpFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDistribution,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile);

ARG. ENTRADA DESCRIPCIN


lpFileName Nombre del puerto COM1, COM2 etc
dwDesiredAcces Tipo de acceso GENERIC_READ, GENERIC_WRITE etc
dwShareMode Modo de compartir. Debe ser 0 al no poderse compartir el
puerto serie por dos aplicaciones de forma simultnea.
lpSecurityAttributes Descriptor de seguridad. Debe ser 0
dwCreationDistribution Especifica que accin se realizar si el fichero existe o si no
existe: CREATE_NEW, OPEN_EXISTING etc. Puesto que un
puerto siempre tiene que existir usaremos OPEN_EXISTING
dwFlagsAndAttributes Especifica el tipo de atributo del fichero/recurso, debe de ser
FILE_ATRIBUTE_NORMAL para los puertos.
hTemplateFile Debe ser NULL
TIPO SALIDA DESCRIPCIN
HANDLE Manejador o Handle del puerto. Un puntero necesario para
acceder al puerto, o NULL si la operacin de apertura fall: No
existe el puerto, ocupado etc.

Si se desea conocer los tipos de datos definidos en WINDOWS para saber exactamente a que
tipos bsicos equivalen bastar con revisar el fichero WINDEF.H que se encuentra en el
directorio INCLUDE de C++ BUILDER.

Por ejemplo el siguiente fragmento abre el puerto COM1 .

..
HADLE hComPor;
hComPor=CreateFile(COM1,
GENERIC_READ|GENERIC_WRITE,
0,0,OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,0 );
if (hComPor==INVALID_HANDLE_VALUE)
{
//Error de apertura del puerto
}

Una vez finalizado el trabajo con el puerto hemos de cerrar su manejador para que el sistema
operativo libere los recursos que estamos ocupando, esto se hace mediante la funcin
CloseHandle(), esta funcin devuelve TRUE si el cierre del dispostivo se hizo correctamente,
es importante no tratar de cerrar un manejador no vlido, es decir un manejador no inicializado
correctamente por CreateFile() .

CloseHandle()
BOOL CloseHandle(HANDLE hObject);
ARG. ENTRADA DESCRIPCIN
hObject Manejador o handle genrico.
TIPO SALIDA DESCRIPCIN
BOOL TRUE si la funcin se ejecut correctamente.

2.- Configuracin del puerto

Para leer y escribir la configuracin el puerto se utilizan las funciones GetCommState() y


SetCommState() ambas utilizan como argumento una estructura del tipo DCB. Esta estructura
encapsula en sus campos la configuracin de un puerto serie como se muestra en la tabla
siguiente, se encuentra disponible en el fichero WINBASE.H del directorio INCLUDE\WIN32 del
C++ BUILDER.

MIEMBROS DE LA ESTRUCTURA DCB


CAMPO TIPO DESCRIPCIN
DCBlength DWORD Tamao de la estructura DCB
BaudRate DWORD Velocidad en bps: CBR_100,CBR_300,CBR_600 ...CBR_560000..
fBinary:1 BIT Modo binario. Debe de ser TRUE
fParity: 1 BIT Si TRUE se chequearn errores de paridad
fOutxCtsFlow:1 BIT Si TRUE, se usa CTS para controlar el flujo entre el DTE y el DCE. Si CTS se
desactiva el DTE dejar de transmitir hasta que se active de nuevo.
fOutxDsrFlow:1 BIT Si TRUE, se usa DSRpara controlar el flujo entre el DTE y el DCE. Si DSR se
desactiva el DTE dejar de transmitir hasta que se active de nuevo.
fDtrControl:2 2 BITS Especifica como se manejara la lnea DTR de salida del DTE. Valores posibles:
DTR_CONTROL_ENABLE : Se activar DTR al abrir el puerto.
DTR_CONTROL_DISABLE: Se desactivar DTR al abrit el puerto
DTR_CONTROL_HANDSHAKE: Habilita el control de flujo hardware DTR-DSR
En los dos primeros casos se puede utilizar la funcin EscapeCommFunction()
para cambiar el estado de la lnea DTR
fDsrSensitivity:1 BIT Si TRUE se descartn los datos recibidos mientras la lnea DSR (entrada al DTE)
este inactiva.
fTXContinueOnXoff:1 BIT Configura el modo de operar si se llena el buffer de entrada y ya se ha transmitido
el caracter XOFF. Si FALSE la transmisin se parar despus de recibir XOFF, se
reanudar si se recibe un XON o bien se vaca el BUFFER del receptor por debajo
del lmite inferior XOnLimit.
fOutX: 1 BIT Si TRUE habilita el control de flujo XON/XOFF durante la transmisin .La
transmisin cesar al recibirse un XOFF, se reanudar al recibirse un XON
fInX: 1 BIT Si TRUE habilita el control de flujo XON/XOFF durante la recepcin .Se enviar el
caracter XOFF si el BUFFER del receptor alcance el lmite XoffLimit. Se enviar
el caracter XON si el BUFFER baja se vacia por debajo de XonLimit.
fErrorChar: 1 BIT Si TRUE se sustituirn todos los caracteres recibido con errores por el dato
ErrorChar definido en esta misma estructura.
fNull: 1 DWORD Si TRUE se descartar los BYTES recibidos que sean nulos.
fRtsControl:2 2 BITS Configura como se manejar el control de flujo mediante la lnea RTS de salida del
DTE. Los valores posibles son:
RTS_CONTROL_DISABLE: Se mantendr RTS inactiva
RTS_CONTROL_ENABLE: Se mantendr RTS activa
RTS_CONTROL_HANDSHAKE: Habilita el control de flujo hardware RTS-CTS
RTS_CONTROL_TOGGLE: Se activa mientras el BUFFER de transmisin tenga
datos para transmitir.
En los dos primeros casos se puede utilizar la funcin EscapeCommFunction()
para cambiar el estado de la lnea RTS
fAbortOnError:1 BIT Si TRUE se abortar cualquier operacin de lectura o escritura hasta que se llame
explicitamente a ClearCommError().
fDummy2:17 17 BITS No se usan
wReserved WORD No se usan
XonLim WORD Determina el lmite inferior del BUFFER del receptor que fuerza la transmisin de
XON
XoffLim WORD Determina el lmite superiro del BUFFER del receptor que fuerza la transmisin de
XOFF
ByteSize BYTE Determina el nmero de bits de datos: 4,5,6,7,8
Parity BYTE Paridad: NOPARITY, EVENPARITY(par),MARKPARITY o ODDPARITY (impar)
StopBits BYTE Bist de Stop: ONESTOPBIT, ONE5STOPBITS,TWOSTOPBITS
XonChar char Fija el caracter que se usar como XON
XoffChar char Fija el caracter que se usar como XOFF
ErrorChar char Fija el caracter que se usar para sustituir un datos recibido erroneamente.
EofChar char Fija el caracter que se usar para forzar el final de la transmisin
EvtChar char Fija el caracter que se usar para forzar el lanzamiento de un evento
wReserved1 WORD Reservado

La estructura anterior permite un control total sobre la configuracin del puerto: tipo de SDU,
tipo de control de flujo, parmetros del control de flujo etc. Para trabajar con la estructura DCB
disponemos de trs funciones GetCommState() para leer la configuracin actual del puerto,
SetCommState() para configurar el puerto y BuildCommDCB() para configurar el puerto
utilizando el estilo clsico del comando MODE del DOS.

GetCommState()
BOOL GetCommState(HANDLE hFile,LPDCB lpDCB);
ARG. ENTRADA DESCRIPCIN
hFile Manejador del puerto o Handle devuelto por CreateFile()
lpDCB Puntero a una estructura de tipo DCB donde se recibir la
configuracn actual del puerto.
TIPO SALIDA DESCRIPCIN
BOOL TRUE si la funcin se ejecut correctamente.
SetCommState()
BOOL SetCommState(HANDLE hFile,LPDCB lpDCB)
ARG. ENTRADA DESCRIPCIN
hFile Manejador del puerto o Handle devuelto por CreateFile()
lpDCB Puntero a una estructura de tipo DCB que se usar para
configurar el puerto.
TIPO SALIDA DESCRIPCIN
BOOL TRUE si la funcin se ejecut correctamente.

BuildCommDCB()
BOOL BuildCommDCB(LPCTSTR lpDef,LPDCB lpDCB)
ARG. ENTRADA DESCRIPCIN
lpDef Puntero a una cadena al estilo del comando MODE con la
configuracin a fijar, por ejemplo:
baud=9600 parity=N data=8 stop=1
lpDCB Puntero a una estructura de tipo DCB donde se recibir la
configuracin completa del puerto.
TIPO SALIDA DESCRIPCIN
BOOL TRUE si la funcin se ejecut correctamente.

Para probar la configuracin del puerto usaremos una aplicacin de cnsola que configure el
puerto a 9600-N-8-1. Mediante Proyect-New-Console App creamos un nuevo proyecto en un
nuevo directorio llamado PROYECT1.CPP. Para poder tener acceso a las funciones del API de
windows ser necesario incluir el fihero WINDOWS.H .

//-----------------------------------
#include <vcl\condefs.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <iostream.h>
#pragma hdrstop
//-----------------------------------
USERES("Project1.res");
//-----------------------------------
int main(int argc, char **argv)
{
BOOL lOk;
HANDLE hCom; // Manejador del puerto
DCB sComCfg; // Estructura DCB
char cTec;
while (true)
{
// Abrir el puerto
hCom=CreateFile("COM1",
GENERIC_READ|GENERIC_WRITE,
0,0,OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,0);
// Si error finalizar
if (hCom==INVALID_HANDLE_VALUE)
{
cout<<"ERROR al abrir el puerto";
cout<<GetLastError();
break;
}
// Leer la configuracin actual
lOk=GetCommState(hCom,&sComCfg);
if (!lOk)
{
cout<<"ERROR al leer la configuracin";
cout<<GetLastError();
break;
};
// Cambiar la configuracin a 9600-N-8-1
sComCfg.BaudRate=CBR_9600;
sComCfg.ByteSize=8;
sComCfg.Parity=NOPARITY;
sComCfg.StopBits=ONESTOPBIT;
// Escribir la nueva configuracin
lOk=SetCommState(hCom,&sComCfg);
if (!lOk)
{
cout<<"ERROR al escribir la configuracin";
cout<<GetLastError();
break;
};
cout<<"\n El puerto est abierto";
cout<<"\n y configurado a 9600-N-8-1";
cout<<"\n Pulse una tecla";
cin>>cTec;
// Salir del bucle principal
break;
}
if (hCom!=INVALID_HANDLE_VALUE)
CloseHandle(hCom);
return 0;
}

Este empieza abriendo el puerto, a continuacin se lee se lee la configuracin actual mediante
GetCommState(), se cambian los miembros de la estructura que se necesitan y se graba la
nueva configuracin en el puerto mediante SetCommState() . El bucle principal se utiliza para
cancelar la ejecucin del programa ante cualquier error. Finalmente se cierra el puerto slo si
se abri correctamente.

<FIGURA 1. SALIDA DE LA CONFIGURACIN>

La funcin GetLastError() sirve para obtener el cdigo del error que se ha producido, estos
cdigos de error se encuentran definidos en el fichero WINNT.H .

GetLastError()
DWORD GetLastError(VOID)
ARG. ENTRADA DESCRIPCIN
No hay
TIPO SALIDA DESCRIPCIN
DWORD Cdigo del ltimo error producido

Para utilizar la funcin BuilCommDCB() se deber de declarar una estructur DCB y pasarla
como parmetro por referencia a la funcin. La funcin rellenar los campos de la estructura y
la podremos utilizar entonces para configurar el puerto con SetCommState().

..
HANDLE hCom; // Manejador del puerto
DCB sComCfg; // Estructura DCB

// Abrir el puerto con CreateFile()


CreateFile(..
// Construir una estructura DCB
if (BuidCommDCB(baud=9600 parity=N data=8 stop=1, &sComCfg) )
{
// Configurar el puerto
SetComState(hCom,&sComCfg);
};
..

10.2. LINEAS DE CONTROL DE FLUJO

Como se ha visto en el punto anterior, determinados miembros de la estructura DCB


condicionarn el funcionamiento de las lneas de control del flujo DTR-DSR y RTS-CTS. Estas
lneas de salida del DTE (DTR,RTS) pueden ser manipuladas mendiante la funcin
EscapeCommFuntion() . Del mismo modo se puede leer el estado de las lneas de entrada al
DTE (DSR,CTS,RING,CD) mediante la funcin GetCommModemStatus().
EscapeCommFunction()
BOOL EscapeCommFunction(HANDLE hFile,DWORD dwFunc)
ARG. ENTRADA DESCRIPCIN
hFile Manejador del puerto o Handle devuelto por CreateFile()
dwFunc Una palabra para indicar la operacin a realizar:
CLRDTR Pone a cero la seal DTR (data-terminal-ready).
CLRRTS Pone a cero la seal RTS (request-to-send) signal.
SETDTR Pone a uno la seal DTR (data-terminal-ready)
SETRTS Pone a uno la seal RTS (request-to-send)
SETXOFF Simula la recepcin de un carcter XOFF.
SETXON Simula la recepcin de un carcter XON
SETBREAK Suspende la transmisin
CLRBREAK Reanuda la la transmisin
TIPO SALIDA DESCRIPCIN
BOOL TRUE si la funcin se ejecut correctamente.

Como ejemplo de uso, el siguiente cdigo activa y desactiva la lnea RTS cada segundo.

.. Abrir y configurar el puerto


lOn=FALSE;
while (!kbhit())
{
if (lOn)
EscapeCommFunction(hCom,CLRRTS);
if (!lOn)
EscapeCommFunction(hCom,SETRTS);
Sleep(1000);
lOn=!lOn;
}
.. Cerrar el puerto

Para suspender y reanudar la transmisin se puede utilizar la funcin anterior con los
parmetros SETBREAK y CLRBREAK o bien utilizar las funciones equivalentes
SetCommBreak() y ClearCommBreak(), ambas slo requieren el manejador del puerto y
retornan TRUE si la operacin se complet. Si se desean leer las lneas de estado que
proceden del DCE se puede usar la funcin GetCommModemStatus().

GetCommModemStatus()
BOOL GetCommModemStatus(HANDLE hFile,LPDWORD lpModemStat);
ARG. ENTRADA DESCRIPCIN
hFile Manejador del puerto o Handle devuelto por CreateFile()
lpModemStat Un puntero a una variable DWORD donde se escribir la
configuaracin del registro de estado del mode, segn los
siguientes valores.
MS_CTS_ON La seal CTS est activa
MS_DSR_ON La seal DSR est activa
MS_RING_ON La seal RING est activa
MS_RLSD_ON La seal CD est activa
TIPO SALIDA DESCRIPCIN
BOOL TRUE si la funcin se ejecut correctamente.

El siguiente cdigo detecta si el DTE tiene un DCE conectado y preparado para aceptar datos.

..
DWORD dwEst;
.. Abrir y configurar el puerto
if (GetCommModemStatus(hCom,&dwErr) )
{
if ( (dwErr&MS_CTS_ON) && (dwErr&MS_DSR_ON) )
cout<<Hay un DCE preparado;
}
.. Cerrar el puerto

Se puede leer ms informacin sobre el estado del puerto serie y comprobar si se ha producido
un error y el tipo de error es ClearCommError() . Esta funcin adems de leer el tipo de error
que se ha producido sirve para resetear el flag de error del puerto, en determinados modos de
funcionamiento (fAbortOnError de la estructura DCB), el puerto deja de trabajar hasta que no
se llama a estra funcin despus de haberse producido un error.
ClearCommError()
BOOL ClearCommError(HANDLE hFile,
LPDWORD lpErrors,
LPCOMSTAT lpStat );
ARG. ENTRADA DESCRIPCIN
hFile Manejador del puerto o Handle devuelto por CreateFile()
lpErrors Puntero a una variable DWORD donde se recibir el cdigo del
error.
CE_BREAK Detectado corte en la lnea
CE_FRAME Detectado error de trama
CE_IOE Detectado error de E/S
CE_MODE Manejador hFile especificado no vlido.
CE_OVERRUN Se han perdido bytes al transmitir.
CE_RXOVER Overflow en el buffer de entrada.
CE_RXPARITY Detectado error de paridad.
CE_TXFULL Buffer del transmisor est lleno.
lpStat Puntero a una estructura de tipo COMSTAT donde se podr
leer el estado del puerto.
TIPO SALIDA DESCRIPCIN
BOOL TRUE si la funcin se ejecut correctamente.

El segundo argumento de la funcin es un puntero a una variable DWORD donde la funcin


depositar el tipo de error que se haya producido. El tercer argumento es un puntero a una
estructura de tipo COMSTAT donde la funcin depositar informacin detallada del estado del
puerto serie, los miembros de esta estructura son los siguientes.

MIEMBROS DE LA ESTRUCTURA COMSTAT


CAMPO TIPO DESCRIPCIN
fCtsHold : 1 BIT Si TRUE el puerto est esperando que el DCE active CTS
fDsrHold : 1 BIT Si TRUE el puerto est esperando que el DCE active DSR
fRlsdHold : 1 BIT Si TRUE el puerto est esperando que el DCE active CD
fXoffHold : 1; BIT Si TRUE el puerto ha recibido XOFF
fXoffSent : 1; BIT Si TRUE el puerto ha enviado XOFF
fEof : 1; BIT Si TRUE el puerto ha recibdio un carcter EOF
fTxim : 1; BIT Si TRUE Hay un caracter esperando ser transmitido
DWORD fReserved : 25 BIT No se usan
25;
DWORD cbInQue; DWORD Nmero de caracteres en BUFFER del receptor
DWORD cbOutQue; DWORD Nmero de caracteres en el BUFFER del transmisor

..
COMSTAT sComSta;
DWORD dwErr;
..
// Abrir y configuar el puerto
if( ClearCommError(hCom,&dwErr,&sComSta))
{
cout<<"Bytes en BUFFER RX:"<<sComSta.cbInQue;
if ( (dwErr&CE_BREAK)==CE_BREAK)
cout<<"\n Detectado corte en de lnea";
}
// Cerrar el puerto

10.3. BUFFERS DEL TRANSMISOR Y DEL RECEPTOR

Todas las comunicaciones mediante el API de WINDOWS se realizan a travs del BUFFERS
tanto para la transmisin como para la recepcin. Una vez que el puerto ha sido abierto y
configurado cualquier carcter que llegue al puerto ser almacenado en el buffer del receptor
de forma automtica por el sistema operativo, no importa lo que est haciendo nuestro
programa o cualquier otro que se encuentre en ejecucin. Del mismo modo, cuando
escribamos en el puerto se har a travs del BUFFER del transmisor, el programa escribir en
l y el sistema operativo ir enviando los datos de forma totalmente automtica siempre que el
control de flujo implementado lo permita (Si se habilita control de flujo RTS-CTS, ser necesario
que DCE mantenga activa su lnea CTS para que la transmisin se produzca).

<FIGURA 2. BUFFER DEL TRANSMISOR Y RECEPTOR>


El tamao de los BUFFERS de entrada y salida deber de ser acorde con el tamao de la
trama que se est manejando, si se utiliza una trama de 512 Bytes el BUFFER deber tener al
menos ese tamao.

Cuando se abre el puerto se crean los buffers de un tamao predeterminado, se puede conocer
el tamao actual de los BUFFERS mediante la funcin GetCommPropeties(), la cual
suministra una estructura COMMPROP con las propiedades del puerto dos de los campos de
esta estructura son dwCurrentTxQueue y dwCurrentRxQueue que informan del tamao total
del BUFFER del transmisor y del receptor respectivamente, esta funcin requiere que el puerto
est abierto y configurado.
GetCommProperties()
BOOL GetCommProperties(HANDLE hFile,LPCOMMPROP lpCommProp)
ARG. ENTRADA DESCRIPCIN
hFile Manejador del puerto o Handle devuelto por CreateFile()
lpCommProp Puntero a un estructura COMMPROP donde la funcin
depositar informacin sobre las caractersticas del puerto.
TIPO SALIDA DESCRIPCIN
BOOL TRUE si la funcin se ejecut correctamente.

Podemos monitorizar los BYTES pendientes de ser enviados o los que esten pendientes de
ser leidos mediante los campos cbInQue y cbOutQue de la estructura COMSTAT vista
anteriormente. Podemos adems cambiar el tamao de estos BUFFERS mediante la funcin
SetupComm().
SetupComm()
BOOL SetupComm(HANDLE hFile,DWORD dwInQueue,DWORD dwOutQueue)
ARG. ENTRADA DESCRIPCIN
hFile Manejador del puerto o Handle devuelto por CreateFile()
dwInQueue Tamao que se desea para el BUFFER del receptor
dwOutQueue Tamao que se desea para el BUFFER del transmisor
TIPO SALIDA DESCRIPCIN
BOOL TRUE si la funcin se ejecut correctamente.

Esta funcin se llamar normalmente despus de la apertura del puerto antes de cualquier
operacin de lectura o escritura. El siguiente ejemplo es una aplicacin de cnsola que muestra
como usar estas funciones.

//-----------------------------------
#include <vcl\condefs.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <iostream.h>
#include <conio.h>
#pragma hdrstop
//-------------------------------------
USERES("Project1.res");
//-------------------------------------
int main(int argc, char **argv)
{
HANDLE hCom; // Manejador del puerto
DCB sComCfg;// Estructura configuracin
COMMPROP sComPro;// Estructura propiedades
COMSTAT sComSta;// Estado del puerto
DWORD dwErr; // Estado del error

// Bucle para controlar errores


while (true)
{
// Abrir el puerto
hCom=CreateFile("COM1",
GENERIC_READ|GENERIC_WRITE,
0,0,OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,0);
// Si error finalizar
if (hCom==INVALID_HANDLE_VALUE) break;
// Leer configuracin actual
if (!GetCommState(hCom,&sComCfg)) break;
// Cambiar configuracin
sComCfg.BaudRate=CBR_9600;
sComCfg.ByteSize=8;
sComCfg.Parity=NOPARITY;
sComCfg.StopBits=ONESTOPBIT;
// Escribir nueva configuracin
if (!SetCommState(hCom,&sComCfg)) break;
// Fijar tamao de los BUFFERS del TX y RX
if (!SetupComm(hCom,2048,1024)) break;
// Leer las propiedades del puerto
if (!GetCommProperties(hCom,&sComPro)) break;
// Leer estado del puerto
if (!ClearCommError(hCom,&dwErr,&sComSta)) break;
// Mostrar tamao Buffer
cout<<"\n Tamao BUFFER TX:"<<
sComPro.dwCurrentTxQueue;
cout<<"\n Tamao BUFFER RX:"<<
sComPro.dwCurrentRxQueue;
// Mostrar tamao ocupado
cout<<"\n Ocupado BUFFER TX:"<<
sComSta.cbInQue;
cout<<"\n Ocupado BUFFER RX:"<<
sComSta.cbOutQue;
getch();
// Salir del bucle principal
break;
}
// Cerrar el puerto
if (hCom!=INVALID_HANDLE_VALUE)
CloseHandle(hCom);
return 0;
}

En el ejemplo se configura el puerto para un BUFFER del receptro a 2048 bytes y el BUFFER
del transmisor a 1024 BYTES. Este modo de operacin puede ser un problema cuando
queremos enviar un carcter de control de forma inmediata, en este caso el dato sera aadido
al final del BUFFER y se transmitira despus de los caracteres que estuvieran pendienetes.
Para el envio inmedianto de un carcter se dispone de la funcin TransmitCommChar().
TransmitCommChar()
BOOL TransmitCommChar(HANDLE hFile,char cChar);
ARG. ENTRADA DESCRIPCIN
hFile Manejador del puerto o Handle devuelto por CreateFile()
cChar Carcter a envias
TIPO SALIDA DESCRIPCIN
BOOL TRUE si la funcin se ejecut correctamente.

Si en algn momento se desaear borrar los bytes que quedan remanentes en alguno de los dos
BUFFERS se pude utiliza la funcin PurgeComm() , esta operacin hace que se pierda el
contendido del BUFFER del transmisor sin ser transmitido realmente o que se pierda el
contenido del BUFFER del receptor sin que sea guardadp por el programa.
PurgeCommr()
BOOL PurgeComm(HANDLE hFile,DWORD dwFlags);
ARG. ENTRADA DESCRIPCIN
hFile Manejador del puerto o Handle devuelto por CreateFile()
dwFlags Es uno o combinacin de los siguiente valores:
PURGE_TXABORT o PURGE_TXCLEAR para limpia el
BUFFER del transmisor
PURGE_RXABORT o PURGE_RXCLEAR para limpiar el
BUFFER del receptor
TIPO SALIDA DESCRIPCIN
BOOL TRUE si la funcin se ejecut correctamente.

Si queremos vaciar el BUFFER del transmisor, pero garantizando que se transmite todo su
contenido deberemos de usar la funcin FlushFileBuffers() .

10.4. LEER Y ESCRIBIR EN PUERTO


Para leer y escribir en el puerto se utilizan las funciones genricas ReadFile() y WriteFile(),
ambas son funciones genricas que permiten leer o escribir ficheros, puertos etc. En la tabla
siguiente se muestra una descripcin de cada una de ellas
ReadFile()
BOOL ReadFile(HANDLE hFile,
LPVOID lpBuffer,
DWORD nNumberOfBytesToRead,
LPDWORD lpNumberOfBytesRead,
LPOVERLAPPED lpOverlapped);
ARG. ENTRADA DESCRIPCIN
hFile Manejador del puerto o Handle devuelto por CreateFile()
lpBuffer Puntero al buffer donde la funcin depositar los datos leidos.
nNumberOfByteToRead Nmero de bytes que se desean leer
lpNumberOfByteRead Puntero a una DWORD donde la funcin indicar los datos
que realmente se han lido.
lpOverlapped Puntero a una estructura OVERLAPPED para acceso a
especial a puerto.
TIPO SALIDA DESCRIPCIN
BOOL TRUE si la funcin se ejecut correctamente.

La funcin utiliza como parmetros de entrada el manejador de dispositivo(hFile) y el nmero


de bytes que se desean leer(nNumberOfByteRead) . La funcin retorna a travs de los
argumentos pasados por referencia el nmero de bytes realmente leidos del dispostivo en
lpNumberOfByteRead y los bytes leidos a travs del BUFFER que empieza en lpBuffer. El
argumento lpOverlapped es para permitir un modo de acceso sobrecargado que no se
utilizar.

WriteFile()
BOOL WriteFile(HANDLE hFile,
LPCVOID lpBuffer,
DWORD nNumberOfBytesToWrite,
LPDWORD lpNumberOfBytesWritten,
LPOVERLAPPED lpOverlapped);
ARG. ENTRADA DESCRIPCIN
hFile Manejador del puerto o Handle devuelto por CreateFile()
lpBuffer Puntero al buffer donde la funcin leer los datos que
escribir en el puerto.
nNumberOfByteToWrite Nmero de bytes que se escribirn en el puerto
lpNumberOfByteWritten Puntero a una DWORD donde la funcin indicar los datos
que realmente se han escrito.
lpOverlapped Puntero a una estructura OVERLAPPED para acceso a
especial a puerto.
TIPO SALIDA DESCRIPCIN
BOOL TRUE si la funcin se ejecut correctamente.

La funcin WriteFile() utiliza como parmetros de entrada el manejador de dispositivo(hFile) y


el nmero de bytes que se desean escribir(nNumberOfByteToWrite), y un BUFFER donde la
funcin leer los bytes que se escribirn en el dispostivo lpBuffer. La funcin retorna a travs
del argumentos pasados por referencia el nmero de bytes realmente escritos en el dispostivo
en lpNumberOfByteWritten. Ambas devuelven TRUE si la operacin de lectura o escritura se
efectu correctamente.

Los dispositivos sobre los que se utilizan estas funciones soportan el control de TIMEOUT o
tiempo mximo de lectura y escritura. Por ejemplo, un dispositivo puede ser configurado para
que la operacin de lectura dure como mximo 100 ms. esto implica que si la operacin se
prolonga ms del tiempo permitido la funcin finalizar normalmente, es decir retornar
TRUE.Para determinar si una opeacin de lectura/escritur finaliz por un timeout habra que
comparn los bytes que se queras leer/escribir con los bytes realmente leidos/escritos.

La funciones GetCommTimmeout() y SetCommTimeouts() permiten configurar como se


realizarn las operaciones de lectura y escritura. Ambas trabajan sobre una estructura
COMMTIMEOUTS que define el tiempo mximo (TIMEOUT) que durar una operacin de
lectura o escritura.
MIEMBROS DE LA ESTRUCTURA COMMTIMEOUTS
CAMPO TIPO DESCRIPCIN
ReadIntervalTimeout DWORD Tiempo mximo de espera (ms) entre dos bytes recibidos.
ReadTotalTimeoutMultiplier DWORD Multiplicador (ms) para calcular el TIMEOUT total en
operaciones de lectura.
ReadTotalTimeoutConstant DWORD Constate (ms) para calcular el TIMEOUT total en
operaciones de lectura.
WriteTotalTimeoutMultiplier DWORD Multiplicador (ms) para calcular el TIMEOUT total en
operaciones de escritura.
WriteTotalTimeoutConstant DWORD Constate (ms) para calcular el TIMEOUT total en
operaciones de escritura

Si el el primer miembro usamos MAXDWORD y en el segundo 0 obtendremos que una


operacin de lectura finalizar inmediatamente incluso si no se ha leido nada del buffer. Si en el
segundo y tercer miembro se usa 0 no se utilizarn el control de TIMEOUT en operaciones de
lectura. Si en todos los miembros ponemos 0 las operaciones de lectura no finalizan hasta que
no se lean los bytes que se piden. Para leer y escribir esta configuracin se disponde de las
funciones GetCommTimeout() y SetCommTimeout() respectivamente.

GetCommTimeoust()
BOOL GetCommTimeouts(HANDLE hFile,LPCOMMTIMEOUTS lpCommTimeouts)
ARG. ENTRADA DESCRIPCIN
hFile Manejador del puerto o Handle devuelto por CreateFile()
lpCommTimeouts Puntero a una estructura de tipo COMMTIMEOUTS que se
usar para leer la configurar del puerto.
TIPO SALIDA DESCRIPCIN
BOOL TRUE si la funcin se ejecut correctamente.

SetCommTimeoust()
BOOL SetCommTimeouts(HANDLE hFile,LPCOMMTIMEOUTS lpCommTimeouts)
ARG. ENTRADA DESCRIPCIN
hFile Manejador del puerto o Handle devuelto por CreateFile()
lpCommTimeouts Puntero a una estructura de tipo COMMTIMEOUTS que se
usar para escribir la configurar del puerto.
TIPO SALIDA DESCRIPCIN
BOOL TRUE si la funcin se ejecut correctamente.

El siguiente programa se utiliza una aplicacin de cnsola para transmitir por el puerto una
cadena de caracteres.

//-------------------------------------
#include <vcl\condefs.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <iostream.h>
#include <conio.h>
#pragma hdrstop
//-------------------------------------
USERES("Project1.res");
//-------------------------------------
int main(int argc, char **argv)
{
HANDLE hCom; // Manejador del puerto
DCB sComCfg;// Estructura configuracin
COMMPROP sComPro;// Estructura propiedades
COMSTAT sComSta;// Estado del puerto
DWORD dwErr; // Estado del error
COMMTIMEOUTS sTimOut;
BYTE acBuf[32]; // Un buffer
DWORD dwBytWri; //
DWORD dwLen;

// Bucle para controlar errores


while (true)
{
// Abrir el puerto
hCom=CreateFile("COM1",
GENERIC_READ|GENERIC_WRITE,
0,0,OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,0);
// Si error finalizar
if (hCom==INVALID_HANDLE_VALUE) break;
// Leer configuracin actual
if (!GetCommState(hCom,&sComCfg)) break;
// Cambiar configuracin
sComCfg.BaudRate=CBR_9600;
sComCfg.ByteSize=8;
sComCfg.Parity=NOPARITY;
sComCfg.StopBits=ONESTOPBIT;
sComCfg.fRtsControl=RTS_CONTROL_ENABLE;
sComCfg.fDtrControl=DTR_CONTROL_ENABLE;
// Escribir nueva configuracin
if (!SetCommState(hCom,&sComCfg)) break;
// Configurar TIMEOUTS
sTimOut.ReadIntervalTimeout=0;
sTimOut.ReadTotalTimeoutMultiplier=0;
sTimOut.ReadTotalTimeoutConstant=0;
sTimOut.WriteTotalTimeoutMultiplier=0;
sTimOut.WriteTotalTimeoutConstant=0;
sTimOut.WriteTotalTimeoutConstant=0;
if (!SetCommTimeouts(hCom,&sTimOut)) break;
// Contruimos la cadena a enviar
strcpy(acBuf,"Hola Mundo");
dwLen=strlen(acBuf);
while (!kbhit())
{
if ( WriteFile(hCom,acBuf,dwLen,&dwBytWri,0) )
cout<<"\nTx..:"<<acBuf ;
}
// Salir del bucle principal
break;
}
// Cerrar el puerto
if (hCom!=INVALID_HANDLE_VALUE)
CloseHandle(hCom);
return 0;
}

En este programa se va a transmitir la cadena de forma constante mientras no se pulse una


tecla. Para comprobarlo basta conectar dos ordenadores mediante un cable NULL-MODEM, en
uno de ellos dejamos funcionando el programa, en el otro abrimos el programa HiperTerminal
en Inicio-Programas-Accesorios-Hyperterminal , lo configuramos como Directo a Com1 :9600-
N-8-1 y conectamos. Si disponemos de dos puertos serie libres podemos podemos ahorrarnos
el segundo ordenador si conectamos ambos puertos con el cable NULL-MODEM, en este caso
se obtendr un resultado como el de la figura.

<FIGURA 3. TRANSMITIR >

Para leer del puerto podemos utilizar un cable NULL-MODEM con FEED-BACK , en este caso
lo que escribamos en el puerto ser leido a travs del mismo puerto. En base al programa
anterior modificamos el bucle de transmisin y escribimos lo siguiente.

DWORD dwBytRea; // Bytes leidos


... Abrir y configurar el puerto
// Contruimos la cadena a enviar
strcpy(acBuf,"Hola Mundo");
dwLen=strlen(acBuf);
// Transmitimos la cadena
if (!WriteFile(hCom,acBuf,
dwLen,&dwBytWri,0)) break;
cout<<"\nTx..:"<<acBuf ;
strcpy(acBuf,"");
if (!ReadFile(hCom,acBuf,
dwLen,&dwBytRea,0)) break;
acBuf[dwBytRea]='\0';
cout<<"\nRx...:"<<acBuf;
cout<<"\n Pulse una tecla";
getch();
..

En este ejemplo se transmite la cadena, despus se borra y se lee del puerto un nmero de
byte igual a la cadena que se envi. Finalmente, se le aade el carcter de fin de cadena \0
para convertir el buffer leido del puerto acBuf en una cadena.

<FIGURA 4. RECIBIR>

Tal y como se ha configurado el TIMEOUT, si se ejecuta ReadFile() y no hay datos en el


BUFFER la funcin esperar de forma indefinida la llegada de nuevos datos. Si lo que
queremos es que la funcin finalice inmediatamente debemos configuar el TIMEOUT como se
muestra a continuacin.

sTimOut.ReadIntervalTimeout=MAXDWORD;
sTimOut.ReadTotalTimeoutMultiplier=0;
sTimOut.ReadTotalTimeoutConstant=0;
sTimOut.WriteTotalTimeoutMultiplier=0;
sTimOut.WriteTotalTimeoutConstant=0;
sTimOut.WriteTotalTimeoutConstant=0;

En este caso, habr que revisar el proceso de lectura, ya que la funcin finalizar
inmediatamente despus de llamarla y puede ocurrir que no haya leido ningn dato. Otra
solucin par evitar este problema es comprobar que hay datos en el BUFFER del receptor
mediante el campo cbInQue de la estructura COMSTAT antes de usar ReadFile() . Si se opta
por utilizar la primera de estas tcnicas el programa quedar como se muestra a continuacin.

//--------------------------------------------
int main(int argc, char **argv)
{
HANDLE hCom; // Manejador del puerto
DCB sComCfg;// Estructura configuracin
COMMPROP sComPro;// Estructura propiedades
COMSTAT sComSta;// Estado del puerto
DWORD dwErr; // Estado del error
COMMTIMEOUTS sTimOut;
BYTE acBuf[32]; // Un buffer
BYTE acBufRx[32];
DWORD dwBytWri; // Bytes escritos
DWORD dwBytRea; // Bytes leidos
DWORD dwTotBytRea;
DWORD dwLen;
// Bucle para controlar errores
while (true)
{
// Abrir el puerto
hCom=CreateFile("COM1",
GENERIC_READ|GENERIC_WRITE,
0,0,OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,0);
// Si error finalizar
if (hCom==INVALID_HANDLE_VALUE) break;
// Leer configuracin actual
if (!GetCommState(hCom,&sComCfg)) break;
// Cambiar configuracin
sComCfg.BaudRate=CBR_9600;
sComCfg.ByteSize=8;
sComCfg.Parity=NOPARITY;
sComCfg.StopBits=ONESTOPBIT;
sComCfg.fRtsControl=RTS_CONTROL_ENABLE;
sComCfg.fDtrControl=DTR_CONTROL_ENABLE;
// Escribir nueva configuracin
if (!SetCommState(hCom,&sComCfg)) break;
// Configurar TIMEOUTS
// Para que la operacin de lectura
// finalice inmediatamente
sTimOut.ReadIntervalTimeout=MAXDWORD;
sTimOut.ReadTotalTimeoutMultiplier=0;
sTimOut.ReadTotalTimeoutConstant=0;
sTimOut.WriteTotalTimeoutMultiplier=0;
sTimOut.WriteTotalTimeoutConstant=0;
sTimOut.WriteTotalTimeoutConstant=0;
if (!SetCommTimeouts(hCom,&sTimOut)) break;
// Contruimos la cadena a enviar
strcpy(acBuf,"Hola Mundo");
dwLen=strlen(acBuf);
// Transmitimos la cadena
if (!WriteFile(hCom,acBuf,
dwLen,&dwBytWri,0)) break;
cout<<"\nTx..:"<<acBuf ;
strcpy(acBuf,"");
strcpy(acBufRx,"");
while (true)
{
if (!ReadFile(hCom,acBuf,
dwLen,&dwBytRea,0)) break;
// Si se ha leido algo
if (dwBytRea>0)
{
acBuf[dwBytRea]='\0';
// Cuento el total leido
dwTotBytRea=dwTotBytRea+dwBytRea;
// Encadeno la informacin leida
strcat(acBufRx,acBuf);
// Salir si se ley toda
if (dwTotBytRea>=dwLen) break;
}
}
cout<<"\nRx...:"<<acBufRx;
cout<<"\n Pulse una tecla";
getch();
// Salir del bucle principal
break;
}
// Cerrar el puerto
if (hCom!=INVALID_HANDLE_VALUE)
CloseHandle(hCom);
return 0;
}

Como se observa para recibir utilizamos llamadas repetidas a la funcin ReadFile() dentro de
un bucle, cuando una llamada lee algo del BUFFER se contruye una cadena en acBuf que se
va encadenado a la cadena total acBufRx . El bucle finaliza cuando se lee toda la cadena. Este
bucle presenta el problema de que se convierte en un bucle infinito si no se leen dwLen bytes
del puerto, esto se puede solucionar utilizando tcnicas de control de tiempo como las que se
vieron en la programacin DOS.

10.5. CLASE TWinSerCom PARA COMUNICACIONES BAJO WINDOWS

Como ejemplo de utilizacin de estas funciones se va mostrar una clase TWinSerCom


similar a la que se vi en MS-DOS para envio y recepcin de bytes y tramas. En un proyecto
nuevo creamos una nueva unidad con File-New-Unit esta la salvaremos con el nombre de
PORTCOM.CPP y estar compuesta por dos ficheros, el anterior ms el fichero PORTCOM.H.

En el fichero PORTCOM.H escribiremos el interface de la clase

//----------------------------------------
// Fichero....:PORTCOM.H
// Descripcin:Declaracin clase TWINSERCOM
// Permite transmitir y recibir por sondeo
// mediante el API WIN32
// Autor......: PMR 1999
//-----------------------------------------
#include <windows.h>
//----------Declaracin de la clase
class TWinSerCom
{
protected :
int iNumCom; // Nmero de puerto
bool lOpen; // Abierto ?
HANDLE hCom; // Manejador del puerto
DCB sComCfg; // Estructura configuracin
COMSTAT sComSta; // Estado del puerto
DWORD dwErr; // Errores del puerto
DWORD dwTamBufIn;
DWORD dwTamBufOut;
DWORD dwEvtMask; // Mscara de eventos
COMMTIMEOUTS sTimOut;
public :
TWinSerCom(int iPor); // Constructor
~TWinSerCom(); // Destructor
bool Open(void); // Abrir puerto
bool Open(int iPor); // Abrir en otro
void Close(void); // Cerrarlo
bool IsOpen(void); // Leer estado
bool IsDCE(void); // Leer si DCE
bool RxByte(BYTE &bDat); // Tx BYTE
bool TxByte(BYTE bDat); // Rx BYTE
bool RxCad(char *pCad,DWORD dwLen); // Tx Cadena
bool TxCad(char *pCad); // Rx Cadena
DWORD LeerError(void); // Leer el error
bool SetEventos(DWORD dwDat);// Fijar eventos
DWORD EsperaEventos(void); // Esperar un evento
HANDLE LeeIde(void); // Leer Handle de COMx
};

Como se observa el interface de la clase es muy parecido al de la versin de DOS,


disponemos la siguiente tabla muestra un resumen del interface.

INTERFACE DE LA CLASE CWINSERCOM


Mtodo Descripcin
TWinSerCom() Constructor, requiere un nmero de puerto
~TWinSerCom() Destructor. Se encarga de cerrar el puerto
Open(void) Abrir el puerto configurado en el constructor
Open(int iPor) Abrir cualquier otro puerto
Close() Cerrar el puerto
IsOpen() Revisar si el puerto est abierto
IsDCE() Revisar si el DCE est conectado y listo
TxByte() Transmitir un Byte
RxByte() Recibir un Byte
TxCad() Transmitir una cadena o trama
RxCad() Recibir una cadena o trama
LeeIde() Leer el manejador del puerto
SetEventos() Fija los eventos a los que se responder
EsperaEventos() Espera los eventos programados

En el fichero PORTCOM.CPP se escribir la definicin de la clase que es la siguiente

//----------------------------------------
// Fichero....: PORTCOM.CPP
// Descripcin: Definicin clase TWinSerCom
// Autor......: PMR Julio 1999
//----------------------------------------
#include <vcl\vcl.h>
#pragma hdrstop
#include "PortCom.h"
//---------------------------- Constructor
TWinSerCom::TWinSerCom(int iPor)
{
lOpen=FALSE;
iNumCom=iPor;
dwTamBufIn=2048;
dwTamBufOut=1024;
};
//---------------------------- Destructor
TWinSerCom::~TWinSerCom(){Close();};
//-----------------Leer estado del puerto
bool TWinSerCom::IsOpen(void)
{return lOpen;}
//-----------------Leer manejador del puerto
HANDLE TWinSerCom::LeeIde(void)
{return hCom;}
//------------------Leer si DCE conectado
bool TWinSerCom::IsDCE(void)
{
bool lRes=FALSE;
while (true)
{
if (!lOpen) break;
if (!GetCommModemStatus(hCom,&dwErr))break;
if ((dwErr&MS_CTS_ON) && (dwErr&MS_DSR_ON))
lRes=TRUE;
break;
}
return lRes;
}
//------------------Leer si DCE conectado
DWORD TWinSerCom::LeerError(void)
{
ClearCommError(hCom,&dwErr,&sComSta);
return dwErr;
}
//------------------Fijar eventos
bool TWinSerCom::SetEventos(DWORD dwDat)
{
bool lRes=true;
dwEvtMask=dwDat;
if (!SetCommMask(hCom,dwEvtMask))
lRes=false;
return lRes;
}
//------------------Esperar evento
DWORD TWinSerCom::EsperaEventos(void)
{
// Esperar los eventos programados
// Aqu el proceso se quedar parado
// hasta que se produzca uno de los eventos
WaitCommEvent(hCom,&dwEvtMask,0);
return dwEvtMask;
}
//-------------------------Abrir el puerto
bool TWinSerCom::Open(int iPor)
{
if (lOpen) Close();
iNumCom=iPor;
return Open();
}
//-------------------------Abrir el puerto
bool TWinSerCom::Open(void)
{
char acCom[5]; // Para la cadena del puerto
while(TRUE)
{
// Si ya est abierto cancelar
if (lOpen) break;
lOpen=false;
// Abrir el puerto
sprintf(acCom,"COM%d",iNumCom);
hCom=NULL;
hCom=CreateFile(acCom,
GENERIC_READ|GENERIC_WRITE,
0,0,OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,0);
// Si falla cancelar
if (hCom==INVALID_HANDLE_VALUE) break;
// Leer CFG actual
if (!GetCommState(hCom,&sComCfg)) break;
// Cambiar configuracin
sComCfg.BaudRate=CBR_9600;
sComCfg.ByteSize=8;
sComCfg.Parity=NOPARITY;
sComCfg.StopBits=ONESTOPBIT;
sComCfg.fRtsControl=RTS_CONTROL_ENABLE;
sComCfg.fDtrControl=DTR_CONTROL_ENABLE;
// Escribir nueva configuracin
if (!SetCommState(hCom,&sComCfg))
break;
if (!SetupComm(hCom,dwTamBufIn,dwTamBufOut))
break;
// Configurar TIMEOUTS
// Para que la operacin de lectura
// finalice inmediatamente
sTimOut.ReadIntervalTimeout=MAXDWORD;
sTimOut.ReadTotalTimeoutMultiplier=0;
sTimOut.ReadTotalTimeoutConstant=0;
sTimOut.WriteTotalTimeoutMultiplier=0;
sTimOut.WriteTotalTimeoutConstant=0;
sTimOut.WriteTotalTimeoutConstant=0;
if (!SetCommTimeouts(hCom,&sTimOut))
break;
lOpen=TRUE;
break;
};
return lOpen;
};
//---------------------------Cerrar el puerto
void TWinSerCom::Close(void)
{
if (hCom!=INVALID_HANDLE_VALUE)
{
// Limpiar el BUFFER
PurgeComm(hCom,PURGE_TXCLEAR|PURGE_RXCLEAR);
// Cerrar el puerto
CloseHandle(hCom);
}
lOpen=FALSE;
return;
};
//--------------- Transmisin
bool TWinSerCom::TxByte(BYTE bDat)
{
DWORD dwBytWri; // Bytes escritos
if (lOpen)
{
// Transmitimos un BYTE
if (WriteFile(hCom,&bDat,1,&dwBytWri,0))
if (dwBytWri==1) return TRUE;
}
return FALSE;
};
//-------------Recepcin
bool TWinSerCom::RxByte(BYTE &bDat)
{
DWORD dwBytRea; // Bytes leidos
BYTE bDatRx; // Buffer para lo que se lea
if ( lOpen )
{
if (ReadFile(hCom,&bDatRx,1,&dwBytRea,0))
if (dwBytRea==1)
{
bDat=bDatRx;
return TRUE;
}
}
return FALSE;
};
//----------- Transmisin cadena
bool TWinSerCom::TxCad(char *pCad)
{
DWORD dwBytWri; // Bytes escritos
DWORD dwLen=strlen(pCad);
if (lOpen)
{
// Transmitimos la cadena
if (WriteFile(hCom,pCad,dwLen,&dwBytWri,0))
if (dwBytWri==dwLen) return TRUE;
}
return FALSE;
};
//-------------Recepcin cadena
bool TWinSerCom::RxCad(char *pCad,DWORD dwLen)
{
DWORD dwBytRea;
bool lRes=FALSE;
while (TRUE)
{
// Si el puerto est cerrado cancelo
if (!lOpen) break;
// Si falla la lectura del estado cancelo
if (!ClearCommError(hCom,&dwErr,&sComSta))
break;
// Si en el BUFFER no hay todava el
// nro de bytes que se desean leer cancelo
if (sComSta.cbInQue<dwLen) break;
if (!ReadFile(hCom,pCad,dwLen,&dwBytRea,0))
break;
// Si no leo el nmero de bytes que se piden
if (dwBytRea!=dwLen) break;
// Contruir una cadena
*(pCad+dwBytRea)='\0';
lRes=TRUE;
break;
}
return lRes;
};

El constructor se encarga de guardar el puerto inicial y de asignar unos valores por defecto el
BUFFER del transmisor y del receptor. El mtodo Open() se encarga de abrir el puerto y
configurarlo a 9600-N-8-1 ,adems se configura el TIMEOUT para que las funciones de lectura
y escritura finalicen inmediatamente. El mtodo Close() se encarga de cerrar el puerto, puesto
que este mtodo es llamado por el destructor de la clase, cada vez que se borre un objeto de la
clase se cerrar el puerto, esto es util ya que si nos olvidamos de cerrar el puerto se cerrar de
forma automtica cuando finalice el programa. El mtodo TxByte() se encarga de escribir un
BYTE en el BUFFER del transmisor, si la funcin no falla retornar TRUE. El mtodo RxByte()
se encarga de leer del BUFFER del receptor un nico byte. El mtodo TxCad() recibe un
puntero a un BUFFER con la cadena a transmitir y la escribe en el BUFFER del transmisor sin
transmitir el carcter de terminacin de cadena \0. El mtodo RxCad() requiere un BUFFER
donde alojar los datos y el nmero de bytes que se desean leer, el mtodo testea el nmero de
bytes pendientes de ser leidos cbInque y si son suficientes se leen. El mtodo LeeIde()
permite leer el idendtificador del puerto con el que se trabaja, esto es muy util si deseamos
utilizar cualquier otra funcin del API de sobre un puerto ya abierto por la clase. Los mtodos
relacionados con los eventos se vern posteriormente.

10.6. UN CHAT BAJO WINDOWS

La clase del apartado anterior se puede utilizar para desarrollar un chat bajo WINDOWS como
el que se muestra en la figura siguiente.

<FIGURA 5- CHAT BAJO WINDOWS>

Para probar este programa se pueden utilizar dos ordenador conectados mediante un cable
NULL-MODEM , un nico ordenador con un cable NULL-MODE con FEEDBACK y una nica
sesin del programa, o bien, como se muestra en la figura con un nico ordenador y un cable
NULL-MODEM conectado entre dos de los puetos libres del mismo ordenador.

Para empezar se debe seleccionar un puerto mediante los controles de la parte superio,si se
conectan cada ordenador puede escribir en la ventana local, cada pulsacin de tecla se
transmite al ordenador remoto que lo visualiza en la ventana remota. El LED de la parte
superior indica si el puerto est abierto o no, el led inferior indica si se detecta el DCE, en este
caso como se usa un cable NULL-MODEM el LED inferior indica la presencia del ordenador
remoto. Si se desar probar este programa se crear un nuevo proyecto en el que se
depositarn los objetos que se muestran en la figura y que se pueden extrar de la seccin
published en la declaracin de la clase que se encuentra en el fichero UNIT1.H.

..
#include "..\PortCom\PortCom.h"
..
class TForm1 : public TForm
{
__published:// IDE-managed Components
TPanel *Panel1;
TMemo *Memo1;
TPanel *Panel2;
TRadioButton *RadioButton1;
TRadioButton *RadioButton2;
TRadioButton *RadioButton3;
TRadioButton *RadioButton4;
TShape *Shape1;
TShape *Shape2;
TMemo *Memo2;
TTimer *Timer1;
TLabel *Label1;
TLabel *Label2;
void __fastcall RadioButton1Click(TObject *Sender);
void __fastcall RadioButton2Click(TObject *Sender);
void __fastcall RadioButton3Click(TObject *Sender);
void __fastcall RadioButton4Click(TObject *Sender);
void __fastcall Timer1Timer(TObject *Sender);
void __fastcall Memo1KeyPress(TObject *Sender, char &Key);
private: // User declarations
TWinSerCom *pCom;
public: // User declarations
__fastcall TForm1(TComponent* Owner);
};

En la declaracin de la clase, lo nico que se ha tenido que escribir es la declaracin de un


puntero a un objeto de la clase de comunicaciones pCom, para que se reconozca este objeto
se debe incluir el fichero donde se encuentra la declaracin de la clase PORTCOM.H. Adems,
en el proyecto hay que aadir la unidad PORTCOM.CPP donde se encuentra la definicin de la
clase para que la reonozca y compile. Para el texto local se utilza el objeto Memo1, para el
remoto el Memo2. El LED des estado abierto se crea mediante el objeto Shape1, para el LED
del DCE se utilzia el Shape2.El objeto Timer1 se utiliza para sondear cada 100 ms el estado
del puerto y si se ha recibido algn nuevo byte. En el formulario UNIT1.CPP escribimos el
siguiente cdigo.

//----------------------------------------
// Fichero....: UNIT1.CPP PROYECTO SERCHAT.CPP
// Descripcin: CHAT bajo windows
// Autor......: PMR - Julio 1999
//----------------------------------------
#include <vcl\vcl.h>
#pragma hdrstop
#include "Unit1.h"
//-----------------------------------------
#pragma resource "*.dfm"
TForm1 *Form1;
//-----------------------------------------
__fastcall TForm1::
TForm1(TComponent* Owner): TForm(Owner)
{
// Crear el objeto para las comunicaciones
pCom= new TWinSerCom(1);
Memo1->Clear();Memo2->Clear();
Memo2->Lines->Add("");
}
//-----------------------------------------
void __fastcall TForm1::
RadioButton1Click(TObject *Sender)
{pCom->Open(1);}
//-----------------------------------------
void __fastcall TForm1::
RadioButton2Click(TObject *Sender)
{pCom->Open(2);}
//-----------------------------------------
void __fastcall TForm1::
RadioButton3Click(TObject *Sender)
{pCom->Open(3);}
//-----------------------------------------
void __fastcall TForm1::
RadioButton4Click(TObject *Sender)
{pCom->Open(4);}
//-----------------------------------------
void __fastcall TForm1::
Timer1Timer(TObject *Sender)
{
BYTE bDat;
int iLin;
// LED de puerto abierto
if (pCom->IsOpen())
Shape1->Brush->Color=clRed;
else
Shape1->Brush->Color=clWhite;
// LED de DCE desconectado
if (pCom->IsDCE())
Shape2->Brush->Color=clRed;
else
Shape2->Brush->Color=clWhite;
// Si se ha recibido un BYTE
if(pCom->RxByte(bDat) )
{
// Si es retorno de carro
if(bDat=='\r')
{
Memo2->Lines->Add("");
iLin++;
}
else
{
iLin=Memo2->Lines->Count-1;
Memo2->Lines->Strings[iLin]=
Memo2->Lines->Strings[iLin]+char(bDat);
}
};
}
//--------------------------------------------
void __fastcall TForm1::
Memo1KeyPress(TObject *Sender, char &Key)
{
// Transmito
if (!pCom->TxByte(Key))
{
Application->MessageBox
("ERROR: No se ha podido transmitir",
"SerChat",MB_ICONERROR);
};
}
//----------------------------------------------

En el contructor se instancia un objeto de la clase de comunicaciones sobre COM1 mediante el


puntero pCom que es accesible en todos los miembros de la clase. En el evento OnClick() de
cada uno de los cuatro botones de rdio se ha asociado un cdigo para abrir el puerto
correspondiente. Mediante el evento OnTimer() se sondea periodicamente el estado del puerto
y si hay algn dato recibido. Si se recibe el retorno de carro \r se aade una lnea al objeto que
muestra el texto remoto, cualquier otro caracter es aadido a la ltima lnea mediante la
propiedad Lines del objeto Memo2. En el evento OnKeyPress() del objeto que visualiza el
texto local Memo1 se controla cada pulsacin de tecla, si la transmisin falla se mostrar un
mensaje en pantalla.

10.7. ENVIAR Y RECIBIR TRAMAS

La siguiente aplicacin puede ser til cuando se desea enviar tramas de test a un dispositivo
conectado al puerto serie y se desea mnonitorizar su respuesta: por ejemplo PLC, SENSORES
ETC.

<FIGURA 6.TRAMAS>
En la pantalla se puede escribir una cadena en el objeto Memo1 de la parte superior, al pulsar
el botn BitBtn1 se enviarn la cadena, entonces se esper el tiempo fijado por el objeto
TrackBar1 (este objeto tiene fijadas sus propiedades Min=100 y Max=3000) para leer el
nmero de bytes que se indican en el objeto MaskEdit1. En el ejemplo se ha conectado un
cable NULL-MODEM con FEED-BACK y se ha escrito un texto de 10 caracteres , se ha fijado
bytes de respuesta en 4 y se ha pulsado tres botn enviar. Para probar este ejemplo seguimos
los pasos indicados en el ejemplo anterior, la declaracin de la clase en UNIT.H es la
siguiente.

class TForm1 : public TForm


{
__published: // IDE-managed Components
TPanel *Panel1;
TMemo *Memo1;
TPanel *Panel2;
TRadioButton *RadioButton1;
TRadioButton *RadioButton2;
TRadioButton *RadioButton3;
TRadioButton *RadioButton4;
TShape *Shape1;
TShape *Shape2;
TMemo *Memo2;
TMaskEdit *MaskEdit1;
TLabel *Label1;
TLabel *Label2;
TTrackBar *TrackBar1;
TBitBtn *BitBtn1;
void __fastcall RadioButton1Click(TObject *Sender);
void __fastcall RadioButton2Click(TObject *Sender);
void __fastcall RadioButton3Click(TObject *Sender);
void __fastcall RadioButton4Click(TObject *Sender);
void __fastcall BitBtn1Click(TObject *Sender);
private: // User declarations
TWinSerCom *pCom;
void __fastcall RefrescaBotones(void);
public: // User declarations
__fastcall TForm1(TComponent* Owner);
};

En este caso hay que hay que escribir la declaracin del puntero pCom y el prototipo de un
mtodo que se utilizar para refrescar los LEDS, ya que en este ejemplo no disponemos de
ningn timer.

//-----------------------------------------
//Programa: Unit1 del proyecto SERTRAMA.cpp
//Desc....: Envio y recepcin de tramas
//Autor...: PMR 1999
//-----------------------------------------
#include <vcl\vcl.h>
#pragma hdrstop
#include "Unit1.h"
//-----------------------------------------
#pragma resource "*.dfm"
TForm1 *Form1;
//------------------------------------------
__fastcall TForm1::
TForm1(TComponent* Owner): TForm(Owner)
{
pCom= new TWinSerCom(1);
Memo1->Clear();Memo2->Clear();
}
//------------------------------------------
void __fastcall TForm1::
RadioButton1Click(TObject *Sender)
{pCom->Open(1);RefrescaBotones();}
//------------------------------------------
void __fastcall TForm1::
RadioButton2Click(TObject *Sender)
{pCom->Open(2);RefrescaBotones();}
//------------------------------------------
void __fastcall TForm1::
RadioButton3Click(TObject *Sender)
{pCom->Open(3);RefrescaBotones();}
//------------------------------------------
void __fastcall TForm1::
RadioButton4Click(TObject *Sender)
{pCom->Open(4);RefrescaBotones();}
//-------------------------------------------
void __fastcall TForm1::
RefrescaBotones(void)
{
// LED de puerto abierto
if (pCom->IsOpen())
Shape1->Brush->Color=clRed;
else
Shape1->Brush->Color=clWhite;
// LED de DCE desconectado
if (pCom->IsDCE())
Shape2->Brush->Color=clRed;
else
Shape2->Brush->Color=clWhite;
}
//------------------------------------------
void __fastcall TForm1::
BitBtn1Click(TObject *Sender)
{
{
bool lResOk; // Respuesta OK
char acBufTx[132];
char acBufRx[132];
char acMsg[200];
int iTamTrama;
DWORD tTimIni,tEsp;
while (true)
{
// Transmitir y esperar respuesta
if(!pCom->IsOpen())
{
Application->MessageBox
("ERROR: El puerto est cerrado",
"SerTrama",
MB_ICONERROR);
break;
}
// Convertit a cadena
strcpy(acBufTx,
Memo1->Lines->Strings[0].c_str());
if(!pCom->TxCad(acBufTx)) // Transmitir
{
Application->MessageBox(
"ERROR: No se ha podido transmitir",
"SerTrama",MB_ICONERROR);
break;
}
sprintf(acMsg,"Tx..:%s",acBufTx);
Memo2->Lines->Add(acMsg);
// Esperar respuesta
iTamTrama=MaskEdit1->Text.ToInt();
if (iTamTrama<1 ||iTamTrama>131) break;
// Leer tiempo
tEsp=TrackBar1->Position;
lResOk=false;
// Bucle de espera de respuesta
strcpy(acBufRx,"");
tTimIni=GetTickCount();
while(GetTickCount()-tTimIni<tEsp)
{
if(pCom->RxCad(acBufRx,iTamTrama))
{
lResOk=true;
break;
}
};
// Testeo si hubo error al recibir
if(!lResOk)
{
Application->MessageBox(
"ERROR: No se ha podido recibir",
"SerTrama",MB_ICONERROR);
break;
}
else
{
sprintf(acMsg,"Rx..:%s",acBufRx);
Memo2->Lines->Add(acMsg);
}
Memo1->Lines->Clear();
// Salir del bucle principal
break;
}
}
}
//---------------------------------------------------------------------------

En la definicin de la clase observar como el mtodo RefrescaBotones() se llama de forma


manual cada vez que seleccionamos un puerto. El mtodo asociado al evento OnClick() del
objeto BitBtn1 se encarga de transmitir y esperar la respuesta, para ello construye una cadena
en el array acBufTx y trata de enviarla mediante el mtodo TxCad() de la clase de
comunicaciones, a continuacin se espera la respuesta por un espacio de tiempo controlado
por el objeto TrackBar1 en su propiedad Position. Para medir el tiempo se utiliza la funcin del
API de WINDOS GetTickCount() .

GetTickCount()
DWORD GetTickCount(VOID)
ARG. ENTRADA DESCRIPCIN
void NINGUNO
TIPO SALIDA DESCRIPCIN
DWORD Nmero de milisegundos transcurridos desde que se inici el
sistema operativo.

Mediante un bucle para controla el tiempo y una variable tipo lgica lResOk determinamos si
en el tiempo permitido se ha producido la lectura correcta.

10.8.CONTROL DEL PUERTO MEDIANTE EVENTOS

Los eventos del puerto serie son parecidos a las interruppciones en MS-DOS, se trata de no
tener que sondear el puerto para transmitir, recibir o controlar errores: que sea el puerto el que
avise al programa de estas situaciones mediante un evento o mensaje. Un programa puede
configurar el puerto para que el sistema operativo le enve un mensaje cuando se produza
algunos de los eventos que se muestran en la siguiente tabla.

EVENTOS DEL PUERTO SERIE


Mtodo Descripcin
EV_BREAK Constructor, requiere un nmero de puerto
EV_CTS Se ha producido un cmbio en la lnea CTS
EV_DSR Se ha producido un cmbio en la lnea DSR
EV_ERR Se ha producido uno de los siguientes errores:
CE_FRAME,CE_OVERRUN,CE_RXPARITY.
EV_RING Se ha activado la lnea RING
EV_RLSD Se ha producido un cambio en la lnea DCD
EV_RXCHAR Hay datos pendientes de ser leiodos en el buffer , o
acaba de llegar al puerto un nuevo carcter
EV_RXFLAG Acaba de llegar al puerto el carcter especificado en el
campo EvtChar de la estructura DCB
EV_TXEMPTY Se acaba de producir la transmisin del ltimo carcter
pendiente.

El evento RLSD es util cuando se realizan comunicaciones con MODEM y se desea controlar la
posible prdida de portadora por errores de transmisin. El evento RXCHAR sirve para que el
sistema avise de hay datos pendientes de ser leidos en el buffer,si el tamao del BUFFER del
receptores uno se producir en cada carcter que llegue al puerto, no obstante, leer carcter a
carcter los bytes que van llegando al puerto es muy poco eficaz es mejor leer los datos
recibidos por bloques a travs del BUFFER de entrada. El evento RXFLAG es muy util para
recibir tramas que finalizan con un determinado carcter: suponer que deseamos recibir tramas
que finalizan con el carcter 0x0D. Si al abrir el puerto configuramos el miembro EvtChar de la
estructura DCB con dicho carcter 0x0D se producir un evento cada vez que se reciba una
trama completa.

En el API para el puerto serie existen trs funciones de aplicacin GetCommMask(),


SetCommMask() y WaitCommEvent(). Las dos primeras permiten leer o fijar la mscara de
eventos, es decir habilitar o iniohibir determinados sucesos, los eventos posibles son los
siguientes. Los prototipos de estas funciones son las siguientes.

GetCommMask()
BOOL GetCommMask(HANDLE hFile,LPDWORD lpEvtMask)
ARG. ENTRADA DESCRIPCIN
hFile Manejador del puerto o Handle devuelto por CreateFile()
LPDWORD Puntero a un DWORD donde la funcin depositar la mscara
de eventos.
TIPO SALIDA DESCRIPCIN
BOOL TRUE si la funcin se ejecut correctamente.

SetCommMask()
BOOL SetCommMask(HANDLE hFile,DWORD lpEvtMask)
ARG. ENTRADA DESCRIPCIN
hFile Manejador del puerto o Handle devuelto por CreateFile()
DWORD Un DWORD con la nueva mscara de eventos que se desea
asociar al puerto
TIPO SALIDA DESCRIPCIN
BOOL TRUE si la funcin se ejecut correctamente.

El siguiente programa configura la mscara de eventos para que se aada el evento EV_ERR

DWORD dwEvt; // Mscara de eventos


... Abrir y configurar el puerto
// Leer eventos actiales
GetCommEvent(hCom,&dwEvt);
dwEvt=dWEvt|EV_ERR;
SetCommEvent(hCom,dwEvt);
..

Una vez que se ha programado la mscara de eventos es necesario utilizar la funcin


WaitCommEvent() para esperar que se produzca el evento deseado, esta funcin finaliza
cuando se produce alguno de los eventos programados.

WaitCommEvent()
BOOL WaitCommEvent(HANDLE hFile,DWORD lpEvtMask)
ARG. ENTRADA DESCRIPCIN
hFile Manejador del puerto o Handle devuelto por CreateFile()
LPDWORD Puntero a un DWORD con la mscara de los eventos que hace
que finalice la funcin
LPOverlapped Puntero a una estructura de sobrecarga (0 si no se utiliza)
TIPO SALIDA DESCRIPCIN
BOOL TRUE si la funcin se ejecut correctamente.

El siguiente ejemplo es una versin bajo windows del contador serie que se vi en MS-DOS, en
la figura, la ventana DOS transmite a travs del puerto COM1 y la aplicacin WINDOWS recibe
a travs del COM4, se ha utilizado un cable NULL-MODEM entre COM1 y COM4.

FIGURA 7 .CONTADOR POR EVENTOS


Para probar este programa se crear un nuevo proyecto en el que se depositarn los objetos
que se muestran en la figura y que se pueden extrar de la seccin published en la declaracin
de la clase que se encuentra en el fichero UNIT1.H.

...
#include "..\PortCom\PortCom.h"
#include "RXSpin.hpp"
..
//------------------------------------
class TForm1 : public TForm
{
__published:// IDE-managed Components
TGroupBox *GroupBox2;
TMemo *Memo1;
TGroupBox *GroupBox1;
TLabel *Label1;
TPanel *Panel1;
TComboBox *ComboBox1;
TButton *Button1;
TLabel *Label2;
TRxSpinEdit *RxSpinEdit1;
TLabel *Label3;
void __fastcall Button1Click(TObject *Sender);
private: // User declarations
TWinSerCom *pCom;
public: // User declarations
__fastcall TForm1(TComponent* Owner);
};

Como se observa, se ha utilizado un componente RxSpin de la librera RxLib y que se utiliza


para el nmero de bytes que se desean leer. Como contador se ha utilizado el objeto Label1 al
que se le ha cambiado el tamao de la fuente para hacerlo gigante. En el proyecto abr que
incluir adems la unidad PORTCOM.CPP con la definicin de la clase de comunicaciones. La
definicin de la clase se encuentra en UNIT1.CPP y es la siguiente.

//----------------------------------------
// Fichero....: UNIT1.CPP
// Descripcin: Recibir bytes con eventos
// Autor......: PMR - Julio 1999
//----------------------------------------
#include <vcl\vcl.h>
#pragma hdrstop
#include "Unit1.h"
//----------------------------------------
#pragma link "RXSpin"
#pragma resource "*.dfm"
TForm1 *Form1;
//----------------------------------------
__fastcall TForm1::
TForm1(TComponent* Owner): TForm(Owner)
{
ComboBox1->ItemIndex=0;
// Crear el objeto de comunicaciones
pCom= new TWinSerCom(1);
}
//----------------------------------------
void __fastcall TForm1::
Button1Click(TObject *Sender)
{
AnsiString sCom;
BYTE cDatRx;
DWORD dwErr,dwEvtMask;
COMSTAT sComSta;
int iCnt;

while(TRUE)
{
// Leer el puerto seleccionado
sCom=ComboBox1->Text;
pCom->Open(ComboBox1->ItemIndex+1);
// Si est abierto
if (!pCom->IsOpen())
{
Memo1->Lines->Add("ERROR al abrir el puerto");
break;
}
Memo1->Lines->Add("Abierto "+sCom);
// Fijar la mscara de eventos
dwEvtMask=EV_RXCHAR|EV_ERR;
if (!SetCommMask(pCom->LeeIde(),dwEvtMask))
break;;
// Esperar el evento
for(iCnt=1;iCnt<=RxSpinEdit1->Value;iCnt++)
{
Memo1->Lines->Add
("Esperando byte nro.."+String(iCnt) );
// Esperar los eventos programados
WaitCommEvent(pCom->LeeIde(),&dwEvtMask,0);
// Si se ha producido un error
if(dwEvtMask & EV_ERR)
{
ClearCommError(pCom->LeeIde(),&dwErr,&sComSta);
Memo1->Lines->Add("Error al recibir.");
};
// Si se ha leido un byte
if (dwEvtMask & EV_RXCHAR)
{
// Leer el Byte del Buffer
if (pCom->RxByte(cDatRx));
{
Memo1->Lines->Add("Recibido un byte " );
Label1->Caption=AnsiString(cDatRx);
Label1->Refresh();
};
};
}; // Fin del for

break;
}; // Fin del while
pCom->Close();
};
//----------------------------------------------------

Para manejar el puerto se utiliza el puntero pCom de la clase de comunicaciones, este puntero
se inicializa en el constructor del formulario. Cuano se pulsa el botn se ejecuta el mtodo
Button1Click el cual abre el puerto seleccionado y configura la mscara de eventos para
detectar los bytes que se reciban o los errores que se produzcan. Cuando se alcanza la funcin
WaitCommEvent() el programa se queda congelado esperando uno de los dos eventos
programados, a continuacin se determina si se finaliz por error o por la entrada de un nuevo
byte. Los datos que se leen se muestran mediante la etiqueta Label1. Si se produce un error
en la recepcin se utiliza ClearCommError() para leer el tipo de error. Si se program el
miembro fAbortOnError al abrir el puerto es necesario llamar a esta funcin para que el puerto
pueda seguir trabajando. Este ejemplo sirve de base para recibir tramas completas en vez de
bytes, basta con programar el miembro fEvtChar al abrir el puerto y la mscara de
interrupciones con el bit EV_RXFLAG.

Aunque el ejemplo anterior funciona bienm tiene un grave problema y es que el programa se
quedar bloqueado en WaitCommEvent() si no se produce alguno de los eventos
programados. La solucin a este problema pasa por utilizar subprocesos,hilos o treats en los
programas.

10.9. PROCESOS Y SUBPROCESOS

El API WIN32 permite la creacin de subprocesos, hilos o Threads esta es una capacidad de
los sistemas operativos multitarea de que las aplicaciones en ejecucin (procesos) puedan
crear subprocesos que se ejecutan de forma independiente. Esta tcnica es til cuando
deseamos hacer alguna tarea en segundo plano o cuando se disponde de varios procesadores
y se desea asignar a cada uno de ellos una tarea especfica. Por ejemplo, el WORD de
Microsoft puede tener activado los subprocesos del corrector ortogrfico o del corrector
gramatical, mientras escribimos los correctores se estan ejecutando en paralelo con la edicin
del texto.
FIGURA 8. PROCESOS EN EJECUCIN
Del mismo modo que en el escritorio de WINDOWS podemos terner abiertos varios programas
(procesos) simultneamnete, dentro de una aplicacin podremos crear tantos subprocesos
como necesitemos. Los procesos permiten por tanto implementar la multitarea a nivel de
programa. No hay que abusar del nmero de subprocesos, ya que cada uno de ellos consume
tiempo del procesosadr como si se tratase de un programa independiente. En comunicaciones
los subprocesos se utilizan para recibir o transmitir en segundo plano mientras se atiende al
interface del usuario.

El API Win32 incluye una serie de funciones para manejar los procesos (GetThreadPriority(),
ResumeThread(), SetThreadPriority(), CreateThreat(), SuspedThreat() etc) estas funciones
pueden ser utilizadas como las que se han vista anteriormente, no obstante el C++ Builder
encapsula y facilita el uso de subprocesos mediante la clase TThread. La tabla siguiente
muestra un resumen del interface de la clase.

La clase TThread
Nombre Descripcin
TThread() Es el contructor de la clase, admite un parmetro
lgico para indicar si el subproceso se crea activado o
no
Execute() Cdigo que se ejecutar como si fuese una aplicacin
independiente. El constructor de la clase llama a este
mtodo de forma automtica.
Suspend() Suspende temporalmente la ejecucin de un proceso.
En este estado el proceso no hace uso del
microprocesador.
Resume() Reanuda la ejecucin de un subproceso
Termiate() Finaliza un subproceso y libera los recusos que
tuviese asignados. Cuando un proceso finaliza (al
cerrar el programa) se terminan de forma
automticas todos sus subprocesos
Synchronize() Permite ejecutar una funcin con aceso a cualquier
objeto de la VCL.

La clase no se encuentra encapsulada como un componente de la VCL, sino que para utilizarla
debemos de crear un nuevo proyecto y despues aadir una nueva unidad con File-New-
ThreadObject .

Como ejemplo de subprocesos vamos a crear una aplicacin que utilizar un contador
asociado a un subproceso. Crearemos un un nuevo proyecto PROCNT que guardaremos como
PROCNT.MAK y UNIT1.CPP. Sobre la unidad depositamos los componentes que se muestran
en la figura 9 y que se detallan en el listado UNIT1.H.

FIGURA 9. EN CONTADOR CON SUBPROCESO


A continuacin creamos un objeto Thread con File-New-ThreadObject como nombre de clase
utilizamos CntHilo, al aceptar se crear la unidad UNIT2. En esta unidad se declara
(UNIT2.H) y define (UNIT2.CPP) la clase CntHilo tal y como se muestra a continuacun.

// Fichero: Unit2.h
// Desc...: Definicin de la clase CntHilo
// Autor..: PMR 1999
//------------------------------------
#ifndef Unit2H
#define Unit2H
//------------------------------------
#include <vcl\Classes.hpp>
//------------------------------------
class CntHilo : public TThread
{
private:
protected:
void __fastcall Execute();
int iCnt;
public:
__fastcall CntHilo(bool CreateSuspended);
void __fastcall CntHilo::MostrarCnt(void);
};
//-------------------------------------------
#endif

// Fichero:Unit2.cpp
// Desc...: Declaracin de la clase CntHilo
// Autor..: PMR 1999
//-------------------------------------------
#include <vcl\vcl.h>
#pragma hdrstop
#include "Unit1.h" // Para acceso a Label1
//---------------------------------------------
__fastcall CntHilo::CntHilo(bool CreateSuspended)
: TThread(CreateSuspended)
{iCnt=0;}
//---------------------------------------------
void __fastcall CntHilo::Execute()
{
//---- Place thread code here ----
while (true)
{
iCnt++;
Synchronize(MostrarCnt);
};
}
//------------------------------------------
void __fastcall CntHilo::MostrarCnt(void)
{
Form1->Label1->Caption=iCnt;
}

En la clase la variable iCnt se utiliza como contador, se inicializa en el constructor de la clase y


se actualiza constantemente mientras el proceso est activo mediante el mtodo Execute(). Al
observar que el cuerpo de este mtodo se puede pensar que la aplicacin se quedar colgada
al ser un bucle infinito, esto sera cierto si se ejecutase ese cdigo en el hilo principal de la
aplicacin. Como se encuentra en un subproceso el cdigo se est ejecutando en paralelo con
el cdigo del programa principal. La lnea Synchronize(MostrarCnt) permite hacer que se
muestre el contador en la pantalla a travs de la etiqueta Label1. Esta objeto Label es
miembro de clase Form1 declarada en UNIT1.H, es por ello que se debe de incluir esta
unidad en el fichero UNIT2.CPP.

Para utilizar la clase debemos de declarar un objeto poCnt de la clase CntHilo en la


declaracin de la clase Form1 e inicializarlo al crear el formulario, despus asignamos los
mtodos a los botones segn se desprende del cdigo.

// Fichero: Unit1.cpp
// Desc...: Declaracin clase Form1
// ejemplo multihilo
// Autor..: PMR 1999
//--------------------------------------
#ifndef Unit1H
#define Unit1H
//-------------------------------------
#include <vcl\Classes.hpp>
#include <vcl\Controls.hpp>
#include <vcl\StdCtrls.hpp>
#include <vcl\Forms.hpp>
#include <vcl\Mask.hpp>
#include "unit2.h"
//---------------------------------------
class TForm1 : public TForm
{
__published: // IDE-managed Components
TLabel *Label1;
TButton *Button1;
TButton *Button2;
TButton *Button3;
TMaskEdit *MaskEdit1;
void __fastcall Button1Click(TObject *Sender);
void __fastcall Button2Click(TObject *Sender);
void __fastcall Button3Click(TObject *Sender);
private: // User declarations
CntHilo *poCnt;
public: // User declarations
__fastcall TForm1(TComponent* Owner);
};
//-------------------------------------------
extern TForm1 *Form1;
//-------------------------------------------
#endif

// Fichero:Unit2.cpp
// Desc...: Defincin clase CntHilo
// Autor..: PMR 1999
//-------------------------------------------
#include <vcl\vcl.h>
#pragma hdrstop
#include "Unit1.h"
//------------------------------------------
#pragma resource "*.dfm"
TForm1 *Form1;
//------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
// Creamos el subproceso
// inicialmente suspendido
poCnt=new CntHilo(true);
}
//-----------------------------------------
void __fastcall TForm1::
Button1Click(TObject *Sender)
{poCnt->Resume();}
//------------------------------------------
void __fastcall TForm1::
Button2Click(TObject *Sender)
{poCnt->Suspend();}
//------------------------------------------
void __fastcall TForm1::
Button3Click(TObject *Sender)
{poCnt->Terminate();}
//-------------------------------------------

En tiempo de ejecucin, al activar el subproceso veremos el contador funcionando y al mismo


tiempo podemos manejar el ratn, editar el control de edicin, pulsar un botn etc. Hemos
contruido una aplicacin multitarea que hace dos cosas a la vez, en primer plano atiende los
controles del formulario Form1,en paralelo y en segundo plano hay un contador que se
muestra en una etiqueta.

10.10. RECEPCIN EN SEGUNDO PLANO

Los subprocesos combinados con los eventos es el mecanismo ms rpido y eficaz para
realizar comunicaciones a travs del puerto serie, mientras que el proceso principal atiende la
interface del usuario, un proceso en segundo plano atiende al puerto: monitorizacin, lectura,
escritura etc. En este ejemplo se va ha disear un programa que permita enviar ordenes a un
modem y observar sus respuestas. El envio de las ordenes se realizar en primer plano y la
recepcin de las respuestas en segundo plano. El aspecto del programa se muestra en la figura
siguiente.

FIGURA 10. CONTROL DEL MODEM


En este programa , una vez abierto el puerto cualquier carcter que se reciba se mostrar en el
control TMemo de la parte izquierda, para enviar una cadena al puerto se debe teclear en el
control TMaskEdit y pulsar enviar, en el ejemplo se ha marcado un nmero de telfono
inexistente con el comando ATDP y despus de unos segundos se ha producido la respuesta
NO CARRIER indicando que en el otro extremo no existe ninguna portadora.

Para poner en prctica este programa hay que crear un proyecto nuevo sobre el que
insertaremos los componentes que se desprenden de UNIT1.H.

//--------------------------------------------
// Fichero....: Unit1.H
// Descripcin: Declaracin de la clase Form1
// Autor......: PMR Julio 1999
//---------------------------------------------
#ifndef Unit1H
#define Unit1H
#include <vcl\Classes.hpp>
#include <vcl\Controls.hpp>
#include <vcl\StdCtrls.hpp>
#include <vcl\Forms.hpp>
#include <vcl\ExtCtrls.hpp>
#include <vcl\Buttons.hpp>
#include "..\PortCom\PortCom.h"
#include "SerRx.h"
#include <vcl\Mask.hpp>
#include <vcl\Classes.hpp>
//---------------------------------------------
class TForm1 : public TForm
{
__published: // IDE-managed Components
TPanel *Panel1;
TLabel *Label2;
TMaskEdit *oEdi;
TGroupBox *GroupBox1;
TMemo *oMem;
TPanel *Panel2;
TButton *ButSal;
TButton *ButEnv;
TButton *ButOpen;
TButton *ButClose;
TComboBox *CBCom;
TLabel *Label1;
void __fastcall ButOpenClick(TObject *Sender);
void __fastcall ButCloseClick(TObject *Sender);
void __fastcall ButSalClick(TObject *Sender);
void __fastcall ButEnvClick(TObject *Sender);
private: // User declarations
// Para el subproceso
CSerRx *poSerRx;
public: // User declarations
// Objetos de comunicaciones
// pblico para tener acceso
// desde el subproceso
TWinSerCom *pCom;
// Para datos recibidos
BYTE acBuf[1024];
// Constructor
__fastcall TForm1(TComponent* Owner);
};
//--------------------------------------------
extern TForm1 *Form1;
//--------------------------------------------
#endif

Los nombres de los objetos se han cambiado para que aporten mas informacin: ButOpen,
ButClose, ButEnv, oMem etc. Se ha aadido un puntero pCom para manejar las
comunicaciones y un puntero poSerRx para manejar el subproceso. El buffer donse se
almacenarn los datos acBuf que se reciban y el objeto pCom se han puesto en la seccin
pblica para permitir el acceso desde el subproceso a estos objetos. En el proyecto hay que
incluir la unidad PORTCOM.CPP node se encuentra la definicin del la clase TWinSerCom
necesario para el objeto pCom. La definicin de la clase se encuentra en UNIT1.CPP

//-------------------------------------------
// Fichero....: Unit1.CPP
// Descripcin: Definicin de la clase Form1
// Autor......: PMR Julio 1999
//-------------------------------------------
#include <vcl\vcl.h>
#pragma hdrstop
#include "Unit1.h"
#pragma resource "*.dfm"
TForm1 *Form1;
//-----------------Constructor del formulario
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
CBCom->ItemIndex=0;
// Crear el objeto de comunicaciones
pCom= new TWinSerCom(1);
// Crear el objeto del subproceso
poSerRx=new CSerRx(false);
// Estado de los botones
ButOpen->Enabled=true;
ButClose->Enabled=false;
ButEnv->Enabled=false;
CBCom->Enabled=true;
}
//------------Abrir Puerto
void __fastcall TForm1::
ButOpenClick(TObject *Sender)
{
// Bucle para control de errores
while(true)
{
oMem->Lines->Clear();
// Abrir el puerto
pCom->Open(CBCom->ItemIndex+1);

if (!pCom->IsOpen())
{
oMem->Lines->Add("ERROR al abrir el puerto");
break;
};
// Fijar la mscara de eventos
if (!pCom->SetEventos(EV_RXCHAR|EV_ERR))
{
oMem->Lines->Add("ERROR al configurar");
break;;
}
// Activar el proceso en segundo plano
// para la recepcin de datos
poSerRx->Resume();
// Estado de los botones
ButOpen->Enabled=false;
ButClose->Enabled=true;
ButEnv->Enabled=true;
CBCom->Enabled=false;
oMem->Lines->Add("Puerto abierto");
break;
};
}
//-----------Cerrar el puerto
void __fastcall TForm1::
ButCloseClick(TObject *Sender)
{
//Cerrar puerto
pCom->Close();
// Bloquear proceso en segundo plano
poSerRx->Suspend();
// Estado de los botones
ButOpen->Enabled=true;
ButClose->Enabled=false;
ButEnv->Enabled=false;
CBCom->Enabled=true;
oMem->Lines->Add("Puerto cerrado");
}
//--------------------Salir
void __fastcall TForm1::
ButSalClick(TObject *Sender)
{Close();}
//--------------------Enviar cadena
void __fastcall TForm1::
ButEnvClick(TObject *Sender)
{
AnsiString cTmp;
char *pCad;
// Leo la variable AnsiString del control
cTmp=oEdi->Text;
// Aado el retorno de carro
cTmp=cTmp+"\r";
// Convierto el objeto cTmp a cadena de C
pCad=cTmp.c_str();
// Transmito la cadena y la muestro
if ( pCom->TxCad(pCad))
oMem->Lines->Add(pCad );
}
//--------------------------------------

En el constructor se crean los objetos de comunicaciones y subproceso y se inicializan algunas


variables y estados de los componentes del formulario. Al pulsar al botn ButOpen se abre el
puerto y se activa el subproceso, a partir de ese momento cualquier carcter que llegue al
puerto ser mostrado en el control memo. Observar que cuando se abre el puerto se programa
la mscara de eventos de la clase TWinSerCom mediante el mtodo SetEventos() . Al pulsar
el botn ButClose se cierra el puerto y se suspende el subproceso. Al pulsar el botn ButEnv
se lee el texto que se encuentra en el control oEdi y se envia al puerto mediante el mtodo
TxCad() .

Para crear el subproceso utilizamos File-New-Threath y como nombre de clase utilizamos


CSerRx, esta clase la salvamos en SERRX.CPP y SERRX.H.

//----------------------------------------
// Fichero....: SerRx.H
// Descripcin: Definicin de la clase CSerRx
// para crear un subproceso que lea del puerto
// mediante eventos.
// Autor......: PMR - Julio 1999
//-----------------------------------------
#ifndef SerRxH
#define SerRxH
//-----------------------------------------
#include <vcl\Classes.hpp>
//-----------------------------------------
class CSerRx : public TThread
{
private:
protected:
void __fastcall Execute();
public:
__fastcall CSerRx(bool CreateSuspended);
void __fastcall MostrarRx(void);
};
//------------------------------------------
#endif

//--------------------------------------------
// Fichero....: SerRx.CPP
// Descripcin: Declaracin de la clase CSerRx
// para crear un subproceso que lea del puerto
// mediante eventos.
// Autor......: PMR - Julio 1999
//---------------------------------------------
#include <vcl\vcl.h>
#pragma hdrstop
#include "SerRx.h"
#include "unit1.h"
//---------------------------------------------
__fastcall CSerRx::CSerRx(bool CreateSuspended)
: TThread(CreateSuspended)
{
}
//---------Cdigo asociado al subproceso
void __fastcall CSerRx::Execute()
{
DWORD dwEvt;
BYTE cDatRx;
int iCnt;

// Bucle infinito para el subproceso


while(true)
{
// Esperar los eventos programados
// Aqu el subproceso se quedar parado
// hasta que se produzca uno de los eventos
dwEvt=Form1->pCom->EsperaEventos();
// Si se ha producido un error
if(dwEvt & EV_ERR)
{
// Resetear el error y mostrar mensaje
Form1->pCom->LeerError();
Form1->oMem->Lines->Add("Error al recibir.");
};
// Si se ha leido un byte
if (dwEvt & EV_RXCHAR)
{
// Leer TODOS los BYTES pendientes de
// ser leidos del buffer del receptor
iCnt=0;
while (Form1->pCom->RxByte(cDatRx))
{
Form1->acBuf[iCnt]=cDatRx;
iCnt++;
};
// Crear una cadena
Form1->acBuf[iCnt]=0x00;
// Mostrar en pantalla
Synchronize(MostrarRx);
};
};
}
//-----------------Mostrar cadena recibida
void __fastcall CSerRx::MostrarRx(void)
{
const char *pCad;
pCad=Form1->acBuf;
Form1->oMem->Lines->Add(AnsiString(pCad));
}

El mtodo execute() de la clase disponde de un bucle infinito que se ejecuta en un subproceso


en paralelo con el proceso principal, en este mtodo se esperan que ocurran los eventos
programados mediante el mtodo EsperarEventos() . Si no se produce error se pasa a leer
todos los bytes pendientes en el BUFFER del receptor, recordar que el evento de EV_RXCHAR
se dispara cuando hay datos pendientes de ser leidos, pero no se garantiza que se dispare el
evento en cada byte recibido. Los datos recibidos se almacenan en el buffer acBuf y se
muestran en el control oMem del formulario principal. A continuacin se muestra el archivo de
proyecto final SEREVT2.MAK que queda como resultado .

// Fichero SEREVT2.MAK
// Descripcin: Proyecto modem
// Fecha..: PMR Julio 1999
//---------------------------------------
#include <vcl\vcl.h>
#pragma hdrstop
//---------------------------------------
USEFORM("Unit1.cpp", Form1);
USERES("SEREVT2.res");
USEUNIT("\DATOS\LIBRO_CI\fuentes\win\PortCom\PortCom.cpp");
USEUNIT("SerRx.cpp");
//----------------------------------------
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
try
{
Application->Initialize();
Application->CreateForm(__classid(TForm1), &Form1);
Application->Run();
}
catch (Exception &exception)
{
Application->ShowException(&exception);
}
return 0;
}
//-----

EJERCICIOS Y ACTIVIDADES

Teclear la clase TWinSerCom y hacer un pequeo programa que la use para enviar y recibir un
carcter.

Los ejercicios planteados en el captulo 6 con la clase de MS-DOS CPortCom reescribirlos en


WINDOWS utilizando la clase TWinSerCom.

Teclear el ejemplo del MODEM y verificar las respuestas para los comandos HAYES ms
habituales.

Desarrollar un programa que reciba datos del puerto serie mediante eventos. Los datos
recibidos se almacenarn en un fichero, si se produce un evento de error se mostrar un
mensaje y se cancelar la recepcin de datos.

fin del captulo--