Archivo

Entradas Etiquetadas ‘printf’

Formateando la salida en el Serial para Arduino [ parte II ]

Viernes, 26 de Agosto de 2011 Gaspar Fernández Sin comentarios

El lunes pasado empecé contando formas para formatear la salida en el Serial para Arduino y me dejé dos métodos en el tintero, relacionados con codificar a mano nuestra propia función tipo printf():

printf() usando como salida el Serial

Es fácil de programar, sólo necesitamos un rato para tenerla lista, recae por completo en la biblioteca HardwareSerial, y específicamente en el objeto Serial (aunque podremos cambiarlo cuando queramos si vamos a utilizar otro puerto serie); de primeras, si necesitamos otro puerto, tendremos que cambiar todo el código, con lo que no es demasiado reutilizable. Por otra parte, nuestro binario engorda unos 2.5Kb, lo que ya es bastante sobre todo si lo vamos a usar para depuración. Pero vamos, es sólo un experimento, el printf() nativo es mejor que este:

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#include <stdarg.h>
int contador=1;

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

static void printInteger(const int num, const unsigned base, byte signd)
{
  if (signd && num<0)    
    Serial.print(num, base);
  else
    Serial.print((unsigned)num, base);
}

static void my_vprintf(const char *fmt, va_list args)
{
  // Mientras el carácter leído no sea el terminador
  while (*fmt!=0)
    {
      if (*fmt=='%')
    {
      // Ancho, Decimales y conmutador de ancho y decimales a 0
      ++fmt;        // Incrementamos la posición
      if (*fmt=='\0')
        break;      // Fin, fmt ha terminado
      else if (*fmt=='%')
        Serial.print((*fmt));   // Copiamos el % en la cadena
      else
        {
          while ( ( (*fmt>='0') && (*fmt<='9') ) || (*fmt=='.') )
        // No aceptamos formato
        ++fmt;

          unsigned b=10,s=1// ,val=va_arg(args, int)
        ;
          switch (*fmt)
        {
        case 's':
          Serial.print((char*)va_arg(args, char*));
          break;
        case 'f':
          Serial.print((double)va_arg(args, double));
          break;
        default:
          if ( (*fmt=='i') || (*fmt=='d') )
            {
            }
          else if (*fmt=='x')
            b=16;
          else if (*fmt=='X')
            b=16;
          else if (*fmt=='o')
            b=8;
          else if (*fmt=='b')
            b=2;
          else if (*fmt=='u')
            s=0;
          else
            continue;

          printInteger(va_arg(args, int), b, s);

        }
        }
    }
      else if (*fmt=='\n')
    Serial.println();
      else if (*fmt!='\0')
    Serial.print(*fmt);

      ++fmt;
    }
}

void printSerial(const char *fmt, ...)
{
  va_list args;

  // // Obtenemos la lista de argumentos
  va_start (args, fmt );
  my_vprintf(fmt, args);
  va_end (args);
  // sp.print(tmp);  
}

void loop()
{
  printSerial("Transcurridos %d o %f segundos desde que se inicio.\n", ++contador, (float)contador/10.0);

  delay(100);
}

printf() usando como salida una cadena

Es algo así como vsnprintf(), lo sacamos todo a una cadena y luego la cadena la escribimos en el Serial que queramos. Por otra parte, aunque no podemos controlar las alineaciones, sí podemos controlar la precisión y el tamaño de los valores, lo que lo hace perfecto para depuración o escritura de informes; aunque ocupa 2Kb, puede que muchas veces prefiramos vsnprintf() de toda la vida, pero esta implementación da menos problemas utilizada dentro de clases.

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
#include <stdarg.h>
#define MAX_CADENA 128
#define MAX_BUFFER_BITS 33  // 32 + 1 (terminador)
#define MAX_FLOAT_SIZE 20
int contador=1;

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

// void incdigit(int &var, int digit)
// {
//   var*=10;
//   var+=digit;
// }

static void printString (char *&out, const char *tstr, unsigned len, const int size, int width)
{
  while ( (len<size) && ( (*tstr!=0) || (width>0) ) )
    {
      *(out++)=(*tstr!='\0')?*(tstr++):' ';
      ++len;
      --width;
    }
}

static void printInteger(char *&out, const int num, const unsigned base, byte signd, int width, unsigned len, const int size, const char hexlet)
{
  char buffer[MAX_BUFFER_BITS];
  char *s=buffer+MAX_BUFFER_BITS-1;
  // byte neg=(signd)?(num<0):0;
  // unsigned wnum=(signd && num<0)?num*-1:num;
  unsigned wnum;
  if (signd && num<0)    
    wnum=num*-1;
  else
    wnum=num;

  *s='\0';

  if (wnum==0)
    *(s--)='0';
  else
    while (wnum)
      {
        s--;
        *s=wnum%base;
        if (*s>=10)
          *s+=hexlet-10;
        else
          *s+='0';

        wnum=wnum/base;
      }

  if (signd && num<0)
    *(s--)='-';

  printString(out, s, len, size, width);
}

