AVR上C语言中使用printf()函数处理浮点数的问题

7

我最近重新开始学习C语言,并且正在进行一个“学术性”的练习,以再次磨练一些旧技能。我的项目围绕着生成正弦波的相对简单的过程展开。一开始我只是在x86上使用命令行编码(Fedora Core 20),一切都很顺利。然后我将代码迁移到AVR上,我开始学到了很多东西。例如,如何将UART设置为标准输出设备。但是,由于正弦波是浮点数,我在使用printf和sprintf时遇到了问题。

程序生成30个正弦波,然后将值打印到终端。文本“Sine:”可以正确打印,但是浮点数部分却出现了问号。将变量替换为常量没有任何效果。

第一件事情是有人建议我是否记得链接器选项以支持完整的浮点数-的确,我忘记了。但是,将此添加到我的makefile中没有任何效果。

我不确定这里复制和粘贴代码的政策:我应该在这里粘贴它们和我的makefile以供检查吗?

编辑:抱歉长时间未回复。我做了更多的阅读,包括第一个答案链接到的内容。我之前已经阅读过那个参考文献(GNU),并将链接包含在我的Makefile中,这就是我感到困惑的原因。下面是我的Makefile:

P               = sines
OBJ             = sines.o
PROGRAMMER      = buspirate
PORT            = /dev/ttyUSB0
MCU_TARGET      = atmega328p
AVRDUDE_TARGET  = atmega328p
HZ              = 16000000
DEFS            =
LIBS            = -lprintf_flt -lm
CC              = avr-gcc

override CFLAGS = -g -DF_CPU=$(HZ) -Wall -O1 -mmcu=$(MCU_TARGET) $(DEFS)
override LDFLAGS= -Wl,-Map,$(P).map -u,vfprintf

OBJCOPY     = avr-objcopy
OBJDUMP     = avr-objdump

all: $(P).elf lst text

$(P).elf: $(OBJ)
    $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LIBS)

clean:
    rm -rf *.hex *.bin *.map *~ sine*.csv *.o $(P).elf *.lst

lst: $(P).lst

%.lst: %.elf
    $(OBJDUMP) -h -S $< > $@

text: hex bin

hex: $(P).hex
bin: $(P).bin

%.hex: %.elf
    $(OBJCOPY) -j .text -j .data -O ihex $< $@

%.bin: %.elf
    $(OBJCOPY) -j .text -j .data -O binary $< $@

install:  $(P).hex
    avrdude -p $(AVRDUDE_TARGET) -c $(PROGRAMMER) -P $(PORT) -v -U flash:w:$(P).hex 

我担心的是链接器参数可能没有按正确顺序排列?从我所了解的情况来看,它们是按照正确的顺序排列的,但是......
我相当确定我的代码本身没问题。如果需要,我也可以在这里发布它。 另外,感谢您将我的问题转移到这里。我不太明白两者之间的区别!
以下是源代码。它正在运行在ATmega328P上。这个当前版本打印一个常量作为调试,而不是从sinescalc()得到的结果,尽管我知道那个函数是工作的(至少应该是的,我非常确定我曾经使用avr-gdb检查过 -- 它在命令行上肯定可以工作,并且在MSP430上也可以工作)。
#include <avr/io.h>
//#include <util/delay.h>
#include <string.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>


static int uart_putchar(char c, FILE *stream);

static FILE mystdout = FDEV_SETUP_STREAM(uart_putchar, NULL, _FDEV_SETUP_WRITE);

static int uart_putchar(char c, FILE *stream) {
    if (c == '\n')
        uart_putchar('\r', stream);
    while(!(UCSR0A & (1<<UDRE0)));
    UDR0 = c;
    return 0;
}

void sinescalc(double *sinptr, int cycles, int size) {

    double pi = acos(-1);
    double step = ((pi * (cycles*2))/ size);

    float temp;
    double z = step;

    int y;
    for(y = 0; y<= size; y++) {
        temp = sin(z); // calculate the current sine
        *sinptr = (double)temp; // pass it into the array from main()
        z += step; // add the step value to prepare for next sine
        sinptr++; // should move the pointer by correct size of variable
    }
}

int main(void) {

    unsigned long _fosc = 16000000;
    unsigned int _baud = 19200;
    unsigned long _myubrr = _fosc/16/_baud-1;
    unsigned int array_size = 256;

    UBRR0L = (unsigned char)_myubrr;
    UCSR0B = (1<<RXEN0)|(1<<TXEN0); //enable receiver and transmitter

    stdout = &mystdout;
    double sines[array_size];
    double *sinepointer = sines; // set sinepointer to first element of sines array

    sinescalc(sinepointer, 2, array_size); // calculate two cycles of sine, with 255 data points

    int y;
    //char msg[6] = ("Sine: ");
    char output[40];

    for(y = 0; y <= array_size; y++) {
        sprintf(output, "Sine:\t %.6f", 1.354462);
        printf(output);
        printf("\n");
    }



    return 0;
}

