Getting a ratio to display as a float?

Greetings!

I am working on a project (prototype) where I am using hall effect sensors to read the RPM's from two computer fans. I have servo's that are working as "analog tachometers" and I want one of the servos to range between 0 and 2 in terms of positioning. Both fans are relatively similar in RPM (fan1 ~ 1200rpm and fan2 ~ 1700rpm), so I know I wouldn't go over a ratio of 2.

I have everything working fine and displaying on the serial monitor and everything, however, when it comes time to display the ratio, I get erratic numbers in the thousands jumping between negative and positive!

Here is my code:

#include <Servo.h>

unsigned int rpm1;        // RPM value
unsigned int rpm2;
unsigned long oldTime1;   // time value
unsigned long oldTime2;
unsigned long intTime1;   // time intervals
unsigned long intTime2;
int servPos1;             // servo positions
int servPos2;
int i;                    // counter for serial monitor display
int noAction;             // counter for loops with nothing happening
float ratio;              // used for ratio between RPM's

Servo rpmServo1;          // initialize servos
Servo rpmServo2;

void setup()
{
  Serial.begin(115200);                   // IMPORTANT for timing!
  attachInterrupt(0,magnet1,RISING);      // pin 2 = sensor1 input
  attachInterrupt(1,magnet2,RISING);      // pin 3 = sensor2 input
  rpmServo1.attach(9);                    // pin 9 = servo1 ouput
  rpmServo2.attach(10);                   // pin 10 = servo2 output
  rpm1 = 0;                               // initialize rpm1 to 0
  rpm2 = 0;                               // initialize rpm2 to 0
  oldTime1 = 0;                           // start temp timer1
  oldTime2 = 0;                           // start temp timer2
  intTime1 = 60000;                       // initialize time1 interval
  intTime2 = 60000;                       // initialize time2 interval
  noAction = 0;                           // start counter
  ratio = 1.0;                            // prevent division by zero on startup
} 
void loop()
{
  noAction++;
  if(noAction > 10)                       // if 10 loops pass with no magnetic detection
  {
    rpm1 = 0;                             // reset servo positions to 0
    rpm2 = 0;
    noAction = 11;                        // prevent overflow of int
  }
  else
  {
    rpm1 = 60000/intTime1;                // rev/min = (60s/1min)*(60000ms/60s)*(1rev/timeInt(ms))
    rpm2 = 60000/intTime2;
  }

//////////////////// Calculate Ratio /////////////////////////////

if(rpm2 == 0)
  rpm2 = 1;                             // prevent division by zero
ratio = (float(rpm1)/float(rpm2));      // rpm1 = slower fan, rpm2 = faster fan

/////////////////// Print to Serial Monitor ////////////////////////
  
  char buffer[50];                                                                      // create buffer to hold display char's
  i = sprintf(buffer,"RPM1: %d - RPM2: %d - Ratio: %d", rpm1, rpm2, ratio);             // create display phrase
  for(int j=0;j<=i;j++)                                                                 // cycle through each char
    Serial.print(buffer[j]);                                                            // print each char
  Serial.println();                                                                     // print new line

////////////////////////// Move Servos //////////////////////////////

  servPos1 = map(ratio, 0, 2, 160, 0);        // range = 0-2 for ratio (float)
  servPos2 = map(rpm2, 100, 1800, 180, 20);   // showing RPM of faster fan
  rpmServo1.write(servPos1);                  // set Servo1 to adjusted position
  rpmServo2.write(servPos2);                  // set Servo2 to adjusted position
  delay(50);                                  // refresh rate

//////////////////////////////////////////////////////////////////////
}


void magnet1()                                // when magnet is detected
{
  intTime1 = millis() - oldTime1;             // update time interval
  oldTime1 = millis();                        // update starting point for timer1
  noAction = 0;                               // reset counter
}
void magnet2()                                // when magnet is detected
{
  intTime2 = (millis() - oldTime2);           // update time interval
  oldTime2 = millis();                        // update starting point for timer2
  noAction = 0;                               // reset counter
}

The part causing the erratic behavior is in the "Calculate Ratio" and "Print to Serial Monitor" sections. Everything prior to adding anything regarding the ratio variable was working fine and that was the very last thing I did.

