PROGRAMACION PUERTO SERIE

Document Sample
PROGRAMACION PUERTO SERIE Powered By Docstoc
					                                                 EL APII DEL PUERTO SERIIE
                                                 EL AP DEL PUERTO SER E

                                                                         Introducción

                                                                              Contenido
FUNCIONES EL API WIN32 PARA COMUNICACIONES SERIE
LÍNEAS 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
RECEPCIÓN EN SEGUNDO PLANO


10.1. FUNCIONES DEL API WIN32 PARA COMUNICACIONES SERIE

La utilización del las funciones del API WIN32 para trabajar con el puerto serie garantiza la
portabilidad del código 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 más de dos docenas, se irán 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.- Operación de apertura: sirve para obtener un manejador o handle y comprobar si el sistema
operativo ha podido tomar el control del dispositivo para nuestra aplicación.

2.- Configuración: Sirve par establecer los parámetros de la comunicación: 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 través de BUFFER uno para la transmisión (escritura) y otro para la
recepción (lectura).

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

Las funciones que se irán presentando a continuación han sido resumidad para dejar sólo
aquellos parámetros que determinan el proceso de comunicación.

1.- Apertura y cierre del puerto

Para abrir el puerto se utiliza la función CreateFile() , esta función es una función genérica 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           DESCRIPCIÓN
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 simultánea.
lpSecurityAttributes   Descriptor de seguridad. Debe ser 0
dwCreationDistribution Especifica que acción 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            DESCRIPCIÓN
HANDLE                 Manejador o Handle del puerto. Un puntero necesario para
                       acceder al puerto, o NULL si la operación 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 básicos 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 función
CloseHandle(), esta función devuelve TRUE si el cierre del dispostivo se hizo correctamente,
es importante no tratar de cerrar un manejador no válido, es decir un manejador no inicializado
correctamente por CreateFile() .


                                    CloseHandle()
                            BOOL CloseHandle(HANDLE hObject);
ARG. ENTRADA            DESCRIPCIÓN
hObject                 Manejador o handle genérico.
TIPO SALIDA             DESCRIPCIÓN
BOOL                    TRUE si la función se ejecutó correctamente.




2.- Configuración del puerto

Para leer y escribir la configuración 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 configuración 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       DESCRIPCIÓN
DCBlength             DWORD      Tamaño 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 chequearán 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 línea 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 función EscapeCommFunction()
                                 para cambiar el estado de la línea DTR
fDsrSensitivity:1     BIT        Si TRUE se descartán los datos recibidos mientras la línea 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 transmisión se parará después de recibir XOFF, se
                                 reanudará si se recibe un XON o bien se vacía el BUFFER del receptor por debajo
                                 del límite inferior XOnLimit.
fOutX: 1              BIT        Si TRUE habilita el control de flujo XON/XOFF durante la transmisión .La
                                 transmisión 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 recepción .Se enviará el
                                 caracter XOFF si el BUFFER del receptor alcance el límite XoffLimit. Se enviará
                                 el caracter XON si eñl BUFFER baja se vacia por debajo de XonLimit.
fErrorChar: 1         BIT        Si TRUE se sustituirán 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 línea 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 transmisión tenga
                                 datos para transmitir.
                                 En los dos primeros casos se puede utilizar la función EscapeCommFunction()
                                 para cambiar el estado de la línea RTS
fAbortOnError:1       BIT        Si TRUE se abortará cualquier operación 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 límite inferior del BUFFER del receptor que fuerza la transmisión de
                                 XON
XoffLim               WORD       Determina el límite superiro del BUFFER del receptor que fuerza la transmisión de
                                 XOFF
ByteSize              BYTE       Determina el número 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 transmisión
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 configuración del puerto: tipo de SDU,
tipo de control de flujo, parámetros del control de flujo etc. Para trabajar con la estructura DCB
disponemos de trés funciones GetCommState() para leer la configuración actual del puerto,
SetCommState() para configurar el puerto y BuildCommDCB() para configurar el puerto
utilizando el estilo clásico del comando MODE del DOS.


                             GetCommState()
            BOOL GetCommState(HANDLE hFile,LPDCB lpDCB);
ARG. ENTRADA       DESCRIPCIÓN
hFile              Manejador del puerto o Handle devuelto por CreateFile()
lpDCB              Puntero a una estructura de tipo DCB donde se recibirá la
                   configuracón actual del puerto.
TIPO SALIDA        DESCRIPCIÓN
BOOL               TRUE si la función se ejecutó correctamente.
                              SetCommState()
            BOOL SetCommState(HANDLE hFile,LPDCB lpDCB)
