Go Down

Topic: Measuring RPM using hall effect sensor (Read 33013 times) previous topic - next topic

mayfield

Hi everyone, merry xmas and happy new year.

A little project I've been working on is a RPM display for my drill press using a A3144 hall effect sensor, an arduino uno and a 16x2 lcd display, I'm having some trouble getting the programming right so that the lcd display's  a "0" rpm when drill is not running.

Code: [Select]
#include <LiquidCrystal.h>
int sensorPin = 2; //hall effect
int counter = 0;
int ledpin = 13;
float start, finished;
float elapsed, time;
float revs;
float revolution;
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
/*
* LCD RS pin to digital pin 8
* LCD Enable pin to digital pin 9
* LCD D4 pin to digital pin 4
* LCD D5 pin to digital pin 5
* LCD D6 pin to digital pin 6
* LCD D7 pin to digital pin 7
* LCD R/W pin to ground
* 10K resistor:
* ends to +5V and ground
* wiper to LCD VO pin (pin 3)
*/
void setup()
{
  //setup lcd
  lcd.begin(16, 2);
  lcd.print("  DRILL  SPEED");
  lcd.setCursor(9, 1);
  lcd.print(" RPM");
 
  // setup serial - diagnostics -
  Serial.begin(115200);
  // setup pins
  pinMode(sensorPin, INPUT);
  pinMode(ledpin, OUTPUT);
  // setup interript
  attachInterrupt(0, RPM, RISING);
  start=millis();
 
}

void RPM()
{
  int sensorValue = digitalRead(sensorPin);
  //lcd.clear();
  elapsed=millis()-start;
  start=millis();
  float revs = 60000/elapsed;
  float revolution = elapsed/1000;
  Serial.print(elapsed);
  Serial.print(" mS ");
  Serial.print(revolution);
  Serial.print(" SEC ");
  Serial.print(revs);
  Serial.print(" RPM ");
  Serial.print(millis() );
  Serial.print(" ");
  Serial.println(start);
  lcd.setCursor(3, 1);
  if (elapsed < 1200) {lcd.print(revs,0);}
    else {lcd.print(" 0     ");}
  /*
  if (sensorValue == 0) && ( >= 1000) {lcd.print("0");}
  else {lcd.print(revs);}
  */
 
}

void loop()
{
elapsed=millis()-start;
 
  //counter++;
  int sensorValue = digitalRead(sensorPin);
  //Serial.print(counter);
  //Serial.print(" ");
  //Serial.println(sensorValue);
  //delay(1000);
 
  if (sensorValue == 0) {digitalWrite (ledpin, HIGH);}
  else {digitalWrite(ledpin, LOW);  }
  //lcd.setCursor(3, 1);
  if (elapsed > 1200) {revs == 0;}
 
 
 
 
 
}

muddy

Code: [Select]
if (sensorValue == 0) && ( >= 1000) {lcd.print("0");}
That is not correct.

wildbill

You have a common issue. You can't use serial.print in an interrupt. Moreover, the interrupt should do as little as possible so that it is fast to avoid missing subsequent interrupts.

Better to simply increment a global (volatile) variable in the interrupt and let loop take care of the calculation and display of rpm.

XOIIO

Not sure if this will help but this is what I used in a recent project (Adding a digital RPM readout to a drill press), my hall effect sensor just outputs a constant signal, not variable so it may not work for you but maybe some of it will come in handy. for some reason it was half the actual speed though, so I just doubled it and made it a second rpm value.

Code: [Select]

#include <LiquidCrystal.h>

volatile byte rpmcount;

unsigned int rpm;
unsigned int rpm2;

unsigned long timeold;

LiquidCrystal lcd(12, 11, 6, 5, 4, 3);

void setup()
{
  Serial.begin(9600);
  attachInterrupt(0, rpm_fun, RISING);
  lcd.begin(8, 2);
 
  rpmcount = 0;
  rpm = 0;
  timeold = 0;
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print("  RPM:  ");
}

