domingo, 4 de octubre de 2015

[Lenguaje Arduino 08] parte 2: Recibir datos desde el exterior

Hasta ahora hemos visto instrucciones que permiten al microcontrolador enviar datos a su entorno. Pero ¿cómo se hace a la inversa? Es decir: ¿cómo se pueden enviar datos al microcontrolador que provengan de su entorno, como por ejemplo nuestro computador?

Desde el “Monitor serial” enviar datos a la placa es muy sencillo: basta con escribir lo que queramos en la caja de texto allí mostrada y pulsar el botón “Enviar”. No obstante, si el sketch que se está ejecutando en la placa no está preparado para recibir y procesar estos datos, esa transmisión no llegará a ningún sitio. Por lo tanto, necesitamos recibir convenientemente en nuestros sketches los datos que lleguen a la placa vía comunicación serie. Para ello, disponemos de dos instrucciones básicas: Serial.available() y Serial.read().
  • Serial.available(): devuelve el número de bytes (caracteres) disponibles para ser leídos que provienen del exterior a través del canal serie (vía USB o mediante los pines TX/RX). Estos bytes ya han llegado al microcontrolador y permanecen almacenados temporalmente en una pequeña memoria de 64 bytes que tiene el chip TTL-UART (“buffer”) hasta que sean procesados mediante la instrucción Serial.read(). Si no hay bytes para leer, esta instrucción devolverá 0. No tiene parámetros.
  • Serial.read(): devuelve el primer byte aún no leído de los que estén almacenados en el buffer de entrada del chip TTL-UART. Al hacerlo, lo elimina de ese buffer. Para devolver (leer) el siguiente byte, se ha de volver a ejecutar Serial.read(). Y hacer así hasta que se hayan leído todos. Cuando no haya más bytes disponibles, Serial.read() devolverá -1. No tiene parámetros.
Ejemplo 7
Veamos un código básico de estas nuevas instrucciones:

byte byteRecibido = 0;

void setup() {
          Serial.begin(9600);
}

void loop() {
          if (Serial.available() > 0) {
          byteRecibido = Serial.read();
          Serial.write("Byte recibido: ");
          Serial.write(byteRecibido);
}
}


En el código anterior hemos introducido un elemento del lenguaje que todavía no hemos visto: el condicional “if”. Lo explicaremos en profundidad en su apartado correspondiente de este capítulo, pero baste por ahora saber que un “if” mira si la condición escrita entre paréntesis es cierta o no: si lo es, se ejecutan las instrucciones escritas dentro de sus llaves, y si no, no. La condición vemos que es Serial.available() > 0 , así que lo que se está comprobando es si existen o no datos almacenados en el buffer de entrada del chip TTL-UART. Si esta condición es cierta, se ejecuta el interior del “if”, que lo que hace básicamente es leer el primer byte disponible que haya en el buffer (eliminándolo de allí) y enviarlo al “Serial monitor”. Como todo esto ocurre dentro de la sección “void loop()”, seguidamente regresaremos otra vez a su principio para volver a comprobar si aún existen datos almacenados en el buffer de entrada. Si sigue siendo así, se leerá el siguiente byte disponible y se volverá a enviar al “Serial monitor”. Y se comprobará otra vez si sigue habiendo datos en el buffer, en cuyo caso se leerá el siguiente byte. Y así hasta que ya se hayan leído todos los bytes disponibles. En ese momento, la condición del “if” pasará a ser falsa (porque Serial.available() devolverá 0) y por tanto “void loop()” no ejecutará nada. Pero como a cada repetición de “void loop()” se seguirá comprobando si hay o no datos en el buffer, en el momento que vuelva a ver, la condición del “if” volverá a ser cierta y por tanto otra vez se empezarán a leer los bytes disponibles allí, uno a uno.