ARG. ENTRADA       DESCRIPCIÓN
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        DESCRIPCIÓN
BOOL               TRUE si la función se ejecutó correctamente.


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



Para probar la configuración del puerto usaremos una aplicación de cónsola 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 configuración actual
        lOk=GetCommState(hCom,&sComCfg);
        if (!lOk)
           {
           cout<<"ERROR al leer la configuración";
           cout<<GetLastError();
           break;
           };
        // Cambiar la configuración a 9600-N-8-1
        sComCfg.BaudRate=CBR_9600;
        sComCfg.ByteSize=8;
        sComCfg.Parity=NOPARITY;
       sComCfg.StopBits=ONESTOPBIT;
       // Escribir la nueva configuración
       lOk=SetCommState(hCom,&sComCfg);
       if (!lOk)
          {
          cout<<"ERROR al escribir la configuración";
          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 continuación se lee se lee la configuración actual mediante
GetCommState(), se cambian los miembros de la estructura que se necesitan y se graba la
nueva configuración en el puerto mediante SetCommState() . El bucle principal se utiliza para
cancelar la ejecución del programa ante cualquier error. Finalmente se cierra el puerto sólo si
se abrió correctamente.

<FIGURA        1. SALIDA DE LA CONFIGURACIÓN>

La función GetLastError() sirve para obtener el código del error que se ha producido, estos
códigos de error se encuentran definidos en el fichero WINNT.H .
                            GetLastError()
                            DWORD GetLastError(VOID)
ARG. ENTRADA        DESCRIPCIÓN
No hay
TIPO SALIDA         DESCRIPCIÓN
DWORD               Código del último error producido



Para utilizar la función BuilCommDCB() se deberá de declarar una estructur DCB y pasarla
como parámetro por referencia a la función. La función 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
condicionarán el funcionamiento de las líneas de control del flujo DTR-DSR y RTS-CTS. Estas
líneas de salida del DTE (DTR,RTS) pueden ser manipuladas mendiante la función
EscapeCommFuntion() . Del mismo modo se puede leer el estado de las líneas de entrada al
DTE (DSR,CTS,RING,CD) mediante la función GetCommModemStatus().
                          EscapeCommFunction()
            BOOL EscapeCommFunction(HANDLE hFile,DWORD dwFunc)
ARG. ENTRADA       DESCRIPCIÓN
hFile              Manejador del puerto o Handle devuelto por CreateFile()
dwFunc             Una palabra para indicar la operación a realizar:
                   CLRDTR Pone a cero la señal DTR (data-terminal-ready).
                   CLRRTS Pone a cero la señal RTS (request-to-send) signal.
                   SETDTR Pone a uno la señal DTR (data-terminal-ready)
                   SETRTS Pone a uno la señal RTS (request-to-send)
                   SETXOFF Simula la recepción de un carácter XOFF.
                   SETXON Simula la recepción de un carácter XON
                   SETBREAK Suspende la transmisión
                   CLRBREAK Reanuda la la transmisión
TIPO SALIDA        DESCRIPCIÓN
BOOL               TRUE si la función se ejecutó correctamente.

Como ejemplo de uso, el siguiente código activa y desactiva la línea 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 transmisión se puede utilizar la función anterior con los
parámetros SETBREAK y CLRBREAK o bien utilizar las funciones equivalentes
SetCommBreak() y ClearCommBreak(), ambas sólo requieren el manejador del puerto y
retornan TRUE si la operación se completó. Si se desean leer las líneas de estado que
proceden del DCE se puede usar la función GetCommModemStatus().

                        GetCommModemStatus()
BOOL GetCommModemStatus(HANDLE hFile,LPDWORD lpModemStat);
ARG. ENTRADA      DESCRIPCIÓN
hFile             Manejador del puerto o Handle devuelto por CreateFile()
lpModemStat       Un puntero a una variable DWORD donde se escribirá la
                  configuaración del registro de estado del mode, según los
                  siguientes valores.
                  MS_CTS_ON           La señal CTS está activa
                  MS_DSR_ON           La señal DSR está activa
                  MS_RING_ON          La señal RING está activa
                  MS_RLSD_ON          La señal CD está activa
TIPO SALIDA       DESCRIPCIÓN
BOOL              TRUE si la función se ejecutó correctamente.

El siguiente código 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 más información sobre el estado del puerto serie y comprobar si se ha producido
un error y el tipo de error es ClearCommError() . Esta función además 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 función después de haberse producido un error.
                               ClearCommError()
BOOL ClearCommError(HANDLE hFile,
                    LPDWORD lpErrors,
                    LPCOMSTAT lpStat );
ARG. ENTRADA      DESCRIPCIÓN
hFile             Manejador del puerto o Handle devuelto por CreateFile()
lpErrors          Puntero a una variable DWORD donde se recibirá el código del
                  error.
                  CE_BREAK           Detectado corte en la línea
                  CE_FRAME           Detectado error de trama
                  CE_IOE             Detectado error de E/S
                  CE_MODE            Manejador hFile especificado no válido.
                  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       DESCRIPCIÓN
BOOL              TRUE si la función se ejecutó correctamente.

El segundo argumento de la función es un puntero a una variable DWORD donde la función
depositará el tipo de error que se haya producido. El tercer argumento es un puntero a una
estructura de tipo COMSTAT donde la función depositará información detallada del estado del
puerto serie, los miembros de esta estructura son los siguientes.


                      MIEMBROS DE LA ESTRUCTURA COMSTAT
CAMPO                 TIPO   DESCRIPCIÓN
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 carácter EOF
fTxim : 1;            BIT    Si TRUE Hay un caracter esperando ser transmitido
DWORD fReserved :     25 BIT No se usan
25;
DWORD cbInQue;        DWORD     Número de caracteres en BUFFER del receptor
DWORD cbOutQue;       DWORD     Número 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 línea";
        }
      // Cerrar el puerto



