Archivo

Entradas Etiquetadas ‘sprintf’

Utilizar float con sprintf() y derivados en Arduino

Miércoles, 7 de Diciembre de 2011 Gaspar Fernández 2 comentarios

globo-de-aire
El objetivo de la plataforma Arduino es que los programas sean pequeños, ya que tienen que caber en pocos Kbs. Un pequeño problema que tenemos con eso es que muchas bibliotecas no están implementadas completamente, sino que las encontramos en su versión light, en las que implementan sólo las funcionalidades más normales.
Un ejemplo de ello son los comandos printf() y scanf() cuya funcionalidad no cubre los valores de punto flotante (float, double), y que si hacemos el siguiente programa:

1
2
3
4
5
6
7
8
9
10
11
12
void setup()
{
  Serial.begin(9600);
}

void loop()
{
  char buffer[30];
  sprintf (buffer, "Valor: %f\n", 25.4);
  Serial.println(buffer);
  delay(1000);
}

Veremos constantemente:


Valor: ?
Valor: ?
Valor: ?

Y no muestra el valor deseado. Eso sí, ocupa 4.29Kb

La clave para la solución

El tema está en que el compilador no enlaza con las bibliotecas buenas, sino que enlaza con las ligeras. Por lo que tenemos que hacer que éste enlace con las bibliotecas correctas añadiendo el siguiente parámetro al compilador avr-gcc:

-Wl,-u,vfprintf -lprintf_flt

Haciendo el cambio para SConstruct

Si para compilar tus proyectos de Arduino utilizas este método, tienes que modificar una línea en el archivo SConstruct… pongo parte del contexto, cerca de la línea 170, donde pone:

1
2
3
4
5
6
envArduino.Append(BUILDERS = {'Processing':Builder(action = fnProcessing,
        suffix = '.cpp', src_suffix = '.pde')})
envArduino.Append(BUILDERS={'Elf':Builder(action=AVR_BIN_PREFIX+'gcc '+
        '-mmcu=%s -Os -Wl,--gc-sections -o $TARGET $SOURCES -lm'%MCU)})
envArduino.Append(BUILDERS={'Hex':Builder(action=AVR_BIN_PREFIX+'objcopy '+
        '-O ihex -R .eeprom $SOURCES $TARGET')})

Debemos escribir:

1
2
3
4
5
6
envArduino.Append(BUILDERS = {'Processing':Builder(action = fnProcessing,
        suffix = '.cpp', src_suffix = '.pde')})
envArduino.Append(BUILDERS={'Elf':Builder(action=AVR_BIN_PREFIX+'gcc '+
        '-mmcu=%s -Os -Wl,--gc-sections -o $TARGET $SOURCES-Wl, -u,vfprintf -lprintf_flt -lm'%MCU)})
envArduino.Append(BUILDERS={'Hex':Builder(action=AVR_BIN_PREFIX+'objcopy '+
        '-O ihex -R .eeprom $SOURCES $TARGET')})

Con esta modificación, el programa en Arduino (el binario ocupa 5.8Kb) debe dar:

Valor: 25.400000
Valor: 25.400000
Valor: 25.400000

Más información

La IDE de Arduino hace las llamadas al compilador desde su código fuente en lugar de utilizar un Makefile, por ejemplo, por lo que para compilar incluyendo las instrucciones del linkador en el código fuente del IDE. El problema de esto es que no hay una forma fácil de conmutar el linkado con printf_flt y printf_min (que es como se llama la versión light).

Con scanf() también se puede

scanf() también permite la inclusión de la biblioteca que es capaz de leer floats. La instrucción que hay que escribir es:

-Wl,-u,vfscanf -lscanf_flt

Foto: Eric Lim Photography (Flickr) compartido con CC-by a 30/11/2011

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);
}

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