Problem with speed measurement using a proximity sensor!

Hello guys,

I got an Arduino Uno and I'm trying to read the speed a of 4-blade 12V DC fan. I'm using an IR proximity sensor that sends a HIGH on interrupt 0 (pin 2) that is directly readable by the Arduino. My speed calculations are provided within the code below.

My problem is that at low speeds, the speed readings are ridiculous and as actual speed goes up, the readings go down!! At maximum fan speed, the reading is around 1600 RPM which is believable but at MUCH lower speeds the readings go up as high as 9000 RPM and 14000 RPM which is nonsense, until they go to zero when the fan finally rests. It is worth noting that the fan blades are wide and the gaps between them are small, however I don't see that as a problem since I've set the interrupt to trigger every rising edge, so theoretically there should be no problem.

In my opinion, it is either a flaw in my code (actually I got it off the Arduino playground and tweaked it a bit) or that the interrupt timings are inaccurate. I've thouroughly tested the sensor and it neither is noisy nor slow, hence, I've included the code below to see if there's a problem with the code. Thanks and any input would be much appreciated.

 volatile byte tick;
 unsigned int rpm;
 unsigned long timeold;
 
void setup(){
  Serial.begin(9600);                      //Initialize Serial port communication;
  attachInterrupt(0, tick_count, RISING);  //Execute interrupt function (tick_count) defined below each rising edge on external interrupt 0 (pin 2);
  pinMode(3, OUTPUT);                      //Set PWM pin 3 to output to motor driver;
  pinMode(2, INPUT);                       //Set interrupt port 2 to input from proximity sensor;
  pinMode(A0, INPUT);                      //Set ADC pin A0 to input from potentiometer;
}  

//Interrupt function;
void tick_count()                          //Executed every rising edge on interrupt 0 (pin 2)
 {  
    tick++;                                //Increment "tick" by 1;
 }
 
void loop() {
  int command;
  int pwm;
  int duty_cycle;
  
  command = analogRead(0);                 //Read analog signal on channel 0 of ADC and convert it into a 10-bit digital integer and store it in variable "command";
  pwm = map(command,0,1023,0,255);         //Scale the 10-bit digital value of the ADC with the 8-bit resolution of the PWM output to motor driver;  
  duty_cycle = pwm*100/255;                //Calculate PWM duty cycle in %;
  analogWrite(5,pwm);                      //Send the previously specified pwm signal on pin 5 to motor driver;
  
  //Start RPM calculations;
  if(tick>=4)                                       //When a full fan cycle is completed (fan has 4 blades)
   {
    rpm = 15*1000/(millis() - timeold)*tick;        //Divide tick counter "tick" by the time it took to do the 4 ticks using the "millis()" function. 60/4=15 giving tyhe actual RPM of a 4-blade fan;
    timeold = millis();                             //Save the previous time to use it for next iteration of speed calculation;
    tick = 0;                                       //Reset "tick" to zero to prepare it for the next iteration of speed calculation;
   }
  //End RPM Calculations
  
  //Send desired data over the Serial COM Port;
  Serial.print("Command = ");    
  Serial.print(command);
  Serial.print("  ");
  Serial.print("PWM = ");
  Serial.print(duty_cycle);
  Serial.print("   RPM = ");
  Serial.println(rpm);
  delay(100);
}

What is the sensor?

This is the sensor I am using. Three pins: 5V, GND, Signal directly to Arduino Interrupt 0 (PIN 2). I tested it with "digitalRead()" function to see its response and it responds very well.

http://store.fut-electronics.com/1ELB140D3O.html

Thanks

Since you say the sensor is not noisy, I assume it doesn't need to be debounced.

However, the interrupt rate may be too high for the arduino. You said the maximum plausible RPM is 1600. 1600 * 60 * 4 = 384,000 interrupts per second. Arduino runs at 16Mhz. If an interrupt takes 70 instructions to process, then the max rate is about 228,000 interrupts per second. That puts the max rpm that the arduino can read at less than 1,000.

How fast is the fan? I see 12VDC fans on digikey go from 1,500 to over 15,000 RPM. Do the readings look accurate at VERY low speeds? If you turn the fan by hand, does it read a few RPM?

