Calculating Percentages yields 0% or 100%- never between

Hello! I'm trying to calculate the percentage of something in Arduino. More specifically, I have a fan that I'm reading the RPM from. I know it's maximum RPM, so I would like to be able to display both its RPM and its % speed (0%-100%). I have no problem reading the RPM, but for some reason, I can't get the % to calculate!

Is there something I'm missing? Full code below:

//Calculate RPM from fan tachometer:


int fanRPM= 0;
int maxfanRPM= 1380;
float fanRPM_percentage= 0;
unsigned long lastmillis= 0;
int half_revolutions= 0;


 //--------------------------------------------------------------------------------------
 
 void RPM_fan()  // this code will be executed every time the interrupt 0 (pin 2) gets low.
 {
  half_revolutions= half_revolutions + 1;
 }

//--------------------------------------------------------------------------------------

void setup()
{
 Serial.begin(9600); 
 attachInterrupt(0, RPM_fan, FALLING);
 }
 
//--------------------------------------------------------------------------------------
 
 void loop()
 {
   if (millis() - lastmillis == 1000) //Update every second
   { 
     detachInterrupt(0); //Disable interrupt when calculating
     fanRPM= half_revolutions * 30; //Convert frecuency to RPM, note: this works for one interruption per full rotation. For two interrups per full rotation use half_revolutions * 30.
     fanRPM_percentage= (fanRPM / maxfanRPM) * 100;
     Serial.print("Fan Speed: ");
     Serial.print(fanRPM);
     Serial.print(" (");
     Serial.print(fanRPM_percentage);
     Serial.println("%)");
     half_revolutions = 0;
     fanRPM= 0;
     fanRPM_percentage= 0;
     lastmillis = millis();
     attachInterrupt(0, RPM_fan, FALLING); //Enable interrupt
  }
 }
 

//--------------------------------END OF PROGRAM------------------------------------------

Thanks in advance!!

you are doing integer math and expecting float results

replace this:

fanRPM_percentage= (fanRPM / maxfanRPM) * 100;

with this

fanRPM_percentage= ((float)fanRPM /  (float)maxfanRPM) * 100.0;
fanRPM_percentage= (fanRPM / maxfanRPM) * 100;

That's because all your variables are integers, and integer division is truncated for results less than a whole number.
So 99/100 == 0. 100/100 = 1.

If you use a bit of algebra (even though no one ever uses algebra past their school years; they should just stop teaching it. :-; ), and rearrange your equation like so, you should get much more satisfactory results (without the expense of using floating point, BTW. fanRPM_percentage doesn't need to be float, either.):

fanRPM_percentage= (fanRPM*100) / maxfanRPM;

@BulldogLowell, your fix worked! Thank you!

@westfw, your code didn't exactly work.. it did change to nonzero, but the math was off. I think BulldogLowell's fix is the most accurate. I added in (int) to the whole thing to get a nice round number :slight_smile:

some remarks:
you should declare the variables used in the ISR as volatile

The reason westfw's code didn't work was because there can be an overflow. int = 16 bit ==> -32768..+32767

if fanRPM is integer and at the max value 1380 the math 1380*100 = 138000 which does not fit into an int.

solution is to use an unsigned long. (32 bit) for exact math up to 4 billion something

unsigned long fanRPM;

then the formula

fanRPM_percentage= (fanRPM*100) / maxfanRPM;

will work as intended.

when converting the float variant to integer you better use round() which does proper rounding.

fanRPM_percentage=  round((fanRPM*100.0) / maxfanRPM);

Another comment:

The following is NOT guaranteed to work:

   if (millis() - lastmillis == 1000) //Update every second

millis() can skip values. It would be safer to do:

   if (millis() - lastmillis >= 1000) //Update every second

(Notice the greater-than sign in place of an equal sign).
For most cases, this will be the same as what you had. In cases where millis() skips a value, this will catch it whereas what you had could miss.

vaj4088, thank you! You guys go above and beyond :slight_smile: