I've been getting back into C, and I've been working on an 'academic' exercise to sharpen some old skills again. My project revolves around the rather simple process of generating sines. I started out just coding for x86 on the command line (Fedora Core 20) and everything worked fine. I then migrated the code to AVR, and I started learning a lot. For example, how to set up the UART as stdout. However, as sines are floating-point, I ran into problems using printf and sprintf.
The program generates 30 sines, and then prints the values to terminal. The text "Sine: " prints properly, but then I get question marks for the float. Replacing the variable with a constant had no effect.
The first thing I was suggested was if I had remembered the linker option for full floating point support - indeed I had forgotten. Again, adding this into my makefile had no effect.
I'm not sure of the policy here of copying and pasting code: should I paste it and my makefile here for inspection?
EDIT: Sorry for the long delay. I've done some more reading, including what the first answer links to. I had already read that reference before (the GNU one) and I have included the link in my Makefile, which is why I'm so confused. Here's my Makefile in all its glory:
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
What I'm concerned about is that perhaps the linker arguments aren't in the correct order? From what I can tell, they are but...
I'm fairly sure my code itself is fine. If wanted, I can post it here as well. Also, thanks for transferring my question over here. Didn't quite understand the difference between the two!
Here's the source code. It's being run on an ATmega328P. This current version is printing a constant as a debug, instead of the result from sinescalc(), even though I know that function is working (at least, it should be, I'm pretty sure I checked using avr-gdb at one point -- it definitely works on the command line, and also on an 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;
}
So I solved it. For those wondering, it really DOES come down to the order of arguments after -Wl. Here's the final makefile that seems to be working (so far, in a simulator):
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 $@ $^
I thought that I had tried that order before, and it threw an error. What I think I did wrong before was omit the comma after vfprintf. Also, the suggestion from another website of putting the -u,vfprintf AFTER the LDFLAGS (and after the -o section) was also clearly incorrect.
Edit from 2018: Recent versions of GCC seem to require the libraries to come as the last argument, no matter what other arguments there are. If you are having problems compiling (I'm not sure what versions of gcc/avr-gcc are being distributed by the various distros) and you are getting a bunch of "implicit declaration of x function" errors, it's because your library arguments are in the wrong spot. This just bit me recently, and I haven't seen any information on why this change happened, or which versions are affected.
Why it has to go in exactly that spot, I don't know yet. Perhaps someone can shed some light on it? I can't believe the answer was as inane as this. I just started moving things around until I hit on this. However, after looking at the GNU documentation again, they show the -Wl, directly before the -u,vfprintf. What threw me off was the presence of the -Map,$(P).map, which I thought had to go directly after -Wl as well. It seems like the -lprintf_flt -lm can come afterwards. I know that -lm is the option to link in the GNU math libraries, which is important for floating point math, obviously. I also understand what the other two options do (link in the correct version of the stream functions with floating-point support compiled in). But as I said before, perhaps someone can point me (and others) to a resource regarding hierarchy of gcc linker options? This problem has been dogging me for a week, and nobody was able to just point out that the -Map could come afterwards, but still needed a comma in between. I might try flipping around the -Map and the -u options, still with their commas, to see if it's THAT hierarchically important... It isn't. Just changed it to -Wl,-Map,sines.map,-u,vfprintf and t still works with no problem. So the answer had to do with commas, which I take it means that all linker options need to be attached with commas? Why doesn't -lm need to be there as well? I'm a littl baffled, but relieved that it's working. Now I just need to try it on the hardware, though I'm pretty sure it'll work just fine.
Thanks everyone, for your help! This has been a great introduction to Stack Overflow (and all the Stacks) and I really hope that I can learn a lot, and contribute to this community. I've been carefully re-reading all the articles about asking good questions, and how the reputation and voting system works, so hopefully I get it right and I don't piss anyone off :)
Cheers!!