Anyone with experience with printf() sprintf() on AVRs? (both 8 and 32 bit) - Page 1
Forum section: electronics / general

author: MrAureliusR
author_rank: Unknown
timestamp: October 04, 2014, 09:27:35 pm
content: Hey everyone. I've been playing with a small project, mostly academic in nature, just to resharpen my C skills and learn a few new things along the way. In the past, I've always done my UART transmissions by just manipulating registers directly in my code. However I recently discovered how much easier the stdio.h in avr-libc can make things! So I've implemented it in this little project, and it's printing fine.The code calculates two cycles of sin(x), with 255 data points, and then prints them to UART as a debug (at this point). I actually wrote the code first on the command line in Linux, and it worked after compiling with gcc.Moving to AVR made things a little bit more complex. So far, I'm developing on an ATmega328P, which of course has no floating point, so it's all done in software (no big deal). However, when I get to the part where I print the value of the sine, it craps out. I'm using sprintf() to format the sine as a string, then printing the string using printf(). As it currently exists, the code just gives me ?'s in place of the values, so something is going wrong. Hopefully someone can point out the correct way to do this.Also, when I migrate this code to a 32-bit AVR (the chip on the Due), will it be just as simple to set up stdout to the UART? I'm programming both these boards directly (as if they were just dev boards) so I have to handle all this myself.Here's the code so far (compiles without errors, I can include my makefile if needed):Code:#include <avr/io.h>//#include <util/delay.h>#include <string.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);double temp;float z = step;int y;for(y = 0; y<= size; y++) {temp = (double)sin(z);*sinptr = temp;z += step;sinptr++; // should move the pointer by the size of a double}}int main(void) {unsigned long _fosc = 16000000;unsigned int _baud = 19200;unsigned long _myubrr = _fosc/16/_baud-1;unsigned int array_size = 255;UBRR0L = (unsigned char)_myubrr;UCSR0B = (1<<RXEN0)|(1<<TXEN0); //enable receiver and transmitterstdout = &mystdout;double sines;double *sinepointer = &sines; // set sinepointer to first element of sines arraysinescalc(sinepointer, 2, array_size); // calculate two cycles of sine, with 255 data pointsint y;char msg[] = ("Sine: ");char output;for(y = 0; y <= array_size; y++) {printf(msg);printf("\t"); // tabsprintf(output, "%2.4f", sines);printf(output);printf("\n");}return 0;}

author: f1rmb
author_rank: Regular Contributor
timestamp: October 04, 2014, 09:46:56 pm
content: Hi,As far as I can see, in sinescalc(), it iterates from 0 to 255, which is wrong IMHO (and same problem in the main)Cheers.---Daniel

author: nessatse
author_rank: Regular Contributor
timestamp: October 04, 2014, 10:01:00 pm
content: For starters, sizeof(double) is 8 bytes. Your sines array is 255*8 bytes. The 328 only has 2k or RAM.

author: westfw
author_rank: Super Contributor
timestamp: October 04, 2014, 10:02:01 pm
content: The "default" stdio libraries for AVR do not include floating point support. You need to add to your compile lineCode:-Wl,-u,vfprintf -lprintf_flt -lmSee here for details:http://www.nongnu.org/avr-libc/user-manual/group__avr__stdio.html(double in avr-gcc is still only 4 bytes, btw.)

author: c4757p
author_rank: Super Contributor
timestamp: October 04, 2014, 10:28:57 pm
content: Quote from: MrAureliusR on October 04, 2014, 09:27:35 pmThis Due? That's not AVR32, that's ARM, a completely different architecture. It just happens to also be made by Atmel.Of course, I think AVR32 is also completely different from AVR, though I've never used it.

author: MrAureliusR
author_rank: Unknown
timestamp: October 04, 2014, 10:46:11 pm
content: Quote from: westfw on October 04, 2014, 10:02:01 pmYes, that's already in my makefile. I had a feeling that doubles in avr-gcc were only 4 bytes, but I keep hearing conflicting things. If so, the array of double would take 954 bytes. The ATmega328P has 32k of flash, and the code takes up about 3k so far. And then the 2k of SRAM would be enough.Anyway, that's not the problem. My problem is still that the UART is displaying question marks, which I take to mean either sprintf() or printf() thinks it's input is NaN or something illegal? I have a feeling it's sprintf(), as printf() will just print whatever it's given. So why would sprintf() return a ?Also, yes, sorry it's ARM. I had a bit of a brain fart, I already knew it was ARM, lol.

author: MrAureliusR
author_rank: Unknown
timestamp: October 04, 2014, 10:47:56 pm
content: Quote from: f1rmb on October 04, 2014, 09:46:56 pmWhy is that 'wrong'? That's exactly what I want it to do, fill the array with the calculated sines...

