Poesía Binaria

Recibiendo cadenas de texto completas con Arduino por USB (I)

Uno de los problemas de trabajar con Arduino con el puerto serie es la recepción de cadenas de caracteres. De serie, con las bibliotecas disponibles podemos leer:

Otro tipo de entradas es fácil de leer con un poco de esfuerzo, por eso se me ocurrió crear una función que lea un chorro de caracteres desde el Serial, y lo almacene en un array de char (una cadena de caracteres de toda la vida). Esta función leerá carácter a carácter todo lo que venga del Serial y lo almacenará en la cadena. La función terminará cuando se hayan leído todos los caracteres o cuando se haya alcanzado el tamaño del buffer. Un pequeño ejemplo completo aquí:

serial1.pde:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// serial1
// Hace un eco con el puerto serie del Arduino, leyendo una cadena completa

#include "serial.h"
#include <dynmem.h>

#define MAX_BUFFER 100

// Almacenamos el estado como variable global
int estado=LOW;
int estado_transf=LOW;
// Almacenamos también el número de milisegundos anterior
unsigned long momento_anterior=0;
unsigned long bytes_recibidos=0;

void setup()
{
  Serial.begin(SERIAL_SPEED);
  // Queremos que un led parpadee mientras trabajamos
  pinMode(STATUS_LED, OUTPUT);
  // Queremos salida por el led de transferencia
  pinMode(TRANSF_LED, OUTPUT);
}

int serialGetString(char *string, int max)
{
  unsigned i=0;
  char sIn;
  // Queremos que la cadena se rellene hasta max-2 para que en el carácter
  // max-1 (el último) podamos meter el terminador \0
  --max;       
  while (Serial.available() && i<max)
    {
      sIn=Serial.read();
      string[i++]=sIn;
      // La recepción tiene una latencia, se produce a través de una interrupción, que a lo mejor se ejecuta
      // un poco después del Serial.available() por lo que el dato no entraría, por eso hacemos una pequeña espera
      delayMicroseconds(500);
    }
  string[i++]='\0';
  return i;
}

void loop ()
{  
  int recibe;
  unsigned long momento_actual=millis();
  char buf[MAX_BUFFER];
// No bloqueante, si hay algo para leer entramos, si no, no.
  if(Serial.available())
    {
      serialGetString(buf, MAX_BUFFER);
      // Escribimos el buffer completo
      Serial.println((char*)buf);
    }
  // No usamos delay para el parpadeo porque nos entorpece la comunicación con el serial
  if (momento_actual-momento_anterior>=BLINK_DELAY)
    {
      // Cambiamos el estado siguiente. Si era HIGH (1) ahora será
      // LOW (0). He leído en algún lado que el valor de HIGH no
      // siempre es 1; pero en este caso sí es así.
      estado=!estado;
      // Escribimos el estado actual del led
      digitalWrite(STATUS_LED, estado);
      // Establecemos el momento anterior como actual.
      momento_anterior=momento_actual;
    }
}

Antes de probarlo, tenemos que tener en cuenta lo siguiente:

Este ejemplo, dejará un led parpadeando mientras nos permite recibir cadenas completas desde el Serial, esto nos permitirá algo importante: parsear los datos entrantes.
La función serialGetString() funciona de forma parecida a fgets(), nosotros le pasamos el buffer donde queremos almacenar la información y el tamaño de dicho buffer, pararemos de recibir cuando hayamos terminado de recibir información o cuando el buffer se haya llenado.

Una modificación de serialGetString que funciona muy bien es la siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int serialGetString2(char *string, int max)
{
  unsigned i=0;
  char sIn;
  unsigned long m;
  // Queremos que la cadena se rellene hasta max-2 para que en el carácter
  // max-1 (el último) podamos meter el terminador \0
  --max;       
  while (Serial.available() && i<max)
    {
      sIn=Serial.read();
      string[i++]=sIn;
      m=millis();
      while (!Serial.available() || millis()>=m+1);
    }
  string[i++]='\0';
  return i;
}

Aquí no hacemos la espera con delay, en su lugar hacemos un bucle esperando que haya datos en el Serial, aunque con un timeout de 1ms (millis()>=m+1); es decir cuando pase el milisegundo dejaremos de esperar. Tenemos que tener cuidado con esto, y es que si la transferencia es demasiado lenta, por ejemplo 4800bps (transmitiremos 600 símbolo de 8bit en un segundo, por lo que un símbolo llegará al buffer en 1.66ms, y si introducimos este timeout de 1ms lo más seguro que no se reciban cadenas completas de esta forma).

También es curiosa la forma de introducir el timeout y es que en 50 días más o menos, el reloj que controla millis() en Arduino, se desbordará, lo que es parecido a un pequeño efecto 2000, pero en este caso, un efecto 50 días. Por eso, tanto m como millis() se desbordarán a la vez, es decir, si m está a punto de desbordarse, m+1 será 0 y millis(), que estaría también a punto de desbordarse, cuando avance 1 será también 0.

También podría interesarte....