void loop()
{
  if (rpmcount >= 10) {
    //Update RPM every 20 counts, increase this for better RPM resolution,
    //decrease for faster update
    rpm = 30*1000/(millis() - timeold)*rpmcount;
    timeold = millis();
    rpmcount = 0;
    rpm2 = rpm * 2;
    Serial.println(rpm2,DEC);
   
    lcd.setCursor(0, 0);
    lcd.print("  RPM:  ");
    lcd.setCursor(0, 1);
    lcd.print("  ");
    lcd.print(rpm2);
  }
}

void rpm_fun()
{
  rpmcount++;
  //Each rotation, this interrupt function is run twice
}



mayfield

Hi everyone and thanks for the help very much appreciated I've rewritten the sketch to get the interrupt to do as little as possible (thanks wildbill). I've tried getting the interrupt to increment a counter and converting the number to HZ and then to RPM but have found that not to be accurate enough as HZ increases by 1, RPM increases by 60.

so the sketch below is returning the same results I had with my previous one, it works but when drill stops displays the last RPM untill drill starts again or is reset

Code: [Select]
#include <LiquidCrystal.h>
int sensorPin = 2; //hall effect
int counter = 0;
int ledpin = 13;
float start, finished;
float elapsed, time;
float revs;
float revolution;
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
/*
* LCD RS pin to digital pin 8
* LCD Enable pin to digital pin 9
* LCD D0 pin to digital pin 1
* LCD D1 pin to digital pin 10
* LCD D2 pin to digital pin 11
* LCD D3 pin to digital pin 12
* LCD D4 pin to digital pin 4
* LCD D5 pin to digital pin 5
* LCD D6 pin to digital pin 6
* LCD D7 pin to digital pin 7
* LCD R/W pin to ground
* 10K resistor:
* ends to +5V and ground
* wiper to LCD VO pin (pin 3)
*/
void setup()
{
  //setup lcd
  lcd.begin(16, 2);
  lcd.print("  DRILL  SPEED");
  lcd.setCursor(9, 1);
  lcd.print(" RPM");
 
  // setup serial - diagnostics - port
  Serial.begin(115200);
  // setup pins
  pinMode(sensorPin, INPUT);
  pinMode(ledpin, OUTPUT);
  // setup interript
  attachInterrupt(0, RPM, RISING);
  start=millis();
 
}

void RPM()
{
  elapsed=millis()-start;
  start=millis();
}

void loop()
{
  int sensorValue = digitalRead(sensorPin);
 
  float revs = 60000/elapsed;
  float revolution = elapsed/1000;
  Serial.print(elapsed);
  Serial.print(" mS ");
  Serial.print(revolution);
  Serial.print(" SEC ");
  Serial.print(revs);
  Serial.print(" RPM ");
  Serial.print(millis() );
  Serial.print(" ");
  Serial.println(start);
  lcd.setCursor(3, 1);
  if (elapsed < 1200) {lcd.print(revs,0);}
    else {lcd.print(" 0    ");}
  if (sensorValue == 0) {digitalWrite (ledpin, HIGH);}
  else {digitalWrite(ledpin, LOW);  }
 
}
 
 

3dprinter

It is ok to ask, to experiment and to learn - it is what we do with the Arduino. However, ISR programming is not a beginners task. There are many things wrong with this code

ISR "flaws"
1: Variables used by the ISR must be declared volatile
2: when accessing ISR modified variables, interrupts should be disabled.
Other flaws:
3: your resolution is millisec as you use the millis timer, microsecs would be better.
4: float revs = 60000/elapsed does not work the way you intended - it will an intereger result which then is converted to float. Use float revs = 60000.0/elapsed
Then there is the logic flow. You are working out the speed in every interrupt and then echoing the result in the main loop. So when the drill stops, there is no way it ever will be interrupted to work out the speed is zero ... it just will never happen.