author: IanB
author_rank: Super Contributor
timestamp: October 04, 2014, 11:03:13 pm
content: Quote from: MrAureliusR on October 04, 2014, 10:47:56 pmI often find it easier to think about such things with smaller numbers. Instead of 2^8 (= 256), consider 2^3 (= 8 ).If you write a loop like this:Code:for (int i = 0; i < 8; ++i) {}(which is the canonical way of writing a loop)then "i" will take on the values successively of 0, 1, 2, 3, 4, 5, 6, 7 -- eight values in total.So your array size needs to be  to hold this many values:Code:double sinesThe above statement will declare an array big enough to hold eight double values, indexed from sines to sines.By the same reasoning, an array to be indexed from 0 to 255 will need to be of size 256.

author: MrAureliusR
author_rank: Unknown
timestamp: October 04, 2014, 11:08:57 pm
content: Quote from: IanB on October 04, 2014, 11:03:13 pm*facepalm*Yup, knew that one too. Man I'm off my game lately.I've tried passing the output directly to printf() which doesn't seem to make a difference. Still prints out question marks for the numbers. Here's what I changed://char msg = ("Sine: ");//char output;for(y = 0; y <= array_size; y++) {//sprintf(output, "Sine:\t %.6f", sines);printf("Sine:\t %.6f", sines);printf("\n");}But again, doesn't seem to make any difference. I'm starting to get really confused here. I suppose I should boot into Windows and try and use debugging to look at the memory values in the AVR... although my AVR Dragon seems to have totally crapped out on me.

author: IanB
author_rank: Super Contributor
timestamp: October 04, 2014, 11:14:55 pm
content: Have you tried simpler tests, like for example:Code:printf("Sine:\t %.6f", 0.8660254);Just like debugging a circuit, you need to narrow down where the problem lies. Does it lie in the printf function, or in the sine calculations?

author: westfw
author_rank: Super Contributor
timestamp: October 05, 2014, 03:58:02 am
content: QuoteOk; let's see the makefile (or at least the resulting compile command.)I'm betting that the fp printf options aren't getting included, somehow.QuoteWhen I compile your sample here, it's about 3k with the normal printf, and closer to 5k with the fp printf.And I get actual numbers with the fp options, and "?" with the normal...

author: Brutte
author_rank: Frequent Contributor
timestamp: October 05, 2014, 09:51:34 am
content: Quote from: westfw on October 05, 2014, 03:58:02 am+1Discussed on avrfreaks about zillion times.

author: Jeroen3
author_rank: Super Contributor
timestamp: October 06, 2014, 10:06:07 am
content: Printf fp decimal is about 1KB of ARM Cortex M4F code. Should be much worse on AVR 8 bit.Try to avoid it if possible, because aside from being big, its not adding any value to the uart data the pc cannot add.For example, instead of displaying 1.024 Volts you write 1024 mV.

author: MrAureliusR
author_rank: Unknown
timestamp: October 07, 2014, 02:22:53 am
content: Here is my makefile:Code:P= sinesOBJ= sines.oPROGRAMMER= buspiratePORT= /dev/ttyUSB0MCU_TARGET= atmega328pAVRDUDE_TARGET= atmega328pHZ= 16000000DEFS=LIBS=CC= avr-gccoverride CFLAGS = -g -DF_CPU=$(HZ) -Wall -O1 -mmcu=$(MCU_TARGET) $(DEFS)override LDFLAGS= -Wl,-Map,$(P).map -u,vfprintf -lprintf_flt -lmOBJCOPY= avr-objcopyOBJDUMP= avr-objdumpall: $(P).elf lst text$(P).elf: $(OBJ)$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $^ $(LIBS)clean:rm -rf *.hex *.bin *.map *~ sine*.csv *.o $(P).elf *.lstlst: $(P).lst%.lst: %.elf$(OBJDUMP) -h -S $< > $@text: hex binhex: $(P).hexbin: $(P).bin%.hex: %.elf$(OBJCOPY) -j .text -j .data -O ihex $< $@%.bin: %.elf$(OBJCOPY) -j .text -j .data -O binary $< $@install: $(P).hexavrdude -p $(AVRDUDE_TARGET) -c $(PROGRAMMER) -P $(PORT) -v -U flash:w:$(P).hexI've tried changing it to just print a floating point number, and it still gives question marks.As I said, I did include that line in my makefile, but maybe someone can clarify if I'm using it wrong. I know that order matters in those two commands, perhaps I have those options in the wrong place?

author: westfw
author_rank: Super Contributor
timestamp: October 07, 2014, 04:06:24 am
content: I think your main problem is that the libraries have to come after the project file (which is why the template makefile has a separate LIBS and LDFLAGS. Also, you're missing a -Wl, to pass one of the link parameters to the linker (though perhaps that works anyway.) So that part should read:Code:DEFS=LIBS= -lprintf_flt -lmCC= avr-gccoverride CFLAGS = -g -DF_CPU=$(HZ) -Wall -O1 -mmcu=$(MCU_TARGET) $(DEFS)override LDFLAGS= -Wl,-Map,$(P).map -Wl,-u,vfprintf