10.3. BUFFERS DEL TRANSMISOR Y DEL RECEPTOR

Todas las comunicaciones mediante el API de WINDOWS se realizan a través del BUFFERS
tanto para la transmisión como para la recepción. Una vez que el puerto ha sido abierto y
configurado cualquier carácter que llegue al puerto será almacenado en el buffer del receptor
de forma automática por el sistema operativo, no importa lo que está haciendo nuestro
programa o cualquier otro que se encuentre en ejecución. Del mismo modo, cuando
escribamos en el puerto se hará a través del BUFFER del transmisor, el programa escribirá en
él y el sistema operativo irá enviando los datos de forma totalmente automática siempre que el
control de flujo implementado lo permita (Si se habilita control de flujo RTS-CTS, será necesario
que DCE mantenga activa su línea CTS para que la transmisión se produzca).

<FIGURA 2. BUFFER DEL TRANSMISOR Y RECEPTOR>
El tamaño de los BUFFERS de entrada y salida deberá de ser acorde con el tamaño de la
trama que se está manejando, si se utiliza una trama de 512 Bytes el BUFFER deberá tener al
menos ese tamaño.

Cuando se abre el puerto se crean los buffers de un tamaño predeterminado, se puede conocer
el tamaño actual de los BUFFERS mediante la función 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 tamaño total
del BUFFER del transmisor y del receptor respectivamente, esta función requiere que el puerto
esté abierto y configurado.
                            GetCommProperties()
      BOOL GetCommProperties(HANDLE hFile,LPCOMMPROP lpCommProp)
ARG. ENTRADA         DESCRIPCIÓN
hFile                Manejador del puerto o Handle devuelto por CreateFile()
lpCommProp           Puntero a un estructura COMMPROP donde la función
                     depositará información sobre las características del puerto.
TIPO SALIDA          DESCRIPCIÓN
BOOL                 TRUE si la función 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 además cambiar el tamaño de estos BUFFERS mediante la función
SetupComm().
                              SetupComm()
BOOL SetupComm(HANDLE hFile,DWORD dwInQueue,DWORD dwOutQueue)
ARG. ENTRADA      DESCRIPCIÓN
hFile             Manejador del puerto o Handle devuelto por CreateFile()
dwInQueue         Tamaño que se desea para el BUFFER del receptor
dwOutQueue        Tamaño que se desea para el BUFFER del transmisor
TIPO SALIDA       DESCRIPCIÓN
BOOL              TRUE si la función se ejecutó correctamente.

Esta función se llamará normalmente después de la apertura del puerto antes de cualquier
operación de lectura o escritura. El siguiente ejemplo es una aplicación de cónsola 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 configuración
      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 configuración actual
        if (!GetCommState(hCom,&sComCfg)) break;
        // Cambiar configuración
        sComCfg.BaudRate=CBR_9600;
        sComCfg.ByteSize=8;
        sComCfg.Parity=NOPARITY;
        sComCfg.StopBits=ONESTOPBIT;
        // Escribir nueva configuración
        if (!SetCommState(hCom,&sComCfg)) break;
        // Fijar tamaño 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 tamaño Buffer
        cout<<"\n Tamaño BUFFER TX:"<<
               sComPro.dwCurrentTxQueue;
        cout<<"\n Tamaño BUFFER RX:"<<
               sComPro.dwCurrentRxQueue;
        // Mostrar tamaño 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 operación puede ser un problema cuando
