tsaiwn 发表于 2015-4-5 12:17:45

【教程】用String( )做类似printf("aa=%d, BB=%f",Vint, vFloat)格式化输出

本帖最后由 tsaiwn 于 2015-4-5 14:14 编辑

虽然 Arduino 可以用 sprintf(charArray, "format", data, data2, ...);
但请注意,在 Arduino, 它的 "format" 不可用%f, %lf, %ll .., 不好用 !
用 C++ 的 String( ) 串接, 简单又好用 !

(一)简单用法, 不注重格式, 只要有印出就好
    要格式化输出, 最简单的方法就是直接用 C++ 的 String( ) 串接功能即可:
   int height=168;
   float weight=72.5;
   Serial.println(String("")+"Your Height="+height +   ", and Weight=" + weight);
说明:
      只要第一个是 String(""), 之后不论整数 int, long, 实数 float 等都会自动转为字符串, 用 + 串接在一起 !
缺点:
      float 小数点后会印出几位无法控制 :-(
   (注意 UNO 如果用 double 其实会被偷改为 float )

(二)那如果是要印到 LCD 或 SoftwareSerial软串口甚至 SPI呢?
    简单, 先放到 String 字符串即可, 之后爱怎样就怎样 :
   int height=168;
   float weight=72.5;
   String gy = String("")+"Your Height="+height +
                     ", and Weight=" + weight;

   Serial.println(gy);// 印到串口
   LCD.print(gy);// 假设你已经有 LCD. 可以用

(三)如果实数float要印出小数点后两位呢?
    也很简单, 只是看起来有点麻烦而已(其实你用 Serial.print(float)它也是偷偷这样做 !):
   int height=168;
   float weight=72.5;
   String gy = String("")+"Your Height="+height;
    gy += ", and Weight=";
    // 开始处理 float weight 的值
    long tmp = weight;
    gy += tmp; // 整数部分
    gy += ".";// 小数点, 废话
    tmp = (weight -tmp)*100+0.5;//小数, 四捨五入
    if(tmp >= 100) tmp=99; // 防错, Also see (四)的 防错
    if(tmp < 10) gy += "0";
    gy += tmp;
   Serial.println(gy);// 印到串口
   LCD.print(gy);// 假设你已经有 LCD. 可以用

(四)如果实数float 只要印出小数点后一位呢?
    也很简单, 参考著(三)写应该就会啦:
   int height=168;
   float weight=72.5;
   String gy = String("")+"Your Height="+height;
    gy += ", and Weight=";
    // 开始处理 float weight 的值
    long tmp = weight;// 整数部分
    long yytmp = (weight -tmp)*10+0.5;// 小数, 四捨五入
    if(yytmp >= 10) {// 防错
       tmp += 1;    // 整数部分 + 1
       yytmp = 0;
    }
    gy += tmp; // 整数部分
    gy += ".";// 小数点, 废话
    //if(tmp < 100) gy += "0";    // 小数点后 >= 3位
    //if(tmp < 10) gy += "0";    // 小数点后 >=2位
    gy += tmp;
   Serial.println(gy);// 印到串口
   LCD.print(gy);// 假设你已经有 LCD. 可以用

(五)如果实数float 要印出小数点后三位呢?
    ㄟ, 阿我前面已经有(三)印到两位和(四)印到一位的范例,
    参考著写应该就会啦 !
    不过还有一简单写法,
    就是先把 float 稍微加工处理后用 String 串接 :

   float weight=72.12666;// 故意
   long prec = 1000; // 三位
   float aw = ((long)(weight*prec+0.5))/1.0/prec;    //注意 /1.0/prec;四捨五入
   String ans = String("Weight=") + aw;
   Serial.println(ans);
   LCD.print(ans);
   优点:
          简单, 要小数点后两位就 prec = 100;   要小数点后一位就 prec = 10;
       但请注意, 上面那 /1.0/prec 是必要的, 是让它先变为 float;
       因为 /1.0 左边已经是 long 整数, 如果不先 /1.0 就直接做 /prec则
       变 long / long 將是 long 不对, 因为 38/10 是 3 不是 3.8喔 !
   缺点:
       因为 Arduino 考虑 MCU 能力有限, 无法用 double,
       即使你写 double, 也会被偷改为 float;
       可是 float 的有效精准度只有 7 位左右,
       就是说 从最左边不是 0 的位开始只有七位到八位是可以信任的,
       也就是 float x = 123.45678999; 与 float x = 123.45678922;
       其实几乎是一样的, 这原因是因为 float 只有用 32 bits表示,
       其中 23 bits 加上一个隱藏的 bit 共 24 bits 存有效值 significand;
       所以把float 转换为 binary 之后只能保留左边 24 bits,
       这等於 binary 准確 24位, 相当於十进制的 24*0.3010=7.2位!
      参考:http://zh.wikipedia.org/wiki/IEEE_754
         (如看不到, 请用百度查询 "IEEE 754")

=============================================================

tsaiwn 发表于 2015-4-5 12:28:33


补充

没有float实数, 则可以用 sprintf( )
但你的程序码就会多大约 1.5KB !
注意在 Arduino 上不可以用在 float, double, 以及 long long 都不行!

int height=168;
float weight=72.5;
char cgy;// C 字符串; 自己要注意是否 66 bytes 够用, 別忘了 C 字符串须要多一 char 放 '\0' (就是整数 0)表示结束!!!
long wtmp = weight + 0.5; // 因为Arduino 的 sprintf( )不可用 float/double; 只好似捨五入为整数 long
sprintf(cgy, "Your Height=%d, and Weight=%ld", height, wtmp);
/// 注意 %d 是给 int 用(2 bytes); long 要用 %ld 才对喔 !
Serial.println(cgy);// 印到串口
LCD.print(cgy);// 假设你已经有 LCD. 可以用

参考: vfprintf()
关於 format 格式字符串:
http://www.atmel.com/webdoc/AVRLibcReferenceManual/group__avr__stdio_1gaa3b98c0d17b35642c0f3e4649092b9f1.html

http://www.atmel.com/webdoc/AVRLibcReferenceManual/group__avr__stdio_1ga6017094d9fd800fa02600d35399f2a2a.html

http://www.atmel.com/webdoc/AVRLibcReferenceManual/group__avr__stdio_1ga2b829d696b17dedbf181cd5dc4d7a31d.html

tsaiwn 发表于 2015-4-5 12:33:51

本帖最后由 tsaiwn 于 2015-4-5 12:36 编辑

tsaiwn 发表于 2015-4-5 12:28 static/image/common/back.gif
补充

没有float实数, 则可以用 sprintf( )


再补充
想要类似 %6.3f 印实数 float / double 可不可以 ?
   ** 在 UNO 上其实 double 会被偷改为 float,所以其实没有 double 可用 !
    要类似 %6.3f 印float实数也不难,
偷用 AVR Library 內的 dtostrf( ) 先把 float 转为 C 的字符串即可:
int height=168;
float weight=72.12666;// 故意
char cgy;
char www;// 放 weight 体重, 故意用 22 bytes, 注意位置一定要够用 !
///
dtostrf(weight, 6, 3, www);// 相当於 %6.3f
String gy = String("")+"Your Height="+height +
      ", and Weight=" + www);// 注意 www 是 weight 的字串格式
