NerdKits - electronics education for a digital generation

You are not logged in. [log in]

NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.

Microcontroller Programming » sprintf won't convert to a float.

July 13, 2011
by Robotnik
Robotnik's Avatar

I am making my own functions for displaying things with a MCU and I am almost done. There is just one problem, when I use sprintf to convert floats to a char array and display it, all it displays is '?'. Does anyone know what im doing wrong?

this is my code...

char buffer[20];

float number = 3.14;

sprintf(buffer,"%5.2f",number);

print_string(buffer); // I wrote this function, and tested it. It works.

July 13, 2011
by Noter
Noter's Avatar

The easiest thing to do is use sprintf_P and PSTR( ) -

sprintf_P(buffer,PSTR("%5.2f"),number);
July 15, 2011
by Robotnik
Robotnik's Avatar

What header are sprintf_P() and PSTR() in ? Because I am trying to right my own functions for displaying data to the lcd, just to learn how it works. So I don't want to include the nerdkit's lcd.h or anything like that, because then I might as well use their display functions.

July 15, 2011
by Noter
Noter's Avatar
#include <avr/pgmspace.h>
July 16, 2011
by BobaMosfet
BobaMosfet's Avatar

Instead of assuming it is sprintf() - which your implementation is correct, why don't you output what the value of sprintf() is prior to entering your function (which is likely where the problem is).

Here's my version and it works. Width is 5 chars, default left-padded with a space, per the C standard spec.

/**********
 *
 *  EXAMPLE SPRINTF() USAGE
 *
 *****/

/*****
 *
 *  INCLUDES
 *
 *****/

#include <stdio.h>

/*****
 *
 *  PROTOTYPES
 *
 *****/

int main(void);                 // Main function

/*****
 *
 *  MAIN
 *
 *****/

int main(void)
    {
    char    buf[20];                    // char buffer
    float   num;
    int     len;
    int     i;

    num = 3.14;
    sprintf(buf,">>%5.2f<<",num);           // Do the conversion

    len = strlen(buf);
    for(i=0;i<len;i++)
        putchar(buf[i]);
    }

BM

July 16, 2011
by Noter
Noter's Avatar

BM, this is the result of compiling your code -

avrTest.c:37: warning: format '%5.2f' expects type 'double', but argument 3 has type 'float'
avrTest.c:39: warning: implicit declaration of function 'strlen'
avrTest.c:39: warning: incompatible implicit declaration of built-in function 'strlen'
avrTest.c:42: warning: control reaches end of non-void function
July 16, 2011
by Robotnik
Robotnik's Avatar

I tried using sprintf_P and PSTR() and it did the same thing. I have also tried printing the chars one at time, like BM suggested, and that gave me the same result, a '?' right adjusted in the field. I have tested sprintf with integers and it works fine. I have also tried using sprintf() on my computer and it works as expected. :/

this is my main function...

#define F_CPU 1000000UL

#include <avr/io.h>
#include <util/delay.h>
#include "lcd.h"  // not the nerdkits lcd.h, i wrote this
#include <avr/pgmspace.h>
#include <stdio.h>

int main()

{ 
  char array[20];
  double a = 3.14;
  sprintf_P(array, PSTR("%5.2f"), a);

  lcd_init(15); // my lcd_init function
  print_string(array); // my print string function

  while(1)
  {
  }
}
July 16, 2011
by Noter
Noter's Avatar

We'll have to see your lcd.h and any other source to be of more help. Could be a problem in print_string(). If you want to post it I'll load it and see if I get the same results and go from there. It would be a good idea to name your module a little different from the nerdkits one then maybe you could use them both at the same time and see if the same ? for array is displayed with each.

July 16, 2011
by Robotnik
Robotnik's Avatar

Okay, there are 3 functions, lcd_init(), print_char(), and print_string(). I have the lcd wired up like this... pin 1 to ground, pin 2 to +5, pin 3 to contrast resistor, pin 4 to PB3, pin 5 to PB4, pin 6 to PB5, pin 11 to PC2, pin 12 to PC3, pin 13 to PC4, and pin 14 to PC5.

unsigned int BUS_TIMING;

// sets up the lcd screen
// one argument: bus_timing, the microsecond delay between each message. 15 microseconds is best.