queremos enviar un carácter de control de forma inmediata, en este caso el dato sería añadido
al final del BUFFER y se transmitiría después de los caracteres que estuvieran pendienetes.
Para el envio inmedianto de un carácter se dispone de la función TransmitCommChar().
                          TransmitCommChar()
          BOOL TransmitCommChar(HANDLE hFile,char cChar);
ARG. ENTRADA       DESCRIPCIÓN
hFile              Manejador del puerto o Handle devuelto por CreateFile()
cChar              Carácter a envias
TIPO SALIDA        DESCRIPCIÓN
BOOL               TRUE si la función se ejecutó correctamente.

Si en algún momento se desaear borrar los bytes que quedan remanentes en alguno de los dos
BUFFERS se pude utiliza la función PurgeComm() , esta operación 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        DESCRIPCIÓN
hFile               Manejador del puerto o Handle devuelto por CreateFile()
dwFlags             Es uno o combinación 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         DESCRIPCIÓN
BOOL                TRUE si la función se ejecutó correctamente.

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


10.4. LEER Y ESCRIBIR EN PUERTO
Para leer y escribir en el puerto se utilizan las funciones genéricas ReadFile() y WriteFile(),
ambas son funciones genéricas que permiten leer o escribir ficheros, puertos etc. En la tabla
siguiente se muestra una descripción de cada una de ellas
                                  ReadFile()
          BOOL ReadFile(HANDLE hFile,
               LPVOID lpBuffer,
               DWORD nNumberOfBytesToRead,
               LPDWORD lpNumberOfBytesRead,
               LPOVERLAPPED lpOverlapped);
ARG. ENTRADA          DESCRIPCIÓN
hFile                 Manejador del puerto o Handle devuelto por CreateFile()
lpBuffer              Puntero al buffer donde la función depositará los datos leidos.
nNumberOfByteToRead Número de bytes que se desean leer
lpNumberOfByteRead    Puntero a una DWORD donde la función indicará los datos
                      que realmente se han lido.
lpOverlapped          Puntero a una estructura OVERLAPPED para acceso a
                      especial a puerto.
TIPO SALIDA           DESCRIPCIÓN
BOOL                  TRUE si la función se ejecutó correctamente.

La función utiliza como parámetros de entrada el manejador de dispositivo(hFile) y el número
de bytes que se desean leer(nNumberOfByteRead) . La función retorna a través de los
argumentos pasados por referencia el número de bytes realmente leidos del dispostivo en
lpNumberOfByteRead y los bytes leidos a través 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           DESCRIPCIÓN
hFile                  Manejador del puerto o Handle devuelto por CreateFile()
lpBuffer               Puntero al buffer donde la función leerá los datos que
                       escribirá en el puerto.
nNumberOfByteToWrite Número de bytes que se escribirán en el puerto
lpNumberOfByteWritten Puntero a una DWORD donde la función indicará los datos
                       que realmente se han escrito.
lpOverlapped           Puntero a una estructura OVERLAPPED para acceso a
                       especial a puerto.
TIPO SALIDA            DESCRIPCIÓN
BOOL                   TRUE si la función se ejecutó correctamente.

La función WriteFile() utiliza como parámetros de entrada el manejador de dispositivo(hFile) y
el número de bytes que se desean escribir(nNumberOfByteToWrite), y un BUFFER donde la
función leerá los bytes que se escribirán en el dispostivo lpBuffer. La función retorna a través
del argumentos pasados por referencia el número de bytes realmente escritos en el dispostivo
en lpNumberOfByteWritten. Ambas devuelven TRUE si la operación de lectura o escritura se
efectuó correctamente.

Los dispositivos sobre los que se utilizan estas funciones soportan el control de TIMEOUT o
tiempo máximo de lectura y escritura. Por ejemplo, un dispositivo puede ser configurado para
que la operación de lectura dure como máximo 100 ms. esto implica que si la operación se
prolonga más del tiempo permitido la función finalizará normalmente, es decir retornará
TRUE.Para determinar si una opeación de lectura/escriturá finalizó por un timeout habrña que
comparán los bytes que se querías leer/escribir con los bytes realmente leidos/escritos.