Serial.println(gy);// 印到串口
LCD.print(gy);// 假设你已经有 LCD. 可以用
/// 或是
sprintf(cgy, "Your Height=%d, and Weight=%s", height, www);
// 注意 www 是用 %s "印" 入 cgy 字符串內部!
// 这样 cgy 也是字符串, 是 C 的字符串, 不是 C++ 的 String
// 也可用来 print
Serial.println(cgy);// 印到串口
LCD.print(cgy);// 假设你已经有 LCD. 可以用

参考:
   https://github.com/vancegroup-mirrors/avr-libc/blob/master/avr-libc/libc/stdlib/dtostrf.c

   https://android.googlesource.com/toolchain/avr-libc/+/edcf5bc1c8da8cc4c8b560865d2a54b73c1b51d3/avr-libc-1.7.1/libc/stdlib/dtoa_prf.c

tsaiwn 发表于 2015-4-5 15:41:35

本帖最后由 tsaiwn 于 2015-4-8 23:06 编辑

tsaiwn 发表于 2015-4-5 12:33 static/image/common/back.gif
再补充
想要类似 %6.3f 印实数 float / double 可不可以 ?
   ** 在 UNO 上其实 double 会被偷改为 f ...


再补充 .. 再补充 ..

为何 Arduino 的 printf/sprintf 不支援 float / double / long long ?
   (A)前面说过 Arduino 的 double 根本是骗人的,
      因为 Arduino 的 CPU 是 8 bit CPU, 意思是大部分指令都只处理 8 bit,
      float 用 32 bit 已经很辛苦, double 用 64 bit岂不更辛苦 !?
      参考用百度查询 "IEEE 754" 看看就知道了!
   (B)在標准 C 的程序库大约有一百多个函数(不算入 C++ 的喔),
      其中很多函数都是一行两行就做完了,
      但是, 標准 C 的 printf( ) 却多达两千多行(包括相关的sprintf/vsprintf等) !
      想一想, 如果 Arduino 也让你真的可以像在 PC 或大型电脑上
      使用 printf( )/sprintf( ) 的所有功能,
      那你的 32KB 程序码空间可能就去掉六分之一囉!
   (C)其实就算是现在精简版的 printf/sprintf 也占用约1.5KB,
      只要你的程序码用了一行 sprintf( ) 或 printf( ),
      你编译出的机器码就会多大约 1.5KB,
      当然多用几行並不会再增加太多(只是多了参数传递与函数调用)!
   (D)前面用到的 dtostrf( ) 本身也占用大约 1.5KB;
      所以你可以故意用一行 dtostrf( ) 再重新编译看看它佔多少空间 !?
   (E)啥? 你说反正 Flash/ROM 程序码空间有 31.5KB 不怕喔 !?
      (以 Arduino IDE 1.0.6 为例, 有 32256 Bytes = 32768 - 512 Byte BootLoader)
      如果简单程序码当然没问题啦 !
      要注意, 使用C++的 String 字符串也是要多用大约 1.5KB的空间!
         当你隨著传感器或GSM/Wifi/Ethernet 一直加上去,
      你会发现很快的, 31.5KB 好像不太够用了!
      这时你就要想办法儘量节省著用囉,
      能不用的当然就不用 !
      例如, 用了 String 了(这好像比较好用吧),
      那就儘量不要再用 dtostrf( ) 以及 sprintf( );
         如果空间真的不够用, 且你须要更快的执行效能,
      则可能也不要用 String 类別, 因为 String 类別不但多用了 1.5KB,
      而且它比传统 C 的字符串(就是 char array[ ])处理慢数倍! (这我以前有写过 !)
      只是传统 C 的 strcat( ), strcpy( ), strncpy( ) 使用要很小心,
      且对大多数 Arduino 的入门者也不好用 :-(
      所以空间足够时当然先用 C++ 的 String 来处理方便多了 !

tsaiwn 发表于 2015-4-5 15:45:55

tsaiwn 发表于 2015-4-5 12:33 static/image/common/back.gif
再补充
想要类似 %6.3f 印实数 float / double 可不可以 ?
   ** 在 UNO 上其实 double 会被偷改为 f ...


如果你要研究 dtostrf( ) 以及其用到的 dtoa_prf ( );
可以参考:

   https://github.com/vancegroup-mirrors/avr-libc/blob/master/avr-libc/libc/stdlib/dtostrf.c

https://android.googlesource.com/toolchain/avr-libc/+/edcf5bc1c8da8cc4c8b560865d2a54b73c1b51d3/avr-libc-1.7.1/libc/stdlib/dtoa_prf.c


////// 为了方便想研究的查看, 复製到以下...

char *
dtostrf (double val, signed char width, unsigned char prec, char *sout)
{
    unsigned char flags;

    /* DTOA_UPPER: for compatibility with avr-libc <= 1.4 with NaNs      */
    flags = width < 0 ? DTOA_LEFT | DTOA_UPPER : DTOA_UPPER;
    dtoa_prf (val, sout, abs(width), prec, flags);
    return sout;
}
// If precision is < 0, the string is left adjusted with leading spaces.
// If precision is > 0, the string is right adjusted with trailing spaces.

//////////////////////////////////////////////////////////////////////////////
// 以下是int dtoa_prf ( )

#include "ftoa_engine.h"
#include "dtoa_conv.h"
#include "sectionname.h"

int
dtoa_prf (double val, char *s, unsigned char width, unsigned char prec,
          unsigned char flags)
{
    int exp;
    int n;
    unsigned char vtype;
    unsigned char sign;
    unsigned char ndigs;
    unsigned char buf;
    ndigs = prec < 60 ? prec + 1 : 60;
    exp = __ftoa_engine (val, (char *)buf, 7, ndigs);
    vtype = buf;
    sign = 0;   
    if ((vtype & (FTOA_MINUS | FTOA_NAN)) == FTOA_MINUS)
      sign = '-';
    else if (flags & DTOA_PLUS)
      sign = '+';
    else if (flags & DTOA_SPACE)
      sign = ' ';
    if (vtype & FTOA_NAN) {
      ndigs = sign ? 4 : 3;
      width = (width > ndigs) ? width - ndigs : 0;
      if (!(flags & DTOA_LEFT)) {
            while (width) {
                *s++ = ' ';
                width--;
            }
      }
      if (sign) *s++ = sign;
      if (flags & DTOA_UPPER) {
            *s++ = 'N';*s++ = 'A';*s++ = 'N';
      } else {
            *s++ = 'n';*s++ = 'a';*s++ = 'n';
      }
      while (width) {
            *s++ = ' ';
            width--;
      }
      *s = 0;
      return DTOA_NONFINITE;
    }
    if (vtype & FTOA_INF) {
      ndigs = sign ? 4 : 3;
      width = (width > ndigs) ? width - ndigs : 0;
      if (!(flags & DTOA_LEFT)) {
            while (width) {
                *s++ = ' ';
                width--;
            }
      }
      if (sign) *s++ = sign;
      if (flags & DTOA_UPPER) {
            *s++ = 'I';*s++ = 'N';*s++ = 'F';
      } else {
            *s++ = 'i';*s++ = 'n';*s++ = 'f';
      }
      while (width) {
            *s++ = ' ';
            width--;
      }
      *s = 0;
      return DTOA_NONFINITE;
    }
    n = (sign ? 1 : 0) + (exp>0 ? exp+1 : 1) + (prec ? prec+1 : 0);
    width = width > n ? width - n : 0;

    if (!(flags & DTOA_LEFT) && !(flags & DTOA_ZFILL)) {
      while (width) {
            *s++ = ' ';
            width--;
      }
    }
    if (sign) *s++ = sign;
    if (!(flags & DTOA_LEFT)) {
      while (width) {
            *s++ = '0';
            width--;
      }
    }
    ndigs += exp;                /* exp is resticted approx. -40 .. +40      */
    sign = buf;
    if ((vtype & FTOA_CARRY) && sign == '1')
      ndigs -= 1;
    if ((signed char)ndigs < 1)
      ndigs = 1;
    else if (ndigs > 8)
      ndigs = 8;
    n = exp > 0 ? exp : 0;
    do {
      if (n == -1)
            *s++ = '.';
      flags = (n <= exp && n > exp - ndigs) ? buf : '0';
      if (--n < -prec)
            break;
      *s++ = flags;
    } while (1);
    if ( n == exp && (sign > '5' || (sign == '5' && !(vtype & FTOA_CARRY))) )
      flags = '1';
    *s++ = flags;

    while (width) {
      *s++ = ' ';
      width--;
    }
    *s++ = 0;
    return 0;
}

suoma 发表于 2015-4-6 13:20:07

谢谢分享学习一下
页: [1]
查看完整版本: 【教程】用String( )做类似printf("aa=%d, BB=%f",Vint, vFloat)格式化输出