I suggest you just increment a counter in the ISR, and then in the loop with the help of microseconds work out how many counts you got in the interval between loops, to get your speed. That will fix the "zero" problem, but your measurment will be uncertain by one count.

wildbill

You're basing your RPM calc off the elapsed time for a single rotation, recalculating each time. Consequently, when there's no rotation, there's no interrupt and so start and elapsed are never touched. Hence the  RPM reading when the drill is off will read whatever it was when the last interrupt fired.

I suggest that you revisit the method you used simply counting interrupts - make sure you mark the count variable volatile. Manage the time measurement in loop and do your RPM calc every second, resetting the counter when you do. Then the stopped case should read correctly. Change the calc interval to get smoother or more accurate results, whichever you prefer. Don't forget though that if you change the LCD too often, you won't be able to read it anyway.

mayfield

Hello again and many thanks for the help,  I feel I have had a productive day, I've rewritten the sketch again so here is a newer and improved version from earlier.  The LCD now displays "0" when drill is not running and updates every 1 sec, appears from initial testing to be reasonably accurate but I suspect that this sketch is accurate to +/-60RPM

Code: [Select]

#include <LiquidCrystal.h>
int sensorPin = 2; //hall effect
int ledpin = 13;
float revs;
float rpm;
volatile byte rpmcount;
long previousmicros = 0;
long interval = 1000000;
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
/*
* LCD RS pin to digital pin 8
* LCD Enable pin to digital pin 9
* LCD D0 pin to digital pin 1
* LCD D1 pin to digital pin 10
* LCD D2 pin to digital pin 11
* LCD D3 pin to digital pin 12
* LCD D4 pin to digital pin 4
* LCD D5 pin to digital pin 5
* LCD D6 pin to digital pin 6
* LCD D7 pin to digital pin 7
* LCD R/W pin to ground
* 10K resistor:
* ends to +5V and ground
* wiper to LCD VO pin (pin 3)
*/

void setup()
{
  // setup serial - diagnostics - port
  Serial.begin(115200);
  // setup pins
  pinMode(sensorPin, INPUT);
  pinMode(ledpin, OUTPUT);
  // setup interript
  attachInterrupt(0, RPM, RISING);

}

void RPM()
{
  rpmcount++;
}

void loop()
{
  unsigned long currentmicros = micros();
  int sensorValue = digitalRead(sensorPin);
  if (currentmicros - previousmicros > interval)  {
    previousmicros = currentmicros;

    detachInterrupt(0);
    revs=1000000.0/rpmcount;
    rpm = 60000000.0/revs;
    Serial.print("rpmcount\t");
    Serial.print(rpmcount);
    Serial.print(" rpm\t");
    Serial.println(rpm);
    lcd.clear();
    lcd.begin(16, 2);
    lcd.print("  DRILL  SPEED");
    lcd.setCursor(9, 1);
    lcd.print(" RPM");
    lcd.setCursor(3,1);
    lcd.print(rpm,0);
    rpmcount=0;
    attachInterrupt(0, RPM, RISING);
  }

  if (sensorValue == 0) {
    digitalWrite (ledpin, HIGH);
  }
  else {
    digitalWrite(ledpin, LOW); 
  }

}


wildbill

You could make it more accurate by calculating the elapsed time, rather than assuming it will be 1000000. Also, rather than detatching the interrupt, just disable them briefly(NoInterrupts) while you copy the rpmcount variable to a temporary one. Then you can keep count while you do all that slow serial stuff.

Actually, since rpmcount is a byte, it may not even be necessary to disable interrupts at all.

3dprinter

When writing integer constants grater than 2^15, it is prudent to write it with the trailing L or UL. So your interval should be = 1000000L.  See Integer Constants Also by declaring it const the compiler may be able to elliminate RAM usage. It does not matter in s small a sketch, but a good habit to use.
Code: [Select]
const int sensorPin = 2; //hall effect
const int ledpin = 13;
float revs;
float rpm;
volatile byte rpmcount;
long previousmicros = 0;
const long interval = 1000000;