Un detalle muy importante del código anterior es que, tal como se puede observar, el valor devuelto por Serial.read() se guarda en una variable de tipo “byte”. Esto implica que ese valor se almacena en formato numérico. Es decir: si Serial.read() recibe por ejemplo el valor “a”, lo que se guarda en una variable de tipo “byte” es su valor numérico correspondiente en la tabla ASCII (en este caso, 97); igualmente, si Serial.read() recibe por ejemplo el valor “1”, en realidad lo que se guarda en una variable de tipo “byte” es el valor numérico 49, y así. Y esto es independiente de cómo se muestren estos datos por el “Serial monitor”. Sin embargo, si el tipo de la variable utilizado para guardar el valor devuelto por Serial.read() es “char” (recordemos que es el otro tipo de datos que ocupa 1 byte en memoria), lo que ocurre es que “a” será leído como el carácter “a”, “1” como el carácter “1”, etc. La consecuencia de todo ello es que debemos pensar previamente qué utilidad le vamos a dar en nuestro sketch al valor devuelto (¿número o carácter?) para entonces decidir el tipo de datos de la variable que lo guardará.

Existen otras instrucciones además de Serial.read() que leen datos del buffer de entrada del chip TTL-UART de formas más específicas, las cuales nos pueden venir bien en determinadas circunstancias:
  • Serial.peek(): devuelve el primer byte aún no leído de los que estén almacenados en el buffer de entrada. No obstante, a diferencia de Serial.read(), ese byte leído no se borra del buffer, con lo que las próximas veces que se ejecute Serial.peek() –o una vez Serial.read()– se volverá a leer el mismo byte. Si no hay bytes disponibles para leer, Serial.peek() devolverá -1. Esta instrucción no tiene parámetros.
  • Serial.find(): lee datos del buffer de entrada (eliminándolos de allí) hasta que se encuentre la cadena de caracteres (o un carácter individual) especificada como parámetro, o bien se hayan leído todos los datos actualmente en el buffer. La instrucción devuelve “true” si se encuentra la cadena o “false” si no.

Ejemplo 8
El código siguiente utiliza Serial.find() para leer continuamente los datos presentes en el buffer de entrada en busca de la palabra “hola”. Se puede probar su comportamiento escribiendo en la caja de texto del “Serial monitor” las cadenas que deseemos enviar a la placa. Si la cadena “hola” se encuentra, se mostrará por el “Serial monitor” la palabra “Encontrado”.

boolean encontrado;

void setup(){
          Serial.begin(9600);
}

void loop(){
          encontrado=Serial.find("hola");
          if (encontrado == true){
                Serial.println("Encontrado");
          }
}


Nota: aquí volvemos a ver un ejemplo de “if”: básicamente lo que comprueba es que el valor devuelto por Serial.find() sea verdadero para así poder imprimir “Encontrado”. Es importante darse cuenta de que se han de escribir los dos iguales en la condición del “if” (ya hablaremos de ello en el apartado correspondiente).
  • Serial.findUntil(): lee datos del buffer de entrada (eliminándolos de allí) hasta que se encuentre la cadena de caracteres (o un carácter individual) especificada como primer parámetro, o bien se llegue a una marca de final de búsqueda (la cual es la cadena –o carácter– especificada como segundo parámetro). La instrucción devuelve “true” si se encuentra la cadena a buscar antes que la marca de final de búsqueda o “false” si no.
  • Serial.readBytes(): lee del buffer de entrada (eliminándolos de allí) la cantidad de bytes especificada como segundo parámetro (o bien, si no llegan suficientes bytes, hasta que se haya superado el tiempo especificado por Serial.setTimeout()) . En cualquier caso, los bytes leídos se almacenan en un array –de tipo “char[]”– especificado como primer parámetro. Esta instrucción devuelve el número de bytes leídos del buffer (por lo que un valor 0 significa que no se encontraron datos válidos).