As for the code, It looks, OK, although for safety I would do the rpm calculation using all unsigned longs, to avoid overflows. Declare 'tick' to be unsigned long, and use 15000UL as the constant. Also, you don't have any safety limits on the (millis() - timeold) expression. That is probably not a problem under normal operation, but if the interval got too long the expression would be 0 regardless of how large 'tick' is.

A couple of suggestions: can you set it up so the sensor transitions once per revolution instead of once per blade? That would cut the interrupt rate by 1/4. Otherwise, you might need to use an external counter chip to divide the frequency down to something the arduino can handle.

Thanks a lot for your feedback, ckiick

  1. Yes I'm sure the problem is not from the sensor and doesn't need debouncing.

  2. The 1600 RPM reading was one I got from my rpm calculations in the code, so it could be wrong. It would be useful to know that I'm using a 12V 5.5A DC small car radiator fan.

  3. I don't quite get why you suggest it executes 384,000 interrupt a second. It says 1600 RPM * 4 each cycle, then I think we should divide by 60 to get interrupts/second. Please correct me if I'm wrong.

  4. The readings are no where near accurate at low speeds. Actually when the fan is at rest, and I turn the pot knob just a little bit, not enough to let the motor spin, I get 14000 RPM readings!! It's driving me nuts! However, when I slowly spin it manually, it gives credible readings around 100-200 RPM. Somehow, I believe the problem is with the ADC reading the pot very slowly affecting the efficiency of the code and interrupt timings. I know it's ridiculous and I don't have an explanation :slight_smile:

  5. I will tried using unsigned longs as you suggested, but it didn't help.

I'll try to reduce the amount of interrupt executions and see where it gets me. Thanks a lot

You said the maximum plausible RPM is 1600. 1600 * 60 * 4 = 384,000 interrupts per second.

RPM != Hz

Hi Sherrah,

Probably has no bearing on your problem but does the switch pull the pin low when object is detected? If so then would altering the attachInterrupt to trigger on FALLING or LOW be more suitable. Also what is the rated switching speed? Can it switch 100+ times per second

Hello Riva, Thanks for your input

  1. I'm not connecting a pull down resistor, since the interrupt port 0 (pin 2) is internally pulled down to ground with a built-in 20k resistor I believe. When the object is detected, it sends a high to the interrupt port.
  2. I tried altering the attachInterrupt to trigger every falling edge, but still the same problem.
  3. Unfortunately, the sensor datasheet doesn't include anything about switching speeds/times. My gut feeling says it's not about bandwidth or switching speed, but rather the efficiency of the code.

Thanks again and any input guys would be much appreciated.

I think your problems stays in the sensor. Not that it's bad, but as you said, the gaps are very small.
you have to understand that your sensor can't measure continuously. My opinion is that it has a transistor inside which has a low switching frequency. keeping this in mind, lets consider it needs x ms until it reacts, time in which it won't measure, at the end of this time (which will never be constant) it will measure again, and it will detect or not a gap. So sometimes it might see a gap, sometimes it might not. so this is your problem. The frequency of measurements isn't nearly as fast as you might think, and that's where you get your errors. try spinning it at constant speed and tell us what you get.

My opinion, loose the sensor, and try to find a cheap encoder for the motor of the fan. The sensor isn't built for this types of applications or speeds, and that's why people use those complicated encoders.

Have been hunting for suitable replacement sensor and wonder if this would help http://www.ikalogic.com/infra-red-proximity-sensor-part-1/. Reading the page you see there are pulse type sensors that are no good for high speed. Would a hall effect proximity sensor maybe also work?

I don't know the arduino too much. From the schematic, I can see the opamp they are using has a frequency of 1MHz, so you should be able to take 1milion measurements/second (divide by 4 and you get maximum RPS 0.25Mhz). How fast can the arduino pull that data is a different story and I can't get into too much detail there. You need to see if it works or not. You need to realize that your measurement might get affected by the ambiental light.
The hall effect sensor is definitely a better solution, but harder to implement. look for encoders from old pc fans, those could also be a solution.
My opinion is that they will both get limited by the arduino, but I'm only giving my opinion based on the sensors.
also from your code, you're averaging the speed every rotation, that might give you problems, try waiting for 3-4-5 rotations, the speed will update slower, but the value will be more accurate.