static char* printFloat(char *out, const float num, unsigned decs)
{
  char *s=out+MAX_FLOAT_SIZE-1;
  float wnum=num;
  unsigned i;
  unsigned mult=1;

  *s='\0';
  if (num<0)
    wnum=num*-1;

  for (i=0; (i<decs && i<4); ++i)
    {
      wnum*=10;
      mult*=10;
    }
  unsigned long wun=(unsigned long)mult+wnum%mult;
  while (wun>1)
    {
      *(--s)=(wun%10)+'0';
      wun/=10;
    }

  *(--s)='.';

  i=(num<0)?num*-1:num;
  while (i)
    {
      *(--s)=(i%10)+'0';
      i/=10;
    }

  if (num<0)
    *(--s)='-';
 
  return s;
}

static void my_vsnprintf(char *out, int size, const char *fmt, va_list args)
{
  char *tmp=out;
  char buffer[MAX_BUFFER_BITS];
  char *pbuf;
  unsigned width,decs;
  byte widecs;
  int len;
  // Mientras el carácter leído no sea el terminador
  while (*fmt!=0)
    {
      if (out-tmp>=size)    // Miramos no pasarnos del tamaño
    break;

      if (*fmt=='%')
    {
      // Ancho, Decimales y conmutador de ancho y decimales a 0
      width=decs=widecs=0;   
      ++fmt;        // Incrementamos la posición
      if (*fmt=='\0')
        break;      // Fin, fmt ha terminado
      else if (*fmt=='%')
        *(out++)=(*fmt);    // Copiamos el % en la cadena
      else
        {
          while ( ( (*fmt>='0') && (*fmt<='9') ) || (*fmt=='.') )
        {
          if (*fmt=='.')
            widecs=1;
          else
            // incdigit((widecs)?decs:width, *fmt-'0');
            // Hacerlo a través de función suma casi 200bytes a nuestro binario y no es algo imprescindible.
            if (widecs)
              decs*=10+*fmt-'0';
            else
              width*=10+*fmt-'0';

          ++fmt;
        }

          unsigned b=10,s=1// ,val=va_arg(args, int)
        ;
          char let='a';
          switch (*fmt)
        {
        case 's':
          printString(out, va_arg(args, char*), (unsigned)(out-tmp), size, width);
          break;
        case 'f':
          printString(out, printFloat(buffer, va_arg(args, double), (widecs)?decs:-1), (unsigned)(out-tmp), size, width);
          break;
        default:
          if ( (*fmt=='i') || (*fmt=='d') )
            {
            }
          else if (*fmt=='x')
            b=16;
          else if (*fmt=='X')
            {
              b=16;
              let='A';
            }
          else if (*fmt=='o')
            b=8;
          else if (*fmt=='b')
            b=2;
          else if (*fmt=='u')
            s=0;
          else
            continue;

          printInteger(out, va_arg(args, int), b, s, width, (unsigned)(out-tmp), size, let);

        }
        }
    }
      else
    *(out++)=(*fmt);

      ++fmt;
    }
  *out='\0';
}

void printSerial(HardwareSerial &sp, const char *fmt, ...)
{
  char tmp[MAX_CADENA];
  va_list args;

  // // Obtenemos la lista de argumentos
  va_start (args, fmt );
  // // Escribimos en tmp, con tamaño MAX_CADENA, la cadena de formato será fmt y los
  // // argumentos args
  my_vsnprintf(tmp, MAX_CADENA, fmt, args);
  va_end (args);
  sp.print(tmp);  
}

void loop()
{
  printSerial(Serial, "Transcurridos %d o %f segundos desde que se inicio.\n", contador++, (float)contador/10.0);

  delay(100);
}

Formateando la salida en Serial para Arduino [1ª parte] ( y no sólo para Arduino )

Lunes, 22 de Agosto de 2011 Gaspar Fernández Sin comentarios

Arduino - Minicom - Emacs Una de las cosas que más echo de menos programando con Arduino es el formateo de texto de cara al serial, ya que éste sólo permite cadenas de texto o números, y no todo junto. En principio es comprensible, el sistema debe estar en un tamaño muy reducido, y esta característica engordará el binario. como mínimo 1.5Kb; Podemos optar por varias soluciones que explicaré en este post:

  • sprintf()
  • Usando la clase String
  • vsnprintf()
  • Inventar nuestro printf() particular con salida a cadena
  • Inventar nuestro printf() particular con salida a Serial

