Speed in KM/hr from RPM measured by Hall effect sensor

Guys,

This is more like a physics question or maybe I don't know this part in my project. I'm prepping a sketch for my motorbike's odometer/tachometer and am using Arduino, nokia 5110 LCD and Hall effect sensor to do that.

The problem is that I'm getting wrong readings with the below logic. I'm measuring the RPM near the HUB of the wheel and I'm using the radius of the wheel which is 0.300803 metres (calculated from the circumference measured from the outer edge of the inflated tyre)

Please refer to the attached diagram and excerpt from my sketch and suggest me if I'm making any physics calculative mistakes.

void setup()
{
  lcd.InitLCD();
  Wire.begin();
  RTC.begin();     // Instantiate the RTC
  attachInterrupt(0, rpm_fan, FALLING);
}

void loop()
{
  if (millis() - lastmillis >= 1000)
  { //Uptade every one second, this will be like reading frecuency

 detachInterrupt(0);

 rpm = revs * 60;  
//Speed = 2*pi*r × RPM × (60/1000) km/hr
// Here radius(r) = 0.300803 metres

 speed = (2* 3.1415926536 *0.300803 * rpm * 60)/1000; 

 // lines to print the above values
 revs = 0; 
 attachInterrupt(0, rpm_fan, FALLING);
 }
}

void rpm_fan()
{
  revs++;

}

spokedwheel.jpg

@greatidea, after 13 posts you should know:-
You really should have read the How to use this forum - please read post at the top of the index page and How to use this forum before posting.

ie Your code and any error messages should always be placed between code tags. Posting it inline as you have done makes it much harder to read or copy and paste for diagnosis.

It's still not too late to edit your post and do this. You'll make potential helpers much happier. :slight_smile:

Thanks for adding the code tags.

