SOLVED: DC motor RPM with interrupts

Hello,

I am building a PID controller for a simple DC motor and for that I need to measure the rotational speed of the motor. I based my code on this site Arduino Playground - ReadingRPM but for me this setup does not work at all. Since the counter millis() is not incrementing while an interrupt is running this code cannot measure too high RPMs.
My question is: how am I supposed to circumvent this problem? I am about to use PWM to control the motor and I also want to use serial to plot nice charts on my PC and this seems to be a major roadblock for both of these features.

cheers,
David

P.S.: I'm also using an LCD shield could that be the culprit?

EDIT: I'm using an Arduino Mega 2560

#include <LiquidCrystal.h>

LiquidCrystal lcd(8,9,4,5,6,7);

volatile byte rev;
unsigned int rpm, freq;
unsigned long timeold, timeLCD;


void setup(){
  lcd.begin(16,2);
  lcd.setCursor(6,0);
  lcd.print("rpm");
  lcd.setCursor(6,1);
  lcd.print("1/s");
  attachInterrupt(2, revCount, FALLING);        //pin 21 is our interrupt
  rev=0;
  rpm=0;
  freq=0;
  timeold=0;
  timeLCD=0;
}

void loop(){
  if (rev > 20)
   {
     rpm=(60000*rev)/(millis()-timeold);
     freq=rpm/60;
     timeold=millis();
     rev=0;
   } 
  if ((millis()-timeLCD) >= 250)
   {
     lcd.setCursor(0,0);
     lcd.print("     ");
     lcd.setCursor(0,0);
     lcd.print(rpm);
     lcd.setCursor(0,1);
     lcd.print("     ");
     lcd.setCursor(0,1);
     lcd.print(freq);
     timeLCD=millis();
   }
}

void revCount (){
  rev++;
}

I haven't had the problem with "counter millis() is not incrementing while an interrupt is running" as you describe. What I have done in my code is a detach interrrupt before the calculation, and attach afterwards. Works good for me.

  if (rev > 20)
   {
  DETTACH
     rpm=(60000*rev)/(millis()-timeold);
     freq=rpm/60;
     timeold=millis();
     rev=0;
ATTACH
   }
  if (rev > 20)
   {
     rpm=(60000*rev)/(millis()-timeold);
     freq=rpm/60;
     timeold=millis();
     rev=0;
   }

It might be better to capture the time in the ISR in case loop() is slow getting around to it. For example the time in your code might not be closely synchronized with the number of revs.

Try something like this

void revCount() {
   revNum ++;
   if (revNum == 20) {
       timeStamp = millis();
       newTimeStamp = true; // flag to tell main code to read the value of timeStamp
       revNum = 0;
  }
}

Since the counter millis() is not incrementing while an interrupt is running this code cannot measure too high RPMs.

That is not correct. Unless the ISR takes a long time (and yours does not) it won't affect the updating of millis()

What is the highest RPM you want to measure?
How many pulses do you get per revolution?

...R

I like the idea with the timestamp this method should give way better results, thanks.
That being said it still looks to me that the counter millis() is not incrementing while the interrupt function is running and this was also confirmed by a friend of mine who ran into a similar problem once.

As for your questions:

  • I get one pulse per revolution
  • not exactly sure about the RPM because I couldn't find any documentation about my DC motor (Chinese). Bought a new one that is of better quality going to try it when I get back home

bakadave:
confirmed by a friend of mine who ran into a similar problem once.

I am very doubtful - unless you can post the code he was using.

not exactly sure about the RPM

Some motors have very high speeds - 20,000 or 30,000 rpm.
On the other hand your application may be limiting the speed in the range 1000 to 5000 rpm

In other words - an approximate value would help.

You should measure the RPM over a timeframe that gives you the resolution you want. For example 20 revs at 500 rps takes 40msecs and 20 revs at 450 rps takes 44.4msecs - which will be 44. If that is not sufficient resolution either measure in micros() or measure over a longer number of revs.

...R

All right I think I've got it now.

Robin2:

bakadave:
confirmed by a friend of mine who ran into a similar problem once.

I am very doubtful - unless you can post the code he was using.

Based on some extensive search online it looks like my friend was the one who's wrong and the millis() and micros() counters DO in fact increment while in an interrupt (while being unable to be read). Thanks for helping me clear that up.

Bought a new motor, rewrote the whole code and now it works like a charm.
I'm posting the code below for anybody to use since I believe it's an improvement on the one from Arduino Playground. It grabs the timestamp from within the interrupt, secures the calculation part from being overwritten by an unexpected interrupt and handles the whole timing independently, resulting in (I hope) more accurate readings.
Any comments/critisism appreciaed.

David

/*
  This code measures the RPM of a DC motor (or anything really) using a HALL sensor connected to PIN 21 and outputs data on a LCD display and also some debugging info on the serial
  if I ever want to create a nice chart based on the measurements.
  
  I made quite a bit of an effort to make it as precise as possible. The RPM is sampled with 4 microsecond resolution so it should be enough for any household DC motor.
*/

#include <LiquidCrystal.h>

LiquidCrystal lcd(8,9,4,5,6,7);

volatile byte rev;
volatile long dTime, timeold;
unsigned int rpm, freq;
unsigned long timeLCD;


void setup(){
  lcd.begin(16,2);
  lcd.setCursor(6,0);
  lcd.print("rpm");
  lcd.setCursor(6,1);
  lcd.print("1/s");
  Serial.begin(9600);
  attachInterrupt(2, revInterrupt, RISING);        //pin 21 is our interrupt
  dTime=0, rev=0, rpm=0, freq=0;
  timeold=0, timeLCD=0;
}

void loop(){
  if (rev > 20)
   {
    cli();                           //disable interrupts while we're calculating
    if (dTime > 0)                   //check for timer overflow
    {
      rev-=1;                        //subtract one since the first revolution is not measured
      rpm=(60000000*rev)/(dTime);
      freq=rpm/60;
      Serial.print(rev);
      Serial.print(" ");
      Serial.print(rpm);            //a bit of serial for the debugging (not really needed at this point, perhaps one day for some graphs)
      Serial.print(" ");
      Serial.println(dTime);
      rev=0;
    }
    sei();                          //re-enable interrupts
   }
  if ((millis()-timeLCD) >= 250)
   {
     lcd.setCursor(0,0);
     lcd.print("     ");
     lcd.setCursor(0,0);
     lcd.print(rpm);
     lcd.setCursor(0,1);            //output for the LCD
     lcd.print("     ");
     lcd.setCursor(0,1);
     lcd.print(freq);
     timeLCD=millis();
   }
}

void revInterrupt (){
  if (rev == 0) 
  {
    timeold=micros();              //first measurement is unreliable since the interrupts were disabled
    rev++;
  }
  else 
  {
    dTime=(micros()-timeold);      //'micros()' is not incrementing while inside the interrupt so it should be safe like this right?
    rev++;
  }
}