scorpio20 and Riva, thanks a lot for your feedback guys.

Let me just stress again on a very weird thing going on here. I have pot connected to ADC. It reads the voltage, digitizes it and converts it to a PWM signal to the motor. When the pot is outputting 0V, digital value = 0, PWM = 0 -->> Speed = 0 RPM. Very good. Now when I turn the pot knob just a degree or two, digital value = 60, PWM = 15% -->> Speed = 14378 RPM!! This is quite weird, something wrong is going on. In my humble opinion, the problem is not the sensor. However, I will try to do as you suggested and change the sensor to a hall effect one, just to see what happens.

Guys after some experimenting, I've come to the conclusion that there is some sort of grounding/isolation problem. However, I don't know where it comes from as I have common ground through the whole circuit (high power(motor)/low power(Arduino) sections).

Why? I've noticed that when I give full PWM signal to the motor (255) (running on an independent 12V 4.5A DC power supply), runs at full speed as expected but gives the horrible speed readings. However, when I disconnect the power supply ground from the Arduino ground, it of course slows down but spins for seconds due to interia, giving very reasonable speed readings ranging from 1400 RPM -->> 1200 -->> 900 -->> 700 -->> 300 RPM-->> until it rests. This means that when the grounds are not common, the sensor/code work as they should.

The problem here is how am I going to drive the motor without common ground!

Do you have a schematic of how your motor and arduino are wired up and details of the motor would be good. My main interest here is how you convert the 5 volt PWM output from the arduino to 12 volt fan and separate the 5 & 12 volt.

The earth problem might mean the need to opto isolate the arduino or it could be electrical noise.

Having another look at your code and could this have some bearing...

pinMode(3, OUTPUT); //Set PWM pin 3 to output to motor driver;

analogWrite(5,pwm); //Send the previously specified pwm signal on pin 5 to motor driver;

Here is a schematic for my circuit. Regarding the code, I did notice that yesterday and corrected it, but still the same problem persists.

Uploaded with ImageShack.us

Note: a pull down resistor is used to hold the PWM pin low when afloat. The system works very well and the MOSFET doesn't heat up. It's just the insanity of the sensor that is giving me a hard time.

My gut feeling is noise triggering spurious interrupts or other unpredictable conditions in the arduino or IR device.
Mixing of voltages and supplies (+5V VCC and 12V) is not a good idea, assuming the 5V is from USB port. Could you either opto isolate the MOSFET gate and uncommon the earth or maybe just powering the arduino from the 12V through a rectifier.

Hi again Riva,

One question though, If I optoisolate the gate, will the PWM signal trigger the opto-isolator fast enough to give speed control? Will it even be possible to trigger the opto-isolator without common ground?

I don't understand why making common ground throughout the whole circuit is a bad idea, however, opto-isolation might solve the problem and I'm definitely going to try what you suggested.

One question though, If I optoisolate the gate, will the PWM signal trigger the opto-isolator fast enough to give speed control? Will it even be possible to trigger the opto-isolator without common ground?

You will need a high speed device like this http://www.vishay.com/docs/83647/ild205t.pdf available from here http://www.sparkfun.com/products/9118 on a breakout board. No need for common ground. LED side of opto isolator is powered/triggered from arduino PWM pin through a resistor and earths to arduino GND (I assume arduino is USB powered). MOSFET gate is now fed from 12V side via suitable resistor if needed and the photodiode sits in the gate feed. Gate earths to 12V side of circuit. See image

Clipboard-1.jpg

Riva, I can't thank you enough for your support.

One last question though: Aren't there going to be any repercussions triggering the MOSFET with circa 12V instead of the 5V directly from the Arduino? Or is that the job of the upper resistor (near 12V) to drop some voltage, thus giving the required 5V MOSFET trigger voltage?

I have a Sharp PC817 opto-coupler now. I'm going to play with it until I get one similar to what you suggested. I have a really good feeling about this and hope this works!

Thanks again Riva and will get back to you once I get some results.

be careful with the current to the mosfet. too fast di/dt will damage the the gate on the long run. always calculate the resistor.
you can find the calculation in one of my post in the electrical section.