【教程】用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")
=============================================================
补充
没有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: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-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 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;
}
谢谢分享学习一下
页:
[1]