Desde nuestro monitor serie disponemos de un cable para enviar información y otro para recibirla. En la gran mayoría de casos se envía información para conocer el estado de las variables dentro de un programa para poder conocer como se gestionan durante la ejecución del mismo. Pero muy pocas veces se utilizan para leer datos a menos que creemos una comunicación Bluetooth para recibir datos de otro dispositivo.
Como en cualquier comunicación bidireccional nos interesa que los datos quese intercambian puedan ser muchos en el menor tiempo posible de manera que nuestro control remoto sobre robots sea instantáneo y no bloqueante. Y por este motivo vamos a explicar algunas de las diferencias con las funciones provistas para lectura de datos que son:
- Serial.read() –> Leer solo un carácter del buffer de datos.
- Serial.readString() –> Leer una cadena de texto del buffer de datos
- Serial.readStringUntil() –> Leer una cadena de texto hasta encontrar un carácter
Aunque las diferencias entre unos y otros puedan ser evidentes vamos a realizar unas pruebas sencillas de concepto e intentaremos entender qué es lo que hay detras de estas comunicaciones.
Para empezar habremos de comprender el concepto de buffer de datos. Como muy bien se explica en Wikipedia, es una memoría que almacena datos temporalmente hasta que se liberan cuando un recurso lo requiere. En este caso podemos tener una aplicación móvil que manda datos a un robot y éste ejecute movimientos cuando le llegan instrucciones como se explica en un tutorial anterior.
Uno de los problemas que podríamos considerar resultaría cuando tenemos una aplicación que envía un conjunto de datos a procesar y en lugar de envíar carácter por carácter, queremos leer una linea de texto completa; que también abordamos en un tutorial anterior.
La cuestión que nos quedaría pendiente sería….
- ¿Qué ocurre si envio demasiados datos hasta saturar el buffer de datos?
- ¿Qué ocurre si al enviar estos datos el tiempo de procesado no es instantáneo y la respuesta del robot tiene un desfase?
Para ello vamos a realizar un programa para leer los datos y medir los tiempos de dichas lecturas.
#include <SoftwareSerial.h> char BT_Data; long timestamp; SoftwareSerial BT(12,13); void setup() { Serial.begin(38400); BT.begin(38400); } void loop() { if (BT.available()) { timestamp = micros(); BT_Data = BT.read(); Serial.print("Character: "); Serial.println(BT_Data); Serial.print("Time: "); Serial.println( micros() - timestamp ); } }
ReadString
Ahora pasaremos a la lectura de datos mediante una cadena de texto en el que podremos elaborar un protocolo completo y mandar muchos datos tal y como vimos en el tutorial anterior.
#include <SoftwareSerial.h> String BT_String; long timestamp; SoftwareSerial BT(12,13); void setup() { Serial.begin(38400); BT.begin(38400); } void loop() { if (BT.available()) { timestamp = millis(); BT_String = BT.readString(); Serial.print("String: "); Serial.println(BT_String); Serial.print("Time: "); Serial.print( millis() - timestamp ); Serial.println(" ms"); } }
Como podemos ver, ahora una cadena de texto dura entre lectura y lectura 1 segundo como mínimo. Demasiado para controlar un robot a distancia.
Podríamos pensar que a la hora de gestionar un tipo de dato carácter y otro de tipo String, no es lo mismo leer la reserva de un espacio de memoria de 1 byte para el tipo caracter, como otro de longitud indefinida como es un String. Y aquí es donde se adecua el concepto de buffer.
El tipo de dato String es un objeto que contiene métodos propios, por lo que no es raro pensar que su formato pueda ocupar más espacio… Pero quizás no tanto como para bloquear la placa durante un segundo entero, cosa que nos perjudica seriamente cuando el conjunto de datos tampoco supera una longitud de 10 caracteres.
Si nos informamos un poco mejor, podremos encontrar que existe un valor de tiempo Timeout para lectura de cadenas de texto establecido por defecto de 1 segundo en la clase Stream o flujo de datos, que es una clase que hereda la comunicación Serial.
Este Timeout lo podemos modificar con la siguiente función en la que podemos reducir esta espera por defecto en el flujo de datos.
Y este es el código en el que establecemos un timeout de 200 ms.
#include <SoftwareSerial.h> String BT_String; long timestamp; SoftwareSerial BT(12,13); void setup() { Serial.begin(38400); BT.begin(38400); BT.setTimeout(200); } void loop() { if (BT.available()) { timestamp = millis(); BT_String = BT.readString(); Serial.print("String: "); Serial.println(BT_String); Serial.print("Time: "); Serial.print( millis() - timestamp ); Serial.println(" ms"); } }
Como podremos observar, ahora mismo, las cadenas se leen con tiempo por defecto mínimo de 200 ms y no bloquea la placa cuando el flujo de datos es minimamente manejable. Pero por otra parte y de forma obvia, cuando el flujo de datos es muy grande, al buffer de datos le cuesta más tiempo manejarlo.
ReadStringUntil \n
Para finalizar, hay que atender a un concepto del que ya hemos hablado antes en un tutorial anterior, que es el salto de linea. El salto de linea es un caracter invisible en el monitor serie y que puede aparecer sin que nos demos cuenta o hayamos definido un println en lugar de un print a secas para enviar información.
El uso de println introduce dos caracteres que se interpretan como \r\n.
No es que sea algo malo, pero este modelo nos permite aprovechar una nueva función que es la de ReadStringUntil, y cada vez que se defina un mensaje la leeremos hasta que termine con este salto de linea. *También podemos utilizarlo con otros caracteres que no sean un salto de linea, pero es el más utilizado.
#include <SoftwareSerial.h> String BT_String; long timestamp; SoftwareSerial BT(12,13); void setup() { Serial.begin(38400); BT.begin(38400); } void loop() { if (BT.available()) { timestamp = millis(); BT_String = BT.readStringUntil('\n'); Serial.print("String: "); Serial.println(BT_String); Serial.print("Time: "); Serial.print( millis() - timestamp ); Serial.println(" ms"); } }
Para este ejemplo es necesario utilizar una aplicación que envíe estos saltos de linea. En caso de no haberlo, leera toda la cadena con el timeout anterior por defecto de 1 segundo.
Ganador por goleada. 23 ms para leer una frase completa sin una restricción de longitud y sin necesidad de manipular el timeout. Con toda esta información en la comunicación en una transmisión se puede crear todo un protocolo con el que se abren muchas opciones y que veremos en siguientes tutoriales.
Así que como resumen, utilizaremos a partir de ahora finales de linea con println o con otro caracter que delimite el comienzo y final del mensaje y para leer los mensajes usaremos readStringUntil().