Why do you manually convert from circumference to radius, then have the program convert back to circumference in the calculations. Why not just enter the circumference and work from that? (Maybe I'm overlooking something.)

And I can't see anything directly wrong. Perhaps you should post all of your code, so we can also see your variable declarations etc.

Edit: Have you made "revs" 'volatile'?
Oh, and just how 'wrong' are your readings?

OldSteve:
Thanks for adding the code tags.

Why do you manually convert from circumference to radius, then have the program convert back to circumference in the calculations. Why not just enter the circumference and work from that? (Maybe I'm overlooking something.)

And I can't see anything directly wrong. Perhaps you should post all of your code, so we can also see your variable declarations etc.

Edit: Have you made "revs" 'volatile'?

Hi Oldsteve,

Directly or indirectly using the radius from the circumference yeilds the same results, however the question I have is whether measuring at different points on the wheel will produce different readings or same?

Logically speaking, there would be more(frequent) interrupts if sensor-magnet assembly is near the edge of the wheel compared to that being near the Hub of the wheel. So should I use the full radius of the wheel or only the radius from the centre to the measuring point? (pls refer previously attached diagram)

Yes, revs is volatile int.

greatidea:
Hi Oldsteve,

Directly or indirectly using the radius from the circumference yeilds the same results, however the question I have is whether measuring at different points on the wheel will produce different readings or same?

Logically speaking, there would be more(frequent) interrupts if sensor-magnet assembly is near the edge of the wheel compared to that being near the Hub of the wheel. So should I use the full radius of the wheel or only the radius from the centre to the measuring point?

Regardless of where you measure, you'll still only get one pulse per complete revolution. And each complete revolution will cover the same distance - the circumference of the wheel.
My electric bike has a similar system for odometer and speedometer, and the location of the magnet and sensor makes no difference, for the reason I just outlined.
(The outer edge of the wheel travels faster, but it has a longer distance to cover.)

You didn't answer my questions or post the complete code.

Edit: I'm not real sure it's a good idea to detach interrupts then attach interrupts each time you do a calculation though. I did some frequency measurement using interrupts a couple of weeks ago, and left the interrupts alone for my calculations without any problems. If you must, cli() and sei() might be better, but I don't think you need to. You could miss pulses.
Hopefully someone better acquainted with interrupt-handling can confirm or deny this.

The problem is in the code you didn't show. Please show the declarations for all your variables.

speed = revs * 189 * 0,036 > speed in KPH

The 189 is the circumference of your wheel in cm. No need to keep calculating this every time...

The outcome of your calculation is the same and correct, just a bit more complex than needed.
Like Oldsteve asked, how far are your measurments of?

Calculating with floats and ints can be tricky.
int speed = 4 * 2.5 will not give the result you might expect:
https://www.arduino.cc/en/Reference/Float

Like others asked: how are your variables declared? Most important..what type is the variable speed?

MorganS:
The problem is in the code you didn't show. Please show the declarations for all your variables.

Below is the code which is still in development, so it is somewhat untidy/unorganized

//      SCK  - Pin 3
// DIN/MOSI - Pin 4
//      DC   - Pin 5
//      RST  - Pin 6
//      CS   - Pin 7
//
#include "Wire.h"
#include "LCD5110_Graph.h"
#include "RTClib.h"

RTC_DS1307 RTC;
LCD5110 lcd(3,4,5,6,7);

extern uint8_t SmallFont[];
extern uint8_t TinyFont[];
extern uint8_t BigNumbers[];
extern uint8_t MediumNumbers[];

volatile int revs = 0;
int cnt = 0;
int rpm = 0;
float distance;
float speedk;
unsigned long lastmillis = 0;
int page = digitalRead(A0);

void setup()
{
   lcd.InitLCD();
   Wire.begin();
   RTC.begin();     // Instantiate the RTC
   attachInterrupt(0, rpm_fan, FALLING);
}

void loop()
{
   if (millis() - lastmillis >= 1000)
   { //Uptade every one second, this will be equal to reading frecuency

      detachInterrupt(0);//Disable interrupt when calculating
      cnt=cnt+revs;
      // Get the current time
      DateTime now = RTC.now(); 
      rpm = revs * 60;  
      speedk = (2* 3.1415926536 *0.300803 * rpm * 60)/1000;
      distance = distance + (1.89 * revs); // calculate distance covered by the wheel in metres
      
      //speedk = (1.89 * revs) metres per second
      //speedk = (1.89 * revs * 5)/18;
      
      //     Below code is for displaying on the Nokia5110 LCD:
 /*  lcd.setFont(SmallFont);
      lcd.clrScr();
      lcd.print(String(now.hour(), DEC), 0, 0);
      lcd.print(":",12, 0);
      lcd.print(String(now.minute(), DEC), 18, 0);
      lcd.print(":",30, 0);
      lcd.print(String(now.second(), DEC), 36, 0);
      lcd.print(String(now.day(), DEC), 0, 8);
      lcd.print("/",12, 8);
      lcd.print(String(now.month(), DEC),18, 8);
      lcd.print("/",30, 8);
      lcd.print(String(now.year(), DEC), 36, 8);  */
             lcd.setFont(SmallFont);
             lcd.clrScr();
      //lcd.print("Tot:", LEFT, 16);
      lcd.printNumF(cnt, 2, RIGHT, 16);
      lcd.print("Count:", LEFT, 16);
      lcd.print("RPM:", LEFT, 32);
      lcd.update();
      
      lcd.setFont(MediumNumbers);
      lcd.printNumI(speedk, LEFT, 0, 0,'0');
      lcd.update();
      
      lcd.setFont(BigNumbers);
      lcd.printNumI(rpm, RIGHT, 24, 2,'0');
      lcd.update();

      revs = 0; // Restart the RPM counter
      lastmillis = millis(); // Uptade lasmillis
      attachInterrupt(0, rpm_fan, FALLING); //enable interrupt
    
   }
}

void rpm_fan()
{
   revs++;

}

Hi Oldsteve, MorganS,

This is now working, but the readings are not granular an only going into multiples of ~60.

There seemed to be some noise in the circuit due to vibrations of the engine and it showed garbage/fake readings when the engine started, but when the wheel wasn't moving. I made it vibration proof.

The question here is how do I make the readings granular?

The speed calculation formula here is,

double speedk = (2* 3.1415926536 *0.300803 * (double)rpm * 60)/1000;

and because of the RPM value, the results in KM/h are in fixed multiples. Note that I'm just using one magnet and one hall sensor - would that matter? My readings are as below:

RPM KM/h
60 6.80
120 13.61
180 20.41
240 27.22
300 34.02
360 40.82
420 47.63
480 54.43
540 61.24
600 68.04

I wish the KM/h values can be very granular

So you're saying that your code doesn't measure any difference in RPM between 60 and 120. It just jumps by 60RPM when you go faster?

That's because you're measuring whole revolutions and you're only measuring for one second. When the bike is travelling slowly, like 1.5 revolutions per second, then sometimes it will see two counts in one second, sometimes it will see one. This is then multiplied by 60 so you will see the RPM jump between 60 and 120 randomly, even though the speed hasn't changed.

Change to measuring the time between interrupts. Have your ISR record the time that the interrupt occurred and save the time that the previous one occurred. Then you can just subtract the two times.

Also, don't use detachInterrupt(). Use noInterrupts() to temporarily disable all interrupts while you get a copy of the volatile variables. Then immediately turn interrupts back on with interrupts(). After you have taken a safe copy, you can do all the calculation and display stuff. If an interrupt occurs during the time that the interrupts are disabled then it will be stored until you re-enable interrupts. It will only store one of each type of interrupt, so don't leave them disabled for longer than a few microseconds.

MorganS:
So you're saying that your code doesn't measure any difference in RPM between 60 and 120. It just jumps by 60RPM when you go faster?

That's because you're measuring whole revolutions and you're only measuring for one second. When the bike is travelling slowly, like 1.5 revolutions per second, then sometimes it will see two counts in one second, sometimes it will see one. This is then multiplied by 60 so you will see the RPM jump between 60 and 120 randomly, even though the speed hasn't changed.

Change to measuring the time between interrupts. Have your ISR record the time that the interrupt occurred and save the time that the previous one occurred. Then you can just subtract the two times.

Also, don't use detachInterrupt(). Use noInterrupts() to temporarily disable all interrupts while you get a copy of the volatile variables. Then immediately turn interrupts back on with interrupts(). After you have taken a safe copy, you can do all the calculation and display stuff. If an interrupt occurs during the time that the interrupts are disabled then it will be stored until you re-enable interrupts. It will only store one of each type of interrupt, so don't leave them disabled for longer than a few microseconds.

I'm now using IR opto coupler and observe same results when using the hall effect sensor, but without interrupts. Please see below sketch. No matter how much I take the interval - 1 second or half sec or quater second, the final results are in multiples of 60. Please suggest any changes to below sketch if you can

int val;
long last=0;
int stat=LOW;
int curr_speed =0;
int stat2;
float speedk;
float distance=0;
int cnt=0;
int sens=75;  // this value indicates the limit reading between dark and light,  it has to be tested as it may change acording on the distance the leds are placed.
int notches=1; // the no. of notches on the wheel
int interval=250; // the time it takes each reading. Here update every quater second
double rps=0;
double rpm=0;

void setup()
{
  Serial.begin(9600);
  pinMode(13,OUTPUT);
}

void loop()
{
  val=analogRead(0);
  if(val<sens)
    stat=LOW;
   else
    stat=HIGH;
   digitalWrite(13,stat); //as IR light is invisible for us, the led on pin 13 

   if(stat2!=stat)
   {  //counts when the state change, from dark to light or from (light to dark)
     cnt++;
     stat2=stat;
   }
   if(millis()-last >= interval) //update every quater second
   {
     double rps=((double)cnt/notches)/2.0*1000.0/interval;
     double rpm=((double)cnt/notches)/2.0*60000.0/(interval);
     double speedk = (2* 3.1415926536 *0.300803 * (double)rpm * 60)/1000;
      distance = distance + (cnt * 0.300803);
     
     //Serial.print((cnt/2.0));

     Serial.println("Dist(mtrs) ");Serial.print("Cnt ");Serial.print("  RPS ");Serial.print("   RPM ");Serial.print("   Speed KM/h ");
     Serial.println(distance);Serial.print(" ");Serial.print(cnt); Serial.print("    "); Serial.print(rps); Serial.print("    "); Serial.print(rpm);Serial.print("    "); Serial.print(speedk);
    
     cnt=0;
     last=millis();
   }
}

@greatidea, it's because your approach is fundamentally flawed. The count is by nature, an integer. At low speeds, the division count/interval truncates the result. That is where your inaccuracy is coming from. But if you measure the time between pulses, that problem goes away because you can measure almost to the microsecond, your speed becomes c/interval, where c is your conversion constant. The accuracy won't vary in the same way with speed.

Think about it.

aarg:
@greatidea, it's because your approach is fundamentally flawed. The count is by nature, an integer. At low speeds, the division count/interval truncates the result. That is where your inaccuracy is coming from. But if you measure the time between pulses, that problem goes away because you can measure almost to the microsecond, your speed becomes c/interval, where c is your conversion constant. The accuracy won't vary in the same way with speed.

Think about it.

Hi aarg,

You are right that cnt(count) is a multiplier in the final result, but even if I make cnt as float, the values of cnt will never be between two integers, as here I'm only using one pair of magnet and hall sensor, which essentially means 1 count per revolution, so I have to increment cnt by 1 every time

hello @greatidea have an anemometer with IR but I need arduino data are read in labview but I do not go
Might help thanks .....

greatidea@ can you explain me how to make the vibration proof?
I have same problems with you. Thanks a lot