Table of Contents
Qt no incluye ningún componente para manejar puertos serie, pero se puede recurrir a opciones de código abierto de terceras partes para añadir esta característica.
En el curso 2009/2010 se trabajó en localizarlas, elegir la más adecuada y mostrar cómo hacer uso de ella. La selección de candidatos se hizo según los siguientes criterios por orden de prioridad: código abierto, multi-plataforma, modelo de licencia, aceptación.
En su momento, el ganador fué QextSerialPort
,
que es la biblioteca considerada aquí. La principal "pega" de esta
biblioteca es la falta de una licencia, problema que aún no se ha resuelto
y dificulta un aprovechamiento legal.
En Qt5 se está planteando la incorporación de otra bibloteca, pero la licencia tampoco es interesante. Cuando esté claro, actualizaré la información.
Esta página es un tutorial básico para usar la biblioteca de comunicaciones serie seleccionada y hacer un recopilatorio de otras posibles opciones.
Esto NO es un tutorial para entender qué es un puerto serie ni cómo se programa, para eso es mejor buscar en otra parte o utilizar los apuntes de clase sobre comunicaciones RS-232 y RS-485.
La biblioteca está disponible como código fuente C/C++ en http://code.google.com/p/qextserialport/.
Para descargarla es necesaria una utilidad de gestión de versiones de código llamada Mercurial. En Linux, para conseguir descargar los fuentes, se deberá abrir un terminal y escribir:
hg clone https://qextserialport.googlecode.com/hg/ qextserialport
y se creará el directorio qextserialport
con
el código fuente del componente, los manuales y los ejemplos.
![]() | Note |
---|---|
Si la utilidad hg no está instalada, Ubuntu Linux nos dirá cómo instalarla. |
En Windows, deberemos instalar antes alguna de las utilidades para Windows de Mercurial.
La manera "tradicional" de compilar algo para Qt es la que se vió en "Al grano con Qt" -> Compilando a pelo".
Como estamos más acostumbrados a los IDE, vamos a ilustrarlo aquí con Qt Creator.
Primero abrimos Qt Creator.
Después elegimos Archivo-> Abrir archivo o proyecto. Y nos
vamos al directorio qextserialport
y
seleccionamos el archivo de proyecto
qextserialport.pro
.
Por curiosidad, echad un vistazo al .pro. Destacar que lo que se generará es una biblioteca y que la configuración de proyecto cambia según se trate de Windows, Linux o MacOS.
Ahora hay que construir el proyecto, para ello haremos
Construir -> Reconstruir todo. Como resultado se creará el
directorio build con bibliotecas de enlazado dinámico con el nombre
lib*.so
. si es para Linux o
*.dll
si es para Windows.
![]() | Note |
---|---|
Una biblioteca de enlazado dinámico es una biblioteca normal de C/C++ u otro lenguaje que se enlaza al ejecutable en el momento de la ejecución. En contraposición a esto están las bibliotecas estáticas, que se enlazan al ejecutable cuando lo construimos. Para que los ejecutables encuentren las bibliotecas dinámicas, estas deben estar en el lugar apropiado. |
Para instalar las bibliotecas en el caso de Linux vamos a
copiarlas al directorio /usr/lib
del sistema.
Necesitamos privilegios de superusuario para hacerlo. Por ejemplo,
podemos abrir un terminal y hacer:
$ sudo bash # cd tmp # cd qextserialport # cd build # cp lib* /usr/lib
![]() | Important |
---|---|
Recordad que estamos como superusuarios. Si metemos la pata, podemos destrozar el sistema. |
Para instalar las librerías en el caso de Windows, deberemos
conseguir que estén en la ruta de búsqueda de ejecutables. Bastará
con dejar la .dll
en el mismo directorio que la
aplicación o copiarla al directorio
c:\Windows\system32
o similar. Como en Linux,
como tenéis privilegios avanzados, es peligroso cargarse algo del
directorio de sistema de windows.
Estas maneras de instalar la biblioteca no son las más adecuadas, pero para el caso nos sirven.
Al descargar el componente se crea también el directorio
html
con el manual. Abriendo el archivo
index.html
se accede a la ayuda.
El manual es muy escueto y está orientado a conocedores del tema y que se entiendan bien con Qt.
Para centrar las cosas, indicar que
QextSerialPort
es una clase derivada de
QIODevice
de Qt, que crea una abstracción de los
sistemas de ficheros y similares (en propiedad, se dice flujos de
datos). Esto significa que el puerto serie se nos mostrará como si de un
flujo de datos se tratase.
Para tener un acceso más inmediato a la ayuda, se ha copiado aquí la ayuda del componente QextSerialPort 1.2 a diciembre de 2009.
Algunos enlaces que hacen referencia a clases externas fallarán. Se deberán buscar esas clases en el manual de Qt.
Partamos de que tenemos un proyecto Qt operativo. El primer paso sera modificar el archivo de proyecto (.pro) para dar cabida a la biblioteca. El siguiente listado muestra un ejemplo típico:
# Proyecto sencillo para mostrar la funcionalidad de qextserialport
# Ejemplo que transmite algo
#
# Author: Angel Perles for the DSII subject
# Date: 2010-01-14
QT -= gui
TARGET = qextserialport_sencill_transmisio
CONFIG += console
CONFIG -= app_bundle # MacOS no es el objetivo
TEMPLATE = app
SOURCES += main.cpp
# Añadidos mios al proyecto creado por defecto.
# Como son dependientes de la plataforma destino, utilizamos
# opciones distintas según la plataforma objetivo
win32 {
# anyadir las librerias de qext...
LIBS += -LS:/dsii/dsii_qt/qextserialport/build
LIBS += -lqextserialportd1
# anaydir donde localizar los archivos de cabecera de qext...
INCLUDEPATH += S:/dsii/dsii_qt/qextserialport
# etiquetas para C que permite saber que tipo de implementación serie es
DEFINES = _TTY_WIN_
}
unix {
# anyadir las librerias de qext...
LIBS += -L/home/aperles/tmp/qextserialport/build
LIBS += -lqextserialportd
# anaydir donde localizar los archivos de cabecera de qext...
INCLUDEPATH += /home/aperles/tmp/qextserialport
# etiquetas para saber en que tipo de sistema estamos
DEFINES = _TTY_POSIX_
}
Como deseamos trabajar en, al menos, dos plataformas, se han creado las secciones win32 (Microsoft Windows) y unix (*nix, i.e. Linux, MacOSX, Android, ...) con la configuración particular para dicha plataforma.
Con LIBS se le indica al proyecto dónde ha de buscar las bibliotecas (-L) y qué biblioteca usar (-l).
Con INCLUDEPATH se indica dónde buscar los archivos de cabecera adicionales (cuando se hace #include <algunacosa.h>).
Con DEFINES se crean etiquetas para todos los fuentes de C que formen parte del proyecto (equivalente a hacer #define ALGO, pero no hace falta tocar el código).
QextSerialPort
es una clase Qt que se
utilizará de la misma manera que cualquier otra.
En el manual se indican distintos tipos de constructores. Por claridad, se empleará el que menos configuraciones iniciales hace. Su prototipo es:
QextSerialPort::QextSerialPort ()
;
Por ejemplo, se puede crear una instancia de la clase de la siguiente manera:
QextSerialPort *puerto; // puntero al objeto que maneja el puerto void funcion(void) { QextSerialPort *puerto = new QextSerialPort(); }
El primer paso es configurar el comportamiento del objeto y el puerto a utilizar. Para ello, se pueden utilizara los siguientes métodos:
void setQueryMode (QueryMode mode);
void setPortName (const QString &name);
Con setQueryMode()
se establece la
manera en que se atenderá la información enviada/recibida por nuestro
programa. Se tienen dos opciones: Polling
(por
encuesta) y EventDriven
(por eventos). (De
momento solo he probado Polling).
Con setPortName()
establecenos el
nombre del dispositivo serie a utilizar.
Por ejemplo, se podría hacer:
puerto->setQueryMode(QextSerialPort::Polling); puerto->setPortName("com1");
A continuación se deberán configurar los parámetros típicos de una conexión serie. Para ello se pueden utilizar los siguientes métodos:
void setBaudRate (BaudRateType);
void setDataBits (DataBitsType);
void setParity (ParityType);
void setStopBits (StopBitsType);
void setFlowControl (FlowType);
En el manual del componente se pueden consultar los posible parámetros. Como ejemplo, para una configuración 9600-8N1 sin control de flujo se podría hacer:
puerto->setBaudRate(BAUD9600); puerto->setDataBits(DATA_8); puerto->setStopBits(STOP_1); puerto->setParity(PAR_NONE); puerto->setFlowControl(FLOW_OFF);
Para que una aplicación puede utilizar un determinado puerto serie, primero debe solicitarlo al sistema operativo para que se lo asigne. A esto se le suele denominar "abrir" el puerto serie.
De la misma manera, cuando terminamos de usar una conexión serie, debemos devolverla al SO. A esto se le llama "cerrar" el puerto.
Para gestionar estas actividades se pueden utilizar los siguientes métodos:
bool open (OpenMode mode);
void close ();
La función open()
permite "abrir" el
puerto y saber si se ha logrado abrirlo. Como parámetro admite los
valores ReadOnly
(solo lectura/recibir),
WriteOnly
(solo escritura/enviar),
ReadWrite
(lectura/recibir y
escritura/enviar).
Por ejemplo, se podría hacer:
printf("Abriendo el puerto ... "); if (puerto->open(QextSerialPort::WriteOnly)) { printf("abierto!!!\n"); } else { printf("vaya, esto CASCA\n"); } // ahora se usa el puerto ... printf("Cerrando el puerto\n"); puerto->close();
Una vez abierta la conexión, se pueden enviar datos empleando distintos métodos. Por ejemplo :
putChar (char c);
write (const QByteArray
&byteArray);
write (const char *data, qint64 maxSize);
El método putChar()
permite enviar un
dato y el método write()
permite enviar al
buffer de salida una cadena o maxSize
datos
apuntados por el puntero data.
Por ejemplo:
char *mensaje = "Bon dia pel mati"; char datos[] = {125,42,37,9}; //... puerto->putChar(34); puerto->putChar('A'); puerto->putChar(0xF3); puerto->write(mensaje); puerto->write(datos,4);
Para recibir datos disponemos también de varios métodos. Por ejemplo:
qint64 bytesAvailable ();
getChar (char *c);
read (qint64 maxSize);
read (char *data, qint64 maxSize);
Debemos tener en cuenta que las funciones de recepción son bloqueantes, es decir, si no se ha recibido nada, la función se queda a la espera de que llegue algo.
Esto no es adecuado en el caso de que queramos que nuestra
aplicación atienda a otras cosas simultáneamente, así que, para evitar
el bloqueo, se puede usar el método
bytesAvailable()
, que devuelve el número de
datos disponibles en el buffer de entrada o un valor <= 0 si no hay
nada o hay problemas.
Con los métodos getChar()
y
read()
podremos recoger uno o varios datos
respectivamente del buffer de entrada.
Por ejemplo:
while (1) { int num_datos; char dato; num_datos = puerto->bytesAvailable(); if (num_datos > 0) { // hay algo puerto->getChar(&dato); printf("Recibido el ascii %d (%c)\n",(int)dato, dato); } }
/* Proyecto sencillo para mostrar la funcionalidad de qextserialport Ejemplo que transmite algo Author: Angel Perles for the DSII subject Date: 2010-01-14 */ #include <QtCore/QCoreApplication> #include <stdio.h> #ifdef Q_OS_UNIX #include <unistd.h> /* anyadir esta cabecera en los *nix para la función sleep() */ #endif // anyadimos la cabecera con la informacion sobre la clase #include <qextserialport.h> // nombre del puerto que deseamos abrir #define PUERTO_NOMBRE "/dev/ttyS0" //#define PUERTO_NOMBRE "/dev/ttyUSB0" // adaptador USB-serie //#define PUERTO_NOMBRE "COM1" //#define PUERTO_NOMBRE "COM3" QextSerialPort *puerto; // puntero al objeto que maneja el puerto int main(int argc, char *argv[]) { //QCoreApplication a(argc, argv); // no eliminar esto en aplicaciones serias printf ("Creando instancia de QextSerialPort para abrir %s\n", PUERTO_NOMBRE); QextSerialPort *puerto = new QextSerialPort(); puerto->setPortName(PUERTO_NOMBRE); puerto->setQueryMode(QextSerialPort::Polling); // modo mas sencillo printf("Configurando puerto a 9600-8N1 sin control de flujo\n"); puerto->setBaudRate (BAUD9600); puerto->setDataBits(DATA_8); puerto->setStopBits(STOP_1); puerto->setParity(PAR_NONE); puerto->setFlowControl(FLOW_OFF); printf("Abriendo el puerto ... "); if (puerto->open(QextSerialPort::WriteOnly)) { printf("abierto!!!\n"); } else { printf("vaya, esto CASCA\n"); } printf("Enviar \"Hola, puerto serie\" al puerto serie\n"); puerto->write("Hola, puerto serie"); //esperar un poco a que salga todo por el puerto #ifdef Q_OS_WIN Sleep(1); #else sleep(1); #endif printf("Cerrando el puerto\n"); puerto->close(); printf("Saliendo\n"); //return a.exec(); return(0); }
/* Proyecto sencillo para mostrar la funcionalidad de qextserialport Ejemplo que recibe cosas Author: Angel Perles for the DSII subject Date: 2010-01-14 */ #include <QtCore/QCoreApplication> #include <stdio.h> // anyadimos la cabecera con la informacion sobre la clase #include <qextserialport.h> // nombre del puerto que deseamos abrir #define PUERTO_NOMBRE "/dev/ttyS0" //#define PUERTO_NOMBRE "/dev/ttyUSB0" // adaptador USB-serie //#define PUERTO_NOMBRE "/dev/rfcomm3" // Bluettoth SPP //#define PUERTO_NOMBRE "COM1" QextSerialPort *puerto; // puntero al objeto que maneja el puerto int main(int argc, char *argv[]) { //QCoreApplication a(argc, argv); // no eliminar esto en aplicacione serias printf ("Creando instancia de QextSerialPort para abrir %s\n", PUERTO_NOMBRE); QextSerialPort *puerto = new QextSerialPort(); puerto->setPortName(PUERTO_NOMBRE); puerto->setQueryMode(QextSerialPort::Polling); // modo mas sencillo printf("Configurando puerto a 9600-8N1 sin control de flujo\n"); puerto->setBaudRate (BAUD9600); puerto->setDataBits(DATA_8); puerto->setStopBits(STOP_1); puerto->setParity(PAR_NONE); puerto->setFlowControl(FLOW_OFF); printf("Abriendo el puerto ... "); if (puerto->open(QextSerialPort::ReadOnly)) { printf("abierto!!!\n"); } else { printf("vaya, esto CASCA\n"); exit(1); } printf("Entrando al bucle que mira si se van recibiendo cosas:\n"); while (1) { int num_datos; char dato; num_datos = puerto->bytesAvailable(); if (num_datos > 0) { // hay algo puerto->getChar(&dato); printf("Recibido el ascii %d (%c)\n",(int)dato, dato); } } // aqu´i no llegamos nunca, otro dia sera printf("Cerrando el puerto\n"); puerto->close(); printf("Saliendo\n"); //return a.exec(); return(0); }
/* Proyecto sencillo para mostrar la funcionalidad de qextserialport Ejemplo de recepcion y transmision Author: Angel Perles for the DSII subject Date: 2010-01-14 */ #include <QtCore/QCoreApplication> #include <stdio.h> #ifdef Q_OS_UNIX #include <unistd.h> /* anyadir esta cabecera en los *nix para la función sleep() */ #endif #include <ctype.h> // anyadimos la cabecera con la informacion sobre la clase #include <qextserialport.h> // nombre del puerto que deseamos abrir #define PUERTO_NOMBRE "/dev/ttyS0" //#define PUERTO_NOMBRE "/dev/ttyUSB0" // adaptador USB-serie //#define PUERTO_NOMBRE "COM1" QextSerialPort *puerto; // puntero al objeto que maneja el puerto int main(int argc, char *argv[]) { //QCoreApplication a(argc, argv); // no eliminar esto en aplicacione serias printf ("Creando instancia de QextSerialPort para abrir %s\n", PUERTO_NOMBRE); QextSerialPort *puerto = new QextSerialPort(); puerto->setPortName(PUERTO_NOMBRE); puerto->setQueryMode(QextSerialPort::Polling); // modo mas sencillo printf("Configurando puerto a 9600-8N1 sin control de flujo\n"); puerto->setBaudRate (BAUD9600); puerto->setDataBits(DATA_8); puerto->setStopBits(STOP_1); puerto->setParity(PAR_NONE); puerto->setFlowControl(FLOW_OFF); printf("Abriendo el puerto ... "); if (puerto->open(QextSerialPort::ReadWrite)) { printf("abierto!!!\n"); } else { printf("vaya, esto CASCA\n"); } printf("Entrando al bucle\n"); printf("Pulsa \"f\" en el otro lado para salir\n"); bool fin; fin = false; do { int num_datos; char dato; // comprobar si se ha recibido algo y sacarlo por pantalla num_datos = puerto->bytesAvailable(); if (num_datos > 0) { // hay algo, saco un solo byte puerto->getChar(&dato); printf("Recibido el ascii %d (%c)\n",(int)dato, dato); // envio el ascii de vuelta por el canal serie //sleep(1); puerto->putChar(toupper(dato)); if (dato == '-') { // tecla de fin fin = true; } } } while (fin == false); printf("Se ha pulsado \'f\'' en el otro extremo. Me salgo\n"); puerto->write("\nAdios. Hasta la proxima\n"); //esperar un poco a que salga todo por el puerto #ifdef Q_OS_WIN Sleep(1); #else sleep(1); #endif printf("Cerrando el puerto\n"); puerto->close(); printf("Saliendo\n"); //return a.exec(); return(0); }
Para que en las práctica de la asignatura se entre en materia más
rápidamente, se ha preparado un proyecto plantilla para Qt que incluye
QextSerialPort
precompilado para Linux y
Windows.
El proyecto está formado por los módulos
main.cpp
(programa principal),
mainwindows.cpp
(código asociado a un formulario
visual) y serie.cpp
(código asociado al manejo del
puerto serie).
Destacar el módulo serie.cpp
, que contiene
funciones de soporte para el manejo del puerto serie e implementan las
funciones genéricas que se proponen en los apuntes de teoría. Es
importante aprender a desarrollar estas funciones y a usarlas, así que se
ha creado la ayuda para
usar las funciones del módulo serie.cpp.
El proyecto se deberá adaptar al problema concerto que se pida en prácticas. Una vez construido y ejecutado, aparecerá una ventana similar a la mostrada aquí.
En el área de descargas se puede obtener la plantilla.
Como un ejemplo más serio de uso, se propone una aplicación capaz de interpretar el flujo de datos de un GPS compatible NMEA-0183 y obtener la posición actual.
Para que sea lo más real posible (sin pasarse), la funcionalidad del
tratamiento se ha encapsulado en una clase llamada
TGeoposition
, que será la encargada de ofrecer una
interfaz sencilla a la aplicación. Si se mira el resto del código de la
aplicación, se verá que es muy sencillo (creo).
Se ha procurado que le código de TGeoposition sea parecido a los proyectos que se puedan obtener en la red. A diferencia del lo mostrada hasta ahora, este código está documentado en inglés y se ha empleado doxygen para generar automáticamente la documentación.
Podemos acceder a la documentación de TGeoposition generada automáticamente con doxygen.
La siguiente imagen muestra el aspecto del visor.
En el área de descargas se puede obtener este proyecto.
Lo que se ha encontrado por ahí es:
Esto está obsoleto a fecha de hoy. En gitorious hay más proyectos, pero no está claro aún su futuro.
QextSerialPort Licencia: Se propone new BSD license (perfecto para nosotros). Aún no está claro a 5 de febrero de 2011. Plataformas: Windows y POSIX (la mayoría de los *nix, incluyendo Mac). Comentarios: Esta biblioteca parece que está bien aceptada y asentada, así que es un candidato perfecto para empezar a trabajar. Hay bastante actividad en los foros para ampliarla y corregir defectos. URL: http://code.google.com/p/qextserialport/ Más URLS: http://www.qt-apps.org/content/show.php/QT_comport?content=95246, http://www.chuktech.net/serial/index.html QPort Licencia: GPL (el código que hagamos debe ser abierto). Plataformas: Windows y POSIX (la mayoría de los *nix, incluyendo Mac). Comentarios: Esta biblioteca proporciona un "widget" para incorporar en Designer e incorporar fácilmente el componente en un formulario. Así es como lo hemos hecho normalmente en Borland C++ Builder con comport. A la larga, esa manera de trabajar tiene pegas, así que prefiero dejarlo en la recámar. Por dentro utiliza QextSerialPort. David ha contactado con el autor para que le eche una mano probándola. URL: http://www.sebest.com.ar/?q=node/44 Más URLS: http://sourceforge.net/projects/qport/ QxtSerialDevice Licencia: LGPL (perfecta para nosotros). Plataformas: la parte serie es POSIX. Comentarios: Por ahí dicen que esta biblioteca puede manejar el serie, pero no veo nada al respecto en la documentación. Eso sí, esta librería es una extesión de Qt con un montón de cosas. Vaya, vaya. (Puede que sea QAbstractSocket). URL: http://www.libqxt.org/ Más URLS: http://sourceforge.net/projects/qport/ QSerialDevice Licencia: GPL (el código que hagamos debe ser abierto). Plataformas: multiplataforma, no sé cuales. Comentarios: Parece que es una reimplementación de QextSerialPort que corriege algunos de los planteamientos de la anterior. Solo hay una versión que salió en septiembre de 2009. Mejor no nos arriesgamos, no creo que sea bueno empezar con una biblioteca de una única persona y con solo una versión. URL: http://qt-apps.org/content/show.php/QSerialDevice?content=112039 Más URLS: http://fireforge.net/projects/qserialdevice/ QSerialPort Licencia: ?? Plataformas: es para Qtopia (embedded Linux en móviles, navegadores GPS, ...). Comentarios: Da la sensación de que es una biblioteca hecha adrede por Trolltech (fundadora de Qt) para meterse en el mundo de los móviles (los Samsung Omnia, LG Viewty, etc. llevan eso) y poder comunicarse con los chips internos del móvil. La dejamos ahí. URL: http://doc.trolltech.com/qtopia4.2/qserialport.html Más URLS: http://fireforge.net/projects/qserialdevice/