I am trying to measure the speed of a bicycle wheel using a hall effect sensor. I am using the code below and get the RPM which I then convert into m/s. However, I want the measurement to be more accurate. Using my code the RPM changes by 60 which is roughly 2 m/s. I want a more accurate speed measurements with not such big intervals between the speed changes. If I place 6 magnets on the wheel instead of 1 will I get more accurate speed measurements? And will I need to change my rpm expression in the code?
volatile float rpmcount = 0;
float rpm = 0;
float radial_vel = 0;
float linear_vel = 0;
unsigned long lastmillis = 0;
void setup()
{
Serial.begin(9600);
attachInterrupt(0, rpm_bike, FALLING); //interrupt zero (0) is on pin two(2).
}
void loop()
{
if (millis() - lastmillis == 1000)
{ /*Uptade every one second, this will be equal to reading frecuency (Hz).*/
detachInterrupt(0); //Disable interrupt when calculating.
rpm = rpmcount * 60; //Convert frecuency to RPM, note: this works for one interruption per full rotation. For two interrups per full rotation use rpmcount * 30.
radial_vel = rpm * M_PI / 30; //convert rpm to radial velocity in rad/s.
linear_vel = radial_vel * 0.33; //convert radial velocity to linear velocity in m/s.
Serial.print("RPM = "); //print the word "RPM".
Serial.print(rpm); // print the rpm value.
Serial.print("\t\t Linear Speed = "); //print the word "Linear Velocity".
Serial.print(linear_vel); //print the linear velocity value.
Serial.println(" m/s"); //print the word "m/s".
rpmcount = 0; // Restart the RPM counter
lastmillis = millis(); // Update lasmillis
attachInterrupt(0, rpm_bike, FALLING); //enable interrupt
}
}
void rpm_bike()
{ /* this code will be executed every time the interrupt 0 (pin2) gets low.*/
rpmcount++;
}
I am trying to measure the speed of a bicycle wheel using a hall effect sensor. I am using the code below and get the RPM which I then convert into m/s. However, I want the measurement to be more accurate. Using my code the RPM changes by 60 which is roughly 2 m/s. I want a more accurate speed measurements with not such big intervals between the speed changes. If I place 6 magnets on the wheel instead of 1 will I get more accurate speed measurements? And will I need to change my rpm expression in the code?
volatile float rpmcount = 0;
float rpm = 0;
float radial_vel = 0;
float linear_vel = 0;
unsigned long lastmillis = 0;
void setup()
{
Serial.begin(9600);
attachInterrupt(0, rpm_bike, FALLING); //interrupt zero (0) is on pin two(2).
}
void loop()
{
if (millis() - lastmillis == 1000)
{ /*Uptade every one second, this will be equal to reading frecuency (Hz).*/
detachInterrupt(0); //Disable interrupt when calculating.
rpm = rpmcount * 60; //Convert frecuency to RPM, note: this works for one interruption per full rotation. For two interrups per full rotation use rpmcount * 30.
radial_vel = rpm * M_PI / 30; //convert rpm to radial velocity in rad/s.
linear_vel = radial_vel * 0.33; //convert radial velocity to linear velocity in m/s.
Serial.print("RPM = "); //print the word "RPM".
Serial.print(rpm); // print the rpm value.
Serial.print("\t\t Linear Speed = "); //print the word "Linear Velocity".
Serial.print(linear_vel); //print the linear velocity value.
Serial.println(" m/s"); //print the word "m/s".
rpmcount = 0; // Restart the RPM counter
lastmillis = millis(); // Update lasmillis
attachInterrupt(0, rpm_bike, FALLING); //enable interrupt
}
}
void rpm_bike()
{ /* this code will be executed every time the interrupt 0 (pin2) gets low.*/
rpmcount++;
}
For higher accuracy monitor the time between pulses rather than counting pulses in a fixed time period. For more frequent updates, install more evenly-spaced sensed elements on the wheel.
For higher accuracy monitor the time between pulses rather than counting pulses in a fixed time period. For more frequent updates, install more evenly-spaced sensed elements on the wheel.
How can I do change that in my code? Any tips would help please!
Also, I have 8 magnets now instead of one, so this will not make any better with my existing code?
Sometimes millis() increments by 2 instead of 1. Always use '>=' instead of '=='.
I'm doing the exact same type of measurement you are and I found 1 magnet is 60 rmp resolution and took my magnet count up to 6--still not good enough so....
I now get the micros() for each pulse and compute the micros() delta using that i can get very accurate rpms.
The only downside is that this code lives in the isr and will have max speed limits but no where near what a bike will produce
I also have a debounce If in the code to prevent bounced counts. Make sure your times are volatile unsigned long, and rpm volatile int (or float of your want to get even more accurate (all should be global variables).
chrismil:
will I get more accurate speed measurements?
You will get more fine-grained measurement. Currently, you are measuring the time of a complete revolution. With only one magnet, you can't tell whether there were speed changes during the course of that revolution. You are only calculating the average speed for the entire revolution. Maybe it was turning slowly for half the revolution and quickly for the other half. Whether that's a problem depends on your application. To measure the speed of a bicycle that someone is riding, I would say that it's not useful at all. However, if the bike wheel was being used for a different application, it could be useful.
chrismil:
will I need to change my rpm expression in the code?
Yes. Instead of each count increment representing one revolution, as it is now, each count increment will represent 1/6 of a revolution. Your equation will need to be modified accordingly.
The real problem is that the approach you have taken is wrong, and since you don't seem to understand it, copy a better example from somewhere else.
You can quite precisely measure the average speed the bike is moving during one revolution of the wheel.
In m/s it is (wheel circumference in meters)/(time for one revolution in seconds), where "seconds" is measured using millis() or micros() as suggested above.
Hint: don't increment a float variable in an interrupt routine. Increment an integer variable instead (byte in this case).
This is what I have come up with. I hope that this code will measure the time between 2 pulses and return the velocity. However now I have 8 magnets on the wheel. Does this seem to be working? And do I have to change anything?
Any suggestions would be helpful.
Thanks!
volatile int rpmcount = 0;
volatile int rpm = 0;
float radial_vel = 0;
float linear_vel = 0;
volatile unsigned long thetime = 0;
volatile unsigned long oldtime = 0;
volatile unsigned long delta = 0;
void setup()
{
Serial.begin(9600);
attachInterrupt(0, rpm_bike, RISING); //interrupt zero (0) is on pin two(2).
}
void rpm_bike()
{
rpmcount++;
}
void isr()
{
if (rpmcount >= 1)
{
detachInterrupt(0);
thetime = millis();
delta = thetime - oldtime;
rpm = 60000 / delta;
radial_vel = rpm * M_PI / 30; //convert rpm to radial velocity in rad/s.
linear_vel = radial_vel * 0.33; //convert radial velocity to linear velocity in m/s.
Serial.print("RPM = "); //print the word "RPM".
Serial.print(rpm); // print the rpm value.
Serial.print("\t\t Linear Speed = "); //print the word "Linear Velocity".
Serial.print(linear_vel); //print the linear velocity value.
Serial.println(" m/s"); //print the word "m/s".
oldtime = millis();
rpmcount = 0;
attachInterrupt(0, rpm_bike, RISING);
}
}
void loop()
{
rpm_bike();
isr();
}
// Configuration constants:
const byte RevSensePin = 2;
const float WheelRadiusInMeters = 0.33;
const unsigned long DisplayIntervalMillis = 1000; // Update once per second
const unsigned long MaxRevTimeMicros = 2000000UL; // >2 seconds per revolution counts as 0 RPM
// Variables used in the ISR and in the main code must be 'volatile'
volatile unsigned long RevSenseTimeMicros = 0; // Time that the rising edge was sensed
volatile unsigned long RevTimeMicros = 0; // Microseconds between consecutive pulses
// Useful constants:
const unsigned long SixtySecondsInMicros = 60000000UL;
const float WheelCircumferenceInMeters = TWO_PI * WheelRadiusInMeters;
void setup()
{
Serial.begin(9600);
pinMode(RevSensePin, INPUT);
attachInterrupt(digitalPinToInterrupt(RevSensePin), RevSenseISR, RISING);
}
void RevSenseISR()
{
static unsigned long revSensePreviousMicros = 0; // 'static' to retain value between calls
RevSenseTimeMicros = micros();
RevTimeMicros = RevSenseTimeMicros - revSensePreviousMicros; // Time for last revolution
}
void loop()
{
static unsigned previousRPM;
// Only update the display once per DisplayIntervalMillis
unsigned long currentMillis = millis();
static unsigned long previousMillis = 0;
if (currentMillis - previousMillis >= DisplayIntervalMillis)
{
previousMillis += DisplayIntervalMillis;
// With interrupts disabled, make local copies of volatile variables
// This is so the ISR can't change them while we read them
noInterrupts();
unsigned long revSenseTimeMicros = RevSenseTimeMicros; // Time that the last rising edge was sensed
unsigned long revTimeMicros = RevTimeMicros; // Microseconds between consecutive pulses
interrupts();
// Calculate RPM
unsigned newRPM;
if (micros() - revSenseTimeMicros > MaxRevTimeMicros)
newRPM = 0; // Going so slow we're essentially stopped
else
newRPM = SixtySecondsInMicros / revTimeMicros;
// No need to update the display unless the RPM value has changed
if (newRPM != previousRPM)
{
previousRPM = newRPM;
displayRPM(newRPM);
}
}
}
void displayRPM(unsigned RPM)
{
float metersPerMinute = RPM * WheelCircumferenceInMeters;
Serial.print("RPM = "); //print the word "RPM".
Serial.print(RPM); // print the rpm value.
Serial.print("\t\t Linear Speed = ");
Serial.print(metersPerMinute); //print the linear velocity value.
Serial.println(" m/s");
}
Johnwasser thank you for your response. But does the code you wrote calculate the speed by taking account the time between the pulses instead of counting pulses in a fixed time period?
I assume the linear speed is in meter / minute because there is no /60 in the equation.
But does the code you wrote calculate the speed by taking account the time between the pulses instead of counting pulses in a fixed time period?
An excellent approach to learning how to code is to carefully study a good example.
johnwasser's is a good example for you to study.
Hint: reading comments can be helpful, for example:
// Variables used in the ISR and in the main code must be 'volatile'
volatile unsigned long RevSenseTimeMicros = 0; // Time that the rising edge was sensed
volatile unsigned long RevTimeMicros = 0; // Microseconds between consecutive pulses
I read his example carefully and I saw these comments but I am not sure where in the code does the time between pulses is taken into account. I assume is in the RevSenseISR function and in the void loop it updates the speed every second. But I have 8 magnets on the wheel and for each second the sensor will pass in front of the magnets many times. So, it will just take into account the last pulse before the 1 second and give that speed?
If you have 8 magnets spaced around the wheel, you will need to take those (very likely uneven) spacings into account. I suggest to remove 7 of the magnets and make your life simpler.