format and print float numbers without String

Using: Arduino Micro, IDE v1.0.5, and this 16x2 display.

This is a very early version of the sketch. It will eventually grow into a full-blown tachometer (I'll attach two optical sensors that will time the rotation of two rods - but that code is not there yet). For now, it just generates two random numbers, changes their values every second, and displays the result. I'm just testing the display for now.

The numbers can be any value between less than 1, and a few hundreds (roughly speaking), and not necessarily integers. I want to display the numbers with 4 digits total, including the decimal dot (if any). Examples:

0.236
7.59
48.1
236

If the value is negative, or larger than 1000, an error message needs be displayed instead.

So: read (or, for now, simulate) the value, give it the right format, and display. This is my current code:

#include <Adafruit_CharacterOLED.h>
Adafruit_CharacterOLED lcd(OLED_V2, 6, 7, 8, 9, 10, 11, 12);

// global variables for the simulated speeds
float mir;
float ecc;

void setup() 
{
  // initialize display
  lcd.begin(16, 2);
  lcd.setCursor(0, 0);
  lcd.print("mirror:      RPM");
  lcd.setCursor(0, 1);
  lcd.print("eccent:      RPM");
  
  // initialize simulated values
  mir = random(0, 100);
  ecc = random(0, 100);
}

void printspeed(float sp, int x, int y)
{
  // the formatted string
  String buf;
  // required by dtostrf()
  char temp[5];
  // position the cursor
  lcd.setCursor(x, y);
  
  // format the number depending on its value
  if (sp < 0) {
    buf = " neg";
  } else if (sp >= 0 && sp < 10) {
    buf = dtostrf(sp, 4, 2, temp);
  } else if (sp >= 10 && sp < 100) {
    buf = dtostrf(sp, 4, 1, temp);
  } else if (sp >= 100 && sp < 1000) {
    buf = (String) round(sp);
  } else {
    buf = "high";
  }
  
  // and print to LCD
  lcd.print(buf);
}

// generate a new random value for the simulation
float tweaksp(float sp)
{
  float jit = 1.5;
  float ret;
  int jitint = (int) 1000 * jit;
  
  ret = sp + (float)random(-jitint, jitint)/1000;
  if (ret < 0)
    ret += jit;
  if (ret >= 1000)
    ret -= 100;

  return ret;
}

void loop() 
{
  // refresh random value
  mir = tweaksp(mir);
  ecc = tweaksp(ecc);
  // display
  printspeed(mir, 8, 0);
  printspeed(ecc, 8, 1);
  delay(1000);
}

It works fine for a while, but then it crashes and the display becomes corrupt. I suspect this might have something to do with my using String in the sketch.

Is there a way to format and display the numbers without using String? I've tried to avoid using it, but couldn't find a solution.

I don't have an LCD handy, but used Serial instead for output. First, using the String class is often an H-bomb-to-kill-an-ant. A char array usually works better. He's my modified version:

float mir;
float ecc;

void setup() 
{
  // initialize display
  /*
  lcd.begin(16, 2);
  lcd.setCursor(0, 0);
  lcd.print("mirror:      RPM");
  lcd.setCursor(0, 1);
  lcd.print("eccent:      RPM");
  */
  Serial.begin(115200);
  Serial.println("mirror:    RPM");
  Serial.println("eccent:    RMP");
  // initialize simulated values
  mir = random(0, 100);
  ecc = random(0, 100);
}

void printspeed(float sp, int x, int y)
{
  // the formatted string
  char buf[21];
  // required by dtostrf()
  char temp[6];
  // position the cursor
//  lcd.setCursor(x, y);
  
  // format the number depending on its value
  if (sp < 0) {
    strcpy(temp, " neg");
  } else if (sp >= 0 && sp < 10) {
    dtostrf(sp, 4, 2, temp);
  } else if (sp >= 10 && sp < 100) {
    dtostrf(sp, 4, 1, temp);
  } else if (sp >= 100 && sp < 1000) {
    sp = (float) round(sp);
    dtostrf(sp, 4, 2, temp);
  } else {
    strcpy(temp, "high");
  }
  strcpy(buf, temp);
  Serial.println(buf);
}

// generate a new random value for the simulation
float tweaksp(float sp)
{
  float jit = 1.5;
  float ret;
  int jitint = 1500;
  
  ret = sp + (float)random(-1, 1);
  if (ret < 0)
    ret += jit;
  if (ret >= 1000)
    ret -= 100;

  return ret;
}

void loop() 
{
  // refresh random value
  mir = tweaksp(mir);
  ecc = tweaksp(ecc);
  // display
  Serial.print("Mirror: ");
  printspeed(mir, 8, 0);
  Serial.print("eccent: ");
  printspeed(ecc, 8, 1);
  delay(1000);
}

I don't see much variation in the output, mainly because I think random() takes long ints as its argument. It ends up truncating your -1.5 and +1.5 to -1 and 1. Probably not what you want to do. I think the rest will work. I didn't run it for a real long time, but it never choked during the runs I made.

The problem with doing strcpy or memcpy is that you're allocating memory from the stack each time. It should be freed up when the variable falls out of scope, however. Nevertheless, try something like this:

char temp[10];
void printspeed(float sp, int x, int y) {  
    lcd.setCursor(x, y);
    
    if (sp < 0) {
       lcd.print(" neg");
    }
    else if (sp>=1000) {
       lcd.print("high");
    }
    else {
        // this gives you what you want, 
        // but might give you more than 4 characters
        sprintf(temp, "%-#4.3f", sp);
        // terminate the string after the first 4 characters
        temp[4] = '\0';
        lcd.print(temp);
    }
}

Nevertheless, try something like this:

The %f format specifier is not supported by sprintf() on the Arduino, unless you've installed a patch.

Why "float numbers"?

Use casting and arithmetic so you're dealing only with integers.
Something like this:

float x = 123.45; // this is the number you want to display

float xabs = x;
char xsign = '+';
if (xabs < 0) {
  // take care of negative numbers
  xabs = 0.0 - xabs;
  xsign = '-';
}
// at this point:
// xabs is the absolute value of x
// xsign is the sign of x, either '+' or '-'
unsigned int xwhole = (int) xabs;
byte xcent = (byte)((xabs - xwhole) * 100 + 0.5);
if (xcent > 99) {
  xcent = 0;
  xwhole++;
}
// at this point:
// xsign is the sign of x, either '+' or '-'
// xwhole is the whole number part of x
// xcent is the "hundredths" part of x

// for example:
// if x is 123.45, then xsign will be '+',
// xwhole will be 123, and xcent will be 45

// another example:
// if x is -8.09, then xsign will be '-',
// xwhole will be 8, and xcent will be 9

Once you split the floating-point number into sign, integer part, and fractional part, then you can display these parts by other means.

small optimization (reusing already tested values) and readability improvement

void printspeed(float sp, int x, int y)
...
  if (sp < 0)    buf = " neg";
  else if ( sp < 10)  buf = dtostrf(sp, 4, 2, temp);
  else if ( sp < 100)  buf = dtostrf(sp, 4, 1, temp);
  else if ( sp < 1000)  buf = (String) round(sp);
  else  buf = "high";
  ...