Programación de los puertos Seriales sobre WIN32 Preparado por: J. Huircan Departamento de Ingeniería Eléctrica Universidad de La Frontera Abril 2012 Introducción La programación sobre Win32 es más complicada de que MSDOS, debido a que el manejo de puertos no es directo Bayer (2008) plantea un ejemplo de uso de las funciones, éstas fueron modificadas de tal forma de implementar un terminal básico sobre Windows, sin embargo, la visualización sigue siendo sobre la ventana de comandos de Windows . Implementación sobre Windows El siguiente código une las funciones propuestas por Bayer, e incorpora algunos elementos de visualización para el control y uso de los datos. Este código puede ser compilado sobre DevC++. /*------------------------------------------------------------------------------Terminal básico implementado por jhuircan- 2011-2012 basado en Bayer (2008) Solo envia ---------------------------------------------------------------------------------*/ #include "windows.h" #include "commctrl.h" #include "stdio.h" #include "conio.h" #include "string.h" HANDLE hSerial; DCB dcbSerial; DCB dcbSerialParams; int init_com() { char *p="COM1"; int status=0; hSerial = CreateFile(p,GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); if(hSerial==INVALID_HANDLE_VALUE) { if(GetLastError()==ERROR_FILE_NOT_FOUND) { printf("PUERTO %s NO EXISTE !! \n", p); status=-1; } } else { dcbSerial.DCBlength=sizeof(dcbSerialParams); if (!GetCommState(hSerial, &dcbSerialParams)) { printf("Error get Status!!\n"); status=-2; } dcbSerialParams.BaudRate=CBR_9600; dcbSerialParams.ByteSize=8; dcbSerialParams.StopBits=ONESTOPBIT; dcbSerialParams.Parity=NOPARITY; status=0; } return(status); } Código Fuente para Windows Protocolos de Comunicación 2012 // Seria una funcion de acuerdo a articulo de [1] char *rec_comChar() { DWORD dwBytesRead=1 ; char szBuff[2] = {0,0}; // debe almacenar los datos char n=1; // Cantidad de datos rx char *pBuff=NULL; // Puntero a un buf para devolver if(!ReadFile(hSerial, szBuff, n, &dwBytesRead, NULL)) { // Error occurred. Report to user pBuff=NULL; printf("%d ",GetLastError()); } return (pBuff); } int env_com(char *data) { DWORD dwBytesWrite = 0; char szBuff[1024], n=1; strcpy(&szBuff[0],data); n=strlen(&szBuff[0]); szBuff[1]=0x00; WriteFile(hSerial, szBuff, n, &dwBytesWrite, NULL); return 0; } int main(void) { char c, *pbuff,buff[20]; printf("-- TTY - BASICO 1.0-jhuircan 2011--\n"); printf("-- Inicializando Puerto --\n"); if(!init_com()) { printf("-- Puerto Configurado --\n"); printf("-- Puerto Abierto --\n"); while(1) { // pbuff=rec_comChar(); // Lectura deshabilidada – para rx if(kbhit()) { c=getch(); putchar(c); if(c==0x1b) break; env_com(&c); c=0; } } CloseHandle(hSerial); printf("Puerto Cerrado\n"); printf("Presione Cualquier Tecla\n"); } getch(); return 0; } El código está hecho para enviar datos por el puerto. Para recibir se debe deshabilitar el comentario indicado. Sin embargo, el programa quedara trabado para enviar debido a que la función de recepción queda en espera. De acuerdo a lo indicado por Bayer, debiera utilizarse un mecanismo para realizar un timeout. Código Fuente para Windows Protocolos de Comunicación 2012 Segunda versión de código Este código fue desarrollado por J. Rodríguez, se divide en 4 archivos básicos, el primero es el serie.cpp, el cual contiene las funciones básicas, serie.h el archivo de cabecera respectivo y dos códigos básicos uno pare enviar (enviar.cpp) y el otro para recibir (recibir.cpp). El código permite enviar un carácter pulsado, pero usa la consola de Windows. El código fue realizado para ser compilado por MinGW Developer Studio, Studio en modo proyecto. /*------------------------------------------------------------------*/ /* UPCO ICAI - Departamento de Electrónica y Automática */ /*------------------------------------------------------------------*/ /* serie.cpp: Manejo comunicaciones serie */ /* */ /* Autor: José Antonio Rodríguez Mondéjar */ /* Fecha: 12/11/04 */ /* Versión: 1.0 */ /*------------------------------------------------------------------*/ #include "serie.h" HANDLE OpenSerialPort( char *psPort, DWORD dwBaudRate, BYTE bByteSize, BYTE bParity, BYTE bStopBits, DWORD Timeout // // // // // // "COM1","COM2" CBR_9600, CBR_19200. CBR_56000 7,8 NOPARITY, EVENPARITY, ODDPARITY ONESTOPBIT, ONE5STOPBITS, TWOSTOPBITS Timeout ) { HANDLE hPort; DCB dcbPort; COMMTIMEOUTS commTimeouts; DWORD dwError; // Open Serial Port hPort=CreateFile( psPort, GENERIC_READ | 0, NULL, OPEN_EXISTING, 0, NULL); // // // // port handler Port configuration Port Timeouts Error code // pointer to name of the file GENERIC_WRITE, // access (read-write) mode // share mode: 0 the object cannot be share // pointer to security attributes: NULL the handle cannot be inherited // how to create: Comx exist // file/port attributes // handle to file/port with attributes to copy // If the function fails, the return value is INVALID_HANDLE_VALUE if ( hPort == INVALID_HANDLE_VALUE ) { dwError = GetLastError (); return hPort; } // Flush error code // Set Port Configuration FillMemory(&dcbPort, sizeof(dcbPort), 0); // Delete DCB configuration dcbPort.DCBlength = sizeof(dcbPort); // Current DCB in use for the communications port GetCommState (hPort, &dcbPort); // Update DCB with new parameters dcbPort.BaudRate = dwBaudRate; dcbPort.ByteSize = bByteSize; dcbPort.Parity = bParity; dcbPort.StopBits = bStopBits; // Fixed parameters (Disable XON-XOFF and modem handshake) dcbPort.fBinary = TRUE; // Binary mode; no EOF check dcbPort.fParity = TRUE; // Enable parity checking dcbPort.fOutxCtsFlow = FALSE; // No CTS output flow control dcbPort.fOutxDsrFlow = FALSE; // No DSR output flow control dcbPort.fDtrControl = DTR_CONTROL_ENABLE; // DTR flow control type Código Fuente para Windows Protocolos de Comunicación 2012 // // // // // // // // // // dcbPort.fDsrSensitivity = FALSE; dcbPort.fTXContinueOnXoff = TRUE; dcbPort.fOutX = FALSE; dcbPort.fInX = FALSE; dcbPort.fErrorChar = FALSE; dcbPort.fNull = FALSE; dcbPort.fRtsControl = RTS_CONTROL_ENABLE; dcbPort.fAbortOnError = FALSE; Raises the DTR line when the device is opened DSR sensitivity XOFF continues Tx No XON/XOFF out flow control No XON/XOFF in flow control Disable error replacement Disable null stripping RTS flow control Raises the RTS line when the device is opened Do not abort reads/writes on error // Set new configuration if (!SetCommState (hPort, &dcbPort)) { dwError = GetLastError (); CloseSerialPort(hPort); hPort = INVALID_HANDLE_VALUE; return hPort; } // Flush error code // Set Port Timeouts // Timeouts preparation MORE INFORMATION IN WIN32 API: COMMTIMEOUTS commTimeouts.ReadIntervalTimeout = 0; // Specifies the maximum time, in milliseconds, allowed to elapse // between the arrival of two characters on the communications line // A value of zero indicates that interval time-outs are not used. commTimeouts.ReadTotalTimeoutMultiplier = 50; // Specifies the multiplier, in milliseconds, used to // calculate the total time-out period for read operations // For each read operation, this value is multiplied by the requested number of bytes to be read. commTimeouts.ReadTotalTimeoutConstant =Timeout; // Specifies the constant, in milliseconds, used to // calculate the total time-out period for read operations commTimeouts.WriteTotalTimeoutMultiplier = 10; // Specifies the multiplier, in milliseconds, used to // calculate the total time-out period for write operation // For each write operation, this value is multiplied by the number of bytes to be written. commTimeouts.WriteTotalTimeoutConstant = 1000; // Specifies the constant, in milliseconds, used to // calculate the total time-out period for write operations // See de win32 api for more information Set Timeouts if (!SetCommTimeouts (hPort, &commTimeouts)) { dwError = GetLastError (); CloseSerialPort(hPort); hPort = INVALID_HANDLE_VALUE; return hPort; } return hPort; // Flush error code } BOOL SerialSendByte(HANDLE hPort, BYTE byte) { BOOL bRes; DWORD dwError, dwNumBytesWritten=0; bRes=WriteFile( hPort, &byte, 1, &dwNumBytesWritten, NULL ); if ((!bRes)||(dwNumBytesWritten!=1)){ dwError = GetLastError (); } return bRes; // // // // // handle to file or serial port to write to pointer to data to write to file number of bytes to write pointer to number of bytes written NULL // Flush error code } Código Fuente para Windows Protocolos de Comunicación 2012 BOOL SerialReceiveByte(HANDLE hPort, BYTE *pbyte, BOOL *pTimeout) { BOOL bRes; DWORD dwError, lpNumberOfBytesRead=0; *pTimeout=FALSE; bRes=ReadFile( hPort, // handle of file or serial port to read pbyte, // address of buffer that receives data 1, // number of bytes to read &lpNumberOfBytesRead, // address of number of bytes read NULL // NULL ); if (!bRes) { dwError = GetLastError (); } if ((bRes)&&(lpNumberOfBytesRead==0)){ *pTimeout = TRUE; } return bRes; // Flush error code } BOOL CloseSerialPort(HANDLE hPort){ BOOL bRes; DWORD dwError; bRes=CloseHandle(hPort); if (!bRes) { dwError = GetLastError (); } return bRes; // Flush error code } Se lista el código fuente para enviar correspondiente a un archivo enviar.cpp. Este programa llama a la función OpenSerialPort y configura de una vez el puerto. A través de getchar lee el teclado y envía el código de la tecla usando la función SerialSendByte. #include "serie.h" #include "windows.h" #include "stdio.h" int main(){ HANDLE hPort; BOOL bRes; char c; hPort=OpenSerialPort("COM1",CBR_9600,8,NOPARITY,TWOSTOPBITS,5000); if (hPort==INVALID_HANDLE_VALUE) { printf("Error abriendo puerto com1"); return 1; } while (1) { c=getchar(); bRes=SerialSendByte(hPort,c); if (!bRes) { printf("Error escribiendo en puerto com1"); return 1; } if (c=='x') { break; } } CloseSerialPort(hPort); } Código Fuente para Windows Protocolos de Comunicación 2012 Se lista el código para recibir, en este caso se inicializa el puerto serie, a través de un loop se ejecuta la función SerialReceiveByte, la cual espera el dato un tiempo y luego sale. #include "serie.h" #include "windows.h" #include "stdio.h" int main(){ HANDLE hPort; BOOL bRes; BYTE byte; BOOL timeout; hPort=OpenSerialPort("COM4",CBR_9600,8,NOPARITY,TWOSTOPBITS,5000); if (hPort==INVALID_HANDLE_VALUE) { printf("Error abriendo puerto com4"); return 1; } while(1){ bRes=SerialReceiveByte(hPort,&byte,&timeout); if (!bRes) { break; } if (timeout){ printf("\n--->timeout\n"); } else { putchar(byte); } } if (!bRes) { printf("Error leyendo de puerto com4"); return 1; } CloseSerialPort(hPort); return 0; } Similitudes y diferencias en los códigos Listados Ambos códigos listados usan las mismas funciones (solo hay cambio de nombre). En el caso de la segunda aplicación, se encapsula el manejo de las funciones del puerto en el archivo serie.cpp. Se incorporan funciones para el manejo del timeout. Bayer (2008) hace referencia a este aspecto pero no lo revisa extensamente. Esto es importante ya que permite no dejar la función de lectura en permanente ejecución. El código realizado por jhuircan puede ser modificado incorporando el timeout, esto ser realiza agregando el siguiente código en la función de inicialización init_com(). commTimeouts.ReadIntervalTimeout = 1; commTimeouts.ReadTotalTimeoutMultiplier = 10; commTimeouts.ReadTotalTimeoutConstant = 100; commTimeouts.WriteTotalTimeoutMultiplier = 10; commTimeouts.WriteTotalTimeoutConstant = 1000; SetCommTimeouts (hSerial, &commTimeouts); Referencias [1] Robertson Bayer, Windows Serial Port Programming, March 30, 2008 [2] Allen Denver, Serial Communications in Win32, Microsoft Windows Developer Support, December 11, 1995. Código Fuente para Windows Protocolos de Comunicación 2012 Código Fuente para Windows Protocolos de Comunicación 2012