void lcd_init(unsigned int bus_timing)
{
  BUS_TIMING = bus_timing;
  _delay_us(BUS_TIMING);

  // initialize message pins
  DDRB |= (1<<PB3) | (1<<PB4) | (1<<PB5);

  // initialize digit trunk
  DDRC |= (1<<PC5) | (1<<PC4) | (1<<PC3) | (1<<PC2);

  // set to 4 bit mode
  PORTB &= ~((1<<PB3) | (1<<PB4) | (1<<PB5));
  PORTC &= ~((1<<PC2) | (1<<PC3) | (1<<PC4) | (1<<PC5));
  PORTC |= (1<<PC3);
  PORTB |= (1<<PB5);
  _delay_us(BUS_TIMING);
  PORTB &= ~(1<<PB5);
  _delay_us(BUS_TIMING);

  // enable display with blinking cursor
  PORTB &= ~((1<<PB3) | (1<<PB4) | (1<<PB5));
  PORTC &= ~((1<<PC2) | (1<<PC3) | (1<<PC4) | (1<<PC5));
  PORTB |= (1<<PB5);
  _delay_us(BUS_TIMING);
  PORTB &= ~(1<<PB5);
  _delay_us(BUS_TIMING);
  PORTC |= (1<<PC2) | (1<<PC3) | (1<<PC4) | (1<<PC5);
  PORTB |= (1<<PB5);
  _delay_us(BUS_TIMING);
  PORTB &= ~(1<<PB5);
  _delay_us(BUS_TIMING);

  // reset display
  PORTB &= ~((1<<PB3) | (1<<PB4) | (1<<PB5));
  PORTC &= ~((1<<PC2) | (1<<PC3) | (1<<PC4) | (1<<PC5));
  PORTB |= (1<<PB5);
  _delay_us(BUS_TIMING);
  PORTB &= ~(1<<PB5);
  _delay_us(BUS_TIMING);
  PORTC |= (1<<PC2);
  PORTB |= (1<<PB5);
  _delay_us(BUS_TIMING);
  PORTB &= ~(1<<PB5);
  _delay_us(BUS_TIMING);
}

void print_char(char charactor)
{
  PORTB |= (1<<PB3);

  // send first 4 bits
  if(charactor & (1<<4))
  {
    PORTC |= (1<<PC2);
  }
  else
  {
    PORTC &= ~(1<<PC2);
  }
  if(charactor & (1<<5))
  {
    PORTC |= (1<<PC3);
  }
  else
  {
    PORTC &= ~(1<<PC3);
  }  
  if(charactor & (1<<6))
  {
    PORTC |= (1<<PC4);
  }
  else
  {
    PORTC &= ~(1<<PC4);
  }
  if(charactor & (1<<7))
  {
    PORTC |= (1<<PC5);
  }
  else
  {
    PORTC &= ~(1<<PC5);
  }
  PORTB |= (1<<PB5);
  _delay_us(BUS_TIMING);
  PORTB &= ~(1<<PB5);
  _delay_us(BUS_TIMING);

  // send second 4 bits
  if(charactor & (1<<0))
  {
    PORTC |= (1<<PC2);
  }
  else
  {
    PORTC &= ~(1<<PC2);
  }
  if(charactor & (1<<1))
  {
    PORTC |= (1<<PC3);
  }
  else
  {
    PORTC &= ~(1<<PC3);
  }  
  if(charactor & (1<<2))
  {
    PORTC |= (1<<PC4);
  }
  else
  {
    PORTC &= ~(1<<PC4);
  }
  if(charactor & (1<<3))
  {
    PORTC |= (1<<PC5);
  }
  else
  {
    PORTC &= ~(1<<PC5);
  }
  PORTB |= (1<<PB5);
  _delay_us(BUS_TIMING);
  PORTB &= ~(1<<PB5);
  _delay_us(BUS_TIMING);

}

void print_string(char* string)
{
  unsigned short int length = 0;
  while(string[length] != 0)
  {
    length++;
  }

  unsigned short int i;
  for(i = 0;i<length;i++)
  {
    print_char(string[i]);
  }

}
July 16, 2011
by Noter
Noter's Avatar

