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); } |
Pingback: Bitacoras.com /