Ejemplo 9
El código siguiente (donde se ve el uso de Serial.readBytes()) obtendrá lo que haya en el buffer de entrada de 20 en 20 bytes, y los almacenará en el array “miarray”, mostrando seguidamente la cantidad de bytes obtenidos y sus valores en forma de cadena. Si en el buffer hay más de 20 bytes, a la siguiente repetición de “void loop()” se volverá a ejecutar Serial.readBytes(), con lo que leerá los siguientes 20 bytes del buffer y se sobrescribirán los valores que había en el array por los nuevos. Se puede utilizar el botón “Send” del “Serial monitor” para enviar caracteres a la placa y observar el resultado.

char miarray[30];
byte bytesleidos;

void setup(){
          Serial.begin(9600);
}

void loop(){
          bytesleidos=Serial.readBytes(miarray,20);
          Serial.println(bytesleidos);
          Serial.println(miarray);
}



  • Serial.readBytesUntil(): lee del buffer de entrada (eliminándolos de allí) la cantidad de bytes especificada como tercer parámetro, o bien, si se encuentra antes una cadena de caracteres –o carácter individual– especificada como primer parámetro que hace de marca de final, o bien, si no llegan suficientes bytes ni se encuentra la marca de final, hasta que se haya superado el tiempo especificado por Serial.setTimeout(). En cualquier caso, los bytes leídos se almacenarán en un array –de tipo “char[]”– especificado como segundo parámetro. Esta instrucción devuelve el número de bytes leídos del buffer (por lo que un valor 0 significa que no se encontraron datos válidos).
  • Serial.setTimeout(): tiene un parámetro (de tipo “long”) que sirve para establecer el número de milisegundos máximo que las instrucciones Serial.readBytesUntil() y Serial.readBytes() esperarán a la llegada de datos al búfer de entrada serie. Si alguna de estas dos instrucciones no recibe ningún dato y se supera ese tiempo, el sketch continuará su ejecución en la línea siguiente. El tiempo de espera por defecto es de 1000 milisegundos. Esta instrucción se suele escribir en “void setup ()”. No tiene valor de retorno.
  • Serial.parseFloat(): lee del buffer de entrada (eliminándolos de allí) todos los datos hasta que se encuentre con un número decimal. Su valor de retorno – de tipo “long”– será entonces ese número decimal encontrado. Cuando detecte el primer carácter posterior no válido, dejará de leer (y por tanto, no seguirá eliminando datos del buffer). Esta instrucción no tiene parámetros.

Ejemplo 10
El siguiente código (junto con la ayuda del botón “Send” del “Serial monitor”) nos permite probar el uso de Serial.parseFloat():

float numero;

void setup(){
          Serial.begin(9600);
}

void loop(){
/* Vacía el buffer hasta reconoceralgún número decimal o vaciarlo del todo */
          numero=Serial.parseFloat();
/*Imprime el número decimal detectado,y si no se ha encontrado ninguno, imprime 0.00 */
          Serial.println(numero);
/*Lee un byte más y lo imprime. Si se hubiera detectado un númerodecimal, ese byte sería el carácter que está justo después de él. Siel buffer está vacío porque Serial.parseFloat() no encontró ningúnnúmero decimal, entonces devuelve -1 */
          Serial.println(Serial.read());
}


  • Serial.parseInt(): lee del buffer de entrada (eliminándolos de allí) todos los datos hasta que se encuentre con un número entero. Su valor de retorno –de tipo “long”– será entonces ese número entero encontrado. Cuando detecte el primer carácter posterior no válido, dejará de leer (y por tanto, no seguirá eliminando datos del buffer). Esta instrucción no tiene parámetros.



No olvides visitar la pestaña Tutoriales arduino y Lenguaje arduino, donde podrás encontrar la lista de tutoriales que he hecho hasta el momento. Como siempre muchas gracias por visitar mi blog y si tienen alguna consulta o consejo puede comentarlo por medio de este mismo blog.

2 comentarios:

  1. Hola, muy buenos los tutoriales y ejemplo
    Me ha surgido una duda: como se compararia la fila de un archivo .txt puesto en una Sd con un rtc en tiempo real, es decir dd/mm, hh:mm escritos en una sd con la hora en tiempo real de un rtc. Saludos

    ResponderBorrar