Here is the output:

[...]
RPM1: 1276 - RPM2: 1714 - Ratio: -27453
RPM1: 1304 - RPM2: 1764 - Ratio: 15901
RPM1: 1304 - RPM2: 1666 - Ratio: 24546
RPM1: 1276 - RPM2: 1714 - Ratio: -27453
RPM1: 1304 - RPM2: 1714 - Ratio: -15524
RPM1: 1304 - RPM2: 1764 - Ratio: 15901
RPM1: 1304 - RPM2: 1714 - Ratio: -15524
RPM1: 1276 - RPM2: 1714 - Ratio: -27453
RPM1: 1304 - RPM2: 1714 - Ratio: -15524
RPM1: 1276 - RPM2: 1714 - Ratio: -27453
[...]

While the RPM's of both fans are fairly constant, the ratio goes crazy, even though I simply have it dividing something like 1714/1276 which should give me 1.34.

I tried seeing if those numbers correlate to anything, but I couldn't find anything. I tried setting the float variable as "unsigned", but it wouldn't let me. I tried putting the variables as float(rpm1) and float(rpm2) and that's what I have so far and it's not displaying the correct answer.

I have read that some people have had issues with simple arithmetic division in the past, but those are posts from 2011 and I haven't found anything more recent and they were getting outputs with "?" instead of the numbers I'm getting. I got that problem already, but that's when I tried printing with the %f in the sprintf function instead of %d. Surprisingly, %d shows with something while %f shows up with nothing. Weird.

Any thoughts? Any help is greatly appreciated!

 i = sprintf(buffer,"RPM1: %d - RPM2: %d - Ratio: %d", rpm1, rpm2, ratio);

Use dtostrf instead,( and check your format vs data type).

sprintf(buffer,"RPM1: %d - RPM2: %d - Ratio: %d", rpm1, rpm2, ratio);

You have asked the sprintf function to treat the bits of a floating point number (ratio) as if they were an integer (%d). sprintf has no way to know that this makes no sense, so it just does it.

Usually, you'd

sprintf(buffer,"RPM1: %d - RPM2: %d - Ratio: %f", rpm1, rpm2, ratio);

but I have gotten the impression that if you want sprintf to do that, you have to compile in an extension. So what yo may want to do instead is just to do it as two ints. Print the whole part, then snip of the frational part and multiply by a thousand. Print it using a zero-padded format:

sprintf(buffer,"RPM1: %d - RPM2: %d - Ratio: %d.%03d", rpm1, rpm2, 
  (int) ratio, (int) ((ratio - (int)ratio) * 1000));

AWOL:

 i = sprintf(buffer,"RPM1: %d - RPM2: %d - Ratio: %d", rpm1, rpm2, ratio);

Use dtostrf instead,( and check your format vs data type).

From ATMEL's site, it shows this:

char * dtostrf(
	double __val,
	signed char __width,
	unsigned char __prec,
	char * __s)

And from hobbytronics.co.uk, they show:

// floatvar	float variable
// StringLengthIncDecimalPoint	This is the length of the string that will be created
// numVarsAfterDecimal	The number of digits after the deimal point to print
// charbuf	the array to store the results

static float f_val = 123.6794;
static char outstr[15];

void setup() {
  dtostrf(f_val,7, 3, outstr);

  Serial.begin(9600);
  Serial.println(outstr);
}

void loop(){
}

//The output of this sketch is

//123.679

Now, I understand this will work for displaying a float in the serial monitor, but will the value itself be stored as a float in order for me to use it to actually calculate the ratio I need?

For example, I want to do something like this:

float a = 3.0;
float b = 7.0;
float c = a/b;

dtostrf(c, 5, 3, ratio);
Serial.println(ratio);

float servPos = map(ratio,0,2,180,0);   // Concern here... can I set a servo position as a float? Or can I convert the float and round it to an int? I'll be multiplying by 1000 as to keep the proportionality...

servo1.write(servPos)

This is just pseudo code, of course, I would initialize everything accordingly, I am just curious as to the process being correct in what I am trying to accomplish, which is essentially getting a servo to move between positions 0 and 2000 respectively to the 0 to 180 degrees of motion to move to position that is essentially "ratio*1000".

