Inicio > Arduino, C/C++ > Recibiendo cadenas de texto completas con Arduino por USB (I)

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:

  • Caracteres de uno en uno
  • Valores numéricos tipo int

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:

  • La recepción por puerto serie se hace a través de interrupciones, cada vez que llega algo, y tarda un tiempo en meterse la información en el buffer de entrada de Serial por lo que a veces hay un pequeño retraso para ver la información. (por eso el delayMicroseconds())
  • La información recibida se guarda en un ring buffer, o buffer circular, y su tamaño típico es de 128bytes, por lo que si escribimos muy rápidamente por el puerto serie (que podemos), el Arduino llena el buffer muy rápido, y si no sacamos la información a tiempo podemos tener problemas. Lo ideal es no enviar muchos datos juntos, porque las interrupciones siempre tendrán prioridad, aunque también podemos hacer el buffer un poco más grande (sin pasarnos, no sea que nos quedemos sin memoria). Una velocidad segura son 19200 bps

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.

  1. Andres
    2 febrero, 2013 17:24 | #1
    Usando Google Chrome Google Chrome 24.0.1312.57 en Windows Windows 7

    Muchas gracias por su explicación del código, usted aborda unos temas interesantes que son desconocidos para mi…

    ¿Usted podría hacerme el favor de pasarme el archivo “Serial.h”?

    • admin
      8 febrero, 2013 3:23 | #2
      Usando Mozilla Firefox Mozilla Firefox 18.0 en Ubuntu Linux Ubuntu Linux

      Muchas gracias por tu comentario Andrés. Creo que por algún post anda perdido el serial.h, sólo contiene algunas directrices de configuración:
      /* En este archivo veremos la configuración de nuestro proyecto */

      #define SERIAL_SPEED 19200
      /* Este led se encenderá cuando hayamos terminado de inicializar */
      #define INIT_LED 12
      /* Led que queremos que parpadee mientras se ejecuta el programa */
      #define STATUS_LED 11
      /* Led que indica transferencia de datos */
      #define BLINK_DELAY 500
      /* Tamaño máximo de buffer */
      #define MAX_BUFFER 64

  2. Andres
    18 febrero, 2013 15:02 | #3
    Usando Google Chrome Google Chrome 24.0.1312.57 en Windows Windows 7

    listo gracias

  3. 7 marzo, 2013 22:00 | #4
    Usando Mozilla Firefox Mozilla Firefox 19.0 en Windows Windows 7

    me encontré con tu sitio tratando de encontrar una solución a mi problema, y la verdad me ayudo mucho.

    existe alguna forma de agregar un carácter de escape para terminar de leer?

    • admin
      22 abril, 2013 18:27 | #5
      Usando Mozilla Firefox Mozilla Firefox 20.0 en Ubuntu Linux Ubuntu Linux

      Gracias por tu comentario. Te refieres a que por USB envíen un carácter especial que nos permita dejar de leer? Yo creo que en el while() de serialGetString se podría implementar la detección de ese carácter. Por el momento los únicos sistemas para dejar de leer son:
      * que no haya nada en la entrada
      * que hayamos llegado al número máximo de caracteres.

  4. YAHAIRA
    5 junio, 2013 19:00 | #6
    Usando Google Chrome Google Chrome 27.0.1453.94 en Windows Windows 8

    Hola disculpa para habilitar el puerto usb en el arduino, es que lo necesito conectar con Visual Basic

    • admin
      12 junio, 2013 2:22 | #7
      Usando Mozilla Firefox Mozilla Firefox 21.0 en Ubuntu Linux Ubuntu Linux

      El puerto en Arduino viene habilitado, sólo tienes que utilizar el Serial y ya está. En el tema de Visual Basic lo siento, no te puedo ayudar. Aunque tal vez se identifique como un puerto COMx…

  5. Agustín
    27 junio, 2013 20:00 | #8
    Usando Google Chrome Google Chrome 27.0.1453.116 en Windows Windows 7

    Primero que nada! muy buena la info!

    @YAHAIRA
    tenes que usar el COM Serial de VB, es muy sencillo busca en google “Visual Basic comunicacion con puerto serie”

  6. Carlos
    15 julio, 2013 21:30 | #9
    Usando Google Chrome Google Chrome 28.0.1500.72 en Windows Windows 7

    Hola solo cuando agregues el serial en tu forma de visual(vb.net) configura con el com que te da el arduino y listo.@YAHAIRA

  7. Carlos
    15 julio, 2013 21:31 | #10
    Usando Google Chrome Google Chrome 28.0.1500.72 en Windows Windows 7

    tendras algo parecido a lo que te da el monitor serial de arduino@Carlos

  8. admin
    12 agosto, 2013 0:34 | #11
    Usando Mozilla Firefox Mozilla Firefox 22.0 en Ubuntu Linux Ubuntu Linux

    @Carlos
    Es verdad los puertos COM :) Ya ni lo recordaba. Gracias Carlos por estar atento :)

  9. admin
    12 agosto, 2013 0:42 | #12
    Usando Mozilla Firefox Mozilla Firefox 22.0 en Ubuntu Linux Ubuntu Linux

    @Agustín
    Gracias por estar atento Agustín. Vi en la timeline antes a Carlos, lo siento.

  10. Fran
    16 septiembre, 2013 15:44 | #13
    Usando Google Chrome Google Chrome 29.0.1547.66 en Windows Windows 7

    Hola,
    estoy trabajando sobre y me gustaría saber si podrías facilitar los .cpp y .h?

    • admin
      16 septiembre, 2013 16:37 | #14
      Usando Mozilla Firefox Mozilla Firefox 23.0 en Ubuntu Linux Ubuntu Linux

      Encuentras el código para copiar y pegar en el cpp. La primera ventana de código tiene el programa completo. La segunda sólo tiene la función que se comenta.

      Muchas gracias por tu comentario !

  11. GENTEC
    20 febrero, 2014 22:14 | #15
    Usando Mozilla Firefox Mozilla Firefox 27.0 en Windows Windows 7

    Hola Agradezco si me pueden colaborar con mi proyecto lo que deseo hacer es conectar mi arduino uno por los pines rx y tx a un modem ENFORA enviar un comando at recibir la info de respuesta del modem adjuntar un dato y devolver la info al modem con otro comando.
    Lo que hago es enviar el comando
    AT$MDMID? El modem me responde
    $MDMID: “AAA001″
    OK
    Necesito tomar solo el dato AAA001. El primer problema que tengo es con la instrucción Seril.read() no puedo tomar todo el dato solo me toma el primer carácter y lo segundo como hago para tomar solo el dato AAA001?

    Agradezco su colaboración

  12. Gaspar Fernández
    14 marzo, 2014 16:37 | #16
    Usando Mozilla Firefox Mozilla Firefox 27.0 en Ubuntu Linux Ubuntu Linux

    @GENTEC
    Hola, puedes probar leer la cadena entera y cortar por el “:”, puedes utilizar strpos() (incluyendo ), y para quitar las comillas puedes utilizar la función trim (http://totaki.com/poesiabinaria/2010/03/trim-un-gran-amigo-php-c-c/), aunque si el módem, seguro seguro que siempre te responde con las comillas, puedes hacer strpos directamente al caracter “

  1. agosto 18th, 2011 at 10:44 | #1
  2. agosto 29th, 2011 at 10:05 | #2
  3. diciembre 30th, 2013 at 10:45 | #3

Current ye@r *

Top