1
非常感谢阅读本指南。这个问题应该发布在 StackOverflow.com 上,因为它涉及到具体实现或编译器问题。插入一些小的代码片段并引导回答者来解决问题也是好的和适当的。我猜测你的浮点数正在被提升为双精度,格式化字符串需要正确设置。 - msw
1
如果您能展示出现问题的代码,特别是调用printf等函数的地方,那将会很有帮助。另外,请说明您正在使用哪个编译器以及工作环境。 - nos
我正在使用avr-gcc,并且如果需要的话,我可以向您展示所有我的源代码。实际上,我将把它附加到我的上面的问题中。 - Aurelius
3个回答

5

我解决了它。对于那些想知道的人,这确实归结于在 -Wl 后面参数的顺序。以下是最终 makefile 的内容,看起来是可行的(目前,在模拟器中):

P           = sines
OBJ         = sines.o
PROGRAMMER  = buspirate
PORT        = /dev/ttyUSB0
MCU_TARGET  = atmega328p
AVRDUDE_TARGET  = atmega328p
HZ          = 16000000
DEFS        =
LIBS        = -lprintf_flt -lm
CC          = avr-gcc

override CFLAGS = -g -DF_CPU=$(HZ) -Wall -O2 -mmcu=$(MCU_TARGET) $(DEFS)
override LDFLAGS= -Wl,-u,vfprintf,-Map,$(P).map

OBJCOPY     = avr-objcopy
OBJDUMP     = avr-objdump

all: $(P).elf lst text

$(P).elf: $(OBJ)
    $(CC) $(CFLAGS) $(LDFLAGS) $(LIBS) -o $@ $^

我之前以为我尝试过那个顺序,但它会抛出错误。我认为我之前做错的是在vfprintf后忘记加逗号。另一个网站的建议是将-u,vfprintf放在LDFLAGS之后(-o部分之后),这显然是不正确的。
2018年的编辑: 最近的GCC版本似乎要求库作为最后一个参数,无论其他参数如何。如果您在编译时遇到问题(我不确定各种发行版正在分发哪些版本的gcc / avr-gcc),并且收到一堆“x函数的隐式声明”错误,则是因为库参数位于错误的位置。这最近也困扰了我,我还没有看到有关为什么发生这种变化或哪些版本受到影响的任何信息。
为什么它必须恰好放在那个位置,我还不知道。也许有人可以阐明一下?我不敢相信答案像这样无聊。我只是开始移动东西,直到我找到这个。然而,在再次查看GNU文档后,他们显示-Wl,直接在-u,vfprintf之前。让我困惑的是 -Map,$(P).map,我认为它也必须直接放在-Wl后面。看起来-lprintf_flt -lm可以放在后面。我知道-lm是链接GNU数学库的选项,这对于浮点数学很重要。我也理解其他两个选项的作用(链接具有编译支持浮点支持的流函数的正确版本)。但是像我之前说的那样,也许有人可以指向一个关于gcc链接器选项层次结构的资源?这个问题困扰了我一周,没有人能够指出-Map之后仍然需要逗号。我可能会尝试翻转-Map和-u选项,它们仍然带有逗号,看看它是否具有层次结构上的重要性......它不是。只需将其更改为-Wl,-Map,sines.map,-u,vfprintf,t仍然可以正常工作。所以答案与逗号有关,这意味着所有链接器选项都需要使用逗号连接吗?为什么-lm不需要在那里呢?我有点困惑,但松了一口气,因为它正在工作。现在我只需要在硬件上尝试它,虽然我非常确定它会正常工作。
谢谢大家的帮助!这是Stack Overflow(以及所有堆栈)的绝佳介绍,我真的希望我能学到很多,并为这个社区做出贡献。我仔细阅读了所有关于提问好问题以及声望和投票系统如何工作的文章,所以希望我能做对,不要惹恼任何人 :)
干杯!

5

这个页面说明了要获得支持打印浮点数的完整printf()实现,您应该使用以下代码:

-Wl,-u,vfprintf -lprintf_flt -lm

是的,我知道,那就是我添加的,不是吗?我尝试使用整个字符串,有人建议我按现在的方式分解它,但两者仍然为浮点数给了我问号。 我如何将整个字符串与-Map,$(P).map集成而不破坏它?我似乎无法理解这里参数的层次结构。 - Aurelius
谢谢您的评论——它让我找到了正确的答案!实际上,您的答案是正确的,只是我不知道如何考虑-Map选项。 - Aurelius
现在我有足够的声望来为你的答案点赞了!再次感谢你的帮助 :) - Aurelius

0

在打印浮点数之前,您可以将其转换为字符串(可以是双精度,但在avr-gcc中直到v9实现与浮点数相同),并且您可以通过以下方式进行操作而无需进行链接器修改:

float in_volt = temp/1024.0*5.0; 
char in_volt_string[8]; 
dtostrf(in_volt, 6, 4, in_volt_string); 

这对我在AVR Studio 4中解决了问题,使用Atmega32。然后将其放到液晶屏和Tera Term中,没有出现任何问题。


有趣,下次遇到这种情况我得好好看看。有趣的是,像这样的旧帖子现在还会引起关注! - Aurelius
1
实际上,使用 avr-gcc 时,float 等同于 double(即 double 不符合 C 语言标准)。因此,这个解决方案不是关于 float vs. double,而是关于使用 dtostrf 而不是 *printf。有关 double 布局,请参见 avr-gcc ABI - emacs drives me nuts

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