La funciones GetCommTimmeout() y SetCommTimeouts() permiten configurar como se
realizarán las operaciones de lectura y escritura. Ambas trabajan sobre una estructura
COMMTIMEOUTS que define el tiempo máximo (TIMEOUT) que durará una operación de
lectura o escritura.


                      MIEMBROS DE LA ESTRUCTURA COMMTIMEOUTS
CAMPO                         TIPO    DESCRIPCIÓN
ReadIntervalTimeout           DWORD   Tiempo máximo 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
operación 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 utilizarán 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 configuración se disponde de las
funciones GetCommTimeout() y SetCommTimeout() respectivamente.


                          GetCommTimeoust()
BOOL GetCommTimeouts(HANDLE hFile,LPCOMMTIMEOUTS lpCommTimeouts)
ARG. ENTRADA      DESCRIPCIÓN
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       DESCRIPCIÓN
BOOL              TRUE si la función se ejecutó correctamente.




                          SetCommTimeoust()
BOOL SetCommTimeouts(HANDLE hFile,LPCOMMTIMEOUTS lpCommTimeouts)
ARG. ENTRADA      DESCRIPCIÓN
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       DESCRIPCIÓN
BOOL              TRUE si la función se ejecutó correctamente.



El siguiente programa se utiliza una aplicación de cónsola 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 configuración
      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 configuración actual
       if (!GetCommState(hCom,&sComCfg)) break;
       // Cambiar configuración
       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 configuración
       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 través del mismo puerto. En base al programa
anterior modificamos el bucle de transmisión 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, después se borra y se lee del puerto un número de
byte igual a la cadena que se envió. Finalmente, se le añade el carácter 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 función esperará de forma indefinida la llegada de nuevos datos. Si lo que
queremos es que la función finalice inmediatamente debemos configuar el TIMEOUT como se
muestra a continuación.

       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 función finalizará
inmediatamente después de llamarla y puede ocurrir que no haya leido ningún dato. Otra
solución 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 técnicas el programa quedará como se muestra a continuación.

     //--------------------------------------------
     int main(int argc, char **argv)
     {
     HANDLE   hCom;   // Manejador del puerto
     DCB      sComCfg;// Estructura configuración
     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 configuración actual
       if (!GetCommState(hCom,&sComCfg)) break;
       // Cambiar configuración
       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 configuración
       if (!SetCommState(hCom,&sComCfg)) break;
       // Configurar TIMEOUTS
       // Para que la operación 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 información 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 función 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 técnicas de control de tiempo como las que se
vieron en la programación DOS.

10.5. CLASE TWinSerCom PARA COMUNICACIONES BAJO WINDOWS

Como ejemplo de utilización de estas funciones se va mostrará una clase TWinSerCom
similar a la que se vió en MS-DOS para envio y recepción 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 más el fichero PORTCOM.H.

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

     //----------------------------------------
     // Fichero....:PORTCOM.H
     // Descripción:Declaración clase TWINSERCOM
     // Permite transmitir y recibir por sondeo
     // mediante el API WIN32
     // Autor......: PMR 1999
     //-----------------------------------------
     #include <windows.h>
     //----------Declaración de la clase
     class TWinSerCom
     {
     protected :
        int iNumCom;       // Número de puerto
        bool lOpen;        // Abierto ?
        HANDLE   hCom;     // Manejador del puerto
        DCB      sComCfg; // Estructura configuración
         COMSTAT sComSta; // Estado del puerto
         DWORD    dwErr;    // Errores del puerto
         DWORD    dwTamBufIn;
         DWORD    dwTamBufOut;
         DWORD    dwEvtMask; // Máscara 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 versión de DOS,
disponemos la siguiente tabla muestra un resumen del interface.

             INTERFACE DE LA CLASE CWINSERCOM
Método              Descripción
TWinSerCom()        Constructor, requiere un número 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 definición de la clase que es la siguiente

      //----------------------------------------
      // Fichero....: PORTCOM.CPP
      // Descripción: Definición 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 configuración
   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 configuración
   if (!SetCommState(hCom,&sComCfg))
      break;
   if (!SetupComm(hCom,dwTamBufIn,dwTamBufOut))
      break;
   // Configurar TIMEOUTS
   // Para que la operación 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;
};
//--------------- Transmisión
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;
};
//-------------Recepción
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;
};
//----------- Transmisión 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;
};
//-------------Recepción 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 todavía 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 número 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 método Open() se encarga de abrir el puerto y
configurarlo a 9600-N-8-1 ,además se configura el TIMEOUT para que las funciones de lectura
y escritura finalicen inmediatamente. El método Close() se encarga de cerrar el puerto, puesto
que este método 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 automática cuando finalice el programa. El método TxByte() se encarga de escribir un
BYTE en el BUFFER del transmisor, si la función no falla retornará TRUE. El método RxByte()
se encarga de leer del BUFFER del receptor un único byte. El método TxCad() recibe un
puntero a un BUFFER con la cadena a transmitir y la escribe en el BUFFER del transmisor sin
transmitir el carácter de terminación de cadena ‘\0’. El método RxCad() requiere un BUFFER
donde alojar los datos y el número de bytes que se desean leer, el método testea el número de
bytes pendientes de ser leidos cbInque y si son suficientes se leen. El método LeeIde()
permite leer el idendtificador del puerto con el que se trabaja, esto es muy util si deseamos
utilizar cualquier otra función del API de sobre un puerto ya abierto por la clase. Los métodos
relacionados con los eventos se verán 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
sesión 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 pulsación 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
depositarán los objetos que se muestran en la figura y que se pueden extrar de la sección
published en la declaración 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 declaración de la clase, lo único que se ha tenido que escribir es la declaración 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 declaración de la clase PORTCOM.H. Además,
en el proyecto hay que añadir la unidad PORTCOM.CPP donde se encuentra la definición 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 algún nuevo byte. En el formulario UNIT1.CPP escribimos el
siguiente código.

     //----------------------------------------
     // Fichero....: UNIT1.CPP PROYECTO SERCHAT.CPP
     // Descripción: 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 rádio se ha asociado un código para abrir el puerto
