Comunicaciones serie con Processing

Comunicaciones serie con Processing

Comunicaciones serie con Processing

La clase Serial

Las operaciones para utilizar las comunicaciones serie en Processing están definidas en la clase Serial.La primera operación a realizar para utilizarlas en un programa (sketch) será incorporarla al código con import processing.serial.*;.

Tabla de contenidos

    La clase Serial tiene cinco constructores diferentes en función de los parámetros que se indiquen. El único parámetro obligatorio es el objeto padre (parent) que suele corresponder con el programa principal (digamos, la ventana del programa) de la clase PApplet. Como normalmente el padre será el programa que se está escribiendo (el sketch actual), el valor de este primer parámetro obligatorio será this.

    Los otros cinco parámetros que pueden pasarse al constructor son ① la velocidad, ② el nombre del puerto serie ③ la paridad usada en el protocolo, ④ los bits de datos y ⑤ los bits de parada. Los parámetros que con más frecuencia se pasan, además del objeto padre, obligatorio, son el nombre del puerto y la velocidad.

    La velocidad de las comunicaciones serie es un número entero (int) que toma por defecto el valor 9600 si es que este parámetro no se pasa al constructor.

    Puertos serie disponibles. El método list.

    El nombre del puerto tiene la forma que determina el sistema, de esta forma, por ejemplo en distribuciones Linux será algo como /dev/ttyS4 /dev/ttyACM3 o /dev/ttyUSB1 (dependiendo del tipo de puerto), mientras que en Windows será algo como COM12. Salvo que se asocie físicamente un puerto a un dispositivo, normalmente no se sabrá desde el programa qué puerto utilizar. Una forma habitual de seleccionar el puerto es obtener una lista de los disponibles, mostrarla al usuario y permitirle elegir el que desea utilizar. El método Serial.list() devuelve una vector de cadenas de texto (String) con los nombres de los puertos disponibles en el sistema.

    El puerto usado por defecto por la librería Serial es el primero de los devueltos por el método list (seguramente COM1 en Windows o /dev/ttyS0 en GNU/Linux). Salvo en contextos muy acotados en los que se conoce estrictamente el hardware con el que se trabaja (como un sistema en modo quiosco) no suele omitirse y se indica expresamente el puerto destino.

    Processing serie Linux Serial list port ttyACM ttyS ttyUSB

    En la captura de pantalla de arriba se muestra la salida de un sistema GNU/Linux que cuenta con cuatro puertos serie RS-232 (ttyS0 a ttyS3) y cinco adaptadores de dos tipos (ttyACM0 a ttyACM1 y ttyUSB0 a ttyUSB2).

    Dispositivos serie Linux permisos ttyACM ttyS ttyUSB

    Para poder acceder a los puertos serie, el usuario debe pertenecer al grupo al que el sistema los asigne, normalmente tty o dialout. En la captura de pantalla de la imagen de arriba puede verse que los puertos serie listados con ls /dev/tty[ASU]* -la pertenecen al grupo dialout que tiene permisos de acceso de lectura y escritura sobre ellos.

    Parámetros del protocolo serie

    La paridad de las comunicaciones serie se expresa en Processing como un carácter (char) que puede tomar los valores: ① N (none) para no detectar la paridad, ② E (even) para indicar que el bit de paridad es par, ③ O (odd) para indicar que el bit de paridad es impar, ④ M (mark) para hacer siempre cero el bit de paridad y ⑤ S (space) para hacer siempre uno el bit de paridad. El valor por defecto, si no se pasa al constructor como parámetro, es N (sin paridad).

    El número de bits de datos, que es ocho por defecto, indica el número de bits que componen la carga neta de datos (llamado carácter o a veces palabra) que se transmiten en cada unidad básica de la trama. El parámetro que indica el número de bits de datos se expresa como un número entero (int).

    Por último, el quinto posible parámetro indica la duración de la marca final, expresado como bits de parada (stop-bits), que se indica como un número representado en coma flotante (float) que puede tomar los valores 1.0 (el valor por defecto si no se pasa el parámetro al constructor), 1.5, o 2.0.

    Constructores de la clase Serial

    En la siguiente lista se muestran las diferentes combinaciones de los parámetros que se pueden pasar al constructor de la clase Serial:

    • Serial(padre)
    • Serial(padre,puerto)
    • Serial(padre,velocidad)
    • Serial(padre,puerto,velocidad)
    • Serial(padre,puerto,velocidad,paridad,bits_datos,bits_parada)
    Finalizar las comunicaciones serie. El método stop.

    Para liberar el puerto serie, asignado al instanciar Serial, y que otras aplicaciones del sistema puedan utilizarlo, se finalizan las comunicaciones con el método stop, que no recibe parámetros.

    Enviar datos por el puerto serie. El método write.

    Para enviar datos, la clase Serial de Processing incorpora el método write con el que se pueden transmitir ① cadenas de texto (String), ② bytes o ③ vectores de bytes (byte[]). Es interesante recordar que byte en Processing (en Java) representa un número entero comprendido entre -128 y 127 y que, por defecto, las cadenas utilizan la codificación UTF-16.

    Leer datos desde el puerto serie

    Para que el programa pueda realizar otras tareas mientras se reciben datos por el puerto serie, lo habitual es almacenar en un buffer los datos que llegan y leerlos cuando corresponda. Aunque normalmente no es muy eficiente, se puede detener la aplicación para cargar todos los datos que haya disponibles; sin embargo, lo más habitual será ir leyendo la información según llega, ya sea en cada iteración de draw, cuando estén disponibles cierta cantidad o se haya recibido un código especial.

    Cantidad de datos disponibles en el buffer. El método available

    Para saber si han llegado datos al buffer serie, el método available devuelve el número de bytes que ya se han almacenado en este buffer. En cualquier caso, las operaciones de lectura pueden devolver un valor especial (como -1 o null) cuando se intenta cargar un dato del buffer serie cuando está vacío.

    Cargar un byte cada vez. El método read

    Los principales métodos de la clase Serial que sirven para leer la información recibida por un puerto serie son los de «tipo read» que se diferencian entre ellos, principalmente, por el tipo de dato en el que entregan la información recibida.

    read se utiliza para entregar los bytes recibidos por el puerto serie como un valor entre 0 y 255. Como el tipo de datos byte de Processing representa el rango entre -128 y 127 y no entre 0 y 255, es necesario utilizar un int para poder representar el rango devuelto por read. Si se intenta leer con read y el buffer serie está vacío, devuelve el valor -1

    Leer caracteres desde el puerto serie. El método readChar

    El método readChar es similar a read pero devuelve un valor en formato char en lugar de un int. Como internamente, los char en Processing (en Java) se almacenan con dos bytes, el valor elegido para devolver cuando se realiza la lectura con readChar de un buffer serie vacío es 0xFFFF o -1.

    Cargar una cadena de texto. Los métodos readString y readStringUntil.

    El método readString devuelve un objeto String formado a partir de todos los datos que haya disponibles en el buffer serie en el momento de consultarlo.

    El método readString crea la cadena de texto suponiendo que los bytes recibidos por el puerto serie están en el formato ASCII así que este método de lectura no podrá utilizarse para otras codificaciones.

    Si se trata de leer el buffer serie con readString cuando está vacío, el valor devuelto es null.

    El método readStringUntil añade a readString la capacidad de devolver la información cargada en el buffer serie partiéndola por un carácter (código) especial que se pasa como parámetro. Esta forma de leer la información recibida permite distinguir tanto separadores como terminadores que ayudan a interpretar la información recibida.

    El método readStringUntil devuelve null cuando en el buffer serie no se encuentra el código especificado en el argumento que se le pasa (un byte).

    En el siguiente código para Arduino envía tres mensajes por el puerto serie. Los dos primeros terminan en un tabulador, por lo que aparecerán en la consola de Processing, mientras que el tercero, aunque se enviará por el puerto serie no se leerá con readStringUntil(9) ya que no termina en tabulador (con código ASCII 9).

    Processing Serial.readStringUntil leer cadena serie

    Leer bloques de datos. Los métodos readBytes y readBytesUntil.

    Los métodos vistos anteriormente se utilizan para leer datos con formatos específicos, para leer bloques de datos en bruto o con un formato que no esté previsto en Processing se utilizan los métodos readBytes y readBytesUntil

    El método readBytes trata de leer los datos disponibles en el buffer serie. Si no se pasa ningún parámetro al método readBytes se leen todos los datos disponibles y se devuelven en un vector (byte[]). Si se pasa como parámetro un número entero, se leen como máximo, el número de bytes que este número indica y se devuelven también como un vector.

    Existe una tercera forma de utilizar readBytes, más eficiente, que toma como argumento un vector de bytes en el que se cargará el contenido del buffer serie. Esta forma de utilizar readBytes devuelve un número entero (int) que representa la cantidad de bytes que se han leído.

    El método readBytesUntil funciona de manera similar pero incluye un primer parámetro que representa el valor del byte que, si se encuentra en el buffer, indicará el final de la lectura. En este método no tiene sentido el parámetro que determina el máximo número de bytes que se leerán ya que la cantidad estará determinada por el código especial.

    Para probar el funcionamiento del método readBytes supongamos el siguiente código para Arduino que envía un texto por el puerto serie.

    El siguiente programa de ejemplo para Processing lee el texto desde el puerto serie en bloques de 32 bytes (TOTAL_BYTES). Para verificar que funciona lo muestra por la consola como caracteres forzando el tipo de los bytes recibidos a char.

    En la siguiente captura de pantalla puede verse como se muestran en la consola de Processing los datos que se han ido cargando en bloques de (como máximo) 32 bytes (TOTAL_BYTES) cada vez. Pero hay un problema del que ya se ha hablado: Arduino ha ido enviando los versos de Federico García Lorca del ejemplo codificados como texto en formato UTF-8, que no es el que utiliza Processing (Java), que prefiere UTF-16 así que los que no corresponden al rango del ASCII imprimible se interpretan incorrectamente.

    Processing Serial.readBytes UTF-16

    Para resolver este inconveniente se pueden cargar los juegos de caracteres (charset) y definir un nuevo objeto String forzando que se represente con la codificación UTF-8 como se muestra en el siguiente código de ejemplo.

    Processing Serial.readBytes UTF-8

    Leer los últimos datos recibidos. Los métodos last y lastChar.

    Mientras que el resto de los métodos de lectura (los «tipo read») van cargando la información del buffer serie en el mismo orden que ha llegado (FIFO), con estos dos métodos se lee el último byte que ha llegado al buffer serie. El método last devuelve el valor del último byte como un int y lastChar devuelve el valor como un char.

    Gestión del buffer serie

    Aunque los métodos que se han visto hasta ahora son perfectamente funcionales no representan siempre la mejor forma de explotar el acceso al puerto serie. Para cargar los datos, necesitan consultar periódicamente el estado del buffer serie y leer los datos disponibles en una parte del código que se repita. Una forma generalmente más eficiente consiste en leer los datos solamente cuando se tiene constancia de que están disponibles.

    Leer el puerto serie cuando se reciban datos. El evento Serial.

    Para realizar los accesos al buffer serie cuando se reciban los datos, se puede explotar el evento Serial gestionándolo por medio de la definición del método serialEvent. Este método utiliza como argumento el puerto serie que lo lanza.

    Dimensionar el buffer serie. El método buffer.

    Si se conoce la cantidad de bytes que forman un bloque de datos útiles se puede optimizar aún más este estilo de lectura del buffer serie por medio de serialEvent. El método buffer permite establecer el número de bytes que se almacenarán en el buffer antes de lanzar un evento Serial. El método espera como parámetro un entero que representa la cantidad de bytes.

    Llenar el buffer hasta recibir un valor. El método bufferUntil.

    En lugar de configurar la llamada al método serialEvent por una cantidad de datos en el buffer, con el método bufferUntil se puede configurar que se almacenen datos hasta que llegue un valor especial y entonces lanzar el evento Serial. El parámetro que se pasa a este método es un int que representa el valor que produce la llamada a serialEvent.

    Borrar los datos almacenados en el buffer. El método clear.

    Con el método clear se pueden borrar los datos que estén actualmente en el buffer. Este método puede utilizarse, por ejemplo, para empezar una nueva sesión de recepción de datos ignorando los que resten de la anterior.

    Aplicación Processing típica para lectura de datos por el puerto serie

    Para terminar, conviene recapitular las operaciones del objeto Serial de Processing que se utilizan más habitualmente, recorriendo un ejemplo típico de recepción de datos por el puerto serie para dibujar con ellos un gráfico, en este caso de áreas apiladas.

    Importar la librería Serial

    Determinar el protocolo de datos (separadores)

    Determinar el objeto de la clase Serial

    Instanciar el objeto de la clase Serial configurando el puerto serie usado

    Configurar el buffer del puerto serie

    Implementar un manejador para el evento Serial

    Leer el buffer serie

    Acondicionar los datos recibidos

    Finalizar las comunicaciones serie

    En el código de ejemplo de más abajo se ilustra este resumen con una aplicación funcional (aunque muy sencilla) que genera un gráfico de áreas con los valores que se van recibiendo por el puerto serie, algo similar a lo que muestra la siguiente animación.

    gráfica con Processing con datos recibidos por el puerto serie

    Para no perderse en el resto del programa y centrar la atención en las comunicaciones serie con Processing, están resaltadas las líneas de código que corresponden con las operaciones anteriores.

    Publicar comentario

    Puede que te hayas perdido