Usando sprintf()

Esta forma es muy sencilla y engordará nuestro ejecutable 1.5Kb aproximadamente. Será útil cuando vayamos a formatear poco texto, en pocas salidas, ya que debemos almacenar en una variable la cadena resultante y luego esa cadena es la que podemos escribir en Serial; son 3 líneas de código por cada salida que vayamos a hacer:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int contador=0;

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

void loop()
{
  char texto[50];

  sprintf(texto, "Transcurridos %d segundos", contador++);
  Serial.write(texto);
  delay(1000);
}

Aunque con sprintf todo funciona bien, recomiendo utilizar snprintf() ya que a éste último le debemos decir el tamaño de la variable a escribir y si los valores hacen la cadena más larga de lo que debería, ésta será recortada, es decir, si reservamos 50 bytes para la cadena, con sprintf() tal vez se escriban más, depende de los datos a escribir, pero snprintf() escribirá 50.

1
snprintf(texto, 50, "Transcurridos %d segundos desde la ultima vez que se nos ocurrio poner en pantalla el valor del contador", contador++);

Este ejemplo se recortará, por ejemplo, cuando contador vale 100 a:

Transcurridos 100 segundos desde la ultima vez qu

Usando la clase String

Es más estilo C++, pero la clase String nos permite hacer muchas cosas, entre ellas almacenar cadenas dinámicas y no tener que preocuparnos tanto por el tamaño de un array de caracteres, aunque en un ordenador no pasa nada, en los tiempos que corren tenemos memoria de sobra, pero un pequeño Arduino no tiene mucha, por lo que tenemos que procurar no pasarnos. Este método tiene dos inconvenientes: engordará el ejecutable 2.2Kb y sólo vale para pocas salidas formateadas porque son más líneas de código que antes; aunque algo más intuitivo (si te resulta incómoda la forma de formatear de printf(), aunque este método es menos potente).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int contador=0;

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

void loop()
{
  String texto;
  texto="Transcurridos "+contador;
  texto=texto+" segundos desde la ultima vez que se nos ocurrio poner en pantalla el valor del contador";
  Serial.println(texto);

  delay(1000);
}

Usando vsnprintf()

Llegamos al primer método interesante y es que nos permitirá hacer múltiples salidas, incluso escribir una función con la que podemos crear esa salida. Este método incrementará el binario en 1.5Kb, y nos permitirá fácilmente añadir salidas formateadas al estilo printf(), para ello crearemos la función printSerial(), que aceptará múltiples argumentos y se los pasará a la función vsnprintf(). Para esta función también debemos saber el número de bytes que ocupará la cadena, por lo que estableceremos un máximo: MAX_CADENA.

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
#include <stdarg.h>
#define MAX_CADENA 128
int contador=0;

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

void printSerial(const char *fmt, ...)
{
  char tmp[MAX_CADENA];
  va_list args;

  // Obtenemos la lista de argumentos
  va_start (args, fmt );
  // Escribimos en tmp, con tamaño MAX_CADENA, la cadena de formato será fmt y los
  // argumentos args
  vsnprintf(tmp, MAX_CADENA, fmt, args);
  va_end (args);

  Serial.print(tmp);  
}

void loop()
{
  printSerial("Transcurridos %d segundos desde que se inicio.", contador++);

  delay(1000);
}

Y con una pequeña modificación podemos especificar en qué Serial escribir (si tenemos Arduino Mega, por ejemplo), por unos pocos bytes más:

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
#include <stdarg.h>
#define MAX_CADENA 128
int contador=0;

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

void printSerial(HardwareSerial &sp, const char *fmt, ...)
{
  char tmp[MAX_CADENA];
  va_list args;

  // Obtenemos la lista de argumentos
  va_start (args, fmt );
  // Escribimos en tmp, con tamaño MAX_CADENA, la cadena de formato será fmt y los
  // argumentos args
  vsnprintf(tmp, MAX_CADENA, fmt, args);
  va_end (args);

  sp.print(tmp);  
}

void loop()
{
  printSerial(Serial, "Transcurridos %d segundos desde que se inicio.", contador++);

  delay(1000);
}

Esta función podrá ser copiada en vuestro código o añadida como una librería.

Número variable de argumentos en una función de C

Lunes, 24 de Enero de 2011 Gaspar Fernández 3 comentarios

Si habéis programado en C, seguramente la primera función que aprendisteis a utilizar era printf() ; y en todo el tiempo que pasasteis estudiando C no encontrasteis una función similar. Y es que esta función puede tener un número indeterminado de parámetros y es capaz de analizarlos todos.

Y es que quitando printf(), scanf() y derivadas de estas, la necesidad de una función con múltiples parámetros casi no se nos presenta. Aunque tal vez, alguna vez necesitemos crear una función con parámetros opcionales, es decir, si están presentes se tomarán y si no, se tomará un valor por defecto.

