Conexión de puerto serie en C++
En este artículo se tratan los aspectos básicos del uso de la API de Windows para abrir, leer, escribir y administrar conexiones de puerto serie.
El objetivo principal de este tutorial es brindarle una comprensión básica de cómo funcionan las comunicaciones en serie en la programación y ayudarlo a comenzar en la dirección correcta.
Este artículo supondrá que tiene conocimientos básicos de C/C++, que puede compilar y ejecutar programas y que su entorno de desarrollo está configurado para usar llamadas a la API de Windows.
Conexión de puerto serie en C++
Hay seis pasos para leer datos o escribir en puertos serie en C++:
- Abra los puertos serie
- Establecer algunas propiedades básicas
- Establecer el tiempo de espera
- Leer o escribir los datos
- Limpia los puertos
- Algunas funciones avanzadas
Abra el puerto serie
El primer paso es la apertura del puerto. Este es uno de los pasos más fáciles y sencillos, especialmente si ya conoce la E/S de archivos de Windows.
Primero, debe asegurarse de haber incluido los archivos de encabezado requeridos, es decir, windows.h
en su archivo. Luego, usa el siguiente código:
HANDLE h_Serial;
h_Serial = CreateFile("COM1", GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, 0);
if (h_Serial == INVALID_HANDLE_VALUE) {
if (GetLastError() == ERROR_FILE_NOT_FOUND) {
// serial port not found. Handle error here.
}
// any other error. Handle error here.
}
Hicimos una variable de tipo HANDLE
en la primera línea y luego llamamos a la función CreateFile
para inicializarla. El primer argumento para esta función es el nombre del archivo que necesita abrir; en este caso queremos abrir un puerto serial, por eso usamos el nombre de ese puerto, es decir, COM1
.
El segundo argumento es para especificar si necesitamos leer o escribir los datos. Si no se va a realizar ninguna de las tareas, puede dejar este argumento.
Los siguientes dos argumentos siempre se mantienen en cero. El siguiente argumento es especificar si se necesita abrir un archivo existente o crear uno nuevo.
En este caso, es el puerto, por lo que usamos OPEN_EXISTING
. El siguiente argumento es especificarle a Windows que no necesitamos nada sofisticado sino lectura o escritura regulares.
El último argumento también se mantiene siempre en cero.
Establecer las propiedades básicas
Después de obtener el HANDLE
del puerto, debemos establecer algunas de las propiedades básicas como la tasa de banda, el tamaño de byte, los bits de parada, etc. Esto se hace usando una estructura DCB
.
DCB dcbSerialParam = {0};
dcbSerial.DCBlength = sizeof(dcbSerialParam);
if (!GetCommState(h_Serial, &dcbSerialParam)) {
// handle error here
}
dcbSerialParam.BaudRate = CBR_19200;
dcbSerialParam.ByteSize = 8;
dcbSerialParam.StopBits = ONESTOPBIT;
dcbSerialParam.Parity = NOPARITY;
if (!SetCommState(h_Serial, &dcbSerialParam)) {
// handle error here
}
En el fragmento de código anterior, creamos un objeto de DCB
en la primera línea de código y lo inicializamos con cero para borrar los valores. En la siguiente línea, hemos establecido la longitud de esta estructura, que es un paso obligatorio de Windows.
Después de eso, llamamos a la función GetCommState
y le pasamos dos parámetros, es decir, nuestro puerto HANDLE
y el objeto DCB
, para completar los parámetros actualmente en uso.
Una vez que tengamos esto, debemos configurar los parámetros críticos, es decir, BaudRate
, ByteSize
, StopBits
y Parity
.
Windows necesita que proporcionemos el BaudRate
usando constantes especiales. Por ejemplo, CBR 19200
representa 19200 baudios, CBR 9600
representa 9600 baudios, CBR 57600
representa 57600 baudios, etc.
Podemos definir el ByteSize
directamente, pero StopBits
y Parity
requieren variables adicionales. ONESTOPBIT
, ONE5STOPBITS
y TWOSTOPBITS
son las posibilidades de StopBits
.
EVENPARITY
, NOPARITY
y ODDPARITY
son las alternativas más utilizadas para Parity
. Existen otros, pero son menos conocidos.
Consulte el elemento de la biblioteca de MSDN (busque DCB) para obtener más información.
Necesitamos aplicar esta configuración al puerto serie después de configurar la estructura DCB según nuestras prioridades. La función SetCommState
se utiliza para lograr esto.
Establecer el tiempo de espera
La lectura desde el puerto serie puede hacer que su aplicación se detenga mientras espera que aparezcan los datos si no ingresan datos al puerto serie (por ejemplo, el dispositivo del puerto serie está apagado o desconectado). Hay dos maneras de lidiar con esta situación.
En primer lugar, se pueden usar subprocesos múltiples en su aplicación, con un subproceso que se ocupa de las dificultades del puerto serie y el otro con el procesamiento real. Esto puede volverse engorroso y complicado, y no es necesario.
La alternativa es mucho más simple: ¡dígale a Windows que deje de esperar a que aparezcan los datos! Esto se logra a través de lo siguiente:
COMMTIMEOUTS timeout = {0};
timeout.ReadIntervalTimeout = 60;
timeout.ReadTotalTimeoutConstant = 60;
timeout.ReadTotalTimeoutMultiplier = 15;
timeout.WriteTotalTimeoutConstant = 60;
timeout.WriteTotalTimeoutMultiplier = 8;
if (!SetCommTimeouts(h_Serial, &timeout)) {
// handle error here
}
La estructura COMMTIMEOUTS
es bastante simple, con simplemente los campos enumerados anteriormente. Un resumen rápido:
ReadIntervalTimeout
: especifica el tiempo que debe transcurrir entre la recepción de caracteres antes de que se agote el tiempo de espera (en milisegundos).ReadTotalTimeoutConstant
: proporciona la cantidad de tiempo de espera antes de regresar (en milisegundos).ReadTotalTimeoutMultiplier
: especifica el tiempo de espera antes de responder a cada byte solicitado en la operación de lectura (en milisegundos).WriteTotalTimeoutConstant
yWriteTotalTimeoutMultiplier
: ambos logran lo mismo queReadTotalTimeoutConstant
yWriteTotalTimeoutMultiplier
, pero para escrituras en lugar de lecturas.
Establecer ReadIntervalTimeout
en MAXDWORD
y tanto ReadTotalTimeoutConstant
como ReadTotalTimeoutMultiplier
en 0
hace que cualquier operación de lectura regrese instantáneamente con cualquier carácter en el búfer (es decir, ya se ha recibido), incluso si no hay ninguno presente.
Después de configurar la estructura COMMTIMEOUTS
, necesitaremos usar el método SetCommTimeouts
para aplicar los cambios al puerto serie.
Leer/Escribir los datos
Puede comenzar a leer datos después de un puerto serie abierto con los parámetros y tiempos de espera necesarios. Estos son fáciles de entender.
Considere el escenario de leer n
bytes desde el puerto serie. Luego, simplemente seguimos estos pasos:
char sBuff[n + 1] = {0};
DWORD dwRead = 0;
if (!ReadFile(h_Serial, sBuff, n, &dwRead, NULL)) {
// handle error here
}
Un HANDLE
a un archivo (puerto serie), el búfer donde se almacenan los datos, el número de bytes para leer, la referencia a un número entero para establecer el número de bytes leídos y NULL
se pasan a ReadFile
. El número de bytes leídos por la operación ReadFile
se almacenará en dwRead
.
Escribir los datos también es el mismo procedimiento. La única diferencia es que la función WriteFile
puede escribir datos.
cerrar el puerto
Cuando haya terminado de usar el puerto serie, cierre el asa. Si no lo hace, pueden suceder cosas extrañas, incluido que nadie más pueda usar el puerto serie hasta que reinicie.
En cualquier caso, es bastante fácil de lograr, así que ten en cuenta lo siguiente:
CloseHandle(h_Serial);
Manejar los errores
Como habrá visto, hemos insertado una nota después de cada llamada al sistema que indica que debe manejar los errores.
Esta siempre es una excelente práctica de programación, pero es especialmente crucial con las funciones de E/S, que fallan con más frecuencia. Todas las siguientes funciones, en cualquier caso, devuelven 0
cuando fallan y cualquier cosa que no sea 0
cuando tienen éxito.
Para averiguar qué salió mal, utilice la función GetLastError()
, que devuelve el código de error como DWORD
. Puedes usar el método FormatMessage
para convertir esto en una cadena que tenga sentido:
char lastErr[1020];
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL,
GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
lastErr, 1020, NULL);
Husnain is a professional Software Engineer and a researcher who loves to learn, build, write, and teach. Having worked various jobs in the IT industry, he especially enjoys finding ways to express complex ideas in simple ways through his content. In his free time, Husnain unwinds by thinking about tech fiction to solve problems around him.
LinkedIn