Introduccin
Contenido
FUNCIONES EL API WIN32 PARA COMUNICACIONES SERIE
LNEAS DE CONTROL DE FLUJO
BUFFER DEL TRANSMISOR Y DEL RECEPTOR
LEER Y ESCRIBIR EN EL PUERTO
CLASE TWinSerCom PARA TRANSMITIR Y RECIBIR CON WIN32
UN CHAT BAJO WINDOWS
ENVIAR Y RECIBIR TRAMAS
CONTROL DEL PUERTO MEDIANTE EVENTOS
PROCESOS Y SUBPROCESOS
RECEPCIN EN SEGUNDO PLANO
La utilizacin del las funciones del API WIN32 para trabajar con el puerto serie garantiza la
portabilidad del cdigo entre distintas versiones de WINDOWS (95/98/NT) y la estabilidad de
los programar que desarrollemos. La lista de funciones y estructuras que permiten programar el
puerto serie son ms de dos docenas, se irn explicando segun se necesiten. Trabajar con un
puerto serie se asemeja mucho al trabajo con ficheros mediante flujos, al trabajar con un puerto
serie se pueden distinguir 4 fases.
1.- Operacin de apertura: sirve para obtener un manejador o handle y comprobar si el sistema
operativo ha podido tomar el control del dispositivo para nuestra aplicacin.
2.- Configuracin: Sirve par establecer los parmetros de la comunicacin: velocidad, paridad
etc as como el tipo de acceso: mediante sondeo o mediante eventos.
3.- Acceso al puerto para leer o escribir en el: Hay que tener en cuenta que el acceso al puerto
siempre se realiza a travs de BUFFER uno para la transmisin (escritura) y otro para la
recepcin (lectura).
4.- Cierre del puerto para que otros programas puedan hace uso de l.
Las funciones que se irn presentando a continuacin han sido resumidad para dejar slo
aquellos parmetros que determinan el proceso de comunicacin.
Para abrir el puerto se utiliza la funcin CreateFile() , esta funcin es una funcin genrica que
permite acceder a recursos de naturaleza diversa: puertos de comunicaciones, ficheros,
directorios, discos, consolas etc.
CreateFile()
HANDLE CreateFile(
LPCTSTR lpFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDistribution,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile);
Si se desea conocer los tipos de datos definidos en WINDOWS para saber exactamente a que
tipos bsicos equivalen bastar con revisar el fichero WINDEF.H que se encuentra en el
directorio INCLUDE de C++ BUILDER.
..
HADLE hComPor;
hComPor=CreateFile(COM1,
GENERIC_READ|GENERIC_WRITE,
0,0,OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,0 );
if (hComPor==INVALID_HANDLE_VALUE)
{
//Error de apertura del puerto
}
Una vez finalizado el trabajo con el puerto hemos de cerrar su manejador para que el sistema
operativo libere los recursos que estamos ocupando, esto se hace mediante la funcin
CloseHandle(), esta funcin devuelve TRUE si el cierre del dispostivo se hizo correctamente,
es importante no tratar de cerrar un manejador no vlido, es decir un manejador no inicializado
correctamente por CreateFile() .
CloseHandle()
BOOL CloseHandle(HANDLE hObject);
ARG. ENTRADA DESCRIPCIN
hObject Manejador o handle genrico.
TIPO SALIDA DESCRIPCIN
BOOL TRUE si la funcin se ejecut correctamente.
La estructura anterior permite un control total sobre la configuracin del puerto: tipo de SDU,
tipo de control de flujo, parmetros del control de flujo etc. Para trabajar con la estructura DCB
disponemos de trs funciones GetCommState() para leer la configuracin actual del puerto,
SetCommState() para configurar el puerto y BuildCommDCB() para configurar el puerto
utilizando el estilo clsico del comando MODE del DOS.
GetCommState()
BOOL GetCommState(HANDLE hFile,LPDCB lpDCB);
ARG. ENTRADA DESCRIPCIN
hFile Manejador del puerto o Handle devuelto por CreateFile()
lpDCB Puntero a una estructura de tipo DCB donde se recibir la
configuracn actual del puerto.
TIPO SALIDA DESCRIPCIN
BOOL TRUE si la funcin se ejecut correctamente.
SetCommState()
BOOL SetCommState(HANDLE hFile,LPDCB lpDCB)
ARG. ENTRADA DESCRIPCIN
hFile Manejador del puerto o Handle devuelto por CreateFile()
lpDCB Puntero a una estructura de tipo DCB que se usar para
configurar el puerto.
TIPO SALIDA DESCRIPCIN
BOOL TRUE si la funcin se ejecut correctamente.
BuildCommDCB()
BOOL BuildCommDCB(LPCTSTR lpDef,LPDCB lpDCB)
ARG. ENTRADA DESCRIPCIN
lpDef Puntero a una cadena al estilo del comando MODE con la
configuracin a fijar, por ejemplo:
baud=9600 parity=N data=8 stop=1
lpDCB Puntero a una estructura de tipo DCB donde se recibir la
configuracin completa del puerto.
TIPO SALIDA DESCRIPCIN
BOOL TRUE si la funcin se ejecut correctamente.
Para probar la configuracin del puerto usaremos una aplicacin de cnsola que configure el
puerto a 9600-N-8-1. Mediante Proyect-New-Console App creamos un nuevo proyecto en un
nuevo directorio llamado PROYECT1.CPP. Para poder tener acceso a las funciones del API de
windows ser necesario incluir el fihero WINDOWS.H .
//-----------------------------------
#include <vcl\condefs.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <iostream.h>
#pragma hdrstop
//-----------------------------------
USERES("Project1.res");
//-----------------------------------
int main(int argc, char **argv)
{
BOOL lOk;
HANDLE hCom; // Manejador del puerto
DCB sComCfg; // Estructura DCB
char cTec;
while (true)
{
// Abrir el puerto
hCom=CreateFile("COM1",
GENERIC_READ|GENERIC_WRITE,
0,0,OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,0);
// Si error finalizar
if (hCom==INVALID_HANDLE_VALUE)
{
cout<<"ERROR al abrir el puerto";
cout<<GetLastError();
break;
}
// Leer la configuracin actual
lOk=GetCommState(hCom,&sComCfg);
if (!lOk)
{
cout<<"ERROR al leer la configuracin";
cout<<GetLastError();
break;
};
// Cambiar la configuracin a 9600-N-8-1
sComCfg.BaudRate=CBR_9600;
sComCfg.ByteSize=8;
sComCfg.Parity=NOPARITY;
sComCfg.StopBits=ONESTOPBIT;
// Escribir la nueva configuracin
lOk=SetCommState(hCom,&sComCfg);
if (!lOk)
{
cout<<"ERROR al escribir la configuracin";
cout<<GetLastError();
break;
};
cout<<"\n El puerto est abierto";
cout<<"\n y configurado a 9600-N-8-1";
cout<<"\n Pulse una tecla";
cin>>cTec;
// Salir del bucle principal
break;
}
if (hCom!=INVALID_HANDLE_VALUE)
CloseHandle(hCom);
return 0;
}
Este empieza abriendo el puerto, a continuacin se lee se lee la configuracin actual mediante
GetCommState(), se cambian los miembros de la estructura que se necesitan y se graba la
nueva configuracin en el puerto mediante SetCommState() . El bucle principal se utiliza para
cancelar la ejecucin del programa ante cualquier error. Finalmente se cierra el puerto slo si
se abri correctamente.
La funcin GetLastError() sirve para obtener el cdigo del error que se ha producido, estos
cdigos de error se encuentran definidos en el fichero WINNT.H .
GetLastError()
DWORD GetLastError(VOID)
ARG. ENTRADA DESCRIPCIN
No hay
TIPO SALIDA DESCRIPCIN
DWORD Cdigo del ltimo error producido
Para utilizar la funcin BuilCommDCB() se deber de declarar una estructur DCB y pasarla
como parmetro por referencia a la funcin. La funcin rellenar los campos de la estructura y
la podremos utilizar entonces para configurar el puerto con SetCommState().
..
HANDLE hCom; // Manejador del puerto
DCB sComCfg; // Estructura DCB
Como ejemplo de uso, el siguiente cdigo activa y desactiva la lnea RTS cada segundo.
Para suspender y reanudar la transmisin se puede utilizar la funcin anterior con los
parmetros SETBREAK y CLRBREAK o bien utilizar las funciones equivalentes
SetCommBreak() y ClearCommBreak(), ambas slo requieren el manejador del puerto y
retornan TRUE si la operacin se complet. Si se desean leer las lneas de estado que
proceden del DCE se puede usar la funcin GetCommModemStatus().
GetCommModemStatus()
BOOL GetCommModemStatus(HANDLE hFile,LPDWORD lpModemStat);
ARG. ENTRADA DESCRIPCIN
hFile Manejador del puerto o Handle devuelto por CreateFile()
lpModemStat Un puntero a una variable DWORD donde se escribir la
configuaracin del registro de estado del mode, segn los
siguientes valores.
MS_CTS_ON La seal CTS est activa
MS_DSR_ON La seal DSR est activa
MS_RING_ON La seal RING est activa
MS_RLSD_ON La seal CD est activa
TIPO SALIDA DESCRIPCIN
BOOL TRUE si la funcin se ejecut correctamente.
El siguiente cdigo detecta si el DTE tiene un DCE conectado y preparado para aceptar datos.
..
DWORD dwEst;
.. Abrir y configurar el puerto
if (GetCommModemStatus(hCom,&dwErr) )
{
if ( (dwErr&MS_CTS_ON) && (dwErr&MS_DSR_ON) )
cout<<Hay un DCE preparado;
}
.. Cerrar el puerto
Se puede leer ms informacin sobre el estado del puerto serie y comprobar si se ha producido
un error y el tipo de error es ClearCommError() . Esta funcin adems de leer el tipo de error
que se ha producido sirve para resetear el flag de error del puerto, en determinados modos de
funcionamiento (fAbortOnError de la estructura DCB), el puerto deja de trabajar hasta que no
se llama a estra funcin despus de haberse producido un error.
ClearCommError()
BOOL ClearCommError(HANDLE hFile,
LPDWORD lpErrors,
LPCOMSTAT lpStat );
ARG. ENTRADA DESCRIPCIN
hFile Manejador del puerto o Handle devuelto por CreateFile()
lpErrors Puntero a una variable DWORD donde se recibir el cdigo del
error.
CE_BREAK Detectado corte en la lnea
CE_FRAME Detectado error de trama
CE_IOE Detectado error de E/S
CE_MODE Manejador hFile especificado no vlido.
CE_OVERRUN Se han perdido bytes al transmitir.
CE_RXOVER Overflow en el buffer de entrada.
CE_RXPARITY Detectado error de paridad.
CE_TXFULL Buffer del transmisor est lleno.
lpStat Puntero a una estructura de tipo COMSTAT donde se podr
leer el estado del puerto.
TIPO SALIDA DESCRIPCIN
BOOL TRUE si la funcin se ejecut correctamente.
..
COMSTAT sComSta;
DWORD dwErr;
..
// Abrir y configuar el puerto
if( ClearCommError(hCom,&dwErr,&sComSta))
{
cout<<"Bytes en BUFFER RX:"<<sComSta.cbInQue;
if ( (dwErr&CE_BREAK)==CE_BREAK)
cout<<"\n Detectado corte en de lnea";
}
// Cerrar el puerto
Todas las comunicaciones mediante el API de WINDOWS se realizan a travs del BUFFERS
tanto para la transmisin como para la recepcin. Una vez que el puerto ha sido abierto y
configurado cualquier carcter que llegue al puerto ser almacenado en el buffer del receptor
de forma automtica por el sistema operativo, no importa lo que est haciendo nuestro
programa o cualquier otro que se encuentre en ejecucin. Del mismo modo, cuando
escribamos en el puerto se har a travs del BUFFER del transmisor, el programa escribir en
l y el sistema operativo ir enviando los datos de forma totalmente automtica siempre que el
control de flujo implementado lo permita (Si se habilita control de flujo RTS-CTS, ser necesario
que DCE mantenga activa su lnea CTS para que la transmisin se produzca).
Cuando se abre el puerto se crean los buffers de un tamao predeterminado, se puede conocer
el tamao actual de los BUFFERS mediante la funcin GetCommPropeties(), la cual
suministra una estructura COMMPROP con las propiedades del puerto dos de los campos de
esta estructura son dwCurrentTxQueue y dwCurrentRxQueue que informan del tamao total
del BUFFER del transmisor y del receptor respectivamente, esta funcin requiere que el puerto
est abierto y configurado.
GetCommProperties()
BOOL GetCommProperties(HANDLE hFile,LPCOMMPROP lpCommProp)
ARG. ENTRADA DESCRIPCIN
hFile Manejador del puerto o Handle devuelto por CreateFile()
lpCommProp Puntero a un estructura COMMPROP donde la funcin
depositar informacin sobre las caractersticas del puerto.
TIPO SALIDA DESCRIPCIN
BOOL TRUE si la funcin se ejecut correctamente.
Podemos monitorizar los BYTES pendientes de ser enviados o los que esten pendientes de
ser leidos mediante los campos cbInQue y cbOutQue de la estructura COMSTAT vista
anteriormente. Podemos adems cambiar el tamao de estos BUFFERS mediante la funcin
SetupComm().
SetupComm()
BOOL SetupComm(HANDLE hFile,DWORD dwInQueue,DWORD dwOutQueue)
ARG. ENTRADA DESCRIPCIN
hFile Manejador del puerto o Handle devuelto por CreateFile()
dwInQueue Tamao que se desea para el BUFFER del receptor
dwOutQueue Tamao que se desea para el BUFFER del transmisor
TIPO SALIDA DESCRIPCIN
BOOL TRUE si la funcin se ejecut correctamente.
Esta funcin se llamar normalmente despus de la apertura del puerto antes de cualquier
operacin de lectura o escritura. El siguiente ejemplo es una aplicacin de cnsola que muestra
como usar estas funciones.
//-----------------------------------
#include <vcl\condefs.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <iostream.h>
#include <conio.h>
#pragma hdrstop
//-------------------------------------
USERES("Project1.res");
//-------------------------------------
int main(int argc, char **argv)
{
HANDLE hCom; // Manejador del puerto
DCB sComCfg;// Estructura configuracin
COMMPROP sComPro;// Estructura propiedades
COMSTAT sComSta;// Estado del puerto
DWORD dwErr; // Estado del error
En el ejemplo se configura el puerto para un BUFFER del receptro a 2048 bytes y el BUFFER
del transmisor a 1024 BYTES. Este modo de operacin puede ser un problema cuando
queremos enviar un carcter de control de forma inmediata, en este caso el dato sera aadido
al final del BUFFER y se transmitira despus de los caracteres que estuvieran pendienetes.
Para el envio inmedianto de un carcter se dispone de la funcin TransmitCommChar().
TransmitCommChar()
BOOL TransmitCommChar(HANDLE hFile,char cChar);
ARG. ENTRADA DESCRIPCIN
hFile Manejador del puerto o Handle devuelto por CreateFile()
cChar Carcter a envias
TIPO SALIDA DESCRIPCIN
BOOL TRUE si la funcin se ejecut correctamente.
Si en algn momento se desaear borrar los bytes que quedan remanentes en alguno de los dos
BUFFERS se pude utiliza la funcin PurgeComm() , esta operacin hace que se pierda el
contendido del BUFFER del transmisor sin ser transmitido realmente o que se pierda el
contenido del BUFFER del receptor sin que sea guardadp por el programa.
PurgeCommr()
BOOL PurgeComm(HANDLE hFile,DWORD dwFlags);
ARG. ENTRADA DESCRIPCIN
hFile Manejador del puerto o Handle devuelto por CreateFile()
dwFlags Es uno o combinacin de los siguiente valores:
PURGE_TXABORT o PURGE_TXCLEAR para limpia el
BUFFER del transmisor
PURGE_RXABORT o PURGE_RXCLEAR para limpiar el
BUFFER del receptor
TIPO SALIDA DESCRIPCIN
BOOL TRUE si la funcin se ejecut correctamente.
Si queremos vaciar el BUFFER del transmisor, pero garantizando que se transmite todo su
contenido deberemos de usar la funcin FlushFileBuffers() .
WriteFile()
BOOL WriteFile(HANDLE hFile,
LPCVOID lpBuffer,
DWORD nNumberOfBytesToWrite,
LPDWORD lpNumberOfBytesWritten,
LPOVERLAPPED lpOverlapped);
ARG. ENTRADA DESCRIPCIN
hFile Manejador del puerto o Handle devuelto por CreateFile()
lpBuffer Puntero al buffer donde la funcin leer los datos que
escribir en el puerto.
nNumberOfByteToWrite Nmero de bytes que se escribirn en el puerto
lpNumberOfByteWritten Puntero a una DWORD donde la funcin indicar los datos
que realmente se han escrito.
lpOverlapped Puntero a una estructura OVERLAPPED para acceso a
especial a puerto.
TIPO SALIDA DESCRIPCIN
BOOL TRUE si la funcin se ejecut correctamente.
Los dispositivos sobre los que se utilizan estas funciones soportan el control de TIMEOUT o
tiempo mximo de lectura y escritura. Por ejemplo, un dispositivo puede ser configurado para
que la operacin de lectura dure como mximo 100 ms. esto implica que si la operacin se
prolonga ms del tiempo permitido la funcin finalizar normalmente, es decir retornar
TRUE.Para determinar si una opeacin de lectura/escritur finaliz por un timeout habra que
comparn los bytes que se queras leer/escribir con los bytes realmente leidos/escritos.
GetCommTimeoust()
BOOL GetCommTimeouts(HANDLE hFile,LPCOMMTIMEOUTS lpCommTimeouts)
ARG. ENTRADA DESCRIPCIN
hFile Manejador del puerto o Handle devuelto por CreateFile()
lpCommTimeouts Puntero a una estructura de tipo COMMTIMEOUTS que se
usar para leer la configurar del puerto.
TIPO SALIDA DESCRIPCIN
BOOL TRUE si la funcin se ejecut correctamente.
SetCommTimeoust()
BOOL SetCommTimeouts(HANDLE hFile,LPCOMMTIMEOUTS lpCommTimeouts)
ARG. ENTRADA DESCRIPCIN
hFile Manejador del puerto o Handle devuelto por CreateFile()
lpCommTimeouts Puntero a una estructura de tipo COMMTIMEOUTS que se
usar para escribir la configurar del puerto.
TIPO SALIDA DESCRIPCIN
BOOL TRUE si la funcin se ejecut correctamente.
El siguiente programa se utiliza una aplicacin de cnsola para transmitir por el puerto una
cadena de caracteres.
//-------------------------------------
#include <vcl\condefs.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <iostream.h>
#include <conio.h>
#pragma hdrstop
//-------------------------------------
USERES("Project1.res");
//-------------------------------------
int main(int argc, char **argv)
{
HANDLE hCom; // Manejador del puerto
DCB sComCfg;// Estructura configuracin
COMMPROP sComPro;// Estructura propiedades
COMSTAT sComSta;// Estado del puerto
DWORD dwErr; // Estado del error
COMMTIMEOUTS sTimOut;
BYTE acBuf[32]; // Un buffer
DWORD dwBytWri; //
DWORD dwLen;
Para leer del puerto podemos utilizar un cable NULL-MODEM con FEED-BACK , en este caso
lo que escribamos en el puerto ser leido a travs del mismo puerto. En base al programa
anterior modificamos el bucle de transmisin y escribimos lo siguiente.
En este ejemplo se transmite la cadena, despus se borra y se lee del puerto un nmero de
byte igual a la cadena que se envi. Finalmente, se le aade el carcter de fin de cadena \0
para convertir el buffer leido del puerto acBuf en una cadena.
<FIGURA 4. RECIBIR>
sTimOut.ReadIntervalTimeout=MAXDWORD;
sTimOut.ReadTotalTimeoutMultiplier=0;
sTimOut.ReadTotalTimeoutConstant=0;
sTimOut.WriteTotalTimeoutMultiplier=0;
sTimOut.WriteTotalTimeoutConstant=0;
sTimOut.WriteTotalTimeoutConstant=0;
En este caso, habr que revisar el proceso de lectura, ya que la funcin finalizar
inmediatamente despus de llamarla y puede ocurrir que no haya leido ningn dato. Otra
solucin par evitar este problema es comprobar que hay datos en el BUFFER del receptor
mediante el campo cbInQue de la estructura COMSTAT antes de usar ReadFile() . Si se opta
por utilizar la primera de estas tcnicas el programa quedar como se muestra a continuacin.
//--------------------------------------------
int main(int argc, char **argv)
{
HANDLE hCom; // Manejador del puerto
DCB sComCfg;// Estructura configuracin
COMMPROP sComPro;// Estructura propiedades
COMSTAT sComSta;// Estado del puerto
DWORD dwErr; // Estado del error
COMMTIMEOUTS sTimOut;
BYTE acBuf[32]; // Un buffer
BYTE acBufRx[32];
DWORD dwBytWri; // Bytes escritos
DWORD dwBytRea; // Bytes leidos
DWORD dwTotBytRea;
DWORD dwLen;
// Bucle para controlar errores
while (true)
{
// Abrir el puerto
hCom=CreateFile("COM1",
GENERIC_READ|GENERIC_WRITE,
0,0,OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,0);
// Si error finalizar
if (hCom==INVALID_HANDLE_VALUE) break;
// Leer configuracin actual
if (!GetCommState(hCom,&sComCfg)) break;
// Cambiar configuracin
sComCfg.BaudRate=CBR_9600;
sComCfg.ByteSize=8;
sComCfg.Parity=NOPARITY;
sComCfg.StopBits=ONESTOPBIT;
sComCfg.fRtsControl=RTS_CONTROL_ENABLE;
sComCfg.fDtrControl=DTR_CONTROL_ENABLE;
// Escribir nueva configuracin
if (!SetCommState(hCom,&sComCfg)) break;
// Configurar TIMEOUTS
// Para que la operacin de lectura
// finalice inmediatamente
sTimOut.ReadIntervalTimeout=MAXDWORD;
sTimOut.ReadTotalTimeoutMultiplier=0;
sTimOut.ReadTotalTimeoutConstant=0;
sTimOut.WriteTotalTimeoutMultiplier=0;
sTimOut.WriteTotalTimeoutConstant=0;
sTimOut.WriteTotalTimeoutConstant=0;
if (!SetCommTimeouts(hCom,&sTimOut)) break;
// Contruimos la cadena a enviar
strcpy(acBuf,"Hola Mundo");
dwLen=strlen(acBuf);
// Transmitimos la cadena
if (!WriteFile(hCom,acBuf,
dwLen,&dwBytWri,0)) break;
cout<<"\nTx..:"<<acBuf ;
strcpy(acBuf,"");
strcpy(acBufRx,"");
while (true)
{
if (!ReadFile(hCom,acBuf,
dwLen,&dwBytRea,0)) break;
// Si se ha leido algo
if (dwBytRea>0)
{
acBuf[dwBytRea]='\0';
// Cuento el total leido
dwTotBytRea=dwTotBytRea+dwBytRea;
// Encadeno la informacin leida
strcat(acBufRx,acBuf);
// Salir si se ley toda
if (dwTotBytRea>=dwLen) break;
}
}
cout<<"\nRx...:"<<acBufRx;
cout<<"\n Pulse una tecla";
getch();
// Salir del bucle principal
break;
}
// Cerrar el puerto
if (hCom!=INVALID_HANDLE_VALUE)
CloseHandle(hCom);
return 0;
}
Como se observa para recibir utilizamos llamadas repetidas a la funcin ReadFile() dentro de
un bucle, cuando una llamada lee algo del BUFFER se contruye una cadena en acBuf que se
va encadenado a la cadena total acBufRx . El bucle finaliza cuando se lee toda la cadena. Este
bucle presenta el problema de que se convierte en un bucle infinito si no se leen dwLen bytes
del puerto, esto se puede solucionar utilizando tcnicas de control de tiempo como las que se
vieron en la programacin DOS.
//----------------------------------------
// Fichero....:PORTCOM.H
// Descripcin:Declaracin clase TWINSERCOM
// Permite transmitir y recibir por sondeo
// mediante el API WIN32
// Autor......: PMR 1999
//-----------------------------------------
#include <windows.h>
//----------Declaracin de la clase
class TWinSerCom
{
protected :
int iNumCom; // Nmero de puerto
bool lOpen; // Abierto ?
HANDLE hCom; // Manejador del puerto
DCB sComCfg; // Estructura configuracin
COMSTAT sComSta; // Estado del puerto
DWORD dwErr; // Errores del puerto
DWORD dwTamBufIn;
DWORD dwTamBufOut;
DWORD dwEvtMask; // Mscara de eventos
COMMTIMEOUTS sTimOut;
public :
TWinSerCom(int iPor); // Constructor
~TWinSerCom(); // Destructor
bool Open(void); // Abrir puerto
bool Open(int iPor); // Abrir en otro
void Close(void); // Cerrarlo
bool IsOpen(void); // Leer estado
bool IsDCE(void); // Leer si DCE
bool RxByte(BYTE &bDat); // Tx BYTE
bool TxByte(BYTE bDat); // Rx BYTE
bool RxCad(char *pCad,DWORD dwLen); // Tx Cadena
bool TxCad(char *pCad); // Rx Cadena
DWORD LeerError(void); // Leer el error
bool SetEventos(DWORD dwDat);// Fijar eventos
DWORD EsperaEventos(void); // Esperar un evento
HANDLE LeeIde(void); // Leer Handle de COMx
};
//----------------------------------------
// Fichero....: PORTCOM.CPP
// Descripcin: Definicin clase TWinSerCom
// Autor......: PMR Julio 1999
//----------------------------------------
#include <vcl\vcl.h>
#pragma hdrstop
#include "PortCom.h"
//---------------------------- Constructor
TWinSerCom::TWinSerCom(int iPor)
{
lOpen=FALSE;
iNumCom=iPor;
dwTamBufIn=2048;
dwTamBufOut=1024;
};
//---------------------------- Destructor
TWinSerCom::~TWinSerCom(){Close();};
//-----------------Leer estado del puerto
bool TWinSerCom::IsOpen(void)
{return lOpen;}
//-----------------Leer manejador del puerto
HANDLE TWinSerCom::LeeIde(void)
{return hCom;}
//------------------Leer si DCE conectado
bool TWinSerCom::IsDCE(void)
{
bool lRes=FALSE;
while (true)
{
if (!lOpen) break;
if (!GetCommModemStatus(hCom,&dwErr))break;
if ((dwErr&MS_CTS_ON) && (dwErr&MS_DSR_ON))
lRes=TRUE;
break;
}
return lRes;
}
//------------------Leer si DCE conectado
DWORD TWinSerCom::LeerError(void)
{
ClearCommError(hCom,&dwErr,&sComSta);
return dwErr;
}
//------------------Fijar eventos
bool TWinSerCom::SetEventos(DWORD dwDat)
{
bool lRes=true;
dwEvtMask=dwDat;
if (!SetCommMask(hCom,dwEvtMask))
lRes=false;
return lRes;
}
//------------------Esperar evento
DWORD TWinSerCom::EsperaEventos(void)
{
// Esperar los eventos programados
// Aqu el proceso se quedar parado
// hasta que se produzca uno de los eventos
WaitCommEvent(hCom,&dwEvtMask,0);
return dwEvtMask;
}
//-------------------------Abrir el puerto
bool TWinSerCom::Open(int iPor)
{
if (lOpen) Close();
iNumCom=iPor;
return Open();
}
//-------------------------Abrir el puerto
bool TWinSerCom::Open(void)
{
char acCom[5]; // Para la cadena del puerto
while(TRUE)
{
// Si ya est abierto cancelar
if (lOpen) break;
lOpen=false;
// Abrir el puerto
sprintf(acCom,"COM%d",iNumCom);
hCom=NULL;
hCom=CreateFile(acCom,
GENERIC_READ|GENERIC_WRITE,
0,0,OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,0);
// Si falla cancelar
if (hCom==INVALID_HANDLE_VALUE) break;
// Leer CFG actual
if (!GetCommState(hCom,&sComCfg)) break;
// Cambiar configuracin
sComCfg.BaudRate=CBR_9600;
sComCfg.ByteSize=8;
sComCfg.Parity=NOPARITY;
sComCfg.StopBits=ONESTOPBIT;
sComCfg.fRtsControl=RTS_CONTROL_ENABLE;
sComCfg.fDtrControl=DTR_CONTROL_ENABLE;
// Escribir nueva configuracin
if (!SetCommState(hCom,&sComCfg))
break;
if (!SetupComm(hCom,dwTamBufIn,dwTamBufOut))
break;
// Configurar TIMEOUTS
// Para que la operacin de lectura
// finalice inmediatamente
sTimOut.ReadIntervalTimeout=MAXDWORD;
sTimOut.ReadTotalTimeoutMultiplier=0;
sTimOut.ReadTotalTimeoutConstant=0;
sTimOut.WriteTotalTimeoutMultiplier=0;
sTimOut.WriteTotalTimeoutConstant=0;
sTimOut.WriteTotalTimeoutConstant=0;
if (!SetCommTimeouts(hCom,&sTimOut))
break;
lOpen=TRUE;
break;
};
return lOpen;
};
//---------------------------Cerrar el puerto
void TWinSerCom::Close(void)
{
if (hCom!=INVALID_HANDLE_VALUE)
{
// Limpiar el BUFFER
PurgeComm(hCom,PURGE_TXCLEAR|PURGE_RXCLEAR);
// Cerrar el puerto
CloseHandle(hCom);
}
lOpen=FALSE;
return;
};
//--------------- Transmisin
bool TWinSerCom::TxByte(BYTE bDat)
{
DWORD dwBytWri; // Bytes escritos
if (lOpen)
{
// Transmitimos un BYTE
if (WriteFile(hCom,&bDat,1,&dwBytWri,0))
if (dwBytWri==1) return TRUE;
}
return FALSE;
};
//-------------Recepcin
bool TWinSerCom::RxByte(BYTE &bDat)
{
DWORD dwBytRea; // Bytes leidos
BYTE bDatRx; // Buffer para lo que se lea
if ( lOpen )
{
if (ReadFile(hCom,&bDatRx,1,&dwBytRea,0))
if (dwBytRea==1)
{
bDat=bDatRx;
return TRUE;
}
}
return FALSE;
};
//----------- Transmisin cadena
bool TWinSerCom::TxCad(char *pCad)
{
DWORD dwBytWri; // Bytes escritos
DWORD dwLen=strlen(pCad);
if (lOpen)
{
// Transmitimos la cadena
if (WriteFile(hCom,pCad,dwLen,&dwBytWri,0))
if (dwBytWri==dwLen) return TRUE;
}
return FALSE;
};
//-------------Recepcin cadena
bool TWinSerCom::RxCad(char *pCad,DWORD dwLen)
{
DWORD dwBytRea;
bool lRes=FALSE;
while (TRUE)
{
// Si el puerto est cerrado cancelo
if (!lOpen) break;
// Si falla la lectura del estado cancelo
if (!ClearCommError(hCom,&dwErr,&sComSta))
break;
// Si en el BUFFER no hay todava el
// nro de bytes que se desean leer cancelo
if (sComSta.cbInQue<dwLen) break;
if (!ReadFile(hCom,pCad,dwLen,&dwBytRea,0))
break;
// Si no leo el nmero de bytes que se piden
if (dwBytRea!=dwLen) break;
// Contruir una cadena
*(pCad+dwBytRea)='\0';
lRes=TRUE;
break;
}
return lRes;
};
El constructor se encarga de guardar el puerto inicial y de asignar unos valores por defecto el
BUFFER del transmisor y del receptor. El mtodo Open() se encarga de abrir el puerto y
configurarlo a 9600-N-8-1 ,adems se configura el TIMEOUT para que las funciones de lectura
y escritura finalicen inmediatamente. El mtodo Close() se encarga de cerrar el puerto, puesto
que este mtodo es llamado por el destructor de la clase, cada vez que se borre un objeto de la
clase se cerrar el puerto, esto es util ya que si nos olvidamos de cerrar el puerto se cerrar de
forma automtica cuando finalice el programa. El mtodo TxByte() se encarga de escribir un
BYTE en el BUFFER del transmisor, si la funcin no falla retornar TRUE. El mtodo RxByte()
se encarga de leer del BUFFER del receptor un nico byte. El mtodo TxCad() recibe un
puntero a un BUFFER con la cadena a transmitir y la escribe en el BUFFER del transmisor sin
transmitir el carcter de terminacin de cadena \0. El mtodo RxCad() requiere un BUFFER
donde alojar los datos y el nmero de bytes que se desean leer, el mtodo testea el nmero de
bytes pendientes de ser leidos cbInque y si son suficientes se leen. El mtodo LeeIde()
permite leer el idendtificador del puerto con el que se trabaja, esto es muy util si deseamos
utilizar cualquier otra funcin del API de sobre un puerto ya abierto por la clase. Los mtodos
relacionados con los eventos se vern posteriormente.
La clase del apartado anterior se puede utilizar para desarrollar un chat bajo WINDOWS como
el que se muestra en la figura siguiente.
Para probar este programa se pueden utilizar dos ordenador conectados mediante un cable
NULL-MODEM , un nico ordenador con un cable NULL-MODE con FEEDBACK y una nica
sesin del programa, o bien, como se muestra en la figura con un nico ordenador y un cable
NULL-MODEM conectado entre dos de los puetos libres del mismo ordenador.
Para empezar se debe seleccionar un puerto mediante los controles de la parte superio,si se
conectan cada ordenador puede escribir en la ventana local, cada pulsacin de tecla se
transmite al ordenador remoto que lo visualiza en la ventana remota. El LED de la parte
superior indica si el puerto est abierto o no, el led inferior indica si se detecta el DCE, en este
caso como se usa un cable NULL-MODEM el LED inferior indica la presencia del ordenador
remoto. Si se desar probar este programa se crear un nuevo proyecto en el que se
depositarn los objetos que se muestran en la figura y que se pueden extrar de la seccin
published en la declaracin de la clase que se encuentra en el fichero UNIT1.H.
..
#include "..\PortCom\PortCom.h"
..
class TForm1 : public TForm
{
__published:// IDE-managed Components
TPanel *Panel1;
TMemo *Memo1;
TPanel *Panel2;
TRadioButton *RadioButton1;
TRadioButton *RadioButton2;
TRadioButton *RadioButton3;
TRadioButton *RadioButton4;
TShape *Shape1;
TShape *Shape2;
TMemo *Memo2;
TTimer *Timer1;
TLabel *Label1;
TLabel *Label2;
void __fastcall RadioButton1Click(TObject *Sender);
void __fastcall RadioButton2Click(TObject *Sender);
void __fastcall RadioButton3Click(TObject *Sender);
void __fastcall RadioButton4Click(TObject *Sender);
void __fastcall Timer1Timer(TObject *Sender);
void __fastcall Memo1KeyPress(TObject *Sender, char &Key);
private: // User declarations
TWinSerCom *pCom;
public: // User declarations
__fastcall TForm1(TComponent* Owner);
};
//----------------------------------------
// Fichero....: UNIT1.CPP PROYECTO SERCHAT.CPP
// Descripcin: CHAT bajo windows
// Autor......: PMR - Julio 1999
//----------------------------------------
#include <vcl\vcl.h>
#pragma hdrstop
#include "Unit1.h"
//-----------------------------------------
#pragma resource "*.dfm"
TForm1 *Form1;
//-----------------------------------------
__fastcall TForm1::
TForm1(TComponent* Owner): TForm(Owner)
{
// Crear el objeto para las comunicaciones
pCom= new TWinSerCom(1);
Memo1->Clear();Memo2->Clear();
Memo2->Lines->Add("");
}
//-----------------------------------------
void __fastcall TForm1::
RadioButton1Click(TObject *Sender)
{pCom->Open(1);}
//-----------------------------------------
void __fastcall TForm1::
RadioButton2Click(TObject *Sender)
{pCom->Open(2);}
//-----------------------------------------
void __fastcall TForm1::
RadioButton3Click(TObject *Sender)
{pCom->Open(3);}
//-----------------------------------------
void __fastcall TForm1::
RadioButton4Click(TObject *Sender)
{pCom->Open(4);}
//-----------------------------------------
void __fastcall TForm1::
Timer1Timer(TObject *Sender)
{
BYTE bDat;
int iLin;
// LED de puerto abierto
if (pCom->IsOpen())
Shape1->Brush->Color=clRed;
else
Shape1->Brush->Color=clWhite;
// LED de DCE desconectado
if (pCom->IsDCE())
Shape2->Brush->Color=clRed;
else
Shape2->Brush->Color=clWhite;
// Si se ha recibido un BYTE
if(pCom->RxByte(bDat) )
{
// Si es retorno de carro
if(bDat=='\r')
{
Memo2->Lines->Add("");
iLin++;
}
else
{
iLin=Memo2->Lines->Count-1;
Memo2->Lines->Strings[iLin]=
Memo2->Lines->Strings[iLin]+char(bDat);
}
};
}
//--------------------------------------------
void __fastcall TForm1::
Memo1KeyPress(TObject *Sender, char &Key)
{
// Transmito
if (!pCom->TxByte(Key))
{
Application->MessageBox
("ERROR: No se ha podido transmitir",
"SerChat",MB_ICONERROR);
};
}
//----------------------------------------------
La siguiente aplicacin puede ser til cuando se desea enviar tramas de test a un dispositivo
conectado al puerto serie y se desea mnonitorizar su respuesta: por ejemplo PLC, SENSORES
ETC.
<FIGURA 6.TRAMAS>
En la pantalla se puede escribir una cadena en el objeto Memo1 de la parte superior, al pulsar
el botn BitBtn1 se enviarn la cadena, entonces se esper el tiempo fijado por el objeto
TrackBar1 (este objeto tiene fijadas sus propiedades Min=100 y Max=3000) para leer el
nmero de bytes que se indican en el objeto MaskEdit1. En el ejemplo se ha conectado un
cable NULL-MODEM con FEED-BACK y se ha escrito un texto de 10 caracteres , se ha fijado
bytes de respuesta en 4 y se ha pulsado tres botn enviar. Para probar este ejemplo seguimos
los pasos indicados en el ejemplo anterior, la declaracin de la clase en UNIT.H es la
siguiente.
En este caso hay que hay que escribir la declaracin del puntero pCom y el prototipo de un
mtodo que se utilizar para refrescar los LEDS, ya que en este ejemplo no disponemos de
ningn timer.
//-----------------------------------------
//Programa: Unit1 del proyecto SERTRAMA.cpp
//Desc....: Envio y recepcin de tramas
//Autor...: PMR 1999
//-----------------------------------------
#include <vcl\vcl.h>
#pragma hdrstop
#include "Unit1.h"
//-----------------------------------------
#pragma resource "*.dfm"
TForm1 *Form1;
//------------------------------------------
__fastcall TForm1::
TForm1(TComponent* Owner): TForm(Owner)
{
pCom= new TWinSerCom(1);
Memo1->Clear();Memo2->Clear();
}
//------------------------------------------
void __fastcall TForm1::
RadioButton1Click(TObject *Sender)
{pCom->Open(1);RefrescaBotones();}
//------------------------------------------
void __fastcall TForm1::
RadioButton2Click(TObject *Sender)
{pCom->Open(2);RefrescaBotones();}
//------------------------------------------
void __fastcall TForm1::
RadioButton3Click(TObject *Sender)
{pCom->Open(3);RefrescaBotones();}
//------------------------------------------
void __fastcall TForm1::
RadioButton4Click(TObject *Sender)
{pCom->Open(4);RefrescaBotones();}
//-------------------------------------------
void __fastcall TForm1::
RefrescaBotones(void)
{
// LED de puerto abierto
if (pCom->IsOpen())
Shape1->Brush->Color=clRed;
else
Shape1->Brush->Color=clWhite;
// LED de DCE desconectado
if (pCom->IsDCE())
Shape2->Brush->Color=clRed;
else
Shape2->Brush->Color=clWhite;
}
//------------------------------------------
void __fastcall TForm1::
BitBtn1Click(TObject *Sender)
{
{
bool lResOk; // Respuesta OK
char acBufTx[132];
char acBufRx[132];
char acMsg[200];
int iTamTrama;
DWORD tTimIni,tEsp;
while (true)
{
// Transmitir y esperar respuesta
if(!pCom->IsOpen())
{
Application->MessageBox
("ERROR: El puerto est cerrado",
"SerTrama",
MB_ICONERROR);
break;
}
// Convertit a cadena
strcpy(acBufTx,
Memo1->Lines->Strings[0].c_str());
if(!pCom->TxCad(acBufTx)) // Transmitir
{
Application->MessageBox(
"ERROR: No se ha podido transmitir",
"SerTrama",MB_ICONERROR);
break;
}
sprintf(acMsg,"Tx..:%s",acBufTx);
Memo2->Lines->Add(acMsg);
// Esperar respuesta
iTamTrama=MaskEdit1->Text.ToInt();
if (iTamTrama<1 ||iTamTrama>131) break;
// Leer tiempo
tEsp=TrackBar1->Position;
lResOk=false;
// Bucle de espera de respuesta
strcpy(acBufRx,"");
tTimIni=GetTickCount();
while(GetTickCount()-tTimIni<tEsp)
{
if(pCom->RxCad(acBufRx,iTamTrama))
{
lResOk=true;
break;
}
};
// Testeo si hubo error al recibir
if(!lResOk)
{
Application->MessageBox(
"ERROR: No se ha podido recibir",
"SerTrama",MB_ICONERROR);
break;
}
else
{
sprintf(acMsg,"Rx..:%s",acBufRx);
Memo2->Lines->Add(acMsg);
}
Memo1->Lines->Clear();
// Salir del bucle principal
break;
}
}
}
//---------------------------------------------------------------------------
GetTickCount()
DWORD GetTickCount(VOID)
ARG. ENTRADA DESCRIPCIN
void NINGUNO
TIPO SALIDA DESCRIPCIN
DWORD Nmero de milisegundos transcurridos desde que se inici el
sistema operativo.
Mediante un bucle para controla el tiempo y una variable tipo lgica lResOk determinamos si
en el tiempo permitido se ha producido la lectura correcta.
Los eventos del puerto serie son parecidos a las interruppciones en MS-DOS, se trata de no
tener que sondear el puerto para transmitir, recibir o controlar errores: que sea el puerto el que
avise al programa de estas situaciones mediante un evento o mensaje. Un programa puede
configurar el puerto para que el sistema operativo le enve un mensaje cuando se produza
algunos de los eventos que se muestran en la siguiente tabla.
El evento RLSD es util cuando se realizan comunicaciones con MODEM y se desea controlar la
posible prdida de portadora por errores de transmisin. El evento RXCHAR sirve para que el
sistema avise de hay datos pendientes de ser leidos en el buffer,si el tamao del BUFFER del
receptores uno se producir en cada carcter que llegue al puerto, no obstante, leer carcter a
carcter los bytes que van llegando al puerto es muy poco eficaz es mejor leer los datos
recibidos por bloques a travs del BUFFER de entrada. El evento RXFLAG es muy util para
recibir tramas que finalizan con un determinado carcter: suponer que deseamos recibir tramas
que finalizan con el carcter 0x0D. Si al abrir el puerto configuramos el miembro EvtChar de la
estructura DCB con dicho carcter 0x0D se producir un evento cada vez que se reciba una
trama completa.
GetCommMask()
BOOL GetCommMask(HANDLE hFile,LPDWORD lpEvtMask)
ARG. ENTRADA DESCRIPCIN
hFile Manejador del puerto o Handle devuelto por CreateFile()
LPDWORD Puntero a un DWORD donde la funcin depositar la mscara
de eventos.
TIPO SALIDA DESCRIPCIN
BOOL TRUE si la funcin se ejecut correctamente.
SetCommMask()
BOOL SetCommMask(HANDLE hFile,DWORD lpEvtMask)
ARG. ENTRADA DESCRIPCIN
hFile Manejador del puerto o Handle devuelto por CreateFile()
DWORD Un DWORD con la nueva mscara de eventos que se desea
asociar al puerto
TIPO SALIDA DESCRIPCIN
BOOL TRUE si la funcin se ejecut correctamente.
El siguiente programa configura la mscara de eventos para que se aada el evento EV_ERR
WaitCommEvent()
BOOL WaitCommEvent(HANDLE hFile,DWORD lpEvtMask)
ARG. ENTRADA DESCRIPCIN
hFile Manejador del puerto o Handle devuelto por CreateFile()
LPDWORD Puntero a un DWORD con la mscara de los eventos que hace
que finalice la funcin
LPOverlapped Puntero a una estructura de sobrecarga (0 si no se utiliza)
TIPO SALIDA DESCRIPCIN
BOOL TRUE si la funcin se ejecut correctamente.
El siguiente ejemplo es una versin bajo windows del contador serie que se vi en MS-DOS, en
la figura, la ventana DOS transmite a travs del puerto COM1 y la aplicacin WINDOWS recibe
a travs del COM4, se ha utilizado un cable NULL-MODEM entre COM1 y COM4.
...
#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);
};
//----------------------------------------
// Fichero....: UNIT1.CPP
// Descripcin: Recibir bytes con eventos
// Autor......: PMR - Julio 1999
//----------------------------------------
#include <vcl\vcl.h>
#pragma hdrstop
#include "Unit1.h"
//----------------------------------------
#pragma link "RXSpin"
#pragma resource "*.dfm"
TForm1 *Form1;
//----------------------------------------
__fastcall TForm1::
TForm1(TComponent* Owner): TForm(Owner)
{
ComboBox1->ItemIndex=0;
// Crear el objeto de comunicaciones
pCom= new TWinSerCom(1);
}
//----------------------------------------
void __fastcall TForm1::
Button1Click(TObject *Sender)
{
AnsiString sCom;
BYTE cDatRx;
DWORD dwErr,dwEvtMask;
COMSTAT sComSta;
int iCnt;
while(TRUE)
{
// Leer el puerto seleccionado
sCom=ComboBox1->Text;
pCom->Open(ComboBox1->ItemIndex+1);
// Si est abierto
if (!pCom->IsOpen())
{
Memo1->Lines->Add("ERROR al abrir el puerto");
break;
}
Memo1->Lines->Add("Abierto "+sCom);
// Fijar la mscara de eventos
dwEvtMask=EV_RXCHAR|EV_ERR;
if (!SetCommMask(pCom->LeeIde(),dwEvtMask))
break;;
// Esperar el evento
for(iCnt=1;iCnt<=RxSpinEdit1->Value;iCnt++)
{
Memo1->Lines->Add
("Esperando byte nro.."+String(iCnt) );
// Esperar los eventos programados
WaitCommEvent(pCom->LeeIde(),&dwEvtMask,0);
// Si se ha producido un error
if(dwEvtMask & EV_ERR)
{
ClearCommError(pCom->LeeIde(),&dwErr,&sComSta);
Memo1->Lines->Add("Error al recibir.");
};
// Si se ha leido un byte
if (dwEvtMask & EV_RXCHAR)
{
// Leer el Byte del Buffer
if (pCom->RxByte(cDatRx));
{
Memo1->Lines->Add("Recibido un byte " );
Label1->Caption=AnsiString(cDatRx);
Label1->Refresh();
};
};
}; // Fin del for
break;
}; // Fin del while
pCom->Close();
};
//----------------------------------------------------
Para manejar el puerto se utiliza el puntero pCom de la clase de comunicaciones, este puntero
se inicializa en el constructor del formulario. Cuano se pulsa el botn se ejecuta el mtodo
Button1Click el cual abre el puerto seleccionado y configura la mscara de eventos para
detectar los bytes que se reciban o los errores que se produzcan. Cuando se alcanza la funcin
WaitCommEvent() el programa se queda congelado esperando uno de los dos eventos
programados, a continuacin se determina si se finaliz por error o por la entrada de un nuevo
byte. Los datos que se leen se muestran mediante la etiqueta Label1. Si se produce un error
en la recepcin se utiliza ClearCommError() para leer el tipo de error. Si se program el
miembro fAbortOnError al abrir el puerto es necesario llamar a esta funcin para que el puerto
pueda seguir trabajando. Este ejemplo sirve de base para recibir tramas completas en vez de
bytes, basta con programar el miembro fEvtChar al abrir el puerto y la mscara de
interrupciones con el bit EV_RXFLAG.
Aunque el ejemplo anterior funciona bienm tiene un grave problema y es que el programa se
quedar bloqueado en WaitCommEvent() si no se produce alguno de los eventos
programados. La solucin a este problema pasa por utilizar subprocesos,hilos o treats en los
programas.
El API WIN32 permite la creacin de subprocesos, hilos o Threads esta es una capacidad de
los sistemas operativos multitarea de que las aplicaciones en ejecucin (procesos) puedan
crear subprocesos que se ejecutan de forma independiente. Esta tcnica es til cuando
deseamos hacer alguna tarea en segundo plano o cuando se disponde de varios procesadores
y se desea asignar a cada uno de ellos una tarea especfica. Por ejemplo, el WORD de
Microsoft puede tener activado los subprocesos del corrector ortogrfico o del corrector
gramatical, mientras escribimos los correctores se estan ejecutando en paralelo con la edicin
del texto.
FIGURA 8. PROCESOS EN EJECUCIN
Del mismo modo que en el escritorio de WINDOWS podemos terner abiertos varios programas
(procesos) simultneamnete, dentro de una aplicacin podremos crear tantos subprocesos
como necesitemos. Los procesos permiten por tanto implementar la multitarea a nivel de
programa. No hay que abusar del nmero de subprocesos, ya que cada uno de ellos consume
tiempo del procesosadr como si se tratase de un programa independiente. En comunicaciones
los subprocesos se utilizan para recibir o transmitir en segundo plano mientras se atiende al
interface del usuario.
El API Win32 incluye una serie de funciones para manejar los procesos (GetThreadPriority(),
ResumeThread(), SetThreadPriority(), CreateThreat(), SuspedThreat() etc) estas funciones
pueden ser utilizadas como las que se han vista anteriormente, no obstante el C++ Builder
encapsula y facilita el uso de subprocesos mediante la clase TThread. La tabla siguiente
muestra un resumen del interface de la clase.
La clase TThread
Nombre Descripcin
TThread() Es el contructor de la clase, admite un parmetro
lgico para indicar si el subproceso se crea activado o
no
Execute() Cdigo que se ejecutar como si fuese una aplicacin
independiente. El constructor de la clase llama a este
mtodo de forma automtica.
Suspend() Suspende temporalmente la ejecucin de un proceso.
En este estado el proceso no hace uso del
microprocesador.
Resume() Reanuda la ejecucin de un subproceso
Termiate() Finaliza un subproceso y libera los recusos que
tuviese asignados. Cuando un proceso finaliza (al
cerrar el programa) se terminan de forma
automticas todos sus subprocesos
Synchronize() Permite ejecutar una funcin con aceso a cualquier
objeto de la VCL.
La clase no se encuentra encapsulada como un componente de la VCL, sino que para utilizarla
debemos de crear un nuevo proyecto y despues aadir una nueva unidad con File-New-
ThreadObject .
Como ejemplo de subprocesos vamos a crear una aplicacin que utilizar un contador
asociado a un subproceso. Crearemos un un nuevo proyecto PROCNT que guardaremos como
PROCNT.MAK y UNIT1.CPP. Sobre la unidad depositamos los componentes que se muestran
en la figura 9 y que se detallan en el listado UNIT1.H.
// Fichero: Unit2.h
// Desc...: Definicin de la clase CntHilo
// Autor..: PMR 1999
//------------------------------------
#ifndef Unit2H
#define Unit2H
//------------------------------------
#include <vcl\Classes.hpp>
//------------------------------------
class CntHilo : public TThread
{
private:
protected:
void __fastcall Execute();
int iCnt;
public:
__fastcall CntHilo(bool CreateSuspended);
void __fastcall CntHilo::MostrarCnt(void);
};
//-------------------------------------------
#endif
// Fichero:Unit2.cpp
// Desc...: Declaracin de la clase CntHilo
// Autor..: PMR 1999
//-------------------------------------------
#include <vcl\vcl.h>
#pragma hdrstop
#include "Unit1.h" // Para acceso a Label1
//---------------------------------------------
__fastcall CntHilo::CntHilo(bool CreateSuspended)
: TThread(CreateSuspended)
{iCnt=0;}
//---------------------------------------------
void __fastcall CntHilo::Execute()
{
//---- Place thread code here ----
while (true)
{
iCnt++;
Synchronize(MostrarCnt);
};
}
//------------------------------------------
void __fastcall CntHilo::MostrarCnt(void)
{
Form1->Label1->Caption=iCnt;
}
// Fichero: Unit1.cpp
// Desc...: Declaracin clase Form1
// ejemplo multihilo
// Autor..: PMR 1999
//--------------------------------------
#ifndef Unit1H
#define Unit1H
//-------------------------------------
#include <vcl\Classes.hpp>
#include <vcl\Controls.hpp>
#include <vcl\StdCtrls.hpp>
#include <vcl\Forms.hpp>
#include <vcl\Mask.hpp>
#include "unit2.h"
//---------------------------------------
class TForm1 : public TForm
{
__published: // IDE-managed Components
TLabel *Label1;
TButton *Button1;
TButton *Button2;
TButton *Button3;
TMaskEdit *MaskEdit1;
void __fastcall Button1Click(TObject *Sender);
void __fastcall Button2Click(TObject *Sender);
void __fastcall Button3Click(TObject *Sender);
private: // User declarations
CntHilo *poCnt;
public: // User declarations
__fastcall TForm1(TComponent* Owner);
};
//-------------------------------------------
extern TForm1 *Form1;
//-------------------------------------------
#endif
// Fichero:Unit2.cpp
// Desc...: Defincin clase CntHilo
// Autor..: PMR 1999
//-------------------------------------------
#include <vcl\vcl.h>
#pragma hdrstop
#include "Unit1.h"
//------------------------------------------
#pragma resource "*.dfm"
TForm1 *Form1;
//------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
// Creamos el subproceso
// inicialmente suspendido
poCnt=new CntHilo(true);
}
//-----------------------------------------
void __fastcall TForm1::
Button1Click(TObject *Sender)
{poCnt->Resume();}
//------------------------------------------
void __fastcall TForm1::
Button2Click(TObject *Sender)
{poCnt->Suspend();}
//------------------------------------------
void __fastcall TForm1::
Button3Click(TObject *Sender)
{poCnt->Terminate();}
//-------------------------------------------
Los subprocesos combinados con los eventos es el mecanismo ms rpido y eficaz para
realizar comunicaciones a travs del puerto serie, mientras que el proceso principal atiende la
interface del usuario, un proceso en segundo plano atiende al puerto: monitorizacin, lectura,
escritura etc. En este ejemplo se va ha disear un programa que permita enviar ordenes a un
modem y observar sus respuestas. El envio de las ordenes se realizar en primer plano y la
recepcin de las respuestas en segundo plano. El aspecto del programa se muestra en la figura
siguiente.
Para poner en prctica este programa hay que crear un proyecto nuevo sobre el que
insertaremos los componentes que se desprenden de UNIT1.H.
//--------------------------------------------
// Fichero....: Unit1.H
// Descripcin: Declaracin de la clase Form1
// Autor......: PMR Julio 1999
//---------------------------------------------
#ifndef Unit1H
#define Unit1H
#include <vcl\Classes.hpp>
#include <vcl\Controls.hpp>
#include <vcl\StdCtrls.hpp>
#include <vcl\Forms.hpp>
#include <vcl\ExtCtrls.hpp>
#include <vcl\Buttons.hpp>
#include "..\PortCom\PortCom.h"
#include "SerRx.h"
#include <vcl\Mask.hpp>
#include <vcl\Classes.hpp>
//---------------------------------------------
class TForm1 : public TForm
{
__published: // IDE-managed Components
TPanel *Panel1;
TLabel *Label2;
TMaskEdit *oEdi;
TGroupBox *GroupBox1;
TMemo *oMem;
TPanel *Panel2;
TButton *ButSal;
TButton *ButEnv;
TButton *ButOpen;
TButton *ButClose;
TComboBox *CBCom;
TLabel *Label1;
void __fastcall ButOpenClick(TObject *Sender);
void __fastcall ButCloseClick(TObject *Sender);
void __fastcall ButSalClick(TObject *Sender);
void __fastcall ButEnvClick(TObject *Sender);
private: // User declarations
// Para el subproceso
CSerRx *poSerRx;
public: // User declarations
// Objetos de comunicaciones
// pblico para tener acceso
// desde el subproceso
TWinSerCom *pCom;
// Para datos recibidos
BYTE acBuf[1024];
// Constructor
__fastcall TForm1(TComponent* Owner);
};
//--------------------------------------------
extern TForm1 *Form1;
//--------------------------------------------
#endif
Los nombres de los objetos se han cambiado para que aporten mas informacin: ButOpen,
ButClose, ButEnv, oMem etc. Se ha aadido un puntero pCom para manejar las
comunicaciones y un puntero poSerRx para manejar el subproceso. El buffer donse se
almacenarn los datos acBuf que se reciban y el objeto pCom se han puesto en la seccin
pblica para permitir el acceso desde el subproceso a estos objetos. En el proyecto hay que
incluir la unidad PORTCOM.CPP node se encuentra la definicin del la clase TWinSerCom
necesario para el objeto pCom. La definicin de la clase se encuentra en UNIT1.CPP
//-------------------------------------------
// Fichero....: Unit1.CPP
// Descripcin: Definicin de la clase Form1
// Autor......: PMR Julio 1999
//-------------------------------------------
#include <vcl\vcl.h>
#pragma hdrstop
#include "Unit1.h"
#pragma resource "*.dfm"
TForm1 *Form1;
//-----------------Constructor del formulario
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
CBCom->ItemIndex=0;
// Crear el objeto de comunicaciones
pCom= new TWinSerCom(1);
// Crear el objeto del subproceso
poSerRx=new CSerRx(false);
// Estado de los botones
ButOpen->Enabled=true;
ButClose->Enabled=false;
ButEnv->Enabled=false;
CBCom->Enabled=true;
}
//------------Abrir Puerto
void __fastcall TForm1::
ButOpenClick(TObject *Sender)
{
// Bucle para control de errores
while(true)
{
oMem->Lines->Clear();
// Abrir el puerto
pCom->Open(CBCom->ItemIndex+1);
if (!pCom->IsOpen())
{
oMem->Lines->Add("ERROR al abrir el puerto");
break;
};
// Fijar la mscara de eventos
if (!pCom->SetEventos(EV_RXCHAR|EV_ERR))
{
oMem->Lines->Add("ERROR al configurar");
break;;
}
// Activar el proceso en segundo plano
// para la recepcin de datos
poSerRx->Resume();
// Estado de los botones
ButOpen->Enabled=false;
ButClose->Enabled=true;
ButEnv->Enabled=true;
CBCom->Enabled=false;
oMem->Lines->Add("Puerto abierto");
break;
};
}
//-----------Cerrar el puerto
void __fastcall TForm1::
ButCloseClick(TObject *Sender)
{
//Cerrar puerto
pCom->Close();
// Bloquear proceso en segundo plano
poSerRx->Suspend();
// Estado de los botones
ButOpen->Enabled=true;
ButClose->Enabled=false;
ButEnv->Enabled=false;
CBCom->Enabled=true;
oMem->Lines->Add("Puerto cerrado");
}
//--------------------Salir
void __fastcall TForm1::
ButSalClick(TObject *Sender)
{Close();}
//--------------------Enviar cadena
void __fastcall TForm1::
ButEnvClick(TObject *Sender)
{
AnsiString cTmp;
char *pCad;
// Leo la variable AnsiString del control
cTmp=oEdi->Text;
// Aado el retorno de carro
cTmp=cTmp+"\r";
// Convierto el objeto cTmp a cadena de C
pCad=cTmp.c_str();
// Transmito la cadena y la muestro
if ( pCom->TxCad(pCad))
oMem->Lines->Add(pCad );
}
//--------------------------------------
//----------------------------------------
// Fichero....: SerRx.H
// Descripcin: Definicin de la clase CSerRx
// para crear un subproceso que lea del puerto
// mediante eventos.
// Autor......: PMR - Julio 1999
//-----------------------------------------
#ifndef SerRxH
#define SerRxH
//-----------------------------------------
#include <vcl\Classes.hpp>
//-----------------------------------------
class CSerRx : public TThread
{
private:
protected:
void __fastcall Execute();
public:
__fastcall CSerRx(bool CreateSuspended);
void __fastcall MostrarRx(void);
};
//------------------------------------------
#endif
//--------------------------------------------
// Fichero....: SerRx.CPP
// Descripcin: Declaracin de la clase CSerRx
// para crear un subproceso que lea del puerto
// mediante eventos.
// Autor......: PMR - Julio 1999
//---------------------------------------------
#include <vcl\vcl.h>
#pragma hdrstop
#include "SerRx.h"
#include "unit1.h"
//---------------------------------------------
__fastcall CSerRx::CSerRx(bool CreateSuspended)
: TThread(CreateSuspended)
{
}
//---------Cdigo asociado al subproceso
void __fastcall CSerRx::Execute()
{
DWORD dwEvt;
BYTE cDatRx;
int iCnt;
// Fichero SEREVT2.MAK
// Descripcin: Proyecto modem
// Fecha..: PMR Julio 1999
//---------------------------------------
#include <vcl\vcl.h>
#pragma hdrstop
//---------------------------------------
USEFORM("Unit1.cpp", Form1);
USERES("SEREVT2.res");
USEUNIT("\DATOS\LIBRO_CI\fuentes\win\PortCom\PortCom.cpp");
USEUNIT("SerRx.cpp");
//----------------------------------------
WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
try
{
Application->Initialize();
Application->CreateForm(__classid(TForm1), &Form1);
Application->Run();
}
catch (Exception &exception)
{
Application->ShowException(&exception);
}
return 0;
}
//-----
EJERCICIOS Y ACTIVIDADES
Teclear la clase TWinSerCom y hacer un pequeo programa que la use para enviar y recibir un
carcter.
Teclear el ejemplo del MODEM y verificar las respuestas para los comandos HAYES ms
habituales.
Desarrollar un programa que reciba datos del puerto serie mediante eventos. Los datos
recibidos se almacenarn en un fichero, si se produce un evento de error se mostrar un
mensaje y se cancelar la recepcin de datos.