That was a little more involved than I expected but I have it going and I get "14" on the display with a blinking cursor so the 1st couple of characters "3." are not showing up. I think you are missing some steps and delays in the lcd initalization routine. Look at the nerdkit lcd.c lcd_init() and you'll see what I mean. You can also find the 4 bit initalization sequence in the HD44780 datasheet (pp 46 in my copy). I'm running on a 328p with a 18.432Mhz crystal so that may be why I don't see exactly the same "?" as you do? Or maybe a wire is crossed?

July 16, 2011
by Noter
Noter's Avatar

BTW - are you really running your mcu at 1Mhz? It's important to have the F_CPU match the actual frequency of your mcu clock so delays are accurate.

July 17, 2011
by Noter
Noter's Avatar

I inserted initalization commands like in the nerdkit lcd_init() and still didn't work. So then I removed that code and changed the delay from 15 to 1000 and it works on power up but not on reset.

So, the sprintf is working fine and the issue is one of timing. It's probably a good idea to follow the initalization sequence even though the lcd might work without it. Then use the same delays as the nerdkit guys do in their lcd subroutines and you will be on your way.

Another good practice is to use the ready/busy indicator from the display instead of just waiting a while before sending another command or data nibble/byte. After the display is initialized you can query and wait pending the busy flag value instead of relying on timeouts. Maybe do that after you get it working with timeouts.

July 17, 2011
by Robotnik
Robotnik's Avatar

This is strange, I have tried printing all of the following and they work for me...

print_string("hello world");

print_string("3.14");

print_string("123456");

int a = 45978;
sprintf(buffer, "%d", a);
print_string(buffer);

The only thing that doesn't work is this...

double a = 3.14;
sprintf(buffer,"%f", a);
print_string(buffer);

Which pin is the busy flag read from? That definitely sounds like a better way of doing things. And I am also going too use the initialization sequence specified on the data sheet. As soon as I have time to test it, I will post back my results.

July 17, 2011
by BobaMosfet
BobaMosfet's Avatar

Noter,

avrTest.c:37: warning: format '%5.2f' expects type 'double', but argument 3 has type 'float'

'f' used to mean float, not double. Coercion can fix that.

avrTest.c:39: warning: implicit declaration of function 'strlen' avrTest.c:39: warning: incompatible implicit declaration of built-in function 'strlen'

strlen used to be part of stdio.h, but may have moved to strings.h. I couldn't remember.

avrTest.c:42: warning: control reaches end of non-void function

Obviously because main() is expected to return non-void value, but I didn't bother with that (wasn't germain to the example).

The code I wrote was more for an example of how sprintf() can be used- what he did wasn't wrong. And the fact that it his result is right-aligned is also correct. I'm wondering if he's getting the result he's getting because 'double' might be too big for the MCU.... just a thought.

BM

July 17, 2011
by Rick_S
Rick_S's Avatar

Read the HD44780 datasheet. The read status isn't just read from a pin, you have to change the mode of the display to read mode by changing the R/W line and then you read the from the data lines. Which means you have to change your I/O from output to input for the read. Unless timing is critical, it is much simpler programatically to just wait x# of milliseconds. Most libraries you find will do this. It also saves pins as you don't have to connect R/W to the micro-controller, you just connect it straight to ground since you never need to toggle it into read mode.

If you search the forum, you will also find a library I wrote based off the NK library for 8 bit interfacing through an I2C port. You could look at my timings there and where they are used.

Rick

July 17, 2011
by Noter
Noter's Avatar

Signal timing issues do cause strange results and are sometimes hard to figure out. Did you try 1000 us instead of 15us for your bus time and did it work?

You must become very familiar with the HD44780 datasheet to be successful programming any of the common lcd character displays. 8-bit is a little easier to program than 4-bit but unless you want to use even more pins 4-bit is the way to go. Using the ready/busy flag will only require 1 more mcu pin for read/write control. It is a little tricky to implement but works very well. Since you are doing this to learn programming the display, I recommend you get the timeout version going first then move on to the busy flag version. Also study the nerdkits 4-bit version along with the datasheet and you'll make better progress.

July 17, 2011
by Noter
Noter's Avatar

Thanks BM. When you said in your 1st post that it works and then didn't I just wondered if I was missing something about it.

