Arduino: printf/fprintf打印浮点数时出现问号

11

我有以下代码用于Arduino草图:

#include <LiquidCrystal.h>

LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
static FILE lcdout = {0} ;

static int lcd_putchar(char ch, FILE* stream)
{
    lcd.write(ch) ;
    return (0) ;
}

void setup() {
  lcd.begin(16, 2);
  fdev_setup_stream (&lcdout, lcd_putchar, NULL, _FDEV_SETUP_WRITE);
}

void loop() 
{
  stdout = &lcdout;
  printf("%.2f Volts", 2.0);
}
问题出现在代码的最后一行。这应该打印出“2.00伏特”,但实际上它打印出“?伏特”(一个问号而不是实际的浮点值)。如果我尝试格式化一个整数,那么这个方法非常好用。
所以基本上,如果我用以下内容代替printf语句,则会正常工作:
printf("%d Volts", 2); //prints correctly "2 Volts"

有什么想法是什么问题吗?


1
一个无法处理浮点数转换的简化版stdlib? - Daniel Fischer
1
有趣的是,如果你只是使用lcd.print(2.0),它会准确地打印出你想要的内容。不需要sprintf或其他任何东西。而且你可以用可选的第二个参数指定小数点右侧的位数。例如,lcd.print(2,3)将给你"2.000"。 - James Newton
4个回答

14
AVR的GNU工具链(包括Arduino IDE)默认使用“压缩”的C标准库版本,例如,在格式化I/O函数中减少/去除了浮点支持(只是为了使printf()适合芯片几KB长的存储器中)。
如果您想要这个功能,您必须链接到另一个包含正常版本printf()的库,使用-Wl,-u,vfprintf -lprintf_flt 链接标志。

那么我需要在链接代码时提供这些链接器参数,还是需要重新编译库并提供这些链接器参数? - Nicu Surdu
1
@NicolaeSurdu 你不需要重新编译库,只需要在链接自己的代码时提供这些标志。 - user529758
我不会说是压缩过的。压缩过的意味着它具有所有的功能,但代码已经被混淆以节省空间。你的意思是通过删除某些功能来简化代码。 - Octopus
用户529758,我们需要把这些标志放在哪里?是在platform.txt文件中的“compiler.ldflags=”吗? - Akay

5

来自avr-libc文档的内容:

如果需要完整的功能,包括浮点数转换,则应使用以下选项:

-Wl,-u,vfprintf -lprintf_flt -l

请注意,如果您的MCU没有任何浮点支持,应尽量避免使用浮点运算。浮点运算将在软件中完成,这非常低效并且需要大量的闪存。


据我所知(我已经有一段时间没有玩AVRs了),你甚至不需要-lprintf_min,因为libc.a包含了被压缩的库。 - user529758
@H2CO3 我删掉了这行。我也认为如果你不使用完整的 printf_flt,那么你在 printf 中就没有任何浮点支持。 - ouah
是的,本质上这就是我的意思。你总是会得到 ? - user529758
在哪里需要放置这些标志?是在platform.txt文件中的“compiler.ldflags=”吗? - Akay

0

我有一些旧代码,如果你想完全避免使用printf并且只需要在小数点前后打印给定数量的数字,那么这些代码可能会有所帮助。这些代码可以在C中编译,并且在Arduino IDE中也可以正常工作。它几乎可以用更少的C++行完成。pow10可以通过程序实现,但是在我使用的C版本中不支持幂运算:

#include <stdio.h>

/*
Because lcd and serial don't support printf, and its very costly, and all we need
is simple formating with a certain number of digits and precision, this ftoa is enough.
If digits is negative, it will pad left.
*/
#define  BUF_LEN 20
char buf[BUF_LEN]; //need a buffer to hold formatted strings to send to LCD

int ftoa(char * str, float f, char digits, char precision) {
char i=0,k,l=0;
long a,c;
long pow10[10] = {1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000};
unsigned char b;
char decimal='.';

  if(digits>=10) {return 0;};
  // check for negative float
  if(f<0.0) {
    str[i++]='-';
    f*=-1;
    (0<digits?digits--:digits++);
    }
  a=(int)f; // extracting whole number
  f-=a; // extracting decimal part
  k = digits;
  // number of digits in whole number
  while(k>=0)   {
    c = pow10[k];
    c = a/c;
    if(c>0) { break; }
    k--;
    } // number of digits in whole number are k+1
  if (0<k && digits==k && c>10) { //overflow
    decimal = 'e';
    }
/*
extracting most significant digit i.e. right most digit , and concatenating    to string
obtained as quotient by dividing number by 10^k where k = (number of digit -1)
*/
  for(l=abs(k);l>=0;l--){
    c = pow10[l];
    b = a/c;
    str[i++]=(l&&!b?' ':b+48); //digit or pad
    a%=c;
    }
  if (precision) {str[i++] = decimal;};
/* extracting decimal digits till precision */
  if (0>precision) {k=0; precision=abs(precision);}
  for(l=0;l<precision;l++) {
    f*=10.0;
    b = (int)f; //math floor
    str[i++]=b+48; //48 is ASCII 0
    f-=(float)b;
    if (!k && 0==f) { break; } //nothing left, save chars.
    //won't work if there are any floating point errors.
    }
  str[i]='\0';
  return i;
  }

你可以在这里运行并尝试使用它: http://ideone.com/AtYxPQ

0
我做了这个:
unsigned char buffer[32];

void setup() {
  serial.begin();
}

void loop() {
  if(serial.available()) {
    int size = serial.read(buffer);
    if (size!=0) {
      //serial.write((const uint8_t*)buffer, size);
      int bright = atoi((char *) buffer);

      //int final = ((unsigned int)buffer[0]);

      //int final = bright -'0';
      serial.write(bright);
      serial.write('\n');
    }
  }
  serial.poll();
}

现在我通过 USB 发送 0-255 的值时,会得到一个 ASCII 字符。我应该找到一种将 ASCII 字符转换为整数的方法。

例如,我输入 65,它会打印出 A。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接