I am puzzled why you think you are accurate +/- 60rpm. If your reported rpm jumps in increments of 60, then there is a truncating/rounding error somewhere. You may get the wrong result due to the interval constant problem.

wildbill's comment about the interval is correct but will only be inaccuarte of the order of the two lines of code before the "If" - which I guestimate about 5 uS. Considering the microseconds() is only accurate to 4uS increments ... it does not matter.

But much more important is you do not count while updating the LCD. That will make a great inaccuracy. You only need the interrupts()/noInterrupts() brackets while "reading" the rpmcount, but as wildbill correctly points out - if it is a byte it is superflous. BUT - is a byte enough? how many pulses do you get in one seconds at 6000rpm (or whatever)?

Perhaps you have some other rotating device where you know the rpm you can calibrate against?

estebanl

 Hello everyone,

I'm new with this, and wanna give it a try.


What does this part of the code do:


rpm = 30*1000/(millis() - timeold)*rpmcount;

shashidhar0605

Check this out. Please let me know if there is any mistake with this code

int hallsensor = 2;                // Hall sensor at pin 2
volatile byte counter;
unsigned int rpm;
unsigned long passedtime;
void isr()
 {

   //calculating average RPM

   //Update count
      counter++;
      passedtime = millis();
      rpm= counter*60000/passedtime;
 }
void setup()
 {Serial.begin(9600);
   //Intiates Serial communications
   attachInterrupt(0, isr, RISING); //Interrupts are called on Rise of Input
   pinMode(hallsensor, INPUT); //Sets hallsensor as input
   counter = 0;
   rpm = 0;
   passedtime = 0; //Initialise the values

 }
 void loop()
 {
   attachInterrupt(0, isr, RISING);   //Restart the interrupt processing
   Serial.print("RPM=");
   Serial.println(rpm); //Print out result to monitor
   
   
  }

Johnny010

I think using a hardware counter in this case would be beneficial and also a worth while learning exercise.
Having an interrupt fire every time is a bit of a "waste" in my eyes. I know it is something small, but why do this when the hardware comes with a counter that can literally do the counting for you without any need for interrupts.

Depending on the RPM you may be after, say 8000rpm = 133.33* revs per second...
Then an 8 bit timer could be put in counting mode.

A second timer could be set to trigger an ISR every say 1000ms that fetches the value of the counter and pops it in a global/volatile variable and resets the timer/counter to 0.

In your main() you can then just use the global variable that is the rev/s.

GoForSmoke

At 6000 rpm you get 1 rotation every 10ms. I don't need an interrupt to catch 2 events per rotation at twice that speed.
You don't need interrupts, you just need fast non-blocking code.

You can tell how fast the drill is turning by how long the magnet is sensed, not perfectly but enough to tell if the thing is changing speed + or -.

I would recalculate 2 or 4 times a second to not have the display change faster than eyes can take in easily. If the speed is less than 1, don't show fractions just show 0.
1) http://gammon.com.au/blink  <-- tasking Arduino 1-2-3
2) http://gammon.com.au/serial <-- techniques howto
3) http://gammon.com.au/interrupts
Your sketch can sense ongoing process events in time.
Your sketch can make events to control it over time.

Robin2

This is derived from the code I use to monitor the speed of a small DC motor using an optical detector that produces one pulse per revolution. It's not complete but it should illustrate the idea

Code: [Select]
volatile unsigned long isrMicros;
unsigned long latestIsrMicros;
unsigned long previousIsrMicros;
volatile boolean newISR = false;


void loop() {
   if (newISR == true) {
     previousIsrMicros = latestIsrMicros; // save the old value
     noInterrupts(); // pause interrupts while we get the new value
        latestIsrMicros = isrMicros;
        newISR = false;
     interrupts();
     microsThisRev = latestIsrMicros - previousIsrMicos;
}

void myISR() {
   isrMicros = micros();
   newISR = true;
}


...R
Two or three hours spent thinking and reading documentation solves most programming problems.

Go Up