July 17, 2011
by Robotnik
Robotnik's Avatar

Okay, I redid my lcd_init() function, and I still have the same problem. The thing that drives me crasy is, the following code works for me...

#define F_CPU 1000000UL

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <stdio.h>
#include <util/delay.h>

#include "lcd.h"

int main()
{
  char array[] = " 3.14";
  lcd_init(15);
  print_string(array);

  while(1)
  {
  }
  return 0;
}

But when i use sprintf() to convert a double to a char array like this, it doesn't work...

#define F_CPU 1000000UL

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <stdio.h>
#include <util/delay.h>

#include "lcd.h"

int main()
{
  char buffer[20];
  double number = 3.14;
  sprintf(buffer, "%5.2f", number);

  lcd_init(15);
  print_string(number);

  while(1)
  {
  }
  return 0;
}

Correct me if i am wrong, but shouldn't buffer contain the same charactors as array from the 1st prgm?

July 17, 2011
by Noter
Noter's Avatar

Did you try the 1000 for your delay?

Maybe you are linking with the wrong library. Here's the lib line from my makefile -

-Wl,-u,vfprintf -lprintf_flt -Wl,-u,vfscanf -lscanf_flt -lm
July 18, 2011
by Robotnik
Robotnik's Avatar

I just tried it with 1000 and 1000000 microsecond delays and both displayed the same right adjusted '?'. kinda cool to see the cursor move slowly across the screen though... :/ I might be linking it wrong, I don't know how to make makefiles so I have just been modifying a blink_led makefile. I would get warnings when it compiled though like "sprintf() implicit declaration" or something like that wouldn't I?

July 18, 2011
by Noter
Noter's Avatar

Post your makefile and I'll use it and see if I get the '?'.

July 18, 2011
by Robotnik
Robotnik's Avatar

Okay, here it is...

CC=/usr/bin/avr-gcc

MEGA=328p

CFLAGS=-g -Os -Wall -mcall-prologues -mmcu=atmega$(MEGA)

OBJ2HEX=/usr/bin/avr-objcopy

PROG=/usr/bin/avrdude

TARGET=BlinkLED

program : $(TARGET).hex

    $(PROG) -c avrispv2 -p m$(MEGA) -P /dev/ttyACM0 -e

    $(PROG) -c avrispv2 -p m$(MEGA) -P /dev/ttyACM0 -U flash:w:$(TARGET).hex

%.obj : %.o

    $(CC) $(CFLAGS) $< -o $@

%.hex : %.obj

    $(OBJ2HEX) -R .eeprom -O ihex $< $@

clean :

    rm -f *.hex *.obj *.o

I'm using a usb to serial programmer though, so it probably won't work with the nerdkit one...

July 18, 2011
by Noter
Noter's Avatar

Append this to the end of your CFLAGS and give it a go.

-Wl,-u,vfprintf -lprintf_flt -Wl,-u,vfscanf -lscanf_flt -lm
July 18, 2011
by Robotnik
Robotnik's Avatar

YAY!!! that fixed it. Thanks for your help, Noter, I appreciate it.

July 18, 2011
by Noter
Noter's Avatar

Whew! I was starting to think you were going to have to buy a windows box to get it working - LOL - just kidding ;-)

July 18, 2011
by Robotnik
Robotnik's Avatar

I actually do have a windows partition, but it is strictly for playing games. lol

July 19, 2011
by Robotnik
Robotnik's Avatar

Okay, so I understand how make files work now, but I don't know the cmds for avr-gcc, does anyone know a good place to learn them?

July 19, 2011
by Noter
Noter's Avatar

It's in your installation directory somewhere. My win-avr is installed on drive E and this is the documentation -

E:\WinAVR-20100110\doc\avr-libc\avr-libc-user-manual.pdf
July 19, 2011
by BobaMosfet
BobaMosfet's Avatar

Noter,

I had compiled the program and run it in a console on a PC- my concern was the syntax around sprintf()-- because the op's implementation looked correct I wanted to prove it. It compiled and worked without error or warning.

Sorry I wasn't more clear.

BM

Post a Reply

Please log in to post a reply.

Did you know that you can generate hundreds of volts AC from your microcontroller with a little bit of circuitry? Learn more...