correspondiente. Mediante el evento OnTimer() se sondea periodicamente el estado del puerto
y si hay algún dato recibido. Si se recibe el retorno de carro ‘\r’ se añade una línea al objeto que
muestra el texto remoto, cualquier otro caracter es añadido a la última línea mediante la
propiedad Lines del objeto Memo2. En el evento OnKeyPress() del objeto que visualiza el
texto local Memo1 se controla cada pulsación de tecla, si la transmisión falla se mostrará un
mensaje en pantalla.


10.7. ENVIAR Y RECIBIR TRAMAS

La siguiente aplicación 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 botón BitBtn1 se enviarán 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
número 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 botón enviar. Para probar este ejemplo seguimos
los pasos indicados en el ejemplo anterior, la declaración 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 declaración del puntero pCom y el prototipo de un
método que se utilizará para refrescar los LEDS, ya que en este ejemplo no disponemos de
ningún timer.

     //-----------------------------------------
     //Programa: Unit1 del proyecto SERTRAMA.cpp
     //Desc....: Envio y recepción 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 definición de la clase observar como el método RefrescaBotones() se llama de forma
manual cada vez que seleccionamos un puerto. El método 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 método TxCad() de la clase de
comunicaciones, a continuación 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 función del
API de WINDOS GetTickCount() .


                              GetTickCount()
                        DWORD GetTickCount(VOID)
ARG. ENTRADA        DESCRIPCIÓN
void                NINGUNO
TIPO SALIDA         DESCRIPCIÓN
DWORD               Número de milisegundos transcurridos desde que se inició el
                    sistema operativo.

Mediante un bucle para controla el tiempo y una variable tipo lógica 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 envíe un mensaje cuando se produza
algunos de los eventos que se muestran en la siguiente tabla.


                  EVENTOS DEL PUERTO SERIE
Método            Descripción
EV_BREAK          Constructor, requiere un número de puerto
EV_CTS            Se ha producido un cámbio en la línea CTS
EV_DSR            Se ha producido un cámbio en la línea DSR
EV_ERR            Se ha producido uno de los siguientes errores:
                  CE_FRAME,CE_OVERRUN,CE_RXPARITY.
EV_RING           Se ha activado la línea RING
EV_RLSD           Se ha producido un cambio en la línea DCD
EV_RXCHAR         Hay datos pendientes de ser leiodos en el buffer , o
                  acaba de llegar al puerto un nuevo carácter
EV_RXFLAG         Acaba de llegar al puerto el carácter especificado en el
                  campo EvtChar de la estructura DCB
EV_TXEMPTY        Se acaba de producir la transmisión del último carácter
                  pendiente.

