Archivo

Entradas Etiquetadas ‘string’

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

Jueves, 18 de Agosto de 2011 Gaspar Fernández Sin comentarios

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.

Volteando cadenas

Viernes, 22 de Mayo de 2009 blakeyed Sin comentarios

En C, una de las pequeñas cosas que a veces nos hace más lentos a la hora de hacer un pequeño programa es la posibilidad de darle la vuelta a una cadena. Bien, aquí traigo un pequeño código (función y ejemplo) de strrev, que además de poder dar la vuelta a una cadena, puede manipular sólo ciertos caracteres:

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
#include <stdio.h>
#include <string.h>

char *strrev(char *str, int strl)
{
    /* Creamos punteros al principio y al final de la cadena */
    /*       C   A   R   A   C   T   E   R   5   \0 */
    /*       |                               |      */
    char *start = str, *end = str + strl - 1;
    char temp;
    /* Iteramos hasta que el principio y el fin coincidan */
    while(start < end)
        {
            /* Intercambiamos los caracteres */
            /* 1º  C A R A C T E R 5 */
            /* 2º  5 A R A C T E R C */
            /* 3º  5 R R A C T E A C */
            /* 4º  5 R E A C T R A C */
            /* 4º  5 R E T C A R A C */
            temp = *start;
            *start++ = *end;
            *end-- = temp;
        }
    return str;
}

/* Ejemplo */
int main()
{
    char cadena[50]="Una cadena de caracteres cualquiera";
    char tmp[50]="Una cadena de caracteres cualquiera";
    char *cad2=cadena;
    printf("Cadena original: %s\n", cadena);
    printf("Cadena al revés: %s\n", strrev(tmp, strlen(tmp)));
    strrev(cadena+14, 10);
    printf("Caracteres al revés: %s\n", cad2);
    return 0;
}

Esta función la encontré hace mucho tiempo navegando y me pareció muy interesante, sobre todo por cómo está hecha, creo que es de las más rápidas.

Categories: C/C++ Tags: , ,

Visita otras webs de la red