Publi

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

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

También podría interesarte....

There are 2 comments left Ir a comentario

  1. Pingback: Bitacoras.com /

  2. Fool Me Once S01 Daniel Walker Hoodie /
    Usando Google Chrome Google Chrome 120.0.0.0 en Windows Windows NT

    I really like the way that you have expressed yourself. There is a lot to be admired from this post. You might want to click on

Leave a Reply