El evento RLSD es util cuando se realizan comunicaciones con MODEM y se desea controlar la
posible pérdida de portadora por errores de transmisión. El evento RXCHAR sirve para que el
sistema avise de hay datos pendientes de ser leidos en el buffer,si el tamaño del BUFFER del
receptores uno se producirá en cada carácter que llegue al puerto, no obstante, leer carácter a
carácter los bytes que van llegando al puerto es muy poco eficaz es mejor leer los datos
recibidos por bloques a través del BUFFER de entrada. El evento RXFLAG es muy util para
recibir tramas que finalizan con un determinado carácter: suponer que deseamos recibir tramas
que finalizan con el carácter 0x0D. Si al abrir el puerto configuramos el miembro EvtChar de la
estructura DCB con dicho carácter 0x0D se producirá un evento cada vez que se reciba una
trama completa.
En el API para el puerto serie existen trés funciones de aplicación GetCommMask(),
SetCommMask() y WaitCommEvent(). Las dos primeras permiten leer o fijar la máscara 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        DESCRIPCIÓN
hFile               Manejador del puerto o Handle devuelto por CreateFile()
LPDWORD             Puntero a un DWORD donde la función depositará la máscara
                    de eventos.
TIPO SALIDA         DESCRIPCIÓN
BOOL                TRUE si la función se ejecutó correctamente.




                               SetCommMask()
           BOOL SetCommMask(HANDLE hFile,DWORD lpEvtMask)
ARG. ENTRADA        DESCRIPCIÓN
hFile               Manejador del puerto o Handle devuelto por CreateFile()
DWORD               Un DWORD con la nueva máscara de eventos que se desea
                    asociar al puerto
TIPO SALIDA         DESCRIPCIÓN
BOOL                TRUE si la función se ejecutó correctamente.



El siguiente programa configura la máscara de eventos para que se añada el evento EV_ERR

        DWORD dwEvt; // Máscara 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 máscara de eventos es necesario utilizar la función
WaitCommEvent() para esperar que se produzca el evento deseado, esta función finaliza
cuando se produce alguno de los eventos programados.


                               WaitCommEvent()
          BOOL WaitCommEvent(HANDLE hFile,DWORD lpEvtMask)
ARG. ENTRADA        DESCRIPCIÓN
hFile               Manejador del puerto o Handle devuelto por CreateFile()
LPDWORD             Puntero a un DWORD con la máscara de los eventos que hace
                    que finalice la función
LPOverlapped        Puntero a una estructura de sobrecarga (0 si no se utiliza)
TIPO SALIDA         DESCRIPCIÓN
BOOL                TRUE si la función se ejecutó correctamente.



El siguiente ejemplo es una versión bajo windows del contador serie que se vió en MS-DOS, en
la figura, la ventana DOS transmite a través del puerto COM1 y la aplicación WINDOWS recibe
a través 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 depositarán los objetos
que se muestran en la figura y que se pueden extrar de la sección published en la declaración
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 librería RxLib y que se utiliza
para el número de bytes que se desean leer. Como contador se ha utilizado el objeto Label1 al
que se le ha cambiado el tamaño de la fuente para hacerlo gigante. En el proyecto abrá que
incluir además la unidad PORTCOM.CPP con la definición de la clase de comunicaciones. La
definición de la clase se encuentra en UNIT1.CPP y es la siguiente.

     //----------------------------------------
     // Fichero....: UNIT1.CPP
     // Descripción: 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 máscara 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 botón se ejecuta el método
Button1Click el cual abre el puerto seleccionado y configura la máscara de eventos para
detectar los bytes que se reciban o los errores que se produzcan. Cuando se alcanza la función
WaitCommEvent() el programa se queda congelado esperando uno de los dos eventos
programados, a continuación 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 recepción se utiliza ClearCommError() para leer el tipo de error. Si se programó el
miembro fAbortOnError al abrir el puerto es necesario llamar a esta función 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 máscara 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 solución a este problema pasa por utilizar subprocesos,hilos o treats en los
programas.

10.9. PROCESOS Y SUBPROCESOS

El API WIN32 permite la creación de subprocesos, hilos o Threads esta es una capacidad de
los sistemas operativos multitarea de que las aplicaciones en ejecución (procesos) puedan
crear subprocesos que se ejecutan de forma independiente. Esta técnica 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 específica. Por ejemplo, el WORD de
Microsoft puede tener activado los subprocesos del corrector ortográfico o del corrector
gramatical, mientras escribimos los correctores se estan ejecutando en paralelo con la edición
del texto.

                  FIGURA 8. PROCESOS EN EJECUCIÓN
Del mismo modo que en el escritorio de WINDOWS podemos terner abiertos varios programas
(procesos) simultáneamnete, dentro de una aplicación podremos crear tantos subprocesos
como necesitemos. Los procesos permiten por tanto implementar la multitarea a nivel de
programa. No hay que abusar del número 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                  Descripción
TThread()               Es el contructor de la clase, admite un parámetro
                        lógico para indicar si el subproceso se crea activado
                        o no