PaulMurrayCbr:

sprintf(buffer,"RPM1: %d - RPM2: %d - Ratio: %d", rpm1, rpm2, ratio);

You have asked the sprintf function to treat the bits of a floating point number (ratio) as if they were an integer (%d). sprintf has no way to know that this makes no sense, so it just does it.

Usually, you'd

sprintf(buffer,"RPM1: %d - RPM2: %d - Ratio: %f", rpm1, rpm2, ratio);

but I have gotten the impression that if you want sprintf to do that, you have to compile in an extension. So what yo may want to do instead is just to do it as two ints. Print the whole part, then snip of the frational part and multiply by a thousand. Print it using a zero-padded format:

sprintf(buffer,"RPM1: %d - RPM2: %d - Ratio: %d.%03d", rpm1, rpm2, 

(int) ratio, (int) ((ratio - (int)ratio) * 1000));

I tried the %f trick and it only displayed a "?" in the serial monitor where the ratio value should have been. With %d, it actually shows a number.... a wrong number, but a number nonetheless.

Also, I'm not looking to just simply display the float variable in the serial monitor, but I am actually looking to use the float values to get a ratio between two values that will result in another float, which I would multiply by 1000 in order to get a number that I could input as a servo position using the map function.

  1. sprintf() for Arduino has no float support, so if you want to use it, you are on the right track with truncation/multiplication. You can implement it but is (even more than just sprintf()) overhead. Search the forum for methods.

  2. if you want a float result from integer division you have to explicitly cast (either of) the numerator and denominator to floats.

int numerator = 1;
int denominator = 2;
double ratio = (double)numerator / (double)denominator;

BulldogLowell:

  1. sprintf() for Arduino has no float support, so if you want to use it, you are on the right track with truncation/multiplication. You can implement it but is (even more than just sprintf()) overhead. Search the forum for methods.

  2. if you want a float result from integer division you have to explicitly cast (either of) the numerator and denominator to floats.

int numerator = 1;

int denominator = 2;
double ratio = (double)numerator / (double)denominator;

I am not limited to using sprintf, I could use anything else that works. In fact, it's not even super important that I display the float on the serial monitor, the only reason I want to is just for troubleshooting purposes.

My ultimate goal is getting a 180 degree servo that ranges from 0-2 (0 being in the leftmost 0 degree position and 2 being in the rightmost 180 degree position) to physically display on a dial (hence the use of a servo) the ratio between the RPM's of two spinning objects. A ratio of 1.0 would cause the servo to move to the 90 degree position, essentially.

For example, if rpm1 = 500 and rpm2 = 750, I want the servo to point to where 0.66 would land on the dial that I would make.

I want to be able to just input two ints for rpm1 and rpm2 (which I would turn into floats if I have to) and divide the two and get a float as a resultant and make that float move the servo into the appropriate position with "simple" arithmetic.

I am so close, but I don't have the servo set up in front of me at the moment so I can't give the code a try to see whether or not what was stated above works, unfortunately. But I will be able to get my hands on everything on tomorrow night hopefully and I will see if the dtostrf function works.

jsantana306:
My ultimate goal is getting a 180 degree servo that ranges from 0-2 (0 being in the leftmost 0 degree position and 2 being in the rightmost 180 degree position) to physically display on a dial (hence the use of a servo) the ratio between the RPM's of two spinning objects. A ratio of 1.0 would cause the servo to move to the 90 degree position, essentially.

You don't need floats and you don't need map(). Try:

// variable for output servo position
int servPos1; 

if (rpm2 < 1) {
  // prevent division by zero and by negative
  rpm2 = 1;
}
if (rpm1 < 0) {
  // prevent output less than zero
  servPos1 = 0;
}
else if (rpm1 > (2 * rpm2)) {
  // prevent output greater than 180 degrees
  // which would happen if the ratio exceeds 2
  servPos1 = 180;
}
else {
  // calculate output position
  // it should be 90 degrees times the ratio
  servPos1 = (((long)rpm1) * 90) / rpm2;
}

// calculate ratio as percentage (for debugging)
int ratioPercent = (((long)rpm1) * 100) / rpm2;