Esto lo podemos hacer con la biblioteca stdarg.h la cual nos proporciona macros para hacer esto posible.

Echemos un vistazo al siguiente código:

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

void poneargs(int nargs, ...)
{
  va_list ap;
  int i;
  int num;

  va_start(ap, nargs);
  for (i =0; i<nargs; i++)
    {
      num=va_arg(ap, int);
      printf("Argumento %d = %d\n", i, num);
    }
  va_end(ap);
  printf("\n");
}

int main(int argc, char *argv[])
{
  poneargs(5, 6, 77, 445, 34, 23, 12, 43, 32);
  poneargs(2, 23, 94);
  poneargs(0);

  return EXIT_SUCCESS;
}

Aquí vemos cómo la función poneargs() nos coloca los valores de los argumentos que le hemos pasado, siendo el primero de ellos el número total de argumentos que tenemos.
Con esto, hay que dejar claro que no tenemos forma de contar los argumentos que pasamos (igual que printf, podíamos pasar tantos argumentos como %[x] tuviéramos en la cadena de formato). Así que las técnicas para llegar al límite de los comandos son:

  • Poner un argumento indicando el número de argumentos a colocar.
  • Poner un argumento con un valor fijo al final que nos indique que hemos llegado al último parámetro.
  • Poner códigos con los argumentos que tenemos que leer. Idea de printf()

Lo que sí que tenemos que tener en cuenta es que, como estamos acostumbrados cada argumento tiene un tipo fijo, y se lo tenemos que decir a va_arg() en su segundo argumento; y va_arg() nos va a devolver una variable del mismo tipo que le dijimos (éste es el valor del argumento).

En el siguiente ejemplo creamos una función new_cliente() que puede tener hasta cuatro argumentos de tipo cadena de caracteres. De hecho son opcionales. Cuando creamos un cliente en un programa real puede que no tengamos todos sus datos y puede que algunos sean opcionales:

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

void new_cliente(int nargs, ...)
{
  va_list ap;

  char *valor;

  va_start(ap, nargs);

  switch (nargs)
    {
    case 4:
      valor=va_arg(ap, char*);
      printf("Twitter: %s\n", valor);      
    case 3:
      valor=va_arg(ap, char*);
      printf("URL: %s\n", valor);      
    case 2:
      valor=va_arg(ap, char*);
      printf("Blog: %s\n", valor);      
    case 1:
      valor=va_arg(ap, char*);
      printf("Nombre: %s\n", valor);
      break;
    default:
      printf("Número de parámetros incorrecto\n");
    }
 
  va_end(ap);
  printf("\n");
}

int main(int argc, char *argv[])
{
  printf("Un parámetro:\n");
  new_cliente(1, "Gaspy");

  printf("Dos parámetros:\n");
  new_cliente(2, "Gaspy", "Poesía Binaria");

  printf("Tres parámetros:\n");
  new_cliente(3, "Gaspy", "Poesía Binaria", "http://totaki.com/poesiabinaria/");

  printf("Cuatro parámetros:\n");
  new_cliente(4, "Gaspy", "Poesía Binaria", "http://totaki.com/poesiabinaria/", "http://twitter.com/blakeyed");

  return EXIT_SUCCESS;
}

Esto también podría valernos como una pequeña simulación de sobrecarga de funciones (estilo C++) aunque en C no cumpliría la tarea de hacer más legible el código ya que en la misma función tendríamos que incluir las opciones de los argumentos.

Texto personalizado en matlab

Martes, 26 de Mayo de 2009 blakeyed Sin comentarios

Cuando trabajamos con Matlab® a la hora de poner los datos de una variable en pantalla, basta con poner la variable (sin ; al final), pero si queremos escribir un texto cualquiera, debemos utilizar disp():

1
disp('Estoy escribiendo un mensaje en matlab');

Pero si queremos intercalar información o escribir nuestras variables de una forma más elegante, debemos utilizar sprintf(), se comporta como el sprintf() de C, y acepta sus \n, \t, %d, %f, %s… incluso podemos formatear la salida como veremos en el ejemplo. Para más información acerca de lo que podemos introducir en la función, es mejor acudir a help.

1
disp(sprintf('El número pi: %1.40f\n', pi));

Esta función combinada con disp(), como vemos arriba, trabaja muy parecido a printf() de C.
Pero,
¿qué hace sprintf() cuando en vez de un número le pasamos una matriz? Insertará todos los elementos seguidos, como si de un número muy largo se tratase.
¿cómo pongo un signo %? disp(sprintf(’%%’));

Categories: General Tags: , , ,

Visita otras webs de la red