Execute()               Código que se ejecutará como si fuese una
                        aplicación independiente. El constructor de la clase
                        llama a este método de forma automática.
Suspend()               Suspende temporalmente la ejecución de un proceso.
                        En este estado el proceso no hace uso del
                        microprocesador.
Resume()                Reanuda la ejecución 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
                        automáticas todos sus subprocesos
Synchronize()           Permite ejecutar una función 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 añadir una nueva unidad con File-New-
ThreadObject .

Como ejemplo de subprocesos vamos a crear una aplicación 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 continuación 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 continuacuón.


      // Fichero: Unit2.h
      // Desc...: Definición 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...: Declaración 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 método Execute(). Al
observar que el cuerpo de este método se puede pensar que la aplicación se quedará colgada
al ser un bucle infinito, esto sería cierto si se ejecutase ese código en el hilo principal de la
aplicación. Como se encuentra en un subproceso el código se está ejecutando en paralelo con
el código del programa principal. La línea Synchronize(MostrarCnt) permite hacer que se
muestre el contador en la pantalla a través 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
declaración de la clase Form1 e inicializarlo al crear el formulario, después asignamos los
métodos a los botones según se desprende del código.

     // Fichero: Unit1.cpp
     // Desc...: Declaración 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...: Definción 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 ejecución, al activar el subproceso veremos el contador funcionando y al mismo
tiempo podemos manejar el ratón, editar el control de edición, pulsar un botón etc. Hemos
contruido una aplicación 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. RECEPCIÓN EN SEGUNDO PLANO

Los subprocesos combinados con los eventos es el mecanismo más rápido y eficaz para
realizar comunicaciones a través del puerto serie, mientras que el proceso principal atiende la
interface del usuario, un proceso en segundo plano atiende al puerto: monitorización, lectura,
escritura etc. En este ejemplo se va ha diseñar 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
recepción 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 carácter 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 número de teléfono
inexistente con el comando ATDP y después de unos segundos se ha producido la respuesta
NO CARRIER indicando que en el otro extremo no existe ninguna portadora.
Para poner en práctica este programa hay que crear un proyecto nuevo sobre el que
insertaremos los componentes que se desprenden de UNIT1.H.

     //--------------------------------------------
     // Fichero....: Unit1.H
     // Descripción: Declaración 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
      // público 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 información: ButOpen,
ButClose, ButEnv, oMem etc. Se ha añadido un puntero pCom para manejar las
comunicaciones y un puntero poSerRx para manejar el subproceso. El buffer donse se
almacenarán los datos acBuf que se reciban y el objeto pCom se han puesto en la sección
pública para permitir el acceso desde el subproceso a estos objetos. En el proyecto hay que
incluir la unidad PORTCOM.CPP node se encuentra la definición del la clase TWinSerCom
necesario para el objeto pCom. La definición de la clase se encuentra en UNIT1.CPP

     //-------------------------------------------
     // Fichero....: Unit1.CPP
     // Descripción: Definición 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 máscara de eventos
 if (!pCom->SetEventos(EV_RXCHAR|EV_ERR))
   {
   oMem->Lines->Add("ERROR al configurar");
   break;;
   }
 // Activar el proceso en segundo plano
 // para la recepción 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;
     // Añado 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 botón ButOpen se abre el
puerto y se activa el subproceso, a partir de ese momento cualquier carácter que llegue al
puerto será mostrado en el control memo. Observar que cuando se abre el puerto se programa
la máscara de eventos de la clase TWinSerCom mediante el método SetEventos() . Al pulsar
el botón ButClose se cierra el puerto y se suspende el subproceso. Al pulsar el botón ButEnv
se lee el texto que se encuentra en el control oEdi y se envia al puerto mediante el método
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
     // Descripción: Definición 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
     // Descripción: Declaración 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)
     {
     }
     //---------Código 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 método execute() de la clase disponde de un bucle infinito que se ejecuta en un subproceso
en paralelo con el proceso principal, en este método se esperan que ocurran los eventos
programados mediante el método 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 continuación se muestra el archivo de
proyecto final SEREVT2.MAK que queda como resultado .

     // Fichero SEREVT2.MAK
     // Descripción: 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 pequeño programa que la use para enviar y recibir un
carácter.

Los ejercicios planteados en el capítulo 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 más
habituales.

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




                                   fin del capítulo--

				
DOCUMENT INFO
Shared By:
Categories:
Tags:
Stats:
views:210
posted:8/31/2012
language:Unknown
pages:34